完善了权限管理功能

This commit is contained in:
ap 2021-02-24 23:34:05 +08:00
parent 832dd68f30
commit 046940e2e8
19 changed files with 450 additions and 82 deletions

View File

@ -3,7 +3,8 @@
"prefix": "middleware",
"body": [
"import { Provide } from '@midwayjs/decorator';",
"import { IWebMiddleware, IMidwayWebNext, IMidwayWebContext } from '@midwayjs/web';",
"import { IWebMiddleware, IMidwayWebNext } from '@midwayjs/web';",
"import { Context } from 'egg';",
"",
"/**",
" * 描述",
@ -12,7 +13,7 @@
"export class XxxMiddleware implements IWebMiddleware {",
"",
" resolve() {",
" return async (ctx: IMidwayWebContext, next: IMidwayWebNext) => {",
" return async (ctx: Context, next: IMidwayWebNext) => {",
" // 控制器前执行的逻辑",
" const startTime = Date.now();",
" // 执行下一个 Web 中间件,最后执行到控制器",

View File

@ -1,32 +1,31 @@
import { Provide } from '@midwayjs/decorator';
import * as ipdb from 'ipip-ipdb';
import * as _ from 'lodash';
import { Context } from 'egg';
/**
*
*/
export default class Helper {
/**
* IP
*/
public static async getReqIP() {
// @ts-ignore
const req = this.ctx.req;
return (req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP
req.connection.remoteAddress || // 判断 connection 的远程 IP
req.socket.remoteAddress || // 判断后端的 socket 的 IP
req.connection.socket.remoteAddress).replace('::ffff:', '');
@Provide()
export class Helper {
/**
* IP
*/
public async getReqIP(ctx: Context) {
const req = ctx.req;
return req.headers['x-forwarded-for'] || req.socket.remoteAddress
}
/**
* IP获得请求地址
* @param ip IP地址
*/
public static async getIpAddr(ip?: string) {
public async getIpAddr(ctx: Context, ip?: string | string[]) {
try {
if (!ip) {
ip = await this.getReqIP();
ip = await this.getReqIP(ctx);
}
const bst = new ipdb.BaseStation('app/resource/ipip/ipipfree.ipdb');
const bst = new ipdb.BaseStation('./ipipfree.ipdb');
const result = bst.findInfo(ip, 'CN');
const addArr: any = [];
if (result) {
@ -35,7 +34,7 @@ export default class Helper {
addArr.push(result.cityName);
return _.uniq(addArr).join('');
}
// @ts-ignore
// @ts-ignore
} catch (err) {
return '无法获取地址信息';
}
@ -45,7 +44,7 @@ export default class Helper {
*
* @param obj
*/
public static async removeEmptyP (obj) {
public async removeEmptyP(obj) {
Object.keys(obj).forEach(key => {
if (obj[key] === null || obj[key] === '' || obj[key] === 'undefined') {
delete obj[key];
@ -57,7 +56,7 @@ export default class Helper {
* 线
* @param ms
*/
public static sleep(ms) {
public sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

View File

@ -11,6 +11,6 @@ export default (app: Application) => {
// 模块描述
describe: '基础的权限管理功能,包括登录,权限校验',
// 中间件
middlewares: ['baseLogsMiddleware'],
middlewares: ['baseLogMiddleware', 'baseAuthorityMiddleware'],
} as ModuleConfig;
};

View File

@ -1,7 +1,8 @@
import { Body, Provide, ALL, Inject, Post, Get, Query } from '@midwayjs/decorator';
import { Provide, Inject, Get } from '@midwayjs/decorator';
import { Context } from 'egg';
import { CoolController, BaseController } from 'midwayjs-cool-core';
import { LoginDTO } from '../../dto/login';
import { BaseSysLoginService } from '../../service/sys/login';
import { BaseSysPermsService } from '../../service/sys/perms';
import { BaseSysUserService } from '../../service/sys/user';
/**
* Base
@ -11,23 +12,28 @@ import { BaseSysLoginService } from '../../service/sys/login';
export class BaseCommController extends BaseController {
@Inject()
baseSysLoginService: BaseSysLoginService;
baseSysUserService: BaseSysUserService;
@Inject()
baseSysPermsService: BaseSysPermsService;
@Inject()
ctx: Context;
/**
*
* @param login
*
*/
@Post('/login')
async login(@Body(ALL) login: LoginDTO) {
return this.ok(await this.baseSysLoginService.login(login))
@Get('/person')
public async person() {
return this.ok(await this.baseSysUserService.person());
}
/**
*
*
*/
@Get('/captcha')
async captcha(@Query() type: string, @Query() width: number, @Query() height: number) {
return this.ok(await this.baseSysLoginService.captcha(type, width, height));
@Get('/permmenu')
public async permmenu() {
return this.ok(await this.baseSysPermsService.permmenu(this.ctx.admin.roleIds));
}
}

View File

@ -0,0 +1,40 @@
import { Provide, Body, ALL, Inject, Post, Get, Query } from '@midwayjs/decorator';
import { CoolController, BaseController } from 'midwayjs-cool-core';
import { LoginDTO } from '../../dto/login';
import { BaseSysLoginService } from '../../service/sys/login';
/**
*
*/
@Provide()
@CoolController()
export class BaseSysOpenController extends BaseController {
@Inject()
baseSysLoginService: BaseSysLoginService;
/**
*
* @param login
*/
@Post('/login')
async login(@Body(ALL) login: LoginDTO) {
return this.ok(await this.baseSysLoginService.login(login))
}
/**
*
*/
@Get('/captcha')
async captcha(@Query() type: string, @Query() width: number, @Query() height: number) {
return this.ok(await this.baseSysLoginService.captcha(type, width, height));
}
/**
* token
*/
@Get('/refreshToken')
async refreshToken(@Query() refreshToken: string) {
return this.ok(await this.baseSysLoginService.refreshToken(refreshToken))
}
}

View File

@ -0,0 +1,29 @@
import { ALL, Body, Inject, Post, Provide } from '@midwayjs/decorator';
import { CoolController, BaseController } from 'midwayjs-cool-core';
import { BaseSysDepartmentEntity } from '../../../entity/sys/department';
import { BaseSysDepartmentService } from '../../../service/sys/department';
/**
*
*/
@Provide()
@CoolController({
api: ['add', 'delete', 'update', 'list'],
entity: BaseSysDepartmentEntity,
service: BaseSysDepartmentService
})
export class BaseDepartmentController extends BaseController {
@Inject()
baseDepartmentService: BaseSysDepartmentService;
/**
*
*/
@Post('/order')
async order(@Body(ALL) params: Object) {
await this.baseDepartmentService.order(params);
this.ok();
}
}

View File

@ -0,0 +1,20 @@
import { Inject, Provide } from '@midwayjs/decorator';
import { CoolController, BaseController } from 'midwayjs-cool-core';
import { BaseSysMenuEntity } from '../../../entity/sys/menu';
import { BaseSysMenuService } from '../../../service/sys/menu';
/**
*
*/
@Provide()
@CoolController({
api: ['add', 'delete', 'update', 'list', 'page'],
entity: BaseSysMenuEntity,
service: BaseSysMenuService
})
export class BaseSysMenuController extends BaseController {
@Inject()
baseSysMenuService: BaseSysMenuService;
}

View File

@ -26,4 +26,8 @@ export class BaseSysLogEntity extends BaseEntity {
@Column({ comment: '参数', nullable: true, type: 'text' })
params: string;
@Column({ comment: '响应毫秒数, 值为-1则响应不成功', default: -1 })
ms: number;
}

View File

@ -0,0 +1,105 @@
import { Config, Inject, Provide } from '@midwayjs/decorator';
import { IWebMiddleware, IMidwayWebNext } from '@midwayjs/web';
import * as _ from 'lodash';
import { CoolCache, CoolConfig, RESCODE } from 'midwayjs-cool-core';
import * as jwt from 'jsonwebtoken';
import { Context } from 'egg';
/**
*
*/
@Provide()
export class BaseAuthorityMiddleware implements IWebMiddleware {
@Config('cool')
coolConfig: CoolConfig;
@Inject('cool:cache')
coolCache: CoolCache;
resolve() {
return async (ctx: Context, next: IMidwayWebNext) => {
let statusCode = 200;
const { url } = ctx;
const token = ctx.get('Authorization');
let { prefix } = this.coolConfig.router;
const adminUrl = prefix ? `${prefix}/admin/` : '/admin/';
// 只要登录每个人都有权限的接口
const commUrl = prefix ? `${prefix}/admin/comm/` : '/admin/comm/';
// 不需要登录的接口
const openUrl = prefix ? `${prefix}/admin/open/` : '/admin/open/';
// 路由地址为 admin前缀的 需要权限校验
if (_.startsWith(url, adminUrl)) {
try {
ctx.admin = jwt.verify(token, this.coolConfig.jwt.secret);
} catch (err) { }
// comm 不需要登录 无需权限校验
if (_.startsWith(url, openUrl)) {
await next();
return;
}
if (ctx.admin) {
if (_.startsWith(url, commUrl)) {
await next();
return;
}
// 如果传的token是refreshToken则校验失败
if (ctx.admin.isRefresh) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
return;
}
// 判断密码版本是否正确
const passwordV = await this.coolCache.get(`admin:passwordVersion:${ctx.admin.userId}`);
if (passwordV !== ctx.admin.passwordVersion) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
return;
}
const rToken = await this.coolCache.get(`admin:token:${ctx.admin.userId}`);
if (!rToken) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效或无权限访问~',
};
return;
}
if (rToken !== token && this.coolConfig.sso) {
statusCode = 401;
} else {
let perms = await this.coolCache.get(`admin:perms:${ctx.admin.userId}`);
if (!_.isEmpty(perms)) {
perms = JSON.parse(perms).map(e => {
return e.replace(/:/g, '/');
});
if (!perms.includes(url.split('?')[0].replace('/admin/', ''))) {
statusCode = 403;
}
} else {
statusCode = 403;
}
}
} else {
statusCode = 401;
}
if (statusCode > 200) {
ctx.status = statusCode;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效或无权限访问~',
};
return;
}
}
await next();
};
}
}

View File

@ -1,21 +1,21 @@
import { Provide } from '@midwayjs/decorator';
import { IWebMiddleware, IMidwayWebNext, IMidwayWebContext } from '@midwayjs/web';
import { Inject, Provide } from '@midwayjs/decorator';
import { IWebMiddleware, IMidwayWebNext } from '@midwayjs/web';
import { BaseSysLogService } from '../service/sys/log';
import { Context } from 'egg';
/**
*
*/
@Provide()
export class BaseLogsMiddleware implements IWebMiddleware {
export class BaseLogMiddleware implements IWebMiddleware {
@Inject()
baseSysLogService: BaseSysLogService;
resolve() {
return async (ctx: IMidwayWebContext, next: IMidwayWebNext) => {
console.log('日志')
// 控制器前执行的逻辑
const startTime = Date.now();
// 执行下一个 Web 中间件,最后执行到控制器
return async (ctx: Context, next: IMidwayWebNext) => {
this.baseSysLogService.record(ctx.url.split('?')[0], ctx.req.method === 'GET' ? ctx.request.query : ctx.request.body, ctx.admin ? ctx.admin.userId : null);
await next();
// 控制器之后执行的逻辑
console.log(Date.now() - startTime);
};
}

View File

@ -1,10 +1,12 @@
import { Provide } from '@midwayjs/decorator';
import { Inject, Provide } from '@midwayjs/decorator';
import { BaseService } from 'midwayjs-cool-core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Repository } from 'typeorm';
import { BaseSysDepartmentEntity } from '../../entity/sys/department';
import * as _ from 'lodash';
import { BaseSysRoleDepartmentEntity } from '../../entity/sys/role_department';
import { Context } from 'egg';
import { BaseSysPermsService } from './perms';
/**
*
@ -18,6 +20,42 @@ export class BaseSysDepartmentService extends BaseService {
@InjectEntityModel(BaseSysRoleDepartmentEntity)
baseSysRoleDepartmentEntity: Repository<BaseSysRoleDepartmentEntity>;
@Inject()
baseSysPremsService: BaseSysPermsService;
@Inject()
ctx: Context;
/**
*
*/
async list () {
const permsDepartmentArr = await this.baseSysPremsService.departmentIds(this.ctx.admin.userId); // 部门权限
const departments = await this.nativeQuery(`
SELECT
*
FROM
base_sys_department a
WHERE
1 = 1
${ this.setSql(this.ctx.decoded.userId !== '1', 'and a.id in (?)', [ !_.isEmpty(permsDepartmentArr) ? permsDepartmentArr : [ null ] ]) }
ORDER BY
a.orderNum ASC`);
if (!_.isEmpty(departments)) {
departments.forEach(e => {
const parentMenu = departments.filter(m => {
if (e.parentId === m.id) {
return m.name;
}
});
if (!_.isEmpty(parentMenu)) {
e.parentName = parentMenu[0].name;
}
});
}
return departments;
}
/**
* ID获得部门权限信息
* @param {[]} roleIds
@ -40,4 +78,31 @@ export class BaseSysDepartmentService extends BaseService {
}
return [];
}
/**
*
* @param params
*/
async order (params) {
for (const e of params) {
await this.baseSysDepartmentEntity.update(e.id, e);
}
}
/**
*
*/
async delete (ids: number[]) {
let { deleteUser } = this.ctx.request.body;
await this.baseSysDepartmentEntity.delete(ids);
if (deleteUser) {
await this.nativeQuery('delete from base_sys_user where departmentId in (?)', [ ids ]);
} else {
const topDepartment = await this.baseSysDepartmentEntity.createQueryBuilder().where('parentId is null').getOne();
if (topDepartment) {
await this.nativeQuery('update base_sys_user a set a.departmentId = ? where a.departmentId in (?)', [ topDepartment.id, ids ]);
}
}
}
}

View File

@ -6,6 +6,7 @@ import { Context } from 'egg';
import * as _ from 'lodash';
import { BaseSysLogEntity } from '../../entity/sys/log';
import * as moment from 'moment';
import { Helper } from '../../../../extend/helper';
/**
*
@ -16,6 +17,9 @@ export class BaseSysLogService extends BaseService {
@Inject()
ctx: Context;
// @Inject()
// helper: Helper;
@InjectEntityModel(BaseSysLogEntity)
baseSysLogEntity: Repository<BaseSysLogEntity>;
@ -26,18 +30,18 @@ export class BaseSysLogService extends BaseService {
* @param userId ID
*/
async record(url, params, userId) {
const ip = await this.ctx.helper.getReqIP();
const sysLog = new BaseSysLogEntity();
sysLog.userId = userId;
sysLog.ip = ip;
const ipAddrArr = new Array();
for (const e of ip.split(',')) ipAddrArr.push(await this.ctx.helper.getIpAddr(e));
sysLog.ipAddr = ipAddrArr.join(',');
sysLog.action = url;
if (!_.isEmpty(params)) {
sysLog.params = JSON.stringify(params);
}
await this.baseSysLogEntity.insert(sysLog);
// const ip = await this.helper.getReqIP(this.ctx);
// const sysLog = new BaseSysLogEntity();
// sysLog.userId = userId;
// sysLog.ip = ip;
// const ipAddrArr = new Array();
// for (const e of ip.split(',')) ipAddrArr.push(await this.ctx.helper.getIpAddr(e));
// sysLog.ipAddr = ipAddrArr.join(',');
// sysLog.action = url;
// if (!_.isEmpty(params)) {
// sysLog.params = JSON.stringify(params);
// }
// await this.baseSysLogEntity.insert(sysLog);
}
/**

View File

@ -1,5 +1,5 @@
import { Inject, Provide, Config } from '@midwayjs/decorator';
import { BaseService, CoolCache, CoolCommException } from 'midwayjs-cool-core';
import { BaseService, CoolCache, CoolCommException, CoolConfig, RESCODE } from 'midwayjs-cool-core';
import { LoginDTO } from '../../dto/login';
import * as svgCaptcha from 'svg-captcha';
import * as svgToDataURL from 'svg-to-dataurl';
@ -13,6 +13,7 @@ import * as _ from 'lodash';
import { BaseSysMenuService } from './menu';
import { BaseSysDepartmentService } from './department';
import * as jwt from 'jsonwebtoken';
import { Context } from 'egg';
/**
*
@ -24,7 +25,7 @@ export class BaseSysLoginService extends BaseService {
coolCache: CoolCache;
@InjectEntityModel(BaseSysUserEntity)
baseSysLogEntity: Repository<BaseSysUserEntity>;
baseSysUserEntity: Repository<BaseSysUserEntity>;
@Inject()
baseSysRoleService: BaseSysRoleService;
@ -35,8 +36,11 @@ export class BaseSysLoginService extends BaseService {
@Inject()
baseSysDepartmentService: BaseSysDepartmentService;
@Inject()
ctx: Context;
@Config('cool')
coolConfig;
coolConfig: CoolConfig;
/**
*
@ -46,7 +50,7 @@ export class BaseSysLoginService extends BaseService {
const { username, captchaId, verifyCode, password } = login;
const checkV = await this.captchaCheck(captchaId, verifyCode);
if (checkV) {
const user = await this.baseSysLogEntity.findOne({ username });
const user = await this.baseSysUserEntity.findOne({ username });
if (user) {
if (user.status === 0 || user.password !== md5(password)) {
throw new CoolCommException('账户或密码不正确~');
@ -59,12 +63,12 @@ export class BaseSysLoginService extends BaseService {
throw new CoolCommException('该用户未设置任何角色,无法登录~');
}
const { expire, refreshExpire } = this.coolConfig.token.jwt;
const { expire, refreshExpire } = this.coolConfig.jwt.token;
const result = {
expire,
token: await this.generateToken(user, roleIds, expire),
refreshExpire,
refreshToken: await this.generateToken(user, roleIds, refreshExpire),
refreshToken: await this.generateToken(user, roleIds, refreshExpire, true),
};
const perms = await this.baseSysMenuService.getPerms(roleIds);
@ -111,11 +115,11 @@ export class BaseSysLoginService extends BaseService {
}
/**
*
* @param captchaId ID
* @param value
*/
public async captchaCheck(captchaId, value) {
*
* @param captchaId ID
* @param value
*/
async captchaCheck(captchaId, value) {
const rv = await this.coolCache.get(`verify:img:${captchaId}`);
if (!rv || !value || value.toLowerCase() !== rv) {
return false;
@ -137,16 +141,40 @@ export class BaseSysLoginService extends BaseService {
const tokenInfo = {
isRefresh: false,
roleIds,
username: user.username,
userId: user.id,
passwordVersion: user.passwordV,
};
if (isRefresh) {
delete tokenInfo.roleIds;
tokenInfo.isRefresh = true;
}
return jwt.sign(tokenInfo,
this.coolConfig.token.jwt.secret, {
this.coolConfig.jwt.secret, {
expiresIn: expire,
});
}
/**
* token
* @param token
*/
async refreshToken(token: string) {
try {
const decoded = jwt.verify(token, this.coolConfig.jwt.secret);
if (decoded && decoded['isRefresh']) {
return jwt.sign(decoded,
this.coolConfig.jwt.secret, {
expiresIn: this.coolConfig.jwt.token.expire,
});
}
} catch (err) {
this.ctx.status = 401;
this.ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
return;
}
}
}

View File

@ -22,7 +22,7 @@ export class BaseSysMenuService extends BaseService {
*
*/
async list() {
const menus = await this.getMenus(this.ctx.admin.roleIds);
const menus = await this.getMenus(this.ctx.admin.roleIds, this.ctx.admin.username === 'admin');
if (!_.isEmpty(menus)) {
menus.forEach(e => {
const parentMenu = menus.filter(m => {
@ -46,8 +46,8 @@ export class BaseSysMenuService extends BaseService {
let perms = [];
if (!_.isEmpty(roleIds)) {
const result = await this.nativeQuery(`
SELECT a.perms FROM sys_menu a ${this.setSql(!roleIds.includes('1'),
'JOIN sys_role_menu b on a.id = b.menuId AND b.roleId in (?)', [roleIds])}
SELECT a.perms FROM base_sys_menu a ${this.setSql(!roleIds.includes('1'),
'JOIN base_sys_role_menu b on a.id = b.menuId AND b.roleId in (?)', [roleIds])}
where 1=1 and a.perms is not NULL
`, [roleIds]);
if (result) {
@ -68,14 +68,15 @@ export class BaseSysMenuService extends BaseService {
/**
*
* @param roleIds
* @param isAdmin
*/
async getMenus(roleIds) {
async getMenus(roleIds, isAdmin) {
return await this.nativeQuery(`
SELECT
a.*
FROM
sys_menu a
${this.setSql(!roleIds.includes('1'), 'JOIN sys_role_menu b on a.id = b.menuId AND b.roleId in (?)', [roleIds])}
base_sys_menu a
${this.setSql(!isAdmin, 'JOIN base_sys_role_menu b on a.id = b.menuId AND b.roleId in (?)', [roleIds])}
GROUP BY a.id
ORDER BY
orderNum ASC`);
@ -122,7 +123,7 @@ export class BaseSysMenuService extends BaseService {
* @param menuId
*/
async refreshPerms(menuId) {
const users = await this.nativeQuery('select b.userId from sys_role_menu a left join sys_user_role b on a.roleId = b.roleId where a.menuId = ? group by b.userId', [menuId]);
const users = await this.nativeQuery('select b.userId from base_sys_role_menu a left join base_sys_user_role b on a.roleId = b.roleId where a.menuId = ? group by b.userId', [menuId]);
// 刷新admin权限
await this.ctx.service.sys.perms.refreshPerms(1);
if (!_.isEmpty(users)) {

View File

@ -0,0 +1,62 @@
import { Inject, Provide } from '@midwayjs/decorator';
import { BaseService, CoolCache } from 'midwayjs-cool-core';
import { BaseSysMenuService } from './menu';
import { BaseSysRoleService } from './role';
import { BaseSysDepartmentService } from './department';
/**
*
*/
@Provide()
export class BaseSysPermsService extends BaseService {
@Inject('cool:cache')
coolCache: CoolCache;
@Inject()
baseSysMenuService: BaseSysMenuService;
@Inject()
baseSysRoleService: BaseSysRoleService;
@Inject()
baseSysDepartmentService: BaseSysDepartmentService;
/**
*
* @param userId ID
*/
async refreshPerms(userId) {
const roleIds = await this.baseSysRoleService.getByUser(userId);
const perms = await this.ctx.service.sys.menu.getPerms(roleIds);
await this.coolCache.set(`admin:perms:${userId}`, JSON.stringify(perms), this.app.config.token.expires);
// 更新部门权限
const departments = await this.baseSysDepartmentService.getByRoleIds(roleIds, this.ctx.admin.username === 'admin');
await this.coolCache.set(`admin:department:${userId}`, JSON.stringify(departments), this.app.config.token.expires);
}
/**
*
* @param roleIds
*/
async permmenu(roleIds: number[]) {
const perms = await this.baseSysMenuService.getPerms(roleIds);
const menus = await this.baseSysMenuService.getMenus(roleIds, this.ctx.admin.username === 'admin');
return { perms, menus };
}
/**
* ID获得部门权限
* @param userId
* @return ID数组
*/
async departmentIds(userId: number) {
const department = await this.coolCache.get(`admin:department:${userId}`);
if (department) {
return JSON.parse(department);
} else {
return [];
}
}
}

View File

@ -25,8 +25,10 @@ export class BaseSysUserService extends BaseService {
.execute();
}
async test(){
// const a = await this.adminSysUserEntity.find();
// console.log(a);
/**
*
*/
async person() {
return await this.baseSysUserEntity.findOne({ id: this.ctx.admin.userId })
}
}

View File

@ -53,6 +53,8 @@ export default (appInfo: EggAppInfo) => {
router: {
prefix: ''
},
// 单点登录
sso: false,
// jwt 生成解密token的
jwt: {
// 注意: 最好重新修改,防止破解

View File

@ -7,10 +7,10 @@ export default (appInfo: EggAppInfo) => {
config.orm = {
type: 'mysql',
host: '127.0.0.1',
host: '139.196.196.203',
port: 3306,
username: 'root',
password: '123123',
username: 'cool-admin-next',
password: 'Rr4bktexbMNTPmyJ',
database: 'cool-admin-next',
// 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
synchronize: true,