├── .autod.conf.js ├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── README.md ├── apiDoc.json ├── app.ts ├── app ├── constants │ ├── date.ts │ └── user.ts ├── controller │ ├── index.ts │ ├── menu.ts │ ├── role.ts │ ├── sms.ts │ ├── upload.ts │ └── user.ts ├── core │ ├── model.ts │ └── service.ts ├── exception │ └── ApiException.ts ├── extend │ └── context.ts ├── middleware │ ├── auth.ts │ └── errHandle.ts ├── model │ ├── menu.ts │ ├── role.ts │ ├── roleMenu.ts │ ├── sms.ts │ └── user.ts ├── public │ ├── api_data.json │ ├── api_project.json │ ├── css │ │ └── style.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ └── favicon.ico │ ├── index.html │ └── vendor │ │ ├── bootstrap.min.css │ │ ├── path-to-regexp │ │ └── LICENSE │ │ ├── prettify.css │ │ └── prettify │ │ └── prettify.css ├── response │ ├── responseCode.ts │ ├── responseMsg.ts │ └── rest.ts ├── router.ts ├── router │ ├── index.ts │ ├── menu.ts │ ├── role.ts │ ├── sms.ts │ ├── upload.ts │ └── user.ts ├── service │ ├── aliyun.ts │ ├── menu.ts │ ├── product.ts │ ├── qiniu.ts │ ├── redis.ts │ ├── request.ts │ ├── role.ts │ ├── sms.ts │ ├── task.ts │ ├── upload.ts │ └── user.ts └── utils │ ├── date.ts │ ├── image.ts │ ├── index.ts │ ├── page.ts │ ├── random.ts │ ├── sql.ts │ ├── user.ts │ └── verify.ts ├── appveyor.yml ├── config ├── config.default.ts ├── config.local.ts ├── config.prod.ts └── plugin.ts ├── package-lock.json ├── package.json ├── react-ant-admin.sql ├── test └── utils.ts ├── tsconfig.json ├── tslint.json └── typings ├── app ├── controller │ └── index.d.ts ├── extend │ └── context.d.ts ├── index.d.ts ├── middleware │ └── index.d.ts ├── model │ └── index.d.ts └── service │ └── index.d.ts ├── config ├── index.d.ts └── plugin.d.ts ├── index.d.ts └── iny └── index.ts /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | plugin: 'autod-egg', 6 | prefix: '^', 7 | devprefix: '^', 8 | exclude: [ 9 | 'test/fixtures', 10 | 'coverage', 11 | ], 12 | dep: [ 13 | 'egg', 14 | 'egg-scripts', 15 | ], 16 | devdep: [ 17 | 'autod', 18 | 'autod-egg', 19 | 'egg-bin', 20 | 'tslib', 21 | 'typescript', 22 | ], 23 | keep: [ 24 | ], 25 | semver: [ 26 | ], 27 | test: 'scripts', 28 | }; 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | node_modules/ 4 | coverage/ 5 | .idea/ 6 | run/ 7 | logs/ 8 | .DS_Store 9 | *.swp 10 | *.lock 11 | *.js 12 | !.autod.conf.js 13 | 14 | app/**/*.js 15 | test/**/*.js 16 | config/**/*.js 17 | app/**/*.map 18 | test/**/*.map 19 | config/**/*.map 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | before_install: 6 | - npm i npminstall -g 7 | install: 8 | - npminstall 9 | script: 10 | - npm run ci 11 | after_script: 12 | - npminstall codecov && codecov 13 | -------------------------------------------------------------------------------- /.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": "node", 9 | "request": "launch", 10 | "name": "Egg Debug", 11 | "runtimeExecutable": "npm", 12 | "runtimeArgs": [ 13 | "run", 14 | "debug", 15 | "--", 16 | "--inspect-brk" 17 | ], 18 | "console": "integratedTerminal", 19 | "restart": true, 20 | "protocol": "auto", 21 | "port": 9229, 22 | "autoAttachChildProcesses": true 23 | }, 24 | { 25 | "type": "node", 26 | "request": "launch", 27 | "name": "Egg Test", 28 | "runtimeExecutable": "npm", 29 | "runtimeArgs": [ 30 | "run", 31 | "test-local", 32 | "--", 33 | "--inspect-brk" 34 | ], 35 | "protocol": "auto", 36 | "port": 9229, 37 | "autoAttachChildProcesses": true 38 | }, 39 | { 40 | "type": "node", 41 | "request": "attach", 42 | "name": "Egg Attach to remote", 43 | "localRoot": "${workspaceRoot}", 44 | "remoteRoot": "/usr/src/app", 45 | "address": "localhost", 46 | "protocol": "auto", 47 | "port": 9999 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Ant Admin Api 2 | 3 | `React Ant Admin Api` 是使用 `node`、`eggjs`、`redis`、mysql 为 React-Ant-Admin 开发的配套API 项目,具有完善的框架基础、的错误处理、以及对 mysql 的使用和事物处理 4 | 5 | #### 使用技术 6 | 7 | - **Node框架**: `eggjs`、`typescript` 8 | - **缓存处理**: `redis`、`egg-redis`、`ioredis`、`redis` 9 | - **数据处理**:`mysql`、`sequelize`、`egg-sequelize`、 10 | - **类型检查**:`typescript` 11 | - **跨域处理**:`egg-cros` 12 | - **文档管理**:`api-docs` 13 | - **图片上传**:`七牛云`、`本地服务器存储` 14 | 15 | #### 接口实现 16 | 17 | 接口具体实现请访问线上地址[地址](https://www.landluck.cn/react-ant-admin-api/public/index.html) 18 | 19 | #### 使用 20 | 21 | ```bash 22 | $ git clone https://github.com/landluck/react-ant-admin-api.git 23 | $ cd react-ant-admin-api 24 | $ npm install 25 | $ npm run dev 26 | ``` 27 | 28 | #### 部署 29 | 30 | ```bash 31 | $ npm start 32 | ``` 33 | 34 | #### 删除 35 | 36 | ```bash 37 | $ npm run stop 38 | ``` 39 | 40 | #### 环境 41 | node 8+ 42 | 43 | -------------------------------------------------------------------------------- /apiDoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ant-admin-api api docs", 3 | "version": "0.0.1", 4 | "description": "react-ant-admin-api 接口文档", 5 | "url" : "http://127.0.0.1", 6 | "sampleUrl": "http://127.0.0.1", 7 | "template": {} 8 | } -------------------------------------------------------------------------------- /app.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | // app.js 4 | export default class AppBootHook { 5 | protected app: Application; 6 | constructor(app: Application) { 7 | this.app = app; 8 | } 9 | 10 | configWillLoad() { 11 | // 此时 config 文件已经被读取并合并,但是还并未生效 12 | // 这是应用层修改配置的最后时机 13 | // 注意:此函数只支持同步调用 14 | 15 | // 例如:参数中的密码是加密的,在此处进行解密 16 | } 17 | 18 | async didLoad() { 19 | // 所有的配置已经加载完毕 20 | // 可以用来加载应用自定义的文件,启动自定义的服务 21 | 22 | // 例如:创建自定义应用的示例 23 | } 24 | 25 | async willReady() { 26 | // 所有的插件都已启动完毕,但是应用整体还未 ready 27 | } 28 | 29 | async didReady() { 30 | // 应用已经启动完毕 31 | this.app.logger.info('-----启动成功-----'); 32 | } 33 | 34 | async serverDidReady() { 35 | // http / https server 已启动,开始接受外部请求 36 | // 此时可以从 app.server 拿到 server 的实例 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/constants/date.ts: -------------------------------------------------------------------------------- 1 | const ApiDate = { 2 | ONE_DAY_TIME: 24 * 60 * 60 * 1000, 3 | }; 4 | 5 | export default ApiDate; 6 | -------------------------------------------------------------------------------- /app/constants/user.ts: -------------------------------------------------------------------------------- 1 | const UserConstants = { 2 | USER_TOKEN_KEY: 'USER_TOKEN_KEY', 3 | USER_PWD_KEY: 'USER_PWD_KEY', 4 | UNAVAILABLE: 0, 5 | }; 6 | 7 | export default UserConstants; 8 | -------------------------------------------------------------------------------- /app/controller/index.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | import { ApiResponseCode } from '../response/responseCode'; 3 | import { ApiResponseMsg } from '../response/responseMsg'; 4 | import ApiException from '../exception/ApiException'; 5 | 6 | export default class HomeController extends Controller { 7 | public async queryList() { 8 | // const { ctx } = this; 9 | 10 | // ctx.body = await ctx.service.task.queryTask(); 11 | console.log('ff'); 12 | // ctx.success({ a: 1, b: 2 }); 13 | throw new ApiException(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 14 | // ctx.fail(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/controller/menu.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | 3 | export interface MenuSearchQuery { 4 | id?: number; 5 | url?: string; 6 | name?: string; 7 | level?: number; 8 | parentId?: number; 9 | page?: number; 10 | size?: number; 11 | } 12 | 13 | class MenuController extends Controller { 14 | 15 | public async queryList() { 16 | const query: MenuSearchQuery = this.ctx.query; 17 | 18 | const data = await this.service.menu.findList(query); 19 | 20 | this.ctx.success(data); 21 | } 22 | 23 | public async queryCascader () { 24 | 25 | const data = await this.service.menu.findCascader(); 26 | 27 | this.ctx.success(data); 28 | } 29 | 30 | public async createOne() { 31 | const { ctx } = this; 32 | 33 | ctx.validate({ 34 | name: 'string', 35 | icon: 'string', 36 | url: 'string', 37 | sort: 'number', 38 | level: 'number', 39 | parentId: 'number', 40 | }); 41 | 42 | const data = await this.service.menu.createInstance(ctx.request.body); 43 | 44 | if (data) { 45 | return ctx.success(); 46 | } 47 | 48 | return ctx.fail(); 49 | } 50 | 51 | public async updateOne() { 52 | const { ctx } = this; 53 | 54 | ctx.validate({ 55 | id: 'number', 56 | name: 'string', 57 | icon: 'string', 58 | url: 'string', 59 | sort: 'number', 60 | parentId: 'number', 61 | level: 'number', 62 | }); 63 | 64 | const docs = await this.service.menu.updateById(ctx.request.body, ctx.request.body.id); 65 | 66 | if (docs) { 67 | return ctx.success(); 68 | } 69 | 70 | return ctx.fail(); 71 | } 72 | 73 | public async removeOne() { 74 | const { ctx } = this; 75 | 76 | ctx.validate( 77 | { 78 | id: /\d+/, 79 | }, 80 | ctx.params, 81 | ); 82 | 83 | const docs = await this.service.menu.removeById(ctx.params.id); 84 | 85 | if (docs) { 86 | return ctx.success(); 87 | } 88 | 89 | return ctx.fail(); 90 | } 91 | } 92 | 93 | export default MenuController; 94 | -------------------------------------------------------------------------------- /app/controller/role.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | 3 | export interface RoleSearchParams { 4 | id?: number; 5 | name?: any; 6 | page?: number; 7 | size?: number; 8 | } 9 | 10 | export interface RoleMenuParams { 11 | id: number; 12 | } 13 | 14 | class RoleController extends Controller { 15 | 16 | public async queryRoleMenu () { 17 | const { ctx } = this; 18 | 19 | ctx.validate({ 20 | id: /\d+/, 21 | }, ctx.params); 22 | 23 | const { id }: RoleMenuParams = ctx.params; 24 | 25 | const data = await ctx.service.role.findRoleMenuByRoleId(id); 26 | 27 | ctx.success(data); 28 | } 29 | 30 | public async updateRoleMenu () { 31 | const { ctx } = this; 32 | 33 | ctx.validate({ 34 | menuIds: 'array', 35 | }); 36 | 37 | ctx.validate({ 38 | id: /\d+/, 39 | }, ctx.params); 40 | 41 | const { menuIds }: { menuIds: number[]} = ctx.request.body; 42 | const { id }: { id: string} = ctx.params; 43 | 44 | const docs = await ctx.service.role.updateRoleMenuByRoleId(Number(id), menuIds); 45 | 46 | if (docs) { 47 | return ctx.success(); 48 | } 49 | 50 | ctx.fail(); 51 | } 52 | 53 | public async queryList () { 54 | const { ctx } = this; 55 | 56 | const query: RoleSearchParams = ctx.query; 57 | 58 | const data = await ctx.service.role.findList(query); 59 | 60 | ctx.success(data); 61 | } 62 | 63 | public async createOne() { 64 | const { ctx } = this; 65 | 66 | ctx.validate({ 67 | name: 'string', 68 | }); 69 | 70 | const data = await this.service.role.createInstance(ctx.request.body); 71 | 72 | if (data) { 73 | return ctx.success(); 74 | } 75 | 76 | return ctx.fail(); 77 | } 78 | 79 | public async updateOne() { 80 | const { ctx } = this; 81 | 82 | ctx.validate({ 83 | id: 'number', 84 | name: 'string', 85 | }); 86 | 87 | const docs = await this.service.role.updateById(ctx.request.body, ctx.request.body.id); 88 | 89 | if (docs) { 90 | return ctx.success(); 91 | } 92 | 93 | return ctx.fail(); 94 | } 95 | 96 | public async removeOne() { 97 | const { ctx } = this; 98 | 99 | ctx.validate( 100 | { 101 | id: /\d+/, 102 | }, 103 | ctx.params, 104 | ); 105 | 106 | const docs = await this.service.role.removeById(ctx.params.id); 107 | 108 | if (docs) { 109 | return ctx.success(); 110 | } 111 | 112 | return ctx.fail(); 113 | } 114 | } 115 | 116 | export default RoleController; 117 | -------------------------------------------------------------------------------- /app/controller/sms.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | import { ApiResponseCode } from '../response/responseCode'; 3 | import { ApiResponseMsg } from '../response/responseMsg'; 4 | import { verifyMobile } from '../utils/verify'; 5 | import { createRandomNum } from '../utils/random'; 6 | 7 | export default class SmsController extends Controller { 8 | public async sendMessage() { 9 | const { ctx } = this; 10 | 11 | const body: { 12 | mobile: string; 13 | } = ctx.request.body; 14 | 15 | if (!body.mobile || !verifyMobile(body.mobile)) { 16 | return ctx.fail( 17 | ApiResponseCode.PARAMS_ERROR, 18 | ApiResponseMsg.MOBILE_ERROR, 19 | ); 20 | } 21 | 22 | const count = await ctx.service.sms.countByMobile(body.mobile); 23 | 24 | if (count > this.config.sms.countByMobile) { 25 | return ctx.fail( 26 | ApiResponseCode.RESOURCE_LIMTI, 27 | ApiResponseMsg.SMS_OVER.replace( 28 | '&', 29 | this.config.sms.countByMobile.toString(), 30 | ) as ApiResponseMsg, 31 | ); 32 | } 33 | 34 | if (ctx.ip) { 35 | const ipCount = await ctx.service.sms.countByIp(ctx.ip); 36 | 37 | if (ipCount > this.config.sms.countByIp) { 38 | return ctx.fail( 39 | ApiResponseCode.RESOURCE_LIMTI, 40 | ApiResponseMsg.SMS_OVER.replace( 41 | '&', 42 | this.config.sms.countByMobile.toString(), 43 | ) as ApiResponseMsg, 44 | ); 45 | } 46 | } 47 | 48 | // 生成随机验证码 49 | const code = createRandomNum(999999, 100000); 50 | 51 | const doc = await ctx.service.sms.sendVerifyCode(body.mobile, code, ctx.ip); 52 | 53 | if (!doc) { 54 | return ctx.fail( 55 | ApiResponseCode.SERVER_ERROR, 56 | ApiResponseMsg.SMS_SND_ERROR, 57 | ); 58 | } 59 | 60 | return ctx.fail(ApiResponseCode.SERVER_ERROR, ('验证码为:' + code) as ApiResponseMsg); 61 | 62 | // return ctx.success(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/controller/upload.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | 3 | export default class HomeController extends Controller { 4 | public async uploadImage() { 5 | const { ctx } = this; 6 | 7 | const data = await ctx.service.upload.uploadFilesToQiniu(ctx.request.files); 8 | 9 | if (data) { 10 | return ctx.success(data); 11 | } 12 | 13 | ctx.fail(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/controller/user.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | import { ApiResponseCode } from '../response/responseCode'; 3 | import { ApiResponseMsg } from '../response/responseMsg'; 4 | import { encodeUserPwd, createToken } from '../utils/user'; 5 | import { User } from '../model/user'; 6 | import ApiDate from '../constants/date'; 7 | import UserConstants from '../constants/user'; 8 | import { verifyMobile, verifyCode } from '../utils/verify'; 9 | import { Sms } from '../model/sms'; 10 | 11 | interface UserLoginBody { 12 | account: string; 13 | password: string; 14 | } 15 | 16 | interface UserLoginMobileBody { 17 | mobile: string; 18 | code: string; 19 | } 20 | 21 | interface UpdateUserPwdBody { 22 | mobile: string; 23 | code: number; 24 | password: string; 25 | } 26 | 27 | export interface UserSearchParams { 28 | id?: number; 29 | name?: any; 30 | account?: any; 31 | mobile?: any; 32 | page?: number; 33 | size?: number; 34 | } 35 | 36 | class UserController extends Controller { 37 | public async queryList() { 38 | const { ctx } = this; 39 | 40 | const query: UserSearchParams = ctx.query; 41 | 42 | const data = await ctx.service.user.findList(query); 43 | 44 | ctx.success(data); 45 | } 46 | 47 | public async login() { 48 | const { ctx } = this; 49 | 50 | ctx.validate({ 51 | account: 'string', 52 | password: 'string', 53 | }); 54 | 55 | const body: UserLoginBody = ctx.request.body; 56 | 57 | const user = await ctx.service.user.findUserByAccount(body.account); 58 | 59 | if (!user) { 60 | return ctx.fail( 61 | ApiResponseCode.USER_NOT_FOUND, 62 | ApiResponseMsg.USER_NOT_FOUND, 63 | ); 64 | } 65 | 66 | if (user.status === UserConstants.UNAVAILABLE) { 67 | return ctx.fail( 68 | ApiResponseCode.USER_UNAVAILABLE, 69 | ApiResponseMsg.USER_UNAVAILABLE, 70 | ); 71 | } 72 | 73 | const pwd = encodeUserPwd(body.password); 74 | 75 | ctx.logger.info(pwd); 76 | 77 | if (pwd !== user.password) { 78 | return ctx.fail( 79 | ApiResponseCode.USER_ERROR, 80 | ApiResponseMsg.USER_PWD_ERROR, 81 | ); 82 | } 83 | 84 | const token = createToken(user.id); 85 | 86 | const docs = await ctx.service.redis.setex( 87 | token, 88 | user.id.toString(), 89 | ApiDate.ONE_DAY_TIME, 90 | ); 91 | const docsOther = await ctx.service.redis.setMap(user.id.toString(), user); 92 | 93 | delete user.password; 94 | 95 | if (docs && docsOther) { 96 | return ctx.success({ 97 | token, 98 | ...user, 99 | }); 100 | } 101 | 102 | ctx.fail(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 103 | } 104 | 105 | public async loginByMobile() { 106 | const { ctx } = this; 107 | 108 | const body: UserLoginMobileBody = ctx.request.body; 109 | 110 | if (!verifyMobile(body.mobile)) { 111 | return ctx.fail(ApiResponseCode.USER_ERROR, ApiResponseMsg.MOBILE_ERROR); 112 | } 113 | 114 | if (!verifyCode(body.code)) { 115 | return ctx.fail(ApiResponseCode.USER_ERROR, ApiResponseMsg.CODE_ERROR); 116 | } 117 | 118 | const code = await ctx.service.sms.findCodeByMobileAndCode( 119 | body.mobile, 120 | Number(body.code), 121 | ); 122 | 123 | if (!code) { 124 | return ctx.fail(ApiResponseCode.USER_ERROR, ApiResponseMsg.CODE_ERROR); 125 | } 126 | 127 | const user = await ctx.service.user.findUserByMobile(body.mobile); 128 | 129 | if (!user) { 130 | return ctx.fail( 131 | ApiResponseCode.USER_NOT_FOUND, 132 | ApiResponseMsg.USER_NOT_FOUND, 133 | ); 134 | } 135 | 136 | if (user.status === UserConstants.UNAVAILABLE) { 137 | return ctx.fail( 138 | ApiResponseCode.USER_UNAVAILABLE, 139 | ApiResponseMsg.USER_UNAVAILABLE, 140 | ); 141 | } 142 | 143 | const opt = await ctx.service.sms.removeById(code.id!); 144 | 145 | if (!opt) { 146 | return ctx.fail(); 147 | } 148 | 149 | const token = createToken(user.id); 150 | 151 | const docs = await ctx.service.redis.setex( 152 | token, 153 | user.id.toString(), 154 | ApiDate.ONE_DAY_TIME, 155 | ); 156 | const docsOther = await ctx.service.redis.setMap(user.id.toString(), user); 157 | 158 | delete user.password; 159 | 160 | if (docs && docsOther) { 161 | return ctx.success({ 162 | token, 163 | ...user, 164 | }); 165 | } 166 | 167 | ctx.fail(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 168 | } 169 | 170 | public async registerUser() { 171 | const { ctx } = this; 172 | 173 | ctx.validate({ 174 | name: 'string', 175 | account: 'string', 176 | password: 'string', 177 | mobile: 'string', 178 | code: /\d{6}/, 179 | }); 180 | 181 | const user = ctx.request.body as User & { code: number }; 182 | 183 | const code: Sms | null = await ctx.service.sms.findCodeByMobileAndCode( 184 | user.mobile, 185 | user.code, 186 | ); 187 | 188 | if (!code) { 189 | return ctx.fail(ApiResponseCode.PARAMS_ERROR, ApiResponseMsg.CODE_ERROR); 190 | } 191 | 192 | const hasUser: User | null = await ctx.service.user.findUserByAccount( 193 | user.account, 194 | ); 195 | 196 | if (hasUser) { 197 | return ctx.fail( 198 | ApiResponseCode.RESOURCE_EXISTED, 199 | ApiResponseMsg.USER_EXISTED, 200 | ); 201 | } 202 | 203 | user.password = encodeUserPwd(user.password); 204 | 205 | const opt = await ctx.service.sms.removeById(code.id!); 206 | 207 | if (!opt) { 208 | return ctx.fail(); 209 | } 210 | 211 | const docs = await ctx.service.user.createInstance(user); 212 | 213 | if (docs) { 214 | return ctx.success({ id: docs.id }); 215 | } 216 | 217 | ctx.fail(); 218 | } 219 | 220 | public async createUser() { 221 | const { ctx } = this; 222 | 223 | ctx.validate({ 224 | name: 'string', 225 | account: 'string', 226 | mobile: 'string', 227 | roleId: 'number', 228 | status: 'number', 229 | }); 230 | 231 | const user = ctx.request.body as User; 232 | 233 | const hasUser: User | null = await ctx.service.user.findUserByAccount( 234 | user.account, 235 | ); 236 | 237 | if (hasUser) { 238 | return ctx.fail( 239 | ApiResponseCode.RESOURCE_EXISTED, 240 | ApiResponseMsg.USER_EXISTED, 241 | ); 242 | } 243 | 244 | if (user.password) { 245 | user.password = encodeUserPwd(user.password); 246 | } 247 | 248 | const docs = await ctx.service.user.createInstance(user); 249 | 250 | if (docs) { 251 | return ctx.success(); 252 | } 253 | 254 | ctx.fail(); 255 | } 256 | 257 | public async updateUser() { 258 | const { ctx } = this; 259 | 260 | ctx.validate({ 261 | id: 'number', 262 | name: 'string', 263 | account: 'string', 264 | mobile: 'string', 265 | roleId: 'number', 266 | status: 'number', 267 | }); 268 | 269 | const user = ctx.request.body as User; 270 | 271 | const userData: User | null = await ctx.service.user.findById(user.id); 272 | 273 | if (!userData) { 274 | return ctx.fail( 275 | ApiResponseCode.USER_NOT_FOUND, 276 | ApiResponseMsg.USER_NOT_FOUND, 277 | ); 278 | } 279 | 280 | user.password = encodeUserPwd(user.password); 281 | 282 | const docs = await ctx.service.user.updateById(user, user.id); 283 | 284 | if (docs) { 285 | return ctx.success(); 286 | } 287 | 288 | ctx.fail(); 289 | } 290 | 291 | public async removeUser() { 292 | const { ctx } = this; 293 | 294 | const params: { 295 | id: number; 296 | } = ctx.params; 297 | 298 | ctx.validate( 299 | { 300 | id: /\d+/, 301 | }, 302 | params, 303 | ); 304 | 305 | const user: User | null = await ctx.service.user.findById(params.id); 306 | 307 | if (!user) { 308 | return ctx.fail( 309 | ApiResponseCode.USER_NOT_FOUND, 310 | ApiResponseMsg.USER_NOT_FOUND, 311 | ); 312 | } 313 | 314 | const docs = await ctx.service.user.removeById(params.id); 315 | 316 | if (docs) { 317 | return ctx.success(); 318 | } 319 | 320 | ctx.fail(); 321 | } 322 | 323 | public async updateUserPwd() { 324 | const { ctx } = this; 325 | 326 | ctx.validate({ 327 | mobile: /\d{11}/, 328 | code: /\d{6}/, 329 | password: 'string', 330 | }); 331 | 332 | const data: UpdateUserPwdBody = ctx.request.body; 333 | 334 | const code = await ctx.service.sms.findCodeByMobileAndCode( 335 | data.mobile, 336 | data.code, 337 | ); 338 | 339 | if (!code) { 340 | return ctx.fail(ApiResponseCode.PARAMS_ERROR, ApiResponseMsg.CODE_ERROR); 341 | } 342 | 343 | const user = await ctx.service.user.findUserByMobile(data.mobile); 344 | 345 | if (!user) { 346 | return ctx.fail( 347 | ApiResponseCode.USER_NOT_FOUND, 348 | ApiResponseMsg.USER_NOT_FOUND, 349 | ); 350 | } 351 | 352 | const pwd = encodeUserPwd(data.password); 353 | 354 | const docs = await ctx.service.user.updateById({ password: pwd }, user.id); 355 | 356 | if (docs) { 357 | return ctx.success(); 358 | } 359 | 360 | ctx.fail(); 361 | } 362 | 363 | public async queryUserMenu () { 364 | const { ctx } = this; 365 | 366 | const id = ctx.user.roleId; 367 | 368 | const data = await ctx.service.role.findRoleMenuByRoleId(id); 369 | 370 | ctx.success(data); 371 | } 372 | } 373 | 374 | export default UserController; 375 | -------------------------------------------------------------------------------- /app/core/model.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, Model, BuildOptions } from 'sequelize'; 2 | 3 | export interface BaseModel { 4 | 5 | // 创建者 6 | creator: string; 7 | 8 | // 修改者 9 | modifier: string; 10 | 11 | // 创建时间 12 | readonly createdAt: Date; 13 | // 修改时间 14 | readonly updatedAt: Date; 15 | } 16 | 17 | export type BaseModelStatic = typeof Model & (new (values?: object, options?: BuildOptions) => T); 18 | 19 | export const BaseModelProps = { 20 | 21 | // 创建者 22 | creator: { 23 | type: DataTypes.STRING(16), 24 | }, 25 | 26 | // 修改者 27 | modifier: { 28 | type: DataTypes.STRING(16), 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /app/core/service.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | import { BaseModelStatic } from './model'; 3 | import SqlUtils from '../utils/sql'; 4 | import { Model, FindAndCountOptions, WhereAttributeHash } from 'sequelize'; 5 | import { PageParams, PageInfo } from '../../typings'; 6 | import Page from '../utils/page'; 7 | 8 | /** 9 | * BaseService service 基础类, 提供四个基础方法 10 | * 提供基础方法 11 | * 12 | * 13 | * 需要在每个继承该基础 service 的类构造函数中,指定 model 为当前 service 的 model 14 | */ 15 | 16 | export default class BaseService extends Service { 17 | model: BaseModelStatic; 18 | 19 | public async findById(id: number): Promise { 20 | 21 | const model = await this.model.findOne({ 22 | ...SqlUtils.queryOptions(), 23 | where: { 24 | id, 25 | }, 26 | }); 27 | 28 | if (model) { 29 | return model.get({ plain: true }) as T; 30 | } 31 | 32 | return null; 33 | } 34 | 35 | public async findListByKey( 36 | where: { [key in keyof T]?: T[key] }, 37 | pageParams: PageParams, 38 | options?: FindAndCountOptions, 39 | ): Promise<{ list: T[]; page: PageInfo }> { 40 | const page = new Page(pageParams); 41 | 42 | const { rows, count } = await this.model.findAndCountAll({ 43 | where: where as WhereAttributeHash, 44 | ...SqlUtils.queryOptions(), 45 | ...page.buildOptions(), 46 | ...options, 47 | }); 48 | 49 | page.setTotal(count); 50 | 51 | return { 52 | list: rows, 53 | page: page.getData(), 54 | }; 55 | } 56 | 57 | public async removeById(id: number): Promise { 58 | const result = await this.model.destroy({ 59 | where: { 60 | id, 61 | }, 62 | }); 63 | console.log(result); 64 | 65 | if (result === 1) { 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | public async updateById( 73 | instance: { [key in keyof T]?: T[key] } & { id?: number }, 74 | id: number, 75 | ): Promise { 76 | // 删除数据中原有的id 77 | if (instance.id) { 78 | delete instance.id; 79 | } 80 | 81 | // 这里的 ctx.user 是全局挂载到 ctx 的用户信息 82 | 83 | const [ result ] = await this.model.update( 84 | SqlUtils.updateOptions(instance as T, this.ctx.user), 85 | { 86 | where: { 87 | id, 88 | }, 89 | }, 90 | ); 91 | 92 | if (result === 1) { 93 | return true; 94 | } 95 | 96 | return false; 97 | } 98 | 99 | public async createInstance( 100 | instance: { [key in keyof T]?: T[key] }, 101 | ): Promise { 102 | const model = await this.model.create( 103 | SqlUtils.createOptions(instance as T, this.ctx.user), 104 | ); 105 | 106 | return model.get({ plain: true }) as T; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/exception/ApiException.ts: -------------------------------------------------------------------------------- 1 | import { ApiResponseCode } from '../response/responseCode'; 2 | import { ApiResponseMsg } from '../response/responseMsg'; 3 | 4 | export default class ApiException extends Error { 5 | code: ApiResponseCode; 6 | message: ApiResponseMsg; 7 | 8 | constructor(code: ApiResponseCode, message: ApiResponseMsg) { 9 | super(message); 10 | this.code = code; 11 | this.message = message; 12 | Object.setPrototypeOf(this, ApiException.prototype); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/extend/context.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import { buildSuccess, buildFail } from '../response/rest'; 3 | import { ApiResponseCode } from '../response/responseCode'; 4 | import { ApiResponseMsg } from '../response/responseMsg'; 5 | import { User } from '../model/user'; 6 | 7 | const USER = Symbol('Context#user'); 8 | 9 | export default { 10 | success (this: Context, data?: any) { 11 | this.body = buildSuccess(data); 12 | }, 13 | 14 | fail (this: Context, code: ApiResponseCode = ApiResponseCode.SERVER_ERROR, msg: ApiResponseMsg = ApiResponseMsg.SERVER_ERROR) { 15 | this.body = buildFail(code, msg); 16 | }, 17 | 18 | get user (): User { 19 | return this[USER]; 20 | }, 21 | 22 | set user (user: User) { 23 | this[USER] = user; 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /app/middleware/auth.ts: -------------------------------------------------------------------------------- 1 | import { Context, EggAppConfig } from 'egg'; 2 | import { ApiResponseCode } from '../response/responseCode'; 3 | import { ApiResponseMsg } from '../response/responseMsg'; 4 | import { User } from '../model/user'; 5 | 6 | export default function auth(options: EggAppConfig): any { 7 | return async (ctx: Context, next: () => Promise) => { 8 | 9 | if ((options.url as Set).has(ctx.url)) { 10 | await next(); 11 | } else { 12 | const token = ctx.header.token; 13 | 14 | if (!token) { 15 | return ctx.fail(ApiResponseCode.NO_TOKEN, ApiResponseMsg.NO_TOKEN); 16 | } 17 | 18 | const userId = await ctx.service.redis.get(token); 19 | 20 | if (!userId) { 21 | return ctx.fail(ApiResponseCode.TOKEN_ERROR, ApiResponseMsg.TOKEN_ERROR); 22 | } 23 | 24 | const user = await ctx.service.redis.getMap(userId); 25 | 26 | if (!user) { 27 | return ctx.fail(ApiResponseCode.TOKEN_ERROR, ApiResponseMsg.TOKEN_ERROR); 28 | } 29 | 30 | ctx.user = user; 31 | 32 | await next(); 33 | } 34 | 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /app/middleware/errHandle.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import ApiException from '../exception/ApiException'; 3 | import { ApiResponseCode } from '../response/responseCode'; 4 | import { ApiResponseMsg } from '../response/responseMsg'; 5 | 6 | export default function errHandle(): any { 7 | return async (ctx: Context, next: () => Promise) => { 8 | 9 | try { 10 | await next(); 11 | } catch (error) { 12 | 13 | if (error instanceof ApiException) { 14 | ctx.logger.error(error); 15 | 16 | ctx.fail(error.code, error.message); 17 | return; 18 | } 19 | 20 | // 参数校验错误 21 | if ((error as Error).message === 'Validation Failed') { 22 | const validation = error.errors[0]; 23 | 24 | if (validation) { 25 | 26 | ctx.logger.error(validation); 27 | 28 | return ctx.fail(ApiResponseCode.PARAMS_ERROR, (validation.field + ' ' + validation.message as ApiResponseMsg.SERVER_ERROR)); 29 | } 30 | } 31 | 32 | ctx.logger.error(error); 33 | 34 | const status = error.status || 500; 35 | 36 | const errMsg = status === 500 ? 'Internal Server Error' : error.message; 37 | 38 | ctx.fail(status, errMsg); 39 | 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /app/model/menu.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import { Model, DataTypes } from 'sequelize'; 3 | import { BaseModel, BaseModelProps, BaseModelStatic } from '../core/model'; 4 | 5 | export interface Menu extends BaseModel, Model { 6 | id?: number; 7 | 8 | name: string; 9 | 10 | url: string; 11 | 12 | icon: string; 13 | 14 | desc: string; 15 | 16 | sort: number; 17 | 18 | parentId: number; 19 | 20 | level: number; 21 | 22 | children?: Menu[]; 23 | } 24 | 25 | export default (app: Context) => { 26 | const sequelize = app.model; 27 | 28 | const MenuModel = sequelize.define('uvs_sys_menu', { 29 | id: { 30 | primaryKey: true, 31 | autoIncrement: true, 32 | type: DataTypes.INTEGER.UNSIGNED, 33 | comment: '菜单id', 34 | }, 35 | name: { 36 | type: DataTypes.STRING(5), 37 | comment: '菜单名称', 38 | }, 39 | url: { 40 | type: DataTypes.STRING(32), 41 | comment: '菜单地址', 42 | }, 43 | icon: { 44 | type: DataTypes.STRING(16), 45 | comment: '菜单icon', 46 | }, 47 | desc: { 48 | type: DataTypes.STRING(16), 49 | comment: '菜单描述', 50 | }, 51 | sort: { 52 | type: DataTypes.INTEGER.UNSIGNED, 53 | defaultValue: 0, 54 | comment: '菜单排序', 55 | }, 56 | parentId: { 57 | type: DataTypes.INTEGER.UNSIGNED, 58 | defaultValue: 0, 59 | comment: '父级菜单id', 60 | }, 61 | level: { 62 | type: DataTypes.INTEGER.UNSIGNED, 63 | defaultValue: 0, 64 | comment: '菜单等级', 65 | }, 66 | 67 | // 注入基本model的配置 68 | ...BaseModelProps, 69 | }, { 70 | indexes: [{ fields: [ 'parent_id' ] }], 71 | }) as BaseModelStatic; 72 | 73 | MenuModel.belongsTo(MenuModel, { 74 | // 不创建外健 75 | constraints: false, 76 | // 指定关联id 77 | targetKey: 'id', 78 | as: 'parent', 79 | }); 80 | 81 | // MenuModel.sync({force: true}).then(res => { 82 | // console.log(res) 83 | // }).catch(err => { 84 | // console.log(err) 85 | // }) 86 | 87 | return MenuModel; 88 | }; 89 | -------------------------------------------------------------------------------- /app/model/role.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import { Model, DataTypes } from 'sequelize'; 3 | import { BaseModel, BaseModelProps, BaseModelStatic } from '../core/model'; 4 | 5 | export interface Role extends BaseModel, Model { 6 | id: number; 7 | 8 | name: string; 9 | } 10 | 11 | export default (app: Context) => { 12 | const sequelize = app.model; 13 | 14 | const RoleModel = sequelize.define('uvs_sys_role', 15 | { 16 | id: { 17 | primaryKey: true, 18 | autoIncrement: true, 19 | type: DataTypes.INTEGER.UNSIGNED, 20 | comment: '角色id', 21 | }, 22 | name: { 23 | type: DataTypes.STRING(12), 24 | comment: '角色名称', 25 | }, 26 | // 注入基本model的配置 27 | ...BaseModelProps, 28 | }, 29 | ) as BaseModelStatic; 30 | 31 | // RoleModel.sync({force: true}).then(res => { 32 | // console.log(res) 33 | // }).catch(err => { 34 | // console.log(err) 35 | // }) 36 | 37 | return RoleModel; 38 | }; 39 | -------------------------------------------------------------------------------- /app/model/roleMenu.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import { Model, DataTypes } from 'sequelize'; 3 | import { BaseModel, BaseModelProps, BaseModelStatic } from '../core/model'; 4 | 5 | export interface RoleMenu extends BaseModel, Model { 6 | id: number; 7 | 8 | roleId: number; 9 | 10 | menuId: number; 11 | } 12 | 13 | export default (app: Context) => { 14 | const sequelize = app.model; 15 | 16 | const RoleMenuModel = sequelize.define('uvs_sys_role_menu', 17 | { 18 | id: { 19 | primaryKey: true, 20 | autoIncrement: true, 21 | type: DataTypes.INTEGER.UNSIGNED, 22 | comment: '记录id', 23 | }, 24 | roleId: { 25 | type: DataTypes.INTEGER.UNSIGNED, 26 | comment: '角色id', 27 | }, 28 | menuId: { 29 | type: DataTypes.INTEGER.UNSIGNED, 30 | comment: '菜单id', 31 | }, 32 | // 注入基本model的配置 33 | ...BaseModelProps, 34 | }, 35 | { 36 | indexes: [{ fields: [ 'role_id' ] }, { fields: [ 'menu_id' ] }], 37 | }, 38 | ) as BaseModelStatic; 39 | 40 | // RoleMenuModel.sync({force: true}).then(res => { 41 | // console.log(res) 42 | // }).catch(err => { 43 | // console.log(err) 44 | // }) 45 | 46 | return RoleMenuModel; 47 | }; 48 | -------------------------------------------------------------------------------- /app/model/sms.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import { Model, DataTypes } from 'sequelize'; 3 | import { BaseModel, BaseModelProps, BaseModelStatic } from '../core/model'; 4 | 5 | export interface Sms extends BaseModel, Model { 6 | id?: number; 7 | 8 | userId?: string; 9 | 10 | ip: string; 11 | 12 | bizId: string; 13 | 14 | content: string; 15 | 16 | mobile: string; 17 | 18 | code: number; 19 | 20 | tid: string; 21 | } 22 | 23 | export default (app: Context) => { 24 | const sequelize = app.model; 25 | 26 | const SmsModel = sequelize.define('uvs_sms_log', 27 | { 28 | id: { 29 | primaryKey: true, 30 | autoIncrement: true, 31 | type: DataTypes.INTEGER.UNSIGNED, 32 | comment: '记录id', 33 | }, 34 | userId: { 35 | type: DataTypes.INTEGER.UNSIGNED, 36 | defaultValue: 0, 37 | comment: '用户id', 38 | }, 39 | ip: { 40 | type: DataTypes.STRING(16), 41 | comment: '用户ip', 42 | }, 43 | bizId: { 44 | type: DataTypes.STRING(32), 45 | comment: '三方流水号', 46 | }, 47 | content: { 48 | type: DataTypes.STRING(70), 49 | comment: '短信发送内容', 50 | }, 51 | code: { 52 | type: DataTypes.INTEGER({ length: 6 }).UNSIGNED, 53 | comment: '短信验证码', 54 | }, 55 | tid: { 56 | type: DataTypes.STRING(16), 57 | comment: '模版id', 58 | }, 59 | mobile: { 60 | type: DataTypes.CHAR(11), 61 | defaultValue: '', 62 | comment: '用户手机号', 63 | }, 64 | 65 | // 注入基本model的配置 66 | ...BaseModelProps, 67 | }, 68 | { 69 | indexes: [{ fields: [ 'mobile' ] }], 70 | }, 71 | ) as BaseModelStatic; 72 | 73 | // SmsModel.sync({force: true}).then(res => { 74 | // console.log(res) 75 | // }).catch(err => { 76 | // console.log(err) 77 | // }) 78 | 79 | return SmsModel; 80 | }; 81 | -------------------------------------------------------------------------------- /app/model/user.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import { Model, DataTypes } from 'sequelize'; 3 | import { BaseModel, BaseModelProps, BaseModelStatic } from '../core/model'; 4 | import role from './role'; 5 | 6 | export interface User extends BaseModel, Model { 7 | id: number; 8 | 9 | name: string; 10 | 11 | account: string; 12 | 13 | password: string; 14 | 15 | avatar?: string | null; 16 | 17 | mobile: string; 18 | 19 | roleId: number; 20 | 21 | status: number; 22 | } 23 | 24 | export default (app: Context) => { 25 | const sequelize = app.model; 26 | 27 | const UserModel = sequelize.define('uvs_user', 28 | { 29 | id: { 30 | primaryKey: true, 31 | autoIncrement: true, 32 | type: DataTypes.INTEGER.UNSIGNED, 33 | comment: '用户id', 34 | }, 35 | name: { 36 | type: DataTypes.STRING(8), 37 | comment: '用户名称', 38 | }, 39 | account: { 40 | type: DataTypes.STRING(16), 41 | comment: '用户账号', 42 | }, 43 | avatar: { 44 | type: DataTypes.STRING(100), 45 | comment: '用户头像', 46 | }, 47 | password: { 48 | type: DataTypes.CHAR(32), 49 | comment: '用户密码', 50 | }, 51 | mobile: { 52 | type: DataTypes.CHAR(11), 53 | defaultValue: '', 54 | comment: '用户手机号', 55 | }, 56 | roleId: { 57 | type: DataTypes.INTEGER.UNSIGNED, 58 | defaultValue: 0, 59 | comment: '角色id', 60 | }, 61 | status: { 62 | type: DataTypes.TINYINT({ length : 1 }).UNSIGNED, 63 | defaultValue: 1, 64 | comment: '角色状态:1 正常 0 禁用', 65 | }, 66 | 67 | // 注入基本model的配置 68 | ...BaseModelProps, 69 | }, 70 | { 71 | indexes: [{ fields: [ 'mobile' ] }, { fields: [ 'account' ] }, { fields: [ 'role_id' ] }], 72 | }, 73 | ) as BaseModelStatic; 74 | 75 | UserModel.belongsTo(role(app), { 76 | // 不创建外健 77 | constraints: false, 78 | // 指定关联id 79 | targetKey: 'id', 80 | as: 'role', 81 | }); 82 | 83 | // UserModel.sync({force: true}).then(res => { 84 | // console.log(res) 85 | // }).catch(err => { 86 | // console.log(err) 87 | // }) 88 | 89 | return UserModel; 90 | }; 91 | -------------------------------------------------------------------------------- /app/public/api_data.json: -------------------------------------------------------------------------------- 1 | [ { "type": "post", "url": "/menu", "title": "创建菜单", "name": "CreateMenu", "group": "Menu", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

