From 43d4cc856a44de529aaa08d11a6c001c5eb03e9c Mon Sep 17 00:00:00 2001 From: qiushiming Date: Wed, 17 Jun 2020 00:04:54 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(*):=20=E4=BC=98=E5=8C=96=20permission?= =?UTF-8?q?=20=E8=AE=BE=E7=BD=AE=E6=96=B9=E5=BC=8F,=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20code-message=20=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/admin.js | 125 +++-------- app/api/cms/file.js | 4 +- app/api/cms/log.js | 23 +- app/api/cms/test.js | 21 +- app/api/cms/user.js | 85 +++----- app/api/v1/book.js | 24 +-- app/app.js | 4 +- app/config/code-message.js | 81 +++++++ app/config/setting.js | 4 +- app/dao/admin.js | 108 +++++----- app/dao/book.js | 12 +- app/dao/user.js | 68 +++--- app/lib/err-code.js | 25 --- app/lib/exception.js | 18 ++ app/lib/type.js | 16 ++ app/lib/util.js | 2 +- app/middleware/jwt.js | 26 ++- app/middleware/logger.js | 4 +- app/model/book.js | 4 +- app/model/file.js | 43 ++-- app/model/group.js | 51 ++--- app/model/log.js | 31 +-- app/model/permission.js | 90 ++++---- app/model/user.js | 138 ++++++------ app/starter.js | 11 +- jest.config.js | 9 +- package.json | 9 +- schema.sql | 27 +-- test/api/cms/admin.spec.js | 328 +++++++++++++++++++++++++++++ test/api/cms/test1.test.js | 4 +- test/helper/fake-book/fake-book.js | 2 +- test/helper/fake/fake.js | 45 +--- test/helper/initial.js | 4 +- test/helper/poem/poem.js | 2 +- 34 files changed, 847 insertions(+), 601 deletions(-) create mode 100644 app/config/code-message.js delete mode 100644 app/lib/err-code.js create mode 100644 app/lib/exception.js create mode 100644 app/lib/type.js create mode 100644 test/api/cms/admin.spec.js diff --git a/app/api/cms/admin.js b/app/api/cms/admin.js index 310dc4f..9bf04de 100644 --- a/app/api/cms/admin.js +++ b/app/api/cms/admin.js @@ -9,16 +9,14 @@ import { DispatchPermissionsValidator, RemovePermissionsValidator } from '../../validator/admin'; -import { - PositiveIdValidator, - PaginateValidator -} from '../../validator/common'; +import { PositiveIdValidator, PaginateValidator } from '../../validator/common'; import { adminRequired } from '../../middleware/jwt'; import { AdminDao } from '../../dao/admin'; const admin = new LinRouter({ - prefix: '/cms/admin' + prefix: '/cms/admin', + module: '管理员' }); const adminDao = new AdminDao(); @@ -26,11 +24,7 @@ const adminDao = new AdminDao(); admin.linGet( 'getAllPermissions', '/permission', - { - permission: '查询所有可分配的权限', - module: '管理员', - mount: false - }, + admin.permission('查询所有可分配的权限'), adminRequired, async ctx => { const permissions = await adminDao.getAllPermissions(); @@ -41,11 +35,7 @@ admin.linGet( admin.linGet( 'getAdminUsers', '/users', - { - permission: '查询所有用户', - module: '管理员', - mount: false - }, + admin.permission('查询所有用户'), adminRequired, async ctx => { const v = await new AdminUsersValidator().validate(ctx); @@ -66,18 +56,13 @@ admin.linGet( admin.linPut( 'changeUserPassword', '/user/:id/password', - { - permission: '修改用户密码', - module: '管理员', - mount: false - }, + admin.permission('修改用户密码'), adminRequired, async ctx => { const v = await new ResetPasswordValidator().validate(ctx); await adminDao.changeUserPassword(ctx, v); ctx.success({ - msg: '密码修改成功', - errorCode: 2 + code: 4 }); } ); @@ -85,19 +70,14 @@ admin.linPut( admin.linDelete( 'deleteUser', '/user/:id', - { - permission: '删除用户', - module: '管理员', - mount: false - }, + admin.permission('删除用户'), adminRequired, async ctx => { const v = await new PositiveIdValidator().validate(ctx); const id = v.get('path.id'); await adminDao.deleteUser(ctx, id); ctx.success({ - msg: '删除用户成功', - errorCode: 3 + code: 5 }); } ); @@ -105,18 +85,13 @@ admin.linDelete( admin.linPut( 'updateUser', '/user/:id', - { - permission: '管理员更新用户信息', - module: '管理员', - mount: false - }, + admin.permission('管理员更新用户信息'), adminRequired, async ctx => { const v = await new UpdateUserInfoValidator().validate(ctx); await adminDao.updateUserInfo(ctx, v); ctx.success({ - msg: '更新用户成功', - errorCode: 4 + code: 6 }); } ); @@ -124,11 +99,7 @@ admin.linPut( admin.linGet( 'getAdminGroups', '/group', - { - permission: '查询所有权限组及其权限', - module: '管理员', - mount: false - }, + admin.permission('查询所有权限组及其权限'), adminRequired, async ctx => { const v = await new PaginateValidator().validate(ctx); @@ -139,7 +110,7 @@ admin.linGet( ); if (groups.length < 1) { throw new NotFound({ - msg: '未找到任何权限组' + code: 10024 }); } ctx.json({ @@ -154,17 +125,13 @@ admin.linGet( admin.linGet( 'getAllGroup', '/group/all', - { - permission: '查询所有权限组', - module: '管理员', - mount: false - }, + admin.permission('查询所有权限组'), adminRequired, async ctx => { const groups = await adminDao.getAllGroups(); if (!groups || groups.length < 1) { throw new NotFound({ - msg: '未找到任何权限组' + code: 10024 }); } ctx.json(groups); @@ -174,11 +141,7 @@ admin.linGet( admin.linGet( 'getGroup', '/group/:id', - { - permission: '查询一个权限组及其权限', - module: '管理员', - mount: false - }, + admin.permission('查询一个权限组及其权限'), adminRequired, async ctx => { const v = await new PositiveIdValidator().validate(ctx); @@ -190,23 +153,18 @@ admin.linGet( admin.linPost( 'createGroup', '/group', - { - permission: '新建权限组', - module: '管理员', - mount: false - }, + admin.permission('新建权限组'), adminRequired, async ctx => { const v = await new NewGroupValidator().validate(ctx); const ok = await adminDao.createGroup(ctx, v); if (!ok) { throw new Failed({ - msg: '新建分组失败' + code: 10027 }); } ctx.success({ - msg: '新建分组成功', - errorCode: 13 + code: 15 }); } ); @@ -214,18 +172,13 @@ admin.linPost( admin.linPut( 'updateGroup', '/group/:id', - { - permission: '更新一个权限组', - module: '管理员', - mount: false - }, + admin.permission('更新一个权限组'), adminRequired, async ctx => { const v = await new UpdateGroupValidator().validate(ctx); await adminDao.updateGroup(ctx, v); ctx.success({ - msg: '更新分组成功', - errorCode: 5 + code: 7 }); } ); @@ -233,19 +186,14 @@ admin.linPut( admin.linDelete( 'deleteGroup', '/group/:id', - { - permission: '删除一个权限组', - module: '管理员', - mount: false - }, + admin.permission('删除一个权限组'), adminRequired, async ctx => { const v = await new PositiveIdValidator().validate(ctx); const id = v.get('path.id'); await adminDao.deleteGroup(ctx, id); ctx.success({ - msg: '删除分组成功', - errorCode: 6 + code: 8 }); } ); @@ -253,18 +201,13 @@ admin.linDelete( admin.linPost( 'dispatchPermission', '/permission/dispatch', - { - permission: '分配单个权限', - module: '管理员', - mount: false - }, + admin.permission('分配单个权限'), adminRequired, async ctx => { const v = await new DispatchPermissionValidator().validate(ctx); await adminDao.dispatchPermission(ctx, v); ctx.success({ - msg: '添加权限成功', - errorCode: 6 + code: 9 }); } ); @@ -272,18 +215,13 @@ admin.linPost( admin.linPost( 'dispatchPermissions', '/permission/dispatch/batch', - { - permission: '分配多个权限', - module: '管理员', - mount: false - }, + admin.permission('分配多个权限'), adminRequired, async ctx => { const v = await new DispatchPermissionsValidator().validate(ctx); await adminDao.dispatchPermissions(ctx, v); ctx.success({ - msg: '添加权限成功', - errorCode: 7 + code: 9 }); } ); @@ -291,18 +229,13 @@ admin.linPost( admin.linPost( 'removePermissions', '/permission/remove', - { - permission: '删除多个权限', - module: '管理员', - mount: false - }, + admin.permission('删除多个权限'), adminRequired, async ctx => { const v = await new RemovePermissionsValidator().validate(ctx); await adminDao.removePermissions(ctx, v); ctx.success({ - msg: '删除权限成功', - errorCode: 8 + code: 10 }); } ); diff --git a/app/api/cms/file.js b/app/api/cms/file.js index 1606532..76b2388 100644 --- a/app/api/cms/file.js +++ b/app/api/cms/file.js @@ -7,10 +7,10 @@ const file = new LinRouter({ prefix: '/cms/file' }); -file.linPost('upload', '/', {}, loginRequired, async ctx => { +file.linPost('upload', '/', loginRequired, async ctx => { const files = await ctx.multipart(); if (files.length < 1) { - throw new ParametersException({ msg: '未找到符合条件的文件资源' }); + throw new ParametersException({ code: 10033 }); } const uploader = new LocalUploader('app/assets'); const arr = await uploader.upload(files); diff --git a/app/api/cms/log.js b/app/api/cms/log.js index ab10316..99b8edb 100644 --- a/app/api/cms/log.js +++ b/app/api/cms/log.js @@ -6,7 +6,8 @@ import { groupRequired } from '../../middleware/jwt'; import { LogDao } from '../../dao/log'; const log = new LinRouter({ - prefix: '/cms/log' + prefix: '/cms/log', + module: '日志' }); const logDao = new LogDao(); @@ -14,18 +15,14 @@ const logDao = new LogDao(); log.linGet( 'getLogs', '/', - { - permission: '查询所有日志', - module: '日志', - mount: true - }, + log.permission('查询所有日志'), groupRequired, async ctx => { const v = await new LogFindValidator().validate(ctx); const { rows, total } = await logDao.getLogs(v); if (!rows || rows.length < 1) { throw new NotFound({ - msg: '没有找到相关日志' + code: 10220 }); } ctx.json({ @@ -40,11 +37,7 @@ log.linGet( log.linGet( 'getUserLogs', '/search', - { - permission: '搜索日志', - module: '日志', - mount: true - }, + log.permission('搜索日志'), groupRequired, async ctx => { const v = await new LogFindValidator().validate(ctx); @@ -62,11 +55,7 @@ log.linGet( log.linGet( 'getUsers', '/users', - { - permission: '查询日志记录的用户', - module: '日志', - mount: true - }, + log.permission('查询日志记录的用户'), groupRequired, async ctx => { const v = await new PaginateValidator().validate(ctx); diff --git a/app/api/cms/test.js b/app/api/cms/test.js index f4fcd85..c620064 100644 --- a/app/api/cms/test.js +++ b/app/api/cms/test.js @@ -3,7 +3,8 @@ import { loginRequired, groupRequired } from '../../middleware/jwt'; import { logger } from '../../middleware/logger'; const test = new LinRouter({ - prefix: '/cms/test' + prefix: '/cms/test', + module: '信息' }); test.get('/', async ctx => { @@ -16,18 +17,14 @@ test.get('/', async ctx => { }); test.linGet( - 'getTestMsg', + 'getTestMessage', '/json', - { - permission: '测试日志记录', - module: '信息', - mount: true - }, + test.permission('测试日志记录'), loginRequired, logger('{user.username}就是皮了一波'), async ctx => { ctx.json({ - msg: '物质决定意识,经济基础决定上层建筑' + message: '物质决定意识,经济基础决定上层建筑' }); } ); @@ -35,15 +32,11 @@ test.linGet( test.linGet( 'getTestInfo', '/info', - { - permission: '查看lin的信息', - module: '信息', - mount: true - }, + test.permission('查看lin的信息'), groupRequired, async ctx => { ctx.json({ - msg: + message: 'Lin 是一套基于 Python-Flask 的一整套开箱即用的后台管理系统(CMS)。Lin 遵循简洁、高效的原则,通过核心库加插件的方式来驱动整个系统高效的运行' }); } diff --git a/app/api/cms/user.js b/app/api/cms/user.js index adde81e..8e533c4 100644 --- a/app/api/cms/user.js +++ b/app/api/cms/user.js @@ -16,7 +16,8 @@ import { logger } from '../../middleware/logger'; import { UserDao } from '../../dao/user'; const user = new LinRouter({ - prefix: '/cms/user' + prefix: '/cms/user', + module: '用户' }); const userDao = new UserDao(); @@ -24,62 +25,43 @@ const userDao = new UserDao(); user.linPost( 'userRegister', '/register', - { - permission: '注册', - module: '用户', - mount: false - }, + user.permission('注册'), adminRequired, logger('管理员新建了一个用户'), async ctx => { const v = await new RegisterValidator().validate(ctx); await userDao.createUser(v); ctx.success({ - msg: '注册成功', - errorCode: 9 + code: 11 }); } ); -user.linPost( - 'userLogin', - '/login', - { - permission: '登陆', - module: '用户', - mount: false - }, - async ctx => { - const v = await new LoginValidator().validate(ctx); - const user = await UserIdentityModel.verify( - v.get('body.username'), - v.get('body.password') - ); - const { accessToken, refreshToken } = getTokens({ - id: user.user_id - }); - ctx.json({ - access_token: accessToken, - refresh_token: refreshToken - }); - } -); +user.linPost('userLogin', '/login', user.permission('登录'), async ctx => { + const v = await new LoginValidator().validate(ctx); + const user = await UserIdentityModel.verify( + v.get('body.username'), + v.get('body.password') + ); + const { accessToken, refreshToken } = getTokens({ + id: user.user_id + }); + ctx.json({ + access_token: accessToken, + refresh_token: refreshToken + }); +}); user.linPut( 'userUpdate', '/', - { - permission: '更新用户信息', - module: '用户', - mount: false - }, + user.permission('更新用户信息'), loginRequired, async ctx => { const v = await new UpdateInfoValidator().validate(ctx); await userDao.updateUser(ctx, v); ctx.success({ - msg: '更新用户成功', - errorCode: 4 + code: 6 }); } ); @@ -87,11 +69,7 @@ user.linPut( user.linPut( 'userUpdatePassword', '/change_password', - { - permission: '修改密码', - module: '用户', - mount: false - }, + user.permission('修改密码'), loginRequired, async ctx => { const user = ctx.currentUser; @@ -102,8 +80,7 @@ user.linPut( v.get('body.new_password') ); ctx.success({ - msg: '密码修改成功', - errorCode: 2 + code: 4 }); } ); @@ -111,11 +88,7 @@ user.linPut( user.linGet( 'userGetToken', '/refresh', - { - permission: '刷新令牌', - module: '用户', - mount: false - }, + user.permission('刷新令牌'), refreshTokenRequiredWithUnifyException, async ctx => { const user = ctx.currentUser; @@ -130,11 +103,7 @@ user.linGet( user.linGet( 'userGetPermissions', '/permissions', - { - permission: '查询自己拥有的权限', - module: '用户', - mount: true - }, + user.permission('查询自己拥有的权限'), loginRequired, async ctx => { const user = await userDao.getPermissions(ctx); @@ -145,11 +114,7 @@ user.linGet( user.linGet( 'getInformation', '/information', - { - permission: '查询自己信息', - module: '用户', - mount: true - }, + user.permission('查询自己信息'), loginRequired, async ctx => { const info = await userDao.getInformation(ctx); diff --git a/app/api/v1/book.js b/app/api/v1/book.js index 12cfe24..d7b2a96 100644 --- a/app/api/v1/book.js +++ b/app/api/v1/book.js @@ -7,12 +7,13 @@ import { import { PositiveIdValidator } from '../../validator/common'; import { getSafeParamId } from '../../lib/util'; -import { BookNotFound } from '../../lib/err-code'; +import { BookNotFound } from '../../lib/exception'; import { BookDao } from '../../dao/book'; // book 的红图实例 const bookApi = new LinRouter({ - prefix: '/v1/book' + prefix: '/v1/book', + module: '图书' }); // book 的dao 数据库访问层实例 @@ -24,7 +25,7 @@ bookApi.get('/:id', async ctx => { const book = await bookDto.getBook(id); if (!book) { throw new NotFound({ - msg: '没有找到相关书籍' + code: 10022 }); } ctx.json(book); @@ -34,7 +35,7 @@ bookApi.get('/', async ctx => { const books = await bookDto.getBooks(); // if (!books || books.length < 1) { // throw new NotFound({ - // msg: '没有找到相关书籍' + // message: '没有找到相关书籍' // }); // } ctx.json(books); @@ -53,8 +54,7 @@ bookApi.post('/', async ctx => { const v = await new CreateOrUpdateBookValidator().validate(ctx); await bookDto.createBook(v); ctx.success({ - msg: '新建图书成功', - errorCode: 10 + code: 12 }); }); @@ -63,27 +63,21 @@ bookApi.put('/:id', async ctx => { const id = getSafeParamId(ctx); await bookDto.updateBook(v, id); ctx.success({ - msg: '更新图书成功', - errorCode: 11 + code: 13 }); }); bookApi.linDelete( 'deleteBook', '/:id', - { - permission: '删除图书', - module: '图书', - mount: true - }, + bookApi.permission('删除图书'), groupRequired, async ctx => { const v = await new PositiveIdValidator().validate(ctx); const id = v.get('path.id'); await bookDto.deleteBook(id); ctx.success({ - msg: '删除图书成功', - errorCode: 12 + code: 14 }); } ); diff --git a/app/app.js b/app/app.js index 3b5dd61..2bea980 100644 --- a/app/app.js +++ b/app/app.js @@ -58,7 +58,7 @@ function applyDefaultExtends (app) { } /** - * loader 插件管理 + * loader 加载插件和路由文件 * @param app koa实例 */ function applyLoader (app) { @@ -91,7 +91,7 @@ async function createApp () { applyLoader(app); applyJwt(app); const lin = new Lin(); - await lin.initApp(app, true); + await lin.initApp(app, true); // 是否挂载插件路由,默认为true await PermissionModel.initPermission(); indexPage(app); multipart(app); diff --git a/app/config/code-message.js b/app/config/code-message.js new file mode 100644 index 0000000..8584f8f --- /dev/null +++ b/app/config/code-message.js @@ -0,0 +1,81 @@ +'use strict'; + +module.exports = { + codeMessage: { + getMessage (code) { + return this[code] || ''; + }, + 0: '成功', + 1: '创建成功', + 2: '更新成功', + 3: '删除成功', + 4: '密码修改成功', + 5: '删除用户成功', + 6: '更新用户成功', + 7: '更新分组成功', + 8: '删除分组成功', + 9: '添加权限成功', + 10: '删除权限成功', + 11: '注册成功', + 12: '新建图书成功', + 13: '更新图书成功', + 14: '删除图书成功', + 15: '新建分组成功', + 9999: '服务器未知错误', + 10000: '未携带令牌', + 10001: '权限不足', + 10010: '授权失败', + 10011: '更新密码失败', + 10012: '请传入认证头字段', + 10013: '认证头字段解析失败', + 10020: '资源不存在', + 10021: '用户不存在', + 10022: '未找到相关书籍', + 10023: '分组不存在,无法新建用户', + 10024: '分组不存在', + 10025: '找不到相应的视图处理器', + 10026: '未找到文件', + 10027: '新建分组失败', + 10030: '参数错误', + 10031: '用户名或密码错误', + 10032: '请输入正确的密码', + 10033: '为找到符合条件的文件资源', + 10040: '令牌失效', + 10041: 'access token 损坏', + 10042: 'refresh token 损坏', + 10050: '令牌过期', + 10051: 'access token 过期', + 10052: 'refresh token 过期', + 10060: '字段重复', + 10070: '禁止操作', + 10071: '已经有用户使用了该名称,请重新输入新的用户名', + 10072: '分组名已被使用,请重新填入新的分组名', + 10073: 'root分组不可添加用户', + 10074: 'root分组不可删除', + 10075: 'guest分组不可删除', + 10076: '邮箱已被使用,请重新填入新的邮箱', + 10077: '不可将用户分配给不存在的分组', + 10078: '不可修改root用户的分组', + 10080: '请求方法不允许', + 10100: '刷新令牌获取失败', + 10110: '{name}大小不能超过{size}字节', + 10111: '总文件体积不能超过{size}字节', + 10120: '文件数量过多', + 10121: '文件太多,文件总数不可超过{num}', + 10130: '不支持类型为{ext}的文件', + 10140: '请求过于频繁,请稍后重试', + 10150: '丢失参数', + 10160: '类型错误', + 10170: '请求体不可为空', + 10180: '全部文件大小不能超过{num}', + 10190: '读取文件数据失败', + 10200: '失败', + 10210: '文件损坏,无法读取', + 10220: '没有找到相关日志', + 10230: '已有权限,不可重复添加', + 10231: '无法分配不存在的权限', + 10240: '书籍已存在', + 10250: '请使用正确类型的令牌', + 10251: '请使用正确作用域的令牌' + } +}; diff --git a/app/config/setting.js b/app/config/setting.js index c31c0f1..bdc2fe3 100644 --- a/app/config/setting.js +++ b/app/config/setting.js @@ -1,4 +1,4 @@ -'use strict'; +const path = require('path'); module.exports = { port: 5000, @@ -7,6 +7,8 @@ module.exports = { pageDefault: 0, apiDir: 'app/api', accessExp: 60 * 60, // 1h 单位秒 + // 指定工作目录,默认为 process.cwd() 路径 + baseDir: path.resolve(__dirname, '../../'), // debug 模式 debug: true, // refreshExp 设置refresh_token的过期时间,默认一个月 diff --git a/app/dao/admin.js b/app/dao/admin.js index cd55b81..07f32e8 100644 --- a/app/dao/admin.js +++ b/app/dao/admin.js @@ -7,12 +7,17 @@ import { GroupPermissionModel } from '../model/group-permission'; import { UserGroupModel } from '../model/user-group'; import sequelize from '../lib/db'; +import { MountType, GroupLevel } from '../lib/type'; import { Op } from 'sequelize'; import { has, set, get } from 'lodash'; class AdminDao { async getAllPermissions () { - const permissions = await PermissionModel.findAll(); + const permissions = await PermissionModel.findAll({ + where: { + mount: MountType.Mount + } + }); const result = Object.create(null); permissions.forEach(v => { const item = { @@ -77,15 +82,10 @@ class AdminDao { } async changeUserPassword (ctx, v) { - const user = await UserModel.findOne({ - where: { - id: v.get('path.id') - } - }); + const user = await UserModel.findByPk(v.get('path.id')); if (!user) { throw new NotFound({ - msg: '用户不存在', - errorCode: 10021 + code: 10021 }); } await UserIdentityModel.resetPassword(user, v.get('body.new_password')); @@ -99,8 +99,7 @@ class AdminDao { }); if (!user) { throw new NotFound({ - msg: '用户不存在', - errorCode: 10021 + code: 10021 }); } let transaction; @@ -131,8 +130,7 @@ class AdminDao { const user = await UserModel.findByPk(v.get('path.id')); if (!user) { throw new NotFound({ - msg: '用户不存在', - errorCode: 10021 + code: 10021 }); } @@ -144,7 +142,7 @@ class AdminDao { const groupIds = userGroup.map(v => v.group_id); const isAdmin = await GroupModel.findOne({ where: { - name: 'root', + level: GroupLevel.Root, id: { [Op.in]: groupIds } @@ -153,23 +151,20 @@ class AdminDao { if (isAdmin) { throw new Forbidden({ - msg: '不可修改root用户的分组', - errorCode: 10078 + code: 10078 }); } for (const id of v.get('body.group_ids') || []) { const group = await GroupModel.findByPk(id); - if (group.name === 'root') { + if (group.level === GroupLevel.Root) { throw new Forbidden({ - msg: 'root分组不可添加用户', - errorCode: 10073 + code: 10073 }); } if (!group) { throw new NotFound({ - msg: '不可将用户分配给不存在的分组', - errorCode: 10077 + code: 10077 }); } } @@ -215,8 +210,8 @@ class AdminDao { async getAllGroups () { const allGroups = await GroupModel.findAll({ where: { - name: { - [Op.ne]: 'root' + level: { + [Op.ne]: GroupLevel.Root } } }); @@ -227,8 +222,7 @@ class AdminDao { const group = await GroupModel.findByPk(id); if (!group) { throw new NotFound({ - msg: '分组不存在', - errorCode: 10024 + code: 10024 }); } @@ -241,6 +235,7 @@ class AdminDao { const permissions = await PermissionModel.findAll({ where: { + mount: MountType.Mount, id: { [Op.in]: permissionIds } @@ -258,15 +253,20 @@ class AdminDao { }); if (group) { throw new Forbidden({ - msg: '分组已存在,不可创建同名分组' + code: 10072 }); } for (const id of v.get('body.permission_ids') || []) { - const permission = await PermissionModel.findByPk(id); + const permission = await PermissionModel.findOne({ + where: { + id, + mount: MountType.Mount + } + }); if (!permission) { throw new NotFound({ - msg: '无法分配不存在的权限' + code: 10231 }); } } @@ -307,31 +307,28 @@ class AdminDao { const group = await GroupModel.findByPk(v.get('path.id')); if (!group) { throw new NotFound({ - msg: '分组不存在,更新失败' + code: 10024 }); } group.name = v.get('body.name'); group.info = v.get('body.info'); - group.save(); + await group.save(); } async deleteGroup (ctx, id) { const group = await GroupModel.findByPk(id); if (!group) { throw new NotFound({ - msg: '分组不存在', - errorCode: 10024 + code: 10024 }); } - if (group.name === 'root') { + if (group.level === GroupLevel.Root) { throw new Forbidden({ - msg: 'root分组不可删除', - errorCode: 10074 + code: 10074 }); - } else if (group.name === 'guest') { + } else if (group.level === GroupLevel.Guest) { throw new Forbidden({ - msg: 'guest分组不可删除', - errorCode: 10075 + code: 10075 }); } @@ -363,16 +360,19 @@ class AdminDao { const group = await GroupModel.findByPk(v.get('body.group_id')); if (!group) { throw new NotFound({ - msg: '分组不存在' + code: 10024 }); } - const permission = await PermissionModel.findByPk( - v.get('body.permission_id') - ); + const permission = await PermissionModel.findOne({ + where: { + id: v.get('body.permission_id'), + mount: MountType.Mount + } + }); if (!permission) { throw new NotFound({ - msg: '无法分配不存在的权限' + code: 10231 }); } @@ -384,7 +384,7 @@ class AdminDao { }); if (one) { throw new Forbidden({ - msg: '已有权限,不可重复添加' + code: 10230 }); } await GroupPermissionModel.create({ @@ -397,14 +397,19 @@ class AdminDao { const group = await GroupModel.findByPk(v.get('body.group_id')); if (!group) { throw new NotFound({ - msg: '分组不存在' + code: 10024 }); } for (const id of v.get('body.permission_ids') || []) { - const permission = await PermissionModel.findByPk(id); + const permission = await PermissionModel.findOne({ + where: { + id, + mount: MountType.Mount + } + }); if (!permission) { throw new NotFound({ - msg: '无法分配不存在的权限' + code: 10231 }); } } @@ -433,14 +438,19 @@ class AdminDao { const group = await GroupModel.findByPk(v.get('body.group_id')); if (!group) { throw new NotFound({ - msg: '分组不存在' + code: 10024 }); } for (const id of v.get('body.permission_ids') || []) { - const permission = await PermissionModel.findByPk(id); + const permission = await PermissionModel.findOne({ + where: { + id, + mount: MountType.Mount + } + }); if (!permission) { throw new NotFound({ - msg: '无法分配不存在的权限' + code: 10231 }); } } diff --git a/app/dao/book.js b/app/dao/book.js index d40fce1..23cc1cb 100644 --- a/app/dao/book.js +++ b/app/dao/book.js @@ -1,6 +1,6 @@ import { NotFound, Forbidden } from 'lin-mizar'; -import { Book } from '../model/book'; import Sequelize from 'sequelize'; +import { Book } from '../model/book'; class BookDao { async getBook (id) { @@ -36,7 +36,7 @@ class BookDao { }); if (book) { throw new Forbidden({ - msg: '图书已存在' + code: 10240 }); } const bk = new Book(); @@ -44,21 +44,21 @@ class BookDao { bk.author = v.get('body.author'); bk.summary = v.get('body.summary'); bk.image = v.get('body.image'); - bk.save(); + await bk.save(); } async updateBook (v, id) { const book = await Book.findByPk(id); if (!book) { throw new NotFound({ - msg: '没有找到相关书籍' + code: 10022 }); } book.title = v.get('body.title'); book.author = v.get('body.author'); book.summary = v.get('body.summary'); book.image = v.get('body.image'); - book.save(); + await book.save(); } async deleteBook (id) { @@ -69,7 +69,7 @@ class BookDao { }); if (!book) { throw new NotFound({ - msg: '没有找到相关书籍' + code: 10022 }); } book.destroy(); diff --git a/app/dao/user.js b/app/dao/user.js index 2b0a853..9f4c474 100644 --- a/app/dao/user.js +++ b/app/dao/user.js @@ -1,11 +1,12 @@ -import { RepeatException, generate, NotFound, Forbidden, config } from 'lin-mizar'; -import { UserModel, UserIdentityModel, identityType } from '../model/user'; +import { RepeatException, generate, NotFound, Forbidden } from 'lin-mizar'; +import { UserModel, UserIdentityModel } from '../model/user'; import { UserGroupModel } from '../model/user-group'; import { GroupPermissionModel } from '../model/group-permission'; import { PermissionModel } from '../model/permission'; import { GroupModel } from '../model/group'; import sequelize from '../lib/db'; +import { MountType, GroupLevel, IdentityType } from '../lib/type'; import { Op } from 'sequelize'; import { set, has, uniq } from 'lodash'; @@ -18,8 +19,7 @@ class UserDao { }); if (user) { throw new RepeatException({ - msg: '已经有用户使用了该名称,请重新输入新的用户名', - errorCode: 10071 + code: 10071 }); } if (v.get('body.email') && v.get('body.email').trim() !== '') { @@ -30,23 +30,20 @@ class UserDao { }); if (user) { throw new RepeatException({ - msg: '邮箱已被使用,请重新填入新的邮箱', - errorCode: 10076 + code: 10076 }); } } for (const id of v.get('body.group_ids') || []) { const group = await GroupModel.findByPk(id); - if (group.name === 'root') { + if (group.level === GroupLevel.Root) { throw new Forbidden({ - msg: 'root分组不可添加用户', - errorCode: 10073 + code: 10073 }); } if (!group) { throw new NotFound({ - msg: '分组不存在,无法新建用户', - errorCode: 10023 + code: 10023 }); } } @@ -63,8 +60,7 @@ class UserDao { }); if (exit) { throw new RepeatException({ - msg: '已经有用户使用了该名称,请重新输入新的用户名', - errorCode: 10071 + code: 10071 }); } user.username = v.get('body.username'); @@ -77,8 +73,7 @@ class UserDao { }); if (exit) { throw new RepeatException({ - msg: '邮箱已被使用,请重新填入新的邮箱', - errorCode: 10076 + code: 10076 }); } user.email = v.get('body.email'); @@ -89,7 +84,7 @@ class UserDao { if (v.get('body.avatar')) { user.avatar = v.get('body.avatar'); } - user.save(); + await user.save(); } async getInformation (ctx) { @@ -108,7 +103,7 @@ class UserDao { } } }); - + set(user, 'groups', groups); return user; } @@ -124,7 +119,7 @@ class UserDao { const root = await GroupModel.findOne({ where: { - name: 'root', + level: GroupLevel.Root, id: { [Op.in]: groupIds } @@ -136,7 +131,11 @@ class UserDao { let permissions = []; if (root) { - permissions = await PermissionModel.findAll(); + permissions = await PermissionModel.findAll({ + where: { + mount: MountType.Mount + } + }); } else { const groupPermission = await GroupPermissionModel.findAll({ where: { @@ -152,7 +151,8 @@ class UserDao { where: { id: { [Op.in]: permissionIds - } + }, + mount: MountType.Mount } }); } @@ -178,7 +178,7 @@ class UserDao { await UserIdentityModel.create( { user_id, - identity_type: identityType, + identity_type: IdentityType.Password, identifier: user.username, credential: generate(v.get('body.password')) }, @@ -186,18 +186,9 @@ class UserDao { transaction } ); - // 未指定分组,默认加入游客分组 - if (v.get('body.group_ids').length === 0) { - const guest = await GroupModel.findOne({ - where: { - name: 'guest' - } - }); - await UserGroupModel.create({ - user_id, - group_id: guest.id - }); - } else { + + const groupIds = v.get('body.group_ids'); + if (groupIds && groupIds.length > 0) { for (const id of v.get('body.group_ids') || []) { await UserGroupModel.create( { @@ -209,6 +200,17 @@ class UserDao { } ); } + } else { + // 未指定分组,默认加入游客分组 + const guest = await GroupModel.findOne({ + where: { + level: GroupLevel.Guest + } + }); + await UserGroupModel.create({ + user_id, + group_id: guest.id + }); } await transaction.commit(); } catch (error) { diff --git a/app/lib/err-code.js b/app/lib/err-code.js deleted file mode 100644 index f8628fd..0000000 --- a/app/lib/err-code.js +++ /dev/null @@ -1,25 +0,0 @@ -import { HttpException } from 'lin-mizar'; -import assert from 'assert'; -import { isInteger } from 'lodash'; - -class BookNotFound extends HttpException { - constructor (ex) { - super(); - this.code = 404; - this.msg = '没有找到相关图书'; - this.errorCode = 80010; - if (ex && ex.code) { - assert(isInteger(ex.code)); - this.code = ex.code; - } - if (ex && ex.msg) { - this.msg = ex.msg; - } - if (ex && ex.errorCode) { - assert(isInteger(ex.errorCode)); - this.errorCode = ex.errorCode; - } - } -} - -module.exports = { BookNotFound }; diff --git a/app/lib/exception.js b/app/lib/exception.js new file mode 100644 index 0000000..963e8db --- /dev/null +++ b/app/lib/exception.js @@ -0,0 +1,18 @@ +import { HttpException, config } from 'lin-mizar'; + +const CodeMessage = config.getItem('codeMessage', {}); + +/** + * 自定义异常类 + */ +class BookNotFound extends HttpException { + constructor (ex) { + super(); + this.status = 404; + this.code = 10022; + this.message = CodeMessage.getMessage(10022); + this.exceptionHandler(ex); + } +} + +export { BookNotFound }; diff --git a/app/lib/type.js b/app/lib/type.js new file mode 100644 index 0000000..fc70e6b --- /dev/null +++ b/app/lib/type.js @@ -0,0 +1,16 @@ +const MountType = { + Mount: 1, // 挂载 + Unmount: 0 +}; + +const IdentityType = { + Password: 'USERNAME_PASSWORD' +}; + +const GroupLevel = { + Root: 'root', + Guest: 'guest', + User: 'user' +}; + +export { MountType, IdentityType, GroupLevel }; diff --git a/app/lib/util.js b/app/lib/util.js index 98b7c36..677a59c 100644 --- a/app/lib/util.js +++ b/app/lib/util.js @@ -5,7 +5,7 @@ function getSafeParamId (ctx) { const id = toSafeInteger(get(ctx.params, 'id')); if (!isInteger(id)) { throw new ParametersException({ - msg: '路由参数错误' + code: 10030 }); } return id; diff --git a/app/middleware/jwt.js b/app/middleware/jwt.js index b39acb2..b73e2ea 100644 --- a/app/middleware/jwt.js +++ b/app/middleware/jwt.js @@ -11,6 +11,7 @@ import { GroupModel } from '../model/group'; import { GroupPermissionModel } from '../model/group-permission'; import { PermissionModel } from '../model/permission'; import { UserModel } from '../model/user'; +import { MountType, GroupLevel } from '../lib/type'; import { Op } from 'sequelize'; import { uniq } from 'lodash'; @@ -24,7 +25,7 @@ async function isAdmin (ctx) { const groupIds = userGroup.map(v => v.group_id); const is = await GroupModel.findOne({ where: { - name: 'root', + level: GroupLevel.Root, id: { [Op.in]: groupIds } @@ -40,7 +41,9 @@ async function mountUser (ctx) { const { identity } = parseHeader(ctx); const user = await UserModel.findByPk(identity); if (!user) { - ctx.throw(new NotFound({ msg: '用户不存在', errorCode: 10021 })); + throw new NotFound({ + code: 10021 + }); } // 将user挂在ctx上 ctx.currentUser = user; @@ -56,7 +59,9 @@ async function adminRequired (ctx, next) { if (await isAdmin(ctx)) { await next(); } else { - throw new AuthFailed({ msg: '只有超级管理员可操作' }); + throw new AuthFailed({ + code: 10001 + }); } } else { await next(); @@ -85,7 +90,11 @@ async function refreshTokenRequiredWithUnifyException (ctx, next) { const { identity } = parseHeader(ctx, TokenType.REFRESH); const user = await UserModel.findByPk(identity); if (!user) { - ctx.throw(new NotFound({ msg: '用户不存在', errorCode: 10021 })); + ctx.throw( + new NotFound({ + code: 10021 + }) + ); } // 将user挂在ctx上 ctx.currentUser = user; @@ -130,6 +139,7 @@ async function groupRequired (ctx, next) { const item = await PermissionModel.findOne({ where: { name: permission, + mount: MountType.Mount, module, id: { [Op.in]: permissionIds @@ -139,10 +149,14 @@ async function groupRequired (ctx, next) { if (item) { await next(); } else { - throw new AuthFailed({ msg: '权限不够,请联系超级管理员获得权限' }); + throw new AuthFailed({ + code: 10001 + }); } } else { - throw new AuthFailed({ msg: '权限不够,请联系超级管理员获得权限' }); + throw new AuthFailed({ + code: 10001 + }); } } } else { diff --git a/app/middleware/logger.js b/app/middleware/logger.js index 01dc78d..5982ceb 100644 --- a/app/middleware/logger.js +++ b/app/middleware/logger.js @@ -10,7 +10,7 @@ const REG_XP = /(?<=\{)[^}]*(?=\})/g; * * ```js * test.linGet( - * "getTestMsg", + * "getTestMessage", * "/json", * { * permission: "hello", @@ -21,7 +21,7 @@ const REG_XP = /(?<=\{)[^}]*(?=\})/g; * logger("{user.username}就是皮了一波"), * async ctx => { * ctx.json({ - * msg: "物质决定意识,经济基础决定上层建筑" + * message: "物质决定意识,经济基础决定上层建筑" * }); * } * ); diff --git a/app/model/book.js b/app/model/book.js index febb8e7..9cb26ae 100644 --- a/app/model/book.js +++ b/app/model/book.js @@ -43,9 +43,9 @@ Book.init( }, merge( { + sequelize, tableName: 'book', - modelName: 'book', - sequelize + modelName: 'book' }, InfoCrudMixin.options ) diff --git a/app/model/file.js b/app/model/file.js index a5a2b8b..39321d2 100644 --- a/app/model/file.js +++ b/app/model/file.js @@ -1,4 +1,6 @@ import { Model, Sequelize } from 'sequelize'; +import { InfoCrudMixin } from 'lin-mizar'; +import { merge } from 'lodash'; import sequelize from '../lib/db'; class File extends Model { @@ -45,32 +47,21 @@ File.init( comment: '图片md5值,防止上传重复图片' } }, - { - sequelize, - indexes: [ - { - name: 'md5_del', - unique: true, - fields: ['md5', 'delete_time'] - } - ], - tableName: 'lin_file', - modelName: 'file', - createdAt: 'create_time', - updatedAt: 'update_time', - deletedAt: 'delete_time', - paranoid: true, - getterMethods: { - createTime () { - // @ts-ignore - return new Date(this.getDataValue('create_time')).getTime(); - }, - updateTime () { - // @ts-ignore - return new Date(this.getDataValue('update_time')).getTime(); - } - } - } + merge( + { + sequelize, + tableName: 'lin_file', + modelName: 'file', + indexes: [ + { + name: 'md5_del', + unique: true, + fields: ['md5', 'delete_time'] + } + ] + }, + InfoCrudMixin.options + ) ); export { File as FileModel }; diff --git a/app/model/group.js b/app/model/group.js index dfc2d2e..7678599 100644 --- a/app/model/group.js +++ b/app/model/group.js @@ -1,6 +1,7 @@ -import sequelize from '../lib/db'; import { Model, Sequelize } from 'sequelize'; -import { has, get } from 'lodash'; +import { InfoCrudMixin } from 'lin-mizar'; +import { has, get, merge } from 'lodash'; +import sequelize from '../lib/db'; class Group extends Model { toJSON () { @@ -32,34 +33,28 @@ Group.init( type: Sequelize.STRING({ length: 255 }), allowNull: true, comment: '分组信息:例如:搬砖的人' + }, + level: { + type: Sequelize.ENUM('root', 'guest', 'user'), + defaultValue: 'user', + comment: '分组级别(root、guest分组只能存在一个)' } }, - { - sequelize, - indexes: [ - { - name: 'name_del', - unique: true, - fields: ['name', 'delete_time'] - } - ], - tableName: 'lin_group', - modelName: 'group', - createdAt: 'create_time', - updatedAt: 'update_time', - deletedAt: 'delete_time', - paranoid: true, - getterMethods: { - createTime () { - // @ts-ignore - return new Date(this.getDataValue('create_time')).getTime(); - }, - updateTime () { - // @ts-ignore - return new Date(this.getDataValue('update_time')).getTime(); - } - } - } + merge( + { + sequelize, + tableName: 'lin_group', + modelName: 'group', + indexes: [ + { + name: 'name_del', + unique: true, + fields: ['name', 'delete_time'] + } + ] + }, + InfoCrudMixin.options + ) ); export { Group as GroupModel }; diff --git a/app/model/log.js b/app/model/log.js index ce10a47..84cee8f 100644 --- a/app/model/log.js +++ b/app/model/log.js @@ -1,5 +1,7 @@ -import sequelize from '../lib/db'; import { Model, Sequelize } from 'sequelize'; +import { InfoCrudMixin } from 'lin-mizar'; +import sequelize from '../lib/db'; +import { merge } from 'lodash'; class Log extends Model { toJSON () { @@ -54,25 +56,14 @@ Log.init( type: Sequelize.STRING(100) } }, - { - sequelize, - tableName: 'lin_log', - modelName: 'log', - createdAt: 'create_time', - updatedAt: 'update_time', - deletedAt: 'delete_time', - paranoid: true, - getterMethods: { - createTime () { - // @ts-ignore - return new Date(this.getDataValue('create_time')).getTime(); - }, - updateTime () { - // @ts-ignore - return new Date(this.getDataValue('update_time')).getTime(); - } - } - } + merge( + { + sequelize, + tableName: 'lin_log', + modelName: 'log' + }, + InfoCrudMixin.options + ) ); export { Log as LogModel }; diff --git a/app/model/permission.js b/app/model/permission.js index 507a606..40cbcb6 100644 --- a/app/model/permission.js +++ b/app/model/permission.js @@ -1,7 +1,9 @@ -import sequelize from '../lib/db'; import { Model, Sequelize, Op } from 'sequelize'; -import { routeMetaInfo } from 'lin-mizar'; +import { routeMetaInfo, InfoCrudMixin } from 'lin-mizar'; +import sequelize from '../lib/db'; import { GroupPermissionModel } from './group-permission'; +import { MountType } from '../lib/type'; +import { merge } from 'lodash'; class Permission extends Model { toJSON () { @@ -19,39 +21,43 @@ class Permission extends Model { transaction = await sequelize.transaction(); const info = Array.from(routeMetaInfo.values()); const permissions = await this.findAll(); + for (const { permission: permissionName, module: moduleName } of info) { - if ( - permissions.find( - permission => - permission.name === permissionName && - permission.module === moduleName - ) - ) { - continue; - } - await this.create( - { - name: permissionName, - module: moduleName - }, - { transaction } + const exist = permissions.find( + p => p.name === permissionName && p.module === moduleName ); + // 如果不存在这个 permission 则创建之 + if (!exist) { + await this.create( + { + name: permissionName, + module: moduleName + }, + { transaction } + ); + } } + const permissionIds = []; - for (const { id, name, module: moduleName } of permissions) { - if ( - info.find(val => val.permission === name && val.module === moduleName) - ) { - continue; + for (const permission of permissions) { + const exist = info.find( + meta => + meta.permission === permission.name && + meta.module === permission.module + ); + // 如果能找到这个 meta 则挂载之,否则卸载之 + if (exist) { + permission.mount = MountType.Mount; + } else { + permission.mount = MountType.Unmount; + permissionIds.push(permission.id); } - await this.destroy({ - where: { - id - }, + await permission.save({ transaction }); - permissionIds.push(id); } + + // 相应地要解除关联关系 if (permissionIds.length) { await GroupPermissionModel.destroy({ where: { @@ -85,25 +91,21 @@ Permission.init( type: Sequelize.STRING({ length: 50 }), comment: '权限所属模块,例如:人员管理', allowNull: false + }, + mount: { + type: Sequelize.BOOLEAN, + comment: '0:关闭 1:开启', + defaultValue: 1 } }, - { - sequelize, - tableName: 'lin_permission', - modelName: 'permission', - createdAt: 'create_time', - updatedAt: 'update_time', - deletedAt: 'delete_time', - paranoid: true, - getterMethods: { - createTime () { - return new Date(this.getDataValue('create_time')).getTime(); - }, - updateTime () { - return new Date(this.getDataValue('update_time')).getTime(); - } - } - } + merge( + { + sequelize, + tableName: 'lin_permission', + modelName: 'permission' + }, + InfoCrudMixin.options + ) ); export { Permission as PermissionModel }; diff --git a/app/model/user.js b/app/model/user.js index b0e7390..74c8d31 100644 --- a/app/model/user.js +++ b/app/model/user.js @@ -1,66 +1,72 @@ -import { NotFound, verify, AuthFailed, generate, Failed, config } from 'lin-mizar'; - +import { + NotFound, + verify, + AuthFailed, + generate, + Failed, + config, + InfoCrudMixin +} from 'lin-mizar'; import sequelize from '../lib/db'; +import { IdentityType } from '../lib/type'; import { Model, Sequelize } from 'sequelize'; -import { get, has, unset } from 'lodash'; - -const type = 'USERNAME_PASSWORD'; +import { get, has, unset, merge } from 'lodash'; class UserIdentity extends Model { + checkPassword (raw) { + if (!this.credential || this.credential === '') { + return false; + } + return verify(raw, this.credential); + } + static async verify (username, password) { const user = await this.findOne({ where: { - identity_type: type, + identity_type: IdentityType.Password, identifier: username } }); if (!user) { - throw new NotFound({ msg: '用户不存在', errorCode: 10021 }); + throw new NotFound({ code: 10021 }); } if (!user.checkPassword(password)) { - throw new AuthFailed({ msg: '用户名或密码错误', errorCode: 10031 }); + throw new AuthFailed({ code: 10031 }); } return user; } - checkPassword (raw) { - if (!this.credential || this.credential === '') { - return false; - } - return verify(raw, this.credential); - } - static async changePassword (currentUser, oldPassword, newPassword) { const user = await this.findOne({ where: { - identity_type: type, + identity_type: IdentityType.Password, identifier: currentUser.username } }); if (!user) { - throw new NotFound({ msg: '用户不存在', errorCode: 10021 }); + throw new NotFound({ code: 10021 }); } if (!user.checkPassword(oldPassword)) { throw new Failed({ - msg: '修改密码失败,你可能输入了错误的旧密码' + code: 10011 }); } user.credential = generate(newPassword); - user.save(); + await user.save(); } static async resetPassword (currentUser, newPassword) { const user = await this.findOne({ where: { - identity_type: type, + identity_type: IdentityType.Password, identifier: currentUser.username } }); if (!user) { - throw new NotFound({ msg: '用户不存在', errorCode: 10021 }); + throw new NotFound({ code: 10021 }); } user.credential = generate(newPassword); - user.save(); + await user.save(); } } @@ -90,25 +96,14 @@ UserIdentity.init( comment: '密码凭证(站内的保存密码,站外的不保存或保存token)' } }, - { - sequelize, - tableName: 'lin_user_identity', - modelName: 'user_identity', - createdAt: 'create_time', - updatedAt: 'update_time', - deletedAt: 'delete_time', - paranoid: true, - getterMethods: { - createTime () { - // @ts-ignore - return new Date(this.getDataValue('create_time')).getTime(); - }, - updateTime () { - // @ts-ignore - return new Date(this.getDataValue('update_time')).getTime(); - } - } - } + merge( + { + sequelize, + tableName: 'lin_user_identity', + modelName: 'user_identity' + }, + InfoCrudMixin.options + ) ); class User extends Model { @@ -118,7 +113,9 @@ class User extends Model { username: this.username, nickname: this.nickname, email: this.email, - avatar: `${config.getItem('siteDomain', 'http://localhost')}/assets/${this.avatar}` + avatar: `${config.getItem('siteDomain', 'http://localhost')}/assets/${ + this.avatar + }` }; if (has(this, 'groups')) { return { ...origin, groups: get(this, 'groups', []) }; @@ -163,41 +160,26 @@ User.init( allowNull: true } }, - { - sequelize, - indexes: [ - { - name: 'username_del', - unique: true, - fields: ['username', 'delete_time'] - }, - { - name: 'email_del', - unique: true, - fields: ['email', 'delete_time'] - } - ], - modelName: 'user', - tableName: 'lin_user', - createdAt: 'create_time', - updatedAt: 'update_time', - deletedAt: 'delete_time', - paranoid: true, - getterMethods: { - createTime () { - // @ts-ignore - return new Date(this.getDataValue('create_time')).getTime(); - }, - updateTime () { - // @ts-ignore - return new Date(this.getDataValue('update_time')).getTime(); - } - } - } + merge( + { + sequelize, + tableName: 'lin_user', + modelName: 'user', + indexes: [ + { + name: 'username_del', + unique: true, + fields: ['username', 'delete_time'] + }, + { + name: 'email_del', + unique: true, + fields: ['email', 'delete_time'] + } + ] + }, + InfoCrudMixin.options + ) ); -export { - type as identityType, - User as UserModel, - UserIdentity as UserIdentityModel -}; +export { User as UserModel, UserIdentity as UserIdentityModel }; diff --git a/app/starter.js b/app/starter.js index f94fa94..caad851 100644 --- a/app/starter.js +++ b/app/starter.js @@ -16,14 +16,19 @@ const { config } = require('lin-mizar/lin/config'); // }); /** - * 获取配置 + * 初始化并获取配置 */ function applyConfig () { - const cwd = process.cwd(); - const files = fs.readdirSync(path.resolve(`${cwd}/app/config`)); + // 获取工作目录 + const baseDir = path.resolve(__dirname, '../'); + config.init(baseDir); + const files = fs.readdirSync(path.resolve(`${baseDir}/app/config`)); + + // 加载 config 目录下的配置文件 for (const file of files) { config.getConfigFromFile(`app/config/${file}`); } + // 加载其它配置文件 config.getConfigFromFile('app/extension/file/config.js'); } diff --git a/jest.config.js b/jest.config.js index f332ee1..611c7b0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,5 +4,12 @@ module.exports = { coverageDirectory: 'coverage', testEnvironment: 'node', - testPathIgnorePatterns: ['/node_modules/'] + testPathIgnorePatterns: ['/node_modules/'], + testMatch: [ + '**/?(*.)(spec).js?(x)' + // '**/?(*.)(spec|test).js?(x)' + ], + transform: { + "^.+\\.[t|j]sx?$": "babel-jest" + }, }; diff --git a/package.json b/package.json index bff8f96..cedf9d3 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "simple and practical CMS implememted by koa", "main": "app/starter.js", "scripts": { + "test": "jest test", "start:dev": "cross-env NODE_ENV=development nodemon", "start:prod": "cross-env NODE_ENV=production node index", - "prettier": "prettier --write app/**/*.js app/*.js app/**/**/*.js app/**/**/**/*.js tests/**/*.js && eslint app tests --fix" + "prettier": "prettier --write app/**/*.js app/*.js app/**/**/*.js app/**/**/**/*.js test/**/*.js && eslint app test --fix" }, "keywords": [ "lin", @@ -26,6 +27,7 @@ "@types/koa__cors": "^2.2.3", "@types/prettier": "1.16.1", "@types/supertest": "^2.0.7", + "babel-jest": "^26.0.1", "babel-preset-env": "^1.7.0", "cross-env": "^5.2.0", "eslint": "^5.15.1", @@ -35,7 +37,7 @@ "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", - "jest": "^24.3.1", + "jest": "^24.9.0", "nodemon": "^1.18.10", "prettier": "1.16.4", "supertest": "^3.4.2" @@ -48,6 +50,7 @@ "koa-static": "^5.0.0", "lin-mizar": "^0.3.2", "mysql2": "^2.1.0", - "sequelize": "^5.3.5" + "sequelize": "^5.3.5", + "validator": "^13.1.1" } } diff --git a/schema.sql b/schema.sql index 8393cf2..770b369 100644 --- a/schema.sql +++ b/schema.sql @@ -54,6 +54,7 @@ CREATE TABLE lin_permission id int(10) unsigned NOT NULL AUTO_INCREMENT, name varchar(60) NOT NULL COMMENT '权限名称,例如:访问首页', module varchar(50) NOT NULL COMMENT '权限所属模块,例如:人员管理', + mount tinyint(1) NOT NULL DEFAULT 1 COMMENT '0:关闭 1:开启', create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), delete_time datetime(3) DEFAULT NULL, @@ -71,6 +72,7 @@ CREATE TABLE lin_group id int(10) unsigned NOT NULL AUTO_INCREMENT, name varchar(60) NOT NULL COMMENT '分组名称,例如:搬砖者', info varchar(255) DEFAULT NULL COMMENT '分组信息:例如:搬砖的人', + level ENUM('root', 'guest', 'user') DEFAULT 'user' COMMENT '分组级别(root、guest分组只能存在一个)', create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), delete_time datetime(3) DEFAULT NULL, @@ -177,7 +179,6 @@ SET FOREIGN_KEY_CHECKS = 1; -- ---------------------------- -- 插入超级管理员 -- 插入root分组 --- VALUES (1, 1, 'USERNAME_PASSWORD', 'root', 'sha1$c419e500$1$84869e5560ebf3de26b6690386484929456d6c07'); -- ---------------------------- BEGIN; INSERT INTO lin_user(id, username, nickname) @@ -188,29 +189,13 @@ INSERT INTO lin_user_identity (id, user_id, identity_type, identifier, credentia VALUES (1, 1, 'USERNAME_PASSWORD', 'root', 'sha1$c419e500$1$84869e5560ebf3de26b6690386484929456d6c07'); -INSERT INTO lin_group(id, name, info) -VALUES (1, 'root', '超级用户组'); +INSERT INTO lin_group(id, name, info, level) +VALUES (1, 'root', '超级用户组', 'root'); -INSERT INTO lin_group(id, name, info) -VALUES (2, 'guest', '游客组'); +INSERT INTO lin_group(id, name, info, level) +VALUES (2, 'guest', '游客组', 'guest'); INSERT INTO lin_user_group(id, user_id, group_id) VALUES (1, 1, 1); COMMIT; - --- ---------------------------- --- -- 测试用户是否有无权限 --- -- ---------------------------- --- BEGIN; - --- INSERT INTO lin_user_group(id, user_id, group_id) --- VALUES (1, 1, 1); --- -- --- -- 从分组找 --- -- --- -- SELECT * --- -- from lin_group_permission --- -- WHERE group_id in (SELECT group_id FROM lin_user_group WHERE user_id = 1); - --- COMMIT; \ No newline at end of file diff --git a/test/api/cms/admin.spec.js b/test/api/cms/admin.spec.js new file mode 100644 index 0000000..2ed0aa2 --- /dev/null +++ b/test/api/cms/admin.spec.js @@ -0,0 +1,328 @@ +import '../../helper/initial'; +import request from 'supertest'; +import { generate } from 'lin-mizar'; +import { createApp } from '../../../app/app'; +import { IdentityType } from '../../../app/lib/type'; +import sequelize from '../../../app/lib/db'; +import { saveTokens, getToken } from '../../helper/token'; +import { get, isNumber, isArray } from 'lodash'; + +const sleep = (time) => new Promise(resolve => { + setTimeout(() => { + resolve(); + }, time); +}); + +describe('/cms/admin', () => { + const { UserModel, UserIdentityModel } = require('../../../app/model/user'); + const { GroupModel } = require('../../../app/model/group'); + const { + GroupPermissionModel + } = require('../../../app/model/group-permission'); + const { UserGroupModel } = require('../../../app/model/user-group'); + const { PermissionModel } = require('../../../app/model/permission'); + + let app; + + let token; + + beforeAll(async () => { + // 初始化 app + app = await createApp(); + }); + + beforeEach(async () => { + await sleep(100); + await sequelize.query('START TRANSACTION;'); + await sleep(100); + }); + + afterEach(async () => { + await sleep(100); + await sequelize.query('ROLLBACK;'); + await sleep(100); + }); + + afterAll(() => { + setTimeout(() => { + sequelize.close(); + }, 500); + }); + + it('超级管理员登录', async () => { + const response = await request(app.callback()) + .post('/cms/user/login') + .send({ + username: 'root', + password: '123456' + }); + saveTokens(response.body); + token = getToken(); + expect(response.status).toBe(200); + expect(response.type).toMatch(/json/); + }); + + it('查询所有可分配的权限', async () => { + const response = await request(app.callback()) + .get('/cms/admin/permission') + .auth(token, { + type: 'bearer' + }); + expect(response.status).toBe(200); + expect(response.type).toMatch(/json/); + const is = isArray(get(response, 'body.日志')); + expect(is).toBeTruthy(); + }); + + it('查询所有用户', async () => { + const response = await request(app.callback()) + .get('/cms/admin/users') + .auth(token, { + type: 'bearer' + }); + expect(response.status).toBe(200); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.count')).toBe(10); + const is = isNumber(get(response, 'body.total')); + expect(is).toBeTruthy(); + }); + + it('插入用户信息、分组、权限,查询所有用户', async () => { + const user = await UserModel.create({ username: 'shirmy', email: 'shirmy@gmail.com' }); + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + await UserGroupModel.create({ group_id: group.id, user_id: user.id }); + + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + await GroupPermissionModel.create({ group_id: group.id, permission_id: permission.id }); + + const response = await request(app.callback()) + .get('/cms/admin/users') + .auth(token, { + type: 'bearer' + }) + .send({ + group_id: group.id + }); + expect(response.status).toBe(200); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.count')).toBe(10); + const is = isNumber(get(response, 'body.total')); + expect(is).toBeTruthy(); + }); + + it('修改用户密码', async () => { + const user = await UserModel.create({ username: 'shirmy', email: 'shirmy@gmail.com' }); + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + await UserGroupModel.create({ group_id: group.id, user_id: user.id }); + await UserIdentityModel.create({ + user_id: user.id, + identity_type: IdentityType.Password, + identifier: user.username, + credential: generate('123456') + }); + + const newPassword = '654321'; + + const response = await request(app.callback()) + .put(`/cms/admin/user/${user.id}/password`) + .auth(token, { + type: 'bearer' + }) + .send({ + new_password: newPassword, + confirm_password: newPassword + }); + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(4); + expect(get(response, 'body.message')).toBe('密码修改成功'); + }); + + it('删除用户', async () => { + const user = await UserModel.create({ username: 'shirmy', email: 'shirmy@gmail.com' }); + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + await UserGroupModel.create({ group_id: group.id, user_id: user.id }); + + const response = await request(app.callback()) + .delete(`/cms/admin/user/${user.id}`) + .auth(token, { + type: 'bearer' + }); + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(5); + expect(get(response, 'body.message')).toBe('删除用户成功'); + }); + + it('更新用户', async () => { + const user = await UserModel.create({ username: 'shirmy', email: 'shirmy@gmail.com' }); + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + await UserGroupModel.create({ group_id: group.id, user_id: user.id }); + + const response = await request(app.callback()) + .put(`/cms/admin/user/${user.id}`) + .auth(token, { type: 'bearer' }) + .send({ + group_ids: [group.id] + }); + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(6); + expect(get(response, 'body.message')).toBe('更新用户成功'); + }); + + it('查询所有权限组及其权限', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + await GroupPermissionModel.create({ group_id: group.id, permission_id: permission.id }); + + const response = await request(app.callback()) + .get('/cms/admin/group') + .auth(token, { type: 'bearer' }); + + expect(response.status).toBe(200); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.count')).toBe(10); + const is = isNumber(get(response, 'body.total')); + expect(is).toBeTruthy(); + }); + + it('查询所有权限组', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + await GroupPermissionModel.create({ group_id: group.id, permission_id: permission.id }); + + const response = await request(app.callback()) + .get('/cms/admin/group/all') + .auth(token, { type: 'bearer' }); + + expect(response.status).toBe(200); + expect(response.type).toMatch(/json/); + const is = isArray(get(response, 'body')); + expect(is).toBeTruthy(); + }); + + it('查询一个权限组及其权限', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + await GroupPermissionModel.create({ group_id: group.id, permission_id: permission.id }); + + const response = await request(app.callback()) + .get(`/cms/admin/group/${group.id}`) + .auth(token, { type: 'bearer' }); + + expect(response.status).toBe(200); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.name')).toBe(group.name); + const hasPermission = !!get(response, 'body.permissions').find(v => v.id === permission.id); + expect(hasPermission).toBeTruthy(); + }); + + it('新建权限组', async () => { + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + + const response = await request(app.callback()) + .post('/cms/admin/group') + .auth(token, { type: 'bearer' }) + .send({ + name: 'new group name', + info: 'new group info', + permission_ids: [permission.id] + }); + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(15); + expect(get(response, 'body.message')).toBe('新建分组成功'); + }); + + it('更新一个权限组', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + await GroupPermissionModel.create({ group_id: group.id, permission_id: permission.id }); + + const response = await request(app.callback()) + .put(`/cms/admin/group/${group.id}`) + .auth(token, { type: 'bearer' }) + .send({ + name: 'new group name', + info: 'new group info' + }); + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(7); + expect(get(response, 'body.message')).toBe('更新分组成功'); + + const newGroup = await GroupModel.findByPk(group.id); + expect(newGroup.name).toBe('new group name'); + }); + + it('删除一个权限组', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + + const response = await request(app.callback()) + .delete(`/cms/admin/group/${group.id}`) + .auth(token, { type: 'bearer' }); + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(8); + expect(get(response, 'body.message')).toBe('删除分组成功'); + }); + + it('分配单个权限', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + + const response = await request(app.callback()) + .post('/cms/admin/permission/dispatch') + .auth(token, { type: 'bearer' }) + .send({ + group_id: group.id, + permission_id: permission.id + }); + + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(9); + expect(get(response, 'body.message')).toBe('添加权限成功'); + }); + + it('分配多个权限', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + const permission1 = await PermissionModel.create({ name: '查看研发组的信息', module: '信息' }); + + const response = await request(app.callback()) + .post('/cms/admin/permission/dispatch/batch') + .auth(token, { type: 'bearer' }) + .send({ + group_id: group.id, + permission_ids: [permission.id, permission1.id] + }); + + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(9); + expect(get(response, 'body.message')).toBe('添加权限成功'); + }); + + it('删除多个权限', async () => { + const group = await GroupModel.create({ name: '研发组', info: '研发大佬' }); + const permission = await PermissionModel.create({ name: '查看信息', module: '信息' }); + const permission1 = await PermissionModel.create({ name: '查看研发组的信息', module: '信息' }); + await GroupPermissionModel.create({ group_id: group.id, permission_id: permission.id }); + await GroupPermissionModel.create({ group_id: group.id, permission_id: permission1.id }); + + const response = await request(app.callback()) + .post('/cms/admin/permission/remove') + .auth(token, { type: 'bearer' }) + .send({ + group_id: group.id, + permission_ids: [permission.id, permission1.id] + }); + + expect(response.status).toBe(201); + expect(response.type).toMatch(/json/); + expect(get(response, 'body.code')).toBe(10); + expect(get(response, 'body.message')).toBe('删除权限成功'); + }); +}); diff --git a/test/api/cms/test1.test.js b/test/api/cms/test1.test.js index ac0e5bd..c43018e 100644 --- a/test/api/cms/test1.test.js +++ b/test/api/cms/test1.test.js @@ -37,7 +37,7 @@ describe('test1.test.js', () => { confirm_password: '123456' }); expect(response.status).toBe(400); - expect(response.body).toHaveProperty('error_code', 10030); + expect(response.body).toHaveProperty('code', 10030); expect(response.type).toMatch(/json/); }); -}); \ No newline at end of file +}); diff --git a/test/helper/fake-book/fake-book.js b/test/helper/fake-book/fake-book.js index 867c3d0..9cf5435 100644 --- a/test/helper/fake-book/fake-book.js +++ b/test/helper/fake-book/fake-book.js @@ -24,4 +24,4 @@ const run = async () => { }, 500); }; -run(); \ No newline at end of file +run(); diff --git a/test/helper/fake/fake.js b/test/helper/fake/fake.js index 65e6832..734e080 100644 --- a/test/helper/fake/fake.js +++ b/test/helper/fake/fake.js @@ -1,13 +1,12 @@ import '../initial'; import sequelize from '../../../app/lib/db'; +import { MountType, IdentityType } from '../../../app/lib/type'; import { generate } from 'lin-mizar'; import { UserModel, UserIdentityModel } from '../../../app/model/user'; import { GroupModel } from '../../../app/model/group'; import { PermissionModel } from '../../../app/model/permission'; import { GroupPermissionModel } from '../../../app/model/group-permission'; -const type = 'USERNAME_PASSWORD'; - /** * 如果创建失败,请确保你的数据库中没有同名的分组和同名的用户 */ @@ -26,7 +25,7 @@ const run = async () => { // 创建用户密码 await UserIdentityModel.create({ user_id: user.id, - identity_type: type, + identity_type: IdentityType.Password, identifier: user.username, credential: generate('123456') }); @@ -35,7 +34,8 @@ const run = async () => { const permission = await PermissionModel.findOne({ where: { name: '删除图书', - module: '图书' + module: '图书', + mount: MountType.Mount } }); @@ -51,40 +51,3 @@ const run = async () => { }; run(); - -// /** -// * 权限分配,关联用户和权限组 -// */ -// import '../initial'; -// import sequelize from '../../../app/lib/db'; -// import { UserModel } from '../../../app/model/user'; -// import { GroupModel } from '../../../app/model/group'; -// import { UserGroupModel } from '../../../app/model/user-group'; - -// const run = async () => { -// // 查找需要关联的权限组 id -// const group = await GroupModel.findOne({ -// where: { -// name: '普通分组' -// } -// }); - -// // 查找 pedro 用户的 id 用去关联权限组 -// const user = await UserModel.findOne({ -// where: { -// username: 'pedro' -// } -// }); - -// // 关联用户和权限组 -// await UserGroupModel.create({ -// user_id: user.id, -// group_id: group.id -// }); - -// setTimeout(() => { -// sequelize.close(); -// }, 500); -// }; - -// run(); \ No newline at end of file diff --git a/test/helper/initial.js b/test/helper/initial.js index 760e511..1d5a02e 100644 --- a/test/helper/initial.js +++ b/test/helper/initial.js @@ -4,8 +4,10 @@ const { config } = require('lin-mizar/lin/config'); (() => { const settings = require('../../app/config/setting'); const secure = require('../../app/config/secure'); + const codeMessage = require('../../app/config/code-message'); config.getConfigFromObj({ ...settings, - ...secure + ...secure, + ...codeMessage }); })(); diff --git a/test/helper/poem/poem.js b/test/helper/poem/poem.js index d82c815..c1a2ffc 100644 --- a/test/helper/poem/poem.js +++ b/test/helper/poem/poem.js @@ -66,4 +66,4 @@ const run = async () => { }, 500); }; -run(); \ No newline at end of file +run(); From 85b07fbe8a54cc4e155b46afa0908f128b9aa5b5 Mon Sep 17 00:00:00 2001 From: qiushiming Date: Sun, 21 Jun 2020 17:07:46 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(*):=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3;=20=E6=9B=B4=E6=96=B0=20group=20level=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++-- app/api/cms/admin.js | 4 +- app/api/cms/user.js | 4 +- app/lib/type.js | 6 +-- app/model/group.js | 6 +-- package.json | 6 +-- schema.sql | 6 +-- test/api/cms/{admin.spec.js => admin.test.js} | 41 +++++++++---------- test/helper/initial.js | 2 +- test/helper/secure.js | 16 ++++++++ 10 files changed, 65 insertions(+), 40 deletions(-) rename test/api/cms/{admin.spec.js => admin.test.js} (93%) create mode 100644 test/helper/secure.js diff --git a/README.md b/README.md index 9896b6a..dcd07ec 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ Lin-CMS 是林间有风团队经过大量项目实践所提炼出的一套**内 ### 当前最新版本 -lin-mizar(核心库) :0.3.2 +lin-mizar(核心库) :0.3.3 -lin-cms-koa(当前示例工程):0.3.3 +lin-cms-koa(当前示例工程):0.3.4 ### 文档地址 @@ -61,7 +61,15 @@ QQ 群号:643205479 ## 版本日志 -最新版本 `0.3.3` +最新版本 `0.3.4` + +### 0.3.4 + +1. `U` 更新路由视图权限挂载的方式 +2. `U` HttpException 不允许直接修改 status,传入的参数由 errorCode 改为 code +3. `U` 新增 code-message 配置,返回的成功码和错误码都在这里配置 +4. `U` 支持自定义工作目录 +5. `U` 更新核心库 lin-mizar 到 0.3.3 版本 ### 0.3.3 diff --git a/app/api/cms/admin.js b/app/api/cms/admin.js index 9bf04de..cb5e092 100644 --- a/app/api/cms/admin.js +++ b/app/api/cms/admin.js @@ -16,7 +16,9 @@ import { AdminDao } from '../../dao/admin'; const admin = new LinRouter({ prefix: '/cms/admin', - module: '管理员' + module: '管理员', + // 管理员权限暂不支持分配,开启分配后也无实际作用 + mountPermission: false }); const adminDao = new AdminDao(); diff --git a/app/api/cms/user.js b/app/api/cms/user.js index 8e533c4..0a5fdbf 100644 --- a/app/api/cms/user.js +++ b/app/api/cms/user.js @@ -17,7 +17,9 @@ import { UserDao } from '../../dao/user'; const user = new LinRouter({ prefix: '/cms/user', - module: '用户' + module: '用户', + // 用户权限暂不支持分配,开启分配后也无实际作用 + mountPermission: false }); const userDao = new UserDao(); diff --git a/app/lib/type.js b/app/lib/type.js index fc70e6b..df56665 100644 --- a/app/lib/type.js +++ b/app/lib/type.js @@ -8,9 +8,9 @@ const IdentityType = { }; const GroupLevel = { - Root: 'root', - Guest: 'guest', - User: 'user' + Root: 1, + Guest: 2, + User: 3 }; export { MountType, IdentityType, GroupLevel }; diff --git a/app/model/group.js b/app/model/group.js index 7678599..8e35545 100644 --- a/app/model/group.js +++ b/app/model/group.js @@ -35,9 +35,9 @@ Group.init( comment: '分组信息:例如:搬砖的人' }, level: { - type: Sequelize.ENUM('root', 'guest', 'user'), - defaultValue: 'user', - comment: '分组级别(root、guest分组只能存在一个)' + type: Sequelize.INTEGER(2), + defaultValue: 3, + comment: '分组级别 1:root 2:guest 3:user(root、guest分组只能存在一个)' } }, merge( diff --git a/package.json b/package.json index cedf9d3..5282981 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lin-cms-koa", - "version": "0.3.3", + "version": "0.3.4", "description": "simple and practical CMS implememted by koa", "main": "app/starter.js", "scripts": { @@ -48,9 +48,9 @@ "koa-bodyparser": "^4.2.1", "koa-mount": "^4.0.0", "koa-static": "^5.0.0", - "lin-mizar": "^0.3.2", + "lin-mizar": "^0.3.3", "mysql2": "^2.1.0", - "sequelize": "^5.3.5", + "sequelize": "^5.21.13", "validator": "^13.1.1" } } diff --git a/schema.sql b/schema.sql index 770b369..422d837 100644 --- a/schema.sql +++ b/schema.sql @@ -72,7 +72,7 @@ CREATE TABLE lin_group id int(10) unsigned NOT NULL AUTO_INCREMENT, name varchar(60) NOT NULL COMMENT '分组名称,例如:搬砖者', info varchar(255) DEFAULT NULL COMMENT '分组信息:例如:搬砖的人', - level ENUM('root', 'guest', 'user') DEFAULT 'user' COMMENT '分组级别(root、guest分组只能存在一个)', + level tinyint(2) NOT NULL DEFAULT 3 COMMENT '分组级别 1:root 2:guest 3:user(root、guest分组只能存在一个)', create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), delete_time datetime(3) DEFAULT NULL, @@ -190,10 +190,10 @@ VALUES (1, 1, 'USERNAME_PASSWORD', 'root', 'sha1$c419e500$1$84869e5560ebf3de26b6690386484929456d6c07'); INSERT INTO lin_group(id, name, info, level) -VALUES (1, 'root', '超级用户组', 'root'); +VALUES (1, 'root', '超级用户组', 1); INSERT INTO lin_group(id, name, info, level) -VALUES (2, 'guest', '游客组', 'guest'); +VALUES (2, 'guest', '游客组', 2); INSERT INTO lin_user_group(id, user_id, group_id) VALUES (1, 1, 1); diff --git a/test/api/cms/admin.spec.js b/test/api/cms/admin.test.js similarity index 93% rename from test/api/cms/admin.spec.js rename to test/api/cms/admin.test.js index 2ed0aa2..4213e3f 100644 --- a/test/api/cms/admin.spec.js +++ b/test/api/cms/admin.test.js @@ -7,12 +7,6 @@ import sequelize from '../../../app/lib/db'; import { saveTokens, getToken } from '../../helper/token'; import { get, isNumber, isArray } from 'lodash'; -const sleep = (time) => new Promise(resolve => { - setTimeout(() => { - resolve(); - }, time); -}); - describe('/cms/admin', () => { const { UserModel, UserIdentityModel } = require('../../../app/model/user'); const { GroupModel } = require('../../../app/model/group'); @@ -26,27 +20,28 @@ describe('/cms/admin', () => { let token; - beforeAll(async () => { + beforeAll(async (done) => { + console.log('start admin'); // 初始化 app app = await createApp(); + done(); }); - beforeEach(async () => { - await sleep(100); - await sequelize.query('START TRANSACTION;'); - await sleep(100); - }); - - afterEach(async () => { - await sleep(100); - await sequelize.query('ROLLBACK;'); - await sleep(100); + afterAll(async (done) => { + setTimeout(async () => { + await sequelize.close(); + done(); + }, 500); }); - afterAll(() => { - setTimeout(() => { - sequelize.close(); - }, 500); + beforeEach(async (done) => { + await sequelize.sync({ force: true }); + await UserModel.create({ username: 'root', nickname: 'root' }); + await UserIdentityModel.create({ user_id: 1, identity_type: IdentityType.Password, identifier: 'root', credential: 'sha1$c419e500$1$84869e5560ebf3de26b6690386484929456d6c07' }); + await GroupModel.create({ name: 'root', info: '超级用户组', level: 1 }); + await GroupModel.create({ name: 'guest', info: '游客组', level: 2 }); + await UserGroupModel.create({ user_id: 1, group_id: 1 }); + done(); }); it('超级管理员登录', async () => { @@ -63,6 +58,8 @@ describe('/cms/admin', () => { }); it('查询所有可分配的权限', async () => { + await PermissionModel.create({ name: '查看信息', module: '信息' }); + const response = await request(app.callback()) .get('/cms/admin/permission') .auth(token, { @@ -70,7 +67,7 @@ describe('/cms/admin', () => { }); expect(response.status).toBe(200); expect(response.type).toMatch(/json/); - const is = isArray(get(response, 'body.日志')); + const is = isArray(get(response, 'body.信息')); expect(is).toBeTruthy(); }); diff --git a/test/helper/initial.js b/test/helper/initial.js index 1d5a02e..2957c8b 100644 --- a/test/helper/initial.js +++ b/test/helper/initial.js @@ -3,7 +3,7 @@ const { config } = require('lin-mizar/lin/config'); // 初始化数据库配置 (() => { const settings = require('../../app/config/setting'); - const secure = require('../../app/config/secure'); + const secure = require('./secure'); const codeMessage = require('../../app/config/code-message'); config.getConfigFromObj({ ...settings, diff --git a/test/helper/secure.js b/test/helper/secure.js new file mode 100644 index 0000000..377657c --- /dev/null +++ b/test/helper/secure.js @@ -0,0 +1,16 @@ +'use strict'; + +module.exports = { + db: { + database: 'lin-cms-test', + host: 'localhost', + dialect: 'mysql', + port: 3306, + username: 'root', + password: '123456', + logging: false, + timezone: '+08:00' + }, + secret: + '\x88W\xf09\x91\x07\x98\x89\x87\x96\xa0A\xc68\xf9\xecJJU\x17\xc5V\xbe\x8b\xef\xd7\xd8\xd3\xe6\x95*4' +};