├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .vscode └── launch.json ├── README.md ├── package.json ├── src ├── api │ └── v1 │ │ ├── gameMarker │ │ └── query.ts │ │ └── system │ │ ├── auth │ │ ├── login.ts │ │ ├── logout.ts │ │ ├── register.ts │ │ └── sendCodeEmail.ts │ │ ├── common │ │ ├── code.ts │ │ └── upload │ │ │ └── img.ts │ │ ├── menu │ │ ├── add.ts │ │ ├── delete.ts │ │ ├── edit.ts │ │ ├── getMenuByRoleId.ts │ │ ├── getMenuMap.ts │ │ └── list.ts │ │ ├── role │ │ ├── add.ts │ │ ├── delete.ts │ │ ├── edit.ts │ │ ├── editPermission.ts │ │ ├── getRoleMap.ts │ │ └── list.ts │ │ └── user │ │ ├── add.ts │ │ ├── edit.ts │ │ ├── editInfo.ts │ │ ├── editPassword.ts │ │ ├── getUserMenu.ts │ │ ├── list.ts │ │ └── query.ts ├── app.ts ├── common │ ├── apiJsonSchema │ │ ├── common │ │ │ └── pagination.ts │ │ ├── lowCode │ │ │ ├── board │ │ │ │ ├── add.ts │ │ │ │ ├── delete.ts │ │ │ │ ├── editBoardAttrById.ts │ │ │ │ ├── editBoardPageById.ts │ │ │ │ └── query.ts │ │ │ ├── boardCategoryManager │ │ │ │ └── getBoardAndCategoryListByParentId.ts │ │ │ ├── component │ │ │ │ ├── addComponent.ts │ │ │ │ ├── editComponentAttrById.ts │ │ │ │ └── getComponentById.ts │ │ │ └── componentCategoryManager │ │ │ │ └── list.ts │ │ └── system │ │ │ ├── auth │ │ │ ├── login.ts │ │ │ ├── logout.ts │ │ │ ├── register.ts │ │ │ └── sendCodeEmail.ts │ │ │ ├── menu │ │ │ ├── add.ts │ │ │ ├── deleteMenuByIds.ts │ │ │ └── edit.ts │ │ │ ├── role │ │ │ ├── addRole.ts │ │ │ ├── delete.ts │ │ │ ├── edit.ts │ │ │ └── editPermission.ts │ │ │ └── user │ │ │ ├── add.ts │ │ │ ├── edit.ts │ │ │ ├── editInfo.ts │ │ │ └── editPassword.ts │ ├── typings │ │ ├── index.ts │ │ ├── lowCode.d.ts │ │ └── model.d.ts │ └── utils │ │ ├── date.ts │ │ ├── result.ts │ │ └── utils.ts ├── config │ ├── Config.ts │ └── RedisDbName.ts ├── core │ ├── HttpException.ts │ ├── Init.ts │ └── code.ts ├── middlewares │ ├── catchError.ts │ ├── validator.ts │ ├── verificationCodeValidator.ts │ └── verifyToken.ts ├── plugin │ ├── WebSocket │ │ └── index.ts │ └── index.ts └── server │ ├── ajv │ ├── ajv.ts │ ├── ajvConfig.ts │ └── index.ts │ ├── auth │ ├── index.ts │ └── token.ts │ ├── db │ ├── dbConfing.ts │ └── index.ts │ ├── logs │ ├── index.ts │ ├── logger.ts │ └── logsConfing.ts │ ├── mailer │ ├── index.ts │ ├── mailerConfing.ts │ └── transporter.ts │ ├── mysql │ ├── index.ts │ ├── mysqlConfing.ts │ └── pool.ts │ ├── qiniu │ ├── index.ts │ └── qiniuConfig.ts │ └── redis │ ├── index.ts │ ├── redis.ts │ └── redisConfing.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | *.d.ts -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "env": { 5 | "node": true, 6 | "es2021": true 7 | }, 8 | 9 | "parser": "@typescript-eslint/parser", 10 | 11 | "parserOptions": { 12 | "ecmaVersion": 12, 13 | "sourceType": "module", 14 | // "tsconfigRootDir": __dirname, 15 | "project": ["./tsconfig.json"] 16 | }, 17 | 18 | "plugins": ["@typescript-eslint"], 19 | "rules": { 20 | "@typescript-eslint/no-unsafe-assignment": "off", 21 | "@typescript-eslint/no-non-null-assertion": "off", 22 | "no-useless-escape": "off", 23 | "@typescript-eslint/no-unsafe-member-access": "off", 24 | "@typescript-eslint/unbound-method": "off", 25 | // 'prettier/prettier': 'error', 26 | "@typescript-eslint/await-thnable": "off", 27 | "@typescript-eslint/restrict-template-expressions": "off", 28 | "@typescript-eslint/no-misused-promises": "off", 29 | "@typescript-eslint/no-explicit-any": "off", 30 | "@typescript-eslint/no-unsafe-call": "off", 31 | "@typescript-eslint/no-unsafe-argument": "off", 32 | "no-async-promise-executor": "off", 33 | "@typescript-eslint/no-floating-promises": "off", 34 | "@typescript-eslint/require-await": "off", 35 | "@typescript-eslint/no-var-requires": "off", 36 | "@typescript-eslint/ban-types": "off", 37 | "no-prototype-builtins": "off", 38 | "space-before-function-paren": 0 39 | }, 40 | 41 | "extends": [ 42 | "eslint:recommended", 43 | "plugin:@typescript-eslint/recommended", 44 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 45 | "prettier" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /logs 3 | 4 | dist/ 5 | 6 | package-lock.json 7 | yarn-lock.json 8 | yarn.lock 9 | 10 | /src/config/Config.ts 11 | 12 | /src/ak.ts -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": false, 4 | "singleQuote": true, 5 | "prettier.spaceBeforeFunctionParen": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}\\src\\config\\Config.ts", 15 | "outFiles": [ 16 | "${workspaceFolder}/**/*.js" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 超细致 nodejs + koa2 + ts + mysql + redis 后端框架搭建 2 | 3 | 目录 4 | 5 | - 1.后端环境搭建和常见中间件使用和开发 6 | 7 | - 2.注册和登录相关功能的设计与实现 8 | 9 | - 3.权限模块的设计与实现 10 | 11 | - 4.使用 koa 将客户端的图片上传至七牛云 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-ts-learn", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development nodemon ./src/app.ts", 8 | "build": "tsc && cross-env NODE_ENV=production node dist/src/app.js", 9 | "lint": "eslint . --ext .ts --fix" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "ajv": "^8.11.0", 16 | "ajv-keywords": "^5.1.0", 17 | "ioredis": "^5.0.4", 18 | "jsonwebtoken": "^8.5.1", 19 | "koa": "^2.13.4", 20 | "koa-body": "^5.0.0", 21 | "koa-bodyparser": "^4.3.0", 22 | "koa-router": "^10.1.1", 23 | "koa-session": "^6.2.0", 24 | "koa-static": "^5.0.0", 25 | "koa2-cors": "^2.0.6", 26 | "log4js": "^6.4.6", 27 | "mongodb": "^4.5.0", 28 | "mysql": "^2.18.1", 29 | "nodemailer": "^6.7.5", 30 | "qiniu": "^7.7.0", 31 | "socket.io": "^4.5.2", 32 | "svg-captcha": "^1.4.0", 33 | "validator": "^13.7.0" 34 | }, 35 | "devDependencies": { 36 | "@fhtwl-admin/common": "0.0.7", 37 | "@fhtwl-admin/system": "0.0.9", 38 | "@types/ajv": "^1.0.0", 39 | "@types/ajv-keywords": "^3.5.0", 40 | "@types/ioredis": "^4.28.10", 41 | "@types/jsonwebtoken": "^8.5.8", 42 | "@types/koa": "^2.13.4", 43 | "@types/koa-bodyparser": "^4.3.7", 44 | "@types/koa-router": "^7.4.4", 45 | "@types/koa-session": "^5.10.6", 46 | "@types/koa-static": "^4.0.2", 47 | "@types/koa2-cors": "^2.0.2", 48 | "@types/log4js": "^2.3.5", 49 | "@types/moment": "^2.13.0", 50 | "@types/mongodb": "^4.0.7", 51 | "@types/mysql": "^2.15.21", 52 | "@types/nodemailer": "^6.4.4", 53 | "@types/qiniu": "^7.0.1", 54 | "@types/validator": "^13.7.2", 55 | "@typescript-eslint/eslint-plugin": "^5.21.0", 56 | "@typescript-eslint/parser": "^5.21.0", 57 | "cross-env": "^7.0.3", 58 | "eslint": "^8.14.0", 59 | "eslint-config-prettier": "^8.5.0", 60 | "moment": "^2.29.3", 61 | "nodemon": "^2.0.15", 62 | "prettier": "^2.6.2", 63 | "ts-node": "^10.7.0", 64 | "typescript": "^4.6.4" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/api/v1/gameMarker/query.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Models } from '../../../common/typings/model' 3 | import { command } from '../../../server/mysql' 4 | import { Success } from '../../../core/HttpException' 5 | import Config from '../../../config/Config' 6 | 7 | const router = new KoaRouter({ 8 | prefix: `${Config.API_PREFIX}v1/gameMarker`, 9 | }) 10 | 11 | router.get('/query', async (ctx: Models.Ctx) => { 12 | // 查询获取所有的菜单(包括菜单目录和按钮) 13 | const { name = '' } = ctx.query 14 | const res = ( 15 | await command(` 16 | SELECT 17 | code, 18 | name 19 | FROM 20 | game_marker_list 21 | WHERE 22 | name like '%${name}%' 23 | `) 24 | ).results 25 | throw new Success(res) 26 | }) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /src/api/v1/system/auth/login.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Models } from '../../../../common/typings/model' 3 | import { command } from '../../../../server/mysql' 4 | import { ParameterException, QueryFailed, Success } from '../../../../core/HttpException' 5 | import Config from '../../../../config/Config' 6 | import validator from '../../../../middlewares/validator' 7 | import schema from '../../../../common/apiJsonSchema/system/auth/login' 8 | import verificationCodeValidator from '../../../../middlewares/verificationCodeValidator' 9 | import { generateToken, updateRedisRole } from '../../../../server/auth' 10 | import { saveToken } from '../../../../server/auth/token' 11 | 12 | const router = new KoaRouter({ 13 | prefix: `${Config.API_PREFIX}v1/system/auth`, 14 | }) 15 | 16 | router.post('/login', validator(schema, 'body'), verificationCodeValidator, async (ctx: Models.Ctx) => { 17 | const { password, userName } = ctx.request.body 18 | const res: Models.Result = await command(` 19 | SELECT 20 | id,email,deleted,info,role_ids,password 21 | FROM 22 | system_user 23 | where 24 | user_name = '${userName}' 25 | `) 26 | if ((res.results as System.User[]).length > 0) { 27 | const user = res.results[0] 28 | const token = getToken(user, password) 29 | saveToken(token, user.id) 30 | updateRedisRole() 31 | throw new Success(token) 32 | } else { 33 | throw new QueryFailed('该用户名不存在') 34 | } 35 | }) 36 | 37 | export default router 38 | 39 | /** 40 | * 获取token 41 | * @param user 42 | * @param password 43 | * @returns 44 | */ 45 | function getToken(user: System.User, password: string): string { 46 | if (user.password !== password) { 47 | throw new ParameterException('密码不正确') 48 | } 49 | return generateToken(user.id, user.roleIds) 50 | } 51 | -------------------------------------------------------------------------------- /src/api/v1/system/auth/logout.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Success } from '../../../../core/HttpException' 3 | import Config from '../../../../config/Config' 4 | import { Models } from '../../../../common/typings/model' 5 | import { getToken } from '../../../../middlewares/verifyToken' 6 | import { deleteToken } from '../../../../server/auth/token' 7 | 8 | const router = new KoaRouter({ 9 | prefix: `${Config.API_PREFIX}v1/system/auth`, 10 | }) 11 | 12 | /* 13 | * 退出登录 14 | */ 15 | router.get('/logout', async (ctx: Models.Ctx) => { 16 | await deleteToken(getToken(ctx)) 17 | throw new Success() 18 | }) 19 | 20 | export default router 21 | -------------------------------------------------------------------------------- /src/api/v1/system/auth/register.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Models } from '../../../../common/typings/model' 3 | import { command } from '../../../../server/mysql' 4 | import { Success } from '../../../../core/HttpException' 5 | import { format } from '../../../../common/utils/date' 6 | import Config from '../../../../config/Config' 7 | import validator from '../../../../middlewares/validator' 8 | import schema from '../../../../common/apiJsonSchema/system/auth/register' 9 | import verificationCodeValidator from '../../../../middlewares/verificationCodeValidator' 10 | 11 | const router = new KoaRouter({ 12 | prefix: `${Config.API_PREFIX}v1/system/auth`, 13 | }) 14 | 15 | router.post('/register', validator(schema, 'body'), verificationCodeValidator, async (ctx: Models.Ctx) => { 16 | const { password, userName, email } = ctx.request.body 17 | const date = format(new Date()) 18 | 19 | // 注册 20 | await command(` 21 | INSERT INTO system_user ( user_name, email, password, role_ids, created_at, updated_at ) 22 | VALUES 23 | ( '${userName}', '${email}', '${password}', '2', '${date}', '${date}' ); 24 | `) 25 | 26 | throw new Success() 27 | }) 28 | 29 | export default router 30 | -------------------------------------------------------------------------------- /src/api/v1/system/auth/sendCodeEmail.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Models } from '../../../../common/typings/model' 3 | import Config from '../../../../config/Config' 4 | import { HttpException, ParameterException, Success } from '../../../../core/HttpException' 5 | import { sendEmail } from '../../../../server/mailer' 6 | import { command } from '../../../../server/mysql' 7 | import validator from '../../../../middlewares/validator' 8 | import schema from '../../../../common/apiJsonSchema/system/auth/sendCodeEmail' 9 | 10 | const router = new KoaRouter({ 11 | prefix: `${Config.API_PREFIX}v1/system/auth`, 12 | }) 13 | 14 | router.post('/sendCodeEmail', validator(schema, 'body'), async (ctx: Models.Ctx) => { 15 | const { email, userName } = ctx.request.body 16 | // await checkUserNameAndEmail(userName, email) 17 | const code = (Math.random() * 1000000).toFixed() 18 | // 在会话中添加验证码字段code 19 | ctx.session!.code = code 20 | try { 21 | // 发送邮件 22 | await sendEmail({ 23 | to: email, 24 | subject: '验证码', 25 | text: '验证码', 26 | html: ` 27 |
28 |

您正在注册FHTWL低代码平台帐号,用户名${userName}, 29 | 验证邮箱为${email} 。 30 | 验证码为:

31 |

32 | ${code} 33 |

34 |

请在注册页面填写该改验证码

35 |
36 | `, 37 | }) 38 | } catch (error) { 39 | throw new ParameterException(error as string) 40 | } 41 | throw new Success() 42 | }) 43 | 44 | export default router 45 | 46 | /** 47 | * 邮箱和用户名作为唯一值需要校验是否已经有用户在使用 48 | * @param { string } userName 49 | * @param { string } email 50 | * @returns 51 | */ 52 | async function checkUserNameAndEmail(userName: string, email: string) { 53 | return new Promise(async (resolve, reject) => { 54 | const res = await command(` 55 | SELECT 56 | user_name, 57 | email 58 | FROM 59 | system_user 60 | where 61 | user_name = '${userName}' 62 | or 63 | email = '${email}' 64 | `) 65 | if (res.results.length > 0) { 66 | const userNameList = res.results.filter((item: { userName: any }) => item.userName === userName) 67 | const emailList = res.results.filter((item: { email: any }) => item.email === email) 68 | const msgList: string[] = [] 69 | if (userNameList.length > 0) { 70 | msgList.push('该用户名已被注册') 71 | } 72 | if (emailList.length > 0) { 73 | msgList.push('该邮箱已被注册') 74 | } 75 | reject(msgList.join(',')) 76 | } else { 77 | resolve(undefined) 78 | } 79 | }).catch((err) => { 80 | throw new HttpException('', err) 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/api/v1/system/common/code.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import { Models } from '../../../../common/typings/model' 3 | import svgCaptcha from 'svg-captcha' 4 | import { Buffer } from '../../../../core/HttpException' 5 | import Config from '../../../../config/Config' 6 | 7 | const router = new Router({ 8 | prefix: `${Config.API_PREFIX}v1/system/common`, 9 | }) 10 | /* 11 | * 获取验证码 12 | * @return { image } 返回图片 13 | */ 14 | router.get('/code', async (ctx: Models.Ctx) => { 15 | const captcha: svgCaptcha.CaptchaObj = svgCaptcha.createMathExpr({ 16 | size: 6, //验证码长度 17 | fontSize: 45, //验证码字号 18 | ignoreChars: '0o1i', // 过滤掉某些字符, 如 0o1i 19 | noise: 1, //干扰线条数目 20 | width: 100, //宽度 21 | // heigth:40,//高度 22 | color: true, //验证码字符是否有颜色,默认是没有,但是如果设置了背景颜色,那么默认就是有字符颜色 23 | background: '#cc9966', //背景大小 24 | }) 25 | 26 | ctx.session!.code = captcha.text //把验证码赋值给session 27 | throw new Buffer(captcha.data, 'image/svg+xml', captcha.text) 28 | }) 29 | 30 | export default router 31 | -------------------------------------------------------------------------------- /src/api/v1/system/common/upload/img.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import { Models } from '../../../../../common/typings/model' 3 | import Config from '../../../../../config/Config' 4 | import { Success } from '../../../../../core/HttpException' 5 | import verifyToken from '../../../../../middlewares/verifyToken' 6 | import { upload } from '../../../../../server/qiniu' 7 | import formidable from 'formidable' 8 | 9 | const router = new Router({ 10 | prefix: `${Config.API_PREFIX}v1/system/common/upload`, 11 | }) 12 | 13 | const resourcePath = '/resource/' 14 | 15 | /* 16 | * 上传图片 17 | */ 18 | router.post('/img', verifyToken, async (ctx: Models.Ctx) => { 19 | const file = ctx.request.files?.img as unknown as formidable.File 20 | 21 | const res = await upload(file) 22 | 23 | throw new Success({ 24 | path: `${resourcePath}${res.key}`, 25 | name: file.originalFilename, 26 | mimetype: file.mimetype, 27 | size: file.size, 28 | }) 29 | }) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /src/api/v1/system/menu/add.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success } from '../../../../core/HttpException' 4 | import validator from '../../../../middlewares/validator' 5 | import addMenu from '../../../../common/apiJsonSchema/system/menu/add' 6 | import Config from '../../../../config/Config' 7 | import { command } from '../../../../server/mysql' 8 | import { format } from '../../../../common/utils/date' 9 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 10 | import { updateRedisRole } from '../../../../server/auth' 11 | 12 | const router = new KoaRouter({ 13 | prefix: `${Config.API_PREFIX}v1/system/menu`, 14 | }) 15 | 16 | router.post('/add', verifyTokenPermission, validator(addMenu, 'body'), async (ctx: Models.Ctx) => { 17 | const { 18 | type, 19 | name, 20 | parentId, 21 | path = '', 22 | icon, 23 | serialNum, 24 | show, 25 | component = '', 26 | componentPath = '', 27 | permission, 28 | } = ctx.request.body 29 | const date = format(new Date()) 30 | await command(` 31 | INSERT INTO system_menu ( name, parent_id, path, icon, type, serial_num, \`show\`, component,component_path, permission, created_at, updated_at ) 32 | VALUES 33 | ( '${name}', ${parentId}, '${path}', '${icon}', ${type}, ${serialNum}, ${show}, '${component}','${componentPath}', '${permission}', '${date}', '${date}' ); 34 | `) 35 | updateRedisRole() 36 | throw new Success() 37 | }) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /src/api/v1/system/menu/delete.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success } from '../../../../core/HttpException' 4 | import validator from '../../../../middlewares/validator' 5 | import deleteMenuByIds from '../../../../common/apiJsonSchema/system/menu/deleteMenuByIds' 6 | import Config from '../../../../config/Config' 7 | import { updateRedisRole } from '../../../../server/auth' 8 | import { command } from '../../../../server/mysql' 9 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 10 | 11 | const router = new KoaRouter({ 12 | prefix: `${Config.API_PREFIX}v1/system/menu`, 13 | }) 14 | 15 | /* 16 | * 删除菜单 17 | * @return 18 | */ 19 | router.get('/delete', verifyTokenPermission, validator(deleteMenuByIds), async (ctx: Models.Ctx) => { 20 | const { ids } = ctx.request.query 21 | const idList = (ids as string).split(',') 22 | let idWhere = '' 23 | idList.forEach((id, index) => { 24 | if (index !== 0) { 25 | idWhere += 'OR ' 26 | } 27 | idWhere += `id = ${id}` 28 | }) 29 | await command(` 30 | DELETE 31 | FROM 32 | system_menu 33 | WHERE 34 | ${idWhere} 35 | `) 36 | updateRedisRole() 37 | throw new Success() 38 | }) 39 | 40 | export default router 41 | -------------------------------------------------------------------------------- /src/api/v1/system/menu/edit.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success } from '../../../../core/HttpException' 4 | import validator from '../../../../middlewares/validator' 5 | import editMenuById from '../../../../common/apiJsonSchema/system/menu/edit' 6 | import Config from '../../../../config/Config' 7 | import { command } from '../../../../server/mysql' 8 | import { updateRedisRole } from '../../../../server/auth' 9 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 10 | 11 | const router = new KoaRouter({ 12 | prefix: `${Config.API_PREFIX}v1/system/menu`, 13 | }) 14 | 15 | router.post('/edit', verifyTokenPermission, validator(editMenuById, 'body'), async (ctx: Models.Ctx) => { 16 | const { 17 | id, 18 | type, 19 | name, 20 | parentId, 21 | path = '', 22 | icon, 23 | serialNum, 24 | show, 25 | component = '', 26 | componentPath = '', 27 | permission = '', 28 | } = ctx.request.body 29 | await command(` 30 | UPDATE 31 | system_menu 32 | SET 33 | name = '${name}', 34 | type = ${type}, 35 | parent_id = ${parentId}, 36 | path = '${path}', 37 | icon = '${icon}', 38 | serial_num = ${serialNum}, 39 | \`show\` = ${show}, 40 | component = '${component}', 41 | component_path = '${componentPath}', 42 | permission = '${permission}' 43 | WHERE id = ${id} 44 | `) 45 | updateRedisRole() 46 | throw new Success() 47 | }) 48 | 49 | export default router 50 | -------------------------------------------------------------------------------- /src/api/v1/system/menu/getMenuByRoleId.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success } from '../../../../core/HttpException' 4 | import { lineToHumpObject } from '../../../../common/utils/utils' 5 | import Config from '../../../../config/Config' 6 | import verifyToken from '../../../../middlewares/verifyToken' 7 | import { command } from '../../../../server/mysql' 8 | 9 | const router = new KoaRouter({ 10 | prefix: `${Config.API_PREFIX}v1/system/menu`, 11 | }) 12 | 13 | router.get('/getMenuByRoleId', verifyToken, async (ctx: Models.Ctx) => { 14 | const { id } = ctx.request.query 15 | const res = ( 16 | await command(` 17 | ( 18 | SELECT 19 | permissions id 20 | FROM 21 | system_role 22 | WHERE 23 | id = ${id} 24 | 25 | ) 26 | ORDER BY 27 | updated_at DESC; 28 | `) 29 | ).results.map(lineToHumpObject) as System.Menu[] 30 | 31 | throw new Success(res.map((item) => item.id)) 32 | }) 33 | 34 | export default router 35 | -------------------------------------------------------------------------------- /src/api/v1/system/menu/getMenuMap.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Success } from '../../../../core/HttpException' 3 | import { lineToHumpObject, getTreeByList, sort } from '../../../../common/utils/utils' 4 | import Config from '../../../../config/Config' 5 | import verifyToken from '../../../../middlewares/verifyToken' 6 | import { command } from '../../../../server/mysql' 7 | 8 | const router = new KoaRouter({ 9 | prefix: `${Config.API_PREFIX}v1/system/menu`, 10 | }) 11 | 12 | router.get('/getMenuMap', verifyToken, async () => { 13 | const res = ( 14 | await command(` 15 | ( 16 | SELECT 17 | m.id, 18 | m.name, 19 | m.updated_at, 20 | m.parent_id, 21 | m.\`show\`, 22 | m.icon, 23 | m.serial_num, 24 | m.component, 25 | m.component_path, 26 | m.type 27 | FROM 28 | system_menu m 29 | 30 | ) 31 | ORDER BY 32 | updated_at DESC; 33 | `) 34 | ).results.map(lineToHumpObject) as System.Menu[] 35 | 36 | const records = getTreeByList(res, 0) 37 | const each = (arr: Common.TreeNode[]) => { 38 | sort(arr, 'serialNum', 'desc') 39 | arr.forEach((item) => { 40 | if (item.children) { 41 | each(item.children) 42 | } 43 | }) 44 | } 45 | each(records) 46 | throw new Success(records) 47 | }) 48 | 49 | export default router 50 | -------------------------------------------------------------------------------- /src/api/v1/system/menu/list.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success } from '../../../../core/HttpException' 4 | import { lineToHumpObject, getTreeByList, sort } from '../../../../common/utils/utils' 5 | import Config from '../../../../config/Config' 6 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 7 | import { command } from '../../../../server/mysql' 8 | import { getPagination } from '../../../../common/utils/result' 9 | 10 | const router = new KoaRouter({ 11 | prefix: `${Config.API_PREFIX}v1/system/menu`, 12 | }) 13 | 14 | router.post('/list', verifyTokenPermission, async (ctx: Models.Ctx) => { 15 | const { params, pageNum, pageSize } = ctx.request.body as unknown as Common.PaginationParams 16 | const { name } = params 17 | const nameStr = name ? `WHERE m.name LIKE '%${name}%'` : '' 18 | const res = ( 19 | await command(` 20 | ( 21 | SELECT 22 | m.id, 23 | m.name, 24 | m.updated_at, 25 | m.parent_id, 26 | m.\`show\`, 27 | m.icon, 28 | m.serial_num, 29 | m.component, 30 | m.component_path, 31 | m.permission, 32 | m.type, 33 | m.path 34 | FROM 35 | system_menu m 36 | ${nameStr} 37 | ) 38 | ORDER BY 39 | updated_at DESC; 40 | `) 41 | ).results.map(lineToHumpObject) as System.Menu[] 42 | 43 | const records = getTreeByList(res, 0) 44 | const total: number = records.length 45 | if (pageNum > 1) { 46 | records.splice((pageNum - 1) * pageSize, pageSize) 47 | } 48 | const each = (arr: Common.TreeNode[]) => { 49 | sort(arr, 'serialNum', 'desc') 50 | arr.forEach((item) => { 51 | // item.children = item.actions 52 | // ? JSON.parse(item.actions).map((button: Models.TreeNode) => { 53 | // return { 54 | // ...button, 55 | // permission: button.id, 56 | // } 57 | // }) 58 | // : item.children 59 | if (item.children) { 60 | each(item.children) 61 | } 62 | }) 63 | } 64 | each(records) 65 | const data = getPagination(records, total, pageSize, pageNum) 66 | throw new Success(data) 67 | }) 68 | 69 | export default router 70 | -------------------------------------------------------------------------------- /src/api/v1/system/role/add.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import Config from '../../../../config/Config' 3 | import KoaRouter from 'koa-router' 4 | import { Success } from '../../../../core/HttpException' 5 | import validator from '../../../../middlewares/validator' 6 | import addRole from '../../../../common/apiJsonSchema/system/role/addRole' 7 | import { format } from '../../../../common/utils/date' 8 | import { command } from '../../../../server/mysql' 9 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 10 | import { updateRedisRole } from '../../../../server/auth' 11 | 12 | const router = new KoaRouter({ 13 | prefix: `${Config.API_PREFIX}v1/system/role`, 14 | }) 15 | 16 | router.post('/add', verifyTokenPermission, validator(addRole, 'body'), async (ctx: Models.Ctx) => { 17 | const { name, parentId, describe = '', serialNum } = ctx.request.body 18 | const date = format(new Date()) 19 | const res = await command(` 20 | INSERT INTO system_role ( name, parent_id, \`describe\`, serial_num, created_at, updated_at ) 21 | VALUES 22 | ( '${name}', ${parentId}, '${describe}', ${serialNum}, '${date}', '${date}' ); 23 | `) 24 | updateRedisRole() 25 | throw new Success(res) 26 | }) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /src/api/v1/system/role/delete.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import Config from '../../../../config/Config' 3 | import KoaRouter from 'koa-router' 4 | import { Success } from '../../../../core/HttpException' 5 | import validator from '../../../../middlewares/validator' 6 | import deleteRoleByIds from '../../../../common/apiJsonSchema/system/role/delete' 7 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 8 | import { command } from '../../../../server/mysql' 9 | import { updateRedisRole } from '../../../../server/auth' 10 | 11 | const router = new KoaRouter({ 12 | prefix: `${Config.API_PREFIX}v1/system/role`, 13 | }) 14 | 15 | /* 16 | * 删除菜单 17 | * @return 18 | */ 19 | router.get('/delete', verifyTokenPermission, validator(deleteRoleByIds), async (ctx: Models.Ctx) => { 20 | const { ids } = ctx.request.query 21 | await command(` 22 | DELETE 23 | FROM 24 | system_role 25 | WHERE 26 | FIND_IN_SET(id, '${ids}') 27 | `) 28 | updateRedisRole() 29 | throw new Success() 30 | }) 31 | 32 | export default router 33 | -------------------------------------------------------------------------------- /src/api/v1/system/role/edit.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import Config from '../../../../config/Config' 3 | import KoaRouter from 'koa-router' 4 | import { Success } from '../../../../core/HttpException' 5 | import validator from '../../../../middlewares/validator' 6 | import editRoleByid from '../../../../common/apiJsonSchema/system/role/edit' 7 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 8 | import { command } from '../../../../server/mysql' 9 | import { getUserPermission, updateRoles } from '../../../../server/auth' 10 | 11 | const router = new KoaRouter({ 12 | prefix: `${Config.API_PREFIX}v1/system/role`, 13 | }) 14 | 15 | router.post('/edit', verifyTokenPermission, validator(editRoleByid, 'body'), async (ctx: Models.Ctx) => { 16 | const { id, name, parentId, describe, serialNum } = ctx.request.body 17 | await command(` 18 | UPDATE 19 | system_role 20 | SET name = '${name}', parent_id = ${parentId}, \`describe\` = '${describe}', serial_num = ${serialNum} 21 | WHERE id = ${id} 22 | `) 23 | 24 | const scope = id 25 | getUserPermission({ 26 | scope, 27 | uid: ctx.auth.uid, 28 | }).then((list) => { 29 | updateRoles( 30 | scope, 31 | new Map([ 32 | ['id', id.toString()], 33 | ['parentId', parentId.toString()], 34 | ['permissions', list.map((item) => item.permission).join(',')], 35 | ]) 36 | ) 37 | }) 38 | throw new Success() 39 | }) 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /src/api/v1/system/role/editPermission.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import Config from '../../../../config/Config' 3 | import KoaRouter from 'koa-router' 4 | import { Success } from '../../../../core/HttpException' 5 | import validator from '../../../../middlewares/validator' 6 | import editRolePermissionById from '../../../../common/apiJsonSchema/system/role/editPermission' 7 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 8 | import { command } from '../../../../server/mysql' 9 | import { updateRedisRole } from '../../../../server/auth' 10 | 11 | const router = new KoaRouter({ 12 | prefix: `${Config.API_PREFIX}v1/system/role`, 13 | }) 14 | 15 | router.post( 16 | '/editPermission', 17 | verifyTokenPermission, 18 | validator(editRolePermissionById, 'body'), 19 | async (ctx: Models.Ctx) => { 20 | const { ids, roleId } = ctx.request.body 21 | await command(` 22 | UPDATE 23 | system_role 24 | SET menu_ids = '${ids}' 25 | WHERE id = ${roleId} 26 | `) 27 | updateRedisRole() 28 | throw new Success() 29 | } 30 | ) 31 | 32 | export default router 33 | -------------------------------------------------------------------------------- /src/api/v1/system/role/getRoleMap.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { getTreeByList, lineToHumpObject, sort } from '../../../../common/utils/utils' 3 | import Config from '../../../../config/Config' 4 | import { command } from '../../../../server/mysql' 5 | import { Success } from '../../../../core/HttpException' 6 | import verifyToken from '../../../../middlewares/verifyToken' 7 | const router = new KoaRouter({ 8 | prefix: `${Config.API_PREFIX}v1/system/role`, 9 | }) 10 | 11 | router.get('/getRoleMap', verifyToken, async () => { 12 | const res = ( 13 | await command(` 14 | (SELECT 15 | id, 16 | name, 17 | \`describe\`, 18 | updated_at, 19 | parent_id, 20 | serial_num 21 | FROM 22 | system_role) 23 | ORDER BY 24 | updated_at DESC; 25 | 26 | `) 27 | ).results.map(lineToHumpObject) 28 | const records = getTreeByList(res, 0) 29 | const each = (arr: Common.TreeNode[]) => { 30 | sort(arr, 'serialNum', 'desc') 31 | arr.forEach((item) => { 32 | if (item.children) { 33 | each(item.children) 34 | } 35 | }) 36 | } 37 | each(records) 38 | throw new Success(records) 39 | }) 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /src/api/v1/system/role/list.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import Config from '../../../../config/Config' 3 | import KoaRouter from 'koa-router' 4 | import { Success } from '../../../../core/HttpException' 5 | import { getTreeByList, lineToHumpObject, sort } from '../../../../common/utils/utils' 6 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 7 | import { command } from '../../../../server/mysql' 8 | import { getPagination } from '../../../../common/utils/result' 9 | const router = new KoaRouter({ 10 | prefix: `${Config.API_PREFIX}v1/system/role`, 11 | }) 12 | 13 | router.post('/list', verifyTokenPermission, async (ctx: Models.Ctx) => { 14 | const { params, pageNum, pageSize } = ctx.request.body as unknown as Common.PaginationParams 15 | const { name } = params 16 | const nameStr = name ? `WHERE name LIKE '%${name}%'` : '' 17 | const res = ( 18 | await command(` 19 | (SELECT 20 | id, 21 | name, 22 | \`describe\`, 23 | updated_at, 24 | parent_id, 25 | serial_num, 26 | menu_ids 27 | FROM 28 | system_role 29 | ${nameStr} 30 | ) 31 | ORDER BY 32 | updated_at DESC; 33 | 34 | `) 35 | ).results.map(lineToHumpObject) 36 | const records = getTreeByList(res, 0) 37 | const total: number = records.length 38 | if (pageNum > 1) { 39 | records.splice((pageNum - 1) * pageSize, pageSize) 40 | } 41 | const each = (arr: Common.TreeNode[]) => { 42 | sort(arr, 'serialNum', 'desc') 43 | arr.forEach((item) => { 44 | if (item.children) { 45 | each(item.children) 46 | } 47 | }) 48 | } 49 | each(records) 50 | const data = getPagination(records, total, pageSize, pageNum) 51 | throw new Success(data) 52 | }) 53 | 54 | export default router 55 | -------------------------------------------------------------------------------- /src/api/v1/system/user/add.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success } from '../../../../core/HttpException' 4 | import validator from '../../../../middlewares/validator' 5 | import Config from '../../../../config/Config' 6 | import add from '../../../../common/apiJsonSchema/system/user/add' 7 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 8 | import { command } from '../../../../server/mysql' 9 | import { format } from '../../../../common/utils/date' 10 | const router = new KoaRouter({ 11 | prefix: `${Config.API_PREFIX}v1/system/user`, 12 | }) 13 | 14 | router.post('/add', verifyTokenPermission, validator(add, 'body'), async (ctx: Models.Ctx) => { 15 | const { userName, roleIds, info, email, password } = ctx.request.body 16 | const date = format(new Date()) 17 | await command(` 18 | INSERT INTO system_user ( user_name, role_ids, info, email, password, created_at, updated_at ) 19 | VALUES 20 | ( '${userName}', ${roleIds}, '${info}', ${email}, ${password}, '${date}', '${date}' ); 21 | `) 22 | 23 | throw new Success() 24 | }) 25 | 26 | export default router 27 | -------------------------------------------------------------------------------- /src/api/v1/system/user/edit.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success } from '../../../../core/HttpException' 4 | import validator from '../../../../middlewares/validator' 5 | import Config from '../../../../config/Config' 6 | import edit from '../../../../common/apiJsonSchema/system/user/edit' 7 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 8 | import { command } from '../../../../server/mysql' 9 | const router = new KoaRouter({ 10 | prefix: `${Config.API_PREFIX}v1/system/user`, 11 | }) 12 | 13 | router.post('/edit', verifyTokenPermission, validator(edit, 'body'), async (ctx: Models.Ctx) => { 14 | const { nickName, profile = '', avatar, roleIds, id } = ctx.request.body 15 | const info = { 16 | nickName, 17 | profile, 18 | avatar, 19 | } 20 | await command(` 21 | UPDATE 22 | system_user 23 | SET info = '${JSON.stringify(info)}', role_ids = '${roleIds}' 24 | WHERE id = ${id} 25 | `) 26 | 27 | throw new Success() 28 | }) 29 | 30 | export default router 31 | -------------------------------------------------------------------------------- /src/api/v1/system/user/editInfo.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import validator from '../../../../middlewares/validator' 4 | import Config from '../../../../config/Config' 5 | import editInfo from '../../../../common/apiJsonSchema/system/user/editInfo' 6 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 7 | import { command } from '../../../../server/mysql' 8 | import { Success } from '../../../../core/HttpException' 9 | const router = new KoaRouter({ 10 | prefix: `${Config.API_PREFIX}v1/system/user`, 11 | }) 12 | 13 | /** 14 | * 更新用户详情 15 | */ 16 | router.post('/editInfo', verifyTokenPermission, validator(editInfo, 'body'), async (ctx: Models.Ctx) => { 17 | const id = ctx.auth?.uid 18 | const { nickName, profile, avatar } = ctx.request.body 19 | const info = { 20 | nickName, 21 | profile, 22 | avatar, 23 | } 24 | await command(` 25 | UPDATE 26 | system_user 27 | SET info = '${JSON.stringify(info)}' 28 | WHERE id = ${id} 29 | `) 30 | throw new Success() 31 | }) 32 | 33 | export default router 34 | -------------------------------------------------------------------------------- /src/api/v1/system/user/editPassword.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { Success, ParameterException } from '../../../../core/HttpException' 4 | import validator from '../../../../middlewares/validator' 5 | import Config from '../../../../config/Config' 6 | import verifyToken from '../../../../middlewares/verifyToken' 7 | import editPassword from '../../../../common/apiJsonSchema/system/user/editPassword' 8 | import verificationCodeValidator from '../../../../middlewares/verificationCodeValidator' 9 | import { command } from '../../../../server/mysql' 10 | import { sendEmail } from '../../../../server/mailer' 11 | const router = new KoaRouter({ 12 | prefix: `${Config.API_PREFIX}v1/system/user`, 13 | }) 14 | 15 | /** 16 | * 更新用户详情 17 | */ 18 | router.post( 19 | '/editPassword', 20 | verifyToken, 21 | validator(editPassword, 'body'), 22 | verificationCodeValidator, 23 | async (ctx: Models.Ctx) => { 24 | const id = ctx.auth?.uid 25 | const { password, emailCode } = ctx.request.body 26 | if (ctx.session!.emailCode !== emailCode) { 27 | throw new ParameterException('邮箱验证码错误') 28 | } 29 | await command(` 30 | UPDATE 31 | system_user 32 | SET password = '${password}' 33 | WHERE id = ${id} 34 | `) 35 | throw new Success() 36 | } 37 | ) 38 | 39 | /** 40 | * 发送邮件 41 | */ 42 | router.post('/editPassword/email', verifyToken, async (ctx: Models.Ctx) => { 43 | const id = ctx.auth?.uid 44 | const res = await command(` 45 | SELECT 46 | * 47 | FROM 48 | system_user 49 | WHERE 50 | id = ${id} 51 | `) 52 | const user: System.User = res.results[0] 53 | const code = (Math.random() * 1000000).toFixed() 54 | ctx.session!.emailCode = code 55 | // 发送邮件 56 | await sendEmail({ 57 | from: '"FHTWL" <1121145488@qq.com>', 58 | to: user.email, 59 | subject: '验证码', 60 | text: '验证码', 61 | html: ` 62 |
63 |

您正在修改FHTWL低代码平台用户名为${user.userName}的密码, 64 | 验证码为:

65 |

66 | ${code} 67 |

68 |

请在修改密码页面填写该邮箱验证码

69 |
70 | `, 71 | }) 72 | 73 | throw new Success() 74 | }) 75 | 76 | export default router 77 | -------------------------------------------------------------------------------- /src/api/v1/system/user/getUserMenu.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { command } from '../../../../server/mysql' 4 | import { Success } from '../../../../core/HttpException' 5 | import verifyToken from '../../../../middlewares/verifyToken' 6 | import { getTreeByList, sort } from '../../../../common/utils/utils' 7 | import Config from '../../../../config/Config' 8 | const router = new KoaRouter({ 9 | prefix: `${Config.API_PREFIX}v1/system/user`, 10 | }) 11 | 12 | /** 13 | * 获取当前用户的菜单 14 | */ 15 | router.post('/getUserMenu', verifyToken, async (ctx: Models.Ctx) => { 16 | const { scope: roleIds } = ctx.auth 17 | // 所有的角色 18 | const roleRes = ( 19 | await command(` 20 | SELECT 21 | * 22 | FROM 23 | system_role 24 | `) 25 | ).results 26 | 27 | // 存放当前用户的角色和祖宗角色 28 | const roleList: System.Role[] = [] 29 | // 过滤, 获取当前角色及当前角色的祖先角色的所有记录 30 | const each = (list: System.Role[], nodeId: number) => { 31 | const arr = list.filter((item) => item.id === nodeId) 32 | if (arr.length) { 33 | roleList.push(...arr) 34 | each(list, arr[0].parentId) 35 | } 36 | } 37 | // 将用户的角色ids转换为数组 38 | const roleIdList: number[] = roleIds.split(',').map((str: string) => Number(str)) 39 | roleIdList.forEach((roleId) => { 40 | each(roleRes, roleId) 41 | }) 42 | 43 | // 当前角色的角色树 44 | const roleTree = getTreeByList(roleList as unknown as Common.List, 0) as unknown as System.Role[] 45 | // 当前角色有权限的所有菜单. 46 | let menuList: number[] = [] 47 | const merge = (list: System.Role[]) => { 48 | list.forEach((item) => { 49 | menuList = [...new Set([...menuList, ...item.menuIds.split(',').map((str) => Number(str))])] 50 | if (item.children) { 51 | merge(item.children) 52 | } 53 | }) 54 | } 55 | // 合并当前角色和当前角色的祖先角色的所有菜单 56 | merge(roleTree) 57 | 58 | // roleId 字段,角色,与权限相关 59 | const res = await command(` 60 | SELECT 61 | menu.id, 62 | menu.name title, 63 | menu.show, 64 | menu.icon, 65 | menu.component, 66 | menu.component_path, 67 | menu.redirect, 68 | menu.parent_id, 69 | menu.path, 70 | menu.hide_children, 71 | menu.serial_num, 72 | menu.permission, 73 | menu.type 74 | FROM 75 | system_menu menu 76 | WHERE 77 | FIND_IN_SET(menu.id , '${menuList.join(',')}') 78 | `) 79 | const sortEach = (arr: System.Menu[]) => { 80 | sort(arr, 'serialNum', 'desc') 81 | arr.forEach((item) => { 82 | if (item.children) { 83 | sortEach(item.children) 84 | } 85 | }) 86 | } 87 | // 根据serialNum排序 88 | sortEach(res.results) 89 | // 构建前端需要的menu树 90 | const list = (res.results as System.Menu[]).map( 91 | ({ 92 | name, 93 | parentId, 94 | id, 95 | icon, 96 | title, 97 | show, 98 | component, 99 | componentPath, 100 | redirect, 101 | path, 102 | hideChildren, 103 | children, 104 | serialNum, 105 | permission, 106 | type, 107 | }) => { 108 | const isHideChildren = Boolean(hideChildren) 109 | const isShow = Boolean(show) 110 | return { 111 | name, 112 | parentId, 113 | id, 114 | meta: { 115 | icon, 116 | title, 117 | show: isShow, 118 | hideChildren: isHideChildren, 119 | }, 120 | component, 121 | componentPath, 122 | redirect, 123 | path, 124 | children, 125 | serialNum, 126 | permission, 127 | type, 128 | } 129 | } 130 | ) 131 | 132 | throw new Success(list) 133 | }) 134 | 135 | export default router 136 | -------------------------------------------------------------------------------- /src/api/v1/system/user/list.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../../../common/typings/model' 2 | import KoaRouter from 'koa-router' 3 | import { lineToHumpObject } from '../../../../common/utils/utils' 4 | import Config from '../../../../config/Config' 5 | import { verifyTokenPermission } from '../../../../middlewares/verifyToken' 6 | import { command } from '../../../../server/mysql' 7 | import { getPagination } from '../../../../common/utils/result' 8 | import { Success } from '../../../../core/HttpException' 9 | const router = new KoaRouter({ 10 | prefix: `${Config.API_PREFIX}v1/system/user`, 11 | }) 12 | 13 | interface RetUser extends System.User { 14 | roleNames?: string 15 | } 16 | 17 | router.post('/list', verifyTokenPermission, async (ctx: Models.Ctx) => { 18 | const { params, pageNum, pageSize } = ctx.request.body as unknown as Common.PaginationParams 19 | const { name } = params 20 | const nameStr = name ? `And u.user_name LIKE '%${name}%'` : '' 21 | // roleId 字段,角色,与权限相关 22 | const res = ( 23 | await command(` 24 | ( 25 | SELECT 26 | u.id, 27 | u.info, 28 | u.updated_at, 29 | u.role_ids, 30 | u.email, 31 | u.user_name, 32 | r.name roleNames 33 | FROM 34 | system_user as u, 35 | system_role as r 36 | WHERE 37 | u.deleted = 0 38 | AND FIND_IN_SET(r.id , u.role_ids) 39 | ${nameStr} 40 | LIMIT ${pageNum - 1}, ${pageSize} 41 | ) 42 | ORDER BY 43 | updated_at DESC; 44 | SELECT FOUND_ROWS() as total; 45 | `) 46 | ).results 47 | const total: number = res[1][0].total 48 | const list: RetUser[] = [] 49 | for (const key in res[0]) { 50 | const xItem = res[0][key] 51 | const oldItem = list.find((item) => item.id === xItem.id) 52 | if (oldItem) { 53 | oldItem.roleNames = `${oldItem.roleNames},${xItem.roleNames}` 54 | } else { 55 | list.push(xItem) 56 | } 57 | } 58 | const data = getPagination( 59 | list.map((item) => { 60 | item.info = JSON.parse(item.info as unknown as string) 61 | return lineToHumpObject(item) 62 | }), 63 | total, 64 | pageSize, 65 | pageNum 66 | ) 67 | throw new Success(data) 68 | }) 69 | 70 | export default router 71 | -------------------------------------------------------------------------------- /src/api/v1/system/user/query.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Models } from '../../../../common/typings/model' 3 | import { command } from '../../../../server/mysql' 4 | import { Success } from '../../../../core/HttpException' 5 | import Config from '../../../../config/Config' 6 | import verifyToken from '../../../../middlewares/verifyToken' 7 | 8 | interface MenuList extends System.User { 9 | roleParentId: number 10 | menuId: number 11 | roleName: string 12 | roleId: number 13 | menuName: string 14 | menuType: System.MenuType 15 | serialNum: number 16 | show: 0 | 1 17 | menuParentId: number 18 | menuPermission: string 19 | } 20 | 21 | const router = new KoaRouter({ 22 | prefix: `${Config.API_PREFIX}v1/system/user`, 23 | }) 24 | 25 | router.get('/query', verifyToken, async (ctx: Models.Ctx) => { 26 | const { uid } = ctx.auth 27 | // 查询获取所有的菜单(包括菜单目录和按钮) 28 | const AllMenulist = ( 29 | ( 30 | await command(` 31 | SELECT 32 | user.user_name, 33 | user.email, 34 | user.info infoStr, 35 | user.deleted, 36 | role.name roleName, 37 | role.id roleId, 38 | role.menu_ids, 39 | role.parent_id roleParentId, 40 | menu.name menuName, 41 | menu.id menuId, 42 | menu.type menuType, 43 | menu.show, 44 | menu.serial_num, 45 | menu.parent_id menuParentId, 46 | menu.permission menuPermission 47 | FROM 48 | system_user user, 49 | system_role role, 50 | system_menu menu 51 | WHERE 52 | user.id = ${uid} 53 | AND FIND_IN_SET(role.id , user.role_ids) 54 | AND FIND_IN_SET(menu.id , role.menu_ids) 55 | `) 56 | ).results as MenuList[] 57 | ).map((item) => { 58 | item.info = JSON.parse(item.infoStr) 59 | return { 60 | ...item, 61 | } 62 | }) 63 | 64 | // 上面的查询会有重复, 过滤重复数据 65 | const filterMenuList: MenuList[] = [] 66 | AllMenulist.forEach((element: MenuList) => { 67 | const info: System.UserInfo = JSON.parse(element.infoStr) 68 | const data = filterMenuList.find( 69 | (item) => 70 | info.nickName === item.info.nickName && element.roleIds === item.roleIds && element.menuId === item.menuId 71 | ) 72 | if (!data) { 73 | filterMenuList.push(element) 74 | } 75 | }) 76 | const { info, roleName, userName, roleId, email } = AllMenulist[0] 77 | 78 | // 将数据转换为前端需要的数据结构 79 | const menuList: System.Permission[] = filterMenuList.map((item) => { 80 | return { 81 | roleId: item.roleId, 82 | roleName: item.roleName, 83 | id: item.menuId, 84 | menuType: item.menuType, 85 | name: item.menuName, 86 | show: item.show, 87 | serialNum: item.serialNum, 88 | actions: [], 89 | parentId: item.menuParentId, 90 | permission: item.menuPermission, 91 | } 92 | }) 93 | // 获取所有的操作(即按钮) 94 | const allActions: System.Permission[] = menuList.filter((item) => item.menuType === 3) 95 | // 获取所有的菜单目录和菜单 96 | const allMenu: System.Permission[] = menuList.filter((item) => item.menuType === 1 || item.menuType === 2) || [] 97 | // 根据parentId给菜单添加操作 98 | allMenu.forEach((menu) => { 99 | menu.actions = allActions 100 | .filter((item) => item.parentId === menu.id) 101 | .map((item) => { 102 | return { 103 | id: item.id, 104 | serialNum: item.serialNum, 105 | permission: item.permission, 106 | } 107 | }) 108 | }) 109 | const userInfo = { 110 | userName, 111 | email, 112 | info, 113 | role: { 114 | roleName, 115 | roleId, 116 | permissions: allMenu, 117 | }, 118 | } 119 | throw new Success(userInfo) 120 | }) 121 | 122 | export default router 123 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import http from 'http' 3 | import initCore from './core/Init' 4 | import Config from './config/Config' 5 | // 创建koa实例 6 | const app = new Koa() 7 | // 创建服务器 8 | const server: http.Server = new http.Server(app.callback()) 9 | 10 | // 执行初始化 11 | initCore(app, server) 12 | // 监听端口 13 | app.listen(Config.HTTP_PORT, () => { 14 | console.log('run success') 15 | console.log(`app started at port ${Config.HTTP_PORT}...`) 16 | console.log(process.env.NODE_ENV) 17 | }) 18 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/common/pagination.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['pageNum', 'pageSize', 'params'], 4 | properties: { 5 | pageNum: { 6 | type: 'number', 7 | min: 1, 8 | }, 9 | pageSize: { 10 | type: 'number', 11 | num: 1, 12 | }, 13 | params: { 14 | type: 'object', 15 | }, 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/board/add.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['name', 'parentId', 'type'], 4 | properties: { 5 | name: { 6 | type: 'string', 7 | }, 8 | parentId: { 9 | type: 'number', 10 | }, 11 | type: { 12 | type: 'number', 13 | enum: [1, 2], 14 | }, 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/board/delete.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['cIds', 'bIds'], 4 | properties: { 5 | cIds: { 6 | type: 'string', 7 | }, 8 | bIds: { 9 | type: 'string', 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/board/editBoardAttrById.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['name', 'id', 'parentId'], 4 | properties: { 5 | name: { 6 | type: 'string', 7 | }, 8 | id: { 9 | type: 'number', 10 | }, 11 | parentId: { 12 | type: 'number', 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/board/editBoardPageById.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['id', 'pageOperation'], 4 | properties: { 5 | id: { 6 | type: 'number', 7 | }, 8 | pageOperation: { 9 | type: 'string', 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/board/query.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['id'], 4 | properties: { 5 | id: { 6 | type: 'string', 7 | }, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/boardCategoryManager/getBoardAndCategoryListByParentId.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['pageNum', 'pageSize', 'params'], 4 | properties: { 5 | pageNum: { 6 | type: 'integer', 7 | minimum: 1, 8 | }, 9 | pageSize: { 10 | type: 'integer', 11 | minimum: 1, 12 | }, 13 | params: { 14 | type: 'object', 15 | required: ['parentId'], 16 | properties: { 17 | parentId: { 18 | type: 'integer', 19 | }, 20 | }, 21 | }, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/component/addComponent.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['name', 'parentId'], 4 | properties: { 5 | name: { 6 | type: 'string', 7 | }, 8 | parentId: { 9 | type: 'number', 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/component/editComponentAttrById.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['name', 'id', 'parentId'], 4 | properties: { 5 | name: { 6 | type: 'string', 7 | }, 8 | id: { 9 | type: 'number', 10 | }, 11 | parentId: { 12 | type: 'number', 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/component/getComponentById.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['id'], 4 | properties: { 5 | id: { 6 | type: 'string', 7 | }, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/lowCode/componentCategoryManager/list.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['pageNum', 'pageSize', 'params'], 4 | properties: { 5 | parentId: { 6 | type: 'integer', 7 | }, 8 | pageNum: { 9 | type: 'integer', 10 | minimum: 1, 11 | }, 12 | pageSize: { 13 | type: 'integer', 14 | minimum: 1, 15 | }, 16 | params: { 17 | type: 'object', 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/auth/login.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['password', 'userName', 'code'], 4 | properties: { 5 | code: { 6 | type: ['number', 'string'], 7 | }, 8 | password: { 9 | type: 'string', 10 | maxLength: 255, 11 | minLength: 6, 12 | }, 13 | userName: { 14 | type: 'string', 15 | maxLength: 255, 16 | minLength: 4, 17 | }, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/auth/logout.ts: -------------------------------------------------------------------------------- 1 | import KoaRouter from 'koa-router' 2 | import { Success } from '../../../../core/HttpException' 3 | import Config from '../../../../config/Config' 4 | import { Models } from '../../../../common/typings/model' 5 | import { getToken } from '../../../../middlewares/verifyToken' 6 | import { deleteToken } from '../../../../server/auth/token' 7 | 8 | const router = new KoaRouter({ 9 | prefix: `${Config.API_PREFIX}v1/system/auth`, 10 | }) 11 | 12 | /* 13 | * 退出登录 14 | */ 15 | router.post('/logout', async (ctx: Models.Ctx) => { 16 | await deleteToken(getToken(ctx)!) 17 | throw new Success() 18 | }) 19 | 20 | export default router 21 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/auth/register.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['password', 'userName', 'email', 'code'], 4 | properties: { 5 | code: { 6 | type: 'string', 7 | maxLength: 6, 8 | minLength: 6, 9 | }, 10 | password: { 11 | type: 'string', 12 | maxLength: 255, 13 | minLength: 6, 14 | }, 15 | userName: { 16 | type: 'string', 17 | maxLength: 255, 18 | minLength: 4, 19 | }, 20 | email: { 21 | type: 'string', 22 | pattern: '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$', 23 | }, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/auth/sendCodeEmail.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['userName', 'email'], 4 | properties: { 5 | userName: { 6 | type: 'string', 7 | maxLength: 255, 8 | minLength: 4, 9 | }, 10 | email: { 11 | type: 'string', 12 | pattern: '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$', 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/menu/add.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['name', 'type', 'parentId', 'serialNum', 'show'], 4 | properties: { 5 | name: { 6 | type: 'string', 7 | minLength: 1, 8 | }, 9 | type: { 10 | type: 'number', 11 | enum: [1, 2, 3], 12 | }, 13 | parentId: { 14 | type: 'number', 15 | }, 16 | actions: { 17 | type: 'string', 18 | }, 19 | path: { 20 | type: 'string', 21 | }, 22 | icon: { 23 | type: 'string', 24 | }, 25 | serialNum: { 26 | type: 'number', 27 | }, 28 | show: { 29 | type: 'number', 30 | enum: [0, 1], 31 | }, 32 | component: { 33 | type: 'string', 34 | }, 35 | permission: { 36 | type: 'string', 37 | }, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/menu/deleteMenuByIds.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['ids'], 4 | properties: { 5 | ids: { 6 | type: 'string', 7 | }, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/menu/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['id', 'type', 'name', 'parentId', 'icon', 'serialNum', 'show'], 4 | properties: { 5 | id: { 6 | type: 'number', 7 | }, 8 | name: { 9 | type: 'string', 10 | minLength: 1, 11 | }, 12 | parentId: { 13 | type: 'number', 14 | }, 15 | path: { 16 | type: ['string', 'null'], 17 | }, 18 | icon: { 19 | type: ['string', 'null'], 20 | }, 21 | serialNum: { 22 | type: 'number', 23 | }, 24 | show: { 25 | type: 'number', 26 | enum: [0, 1], 27 | }, 28 | component: { 29 | type: ['string', 'null'], 30 | }, 31 | permission: { 32 | type: 'string', 33 | }, 34 | type: { 35 | type: 'number', 36 | enum: [1, 2, 3], 37 | }, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/role/addRole.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['name', 'parentId', 'serialNum'], 4 | properties: { 5 | name: { 6 | type: 'string', 7 | minLength: 1, 8 | }, 9 | parentId: { 10 | type: 'number', 11 | }, 12 | describe: { 13 | type: 'string', 14 | }, 15 | serialNum: { 16 | type: 'number', 17 | }, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/role/delete.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['ids'], 4 | properties: { 5 | ids: { 6 | type: 'string', 7 | }, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/role/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['id', 'name', 'parentId', 'describe', 'serialNum'], 4 | properties: { 5 | id: { 6 | type: 'number', 7 | }, 8 | name: { 9 | type: 'string', 10 | minLength: 1, 11 | }, 12 | parentId: { 13 | type: 'number', 14 | }, 15 | describe: { 16 | type: 'string', 17 | }, 18 | serialNum: { 19 | type: 'number', 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/role/editPermission.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['roleId', 'ids'], 4 | properties: { 5 | roleId: { 6 | type: 'number', 7 | }, 8 | ids: { 9 | type: 'string', 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/user/add.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['password', 'userName', 'email', 'info', 'roleIds'], 4 | properties: { 5 | info: { 6 | type: 'object', 7 | properties: { 8 | nickName: { 9 | type: 'string', 10 | maxLength: 255, 11 | minLength: 4, 12 | }, 13 | profile: { 14 | type: 'string', 15 | maxLength: 255, 16 | }, 17 | avatar: { 18 | type: 'string', 19 | }, 20 | }, 21 | }, 22 | password: { 23 | type: 'string', 24 | maxLength: 255, 25 | minLength: 6, 26 | }, 27 | userName: { 28 | type: 'string', 29 | maxLength: 255, 30 | minLength: 4, 31 | }, 32 | email: { 33 | type: 'string', 34 | pattern: '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$', 35 | }, 36 | roleIds: { 37 | type: 'string', 38 | }, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/user/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['userName', 'email', 'info', 'roleIds'], 4 | properties: { 5 | info: { 6 | type: 'object', 7 | properties: { 8 | nickName: { 9 | type: 'string', 10 | maxLength: 255, 11 | minLength: 1, 12 | }, 13 | profile: { 14 | type: 'string', 15 | maxLength: 255, 16 | }, 17 | avatar: { 18 | type: 'string', 19 | }, 20 | }, 21 | }, 22 | userName: { 23 | type: 'string', 24 | maxLength: 255, 25 | minLength: 4, 26 | }, 27 | email: { 28 | type: 'string', 29 | pattern: '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$', 30 | }, 31 | roleIds: { 32 | type: 'string', 33 | }, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/user/editInfo.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['nickName', 'profile', 'avatar'], 4 | properties: { 5 | nickName: { 6 | type: 'string', 7 | }, 8 | profile: { 9 | type: 'string', 10 | }, 11 | avatar: { 12 | type: 'string', 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /src/common/apiJsonSchema/system/user/editPassword.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'object', 3 | required: ['code', 'password', 'emailCode'], 4 | properties: { 5 | code: { 6 | type: 'string', 7 | }, 8 | password: { 9 | type: 'string', 10 | }, 11 | emailCode: { 12 | type: 'string', 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /src/common/typings/index.ts: -------------------------------------------------------------------------------- 1 | // 用于全局引入第三方定义 2 | import System from '@fhtwl-admin/system' 3 | import Common from '@fhtwl-admin/common' 4 | -------------------------------------------------------------------------------- /src/common/typings/lowCode.d.ts: -------------------------------------------------------------------------------- 1 | export namespace LowCode { 2 | interface Board { 3 | id: number 4 | name: string 5 | createdUserId: number 6 | } 7 | 8 | interface boardCategory { 9 | id: number 10 | name: string 11 | createdUserId: number 12 | } 13 | 14 | interface Component { 15 | id: number 16 | name: string 17 | createdUserId: number 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/common/typings/model.d.ts: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import mysql from 'mysql' 3 | export namespace Models { 4 | type Ctx = Koa.Context 5 | 6 | // mysql相关错误 7 | interface MysqlError { 8 | msg: string 9 | error?: mysql.MysqlError 10 | } 11 | 12 | // mysql 连接数据库返回值 13 | interface Result { 14 | /** `state===1`时为成功 */ 15 | state: number 16 | /** 结果数组 或 对象 */ 17 | results: any 18 | /** 状态 */ 19 | fields?: Array 20 | /** 错误信息 */ 21 | error?: mysql.MysqlError 22 | /** 描述信息 */ 23 | msg: string 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/common/utils/date.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import Config from '../../config/Config' 3 | 4 | /** 5 | * 格式化时间 6 | * @param date 7 | * @param pattern 8 | * @returns 9 | */ 10 | export function format(date: Date, pattern = Config.DEFAULT_DATE_FORMAT) { 11 | return moment(date).format(pattern) 12 | } 13 | -------------------------------------------------------------------------------- /src/common/utils/result.ts: -------------------------------------------------------------------------------- 1 | export function getPagination(records: Array, total: number, pageSize: number, pageNum: number) { 2 | return { 3 | records, 4 | total, 5 | pageSize: pageSize, 6 | current: pageNum, 7 | pages: Math.ceil(total / pageSize), 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/common/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { Models } from '../typings/model' 4 | import { format } from './date' 5 | /** 6 | * 获取某个目录下所有文件的默认导出 7 | * @param filePath 需要遍历的文件路径 8 | */ 9 | export async function getAllFilesExport(filePath: string, callback: Function) { 10 | // 根据文件路径读取文件,返回一个文件列表 11 | const files: string[] = fs.readdirSync(filePath) 12 | // 遍历读取到的文件列表 13 | files.forEach((fileName) => { 14 | // path.join得到当前文件的绝对路径 15 | const absFilePath: string = path.join(filePath, fileName) 16 | const stats: fs.Stats = fs.statSync(absFilePath) 17 | const isFile = stats.isFile() // 是否为文件 18 | const isDir = stats.isDirectory() // 是否为文件夹 19 | if (isFile) { 20 | const file = require(absFilePath) 21 | callback(file.default) 22 | } 23 | if (isDir) { 24 | getAllFilesExport(absFilePath, callback) // 递归,如果是文件夹,就继续遍历该文件夹里面的文件; 25 | } 26 | }) 27 | } 28 | 29 | /** 30 | * 判断某个文件夹是否存在 31 | * @param path 32 | * @returns {boolean} 33 | */ 34 | export function isDirectory(path: string): boolean { 35 | try { 36 | const stat = fs.statSync(path) 37 | return stat.isDirectory() 38 | } catch (error) { 39 | return false 40 | } 41 | } 42 | 43 | export function isValidKey(key: string | number | symbol, object: object): key is keyof typeof object { 44 | return key in object 45 | } 46 | 47 | /** 48 | * 下划线转驼峰 49 | * @param str 50 | * @returns 51 | */ 52 | export function lineToHump(str: string): string { 53 | if (str.startsWith('_')) { 54 | return str 55 | } 56 | return str.replace(/\_(\w)/g, (all, letter: string) => letter.toUpperCase()) 57 | } 58 | 59 | /** 60 | * 驼峰转下划线 61 | * @param str 62 | * @returns 63 | */ 64 | export function humpToLine(str = ''): string { 65 | if (typeof str !== 'string') { 66 | return str 67 | } 68 | return str.replace(/([A-Z])/g, '_$1').toLowerCase() 69 | } 70 | 71 | /** 72 | * 将对象的所有属性由下划线转换成驼峰 73 | * @param obj 74 | * @returns 75 | */ 76 | export function lineToHumpObject(obj: Object) { 77 | let key: string 78 | const element: { 79 | [key: string]: any 80 | } = {} 81 | for (key in obj) { 82 | if (obj.hasOwnProperty(key)) { 83 | if (isValidKey(key, obj)) { 84 | const value = obj[key] 85 | if (typeof key === 'string' && (key as string).indexOf('_at') > -1) { 86 | element[lineToHump(key)] = format(value) 87 | } else { 88 | element[lineToHump(key)] = value 89 | } 90 | } 91 | } 92 | } 93 | return { 94 | ...element, 95 | } 96 | } 97 | 98 | /** 99 | * 将对象的所有属性由驼峰转换为下划线 100 | * @param obj 101 | * @returns 102 | */ 103 | export function humpToLineObject(obj: Object) { 104 | let key: string 105 | const element: { 106 | [key: string]: any 107 | } = {} 108 | for (key in obj) { 109 | if (obj.hasOwnProperty(key)) { 110 | if (isValidKey(key, obj)) { 111 | const value = obj[key] 112 | element[humpToLine(key)] = value || null 113 | } 114 | } 115 | } 116 | return { 117 | ...element, 118 | } 119 | } 120 | 121 | /** 122 | * 将数组变成树 123 | * @param list 124 | * @param rootId 125 | * @param options 126 | * @returns 127 | */ 128 | export function getTreeByList(list: Common.List, rootId: number, options?: Common.TreeOption) { 129 | // 属性配置设置 130 | const attr = { 131 | id: options?.id || 'id', 132 | parentId: options?.parentId || 'parentId', 133 | rootId, 134 | } 135 | const toTreeData = ( 136 | data: Common.List, 137 | attr: { 138 | id: string 139 | parentId: string 140 | rootId: number 141 | } 142 | ) => { 143 | const tree: Common.TreeNode[] = [] 144 | const resData: Common.List = data 145 | for (let i = 0; i < resData.length; i++) { 146 | if (resData[i].parentId === attr.rootId) { 147 | const obj = { 148 | ...resData[i], 149 | id: resData[i][attr.id] as number, 150 | children: [], 151 | } 152 | tree.push(obj as unknown as Common.TreeNode) 153 | resData.splice(i, 1) 154 | i-- 155 | } 156 | } 157 | const run = (treeArrs: Common.TreeNode[]) => { 158 | if (resData.length > 0) { 159 | for (let i = 0; i < treeArrs.length; i++) { 160 | for (let j = 0; j < resData.length; j++) { 161 | if (treeArrs[i].id === resData[j][attr.parentId]) { 162 | const obj: Common.TreeNode = { 163 | ...resData[j], 164 | id: resData[j][attr.id] as number, 165 | children: [], 166 | } as unknown as Common.TreeNode 167 | treeArrs[i].children.push(obj) 168 | resData.splice(j, 1) 169 | j-- 170 | } 171 | } 172 | run(treeArrs[i].children) 173 | } 174 | } 175 | } 176 | run(tree) 177 | return tree 178 | } 179 | const arr = toTreeData(list, attr) 180 | return arr 181 | } 182 | 183 | /** 184 | * 根据某个属性排序 185 | * @param arr 186 | * @param propName 187 | * @param type 188 | */ 189 | export function sort(arr: any[], propName: string, type: Common.SortType) { 190 | arr.sort((a, b) => { 191 | if (type === 'asc') { 192 | return b[propName] - a[propName] 193 | } else { 194 | return a[propName] - b[propName] 195 | } 196 | }) 197 | } 198 | -------------------------------------------------------------------------------- /src/config/Config.ts: -------------------------------------------------------------------------------- 1 | import { QINIU } from '../ak' 2 | import REDIS_DB_NAME from './RedisDbName' 3 | const isDev = process.env.NODE_ENV === 'development' 4 | 5 | export default class Config { 6 | // 是否是测试环境 7 | public static readonly IS_DEV = isDev 8 | public static readonly IP = isDev ? '1.116.40.155' : '10.0.16.8' 9 | // 服务器端口 10 | public static readonly HTTP_PORT = 9002 11 | // 接口前缀 12 | public static readonly API_PREFIX = '/api/' 13 | // 根目录 14 | public static readonly BASE = isDev ? 'src' : 'dist/src' 15 | // redis数据库 16 | public static readonly REDIS_DB_NAME = REDIS_DB_NAME 17 | // mysql配置 18 | public static readonly MYSQL = { 19 | DB_NAME: 'admin', 20 | HOST: Config.IP, 21 | PORT: 3306, 22 | USER_NAME: 'admin', 23 | PASSWORD: 'BhxNnfbRWacKpBjy', 24 | CONNECTION_LIMIT: 60 * 60 * 1000, 25 | CONNECT_TIMEOUT: 1000 * 60 * 60 * 1000, 26 | ACQUIRE_TIMEOUT: 60 * 60 * 1000, 27 | TIMEOUT: 1000 * 60 * 60 * 1000, 28 | } 29 | // redis 30 | public static readonly REDIS = { 31 | PORT: 6379, 32 | HOST: Config.IP, 33 | PASSWORD: 'admin', 34 | DB: 0, 35 | } 36 | // 默认时间格式 37 | public static readonly DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss' 38 | 39 | // 安全配置 40 | public static readonly SECURITY = { 41 | // token key 42 | SECRET_KEY: 'learn-koa-ts', 43 | // 过期时间 44 | EXPIRES_IN: 60 * 60 * 24 * 0.5, 45 | // 存储token的redis数据库名 46 | TOKEN_REDIS_DB: Config.REDIS_DB_NAME.TOKEN, 47 | } 48 | 49 | // 七牛云配置 50 | public static readonly QINIU = { 51 | ...QINIU, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/config/RedisDbName.ts: -------------------------------------------------------------------------------- 1 | enum REDIS_DB_NAME { 2 | DEFAULT, 3 | ROLE, 4 | TOKEN, 5 | } 6 | export default REDIS_DB_NAME 7 | -------------------------------------------------------------------------------- /src/core/HttpException.ts: -------------------------------------------------------------------------------- 1 | import HttpCode, { CODE } from './code' 2 | 3 | // http异常 4 | export class HttpException extends Error { 5 | public message: string 6 | public errorCode: number 7 | public code: number 8 | public data: any 9 | public isBuffer = false 10 | public responseType: string | undefined 11 | constructor(data?: unknown, msg = '服务器异常,请联系管理员', errorCode = 10000, code = 400) { 12 | super() 13 | this.message = msg 14 | this.errorCode = errorCode 15 | this.code = code 16 | this.data = data 17 | } 18 | } 19 | // http参数异常 20 | export class ParameterException extends HttpException { 21 | constructor(msg?: string, errorCode?: CODE) { 22 | super() 23 | this.code = 200 24 | this.message = msg || HttpCode.getMessage(CODE.CODE_11001 || errorCode) 25 | this.errorCode = CODE.CODE_11001 26 | } 27 | } 28 | 29 | // http请求成功 30 | export class Success extends HttpException { 31 | public data 32 | public responseType 33 | public session 34 | constructor(data?: any, msg = 'ok', code = 200, errorCode = 10000, responseType?: string, session?: string) { 35 | super() 36 | this.code = code //200查询成功,201操作成功 37 | this.message = msg 38 | this.errorCode = errorCode 39 | this.data = data 40 | this.responseType = responseType 41 | this.session = session 42 | } 43 | } 44 | // 返回文件流 45 | export class Buffer extends Success { 46 | public data 47 | public responseType 48 | public session 49 | public isBuffer 50 | constructor(data?: any, responseType?: string, session?: string) { 51 | super() 52 | this.code = 200 //200查询成功,201操作成功 53 | this.message = 'ok' 54 | this.errorCode = CODE.CODE_10000 55 | this.data = data 56 | this.responseType = responseType 57 | this.session = session 58 | this.isBuffer = true 59 | } 60 | } 61 | // 404 62 | export class NotFount extends HttpException { 63 | constructor(msg: string, errorCode: number) { 64 | super() 65 | this.code = 404 66 | this.message = msg || '资源未找到' 67 | this.errorCode = errorCode || 10001 68 | } 69 | } 70 | // 授权失败 71 | export class AuthFailed extends HttpException { 72 | constructor(msg?: string, errorCode?: number) { 73 | super() 74 | this.code = 401 75 | this.message = msg || '授权失败' 76 | this.errorCode = errorCode || 10002 77 | } 78 | } 79 | // Forbbiden 80 | export class Forbbiden extends HttpException { 81 | constructor(msg: string, errorCode?: number) { 82 | super() 83 | this.code = 403 84 | this.message = msg || '禁止访问' 85 | this.errorCode = errorCode || 100006 86 | } 87 | } 88 | 89 | // 查询失败 90 | export class QueryFailed extends HttpException { 91 | constructor(msg?: string, errorCode?: number) { 92 | super() 93 | this.code = 500 94 | this.message = msg || '未查到匹配的数据' 95 | this.errorCode = errorCode || 100006 96 | } 97 | } 98 | 99 | // 数据库出错 100 | export class DataBaseFailed extends HttpException { 101 | constructor(msg?: string, errorCode?: number) { 102 | super() 103 | this.code = 500 104 | this.message = msg || '数据库出错' 105 | this.errorCode = errorCode || 100005 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/core/Init.ts: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import http from 'http' 3 | import koaBody from 'koa-body' 4 | import path from 'path' 5 | import { getAllFilesExport } from '../common/utils/utils' 6 | import Router from 'koa-router' 7 | import Config from '../config/Config' 8 | import catchError from '../middlewares/catchError' 9 | import session from 'koa-session' 10 | import { updateRedisRole } from '../server/auth' 11 | import { initPlugin } from '../plugin/index' 12 | class Init { 13 | public static app: Koa 14 | public static server: http.Server 15 | public static initCore(app: Koa, server: http.Server) { 16 | Init.app = app 17 | Init.server = server 18 | Init.loadBodyParser() 19 | Init.initCatchError() 20 | Init.loadSession() 21 | Init.initLoadRouters() 22 | Init.updateRedisRole() 23 | Init.initPlugin() 24 | } 25 | 26 | // 解析body参数 27 | public static loadBodyParser() { 28 | Init.app.use( 29 | koaBody({ 30 | multipart: true, // 支持文件上传 31 | // encoding: 'gzip', 32 | formidable: { 33 | maxFieldsSize: 2 * 1024 * 1024, // 最大文件为2兆 34 | keepExtensions: true, // 保持文件的后缀 35 | }, 36 | }) 37 | ) 38 | } 39 | 40 | // http路由加载 41 | static async initLoadRouters() { 42 | const dirPath = path.join(`${process.cwd()}/${Config.BASE}/api/`) 43 | getAllFilesExport(dirPath, (file: Router) => { 44 | Init.app.use(file.routes()) 45 | }) 46 | } 47 | 48 | // 错误监听和日志处理 49 | public static initCatchError() { 50 | Init.app.use(catchError) 51 | } 52 | 53 | // 加载session 54 | public static loadSession() { 55 | Init.app.keys = ['some secret hurr'] 56 | Init.app.use( 57 | session( 58 | { 59 | key: 'koa:sess', //cookie key (default is koa:sess) 60 | maxAge: 86400000, // cookie的过期时间 maxAge in ms (default is 1 days) 61 | overwrite: true, //是否可以overwrite (默认default true) 62 | httpOnly: true, //cookie是否只有服务器端可以访问 httpOnly or not (default true) 63 | signed: true, //签名默认true 64 | rolling: false, //在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false) 65 | renew: false, //(boolean) renew session when session is nearly expired, 66 | }, 67 | Init.app 68 | ) 69 | ) 70 | } 71 | 72 | // 更新redis里的角色数据 73 | public static updateRedisRole() { 74 | updateRedisRole() 75 | } 76 | 77 | public static initPlugin() { 78 | initPlugin({ 79 | pluginNames: ['WebSocket'], 80 | app: Init.app, 81 | server: Init.server, 82 | }) 83 | } 84 | } 85 | 86 | export default Init.initCore 87 | -------------------------------------------------------------------------------- /src/core/code.ts: -------------------------------------------------------------------------------- 1 | export enum CODE { 2 | /** 3 | * 请求成功 4 | */ 5 | CODE_10000 = 10000, 6 | 7 | /** 用户状态码*/ 8 | /** 9 | * 未登录 10 | */ 11 | CODE_10001 = 10001, 12 | /** 13 | * 账号不存在或者密码错误 14 | */ 15 | CODE_10002 = 10002, 16 | /** 17 | * 账号已被锁定,请与管理员联系 18 | */ 19 | CODE_10003 = 10003, 20 | /** 21 | * 登录未授权 22 | */ 23 | CODE_10004 = 10004, 24 | /** 25 | * 请勿重复登录 26 | */ 27 | CODE_10005 = 10005, 28 | /** 29 | * 验证码不正确 30 | */ 31 | CODE_10008 = 10008, 32 | /** 33 | * 验证码已过期 34 | */ 35 | CODE_10009 = 10009, 36 | /** 37 | * 参数错误 38 | */ 39 | CODE_11001 = 11001, 40 | 41 | /** 角色状态码*/ 42 | 43 | /** 44 | * 角色等级不够 45 | */ 46 | CODE_20001 = 20001, 47 | /** 48 | * 被禁言 49 | */ 50 | CODE_20002 = 20002, 51 | 52 | /** 权限状态码*/ 53 | 54 | /** 55 | * 无权限 56 | */ 57 | CODE_30001 = 30001, 58 | /** 59 | * 文本内容含有敏感词汇 60 | */ 61 | CODE_30002 = 30002, 62 | 63 | /** 话题状态码*/ 64 | 65 | /** 66 | * 话题未找到 67 | */ 68 | CODE_40001 = 40001, 69 | /** 70 | * 库存不足 71 | */ 72 | CODE_40002 = 40002, 73 | /** 74 | * 积分不足 75 | */ 76 | CODE_40003 = 40003, 77 | /** 78 | * 奖品已不存在 79 | */ 80 | CODE_40004 = 40004, 81 | 82 | /** 话题回复状态码*/ 83 | /** 84 | * 话题回复未找到 85 | */ 86 | CODE_50001 = 50001, 87 | /** 88 | * 话题回复添加失败 89 | */ 90 | CODE_50002 = 50002, 91 | /** 92 | * 话题回复修改失败 93 | */ 94 | CODE_50003 = 50003, 95 | /** 96 | * 话题回复已被锁定 97 | */ 98 | CODE_50004 = 50004, 99 | /**已结题,暂不能回复 */ 100 | CODE_50005 = 50005, 101 | 102 | /** 评论状态码*/ 103 | /** 104 | * 评论未找到 105 | */ 106 | CODE_60001 = 60001, 107 | /**评论添加失败 */ 108 | CODE_60002 = 60002, 109 | /**评论已被锁定 */ 110 | CODE_60003 = 60003, 111 | 112 | /** 通知状态码*/ 113 | 114 | /**通知对象未找到 */ 115 | CODE_80001 = 80001, 116 | 117 | /** 文件状态码*/ 118 | 119 | /**文件未找到 */ 120 | CODE_90001 = 90001, 121 | /**文件路径不能为空 */ 122 | CODE_90002 = 90002, 123 | /**文件后缀不能为空 */ 124 | CODE_90003 = 90003, 125 | /**文件内容为空 */ 126 | CODE_90004 = 90004, 127 | /**文件上传失败 */ 128 | CODE_90005 = 90005, 129 | /**Excel文件未找到 */ 130 | CODE_91001 = 91001, 131 | /**该文件非Excel文件 */ 132 | CODE_91002 = 91002, 133 | /**Excel文件转储失败 */ 134 | CODE_91003 = 91003, 135 | /**Excel文件转储过程中发生异常 */ 136 | CODE_91004 = 91004, 137 | } 138 | 139 | export default class HttpCode { 140 | private static messageMap = { 141 | [CODE.CODE_10000]: '请求成功!', 142 | [CODE.CODE_10001]: '尚未登录!', 143 | [CODE.CODE_10002]: '账号不存在或者密码错误!', 144 | [CODE.CODE_10003]: '账号已被锁定,请与管理员联系!', 145 | [CODE.CODE_10004]: '登录未授权!', 146 | [CODE.CODE_10005]: '请勿重复登录!', 147 | [CODE.CODE_10008]: '验证码不正确!', 148 | [CODE.CODE_10009]: '验证码已过期!', 149 | [CODE.CODE_11001]: '头衔列表未找到!', 150 | 151 | [CODE.CODE_20001]: '您的角色等级低,请先提高等级!', 152 | [CODE.CODE_20002]: '您的账号已被锁定,请联系管理员!', 153 | 154 | [CODE.CODE_30001]: '您的账号暂无该权限!', 155 | [CODE.CODE_30002]: '文本内容含有敏感词汇:', 156 | 157 | [CODE.CODE_40001]: '话题未找到!', 158 | [CODE.CODE_40002]: '库存不足!', 159 | [CODE.CODE_40003]: '积分不足!', 160 | [CODE.CODE_40004]: '奖品已不存在!', 161 | 162 | [CODE.CODE_50001]: '该回复未找到!', 163 | [CODE.CODE_50002]: '该回复失败!', 164 | [CODE.CODE_50003]: '该回复修改失败!', 165 | [CODE.CODE_50004]: '该回复已被锁定!', 166 | [CODE.CODE_50005]: '已结题,暂不能回复!', 167 | 168 | [CODE.CODE_60001]: '该评论未找到!', 169 | [CODE.CODE_60002]: '不能给自己评论!', 170 | [CODE.CODE_60003]: '该评论已被锁定!', 171 | 172 | [CODE.CODE_80001]: '通知对象未找到!', 173 | 174 | [CODE.CODE_90001]: '文件未找到!', 175 | [CODE.CODE_90002]: '文件路径不能为空!', 176 | [CODE.CODE_90003]: '文件后缀不能为空!', 177 | [CODE.CODE_90004]: '文件内容为空!', 178 | [CODE.CODE_90005]: '文件上传失败!', 179 | [CODE.CODE_91001]: 'Excel文件未找到!', 180 | [CODE.CODE_91002]: '该文件非Excel文件!', 181 | [CODE.CODE_91003]: 'Excel文件转储失败!', 182 | [CODE.CODE_91004]: 'Excel文件转储失败!', 183 | } 184 | static getMessage(code: CODE) { 185 | return HttpCode.messageMap[code] 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/middlewares/catchError.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../common/typings/model' 2 | import { Success, HttpException, Buffer } from '../core/HttpException' 3 | import { infoLog, errorLog } from '../server/logs' 4 | export default async function catchError(ctx: Models.Ctx, next: Function) { 5 | try { 6 | await next() 7 | } catch (error: any) { 8 | // 当前错误是否是我们自定义的Http错误 9 | const isHttpException = error instanceof HttpException 10 | 11 | // 如果不是, 则抛出错误 12 | if (!isHttpException) { 13 | errorLog(ctx, error) 14 | const { method, path } = ctx 15 | ctx.body = { 16 | msg: '未知错误', 17 | errorCode: 9999, 18 | requestUrl: `${method} ${path}`, 19 | } 20 | ctx.status = 500 21 | } 22 | // 如果是已知错误 23 | else { 24 | // 根据给error设置的相应类型设置相应数据类型 25 | if (error.responseType) { 26 | ctx.response.type = error.responseType 27 | } 28 | // 如果是文件流,则直接返回文件 29 | if (error.isBuffer) { 30 | ctx.body = error.data 31 | } else { 32 | ctx.body = { 33 | msg: error.message, 34 | errorCode: error.errorCode, 35 | data: error.data, 36 | } 37 | } 38 | 39 | ctx.status = error.code 40 | if (error instanceof Success || error instanceof Buffer) { 41 | infoLog(ctx) 42 | } else { 43 | errorLog(ctx, error) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/middlewares/validator.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../common/typings/model' 2 | import { ParameterException } from '../core/HttpException' 3 | import { validate } from '../server/ajv' 4 | 5 | // 请求参数类型 6 | type RequestDataType = 'query' | 'body' 7 | /** 8 | * 数据校验中间件 9 | */ 10 | function validator(schema: string | boolean | object, type: RequestDataType = 'query') { 11 | return async function validator(ctx: Models.Ctx, next: Function) { 12 | const data = ctx.request[type] 13 | const errors = validate(schema, data) || null 14 | if (errors) { 15 | console.log('数据校验失败') 16 | //校验失败 17 | throw new ParameterException(errors) 18 | } 19 | await next() 20 | } 21 | } 22 | export default validator 23 | -------------------------------------------------------------------------------- /src/middlewares/verificationCodeValidator.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../common/typings/model' 2 | import { ParameterException } from '../core/HttpException' 3 | 4 | /** 5 | * 校验验证码 6 | * @param ctx 7 | * @param next 8 | */ 9 | export default async function verificationCodeValidator(ctx: Models.Ctx, next: Function) { 10 | const { code } = ctx.request.body 11 | if (ctx.session!.code !== code) { 12 | throw new ParameterException('验证码错误') 13 | } else { 14 | await next() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/middlewares/verifyToken.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../common/typings/model' 2 | import Config from '../config/Config' 3 | import { AuthFailed, Forbbiden } from '../core/HttpException' 4 | import JWT from 'jsonwebtoken' 5 | import { getTokenValue } from '../server/auth/token' 6 | import { getRedisUserPermission } from '../server/auth' 7 | 8 | /** 9 | * 校验token是否合法 10 | * @param ctx 11 | * @param next 12 | * @param callback 13 | */ 14 | export default async function verifyToken(ctx: Models.Ctx, next: Function, callback?: Function) { 15 | // 获取token 16 | const userToken = getToken(ctx) 17 | // 如果token不存在, 或者不存在redis里 18 | if (!userToken || !(await getTokenValue(userToken)).results) { 19 | throw new AuthFailed('登录失效, 请重新登录') 20 | } 21 | // 尝试解析token, 获取uid和scope 22 | const { uid, scope } = (await analyzeToken(userToken)) as System.Decode 23 | // 在上下文保存uid和scope 24 | ctx.auth = { 25 | uid, 26 | scope, 27 | } 28 | if (callback) { 29 | await callback({ uid, scope }) 30 | } 31 | await next() 32 | } 33 | 34 | /** 35 | * 获取token 36 | * @param ctx 37 | * @returns 38 | */ 39 | export function getToken(ctx: Models.Ctx): string { 40 | return ctx.header['authorization'] || ctx.cookies.get('authorization') || '' 41 | } 42 | 43 | /** 44 | * 解析token 45 | * @param token 46 | * @returns 47 | */ 48 | async function analyzeToken(token: string) { 49 | return new Promise((resolve, reject) => { 50 | JWT.verify(token, Config.SECURITY.SECRET_KEY, (error, decode) => { 51 | if (error) { 52 | reject(error) 53 | } 54 | 55 | resolve(decode) 56 | }) 57 | }).catch((error) => { 58 | if (error.name === 'TokenExpiredError') { 59 | throw new AuthFailed('token已过期') 60 | } 61 | throw new AuthFailed('token不合法') 62 | }) 63 | } 64 | 65 | /** 66 | * 校验token权限 67 | * @param ctx 68 | * @param next 69 | */ 70 | export async function verifyTokenPermission(ctx: Models.Ctx, next: Function) { 71 | await verifyToken(ctx, next, async (decode: System.Decode) => { 72 | const permissionList: string[] = await getRedisUserPermission(decode) 73 | 74 | const bool = permissionList.find((permission) => { 75 | const path = `${Config.API_PREFIX}v1/${permission.split(':').join('/')}` 76 | return path === ctx.path 77 | }) 78 | if (!bool) { 79 | throw new Forbbiden('权限不足') 80 | } 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/plugin/WebSocket/index.ts: -------------------------------------------------------------------------------- 1 | import IO, { Server } from 'socket.io' 2 | import { PluginOptions } from '..' 3 | 4 | export default class WebSocket { 5 | constructor(options: PluginOptions) { 6 | const port = 9003 7 | const io = new Server(port, {}) 8 | io.path('/im/') 9 | io.on('connection', (socket) => { 10 | console.log('a user connected') 11 | 12 | socket.on('message', (data) => { 13 | console.log(data) 14 | socket.send('hi') 15 | }) 16 | 17 | socket.on('disconnect', () => { 18 | console.log('user disconnected') 19 | }) 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import http from 'http' 3 | import WebSocket from './WebSocket' 4 | 5 | type PluginName = 'WebSocket' 6 | 7 | const allPlugin = { 8 | WebSocket, 9 | } 10 | 11 | export interface PluginOptions { 12 | pluginNames: PluginName[] 13 | app: Koa 14 | server: http.Server 15 | } 16 | 17 | export function initPlugin(options: PluginOptions) { 18 | options.pluginNames.forEach((pluginName) => { 19 | new allPlugin[pluginName](options) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /src/server/ajv/ajv.ts: -------------------------------------------------------------------------------- 1 | import Ajv from 'ajv' 2 | // import ajvKeywords from 'ajv-keywords' 3 | import ajvConfig from './ajvConfig' 4 | const ajv = new Ajv(ajvConfig) 5 | require('ajv-keywords')(ajv) 6 | 7 | export default ajv 8 | -------------------------------------------------------------------------------- /src/server/ajv/ajvConfig.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config/Config' 2 | 3 | export default { 4 | allErrors: Config.IS_DEV, // 是否输出所有的错误(比较慢) 5 | } 6 | -------------------------------------------------------------------------------- /src/server/ajv/index.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'ajv' 2 | import ajv from './ajv' 3 | 4 | /** 5 | * json schema 校验 6 | * @param schema 7 | * @param data 8 | * @returns 9 | */ 10 | export function validate(schema: string | Schema, data = {}) { 11 | try { 12 | const valid: boolean = ajv.validate(schema, data) 13 | if (!valid) { 14 | return ajv.errorsText() 15 | } 16 | } catch (error) { 17 | console.log(error) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/server/auth/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config/Config' 2 | import JWT from 'jsonwebtoken' 3 | import { command } from '../mysql' 4 | import { Models } from '../../common/typings/model' 5 | import { selectDb } from '../redis' 6 | import { getTreeByList } from '../../common/utils/utils' 7 | import redis from '../redis/redis' 8 | 9 | /** 10 | * 构建token 11 | * @param uid 12 | * @param scope 13 | * @returns 14 | */ 15 | export function generateToken(uid: System.Uid, scope: System.Scope) { 16 | //传入id和权限 17 | const secretKey = Config.SECURITY.SECRET_KEY 18 | const expiresIn = Config.SECURITY.EXPIRES_IN 19 | const token = JWT.sign( 20 | { 21 | uid, 22 | scope, 23 | }, 24 | secretKey, 25 | { 26 | expiresIn, 27 | } 28 | ) 29 | return token 30 | } 31 | 32 | /** 33 | * 获取用户权限 34 | * @param decode 35 | * @returns 36 | */ 37 | export function getUserPermission(decode: System.Decode): Promise { 38 | const { scope } = decode 39 | return new Promise(async (resolve, reject) => { 40 | let res: Models.Result 41 | 42 | try { 43 | res = await command(` 44 | SELECT 45 | menu_ids 46 | FROM 47 | system_role 48 | where 49 | id = ${scope} 50 | `) 51 | if (!res.error) { 52 | const role = res.results[0] 53 | if (role) { 54 | const menuList: System.Menu[] = ( 55 | await command(` 56 | SELECT 57 | permission 58 | FROM 59 | system_menu 60 | WHERE 61 | FIND_IN_SET( 62 | id, 63 | '${role.menuIds}') 64 | `) 65 | ).results 66 | resolve(menuList) 67 | } else { 68 | resolve([]) 69 | } 70 | } else { 71 | reject() 72 | } 73 | } catch (error) { 74 | console.log(error) 75 | reject() 76 | } 77 | }) 78 | } 79 | 80 | /** 81 | * 校验用户权限 82 | * @param decode 83 | * @returns 84 | */ 85 | export function getRedisUserPermission(decode: System.Decode): Promise { 86 | const { scope } = decode 87 | return new Promise(async (resolve) => { 88 | selectDb(Config.REDIS_DB_NAME.ROLE).then(() => { 89 | redis.keys('*').then(async (res) => { 90 | Promise.all(res.map((item) => redis.hgetall(item))).then((result) => { 91 | const allRoleList: Common.List = result.map((item) => { 92 | return { 93 | ...item, 94 | id: Number(item.id), 95 | parentId: Number(item.parentId), 96 | } 97 | }) 98 | const roleTree = getTreeByList(allRoleList, 0) 99 | // 过滤, 获取当前角色及当前角色的祖先角色的所有记录 100 | const roleList: Common.TreeNode[] = [] 101 | const each = (list: Common.TreeNode[], nodeId: number) => { 102 | const arr = list.filter((item) => item.id === nodeId) 103 | if (arr.length) { 104 | roleList.push(...arr) 105 | each(list, arr[0].parentId) 106 | } 107 | } 108 | const roleIds = scope.split(',') 109 | roleIds.forEach((roleId) => { 110 | each(roleTree, Number(roleId)) 111 | }) 112 | 113 | // 当前角色有权限的所有权限. 114 | let permissionList: string[] = [] 115 | const merge = (list: Common.TreeNode[]) => { 116 | list.forEach((item) => { 117 | permissionList = [...new Set([...permissionList, ...(item.permissions as string).split(',')])] 118 | if (item.children) { 119 | merge(item.children) 120 | } 121 | }) 122 | } 123 | // 合并当前角色和当前角色的祖先角色的所有菜单 124 | merge(roleTree) 125 | resolve(permissionList) 126 | }) 127 | }) 128 | }) 129 | }) 130 | } 131 | 132 | /** 133 | * 获取所有角色的权限列表 134 | * @returns 135 | */ 136 | export function getAllRolePermission(): Promise { 137 | return new Promise(async (resolve, reject) => { 138 | let res: Models.Result 139 | try { 140 | res = await command(` 141 | SELECT 142 | id, 143 | menu_ids, 144 | parent_id parentId, 145 | name 146 | FROM 147 | system_role 148 | `) 149 | if (!res.error) { 150 | const RoleList: System.Role[] = [] 151 | for (let i = 0; i < res.results.length; i++) { 152 | const item: System.Role = res.results[i] 153 | RoleList.push({ 154 | id: item.id, 155 | parentId: item.parentId, 156 | name: item.name, 157 | menuList: await getUserPermission({ 158 | scope: String(item.id), 159 | uid: 0, 160 | }), 161 | children: [], 162 | menuIds: item.menuIds, 163 | }) 164 | } 165 | resolve(RoleList) 166 | } else { 167 | reject() 168 | } 169 | } catch (error) { 170 | console.log(error) 171 | reject() 172 | } 173 | }) 174 | } 175 | 176 | /** 177 | * 更新redis里的角色 178 | */ 179 | export function updateRedisRole() { 180 | getAllRolePermission().then((list) => { 181 | list.forEach((res) => { 182 | if (res.menuList.length > 0) { 183 | updateRoles( 184 | (res.id || '').toString(), 185 | new Map([ 186 | ['id', res.id.toString()], 187 | ['parentId', res.parentId.toString()], 188 | ['permissions', res.menuList.map((item: { permission: string }) => item.permission).join(',')], 189 | ]) 190 | ) 191 | } 192 | }) 193 | }) 194 | } 195 | 196 | /** 197 | * 更新权限 198 | * @param roleId 199 | * @param obj 200 | * @returns 201 | */ 202 | export function updateRoles(roleId: string, obj: Map): Promise { 203 | return new Promise((resolve) => { 204 | selectDb(Config.REDIS_DB_NAME.ROLE).then(async () => { 205 | await redis.del(roleId) 206 | redis.hmset(roleId, obj).then((res) => { 207 | const result: Models.Result = { 208 | msg: 'ok', 209 | state: 1, 210 | results: res, 211 | fields: [], 212 | } 213 | resolve(result) 214 | }) 215 | }) 216 | }) 217 | } 218 | -------------------------------------------------------------------------------- /src/server/auth/token.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../common/typings/model' 2 | import Config from '../../config/Config' 3 | import { selectDb } from '../redis' 4 | import redis from '../redis/redis' 5 | 6 | /** 7 | * 保存token 8 | * @param key 9 | * @param uid 10 | * @returns 11 | */ 12 | export async function saveToken(key: string, uid: number): Promise { 13 | return new Promise((resolve) => { 14 | selectDb(Config.SECURITY.TOKEN_REDIS_DB).then(() => { 15 | redis.setex(key, Config.SECURITY.EXPIRES_IN, uid).then((res) => { 16 | const result: Models.Result = { 17 | msg: 'ok', 18 | state: 1, 19 | results: res, 20 | fields: [], 21 | } 22 | resolve(result) 23 | }) 24 | }) 25 | }) 26 | } 27 | 28 | /** 29 | * 获取token的值 30 | * @param key 31 | * @returns 32 | */ 33 | export async function getTokenValue(key: string): Promise { 34 | return new Promise((resolve) => { 35 | selectDb(Config.SECURITY.TOKEN_REDIS_DB).then(() => { 36 | redis.get(key).then((res) => { 37 | const result: Models.Result = { 38 | msg: 'ok', 39 | state: 1, 40 | results: res, 41 | fields: [], 42 | } 43 | resolve(result) 44 | }) 45 | }) 46 | }) 47 | } 48 | 49 | /** 50 | * 删除token 51 | * @param key 52 | * @returns 53 | */ 54 | export async function deleteToken(key: string): Promise { 55 | return new Promise((resolve) => { 56 | selectDb(Config.SECURITY.TOKEN_REDIS_DB).then(() => { 57 | redis.del(key).then((res) => { 58 | const result: Models.Result = { 59 | msg: 'ok', 60 | state: 1, 61 | results: res, 62 | fields: [], 63 | } 64 | resolve(result) 65 | }) 66 | }) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/server/db/dbConfing.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config/Config' 2 | 3 | export default { 4 | url: `mongodb://admin:123456@${Config.IP}:27017/`, 5 | } 6 | -------------------------------------------------------------------------------- /src/server/db/index.ts: -------------------------------------------------------------------------------- 1 | // const mongoClient = require('mongodb').MongoClient 2 | // const ObjectId = require('mongodb').ObjectId 3 | import dbConfing from './dbConfing' 4 | import { DataBaseFailed } from '../../core/HttpException' 5 | import { lineToHumpObject, humpToLineObject } from '../../common/utils/utils' 6 | import { ObjectId, MongoClient } from 'mongodb' 7 | import { Models } from '../../common/typings/model' 8 | 9 | interface DBCommandOption { 10 | dbName: string 11 | collectionName: string 12 | isPagination: boolean 13 | pageSize: number 14 | pageNum: number 15 | } 16 | function connect(callback: Common.Fun) { 17 | MongoClient.connect(dbConfing.url) 18 | .then((client: MongoClient) => { 19 | console.log(client) 20 | 21 | callback(client) 22 | // (err: { message: string }, client: any) => { 23 | // if (err) { 24 | // throw new DataBaseFailed('数据库连接出错' + ':' + err.message) 25 | // } 26 | // callback(client) 27 | // } 28 | }) 29 | .catch((error) => { 30 | throw new DataBaseFailed(`数据库连接出错:${error.message}`) 31 | }) 32 | } 33 | 34 | /* 35 | * 数据库查询 36 | * @param command 过滤条件 37 | * @param option 表明和集合名 38 | */ 39 | export async function command(command: Common.Params = {}, option: DBCommandOption): Promise { 40 | try { 41 | return new Promise(async (resolve, reject) => { 42 | connect(async (client: any) => { 43 | const col = client.db(option.dbName).collection(option.collectionName) 44 | let total = 0 45 | const page = (data: any) => { 46 | if (option.isPagination) { 47 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 48 | return data.skip((option.pageNum - 1) * option.pageSize).limit(option.pageSize) 49 | } else { 50 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 51 | return data 52 | } 53 | } 54 | const query = col.find(humpToLineObject(command)) 55 | 56 | total = await query.count() 57 | page(query) 58 | .toArray() 59 | .then((items: any[]) => { 60 | client?.close() 61 | const result: Models.Result = { 62 | msg: 'ok', 63 | state: 1, 64 | // results: items, 65 | results: { 66 | list: items.map(lineToHumpObject), 67 | total: option.isPagination ? total : undefined, 68 | }, 69 | } 70 | resolve(result) 71 | }) 72 | .catch(reject) 73 | }) 74 | }) 75 | } catch (err) { 76 | // console.log(command) 77 | throw new DataBaseFailed() 78 | } 79 | } 80 | 81 | export function getDbId(id?: string | number | ObjectId | Buffer | Uint8Array | undefined): ObjectId { 82 | return new ObjectId(id) 83 | } 84 | -------------------------------------------------------------------------------- /src/server/logs/index.ts: -------------------------------------------------------------------------------- 1 | import logger from './logger' 2 | import { Models } from '../../common/typings/model' 3 | 4 | /** 5 | * 记录信息日志 6 | * @param ctx 7 | */ 8 | export function infoLog(ctx: Models.Ctx) { 9 | const { method, response, originalUrl } = ctx 10 | logger.info(method, response.status, originalUrl) 11 | } 12 | 13 | /** 14 | * 记录错误日志 15 | * @param ctx 16 | * @param error 17 | */ 18 | export function errorLog(ctx: Models.Ctx, error: any) { 19 | const { method, response, originalUrl } = ctx 20 | logger.error(method, response.status, originalUrl, error) 21 | } 22 | -------------------------------------------------------------------------------- /src/server/logs/logger.ts: -------------------------------------------------------------------------------- 1 | import log4js from 'log4js' 2 | import fs from 'fs' 3 | import { isDirectory } from '../../common/utils/utils' 4 | import logsConfing from './logsConfing' 5 | 6 | //检查某个目录是否存在 7 | 8 | if (!isDirectory('logs')) { 9 | fs.mkdirSync('logs') 10 | } 11 | 12 | log4js.configure(logsConfing) 13 | 14 | const logger = log4js.getLogger('cheese') 15 | 16 | export default logger 17 | -------------------------------------------------------------------------------- /src/server/logs/logsConfing.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | appenders: { 3 | console: { 4 | type: 'console', 5 | }, 6 | date: { 7 | type: 'dateFile', 8 | filename: 'logs/date', 9 | category: 'normal', 10 | alwaysIncludePattern: true, 11 | pattern: '-yyyy-MM-dd-hh.log', 12 | }, 13 | }, 14 | categories: { 15 | default: { 16 | appenders: ['console', 'date'], 17 | level: 'info', 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/server/mailer/index.ts: -------------------------------------------------------------------------------- 1 | import { ParameterException } from '../../core/HttpException' 2 | import transporter from './transporter' 3 | interface MailOptions { 4 | from?: string // 发件人 5 | to: string // 收件人 6 | subject: string // 主题 7 | text: string // plain text body 8 | html: string // html body 9 | } 10 | 11 | /** 12 | * 发送邮件 13 | * @param { MailOptions } mailOptions 14 | * @returns 15 | */ 16 | export async function sendEmail({ from = '"Fhtwl" <1121145488@qq.com>', to, subject, text, html }: MailOptions) { 17 | return new Promise((resolve, reject) => { 18 | const mailOptions = { 19 | from, 20 | to, 21 | subject, 22 | text, 23 | html, 24 | } 25 | mailOptions.from = from 26 | mailOptions.to = to 27 | mailOptions.subject = subject 28 | mailOptions.text = text 29 | mailOptions.html = html 30 | transporter 31 | .sendMail(mailOptions) 32 | .then((res: { response: string | string[] }) => { 33 | if (res.response.indexOf('250') > -1) { 34 | resolve(true) 35 | } else { 36 | reject() 37 | } 38 | }) 39 | .catch((error) => { 40 | reject(error.message) 41 | }) 42 | }).catch((error) => { 43 | throw new ParameterException(error) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/server/mailer/mailerConfing.ts: -------------------------------------------------------------------------------- 1 | import { email } from '../../ak' 2 | 3 | export default { 4 | service: 'qq', //使用了内置传输发送邮件 查看支持列表:https://nodemailer.com/smtp/well-known/ 5 | port: 456, // SMTP 端口 6 | auth: { 7 | user: '1121145488@qq.com', // 邮箱 8 | pass: email.pass, // 这里密码不是邮箱密码,是你设置的smtp授权码 9 | }, 10 | secureConnection: true, // 使用 SSL 11 | } 12 | -------------------------------------------------------------------------------- /src/server/mailer/transporter.ts: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer' 2 | import mailerConfing from './mailerConfing' 3 | 4 | // 开启一个 SMTP 连接池 5 | const transporter = nodemailer.createTransport(mailerConfing) 6 | 7 | export default transporter 8 | -------------------------------------------------------------------------------- /src/server/mysql/index.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../../common/typings/model' 2 | import pool from './pool' 3 | import { DataBaseFailed } from '../../core/HttpException' 4 | import { lineToHumpObject } from '../../common/utils/utils' 5 | import mysql from 'mysql' 6 | 7 | /* 8 | * 数据库增删改查 9 | * @param command 增删改查语句 10 | * @param value 对应的值 11 | */ 12 | export async function command(command: string, value?: Array): Promise { 13 | try { 14 | return new Promise((resolve, reject: (error: Models.MysqlError) => void) => { 15 | pool.getConnection((error: mysql.MysqlError, connection: mysql.PoolConnection) => { 16 | // 如果连接出错, 抛出错误 17 | if (error) { 18 | const result: Models.MysqlError = { 19 | error, 20 | msg: '数据库连接出错' + ':' + error.message, 21 | } 22 | reject(result) 23 | } 24 | 25 | const callback: mysql.queryCallback = (err, results?: any, fields?: mysql.FieldInfo[]) => { 26 | connection.release() 27 | if (err) { 28 | const result: Models.MysqlError = { 29 | error: err, 30 | msg: err.sqlMessage || '数据库增删改查出错', 31 | } 32 | 33 | reject(result) 34 | } else { 35 | const result: Models.Result = { 36 | msg: 'ok', 37 | state: 1, 38 | // 将数据库里的字段, 由下划线更改为小驼峰 39 | results: results instanceof Array ? results.map(lineToHumpObject) : results, 40 | fields: fields || [], 41 | } 42 | resolve(result) 43 | } 44 | } 45 | // 执行mysql语句 46 | if (value) { 47 | pool.query(command, value, callback) 48 | } else { 49 | pool.query(command, callback) 50 | } 51 | }) 52 | }).catch((error) => { 53 | throw new DataBaseFailed(error.msg) 54 | }) 55 | } catch { 56 | throw new DataBaseFailed() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/server/mysql/mysqlConfing.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config/Config' 2 | 3 | export default { 4 | host: Config.MYSQL.HOST, 5 | port: Config.MYSQL.PORT, 6 | user: Config.MYSQL.USER_NAME, 7 | password: Config.MYSQL.PASSWORD, 8 | database: Config.MYSQL.DB_NAME, 9 | multipleStatements: true, // 运行执行多条语句 10 | connectionLimit: Config.MYSQL.CONNECTION_LIMIT, 11 | connectTimeout: Config.MYSQL.CONNECT_TIMEOUT, 12 | acquireTimeout: Config.MYSQL.ACQUIRE_TIMEOUT, 13 | timeout: Config.MYSQL.TIMEOUT, 14 | } 15 | -------------------------------------------------------------------------------- /src/server/mysql/pool.ts: -------------------------------------------------------------------------------- 1 | import mysql from 'mysql' 2 | import mysqlConfing from './mysqlConfing' 3 | 4 | const pool = mysql.createPool(mysqlConfing) 5 | export default pool 6 | -------------------------------------------------------------------------------- /src/server/qiniu/index.ts: -------------------------------------------------------------------------------- 1 | import qiniu from 'qiniu' 2 | import formidable from 'formidable' 3 | import qiniuConfig from './qiniuConfig' 4 | interface RespBody { 5 | key: string 6 | hash: string 7 | size: number 8 | bucket: string 9 | mimeType: string 10 | } 11 | 12 | const { accessKey, secretKey, bucket } = qiniuConfig 13 | 14 | const putPolicy = new qiniu.rs.PutPolicy({ 15 | scope: bucket, 16 | // 上传成功后返回数据键值对参数设置 17 | returnBody: '{"key":"$(key)","hash":"$(etag)","size":$(fsize),"bucket":"$(bucket)", "mimeType":"$(mimeType)"}', 18 | }) 19 | 20 | /** 21 | * 获取上传凭证 22 | * @returns 23 | */ 24 | export function updateToken() { 25 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey) 26 | return putPolicy.uploadToken(mac) 27 | } 28 | 29 | /** 30 | * 上传 31 | * @param file 32 | * @returns 33 | */ 34 | export async function upload(file: formidable.File): Promise { 35 | return new Promise((resolve, reject) => { 36 | const config: qiniu.conf.Config = new qiniu.conf.Config({ 37 | useHttpsDomain: true, // 是否使用https域名 38 | useCdnDomain: true, // 上传是否使用cdn加速 39 | }) 40 | const formUploader = new qiniu.form_up.FormUploader(config) // 生成表单上传的类 41 | const putExtra = new qiniu.form_up.PutExtra() // 生成表单提交额外参数 42 | formUploader.putFile( 43 | updateToken(), 44 | `upload/${file.originalFilename}`, // 默认上传到upload文件夹下 45 | file.filepath, 46 | putExtra, 47 | function (respErr, respBody, respInfo) { 48 | if (respErr) { 49 | console.log(respErr) 50 | reject(respErr) 51 | } 52 | if (respInfo.statusCode == 200) { 53 | resolve(respBody) 54 | } else { 55 | console.log(respInfo.statusCode) 56 | reject(respBody) 57 | } 58 | } 59 | ) 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /src/server/qiniu/qiniuConfig.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config/Config' 2 | export default { 3 | accessKey: Config.QINIU.AK, 4 | secretKey: Config.QINIU.SK, 5 | bucket: Config.QINIU.BUCKET, // 七牛云存储空间名 6 | } 7 | -------------------------------------------------------------------------------- /src/server/redis/index.ts: -------------------------------------------------------------------------------- 1 | import { DataBaseFailed } from '../../core/HttpException' 2 | import redis from './redis' 3 | /** 4 | * redis报错回调 5 | * @param err 6 | */ 7 | export function redisCatch(err: Error) { 8 | throw new DataBaseFailed(err.message) 9 | } 10 | 11 | /** 12 | * 选择数据库 13 | * @param DbName 14 | * @returns 15 | */ 16 | export async function selectDb(DbName: number) { 17 | return new Promise((resolve) => { 18 | redis 19 | .select(DbName) 20 | .then(() => { 21 | resolve(true) 22 | }) 23 | .catch(redisCatch) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/server/redis/redis.ts: -------------------------------------------------------------------------------- 1 | import redisConfing from './redisConfing' 2 | import IoreDis from 'ioredis' 3 | const redis = new IoreDis(redisConfing) 4 | export default redis 5 | 6 | setInterval(() => { 7 | redis.exists('0') 8 | }, 15000) 9 | -------------------------------------------------------------------------------- /src/server/redis/redisConfing.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../config/Config' 2 | 3 | export default { 4 | port: Config.REDIS.PORT, // Redis port 5 | host: Config.REDIS.HOST, // Redis host 6 | password: Config.REDIS.PASSWORD, 7 | db: Config.REDIS.DB, 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", // 目标语言版本 4 | "module": "commonjs", // 指定生成代码的模板标准 5 | "outDir": "./dist", // 指定输出目录 6 | "rootDir": "./", // 编译文件根目录 7 | "strict": true, // 严格模式 8 | "allowSyntheticDefaultImports": true, // 没有默认导出时, 编译器会创建一个默认导出 9 | "esModuleInterop": true, // 允许export= 导出, 由import from导入 10 | "forceConsistentCasingInFileNames": true, // 强制区分大小写 11 | "noImplicitAny": false, 12 | "typeRoots": ["./node_modules/@types/", "./node_modules/@fhtwl-admin/"] 13 | }, 14 | "include": [ // 需要编译的的文件和目录 15 | ".eslintrc.json", 16 | ".prettierrc", 17 | "src/**/*" 18 | ], 19 | "files": [ 20 | "src/app.ts" 21 | ] 22 | } --------------------------------------------------------------------------------