菜单名称

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "icon", "description": "

菜单icon

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "url", "description": "

菜单url

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "desc", "description": "

菜单描述

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "sort", "description": "

菜单排序

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "parentId", "description": "

菜单父级id

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "level", "description": "

菜单等级

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/menu.ts", "groupTitle": "Menu", "sampleRequest": [ { "url": "http://192.168.4.22:3300/menu" } ] }, { "type": "get", "url": "/menu/", "title": "查找菜单列表", "name": "GetMenu", "group": "Menu", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "Number", "optional": false, "field": "id", "description": "

菜单id

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

菜单名称

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "url", "description": "

菜单url

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "level", "description": "

菜单level

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "size", "description": "

分页大小

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "page", "description": "

页码

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list", "description": "

菜单列表

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list.id", "description": "

菜单列表

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.name", "description": "

菜单名称

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.icon", "description": "

菜单icon

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.url", "description": "

菜单url

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.desc", "description": "

菜单描述

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.sort", "description": "

菜单排序

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.parentId", "description": "

菜单父级id

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.level", "description": "

菜单等级

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page", "description": "

分页信息

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.dataTotal", "description": "

总条数

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.size", "description": "

分页大小

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.page", "description": "

当前是第几页

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.pageTotal", "description": "

总页码

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {\n list: [\n {\n id: 1,\n name: '首页',\n icon: 'icon',\n url: '/index',\n desc: '',\n ...\n }\n ],\n page: {\n dataTotal: 100,\n size: 5,\n page: 5,\n pageTotal: 20\n }\n }\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/menu.ts", "groupTitle": "Menu", "sampleRequest": [ { "url": "http://192.168.4.22:3300/menu/" } ] }, { "type": "get", "url": "/menu/cascader", "title": "查找菜单级联列表", "name": "GetMenuCascader", "group": "Menu", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Array", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.id", "description": "

