diff --git a/.vscode/dto.code-snippets b/.vscode/dto.code-snippets new file mode 100644 index 0000000..bd8bfe0 --- /dev/null +++ b/.vscode/dto.code-snippets @@ -0,0 +1,17 @@ +{ + "dto": { + "prefix": "dto", + "body": [ + "import { Rule, RuleType } from '@midwayjs/decorator';", + "/**", + " * 描述", + " */", + "export class xxxDTO {", + " // 描述", + " @Rule(RuleType.string().required())", + " xxx: string;", + "}" + ], + "description": "cool-admin dto代码片段" + } +} diff --git a/package.json b/package.json index a1d0e56..d5abd54 100755 --- a/package.json +++ b/package.json @@ -6,14 +6,20 @@ "dependencies": { "@midwayjs/decorator": "^2.7.5", "@midwayjs/orm": "^1.3.0", - "@midwayjs/web": "^2.7.7", + "@midwayjs/web": "^2.3.0", "egg": "^2.29.3", "egg-scripts": "^2.13.0", "egg-view-nunjucks": "^2.3.0", "ipip-ipdb": "^0.3.0", - "midwayjs-cool-core": "file:/Users/ap/Documents/srcs/cool-admin/midway-core/core/dist", + "jsonwebtoken": "^8.5.1", + "md5": "^2.3.0", + "midway-test-component": "^1.0.1", + "midwayjs-cool-core": "/Users/ap/Documents/srcs/cool-admin/midway-core/core/dist", "midwayjs-cool-redis": "file:/Users/ap/Documents/srcs/cool-admin/midway-core/redis/dist", - "mysql2": "^2.2.5" + "mysql2": "^2.2.5", + "svg-captcha": "^1.4.0", + "svg-to-dataurl": "^1.0.0", + "uuid": "^8.3.2" }, "devDependencies": { "@midwayjs/cli": "^1.2.41", @@ -21,12 +27,13 @@ "@midwayjs/luckyeye": "^1.0.2", "@midwayjs/mock": "^2.7.7", "@types/jest": "^26.0.20", + "@types/jsonwebtoken": "^8.5.0", "@types/node": "14", "cross-env": "^7.0.3", "jest": "^26.6.3", "mwts": "^1.1.2", - "ts-jest": "^26.5.1", - "typescript": "^4.1.5" + "ts-jest": "^26.5.2", + "typescript": "^4.2.2" }, "engines": { "node": ">=12.0.0" diff --git a/src/app/modules/文件夹说明.md b/src/app/modules/README.md similarity index 100% rename from src/app/modules/文件夹说明.md rename to src/app/modules/README.md diff --git a/src/app/modules/admin/config.ts b/src/app/modules/base/config.ts similarity index 88% rename from src/app/modules/admin/config.ts rename to src/app/modules/base/config.ts index 82fe14f..ea9a76f 100644 --- a/src/app/modules/admin/config.ts +++ b/src/app/modules/base/config.ts @@ -11,6 +11,6 @@ export default (app: Application) => { // 模块描述 describe: '基础的权限管理功能,包括登录,权限校验', // 中间件 - middlewares: ['adminLogsMiddleware'], + middlewares: ['baseLogsMiddleware'], } as ModuleConfig; }; diff --git a/src/app/modules/base/controller/admin/comm.ts b/src/app/modules/base/controller/admin/comm.ts new file mode 100644 index 0000000..68e5c84 --- /dev/null +++ b/src/app/modules/base/controller/admin/comm.ts @@ -0,0 +1,33 @@ +import { Body, Provide, 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'; + +/** + * Base 通用接口 一般写不需要权限过滤的接口 + */ +@Provide() +@CoolController() +export class BaseCommController 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)); + } + +} \ No newline at end of file diff --git a/src/app/modules/admin/controller/sys/log.ts b/src/app/modules/base/controller/admin/sys/log.ts similarity index 62% rename from src/app/modules/admin/controller/sys/log.ts rename to src/app/modules/base/controller/admin/sys/log.ts index ee84412..d6311eb 100644 --- a/src/app/modules/admin/controller/sys/log.ts +++ b/src/app/modules/base/controller/admin/sys/log.ts @@ -1,8 +1,8 @@ import { Provide, Post, Inject } from '@midwayjs/decorator'; import { CoolController, BaseController } from 'midwayjs-cool-core'; -import { AdminSysLogEntity } from '../../entity/sys/log'; -import { AdminSysUserEntity } from '../../entity/sys/user'; -import { AdminSysLogService } from '../../service/sys/log'; +import { BaseSysLogEntity } from '../../../entity/sys/log'; +import { BaseSysUserEntity } from '../../../entity/sys/user'; +import { BaseSysLogService } from '../../../service/sys/log'; /** * 系统日志 @@ -10,21 +10,21 @@ import { AdminSysLogService } from '../../service/sys/log'; @Provide() @CoolController({ api: ['page'], - entity: AdminSysLogEntity, + entity: BaseSysLogEntity, pageQueryOp: { keyWordLikeFields: ['b.name', 'a.params', 'a.ipAddr'], select: ['a.*, b.name'], leftJoin: [{ - entity: AdminSysUserEntity, + entity: BaseSysUserEntity, alias: 'b', condition: 'a.userId = b.id' }] } }) -export class AdminSysLogController extends BaseController { +export class BaseSysLogController extends BaseController { @Inject() - adminSysLogService: AdminSysLogService; + adminSysLogService: BaseSysLogService; /** * 清空日志 diff --git a/src/app/modules/admin/controller/sys/role.ts b/src/app/modules/base/controller/admin/sys/role.ts similarity index 74% rename from src/app/modules/admin/controller/sys/role.ts rename to src/app/modules/base/controller/admin/sys/role.ts index 15ec244..b543c05 100644 --- a/src/app/modules/admin/controller/sys/role.ts +++ b/src/app/modules/base/controller/admin/sys/role.ts @@ -1,7 +1,7 @@ import { Provide } from '@midwayjs/decorator'; import { CoolController, BaseController } from 'midwayjs-cool-core'; import { Context } from 'vm'; -import { AdminSysRoleEntity } from '../../entity/sys/role'; +import { BaseSysRoleEntity } from '../../../entity/sys/role'; /** * 系统角色 @@ -9,7 +9,12 @@ import { AdminSysRoleEntity } from '../../entity/sys/role'; @Provide() @CoolController({ api: ['add', 'delete', 'update', 'info', 'list', 'page'], - entity: AdminSysRoleEntity, + entity: BaseSysRoleEntity, + insertParam: (async (ctx: Context) => { + return { + userId: ctx.admin.userId + } + }), pageQueryOp: { keyWordLikeFields: ['name', 'label'], where: (async (ctx: Context) => { @@ -23,6 +28,6 @@ import { AdminSysRoleEntity } from '../../entity/sys/role'; }) } }) -export class AdminSysRoleController extends BaseController { +export class BaseSysRoleController extends BaseController { } \ No newline at end of file diff --git a/src/app/modules/admin/controller/sys/user.ts b/src/app/modules/base/controller/admin/sys/user.ts similarity index 56% rename from src/app/modules/admin/controller/sys/user.ts rename to src/app/modules/base/controller/admin/sys/user.ts index 98509f5..916e672 100644 --- a/src/app/modules/admin/controller/sys/user.ts +++ b/src/app/modules/base/controller/admin/sys/user.ts @@ -1,7 +1,7 @@ import { Body, Inject, Post, Provide } from '@midwayjs/decorator'; import { CoolController, BaseController } from 'midwayjs-cool-core'; -import { AdminSysUserEntity } from '../../entity/sys/user'; -import { AdminSysUserService } from '../../service/sys/user'; +import { BaseSysUserEntity } from '../../../entity/sys/user'; +import { BaseSysUserService } from '../../../service/sys/user'; /** * 系统用户 @@ -9,19 +9,19 @@ import { AdminSysUserService } from '../../service/sys/user'; @Provide() @CoolController({ api: ['add', 'delete', 'update', 'info', 'list', 'page'], - entity: AdminSysUserEntity + entity: BaseSysUserEntity }) -export class AdminSysUserController extends BaseController { +export class BaseSysUserController extends BaseController { @Inject() - adminSysUserService: AdminSysUserService; + BaseSysUserService: BaseSysUserService; /** * 移动部门 */ @Post('/move') async move(@Body() departmentId: number, @Body() userIds: []) { - await this.adminSysUserService.move(departmentId, userIds); + await this.BaseSysUserService.move(departmentId, userIds); this.ok(); } diff --git a/src/app/modules/base/controller/api/README.md b/src/app/modules/base/controller/api/README.md new file mode 100644 index 0000000..df64e76 --- /dev/null +++ b/src/app/modules/base/controller/api/README.md @@ -0,0 +1 @@ +这里写对外的api接口 \ No newline at end of file diff --git a/src/app/modules/base/dto/login.ts b/src/app/modules/base/dto/login.ts new file mode 100644 index 0000000..ce18b85 --- /dev/null +++ b/src/app/modules/base/dto/login.ts @@ -0,0 +1,22 @@ +import { Rule, RuleType } from '@midwayjs/decorator'; +/** + * 登录参数校验 + */ +export class LoginDTO { + // 用户名 + @Rule(RuleType.string().required()) + username: string; + + // 密码 + @Rule(RuleType.string().required()) + password: number; + + // 验证码ID + @Rule(RuleType.string().required()) + captchaId: string; + + // 验证码 + @Rule(RuleType.string().required()) + verifyCode: number; + +} \ No newline at end of file diff --git a/src/app/modules/admin/entity/sys/conf.ts b/src/app/modules/base/entity/sys/conf.ts similarity index 78% rename from src/app/modules/admin/entity/sys/conf.ts rename to src/app/modules/base/entity/sys/conf.ts index f9c13dd..40a93cb 100644 --- a/src/app/modules/admin/entity/sys/conf.ts +++ b/src/app/modules/base/entity/sys/conf.ts @@ -5,8 +5,8 @@ import { BaseEntity } from 'midwayjs-cool-core'; /** * 系统配置 */ -@EntityModel('admin_sys_conf') -export class AdminSysConfEntity extends BaseEntity { +@EntityModel('base_sys_conf') +export class BaseSysConfEntity extends BaseEntity { @Index({ unique: true }) @Column({ comment: '配置键' }) diff --git a/src/app/modules/base/entity/sys/department.ts b/src/app/modules/base/entity/sys/department.ts new file mode 100644 index 0000000..b56c636 --- /dev/null +++ b/src/app/modules/base/entity/sys/department.ts @@ -0,0 +1,22 @@ +import { EntityModel } from '@midwayjs/orm'; +import { BaseEntity } from 'midwayjs-cool-core'; +import { Column } from 'typeorm'; + +/** + * 部门 + */ +@EntityModel('base_sys_department') +export class BaseSysDepartmentEntity extends BaseEntity { + + @Column({ comment: '部门名称' }) + name: string; + + @Column({ comment: '上级部门ID', type: 'bigint', nullable: true }) + parentId: number; + + @Column({ comment: '排序', default: 0 }) + orderNum: number; + // 父菜单名称 + parentName: string; + +} \ No newline at end of file diff --git a/src/app/modules/admin/entity/sys/log.ts b/src/app/modules/base/entity/sys/log.ts similarity index 88% rename from src/app/modules/admin/entity/sys/log.ts rename to src/app/modules/base/entity/sys/log.ts index 2049ce6..bd4f675 100644 --- a/src/app/modules/admin/entity/sys/log.ts +++ b/src/app/modules/base/entity/sys/log.ts @@ -5,8 +5,8 @@ import { Column, Index } from 'typeorm'; /** * 系统日志 */ -@EntityModel('admin_sys_log') -export class AdminSysLogEntity extends BaseEntity { +@EntityModel('base_sys_log') +export class BaseSysLogEntity extends BaseEntity { @Index() @Column({ comment: '用户ID', nullable: true, type: 'bigint' }) diff --git a/src/app/modules/base/entity/sys/menu.ts b/src/app/modules/base/entity/sys/menu.ts new file mode 100644 index 0000000..5619c0c --- /dev/null +++ b/src/app/modules/base/entity/sys/menu.ts @@ -0,0 +1,44 @@ +import { EntityModel } from '@midwayjs/orm'; +import { BaseEntity } from 'midwayjs-cool-core'; +import { Column } from 'typeorm'; + +/** + * 菜单 + */ +@EntityModel('base_sys_menu') +export class BaseSysMenuEntity extends BaseEntity { + + @Column({ comment: '父菜单ID', type: 'bigint', nullable: true }) + parentId: number; + + @Column({ comment: '菜单名称' }) + name: string; + + @Column({ comment: '菜单地址', nullable: true }) + router: string; + + @Column({ comment: '权限标识', nullable: true }) + perms: string; + + @Column({ comment: '类型 0:目录 1:菜单 2:按钮', default: 0, type: 'tinyint' }) + type: number; + + @Column({ comment: '图标', nullable: true }) + icon: string; + + @Column({ comment: '排序', default: 0 }) + orderNum: number; + + @Column({ comment: '视图地址', nullable: true }) + viewPath: string; + + @Column({ comment: '路由缓存', default: true }) + keepAlive: boolean; + + // 父菜单名称 + parentName: string; + + @Column({ comment: '父菜单名称', default: true }) + isShow: boolean; + +} \ No newline at end of file diff --git a/src/app/modules/admin/entity/sys/role.ts b/src/app/modules/base/entity/sys/role.ts similarity index 85% rename from src/app/modules/admin/entity/sys/role.ts rename to src/app/modules/base/entity/sys/role.ts index 4d00575..0abb972 100644 --- a/src/app/modules/admin/entity/sys/role.ts +++ b/src/app/modules/base/entity/sys/role.ts @@ -5,8 +5,8 @@ import { Column, Index } from 'typeorm'; /** * 角色 */ -@EntityModel('admin_sys_role') -export class AdminSysRoleEntity extends BaseEntity { +@EntityModel('base_sys_role') +export class BaseSysRoleEntity extends BaseEntity { @Column({ comment: '用户ID' }) userId: string; diff --git a/src/app/modules/base/entity/sys/role_department.ts b/src/app/modules/base/entity/sys/role_department.ts new file mode 100644 index 0000000..b65bf73 --- /dev/null +++ b/src/app/modules/base/entity/sys/role_department.ts @@ -0,0 +1,17 @@ +import { EntityModel } from '@midwayjs/orm'; +import { BaseEntity } from 'midwayjs-cool-core'; +import { Column } from 'typeorm'; + +/** + * 角色部门 + */ +@EntityModel('base_sys_role_department') +export class BaseSysRoleDepartmentEntity extends BaseEntity { + + @Column({ comment: '角色ID', type: 'bigint' }) + roleId: number; + + @Column({ comment: '部门ID', type: 'bigint' }) + departmentId: number; + +} \ No newline at end of file diff --git a/src/app/modules/admin/entity/sys/user.ts b/src/app/modules/base/entity/sys/user.ts similarity index 93% rename from src/app/modules/admin/entity/sys/user.ts rename to src/app/modules/base/entity/sys/user.ts index 7e9501d..9ffbe3d 100644 --- a/src/app/modules/admin/entity/sys/user.ts +++ b/src/app/modules/base/entity/sys/user.ts @@ -5,8 +5,8 @@ import { Column, Index } from 'typeorm'; /** * 系统用户 */ -@EntityModel('admin_sys_user') -export class AdminSysUserEntity extends BaseEntity { +@EntityModel('base_sys_user') +export class BaseSysUserEntity extends BaseEntity { @Index() @Column({ comment: '部门ID', type: 'bigint', nullable: true }) diff --git a/src/app/modules/base/entity/sys/user_role.ts b/src/app/modules/base/entity/sys/user_role.ts new file mode 100644 index 0000000..d8e7241 --- /dev/null +++ b/src/app/modules/base/entity/sys/user_role.ts @@ -0,0 +1,17 @@ +import { EntityModel } from '@midwayjs/orm'; +import { BaseEntity } from 'midwayjs-cool-core'; +import { Column } from 'typeorm'; + +/** + * 用户角色 + */ +@EntityModel('base_sys_user_role') +export class BaseSysUserRoleEntity extends BaseEntity { + + @Column({ comment: '用户ID', type: 'bigint' }) + userId: number; + + @Column({ comment: '角色ID', type: 'bigint' }) + roleId: number; + +} \ No newline at end of file diff --git a/src/app/modules/admin/middleware/logs.ts b/src/app/modules/base/middleware/logs.ts similarity index 85% rename from src/app/modules/admin/middleware/logs.ts rename to src/app/modules/base/middleware/logs.ts index 424c668..bad3b35 100644 --- a/src/app/modules/admin/middleware/logs.ts +++ b/src/app/modules/base/middleware/logs.ts @@ -5,10 +5,11 @@ import { IWebMiddleware, IMidwayWebNext, IMidwayWebContext } from '@midwayjs/web * 日志中间件 */ @Provide() -export class AdminLogsMiddleware implements IWebMiddleware { +export class BaseLogsMiddleware implements IWebMiddleware { resolve() { return async (ctx: IMidwayWebContext, next: IMidwayWebNext) => { + console.log('日志') // 控制器前执行的逻辑 const startTime = Date.now(); // 执行下一个 Web 中间件,最后执行到控制器 diff --git a/src/app/modules/admin/service/sys/conf.ts b/src/app/modules/base/service/sys/conf.ts similarity index 62% rename from src/app/modules/admin/service/sys/conf.ts rename to src/app/modules/base/service/sys/conf.ts index 815f999..ab06dfd 100644 --- a/src/app/modules/admin/service/sys/conf.ts +++ b/src/app/modules/base/service/sys/conf.ts @@ -2,23 +2,23 @@ import { Provide } from '@midwayjs/decorator'; import { BaseService } from 'midwayjs-cool-core'; import { InjectEntityModel } from '@midwayjs/orm'; import { Repository } from 'typeorm'; -import { AdminSysConfEntity } from '../../entity/sys/conf'; +import { BaseSysConfEntity } from '../../entity/sys/conf'; /** * 系统配置 */ @Provide() -export class AdminSysConfService extends BaseService { +export class BaseSysConfService extends BaseService { - @InjectEntityModel(AdminSysConfEntity) - adminSysConfEntity: Repository; + @InjectEntityModel(BaseSysConfEntity) + baseSysConfEntity: Repository; /** * 获得配置参数值 * @param key */ async getValue(key) { - const conf = await this.adminSysConfEntity.findOne({ cKey: key }); + const conf = await this.baseSysConfEntity.findOne({ cKey: key }); if (conf) { return conf.cValue; } @@ -30,7 +30,7 @@ export class AdminSysConfService extends BaseService { * @param cValue */ async updateVaule(cKey, cValue) { - await this.adminSysConfEntity.createQueryBuilder() + await this.baseSysConfEntity.createQueryBuilder() .update() .set({ cKey, cValue }) .execute(); diff --git a/src/app/modules/base/service/sys/department.ts b/src/app/modules/base/service/sys/department.ts new file mode 100644 index 0000000..cafb46f --- /dev/null +++ b/src/app/modules/base/service/sys/department.ts @@ -0,0 +1,43 @@ +import { 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'; + +/** + * 描述 + */ +@Provide() +export class BaseSysDepartmentService extends BaseService { + + @InjectEntityModel(BaseSysDepartmentEntity) + baseSysDepartmentEntity: Repository; + + @InjectEntityModel(BaseSysRoleDepartmentEntity) + baseSysRoleDepartmentEntity: Repository; + + /** + * 根据多个ID获得部门权限信息 + * @param {[]} roleIds 数组 + * @param isAdmin 是否超管 + */ + async getByRoleIds(roleIds: number[], isAdmin) { + if (!_.isEmpty(roleIds)) { + if (isAdmin) { + const result = await this.baseSysDepartmentEntity.find(); + return result.map(e => { + return e.id; + }); + } + const result = await this.baseSysRoleDepartmentEntity.createQueryBuilder().where('roleId in (:roleIds)', { roleIds }).getMany(); + if (!_.isEmpty(result)) { + return _.uniq(result.map(e => { + return e.departmentId; + })); + } + } + return []; + } +} \ No newline at end of file diff --git a/src/app/modules/admin/service/sys/log.ts b/src/app/modules/base/service/sys/log.ts similarity index 75% rename from src/app/modules/admin/service/sys/log.ts rename to src/app/modules/base/service/sys/log.ts index a30daec..c412ee3 100644 --- a/src/app/modules/admin/service/sys/log.ts +++ b/src/app/modules/base/service/sys/log.ts @@ -4,20 +4,20 @@ import { InjectEntityModel } from '@midwayjs/orm'; import { Repository } from 'typeorm'; import { Context } from 'egg'; import * as _ from 'lodash'; -import { AdminSysLogEntity } from '../../entity/sys/log'; +import { BaseSysLogEntity } from '../../entity/sys/log'; import * as moment from 'moment'; /** * 描述 */ @Provide() -export class AdminSysLogService extends BaseService { +export class BaseSysLogService extends BaseService { @Inject() ctx: Context; - @InjectEntityModel(AdminSysLogEntity) - adminSysLogEntity: Repository; + @InjectEntityModel(BaseSysLogEntity) + baseSysLogEntity: Repository; /** * 记录 @@ -27,7 +27,7 @@ export class AdminSysLogService extends BaseService { */ async record(url, params, userId) { const ip = await this.ctx.helper.getReqIP(); - const sysLog = new AdminSysLogEntity(); + const sysLog = new BaseSysLogEntity(); sysLog.userId = userId; sysLog.ip = ip; const ipAddrArr = new Array(); @@ -37,7 +37,7 @@ export class AdminSysLogService extends BaseService { if (!_.isEmpty(params)) { sysLog.params = JSON.stringify(params); } - await this.adminSysLogEntity.insert(sysLog); + await this.baseSysLogEntity.insert(sysLog); } /** @@ -46,17 +46,17 @@ export class AdminSysLogService extends BaseService { */ async clear(isAll?) { if (isAll) { - await this.adminSysLogEntity.clear(); + await this.baseSysLogEntity.clear(); return; } const keepDay = await this.ctx.service.sys.conf.getValue('logKeep'); if (keepDay) { const beforeDate = `${moment().subtract(keepDay, 'days').format('YYYY-MM-DD')} 00:00:00`; - await this.adminSysLogEntity.createQueryBuilder() + await this.baseSysLogEntity.createQueryBuilder() .where('createTime < :createTime', { createTime: beforeDate }) await this.nativeQuery(' delete from sys_log where createTime < ? ', [beforeDate]); } else { - await this.adminSysLogEntity.clear(); + await this.baseSysLogEntity.clear(); } } } \ No newline at end of file diff --git a/src/app/modules/base/service/sys/login.ts b/src/app/modules/base/service/sys/login.ts new file mode 100644 index 0000000..9e1c753 --- /dev/null +++ b/src/app/modules/base/service/sys/login.ts @@ -0,0 +1,152 @@ +import { Inject, Provide, Config } from '@midwayjs/decorator'; +import { BaseService, CoolCache, CoolCommException } from 'midwayjs-cool-core'; +import { LoginDTO } from '../../dto/login'; +import * as svgCaptcha from 'svg-captcha'; +import * as svgToDataURL from 'svg-to-dataurl'; +import { v1 as uuid } from 'uuid'; +import { BaseSysUserEntity } from '../../entity/sys/user'; +import { Repository } from 'typeorm'; +import { InjectEntityModel } from '@midwayjs/orm'; +import * as md5 from 'md5'; +import { BaseSysRoleService } from './role'; +import * as _ from 'lodash'; +import { BaseSysMenuService } from './menu'; +import { BaseSysDepartmentService } from './department'; +import * as jwt from 'jsonwebtoken'; + +/** + * 登录 + */ +@Provide() +export class BaseSysLoginService extends BaseService { + + @Inject('cool:cache') + coolCache: CoolCache; + + @InjectEntityModel(BaseSysUserEntity) + baseSysLogEntity: Repository; + + @Inject() + baseSysRoleService: BaseSysRoleService; + + @Inject() + baseSysMenuService: BaseSysMenuService; + + @Inject() + baseSysDepartmentService: BaseSysDepartmentService; + + @Config('cool') + coolConfig; + + /** + * 登录 + * @param login + */ + async login(login: LoginDTO) { + const { username, captchaId, verifyCode, password } = login; + const checkV = await this.captchaCheck(captchaId, verifyCode); + if (checkV) { + const user = await this.baseSysLogEntity.findOne({ username }); + if (user) { + if (user.status === 0 || user.password !== md5(password)) { + throw new CoolCommException('账户或密码不正确~'); + } + } else { + throw new CoolCommException('账户或密码不正确~'); + } + const roleIds = await this.baseSysRoleService.getByUser(user.id); + if (_.isEmpty(roleIds)) { + throw new CoolCommException('该用户未设置任何角色,无法登录~'); + } + + const { expire, refreshExpire } = this.coolConfig.token.jwt; + const result = { + expire, + token: await this.generateToken(user, roleIds, expire), + refreshExpire, + refreshToken: await this.generateToken(user, roleIds, refreshExpire), + }; + + const perms = await this.baseSysMenuService.getPerms(roleIds); + const departments = await this.baseSysDepartmentService.getByRoleIds(roleIds, user.username === 'admin'); + await this.coolCache.set(`admin:department:${user.id}`, JSON.stringify(departments)); + await this.coolCache.set(`admin:perms:${user.id}`, JSON.stringify(perms)); + await this.coolCache.set(`admin:token:${user.id}`, result.token, expire); + await this.coolCache.set(`admin:token:refresh:${user.id}`, result.token, refreshExpire); + + return result; + } else { + throw new CoolCommException('验证码不正确'); + } + } + + /** + * 验证码 + * @param type 图片验证码类型 svg + * @param width 宽 + * @param height 高 + */ + async captcha(type: string, width = 150, height = 50) { + const svg = svgCaptcha.create({ + ignoreChars: 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM', + width, + height, + }); + const result = { + captchaId: uuid(), + data: svg.data.replace(/\"/g, "'"), + }; + // 文字变白 + const rpList = ['#111', '#222', '#333', '#444', '#555', '#666', '#777', '#888', '#999']; + rpList.forEach(rp => { + // @ts-ignore + result.data = result.data.replaceAll(rp, '#fff'); + }); + if (type === 'base64') { + result.data = svgToDataURL(result.data); + } + // 半小时过期 + await this.coolCache.set(`verify:img:${result.captchaId}`, svg.text.toLowerCase(), 1800); + return result; + } + + /** + * 检验图片验证码 + * @param captchaId 验证码ID + * @param value 验证码 + */ + public async captchaCheck(captchaId, value) { + const rv = await this.coolCache.get(`verify:img:${captchaId}`); + if (!rv || !value || value.toLowerCase() !== rv) { + return false; + } else { + this.coolCache.del(`verify:img:${captchaId}`); + return true; + } + } + + /** + * 生成token + * @param user 用户对象 + * @param roleIds 角色集合 + * @param expire 过期 + * @param isRefresh 是否是刷新 + */ + async generateToken(user, roleIds, expire, isRefresh?) { + await this.coolCache.set(`admin:passwordVersion:${user.id}`, user.passwordV); + const tokenInfo = { + isRefresh: false, + roleIds, + userId: user.id, + passwordVersion: user.passwordV, + }; + if (isRefresh) { + delete tokenInfo.roleIds; + tokenInfo.isRefresh = true; + } + return jwt.sign(tokenInfo, + this.coolConfig.token.jwt.secret, { + expiresIn: expire, + }); + } +} \ No newline at end of file diff --git a/src/app/modules/base/service/sys/menu.ts b/src/app/modules/base/service/sys/menu.ts new file mode 100644 index 0000000..68e77dc --- /dev/null +++ b/src/app/modules/base/service/sys/menu.ts @@ -0,0 +1,135 @@ +import { Inject, Provide } from '@midwayjs/decorator'; +import { BaseService } from 'midwayjs-cool-core'; +import { InjectEntityModel } from '@midwayjs/orm'; +import { Repository } from 'typeorm'; +import { BaseSysMenuEntity } from '../../entity/sys/menu'; +import * as _ from 'lodash'; +import { Context } from 'egg'; + +/** + * 菜单 + */ +@Provide() +export class BaseSysMenuService extends BaseService { + + @Inject() + ctx: Context; + + @InjectEntityModel(BaseSysMenuEntity) + baseSysMenuEntity: Repository; + + /** + * 获得所有菜单 + */ + async list() { + const menus = await this.getMenus(this.ctx.admin.roleIds); + if (!_.isEmpty(menus)) { + menus.forEach(e => { + const parentMenu = menus.filter(m => { + if (e.parentId === m.id) { + return m.name; + } + }); + if (!_.isEmpty(parentMenu)) { + e.parentName = parentMenu[0].name; + } + }); + } + return menus; + } + + /** + * 根据角色获得权限信息 + * @param {[]} roleIds 数组 + */ + async getPerms(roleIds) { + 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])} + where 1=1 and a.perms is not NULL + `, [roleIds]); + if (result) { + result.forEach(d => { + if (d.perms) { + perms = perms.concat(d.perms.split(',')); + } + }); + } + perms = _.uniq(perms); + perms = _.remove(perms, n => { + return !_.isEmpty(n); + }); + } + return _.uniq(perms); + } + + /** + * 获得用户菜单信息 + * @param roleIds + */ + async getMenus(roleIds) { + 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])} + GROUP BY a.id + ORDER BY + orderNum ASC`); + } + + /** + * 删除 + * @param ids + */ + async delete(ids) { + let idArr; + if (ids instanceof Array) { + idArr = ids; + } else { + idArr = ids.split(','); + } + for (const id of idArr) { + await this.baseSysMenuEntity.delete({ id }); + await this.delChildMenu(id); + } + } + + /** + * 删除子菜单 + * @param id + */ + private async delChildMenu(id) { + await this.refreshPerms(id); + const delMenu = await this.baseSysMenuEntity.find({ parentId: id }); + if (_.isEmpty(delMenu)) { + return; + } + const delMenuIds = delMenu.map(e => { + return e.id; + }); + await this.baseSysMenuEntity.delete(delMenuIds); + for (const menuId of delMenuIds) { + await this.delChildMenu(menuId); + } + } + + /** + * 更新权限 + * @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]); + // 刷新admin权限 + await this.ctx.service.sys.perms.refreshPerms(1); + if (!_.isEmpty(users)) { + // 刷新其他权限 + for (const user of users) { + await this.ctx.service.sys.perms.refreshPerms(user.userId); + } + } + } +} \ No newline at end of file diff --git a/src/app/modules/base/service/sys/role.ts b/src/app/modules/base/service/sys/role.ts new file mode 100644 index 0000000..312844c --- /dev/null +++ b/src/app/modules/base/service/sys/role.ts @@ -0,0 +1,34 @@ +import { Provide } from '@midwayjs/decorator'; +import { BaseService } from 'midwayjs-cool-core'; +import { InjectEntityModel } from '@midwayjs/orm'; +import { Repository } from 'typeorm'; +import { BaseSysRoleEntity } from '../../entity/sys/role'; +import { BaseSysUserRoleEntity } from '../../entity/sys/user_role'; +import * as _ from 'lodash'; + +/** + * 角色 + */ +@Provide() +export class BaseSysRoleService extends BaseService { + + @InjectEntityModel(BaseSysRoleEntity) + baseSysRoleEntity: Repository; + + @InjectEntityModel(BaseSysUserRoleEntity) + baseSysUserRoleEntity: Repository; + + /** + * 根据用户ID获得所有用户角色 + * @param userId + */ + async getByUser(userId: number): Promise { + const userRole = await this.baseSysUserRoleEntity.find({ userId }); + if (!_.isEmpty(userRole)) { + return userRole.map(e => { + return e.roleId; + }); + } + return []; + } +} \ No newline at end of file diff --git a/src/app/modules/admin/service/sys/user.ts b/src/app/modules/base/service/sys/user.ts similarity index 68% rename from src/app/modules/admin/service/sys/user.ts rename to src/app/modules/base/service/sys/user.ts index eb1f859..97cc572 100644 --- a/src/app/modules/admin/service/sys/user.ts +++ b/src/app/modules/base/service/sys/user.ts @@ -2,16 +2,16 @@ import { Provide } from '@midwayjs/decorator'; import { BaseService } from 'midwayjs-cool-core'; import { InjectEntityModel } from '@midwayjs/orm'; import { Repository } from 'typeorm'; -import { AdminSysUserEntity } from '../../entity/sys/user'; +import { BaseSysUserEntity } from '../../entity/sys/user'; /** * 系统用户 */ @Provide() -export class AdminSysUserService extends BaseService { +export class BaseSysUserService extends BaseService { - @InjectEntityModel(AdminSysUserEntity) - adminSysUserEntity: Repository; + @InjectEntityModel(BaseSysUserEntity) + baseSysUserEntity: Repository; /** * 移动部门 @@ -19,7 +19,7 @@ export class AdminSysUserService extends BaseService { * @param userIds */ async move(departmentId, userIds) { - await this.adminSysUserEntity.createQueryBuilder() + await this.baseSysUserEntity.createQueryBuilder() .update().set({ departmentId }) .where('id =: (:userIds)', { userIds }) .execute(); diff --git a/src/config/config.default.ts b/src/config/config.default.ts index ccf0b1e..a41c038 100644 --- a/src/config/config.default.ts +++ b/src/config/config.default.ts @@ -40,8 +40,6 @@ export default (appInfo: EggAppInfo) => { '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.ico')), }; - - // 关闭安全校验 config.security = { csrf: { @@ -55,6 +53,18 @@ export default (appInfo: EggAppInfo) => { router: { prefix: '' }, + // jwt 生成解密token的 + jwt: { + // 注意: 最好重新修改,防止破解 + secret: 'FOAPOFALOEQIPNNLQ', + // token + token: { + // 2小时过期,需要用刷新token + expire: 2 * 3600, + // 15天内,如果没操作过就需要重新登录 + refreshExpire: 24 * 3600 * 15 + }, + }, // 分页配置 page: { // 分页查询每页条数 diff --git a/src/config/config.local.ts b/src/config/config.local.ts index 2d842b9..24a4897 100644 --- a/src/config/config.local.ts +++ b/src/config/config.local.ts @@ -12,7 +12,9 @@ export default (appInfo: EggAppInfo) => { username: 'root', password: '123123', database: 'cool-admin-next', + // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 synchronize: true, + // 打印日志 logging: true, } diff --git a/src/config/config.prod.ts b/src/config/config.prod.ts index e54e7d0..6c28e26 100644 --- a/src/config/config.prod.ts +++ b/src/config/config.prod.ts @@ -12,8 +12,10 @@ export default (appInfo: EggAppInfo) => { username: 'root', password: '123123', database: 'cool-admin-next', + // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 synchronize: true, - logging: false, + // 打印日志 + logging: true, } config.logger = { diff --git a/src/config/config.unittest.ts b/src/config/config.unittest.ts deleted file mode 100644 index b5f095b..0000000 --- a/src/config/config.unittest.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const security = { - csrf: false -} \ No newline at end of file diff --git a/src/config/plugin.ts b/src/config/plugin.ts index ef68a09..f46230d 100755 --- a/src/config/plugin.ts +++ b/src/config/plugin.ts @@ -2,6 +2,7 @@ import { EggPlugin } from 'egg'; export default { static: true, // default is true view: true, + schedule: true, nunjucks: { enable: true, package: 'egg-view-nunjucks', diff --git a/src/configuration.ts b/src/configuration.ts index 13e6dce..a1bee60 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -13,7 +13,6 @@ import * as cool from 'midwayjs-cool-core'; // 必须,不可移除, cool-admin 官方组件 https://www.cool-js.com cool, //redis - ] }) export class ContainerLifeCycle implements ILifeCycle { @@ -22,7 +21,6 @@ export class ContainerLifeCycle implements ILifeCycle { app: Application; // 应用启动完成 async onReady(container?: IMidwayContainer) { - } // 应用停止