菜单id

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.name", "description": "

菜单名称

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.icon", "description": "

菜单icon

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.url", "description": "

菜单url

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.desc", "description": "

菜单描述

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.sort", "description": "

菜单排序

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.parentId", "description": "

菜单父级id

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.level", "description": "

菜单等级

" }, { "group": "Success 200", "type": "Array", "optional": false, "field": "data.list.children", "description": "

子菜单列表

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: [\n {\n id: 1,\n name: '首页',\n icon: 'icon',\n url: '/index',\n desc: '',\n children: []\n }\n ]\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/menu.ts", "groupTitle": "Menu", "sampleRequest": [ { "url": "http://192.168.4.22:3300/menu/cascader" } ] }, { "type": "put", "url": "/menu", "title": "修改菜单", "name": "UpdateMenu", "group": "Menu", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "Number", "optional": false, "field": "id", "description": "

菜单id

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

菜单名称

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "icon", "description": "

菜单icon

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "url", "description": "

菜单url

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "desc", "description": "

菜单描述

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "sort", "description": "

菜单排序

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "parentId", "description": "

菜单父级id

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "level", "description": "

菜单等级

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/menu.ts", "groupTitle": "Menu", "sampleRequest": [ { "url": "http://192.168.4.22:3300/menu" } ] }, { "type": "delete", "url": "/menu/:id", "title": "删除菜单", "name": "removeMenu", "group": "Menu", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/menu.ts", "groupTitle": "Menu", "sampleRequest": [ { "url": "http://192.168.4.22:3300/menu/:id" } ] }, { "type": "post", "url": "/sms", "title": "发送验证码", "name": "SendSms", "group": "Sms", "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "String", "optional": false, "field": "mobile", "description": "

账号

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/sms.ts", "groupTitle": "Sms", "sampleRequest": [ { "url": "http://192.168.4.22:3300/sms" } ] }, { "type": "post", "url": "/user", "title": "创建用户", "name": "CreateUser", "group": "User", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

姓名

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "account", "description": "

账号/手机号

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "password", "description": "

密码

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "avatar", "description": "

用户头像

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "mobile", "description": "

用户手机号

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "user", "description": "

用户权限id

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "status", "description": "

用户状态

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.id", "description": "

用户id

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {\n id: 10\n }\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/user.ts", "groupTitle": "User", "sampleRequest": [ { "url": "http://192.168.4.22:3300/user" } ] }, { "type": "get", "url": "/user/", "title": "查找用户列表", "name": "GetUser", "group": "User", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "Number", "optional": false, "field": "id", "description": "

用户id

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

用户名称

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "account", "description": "

用户账号

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "mobile", "description": "

用户手机号

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "size", "description": "

分页大小

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "page", "description": "

页码

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list", "description": "

用户列表

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.id", "description": "

用户列表

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.roleId", "description": "

用户角色id

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.name", "description": "

用户名称

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.account", "description": "

用户账号

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.mobile", "description": "

用户手机号

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.avatar", "description": "

用户头像

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list.role", "description": "

用户角色

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list.role.id", "description": "

用户角色id

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list.role.name", "description": "

用户角色名称

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page", "description": "

分页信息

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.dataTotal", "description": "

总条数

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.size", "description": "

分页大小

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.page", "description": "

当前是第几页

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.pageTotal", "description": "

总页码

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {\n list: [\n {\n id: 1,\n name: '首页',\n ...\n }\n ],\n page: {\n dataTotal: 100,\n size: 5,\n page: 5,\n pageTotal: 20\n }\n }\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/user.ts", "groupTitle": "User", "sampleRequest": [ { "url": "http://192.168.4.22:3300/user/" } ] }, { "type": "post", "url": "/user/login", "title": "用户登录", "name": "LoginUser", "group": "User", "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "String", "optional": false, "field": "account", "description": "

账号

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "password", "description": "

密码

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.id", "description": "

用户id

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.account", "description": "

用户昵称

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.avatarUrl", "description": "

头像

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.mobile", "description": "

手机号

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.user", "description": "

手机号

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n \"id\": 1000,\n \"name\": \"张三\",\n \"account\": \"landluck\",\n \"avatarUrl\": \"https://www.baidu.com\",\n \"mobile\": \"15558165021\",\n \"user\": 1,\n \"status\": 1,\n \"token\": \"jlkfdjfkdljfdkljk\"\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/user.ts", "groupTitle": "User", "sampleRequest": [ { "url": "http://192.168.4.22:3300/user/login" } ] }, { "type": "post", "url": "/user/login-mobile", "title": "用户手机号码登录", "name": "LoginUserMobile", "group": "User", "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "String", "optional": false, "field": "mobile", "description": "

手机号

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "code", "description": "

验证码

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.id", "description": "

用户id

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.account", "description": "

用户昵称

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.avatarUrl", "description": "

头像

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.mobile", "description": "

手机号

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.user", "description": "

手机号

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n \"id\": 1000,\n \"name\": \"张三\",\n \"account\": \"landluck\",\n \"avatarUrl\": \"https://www.baidu.com\",\n \"mobile\": \"15558165021\",\n \"user\": 1,\n \"status\": 1,\n \"token\": \"jlkfdjfkdljfdkljk\"\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/user.ts", "groupTitle": "User", "sampleRequest": [ { "url": "http://192.168.4.22:3300/user/login-mobile" } ] }, { "type": "put", "url": "/user", "title": "修改用户", "name": "UpdateUser", "group": "User", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "Number", "optional": false, "field": "id", "description": "

用户id

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

姓名

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "account", "description": "

账号/手机号

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "password", "description": "

密码

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "avatar", "description": "

用户头像

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "mobile", "description": "

用户手机号

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "user", "description": "

用户权限id

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "status", "description": "

用户状态

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/user.ts", "groupTitle": "User", "sampleRequest": [ { "url": "http://192.168.4.22:3300/user" } ] }, { "type": "put", "url": "/user/pwd", "title": "修改用户密码", "name": "UpdateUserPwd", "group": "User", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "string", "optional": false, "field": "mobile", "description": "

用户手机号

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "code", "description": "

验证码

" }, { "group": "Parameter", "type": "string", "optional": false, "field": "password", "description": "

新密码

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/user.ts", "groupTitle": "User", "sampleRequest": [ { "url": "http://192.168.4.22:3300/user/pwd" } ] }, { "type": "delete", "url": "/user/:id", "title": "删除用户", "name": "removeUser", "group": "User", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/user.ts", "groupTitle": "User", "sampleRequest": [ { "url": "http://192.168.4.22:3300/user/:id" } ] }, { "type": "post", "url": "/role", "title": "创建角色", "name": "CreateRole", "group": "role", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

角色名称

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/role.ts", "groupTitle": "role", "sampleRequest": [ { "url": "http://192.168.4.22:3300/role" } ] }, { "type": "get", "url": "/role/", "title": "查找角色列表", "name": "GetRole", "group": "role", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "Number", "optional": false, "field": "id", "description": "

角色id

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

角色名称

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "size", "description": "

分页大小

" }, { "group": "Parameter", "type": "Number", "optional": false, "field": "page", "description": "

页码

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list", "description": "

角色列表

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.list.id", "description": "

角色列表

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.name", "description": "

角色名称

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page", "description": "

分页信息

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.dataTotal", "description": "

总条数

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.size", "description": "

分页大小

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.page", "description": "

当前是第几页

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data.page.pageTotal", "description": "

总页码

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {\n list: [\n {\n id: 1,\n name: '首页',\n }\n ],\n page: {\n dataTotal: 100,\n size: 5,\n page: 5,\n pageTotal: 20\n }\n }\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/role.ts", "groupTitle": "role", "sampleRequest": [ { "url": "http://192.168.4.22:3300/role/" } ] }, { "type": "get", "url": "/role/menu/:id", "title": "查找角色菜单", "name": "GetRoleMenu", "group": "role", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Array", "optional": false, "field": "data", "description": "

响应结果

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.id", "description": "

角色id

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.name", "description": "

菜单名称

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.icon", "description": "

菜单icon

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.url", "description": "

菜单url

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "data.list.desc", "description": "

菜单描述

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.sort", "description": "

菜单排序

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.parentId", "description": "

菜单父级id

" }, { "group": "Success 200", "type": "Number", "optional": false, "field": "data.list.level", "description": "

菜单等级

" }, { "group": "Success 200", "type": "Array", "optional": false, "field": "data.list.children", "description": "

子菜单列表

" }, { "group": "Success 200", "type": "Array", "optional": false, "field": "data.ids", "description": "

包含所有菜单的id数组

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: [\n {\n id: 1,\n name: '首页',\n icon: 'icon',\n url: '/index',\n desc: '',\n children: []\n }\n ]\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/role.ts", "groupTitle": "role", "sampleRequest": [ { "url": "http://192.168.4.22:3300/role/menu/:id" } ] }, { "type": "put", "url": "/role", "title": "修改角色", "name": "UpdateRole", "group": "role", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "Number", "optional": false, "field": "id", "description": "

角色id

" }, { "group": "Parameter", "type": "String", "optional": false, "field": "name", "description": "

角色名称

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/role.ts", "groupTitle": "role", "sampleRequest": [ { "url": "http://192.168.4.22:3300/role" } ] }, { "type": "put", "url": "/role/menu/:id", "title": "修改角色菜单", "name": "UpdateRoleMenu", "group": "role", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "parameter": { "fields": { "Parameter": [ { "group": "Parameter", "type": "Array", "optional": false, "field": "menuIds", "description": "

菜单ids

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/role.ts", "groupTitle": "role", "sampleRequest": [ { "url": "http://192.168.4.22:3300/role/menu/:id" } ] }, { "type": "delete", "url": "/role/:id", "title": "删除角色", "name": "removeRole", "group": "role", "header": { "fields": { "Header": [ { "group": "Header", "type": "String", "optional": false, "field": "token", "description": "

用户token

" } ] } }, "success": { "fields": { "Success 200": [ { "group": "Success 200", "type": "Number", "optional": false, "field": "code", "description": "

响应状态码

" }, { "group": "Success 200", "type": "String", "optional": false, "field": "msg", "description": "

响应描述

" }, { "group": "Success 200", "type": "Object", "optional": false, "field": "data", "description": "

响应结果

" } ] }, "examples": [ { "title": "Success-Response:", "content": "HTTP/1.1 200 OK\n{\n code: 200,\n msg: 'success',\n data: {}\n}", "type": "json" } ] }, "version": "0.0.0", "filename": "app/router/role.ts", "groupTitle": "role", "sampleRequest": [ { "url": "http://192.168.4.22:3300/role/:id" } ] } ] 2 | -------------------------------------------------------------------------------- /app/public/api_project.json: -------------------------------------------------------------------------------- 1 | { "name": "uvdcs api docs", "version": "0.0.1", "description": "uvdcs 接口文档", "url": "http://192.168.4.22:3300", "sampleUrl": "http://192.168.4.22:3300", "template": {}, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2019-12-03T07:31:51.801Z", "url": "http://apidocjs.com", "version": "0.17.7" } } 2 | -------------------------------------------------------------------------------- /app/public/css/style.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------------------------------ 2 | * Content 3 | * ------------------------------------------------------------------------------------------ */ 4 | body { 5 | min-width: 980px; 6 | max-width: 1280px; 7 | } 8 | 9 | body, p, a, div, th, td { 10 | font-family: "Source Sans Pro", sans-serif; 11 | font-weight: 400; 12 | font-size: 16px; 13 | } 14 | 15 | td.code { 16 | font-size: 14px; 17 | font-family: "Source Code Pro", monospace; 18 | font-style: normal; 19 | font-weight: 400; 20 | } 21 | 22 | #content { 23 | padding-top: 16px; 24 | z-Index: -1; 25 | margin-left: 270px; 26 | } 27 | 28 | p { 29 | color: #808080; 30 | } 31 | 32 | h1 { 33 | font-family: "Source Sans Pro Semibold", sans-serif; 34 | font-weight: normal; 35 | font-size: 44px; 36 | line-height: 50px; 37 | margin: 0 0 10px 0; 38 | padding: 0; 39 | } 40 | 41 | h2 { 42 | font-family: "Source Sans Pro", sans-serif; 43 | font-weight: normal; 44 | font-size: 24px; 45 | line-height: 40px; 46 | margin: 0 0 20px 0; 47 | padding: 0; 48 | } 49 | 50 | section { 51 | border-top: 1px solid #ebebeb; 52 | padding: 30px 0; 53 | } 54 | 55 | section h1 { 56 | font-family: "Source Sans Pro", sans-serif; 57 | font-weight: 700; 58 | font-size: 32px; 59 | line-height: 40px; 60 | padding-bottom: 14px; 61 | margin: 0 0 20px 0; 62 | padding: 0; 63 | } 64 | 65 | article { 66 | padding: 14px 0 30px 0; 67 | } 68 | 69 | article h1 { 70 | font-family: "Source Sans Pro Bold", sans-serif; 71 | font-weight: 600; 72 | font-size: 24px; 73 | line-height: 26px; 74 | } 75 | 76 | article h2 { 77 | font-family: "Source Sans Pro", sans-serif; 78 | font-weight: 600; 79 | font-size: 18px; 80 | line-height: 24px; 81 | margin: 0 0 10px 0; 82 | } 83 | 84 | article h3 { 85 | font-family: "Source Sans Pro", sans-serif; 86 | font-weight: 600; 87 | font-size: 16px; 88 | line-height: 18px; 89 | margin: 0 0 10px 0; 90 | } 91 | 92 | article h4 { 93 | font-family: "Source Sans Pro", sans-serif; 94 | font-weight: 600; 95 | font-size: 14px; 96 | line-height: 16px; 97 | margin: 0 0 8px 0; 98 | } 99 | 100 | table { 101 | border-collapse: collapse; 102 | width: 100%; 103 | margin: 0 0 20px 0; 104 | } 105 | 106 | th { 107 | background-color: #f5f5f5; 108 | text-align: left; 109 | font-family: "Source Sans Pro", sans-serif; 110 | font-weight: 700; 111 | padding: 4px 8px; 112 | border: #e0e0e0 1px solid; 113 | } 114 | 115 | td { 116 | vertical-align: top; 117 | padding: 10px 8px 0 8px; 118 | border: #e0e0e0 1px solid; 119 | } 120 | 121 | #generator .content { 122 | color: #b0b0b0; 123 | border-top: 1px solid #ebebeb; 124 | padding: 10px 0; 125 | } 126 | 127 | .label-optional { 128 | float: right; 129 | background-color: grey; 130 | margin-top: 4px; 131 | } 132 | 133 | .open-left { 134 | right: 0; 135 | left: auto; 136 | } 137 | 138 | /* ------------------------------------------------------------------------------------------ 139 | * apidoc - intro 140 | * ------------------------------------------------------------------------------------------ */ 141 | 142 | #apidoc .apidoc { 143 | border-top: 1px solid #ebebeb; 144 | padding: 30px 0; 145 | } 146 | 147 | #apidoc h1 { 148 | font-family: "Source Sans Pro", sans-serif; 149 | font-weight: 700; 150 | font-size: 32px; 151 | line-height: 40px; 152 | padding-bottom: 14px; 153 | margin: 0 0 20px 0; 154 | padding: 0; 155 | } 156 | 157 | #apidoc h2 { 158 | font-family: "Source Sans Pro Bold", sans-serif; 159 | font-weight: 600; 160 | font-size: 22px; 161 | line-height: 26px; 162 | padding-top: 14px; 163 | } 164 | 165 | /* ------------------------------------------------------------------------------------------ 166 | * pre / code 167 | * ------------------------------------------------------------------------------------------ */ 168 | pre { 169 | background-color: #292b36; 170 | color: #ffffff; 171 | padding: 10px; 172 | border-radius: 6px; 173 | position: relative; 174 | margin: 10px 0 20px 0; 175 | overflow-x: auto; 176 | } 177 | 178 | pre.prettyprint { 179 | width: 100%; 180 | } 181 | 182 | code.language-text { 183 | word-wrap: break-word; 184 | } 185 | 186 | pre.language-json { 187 | overflow: auto; 188 | } 189 | 190 | pre.language-html { 191 | margin: 0 0 20px 0; 192 | } 193 | 194 | .type { 195 | font-family: "Source Sans Pro", sans-serif; 196 | font-weight: 600; 197 | font-size: 15px; 198 | display: inline-block; 199 | margin: 0 0 5px 0; 200 | padding: 4px 5px; 201 | border-radius: 6px; 202 | text-transform: uppercase; 203 | background-color: #3387CC; 204 | color: #ffffff; 205 | } 206 | 207 | .type__get { 208 | background-color: green; 209 | } 210 | 211 | .type__put { 212 | background-color: #e5c500; 213 | } 214 | 215 | .type__post { 216 | background-color: #4070ec; 217 | } 218 | 219 | .type__delete { 220 | background-color: #ed0039; 221 | } 222 | 223 | pre.language-api .str { 224 | color: #ffffff; 225 | } 226 | 227 | pre.language-api .pln, 228 | pre.language-api .pun { 229 | color: #65B042; 230 | } 231 | 232 | pre code { 233 | display: block; 234 | font-size: 14px; 235 | font-family: "Source Code Pro", monospace; 236 | font-style: normal; 237 | font-weight: 400; 238 | word-wrap: normal; 239 | white-space: pre; 240 | } 241 | 242 | pre code.sample-request-response-json { 243 | white-space: pre-wrap; 244 | max-height: 500px; 245 | overflow: auto; 246 | } 247 | 248 | /* ------------------------------------------------------------------------------------------ 249 | * Sidenav 250 | * ------------------------------------------------------------------------------------------ */ 251 | .sidenav { 252 | width: 228px; 253 | margin: 0; 254 | padding: 0 20px 20px 20px; 255 | position: fixed; 256 | top: 50px; 257 | left: 0; 258 | bottom: 0; 259 | overflow-x: hidden; 260 | overflow-y: auto; 261 | background-color: #f5f5f5; 262 | z-index: 10; 263 | } 264 | 265 | .sidenav > li > a { 266 | display: block; 267 | width: 192px; 268 | margin: 0; 269 | padding: 2px 11px; 270 | border: 0; 271 | border-left: transparent 4px solid; 272 | border-right: transparent 4px solid; 273 | font-family: "Source Sans Pro", sans-serif; 274 | font-weight: 400; 275 | font-size: 14px; 276 | } 277 | 278 | .sidenav > li.nav-header { 279 | margin-top: 8px; 280 | margin-bottom: 8px; 281 | } 282 | 283 | .sidenav > li.nav-header > a { 284 | padding: 5px 15px; 285 | border: 1px solid #e5e5e5; 286 | width: 190px; 287 | font-family: "Source Sans Pro", sans-serif; 288 | font-weight: 700; 289 | font-size: 16px; 290 | background-color: #ffffff; 291 | } 292 | 293 | .sidenav > li.active > a { 294 | position: relative; 295 | z-index: 2; 296 | background-color: #0088cc; 297 | color: #ffffff; 298 | } 299 | 300 | .sidenav > li.has-modifications a { 301 | border-right: #60d060 4px solid; 302 | } 303 | 304 | .sidenav > li.is-new a { 305 | border-left: #e5e5e5 4px solid; 306 | } 307 | 308 | /* ------------------------------------------------------------------------------------------ 309 | * Side nav search 310 | * ------------------------------------------------------------------------------------------ */ 311 | .sidenav-search { 312 | width: 228px; 313 | left: 0px; 314 | position: fixed; 315 | padding: 16px 20px 10px 20px; 316 | background-color: #F5F5F5; 317 | z-index: 11; 318 | } 319 | 320 | .sidenav-search .search { 321 | height: 26px; 322 | } 323 | 324 | .search-reset { 325 | position: absolute; 326 | display: block; 327 | cursor: pointer; 328 | width: 20px; 329 | height: 20px; 330 | text-align: center; 331 | right: 28px; 332 | top: 17px; 333 | background-color: #fff; 334 | } 335 | 336 | /* ------------------------------------------------------------------------------------------ 337 | * Compare 338 | * ------------------------------------------------------------------------------------------ */ 339 | 340 | ins { 341 | background: #60d060; 342 | text-decoration: none; 343 | color: #000000; 344 | } 345 | 346 | del { 347 | background: #f05050; 348 | color: #000000; 349 | } 350 | 351 | .label-ins { 352 | background-color: #60d060; 353 | } 354 | 355 | .label-del { 356 | background-color: #f05050; 357 | text-decoration: line-through; 358 | } 359 | 360 | pre.ins { 361 | background-color: #60d060; 362 | } 363 | 364 | pre.del { 365 | background-color: #f05050; 366 | text-decoration: line-through; 367 | } 368 | 369 | table.ins th, 370 | table.ins td { 371 | background-color: #60d060; 372 | } 373 | 374 | table.del th, 375 | table.del td { 376 | background-color: #f05050; 377 | text-decoration: line-through; 378 | } 379 | 380 | tr.ins td { 381 | background-color: #60d060; 382 | } 383 | 384 | tr.del td { 385 | background-color: #f05050; 386 | text-decoration: line-through; 387 | } 388 | 389 | /* ------------------------------------------------------------------------------------------ 390 | * Spinner 391 | * ------------------------------------------------------------------------------------------ */ 392 | 393 | #loader { 394 | position: absolute; 395 | width: 100%; 396 | } 397 | 398 | #loader p { 399 | padding-top: 80px; 400 | margin-left: -4px; 401 | } 402 | 403 | .spinner { 404 | margin: 200px auto; 405 | width: 60px; 406 | height: 60px; 407 | position: relative; 408 | } 409 | 410 | .container1 > div, .container2 > div, .container3 > div { 411 | width: 14px; 412 | height: 14px; 413 | background-color: #0088cc; 414 | 415 | border-radius: 100%; 416 | position: absolute; 417 | -webkit-animation: bouncedelay 1.2s infinite ease-in-out; 418 | animation: bouncedelay 1.2s infinite ease-in-out; 419 | /* Prevent first frame from flickering when animation starts */ 420 | -webkit-animation-fill-mode: both; 421 | animation-fill-mode: both; 422 | } 423 | 424 | .spinner .spinner-container { 425 | position: absolute; 426 | width: 100%; 427 | height: 100%; 428 | } 429 | 430 | .container2 { 431 | -webkit-transform: rotateZ(45deg); 432 | transform: rotateZ(45deg); 433 | } 434 | 435 | .container3 { 436 | -webkit-transform: rotateZ(90deg); 437 | transform: rotateZ(90deg); 438 | } 439 | 440 | .circle1 { top: 0; left: 0; } 441 | .circle2 { top: 0; right: 0; } 442 | .circle3 { right: 0; bottom: 0; } 443 | .circle4 { left: 0; bottom: 0; } 444 | 445 | .container2 .circle1 { 446 | -webkit-animation-delay: -1.1s; 447 | animation-delay: -1.1s; 448 | } 449 | 450 | .container3 .circle1 { 451 | -webkit-animation-delay: -1.0s; 452 | animation-delay: -1.0s; 453 | } 454 | 455 | .container1 .circle2 { 456 | -webkit-animation-delay: -0.9s; 457 | animation-delay: -0.9s; 458 | } 459 | 460 | .container2 .circle2 { 461 | -webkit-animation-delay: -0.8s; 462 | animation-delay: -0.8s; 463 | } 464 | 465 | .container3 .circle2 { 466 | -webkit-animation-delay: -0.7s; 467 | animation-delay: -0.7s; 468 | } 469 | 470 | .container1 .circle3 { 471 | -webkit-animation-delay: -0.6s; 472 | animation-delay: -0.6s; 473 | } 474 | 475 | .container2 .circle3 { 476 | -webkit-animation-delay: -0.5s; 477 | animation-delay: -0.5s; 478 | } 479 | 480 | .container3 .circle3 { 481 | -webkit-animation-delay: -0.4s; 482 | animation-delay: -0.4s; 483 | } 484 | 485 | .container1 .circle4 { 486 | -webkit-animation-delay: -0.3s; 487 | animation-delay: -0.3s; 488 | } 489 | 490 | .container2 .circle4 { 491 | -webkit-animation-delay: -0.2s; 492 | animation-delay: -0.2s; 493 | } 494 | 495 | .container3 .circle4 { 496 | -webkit-animation-delay: -0.1s; 497 | animation-delay: -0.1s; 498 | } 499 | 500 | @-webkit-keyframes bouncedelay { 501 | 0%, 80%, 100% { -webkit-transform: scale(0.0) } 502 | 40% { -webkit-transform: scale(1.0) } 503 | } 504 | 505 | @keyframes bouncedelay { 506 | 0%, 80%, 100% { 507 | transform: scale(0.0); 508 | -webkit-transform: scale(0.0); 509 | } 40% { 510 | transform: scale(1.0); 511 | -webkit-transform: scale(1.0); 512 | } 513 | } 514 | 515 | /* ------------------------------------------------------------------------------------------ 516 | * Tabs 517 | * ------------------------------------------------------------------------------------------ */ 518 | ul.nav-tabs { 519 | margin: 0; 520 | } 521 | 522 | p.deprecated span{ 523 | color: #ff0000; 524 | font-weight: bold; 525 | text-decoration: underline; 526 | } 527 | 528 | /* ------------------------------------------------------------------------------------------ 529 | * Print 530 | * ------------------------------------------------------------------------------------------ */ 531 | 532 | @media print { 533 | 534 | #sidenav, 535 | #version, 536 | #versions, 537 | section .version, 538 | section .versions { 539 | display: none; 540 | } 541 | 542 | #content { 543 | margin-left: 0; 544 | } 545 | 546 | a { 547 | text-decoration: none; 548 | color: inherit; 549 | } 550 | 551 | a:after { 552 | content: " [" attr(href) "] "; 553 | } 554 | 555 | p { 556 | color: #000000 557 | } 558 | 559 | pre { 560 | background-color: #ffffff; 561 | color: #000000; 562 | padding: 10px; 563 | border: #808080 1px solid; 564 | border-radius: 6px; 565 | position: relative; 566 | margin: 10px 0 20px 0; 567 | } 568 | 569 | } /* /@media print */ 570 | -------------------------------------------------------------------------------- /app/public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/landluck/react-ant-admin-api/6678d8577b2fc94808a81525f51c40d355b0707a/app/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/landluck/react-ant-admin-api/6678d8577b2fc94808a81525f51c40d355b0707a/app/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/landluck/react-ant-admin-api/6678d8577b2fc94808a81525f51c40d355b0707a/app/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/landluck/react-ant-admin-api/6678d8577b2fc94808a81525f51c40d355b0707a/app/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/landluck/react-ant-admin-api/6678d8577b2fc94808a81525f51c40d355b0707a/app/public/img/favicon.ico -------------------------------------------------------------------------------- /app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Loading... 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 41 | 42 | 66 | 67 | 72 | 73 | 78 | 79 | 88 | 89 | 102 | 103 | 177 | 178 | 234 | 235 | 302 | 303 | 394 | 395 | 463 | 464 | 558 | 559 | 638 | 639 |
640 |
641 |
642 |
643 |
644 | 645 |
646 | 647 |
648 |
649 |
650 |
651 | 652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |

Loading...

664 |
665 |
666 | 667 | 668 | 669 | 670 | -------------------------------------------------------------------------------- /app/public/vendor/path-to-regexp/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/public/vendor/prettify.css: -------------------------------------------------------------------------------- 1 | /* Pretty printing styles. Used with prettify.js. */ 2 | /* Vim sunburst theme by David Leibovic */ 3 | 4 | pre .str, code .str { color: #65B042; } /* string - green */ 5 | pre .kwd, code .kwd { color: #E28964; } /* keyword - dark pink */ 6 | pre .com, code .com { color: #AEAEAE; font-style: italic; } /* comment - gray */ 7 | pre .typ, code .typ { color: #89bdff; } /* type - light blue */ 8 | pre .lit, code .lit { color: #3387CC; } /* literal - blue */ 9 | pre .pun, code .pun { color: #fff; } /* punctuation - white */ 10 | pre .pln, code .pln { color: #fff; } /* plaintext - white */ 11 | pre .tag, code .tag { color: #89bdff; } /* html/xml tag - light blue */ 12 | pre .atn, code .atn { color: #bdb76b; } /* html/xml attribute name - khaki */ 13 | pre .atv, code .atv { color: #65B042; } /* html/xml attribute value - green */ 14 | pre .dec, code .dec { color: #3387CC; } /* decimal - blue */ 15 | 16 | pre.prettyprint, code.prettyprint { 17 | background-color: #000; 18 | -moz-border-radius: 8px; 19 | -webkit-border-radius: 8px; 20 | -o-border-radius: 8px; 21 | -ms-border-radius: 8px; 22 | -khtml-border-radius: 8px; 23 | border-radius: 8px; 24 | } 25 | 26 | pre.prettyprint { 27 | width: 95%; 28 | margin: 1em auto; 29 | padding: 1em; 30 | white-space: pre-wrap; 31 | } 32 | 33 | 34 | /* Specify class=linenums on a pre to get line numbering */ 35 | ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE; } /* IE indents via margin-left */ 36 | li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none } 37 | /* Alternate shading for lines */ 38 | li.L1,li.L3,li.L5,li.L7,li.L9 { } 39 | 40 | @media print { 41 | pre .str, code .str { color: #060; } 42 | pre .kwd, code .kwd { color: #006; font-weight: bold; } 43 | pre .com, code .com { color: #600; font-style: italic; } 44 | pre .typ, code .typ { color: #404; font-weight: bold; } 45 | pre .lit, code .lit { color: #044; } 46 | pre .pun, code .pun { color: #440; } 47 | pre .pln, code .pln { color: #000; } 48 | pre .tag, code .tag { color: #006; font-weight: bold; } 49 | pre .atn, code .atn { color: #404; } 50 | pre .atv, code .atv { color: #060; } 51 | } 52 | -------------------------------------------------------------------------------- /app/public/vendor/prettify/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /app/response/responseCode.ts: -------------------------------------------------------------------------------- 1 | export enum ApiResponseCode { 2 | SUCCESS = 200, 3 | SERVER_ERROR = 300, 4 | NO_TOKEN = 400, 5 | TOKEN_ERROR = 400, 6 | NOT_FOUND = 404, 7 | 8 | // 参数校验不通过 9 | PARAMS_ERROR = 4001, 10 | 11 | // 资源已存在 12 | RESOURCE_EXISTED = 4003, 13 | // 超出限制 14 | RESOURCE_LIMTI = 4007, 15 | 16 | // 用户相关 17 | USER_NOT_FOUND = 4004, 18 | USER_ERROR = 4005, 19 | USER_UNAVAILABLE = 4006, 20 | } 21 | -------------------------------------------------------------------------------- /app/response/responseMsg.ts: -------------------------------------------------------------------------------- 1 | export enum ApiResponseMsg { 2 | SUCCESS = 'success', 3 | SERVER_ERROR = '服务器错误', 4 | NO_TOKEN = '鉴权失败、请登录', 5 | TOKEN_ERROR = '登录过期,请重新登录', 6 | WX_CODE_ERROR = '登录失败、微信信息错误', 7 | NOT_FOUND = '资源不存在', 8 | 9 | // 用户相关 10 | USER_NOT_FOUND = '登录失败、用户不存在', 11 | USER_PWD_ERROR = '登录失败、用户密码错误', 12 | USER_EXISTED = '用户已存在,请检查后重试', 13 | USER_UNAVAILABLE = '用户已禁用,请联系管理员', 14 | 15 | // 校验相关 16 | MOBILE_ERROR = '手机号码错误,请输入合法手机号码', 17 | CODE_ERROR = '验证码错误,请输入合法验证码', 18 | 19 | // 验证码 20 | SMS_OVER = '发送失败,每个手机号每天最多发送&条', 21 | SMS_SND_ERROR = '发送失败,请联系管理员', 22 | } 23 | -------------------------------------------------------------------------------- /app/response/rest.ts: -------------------------------------------------------------------------------- 1 | import { ApiResponse } from '../../typings/iny'; 2 | import { ApiResponseCode } from './responseCode'; 3 | import { ApiResponseMsg } from './responseMsg'; 4 | 5 | export function buildSuccess(data?: any): ApiResponse { 6 | 7 | return buildResponse(ApiResponseCode.SUCCESS, data || {}, ApiResponseMsg.SUCCESS); 8 | } 9 | 10 | export function buildFail(code: ApiResponseCode, msg: ApiResponseMsg): ApiResponse { 11 | return buildResponse(code, {} as T, msg); 12 | } 13 | 14 | export function buildResponse(code: ApiResponseCode, data: T, msg: ApiResponseMsg): ApiResponse { 15 | return { 16 | code, 17 | data, 18 | msg, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | import index from './router/index'; 3 | import user from './router/user'; 4 | import sms from './router/sms'; 5 | import menu from './router/menu'; 6 | import role from './router/role'; 7 | 8 | export default (app: Application) => { 9 | 10 | const { router } = app; 11 | 12 | router.prefix('/react-ant-admin-api'); 13 | 14 | index(app); 15 | user(app); 16 | sms(app); 17 | menu(app); 18 | role(app); 19 | }; 20 | -------------------------------------------------------------------------------- /app/router/index.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | const apiRouter = router.namespace('/index'); 6 | 7 | apiRouter.get('/list', controller.index.queryList); 8 | }; 9 | -------------------------------------------------------------------------------- /app/router/menu.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | const apiRouter = router.namespace('/menu'); 6 | 7 | /** 8 | * @api {post} /menu 创建菜单 9 | * @apiName CreateMenu 10 | * @apiGroup Menu 11 | * 12 | * @apiHeader {String} token 用户token 13 | * 14 | * @apiParam {String} name 菜单名称 15 | * @apiParam {String} icon 菜单icon 16 | * @apiParam {String} url 菜单url 17 | * @apiParam {String} desc 菜单描述 18 | * @apiParam {Number} sort 菜单排序 19 | * @apiParam {Number} parentId 菜单父级id 20 | * @apiParam {Number} level 菜单等级 21 | * 22 | * @apiSuccess {Number} code 响应状态码 23 | * @apiSuccess {String} msg 响应描述 24 | * @apiSuccess {Object} data 响应结果 25 | * @apiSuccessExample Success-Response: 26 | * HTTP/1.1 200 OK 27 | * { 28 | * code: 200, 29 | * msg: 'success', 30 | * data: {} 31 | * } 32 | */ 33 | apiRouter.post('/', controller.menu.createOne); 34 | /** 35 | * @api {put} /menu 修改菜单 36 | * @apiName UpdateMenu 37 | * @apiGroup Menu 38 | * 39 | * @apiHeader {String} token 用户token 40 | * 41 | * @apiParam {Number} id 菜单id 42 | * @apiParam {String} name 菜单名称 43 | * @apiParam {String} icon 菜单icon 44 | * @apiParam {String} url 菜单url 45 | * @apiParam {String} desc 菜单描述 46 | * @apiParam {Number} sort 菜单排序 47 | * @apiParam {Number} parentId 菜单父级id 48 | * @apiParam {Number} level 菜单等级 49 | * 50 | * @apiSuccess {Number} code 响应状态码 51 | * @apiSuccess {String} msg 响应描述 52 | * @apiSuccess {Object} data 响应结果 53 | * @apiSuccessExample Success-Response: 54 | * HTTP/1.1 200 OK 55 | * { 56 | * code: 200, 57 | * msg: 'success', 58 | * data: {} 59 | * } 60 | */ 61 | apiRouter.put('/', controller.menu.updateOne); 62 | 63 | /** 64 | * @api {delete} /menu/:id 删除菜单 65 | * @apiName removeMenu 66 | * @apiGroup Menu 67 | * 68 | * @apiHeader {String} token 用户token 69 | * 70 | * 71 | * @apiSuccess {Number} code 响应状态码 72 | * @apiSuccess {String} msg 响应描述 73 | * @apiSuccess {Object} data 响应结果 74 | * @apiSuccessExample Success-Response: 75 | * HTTP/1.1 200 OK 76 | * { 77 | * code: 200, 78 | * msg: 'success', 79 | * data: {} 80 | * } 81 | */ 82 | apiRouter.delete('/:id', controller.menu.removeOne); 83 | 84 | /** 85 | * @api {get} /menu/ 查找菜单列表 86 | * @apiName GetMenu 87 | * @apiGroup Menu 88 | * 89 | * @apiHeader {String} token 用户token 90 | * 91 | * @apiParam {Number} id 菜单id 92 | * @apiParam {String} name 菜单名称 93 | * @apiParam {String} url 菜单url 94 | * @apiParam {Number} level 菜单level 95 | * @apiParam {Number} size 分页大小 96 | * @apiParam {Number} page 页码 97 | * 98 | * @apiSuccess {Number} code 响应状态码 99 | * @apiSuccess {String} msg 响应描述 100 | * @apiSuccess {Object} data 响应结果 101 | * @apiSuccess {Object} data.list 菜单列表 102 | * @apiSuccess {Object} data.list.id 菜单列表 103 | * @apiSuccess {String} data.list.name 菜单名称 104 | * @apiSuccess {String} data.list.icon 菜单icon 105 | * @apiSuccess {String} data.list.url 菜单url 106 | * @apiSuccess {String} data.list.desc 菜单描述 107 | * @apiSuccess {Number} data.list.sort 菜单排序 108 | * @apiSuccess {Number} data.list.parentId 菜单父级id 109 | * @apiSuccess {Number} data.list.level 菜单等级 110 | * @apiSuccess {Object} data.page 分页信息 111 | * @apiSuccess {Object} data.page.dataTotal 总条数 112 | * @apiSuccess {Object} data.page.size 分页大小 113 | * @apiSuccess {Object} data.page.page 当前是第几页 114 | * @apiSuccess {Object} data.page.pageTotal 总页码 115 | * @apiSuccessExample Success-Response: 116 | * HTTP/1.1 200 OK 117 | * { 118 | * code: 200, 119 | * msg: 'success', 120 | * data: { 121 | * list: [ 122 | * { 123 | * id: 1, 124 | * name: '首页', 125 | * icon: 'icon', 126 | * url: '/index', 127 | * desc: '', 128 | * ... 129 | * } 130 | * ], 131 | * page: { 132 | * dataTotal: 100, 133 | * size: 5, 134 | * page: 5, 135 | * pageTotal: 20 136 | * } 137 | * } 138 | * } 139 | */ 140 | apiRouter.get('/', controller.menu.queryList); 141 | 142 | /** 143 | * @api {get} /menu/cascader 查找菜单级联列表 144 | * @apiName GetMenuCascader 145 | * @apiGroup Menu 146 | * 147 | * @apiHeader {String} token 用户token 148 | * 149 | * @apiSuccess {Number} code 响应状态码 150 | * @apiSuccess {String} msg 响应描述 151 | * @apiSuccess {Array} data 响应结果 152 | * @apiSuccess {Number} data.id 菜单id 153 | * @apiSuccess {String} data.list.name 菜单名称 154 | * @apiSuccess {String} data.list.icon 菜单icon 155 | * @apiSuccess {String} data.list.url 菜单url 156 | * @apiSuccess {String} data.list.desc 菜单描述 157 | * @apiSuccess {Number} data.list.sort 菜单排序 158 | * @apiSuccess {Number} data.list.parentId 菜单父级id 159 | * @apiSuccess {Number} data.list.level 菜单等级 160 | * @apiSuccess {Array} data.list.children 子菜单列表 161 | * @apiSuccessExample Success-Response: 162 | * HTTP/1.1 200 OK 163 | * { 164 | * code: 200, 165 | * msg: 'success', 166 | * data: [ 167 | * { 168 | * id: 1, 169 | * name: '首页', 170 | * icon: 'icon', 171 | * url: '/index', 172 | * desc: '', 173 | * children: [] 174 | * } 175 | * ] 176 | * } 177 | */ 178 | apiRouter.get('/cascader', controller.menu.queryCascader); 179 | }; 180 | -------------------------------------------------------------------------------- /app/router/role.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | const apiRouter = router.namespace('/role'); 6 | 7 | /** 8 | * @api {post} /role 创建角色 9 | * @apiName CreateRole 10 | * @apiGroup role 11 | * 12 | * @apiHeader {String} token 用户token 13 | * 14 | * @apiParam {String} name 角色名称 15 | * 16 | * @apiSuccess {Number} code 响应状态码 17 | * @apiSuccess {String} msg 响应描述 18 | * @apiSuccess {Object} data 响应结果 19 | * @apiSuccessExample Success-Response: 20 | * HTTP/1.1 200 OK 21 | * { 22 | * code: 200, 23 | * msg: 'success', 24 | * data: {} 25 | * } 26 | */ 27 | apiRouter.post('/', controller.role.createOne); 28 | /** 29 | * @api {put} /role 修改角色 30 | * @apiName UpdateRole 31 | * @apiGroup role 32 | * 33 | * @apiHeader {String} token 用户token 34 | * 35 | * @apiParam {Number} id 角色id 36 | * @apiParam {String} name 角色名称 37 | * 38 | * @apiSuccess {Number} code 响应状态码 39 | * @apiSuccess {String} msg 响应描述 40 | * @apiSuccess {Object} data 响应结果 41 | * @apiSuccessExample Success-Response: 42 | * HTTP/1.1 200 OK 43 | * { 44 | * code: 200, 45 | * msg: 'success', 46 | * data: {} 47 | * } 48 | */ 49 | apiRouter.put('/', controller.role.updateOne); 50 | 51 | /** 52 | * @api {delete} /role/:id 删除角色 53 | * @apiName removeRole 54 | * @apiGroup role 55 | * 56 | * @apiHeader {String} token 用户token 57 | * 58 | * 59 | * @apiSuccess {Number} code 响应状态码 60 | * @apiSuccess {String} msg 响应描述 61 | * @apiSuccess {Object} data 响应结果 62 | * @apiSuccessExample Success-Response: 63 | * HTTP/1.1 200 OK 64 | * { 65 | * code: 200, 66 | * msg: 'success', 67 | * data: {} 68 | * } 69 | */ 70 | apiRouter.delete('/:id', controller.role.removeOne); 71 | 72 | /** 73 | * @api {get} /role/ 查找角色列表 74 | * @apiName GetRole 75 | * @apiGroup role 76 | * 77 | * @apiHeader {String} token 用户token 78 | * 79 | * @apiParam {Number} id 角色id 80 | * @apiParam {String} name 角色名称 81 | * @apiParam {Number} size 分页大小 82 | * @apiParam {Number} page 页码 83 | * 84 | * @apiSuccess {Number} code 响应状态码 85 | * @apiSuccess {String} msg 响应描述 86 | * @apiSuccess {Object} data 响应结果 87 | * @apiSuccess {Object} data.list 角色列表 88 | * @apiSuccess {Object} data.list.id 角色列表 89 | * @apiSuccess {String} data.list.name 角色名称 90 | * @apiSuccess {Object} data.page 分页信息 91 | * @apiSuccess {Object} data.page.dataTotal 总条数 92 | * @apiSuccess {Object} data.page.size 分页大小 93 | * @apiSuccess {Object} data.page.page 当前是第几页 94 | * @apiSuccess {Object} data.page.pageTotal 总页码 95 | * @apiSuccessExample Success-Response: 96 | * HTTP/1.1 200 OK 97 | * { 98 | * code: 200, 99 | * msg: 'success', 100 | * data: { 101 | * list: [ 102 | * { 103 | * id: 1, 104 | * name: '首页', 105 | * } 106 | * ], 107 | * page: { 108 | * dataTotal: 100, 109 | * size: 5, 110 | * page: 5, 111 | * pageTotal: 20 112 | * } 113 | * } 114 | * } 115 | */ 116 | apiRouter.get('/', controller.role.queryList); 117 | 118 | /** 119 | * @api {get} /role/menu/:id 查找角色菜单 120 | * @apiName GetRoleMenu 121 | * @apiGroup role 122 | * 123 | * @apiHeader {String} token 用户token 124 | * 125 | * @apiSuccess {Number} code 响应状态码 126 | * @apiSuccess {String} msg 响应描述 127 | * @apiSuccess {Array} data 响应结果 128 | * @apiSuccess {Number} data.id 角色id 129 | * @apiSuccess {String} data.list.name 菜单名称 130 | * @apiSuccess {String} data.list.icon 菜单icon 131 | * @apiSuccess {String} data.list.url 菜单url 132 | * @apiSuccess {String} data.list.desc 菜单描述 133 | * @apiSuccess {Number} data.list.sort 菜单排序 134 | * @apiSuccess {Number} data.list.parentId 菜单父级id 135 | * @apiSuccess {Number} data.list.level 菜单等级 136 | * @apiSuccess {Array} data.list.children 子菜单列表 137 | * @apiSuccess {Array} data.ids 包含所有菜单的id数组 138 | * 139 | * @apiSuccessExample Success-Response: 140 | * HTTP/1.1 200 OK 141 | * { 142 | * code: 200, 143 | * msg: 'success', 144 | * data: [ 145 | * { 146 | * id: 1, 147 | * name: '首页', 148 | * icon: 'icon', 149 | * url: '/index', 150 | * desc: '', 151 | * children: [] 152 | * } 153 | * ] 154 | * } 155 | */ 156 | apiRouter.get('/menu/:id', controller.role.queryRoleMenu); 157 | 158 | /** 159 | * @api {put} /role/menu/:id 修改角色菜单 160 | * @apiName UpdateRoleMenu 161 | * @apiGroup role 162 | * 163 | * @apiHeader {String} token 用户token 164 | * @apiParam {Array} menuIds 菜单ids 165 | * 166 | * @apiSuccess {Number} code 响应状态码 167 | * @apiSuccess {String} msg 响应描述 168 | * @apiSuccess {Object} data 响应结果 169 | * @apiSuccessExample Success-Response: 170 | * HTTP/1.1 200 OK 171 | * { 172 | * code: 200, 173 | * msg: 'success', 174 | * data: {} 175 | * } 176 | */ 177 | apiRouter.put('/menu/:id', controller.role.updateRoleMenu); 178 | }; 179 | -------------------------------------------------------------------------------- /app/router/sms.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | const apiRouter = router.namespace('/sms'); 6 | 7 | /** 8 | * @api {post} /sms 发送验证码 9 | * @apiName SendSms 10 | * @apiGroup Sms 11 | * 12 | * @apiParam {String} mobile 账号 13 | * 14 | * @apiSuccess {Number} code 响应状态码 15 | * @apiSuccess {String} msg 响应描述 16 | * @apiSuccess {Object} data 响应结果 17 | * @apiSuccessExample Success-Response: 18 | * HTTP/1.1 200 OK 19 | * { 20 | * code: 200, 21 | * msg: 'success', 22 | * data: {} 23 | * } 24 | */ 25 | apiRouter.post('/', controller.sms.sendMessage); 26 | }; 27 | -------------------------------------------------------------------------------- /app/router/upload.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | const apiRouter = router.namespace('/upload'); 6 | 7 | /** 8 | * @api {post} /upload/image 上传图片 9 | * @apiName UploadImage 10 | * @apiGroup Upload 11 | * 12 | * @apiHeader {String} token 用户token 13 | * 14 | * @apiParam {String} file 文件数据 15 | * 16 | * @apiSuccess {Number} code 响应状态码 17 | * @apiSuccess {String} msg 响应描述 18 | * @apiSuccess {Array} data 响应结果 19 | * @apiSuccess {string} data.url 图片绝对地址 20 | * @apiSuccess {number} data.width 图片宽度 21 | * @apiSuccess {number} data.height 图片高度 22 | * @apiSuccessExample Success-Response: 23 | * HTTP/1.1 200 OK 24 | * { 25 | * code: 200, 26 | * msg: 'success', 27 | * data: [ 28 | * { 29 | * url: '', 30 | * width: '', 31 | * height: '' 32 | * } 33 | * ] 34 | * } 35 | */ 36 | apiRouter.post('/image', controller.upload.uploadImage); 37 | }; 38 | -------------------------------------------------------------------------------- /app/router/user.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | const apiRouter = router.namespace('/user'); 6 | 7 | /** 8 | * @api {post} /user/login 用户登录 9 | * @apiName LoginUser 10 | * @apiGroup User 11 | * 12 | * @apiParam {String} account 账号 13 | * @apiParam {String} password 密码 14 | * 15 | * @apiSuccess {Number} code 响应状态码 16 | * @apiSuccess {String} msg 响应描述 17 | * @apiSuccess {Object} data 响应结果 18 | * @apiSuccess {Number} data.id 用户id 19 | * @apiSuccess {String} data.account 用户昵称 20 | * @apiSuccess {String} data.avatarUrl 头像 21 | * @apiSuccess {String} data.mobile 手机号 22 | * @apiSuccess {String} data.user 手机号 23 | * @apiSuccessExample Success-Response: 24 | * HTTP/1.1 200 OK 25 | * { 26 | * "id": 1000, 27 | * "name": "张三", 28 | * "account": "landluck", 29 | * "avatarUrl": "https://www.baidu.com", 30 | * "mobile": "15558165021", 31 | * "user": 1, 32 | * "status": 1, 33 | * "token": "jlkfdjfkdljfdkljk" 34 | * } 35 | */ 36 | apiRouter.post('/login', controller.user.login); 37 | 38 | /** 39 | * @api {post} /user/login-mobile 用户手机号码登录 40 | * @apiName LoginUserMobile 41 | * @apiGroup User 42 | * 43 | * @apiParam {String} mobile 手机号 44 | * @apiParam {String} code 验证码 45 | * 46 | * @apiSuccess {Number} code 响应状态码 47 | * @apiSuccess {String} msg 响应描述 48 | * @apiSuccess {Object} data 响应结果 49 | * @apiSuccess {Number} data.id 用户id 50 | * @apiSuccess {String} data.account 用户昵称 51 | * @apiSuccess {String} data.avatarUrl 头像 52 | * @apiSuccess {String} data.mobile 手机号 53 | * @apiSuccess {String} data.user 手机号 54 | * @apiSuccessExample Success-Response: 55 | * HTTP/1.1 200 OK 56 | * { 57 | * "id": 1000, 58 | * "name": "张三", 59 | * "account": "landluck", 60 | * "avatarUrl": "https://www.baidu.com", 61 | * "mobile": "15558165021", 62 | * "user": 1, 63 | * "status": 1, 64 | * "token": "jlkfdjfkdljfdkljk" 65 | * } 66 | */ 67 | apiRouter.post('/login-mobile', controller.user.loginByMobile); 68 | 69 | /** 70 | * @api {post} /user/register 用户注册 71 | * @apiName RegisterUser 72 | * @apiGroup User 73 | * 74 | * @apiParam {String} name 姓名 75 | * @apiParam {String} account 账号/手机号 76 | * @apiParam {String} password 密码 77 | * @apiParam {String} mobile 用户手机号 78 | * @apiParam {Number} code 用户手机号验证码 79 | * 80 | * @apiSuccess {Number} code 响应状态码 81 | * @apiSuccess {String} msg 响应描述 82 | * @apiSuccess {Object} data 响应结果 83 | * @apiSuccess {Number} data.id 用户id 84 | * @apiSuccessExample Success-Response: 85 | * HTTP/1.1 200 OK 86 | * { 87 | * code: 200, 88 | * msg: 'success', 89 | * data: { 90 | * id: 10 91 | * } 92 | * } 93 | */ 94 | apiRouter.post('/register', controller.user.registerUser); 95 | 96 | /** 97 | * @api {post} /user 创建用户 98 | * @apiName CreateUser 99 | * @apiGroup User 100 | * 101 | * @apiHeader {String} token 用户token 102 | * 103 | * @apiParam {String} name 姓名 104 | * @apiParam {String} account 账号/手机号 105 | * @apiParam {String} password 密码 106 | * @apiParam {String} avatar 用户头像 107 | * @apiParam {String} mobile 用户手机号 108 | * @apiParam {Number} roleId 用户权限id 109 | * @apiParam {Number} status 用户状态 110 | * 111 | * @apiSuccess {Number} code 响应状态码 112 | * @apiSuccess {String} msg 响应描述 113 | * @apiSuccess {Object} data 响应结果 114 | * @apiSuccessExample Success-Response: 115 | * HTTP/1.1 200 OK 116 | * { 117 | * code: 200, 118 | * msg: 'success', 119 | * data: {} 120 | * } 121 | */ 122 | apiRouter.post('/', controller.user.createUser); 123 | 124 | /** 125 | * @api {put} /user 修改用户 126 | * @apiName UpdateUser 127 | * @apiGroup User 128 | * 129 | * @apiHeader {String} token 用户token 130 | * 131 | * @apiParam {Number} id 用户id 132 | * @apiParam {String} name 姓名 133 | * @apiParam {String} account 账号/手机号 134 | * @apiParam {String} password 密码 135 | * @apiParam {String} avatar 用户头像 136 | * @apiParam {String} mobile 用户手机号 137 | * @apiParam {Number} roleId 用户权限id 138 | * @apiParam {Number} status 用户状态 139 | * 140 | * @apiSuccess {Number} code 响应状态码 141 | * @apiSuccess {String} msg 响应描述 142 | * @apiSuccess {Object} data 响应结果 143 | * @apiSuccessExample Success-Response: 144 | * HTTP/1.1 200 OK 145 | * { 146 | * code: 200, 147 | * msg: 'success', 148 | * data: {} 149 | * } 150 | */ 151 | apiRouter.put('/', controller.user.updateUser); 152 | /** 153 | * @api {delete} /user/:id 删除用户 154 | * @apiName removeUser 155 | * @apiGroup User 156 | * 157 | * @apiHeader {String} token 用户token 158 | * 159 | * 160 | * @apiSuccess {Number} code 响应状态码 161 | * @apiSuccess {String} msg 响应描述 162 | * @apiSuccess {Object} data 响应结果 163 | * @apiSuccessExample Success-Response: 164 | * HTTP/1.1 200 OK 165 | * { 166 | * code: 200, 167 | * msg: 'success', 168 | * data: {} 169 | * } 170 | */ 171 | apiRouter.delete('/:id', controller.user.removeUser); 172 | 173 | /** 174 | * @api {put} /user/pwd 修改用户密码 175 | * @apiName UpdateUserPwd 176 | * @apiGroup User 177 | * 178 | * @apiHeader {String} token 用户token 179 | * 180 | * @apiParam {string} mobile 用户手机号 181 | * @apiParam {Number} code 验证码 182 | * @apiParam {string} password 新密码 183 | * 184 | * @apiSuccess {Number} code 响应状态码 185 | * @apiSuccess {String} msg 响应描述 186 | * @apiSuccess {Object} data 响应结果 187 | * @apiSuccessExample Success-Response: 188 | * HTTP/1.1 200 OK 189 | * { 190 | * code: 200, 191 | * msg: 'success', 192 | * data: {} 193 | * } 194 | */ 195 | apiRouter.put('/pwd', controller.user.updateUserPwd); 196 | 197 | /** 198 | * @api {get} /user/ 查找用户列表 199 | * @apiName GetUser 200 | * @apiGroup User 201 | * 202 | * @apiHeader {String} token 用户token 203 | * 204 | * @apiParam {Number} id 用户id 205 | * @apiParam {String} name 用户名称 206 | * @apiParam {String} account 用户账号 207 | * @apiParam {String} mobile 用户手机号 208 | * @apiParam {Number} size 分页大小 209 | * @apiParam {Number} page 页码 210 | * 211 | * @apiSuccess {Number} code 响应状态码 212 | * @apiSuccess {String} msg 响应描述 213 | * @apiSuccess {Object} data 响应结果 214 | * @apiSuccess {Object} data.list 用户列表 215 | * @apiSuccess {Number} data.list.id 用户列表 216 | * @apiSuccess {Number} data.list.roleId 用户角色id 217 | * @apiSuccess {String} data.list.name 用户名称 218 | * @apiSuccess {String} data.list.account 用户账号 219 | * @apiSuccess {String} data.list.mobile 用户手机号 220 | * @apiSuccess {String} data.list.avatar 用户头像 221 | * @apiSuccess {String} data.list.avatar 用户头像 222 | * @apiSuccess {Object} data.list.role 用户角色 223 | * @apiSuccess {Object} data.list.role.id 用户角色id 224 | * @apiSuccess {Object} data.list.role.name 用户角色名称 225 | * @apiSuccess {Object} data.page 分页信息 226 | * @apiSuccess {Object} data.page.dataTotal 总条数 227 | * @apiSuccess {Object} data.page.size 分页大小 228 | * @apiSuccess {Object} data.page.page 当前是第几页 229 | * @apiSuccess {Object} data.page.pageTotal 总页码 230 | * @apiSuccessExample Success-Response: 231 | * HTTP/1.1 200 OK 232 | * { 233 | * code: 200, 234 | * msg: 'success', 235 | * data: { 236 | * list: [ 237 | * { 238 | * id: 1, 239 | * name: '首页', 240 | * ... 241 | * } 242 | * ], 243 | * page: { 244 | * dataTotal: 100, 245 | * size: 5, 246 | * page: 5, 247 | * pageTotal: 20 248 | * } 249 | * } 250 | * } 251 | */ 252 | apiRouter.get('/', controller.user.queryList); 253 | 254 | /** 255 | * @api {get} /user/menu/:id 查找用户菜单 256 | * @apiName GetUserMenu 257 | * @apiGroup User 258 | * 259 | * @apiHeader {String} token 用户token 260 | * 261 | * @apiSuccess {Number} code 响应状态码 262 | * @apiSuccess {String} msg 响应描述 263 | * @apiSuccess {Array} data 响应结果 264 | * @apiSuccess {Number} data.id 角色id 265 | * @apiSuccess {String} data.list.name 菜单名称 266 | * @apiSuccess {String} data.list.icon 菜单icon 267 | * @apiSuccess {String} data.list.url 菜单url 268 | * @apiSuccess {String} data.list.desc 菜单描述 269 | * @apiSuccess {Number} data.list.sort 菜单排序 270 | * @apiSuccess {Number} data.list.parentId 菜单父级id 271 | * @apiSuccess {Number} data.list.level 菜单等级 272 | * @apiSuccess {Array} data.list.children 子菜单列表 273 | * @apiSuccess {Array} data.ids 包含所有菜单的id数组 274 | * 275 | * @apiSuccessExample Success-Response: 276 | * HTTP/1.1 200 OK 277 | * { 278 | * code: 200, 279 | * msg: 'success', 280 | * data: [ 281 | * { 282 | * id: 1, 283 | * name: '首页', 284 | * icon: 'icon', 285 | * url: '/index', 286 | * desc: '', 287 | * children: [] 288 | * } 289 | * ] 290 | * } 291 | */ 292 | apiRouter.get('/menu', controller.user.queryUserMenu); 293 | }; 294 | -------------------------------------------------------------------------------- /app/service/aliyun.ts: -------------------------------------------------------------------------------- 1 | import { Context, Service } from 'egg'; 2 | import Sms = require('@alicloud/pop-core'); 3 | import { AliyunSmsConfig } from '../../typings'; 4 | 5 | interface AliyunResponse { 6 | BizId: string; 7 | Code: string; 8 | Message: string; 9 | RequestId: string; 10 | } 11 | 12 | export default class AliyunService extends Service { 13 | aliyunSms: Sms; 14 | 15 | options: AliyunSmsConfig; 16 | 17 | constructor(ctx: Context) { 18 | super(ctx); 19 | 20 | this.options = this.config.sms; 21 | 22 | this.aliyunSms = new Sms({ 23 | accessKeyId: this.options.accessKeyId, 24 | accessKeySecret: this.options.accessKeySecret, 25 | endpoint: this.options.endpoint, 26 | apiVersion: '2017-05-25', 27 | }); 28 | } 29 | 30 | // 发送验证码 31 | public async sendVerifyCode(mobile: string, code: number, signName: string, templateId: string) { 32 | return await this.sendMessage( 33 | [ mobile ], 34 | signName, 35 | templateId, 36 | JSON.stringify({ 37 | code, 38 | }), 39 | ); 40 | } 41 | 42 | // 基础发送短信函数 43 | public async sendMessage( 44 | mobiles: string[], 45 | signName: string, 46 | templateId: string, 47 | templateParam?: string, 48 | ) { 49 | 50 | try { 51 | 52 | const data: AliyunResponse = await this.aliyunSms.request( 53 | 'SendSms', 54 | { 55 | RegionId: this.options.regionId, 56 | PhoneNumbers: mobiles.join(','), 57 | SignName: signName, 58 | TemplateCode: templateId, 59 | TemplateParam: templateParam, 60 | }, 61 | { 62 | method: 'POST', 63 | }, 64 | ); 65 | 66 | this.ctx.logger.info('阿里云发送短信验证码结果', data); 67 | 68 | if (data.Code === 'OK') { 69 | return data.BizId; 70 | } 71 | 72 | return null; 73 | 74 | } catch (error) { 75 | 76 | this.ctx.logger.error(error); 77 | 78 | return null; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/service/menu.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import BaseService from '../core/service'; 3 | import { Menu } from '../model/menu'; 4 | import { MenuSearchQuery } from '../controller/menu'; 5 | import { Op, FindAndCountOptions } from 'sequelize'; 6 | import SqlUtils from '../utils/sql'; 7 | 8 | export default class MenuService extends BaseService { 9 | constructor(ctx: Context) { 10 | super(ctx); 11 | 12 | this.model = ctx.model.Menu; 13 | } 14 | 15 | public async findCascader(ids?: number[]) { 16 | const where: { id?: any } = {}; 17 | 18 | if (ids) { 19 | where.id = { 20 | [Op.in]: ids, 21 | }; 22 | } 23 | 24 | const list = await this.ctx.model.Menu.findAll({ 25 | where, 26 | ...SqlUtils.queryOptions(), 27 | }); 28 | 29 | for (let i = list.length - 1; i >= 0; i--) { 30 | const item = list[i].get({ plain: true }) as Menu; 31 | for (const menuData of list) { 32 | const menu = menuData.get({ plain: true }) as Menu; 33 | if (item.parentId === menu.id) { 34 | if (menu.children) { 35 | // 进行排序 36 | for (let s = menu.children.length - 1; s >= 0; s--) { 37 | const child = menu.children[s]; 38 | if (item.sort > child.sort) { 39 | menu.children.splice(s + 1, 0, item); 40 | break; 41 | } 42 | 43 | if (s === 0) { 44 | menu.children.unshift(item); 45 | } 46 | } 47 | } else { 48 | menu.children = [ item ]; 49 | } 50 | 51 | list.splice(i, 1); 52 | break; 53 | } 54 | } 55 | } 56 | 57 | list.sort((a, b) => a.sort - b.sort); 58 | 59 | return list; 60 | } 61 | 62 | public async findList(params: MenuSearchQuery) { 63 | const query: { 64 | name?: any; 65 | level?: number; 66 | parentId?: number; 67 | id?: number; 68 | url?: any; 69 | } = {}; 70 | 71 | if (params.name) { 72 | query.name = { 73 | [Op.like]: `%${params.name}%`, 74 | }; 75 | } 76 | 77 | if (params.id) { 78 | query.id = params.id; 79 | } 80 | 81 | if (params.url) { 82 | query.url = { 83 | [Op.like]: `%${params.url}%`, 84 | }; 85 | } 86 | 87 | if (params.level) { 88 | query.level = params.level; 89 | } 90 | if (params.parentId) { 91 | query.parentId = params.parentId; 92 | } 93 | 94 | const options: FindAndCountOptions = { 95 | include: [ 96 | { 97 | model: this.ctx.model.Menu, 98 | as: 'parent', 99 | required: false, 100 | ...SqlUtils.queryOptions(), 101 | }, 102 | ], 103 | }; 104 | 105 | return this.findListByKey(query, params, options); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/service/product.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | 3 | export default class ProductService extends Service { 4 | public async queryFileList () { 5 | return null; 6 | } 7 | 8 | public async deleteOne () { 9 | return null; 10 | } 11 | 12 | public async writeFile () { 13 | 14 | return null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/service/qiniu.ts: -------------------------------------------------------------------------------- 1 | import { Context, Service } from 'egg'; 2 | import qiniu = require('qiniu'); 3 | import { QiniuConfig } from '../../typings'; 4 | 5 | interface ResponseData { 6 | url: string; 7 | } 8 | interface QiniuResponse { 9 | statusCode: number; 10 | data: { 11 | hash: string; 12 | key: string; 13 | }; 14 | } 15 | 16 | class QiniuService extends Service { 17 | instance: any; 18 | token: string; 19 | uploader: any; 20 | extra: any; 21 | option: QiniuConfig | undefined; 22 | 23 | constructor(ctx: Context) { 24 | super(ctx); 25 | 26 | const option = this.config.oss.qiniu; 27 | 28 | this.option = option; 29 | 30 | if (!option) { 31 | return; 32 | } 33 | 34 | this.instance = new qiniu.auth.digest.Mac( 35 | option.accessKey, 36 | option.secretKey, 37 | ); 38 | 39 | this.token = new qiniu.rs.PutPolicy({ 40 | scope: option.scope, 41 | }).uploadToken(this.instance); 42 | 43 | const config = new qiniu.conf.Config() as any; 44 | config.zone = qiniu.zone.Zone_z0; 45 | 46 | this.uploader = new qiniu.form_up.FormUploader(config); 47 | this.extra = new qiniu.form_up.PutExtra(); 48 | } 49 | 50 | public async upload(fileName: string, path: string): Promise { 51 | return new Promise((resolve, reject) => { 52 | if (!this.option) { 53 | reject(new Error('qiniu config not found')); 54 | } 55 | 56 | this.uploader.putFile( 57 | this.token, 58 | fileName, 59 | path, 60 | this.extra, 61 | (err: any, response: any, responseData: QiniuResponse) => { 62 | if (err) { 63 | reject(new Error('qiniu upload error')); 64 | } 65 | 66 | this.ctx.logger.info('qiniu uplaod repsponse', response); 67 | 68 | if (responseData.statusCode === 200) { 69 | resolve({ 70 | url: this.option 71 | ? this.option.host + '/' + responseData.data.key 72 | : '', 73 | }); 74 | } else { 75 | reject(new Error('qiniu upload error')); 76 | } 77 | }, 78 | ); 79 | }); 80 | } 81 | } 82 | 83 | export default QiniuService; 84 | -------------------------------------------------------------------------------- /app/service/redis.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | import ApiException from '../exception/ApiException'; 3 | import { ApiResponseCode } from '../response/responseCode'; 4 | import { ApiResponseMsg } from '../response/responseMsg'; 5 | 6 | const SUCCESS_REDIS = 'OK'; 7 | 8 | export default class Redis extends Service { 9 | 10 | public async set (key: string, value: string): Promise { 11 | const docs = await this.app.redis.set(key, value); 12 | 13 | if (docs === SUCCESS_REDIS) { 14 | 15 | return true; 16 | } 17 | 18 | this.app.logger.info('redis 储存错误 ------> error', docs); 19 | 20 | throw new ApiException(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 21 | } 22 | 23 | public async setex (key: string, value: string, expire: number): Promise { 24 | const docs = await this.app.redis.setex(key, expire, value); 25 | 26 | if (docs === SUCCESS_REDIS) { 27 | 28 | return true; 29 | } 30 | 31 | this.app.logger.info('redis 储存错误 ------> error', docs); 32 | 33 | throw new ApiException(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 34 | } 35 | 36 | public async get (key: string) { 37 | return await this.app.redis.get(key); 38 | } 39 | 40 | public async setMap (key: string, value: any) { 41 | const docs = await this.app.redis.hmset(key, value); 42 | 43 | // 这里 redis 返回的是 ok 44 | // 但是 ioredis 申明的类型是 Number 45 | // @ts-ignore 46 | if (docs === SUCCESS_REDIS) { 47 | 48 | return true; 49 | } 50 | 51 | this.app.logger.info('redis 储存错误 ------> error', docs); 52 | 53 | throw new ApiException(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 54 | } 55 | 56 | public async getMap (key: string): Promise { 57 | const data = await this.app.redis.hgetall(key); 58 | 59 | return data ? data as T : null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/service/request.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | import ApiException from '../exception/ApiException'; 3 | import { ApiResponseCode } from '../response/responseCode'; 4 | import { ApiResponseMsg } from '../response/responseMsg'; 5 | 6 | export default class Request extends Service { 7 | public async post (url: string, data: any): Promise { 8 | 9 | this.ctx.logger.info('发起POST请求:url ---->', url, ', data 参数 ---->', data); 10 | 11 | const response = await this.app.curl(url, { 12 | method: 'POST', 13 | contentType: 'json', 14 | data, 15 | dataType: 'json', 16 | }); 17 | 18 | if (response.status === 200) { 19 | 20 | this.ctx.logger.info('POST请求结果: --> 成功 <--- 结果为 ---->', response.data); 21 | return response.data; 22 | } else { 23 | this.ctx.logger.info('POST请求结果 ----> 失败 <--- 结果为', response); 24 | } 25 | 26 | throw new ApiException(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 27 | } 28 | 29 | public async get (url: string): Promise { 30 | 31 | this.ctx.logger.info('发起GET请求:url ---->', url); 32 | 33 | const response = await this.app.curl(url, { 34 | method: 'POST', 35 | contentType: 'json', 36 | dataType: 'json', 37 | }); 38 | 39 | if (response.status === 200) { 40 | this.ctx.logger.info('GET请求结果: --> 成功 <--- 结果为 ---->', response.data); 41 | return response.data; 42 | } else { 43 | this.ctx.logger.info('GET请求结果 ----> 失败 <--- 结果为', response); 44 | } 45 | 46 | throw new ApiException(ApiResponseCode.SERVER_ERROR, ApiResponseMsg.SERVER_ERROR); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/service/role.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import BaseService from '../core/service'; 3 | import { Role } from '../model/role'; 4 | import { RoleSearchParams } from '../controller/role'; 5 | import { Op, Transaction } from 'sequelize'; 6 | import SqlUtils from '../utils/sql'; 7 | 8 | export default class RoleService extends BaseService { 9 | constructor(ctx: Context) { 10 | super(ctx); 11 | 12 | this.model = ctx.model.Role; 13 | } 14 | 15 | public async findList(params: RoleSearchParams) { 16 | const query: RoleSearchParams = {}; 17 | 18 | if (params.name) { 19 | query.name = { 20 | [Op.like]: `%${params.name}%`, 21 | }; 22 | } 23 | if (params.id) { 24 | query.id = params.id; 25 | } 26 | 27 | return this.findListByKey(query, params); 28 | } 29 | 30 | public async findRoleMenuByRoleId(id: number) { 31 | const data = await this.ctx.model.RoleMenu.findAll({ 32 | where: { 33 | roleId: id, 34 | }, 35 | ...SqlUtils.queryOptions(), 36 | order: [], 37 | }); 38 | 39 | const ids = data.map(item => item.menuId); 40 | 41 | const menuList = await this.ctx.service.menu.findCascader(ids); 42 | 43 | return { 44 | list: menuList, 45 | ids, 46 | }; 47 | } 48 | 49 | public async updateRoleMenuByRoleId(id: number, menuIds: number[]) { 50 | const data = await this.ctx.model.RoleMenu.findAll({ 51 | where: { 52 | roleId: id, 53 | }, 54 | ...SqlUtils.queryOptions(), 55 | }); 56 | 57 | // 查找到原先的存在的ids 58 | const ids = data.map(item => item.menuId); 59 | 60 | // 要删除的 ids 61 | const removeIds: number[] = []; 62 | // 要新建的 ids 63 | const createIds: number[] = []; 64 | 65 | ids.forEach((id: number, i: number) => { 66 | const index = menuIds.indexOf(id); 67 | if (index === -1) { 68 | // 不存在需要删除,推入该记录的id 69 | removeIds.push(data[i].id); 70 | // 存在,删除 menuIds 中的该元素。剩下的即为新建的 71 | } else { 72 | menuIds.splice(index, 1); 73 | } 74 | }); 75 | 76 | createIds.push(...menuIds); 77 | 78 | if (removeIds.length === 0 && createIds.length === 0) return true; 79 | 80 | // 开始事务 81 | const transaction: Transaction = await this.ctx.model.transaction(); 82 | try { 83 | await this.ctx.model.RoleMenu.destroy({ 84 | where: { 85 | id: { 86 | [Op.in]: removeIds, 87 | }, 88 | }, 89 | transaction, 90 | }); 91 | 92 | await this.ctx.model.RoleMenu.bulkCreate( 93 | createIds.map((menuId: number) => 94 | SqlUtils.createOptions<{ roleId: number; menuId: number }>( 95 | { roleId: id, menuId }, 96 | this.ctx.user, 97 | ), 98 | ), 99 | { 100 | transaction, 101 | }, 102 | ); 103 | // 提交事务 104 | await transaction.commit(); 105 | 106 | return true; 107 | } catch (error) { 108 | this.ctx.logger.info(error); 109 | 110 | // 错误回滚 111 | await transaction.rollback(); 112 | 113 | return false; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/service/sms.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import BaseService from '../core/service'; 3 | import { Sms } from '../model/sms'; 4 | import SqlUtils from '../utils/sql'; 5 | import { Op } from 'sequelize'; 6 | export default class SmsService extends BaseService { 7 | constructor(ctx: Context) { 8 | super(ctx); 9 | 10 | this.model = ctx.model.Sms; 11 | } 12 | 13 | public async countByMobile(mobile: string) { 14 | 15 | const count = await this.ctx.model.Sms.count({ 16 | where: { 17 | mobile, 18 | createdAt: { 19 | [Op.gte]: new Date(), 20 | }, 21 | }, 22 | }); 23 | 24 | return count; 25 | } 26 | 27 | public async countByIp(ip: string) { 28 | const count = await this.ctx.model.Sms.count({ 29 | where: { 30 | ip, 31 | createdAt: { 32 | [Op.gte]: new Date(), 33 | }, 34 | }, 35 | }); 36 | 37 | return count; 38 | } 39 | 40 | public async sendVerifyCode(mobile: string, code: number, ip: string) { 41 | const config = this.config.sms.verifyCode; 42 | 43 | // const bizId = await this.ctx.service.aliyunSms.sendVerifyCode( 44 | // mobile, 45 | // code, 46 | // config.signName, 47 | // config.templateCode 48 | // ); 49 | 50 | // if (!bizId) return null; 51 | 52 | const record = await this.ctx.model.Sms.create({ 53 | mobile, 54 | code, 55 | ip, 56 | bizId: '', 57 | content: '', 58 | tid: config.templateCode, 59 | }); 60 | 61 | return record; 62 | } 63 | 64 | public async findCodeByMobileAndCode(mobile: string, code: number): Promise { 65 | 66 | const message = await this.ctx.model.Sms.findOne({ 67 | where: { 68 | mobile, 69 | code, 70 | }, 71 | ...SqlUtils.queryOptions(), 72 | }); 73 | 74 | if (message) { 75 | return message.get({ plain: true }) as Sms; 76 | } 77 | return null; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/service/task.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | 3 | export default class Task extends Service { 4 | public async queryTask() {} 5 | } 6 | -------------------------------------------------------------------------------- /app/service/upload.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | import { EggFile } from 'egg-multipart'; 3 | import { formatTimeByFormater, now } from '../utils/date'; 4 | import path = require('path'); 5 | import fs = require('fs'); 6 | import { getImageInfo } from '../utils/image'; 7 | 8 | interface UploadImageProps { 9 | url: string; 10 | width: number; 11 | height: number; 12 | } 13 | 14 | class UploadService extends Service { 15 | public async uploadFilesToQiniu(files: EggFile[]) { 16 | const result: UploadImageProps[] = []; 17 | 18 | for (const file of files) { 19 | const name = this.relativeFilePath(file); 20 | const imageInfo = await getImageInfo(file); 21 | 22 | const data = await this.service.qiniu.upload(name, file.filepath); 23 | 24 | result.push({ 25 | url: data.url, 26 | width: imageInfo.width, 27 | height: imageInfo.height, 28 | }); 29 | } 30 | 31 | return result; 32 | } 33 | 34 | public async uploadFilesToLocalServer(files: EggFile[]) { 35 | const { 36 | ctx, 37 | config: { oss }, 38 | } = this; 39 | 40 | if (!oss.local) { 41 | throw new Error('oss local config not found'); 42 | } 43 | 44 | // 先取配置,没有则获取请求域名和协议 45 | const origin = 46 | oss.local && oss.local.host 47 | ? oss.local.host 48 | : ctx.request.protocol + '://' + ctx.request.host; 49 | 50 | const result: UploadImageProps[] = []; 51 | 52 | // 当前系统的静态资源文件夹 53 | const staticDir = path.resolve(__dirname, '../../' + oss.local.dir); 54 | 55 | try { 56 | const stats = await this.readDir(staticDir); 57 | 58 | if (!stats.isDirectory()) { 59 | throw new Error('oss local dir is not dir'); 60 | } 61 | } catch (error) { 62 | throw new Error('oss local dir is not found'); 63 | } 64 | 65 | const now = formatTimeByFormater(new Date(), 'YYYY-MM-DD'); 66 | 67 | const dayDir = staticDir + '/' + now; 68 | 69 | // 按照每天的时间自动创建文件夹 70 | try { 71 | await this.readDir(dayDir); 72 | } catch (error) { 73 | await this.mkdir(dayDir); 74 | } 75 | 76 | for (const file of files) { 77 | const name = this.relativeFilePath(file); 78 | 79 | const filePath = staticDir + '/' + name; 80 | const imageInfo = await getImageInfo(file); 81 | await this.rename(file.filepath, filePath); 82 | 83 | result.push({ 84 | url: origin + (oss.local.prefix ? oss.local.prefix : '') + '/' + name, 85 | width: imageInfo.width, 86 | height: imageInfo.height, 87 | }); 88 | } 89 | return result; 90 | } 91 | 92 | private getFileType(file: EggFile) { 93 | return file.mime.split('/').pop(); 94 | } 95 | 96 | private relativeFilePath(file: EggFile) { 97 | return ( 98 | formatTimeByFormater(new Date(), 'YYYY-MM-DD') + 99 | '/' + 100 | this.ctx.user.id + 101 | now() + 102 | '.' + 103 | this.getFileType(file) 104 | ); 105 | } 106 | 107 | private readDir(path: string): Promise { 108 | return new Promise((resolve, reject) => { 109 | fs.stat(path, (err, stats) => { 110 | if (err) { 111 | reject(err); 112 | } 113 | resolve(stats); 114 | }); 115 | }); 116 | } 117 | 118 | private mkdir(path: string): Promise { 119 | return new Promise((resolve, reject) => { 120 | fs.mkdir(path, err => { 121 | if (err) { 122 | reject(err); 123 | } 124 | resolve(); 125 | }); 126 | }); 127 | } 128 | 129 | private rename(oldPath: string, path: string): Promise { 130 | return new Promise((resolve, reject) => { 131 | fs.rename(oldPath, path, err => { 132 | if (err) { 133 | reject(err); 134 | } 135 | resolve(); 136 | }); 137 | }); 138 | } 139 | } 140 | 141 | export default UploadService; 142 | -------------------------------------------------------------------------------- /app/service/user.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Context } from 'egg'; 3 | import { User } from '../model/user'; 4 | import SqlUtils from '../utils/sql'; 5 | 6 | import BaseService from '../core/service'; 7 | import { UserSearchParams } from '../controller/user'; 8 | import { Op, FindAndCountOptions } from 'sequelize'; 9 | 10 | export default class UserService extends BaseService { 11 | 12 | constructor (ctx: Context) { 13 | super(ctx); 14 | 15 | this.model = ctx.model.User; 16 | } 17 | 18 | public async findList(params: UserSearchParams) { 19 | const query: UserSearchParams = {}; 20 | 21 | if (params.name) { 22 | query.name = { 23 | [Op.like]: `%${params.name}%`, 24 | }; 25 | } 26 | if (params.id) { 27 | query.id = params.id; 28 | } 29 | 30 | if (params.account) { 31 | query.account = { 32 | [Op.like]: `%${params.account}%`, 33 | }; 34 | } 35 | 36 | if (params.mobile) { 37 | query.mobile = { 38 | [Op.like]: `%${params.mobile}%`, 39 | }; 40 | } 41 | 42 | const options: FindAndCountOptions = { 43 | include: [ 44 | { 45 | model: this.ctx.model.Role, 46 | required: false, 47 | as: 'role', 48 | ...SqlUtils.queryOptions(), 49 | }, 50 | ], 51 | }; 52 | 53 | return this.findListByKey(query, params, options); 54 | } 55 | 56 | public async findUserByAccount(account: string): Promise { 57 | 58 | const user = await this.ctx.model.User.findOne({ 59 | where: { 60 | account, 61 | }, 62 | ...SqlUtils.queryOptions(), 63 | }); 64 | 65 | if (user) { 66 | return user.get({ plain: true }) as User; 67 | } 68 | return null; 69 | } 70 | 71 | public async findUserByMobile(mobile: string): Promise { 72 | 73 | const user = await this.ctx.model.User.findOne({ 74 | where: { 75 | mobile, 76 | }, 77 | ...SqlUtils.queryOptions(), 78 | }); 79 | 80 | if (user) { 81 | return user.get({ plain: true }) as User; 82 | } 83 | return null; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /app/utils/date.ts: -------------------------------------------------------------------------------- 1 | 2 | export function formatNumber(value: number): string { 3 | const str: string = value.toString(); 4 | 5 | return str[1] ? str : `0${str}`; 6 | } 7 | 8 | export function formatTimeByFormater(value: Date, formater: string): string { 9 | const info: { 10 | [propName: string]: any, 11 | } = { 12 | YYYY: value.getFullYear(), 13 | MM: value.getMonth() + 1, 14 | DD: value.getDate(), 15 | HH: value.getHours(), 16 | mm: value.getMinutes(), 17 | SS: value.getSeconds(), 18 | }; 19 | 20 | Object.keys(info).forEach(key => { 21 | formater = formater.replace(key, formatNumber(info[key])); 22 | }); 23 | 24 | return formater; 25 | } 26 | 27 | export function now(): number { 28 | return new Date().getTime(); 29 | } 30 | 31 | export const ONE_DAY_TIME = 86400000; 32 | -------------------------------------------------------------------------------- /app/utils/image.ts: -------------------------------------------------------------------------------- 1 | import { EggFile } from 'egg-multipart'; 2 | import size = require('image-size'); 3 | 4 | interface ImageInfo { 5 | width: number; 6 | height: number; 7 | orientation?: number; 8 | type?: string; 9 | } 10 | 11 | export function getImageInfo(file: EggFile): Promise { 12 | return new Promise((resolve, reject) => { 13 | size.imageSize(file.filepath, (err, info) => { 14 | if (err) { 15 | reject(err); 16 | } 17 | resolve(info as ImageInfo); 18 | }); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /app/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function isEmptyObj(value: any): boolean { 2 | return typeof value === 'object' && Object.keys(value).length === 0; 3 | } 4 | 5 | export function isNumber(value: any): value is number { 6 | return typeof value === 'number'; 7 | } 8 | 9 | export function toNumber(value: any, defaultValue?: number): number { 10 | if (!value) { 11 | if (defaultValue) return defaultValue; 12 | } 13 | if (isNumber(value)) return value as number; 14 | const num = Number(value); 15 | if (isNumber(num)) return num; 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /app/utils/page.ts: -------------------------------------------------------------------------------- 1 | import { PageParams, PageSql, PageInfo } from '../../typings'; 2 | import { toNumber } from './index'; 3 | 4 | class Page { 5 | page: number; 6 | size: number; 7 | dataTotal: number; 8 | pageTotal: number; 9 | 10 | constructor({ page, size }: PageParams) { 11 | this.page = toNumber(page, 1); 12 | this.size = toNumber(size, 10); 13 | } 14 | 15 | setTotal(count: number) { 16 | this.dataTotal = count; 17 | 18 | this.pageTotal = Math.ceil(count / this.size); 19 | } 20 | 21 | buildOptions(): PageSql { 22 | return { 23 | limit: this.size, 24 | offset: (this.page - 1) * this.size, 25 | }; 26 | } 27 | 28 | getData(): PageInfo { 29 | return { 30 | page: this.page, 31 | size: this.size, 32 | dataTotal: this.dataTotal, 33 | pageTotal: this.pageTotal, 34 | }; 35 | } 36 | } 37 | 38 | export default Page; 39 | -------------------------------------------------------------------------------- /app/utils/random.ts: -------------------------------------------------------------------------------- 1 | export function createRandomStr (): string { 2 | return Math.random().toString().substr(2); 3 | } 4 | 5 | export function createRandomNum(max: number, min: number | undefined = 0): number { 6 | return Math.floor(Math.random() * (max - min + 1) + min); 7 | } 8 | -------------------------------------------------------------------------------- /app/utils/sql.ts: -------------------------------------------------------------------------------- 1 | import { ExcludeAttributes } from '../../typings'; 2 | import { User } from '../model/user'; 3 | 4 | export function queryOptions(): ExcludeAttributes { 5 | return { 6 | attributes: { 7 | exclude: [ 'updatedAt', 'createdAt', 'deletedAt', 'creator', 'modifier' ], 8 | }, 9 | }; 10 | } 11 | 12 | interface CreateOperation { 13 | creator: string; 14 | modifier: string; 15 | } 16 | 17 | export function createOptions(options: T, user: User): T & CreateOperation { 18 | return { 19 | ...options, 20 | creator: user.account, 21 | modifier: user.account, 22 | }; 23 | } 24 | 25 | interface UpdateOperation { 26 | modifier: string; 27 | } 28 | 29 | export function updateOptions(options: T, user: User): T & UpdateOperation { 30 | return { 31 | ...options, 32 | modifier: user.account, 33 | }; 34 | } 35 | 36 | const SqlUtils = { 37 | queryOptions, 38 | createOptions, 39 | updateOptions, 40 | }; 41 | 42 | export default SqlUtils; 43 | -------------------------------------------------------------------------------- /app/utils/user.ts: -------------------------------------------------------------------------------- 1 | import crypto = require('crypto'); 2 | import { createRandomStr } from './random'; 3 | import UserConstants from '../constants/user'; 4 | 5 | export function encodeUserPwd (pwd: string): string { 6 | return crypto.createHash('md5').update(pwd.repeat(2) + UserConstants.USER_PWD_KEY).digest('hex'); 7 | } 8 | 9 | export function createToken(userId: number): string { 10 | return crypto.createHash('md5').update(userId + createRandomStr() + new Date().getTime() + UserConstants.USER_TOKEN_KEY).digest('hex'); 11 | } 12 | 13 | export default { 14 | encodeUserPwd, 15 | createToken, 16 | }; 17 | -------------------------------------------------------------------------------- /app/utils/verify.ts: -------------------------------------------------------------------------------- 1 | 2 | export function verifyCode (code: string): boolean { 3 | return /\d{6}/.test(code); 4 | } 5 | 6 | export function verifyMobile (mobile: string): boolean { 7 | return /\d{11}/.test(mobile.trim()); 8 | } 9 | 10 | export default { 11 | verifyCode, 12 | verifyMobile, 13 | }; 14 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /config/config.default.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, PowerPartial } from 'egg'; 2 | import { AliyunSmsConfig, OssConfig } from '../typings'; 3 | 4 | export default () => { 5 | const config = {} as PowerPartial & { sms: AliyunSmsConfig, oss: OssConfig, }; 6 | 7 | // override config from framework / plugin 8 | // use for cookie sign key, should change to your own and keep security 9 | config.keys = '_1554196283322_156_xxx'; 10 | 11 | config.cluster = { 12 | listen: { 13 | port: 3300, 14 | hostname: '127.0.0.1', 15 | }, 16 | }; 17 | 18 | config.multipart = { 19 | mode: 'file', 20 | }; 21 | 22 | config.security = { 23 | xframe: { 24 | enable: false, 25 | }, 26 | csrf: { 27 | enable: false, 28 | }, 29 | }; 30 | 31 | // add your egg config in here 32 | config.middleware = [ 'errHandle', 'auth' ]; 33 | 34 | config.sequelize = { 35 | dialect: 'mysql', 36 | host: '127.0.0.1', 37 | port: 3306, 38 | username: 'react-ant-admin', 39 | password: 'xxx', 40 | database: 'react-ant-admin', 41 | logQueryParameters: true, 42 | define: { 43 | timestamps: true, 44 | underscored: true, 45 | paranoid: true, 46 | freezeTableName: true, 47 | }, 48 | }; 49 | 50 | config.redis = { 51 | client: { 52 | port: 6379, 53 | host: '127.0.0.1', 54 | password: '123456', 55 | db: 2, 56 | }, 57 | }; 58 | 59 | config.auth = { 60 | url: new Set([ '/user/login', '/user/login-mobile', '/', '/sms' ]), 61 | // ignore (ctx: Context) { 62 | // return ctx.url.indexOf('.') !== -1 63 | // } 64 | }; 65 | 66 | config.sms = { 67 | accessKeyId: 'xxxx', 68 | accessKeySecret: 'xx', 69 | endpoint: 'https://dysmsapi.aliyuncs.com', 70 | regionId: 'cn-hangzhou', 71 | verifyCode: { 72 | signName: 'xxx', 73 | templateCode: 'xxx', 74 | }, 75 | // 单个手机号每天可发送短信条数 76 | countByMobile: 10, 77 | // 单个ip每天可发送短信条数 78 | countByIp: 30, 79 | }; 80 | 81 | config.oss = { 82 | qiniu: { 83 | accessKey: 'xxx', 84 | secretKey: 'xxxx', 85 | scope: 'xxxx', 86 | host: 'xxxx', 87 | }, 88 | local: { 89 | prefix: '/public/image', 90 | dir: '/app/public/image', 91 | }, 92 | }; 93 | 94 | // the return config will combines to EggAppConfig 95 | return { 96 | ...config, 97 | }; 98 | }; 99 | -------------------------------------------------------------------------------- /config/config.local.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, PowerPartial } from 'egg'; 2 | 3 | export default () => { 4 | const config: PowerPartial = {}; 5 | config.static = { 6 | maxAge: 0, 7 | cacheControl: 'no-cache', 8 | }; 9 | return config; 10 | }; 11 | -------------------------------------------------------------------------------- /config/config.prod.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, PowerPartial } from 'egg'; 2 | 3 | export default () => { 4 | const config: PowerPartial = {}; 5 | 6 | config.static = { 7 | maxAge: 0, 8 | cacheControl: 'no-cache', 9 | buffer: false, 10 | }; 11 | 12 | config.logger = { 13 | dir: '/home/admin/logs/inyou-server', 14 | }; 15 | 16 | return config; 17 | }; 18 | -------------------------------------------------------------------------------- /config/plugin.ts: -------------------------------------------------------------------------------- 1 | import { EggPlugin } from 'egg'; 2 | 3 | const plugin: EggPlugin = { 4 | static: true, 5 | // nunjucks: { 6 | // enable: true, 7 | // package: 'egg-view-nunjucks', 8 | // }, 9 | sequelize: { 10 | enable: true, 11 | package: 'egg-sequelize', 12 | }, 13 | redis: { 14 | enable: true, 15 | package: 'egg-redis', 16 | }, 17 | routerPlus: { 18 | enable: true, 19 | package: 'egg-router-plus', 20 | }, 21 | validate: { 22 | enable: true, 23 | package: 'egg-validate', 24 | }, 25 | }; 26 | 27 | export default plugin; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ant-admin-api", 3 | "version": "1.0.0", 4 | "description": "react admin", 5 | "private": false, 6 | "egg": { 7 | "typescript": true, 8 | "declarations": true 9 | }, 10 | "scripts": { 11 | "start": "npm run ci && egg-scripts start --daemon --title=react-ant-admin-api", 12 | "stop": "egg-scripts stop --title=react-ant-admin-api", 13 | "dev": "egg-bin dev", 14 | "debug": "egg-bin debug", 15 | "test-local": "egg-bin test", 16 | "test": "npm run lint -- --fix && npm run test-local", 17 | "cov": "egg-bin cov", 18 | "tsc": "ets && tsc -p tsconfig.json", 19 | "ci": "npm run lint && npm run cov && npm run tsc", 20 | "autod": "autod", 21 | "lint": "tslint --project . -c tslint.json", 22 | "clean": "ets clean", 23 | "doc": "apidoc -i app/router -o ./app/public", 24 | "fix": "npm run lint -- --fix" 25 | }, 26 | "dependencies": { 27 | "@alicloud/pop-core": "^1.7.7", 28 | "egg": "^2.6.1", 29 | "egg-cors": "^2.2.0", 30 | "egg-multipart": "^2.9.1", 31 | "egg-redis": "^2.4.0", 32 | "egg-router-plus": "^1.3.1", 33 | "egg-scripts": "^2.6.0", 34 | "egg-sequelize": "^5.2.0", 35 | "egg-validate": "^2.0.2", 36 | "image-size": "^0.8.3", 37 | "ip": "^1.1.5", 38 | "mysql2": "^2.0.0", 39 | "qiniu": "^7.2.2" 40 | }, 41 | "devDependencies": { 42 | "@types/mocha": "^2.2.40", 43 | "@types/node": "^7.0.12", 44 | "@types/supertest": "^2.0.0", 45 | "@types/ioredis": "^4.14.5", 46 | "apidoc": "^0.17.7", 47 | "autod": "^3.1.0", 48 | "autod-egg": "^1.1.0", 49 | "egg-bin": "^4.14.0", 50 | "egg-ci": "^1.8.0", 51 | "egg-mock": "^3.16.0", 52 | "sequelize-cli": "^5.4.0", 53 | "tslib": "^1.10.0", 54 | "tslint": "^5.0.0", 55 | "tslint-config-egg": "^1.0.0", 56 | "typescript": "^3.7.2" 57 | }, 58 | "engines": { 59 | "node": ">=8.9.0" 60 | }, 61 | "ci": { 62 | "version": "8" 63 | }, 64 | "repository": { 65 | "type": "git", 66 | "url": "" 67 | }, 68 | "eslintIgnore": [ 69 | "coverage" 70 | ], 71 | "author": "", 72 | "license": "MIT" 73 | } 74 | -------------------------------------------------------------------------------- /react-ant-admin.sql: -------------------------------------------------------------------------------- 1 | 2 | SET NAMES utf8mb4; 3 | SET FOREIGN_KEY_CHECKS = 0; 4 | 5 | -- ---------------------------- 6 | -- Table structure for admin_sms_log 7 | -- ---------------------------- 8 | DROP TABLE IF EXISTS `admin_sms_log`; 9 | CREATE TABLE `admin_sms_log` ( 10 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录id', 11 | `user_id` int(10) unsigned DEFAULT '0' COMMENT '用户id', 12 | `ip` varchar(16) DEFAULT NULL COMMENT '用户ip', 13 | `biz_id` varchar(32) DEFAULT NULL COMMENT '三方流水号', 14 | `content` varchar(70) DEFAULT NULL COMMENT '短信发送内容', 15 | `code` int(6) unsigned DEFAULT NULL COMMENT '短信验证码', 16 | `tid` varchar(16) DEFAULT NULL COMMENT '模版id', 17 | `mobile` char(11) DEFAULT '' COMMENT '用户手机号', 18 | `creator` varchar(16) DEFAULT NULL, 19 | `modifier` varchar(16) DEFAULT NULL, 20 | `created_at` datetime NOT NULL, 21 | `updated_at` datetime NOT NULL, 22 | `deleted_at` datetime DEFAULT NULL, 23 | PRIMARY KEY (`id`), 24 | KEY `admin_sms_log_mobile` (`mobile`) 25 | ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4; 26 | 27 | -- ---------------------------- 28 | -- Records of admin_sms_log 29 | -- ---------------------------- 30 | BEGIN; 31 | INSERT INTO `admin_sms_log` VALUES (1, 0, '127.0.0.1', '', '', 180282, 'SMS_150741810', '15558155021', NULL, NULL, '2020-01-10 03:09:30', '2020-01-10 03:09:30', NULL); 32 | INSERT INTO `admin_sms_log` VALUES (2, 0, '127.0.0.1', '', '', 286118, 'SMS_150741810', '15558155021', NULL, NULL, '2020-01-10 03:10:38', '2020-01-10 03:10:38', '2020-01-10 03:10:44'); 33 | INSERT INTO `admin_sms_log` VALUES (3, 0, '127.0.0.1', '', '', 573672, 'SMS_150741810', '15558155021', NULL, NULL, '2020-01-10 03:13:40', '2020-01-10 03:13:40', '2020-01-10 03:13:48'); 34 | INSERT INTO `admin_sms_log` VALUES (4, 0, '172.16.237.109', '', '', 947478, 'SMS_150741810', '15558155021', NULL, NULL, '2020-01-23 02:49:22', '2020-01-23 02:49:22', NULL); 35 | INSERT INTO `admin_sms_log` VALUES (5, 0, '172.16.237.109', '', '', 929870, 'SMS_150741810', '15558155022', NULL, NULL, '2020-01-28 09:20:09', '2020-01-28 09:20:09', '2020-01-28 09:20:15'); 36 | INSERT INTO `admin_sms_log` VALUES (6, 0, '172.16.237.109', '', '', 781663, 'SMS_150741810', '15558155023', NULL, NULL, '2020-01-28 09:22:20', '2020-01-28 09:22:20', '2020-01-28 09:22:25'); 37 | COMMIT; 38 | 39 | -- ---------------------------- 40 | -- Table structure for admin_sys_menu 41 | -- ---------------------------- 42 | DROP TABLE IF EXISTS `admin_sys_menu`; 43 | CREATE TABLE `admin_sys_menu` ( 44 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '菜单id', 45 | `name` varchar(5) DEFAULT NULL COMMENT '菜单名称', 46 | `url` varchar(32) DEFAULT NULL COMMENT '菜单地址', 47 | `icon` varchar(16) DEFAULT NULL COMMENT '菜单icon', 48 | `desc` varchar(16) DEFAULT NULL COMMENT '菜单描述', 49 | `sort` int(10) unsigned DEFAULT '0' COMMENT '菜单排序', 50 | `parent_id` int(10) unsigned DEFAULT '0' COMMENT '父级菜单id', 51 | `level` int(10) unsigned DEFAULT '0' COMMENT '菜单等级', 52 | `creator` varchar(16) DEFAULT NULL, 53 | `modifier` varchar(16) DEFAULT NULL, 54 | `created_at` datetime NOT NULL, 55 | `updated_at` datetime NOT NULL, 56 | `deleted_at` datetime DEFAULT NULL, 57 | PRIMARY KEY (`id`), 58 | KEY `admin_sys_menu_parent_id` (`parent_id`) 59 | ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4; 60 | 61 | -- ---------------------------- 62 | -- Records of admin_sys_menu 63 | -- ---------------------------- 64 | BEGIN; 65 | INSERT INTO `admin_sys_menu` VALUES (1, '权限管理', '/auth', 'menu-unfold', '权限管理', 10, 0, 1, NULL, 'landluck', '2020-01-10 11:19:21', '2020-01-28 09:05:19', NULL); 66 | INSERT INTO `admin_sys_menu` VALUES (2, '菜单管理', '/auth/menu', 'menu', '菜单管理', 1, 1, 2, NULL, 'landluck', '2020-01-10 11:19:56', '2020-01-28 09:01:40', NULL); 67 | INSERT INTO `admin_sys_menu` VALUES (3, '用户管理', '/auth/user', 'user', '用户管理', 2, 1, 2, 'landluck', 'landluck', '2020-01-10 05:24:45', '2020-01-10 05:24:45', NULL); 68 | INSERT INTO `admin_sys_menu` VALUES (4, '角色管理', '/auth/role', 'team', '角色管理', 3, 1, 2, 'landluck', 'landluck', '2020-01-10 05:25:20', '2020-01-10 05:25:20', NULL); 69 | INSERT INTO `admin_sys_menu` VALUES (5, '首页', '/dashboard', 'dashboard', '首页', 1, 0, 1, 'landluck', 'landluck', '2020-01-10 05:29:40', '2020-01-25 09:13:38', NULL); 70 | INSERT INTO `admin_sys_menu` VALUES (6, '系统介绍', '/dashborad/intro', 'read', '系统介绍', 1, 5, 2, 'landluck', 'landluck', '2020-01-10 05:30:05', '2020-01-10 05:33:31', NULL); 71 | COMMIT; 72 | 73 | -- ---------------------------- 74 | -- Table structure for admin_sys_role 75 | -- ---------------------------- 76 | DROP TABLE IF EXISTS `admin_sys_role`; 77 | CREATE TABLE `admin_sys_role` ( 78 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '角色id', 79 | `name` varchar(12) DEFAULT NULL COMMENT '角色名称', 80 | `creator` varchar(16) DEFAULT NULL, 81 | `modifier` varchar(16) DEFAULT NULL, 82 | `created_at` datetime NOT NULL, 83 | `updated_at` datetime NOT NULL, 84 | `deleted_at` datetime DEFAULT NULL, 85 | PRIMARY KEY (`id`) 86 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; 87 | 88 | -- ---------------------------- 89 | -- Records of admin_sys_role 90 | -- ---------------------------- 91 | BEGIN; 92 | INSERT INTO `admin_sys_role` VALUES (1, '超级管理员', NULL, NULL, '2020-01-10 11:17:34', '2020-01-10 11:17:38', NULL); 93 | COMMIT; 94 | 95 | -- ---------------------------- 96 | -- Table structure for admin_sys_role_menu 97 | -- ---------------------------- 98 | DROP TABLE IF EXISTS `admin_sys_role_menu`; 99 | CREATE TABLE `admin_sys_role_menu` ( 100 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录id', 101 | `role_id` int(10) unsigned DEFAULT NULL COMMENT '角色id', 102 | `menu_id` int(10) unsigned DEFAULT NULL COMMENT '菜单id', 103 | `creator` varchar(16) DEFAULT NULL, 104 | `modifier` varchar(16) DEFAULT NULL, 105 | `created_at` datetime NOT NULL, 106 | `updated_at` datetime NOT NULL, 107 | `deleted_at` datetime DEFAULT NULL, 108 | PRIMARY KEY (`id`), 109 | KEY `admin_sys_role_menu_role_id` (`role_id`), 110 | KEY `admin_sys_role_menu_menu_id` (`menu_id`) 111 | ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4; 112 | 113 | -- ---------------------------- 114 | -- Records of admin_sys_role_menu 115 | -- ---------------------------- 116 | BEGIN; 117 | INSERT INTO `admin_sys_role_menu` VALUES (1, 1, 1, NULL, NULL, '2020-01-10 11:20:17', '2020-01-10 11:20:26', NULL); 118 | INSERT INTO `admin_sys_role_menu` VALUES (2, 1, 2, NULL, NULL, '2020-01-10 11:20:35', '2020-01-10 11:20:39', NULL); 119 | INSERT INTO `admin_sys_role_menu` VALUES (3, 1, 4, NULL, NULL, '2020-01-10 13:25:42', '2020-01-10 13:25:47', NULL); 120 | INSERT INTO `admin_sys_role_menu` VALUES (4, 1, 3, 'landluck', 'landluck', '2020-01-10 05:26:08', '2020-01-10 05:26:08', NULL); 121 | INSERT INTO `admin_sys_role_menu` VALUES (5, 1, 5, 'landluck', 'landluck', '2020-01-10 05:30:14', '2020-01-10 05:30:14', NULL); 122 | INSERT INTO `admin_sys_role_menu` VALUES (6, 1, 6, 'landluck', 'landluck', '2020-01-10 05:30:14', '2020-01-10 05:30:14', NULL); 123 | COMMIT; 124 | 125 | -- ---------------------------- 126 | -- Table structure for admin_user 127 | -- ---------------------------- 128 | DROP TABLE IF EXISTS `admin_user`; 129 | CREATE TABLE `admin_user` ( 130 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户id', 131 | `name` varchar(8) DEFAULT NULL COMMENT '用户名称', 132 | `account` varchar(16) DEFAULT NULL COMMENT '用户账号', 133 | `avatar` varchar(100) DEFAULT NULL COMMENT '用户头像', 134 | `password` char(32) DEFAULT NULL COMMENT '用户密码', 135 | `mobile` char(11) DEFAULT '' COMMENT '用户手机号', 136 | `role_id` int(10) unsigned DEFAULT '1' COMMENT '角色id', 137 | `status` tinyint(1) unsigned DEFAULT '1' COMMENT '角色状态:1 正常 0 禁用', 138 | `creator` varchar(16) DEFAULT NULL, 139 | `modifier` varchar(16) DEFAULT NULL, 140 | `created_at` datetime NOT NULL, 141 | `updated_at` datetime NOT NULL, 142 | `deleted_at` datetime DEFAULT NULL, 143 | PRIMARY KEY (`id`), 144 | KEY `admin_user_mobile` (`mobile`), 145 | KEY `admin_user_account` (`account`), 146 | KEY `admin_user_role_id` (`role_id`) 147 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; 148 | 149 | -- ---------------------------- 150 | -- Records of admin_user 151 | -- ---------------------------- 152 | BEGIN; 153 | INSERT INTO `admin_user` VALUES (1, 'admin1', 'admin1', 'http://image.landluck.com.cn/2020-01-10/11578634065020.jpeg', '363b4089e7829c514b33f966300d60ed', '15558155555', 1, 1, '', 'admin', '2020-01-10 03:13:48', '2020-01-28 09:07:07', NULL); 154 | COMMIT; 155 | 156 | SET FOREIGN_KEY_CHECKS = 1; 157 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/landluck/react-ant-admin-api/6678d8577b2fc94808a81525f51c40d355b0707a/test/utils.ts -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "strict": true, 7 | "noImplicitAny": false, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "charset": "utf8", 11 | "allowJs": false, 12 | "pretty": true, 13 | "noEmitOnError": false, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "allowUnreachableCode": false, 17 | "allowUnusedLabels": false, 18 | "strictPropertyInitialization": false, 19 | "noFallthroughCasesInSwitch": true, 20 | "skipLibCheck": true, 21 | "skipDefaultLibCheck": true, 22 | "inlineSourceMap": true, 23 | "importHelpers": true 24 | }, 25 | "exclude": [ 26 | "app/public", 27 | "app/views", 28 | "node_modules*" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-egg"] 3 | } 4 | -------------------------------------------------------------------------------- /typings/app/controller/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExportIndex from '../../../app/controller/index'; 6 | import ExportMenu from '../../../app/controller/menu'; 7 | import ExportRole from '../../../app/controller/role'; 8 | import ExportSms from '../../../app/controller/sms'; 9 | import ExportUpload from '../../../app/controller/upload'; 10 | import ExportUser from '../../../app/controller/user'; 11 | 12 | declare module 'egg' { 13 | interface IController { 14 | index: ExportIndex; 15 | menu: ExportMenu; 16 | role: ExportRole; 17 | sms: ExportSms; 18 | upload: ExportUpload; 19 | user: ExportUser; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /typings/app/extend/context.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExtendContext from '../../../app/extend/context'; 6 | type ExtendContextType = typeof ExtendContext; 7 | declare module 'egg' { 8 | interface Context extends ExtendContextType { } 9 | } -------------------------------------------------------------------------------- /typings/app/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | export * from 'egg'; 6 | export as namespace Egg; 7 | -------------------------------------------------------------------------------- /typings/app/middleware/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExportAuth from '../../../app/middleware/auth'; 6 | import ExportErrHandle from '../../../app/middleware/errHandle'; 7 | 8 | declare module 'egg' { 9 | interface IMiddleware { 10 | auth: typeof ExportAuth; 11 | errHandle: typeof ExportErrHandle; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /typings/app/model/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExportMenu from '../../../app/model/menu'; 6 | import ExportRole from '../../../app/model/role'; 7 | import ExportRoleMenu from '../../../app/model/roleMenu'; 8 | import ExportSms from '../../../app/model/sms'; 9 | import ExportUser from '../../../app/model/user'; 10 | 11 | declare module 'egg' { 12 | interface IModel { 13 | Menu: ReturnType; 14 | Role: ReturnType; 15 | RoleMenu: ReturnType; 16 | Sms: ReturnType; 17 | User: ReturnType; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /typings/app/service/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExportAliyun from '../../../app/service/aliyun'; 6 | import ExportMenu from '../../../app/service/menu'; 7 | import ExportProduct from '../../../app/service/product'; 8 | import ExportQiniu from '../../../app/service/qiniu'; 9 | import ExportRedis from '../../../app/service/redis'; 10 | import ExportRequest from '../../../app/service/request'; 11 | import ExportRole from '../../../app/service/role'; 12 | import ExportSms from '../../../app/service/sms'; 13 | import ExportTask from '../../../app/service/task'; 14 | import ExportUpload from '../../../app/service/upload'; 15 | import ExportUser from '../../../app/service/user'; 16 | 17 | declare module 'egg' { 18 | interface IService { 19 | aliyun: ExportAliyun; 20 | menu: ExportMenu; 21 | product: ExportProduct; 22 | qiniu: ExportQiniu; 23 | redis: ExportRedis; 24 | request: ExportRequest; 25 | role: ExportRole; 26 | sms: ExportSms; 27 | task: ExportTask; 28 | upload: ExportUpload; 29 | user: ExportUser; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /typings/config/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import { EggAppConfig } from 'egg'; 6 | import ExportConfigDefault from '../../config/config.default'; 7 | type ConfigDefault = ReturnType; 8 | type NewEggAppConfig = ConfigDefault; 9 | declare module 'egg' { 10 | interface EggAppConfig extends NewEggAppConfig { } 11 | } -------------------------------------------------------------------------------- /typings/config/plugin.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.6 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import 'egg-onerror'; 6 | import 'egg-session'; 7 | import 'egg-i18n'; 8 | import 'egg-watcher'; 9 | import 'egg-multipart'; 10 | import 'egg-security'; 11 | import 'egg-development'; 12 | import 'egg-logrotator'; 13 | import 'egg-schedule'; 14 | import 'egg-static'; 15 | import 'egg-jsonp'; 16 | import 'egg-view'; 17 | import 'egg-sequelize'; 18 | import 'egg-redis'; 19 | import 'egg-router-plus'; 20 | import 'egg-validate'; 21 | import { EggPluginItem } from 'egg'; 22 | declare module 'egg' { 23 | interface EggPlugin { 24 | onerror?: EggPluginItem; 25 | session?: EggPluginItem; 26 | i18n?: EggPluginItem; 27 | watcher?: EggPluginItem; 28 | multipart?: EggPluginItem; 29 | security?: EggPluginItem; 30 | development?: EggPluginItem; 31 | logrotator?: EggPluginItem; 32 | schedule?: EggPluginItem; 33 | static?: EggPluginItem; 34 | jsonp?: EggPluginItem; 35 | view?: EggPluginItem; 36 | sequelize?: EggPluginItem; 37 | redis?: EggPluginItem; 38 | routerPlus?: EggPluginItem; 39 | validate?: EggPluginItem; 40 | } 41 | } -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import "egg"; 2 | 3 | declare module "egg" {} 4 | 5 | export interface ExcludeAttributes { 6 | attributes: { 7 | exclude: Array; 8 | }; 9 | } 10 | 11 | export interface CheckRules {} 12 | 13 | export interface AliyunSmsConfig { 14 | accessKeyId: string; 15 | accessKeySecret: string; 16 | endpoint: string; 17 | regionId: string; 18 | 19 | countByMobile: number; 20 | countByIp: number; 21 | 22 | verifyCode: { 23 | signName: string; 24 | templateCode: string; 25 | }; 26 | } 27 | 28 | export interface OssConfig { 29 | qiniu?: QiniuConfig; 30 | local?: LocalConfig; 31 | } 32 | 33 | export interface LocalConfig { 34 | host?: string; 35 | dir: string; 36 | prefix?: string 37 | } 38 | 39 | export interface QiniuConfig { 40 | accessKey: string; 41 | secretKey: string; 42 | scope: string; 43 | host: string; 44 | } 45 | 46 | export interface PageParams { 47 | page?: number; 48 | size?: number; 49 | } 50 | 51 | export interface PageInfo { 52 | size: number; 53 | page: number; 54 | dataTotal: number; 55 | pageTotal: number; 56 | } 57 | 58 | export interface PageSql { 59 | limit: number; 60 | offset: number; 61 | } 62 | -------------------------------------------------------------------------------- /typings/iny/index.ts: -------------------------------------------------------------------------------- 1 | export interface ApiResponse { 2 | code: number; 3 | data: T; 4 | msg: string; 5 | } 6 | --------------------------------------------------------------------------------