├── .cz-config.js ├── .dockerignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode ├── launch.json └── tasks.json ├── Dockerfile ├── LICENSE ├── README.md ├── default.env ├── nest-cli.json ├── package.json ├── public ├── assets │ └── logo.svg └── views │ └── index.ejs ├── src ├── app.controller.ts ├── app.module.ts ├── assets │ ├── banner.txt │ └── email-template │ │ ├── notice.ejs │ │ └── register.ejs ├── config │ ├── env │ │ ├── databse.config.ts │ │ ├── email.config.ts │ │ ├── jwt.config.ts │ │ ├── redis.config.ts │ │ └── swagger.config.ts │ └── module │ │ ├── cmomon.text.ts │ │ ├── get-banner.ts │ │ ├── log4js.ts │ │ └── redis-temp.ts ├── controllers │ ├── account │ │ └── account.controller.ts │ └── user │ │ └── user.controller.ts ├── decorators │ ├── current.user.ts │ └── ip.address.ts ├── dtos │ ├── common.dto.ts │ └── user │ │ └── user.dto.ts ├── entities │ ├── public.entity.ts │ ├── user-code.entity.ts │ └── user.entity.ts ├── enum │ ├── api-code.enum.ts │ ├── common.enum.ts │ └── user.enum.ts ├── exception │ └── api-exception.ts ├── filters │ └── http-exception.filter.ts ├── guard │ └── auth.guard.ts ├── interceptor │ ├── logger.interceptor.ts │ └── transform.interceptor.ts ├── interfaces │ ├── account.interface.ts │ ├── common.interface.ts │ ├── http.interface.ts │ └── user.interface.ts ├── main.ts ├── modules │ ├── account │ │ └── account.module.ts │ ├── base │ │ ├── config.module.ts │ │ ├── database.module.ts │ │ ├── email.module.ts │ │ ├── entitiy.module.ts │ │ └── redis.module.ts │ ├── common │ │ ├── code.module.ts │ │ ├── common.module.ts │ │ ├── jwt.module.ts │ │ └── redis.module.ts │ └── user │ │ └── user.module.ts ├── pipes │ ├── parse.page.pipe.ts │ └── validation.pipe.ts ├── services │ ├── account │ │ └── account.service.ts │ ├── common │ │ ├── code │ │ │ ├── email-code.service.ts │ │ │ └── img-captcha.service.ts │ │ ├── jwt │ │ │ └── jwt.service.ts │ │ └── redis │ │ │ ├── redis-client.service.ts │ │ │ └── redis.cache.service.ts │ └── user │ │ └── user.service.ts └── utils │ ├── common.ts │ ├── format-date.ts │ ├── log4js.ts │ └── terminal-help-text-console.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.cz-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | types: [ 5 | { 6 | value: 'feat', 7 | name: '✨ feat: 新功能', 8 | }, 9 | { 10 | value: 'fix', 11 | name: '🐛 fix: 修复bug', 12 | }, 13 | { 14 | value: 'refactor', 15 | name: '♻️ refactor: 代码重构(既不是新功能也不是改bug)', 16 | }, 17 | { 18 | value: 'chore', 19 | name: '🎫 chore: 修改流程配置', 20 | }, 21 | { 22 | value: 'docs', 23 | name: '📝 docs: 修改了文档', 24 | }, 25 | { 26 | value: 'test', 27 | name: '✅ test: 更新了测试用例', 28 | }, 29 | { 30 | value: 'style', 31 | name: '💄 style: 修改了样式文件', 32 | }, 33 | { 34 | value: 'perf', 35 | name: '⚡️ perf: 新能优化', 36 | }, 37 | { 38 | value: 'revert', 39 | name: '⏪ revert: 回退提交', 40 | }, 41 | ], 42 | scopes: [], 43 | allowCustomScopes: true, 44 | allowBreakingChanges: ['feat', 'fix'], 45 | subjectLimit: 50, 46 | messages: { 47 | type: '请选择你本次改动的修改类型', 48 | customScope: '\n请明确本次改动的范围(可填):', 49 | subject: '简短描述本次改动:\n', 50 | body: '详细描述本次改动 (可填). 使用 "|" 换行:\n', 51 | breaking: '请列出任何 BREAKING CHANGES (可填):\n', 52 | footer: '请列出本次改动关闭的ISSUE (可填). 比如: #31, #34:\n', 53 | confirmCommit: '你确定提交本次改动吗?', 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'prettier/@typescript-eslint', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | 36 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /.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": "启动程序", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/start", 15 | "preLaunchTask": "nest debug", 16 | "outFiles": [ 17 | "${workspaceFolder}/dist/**/*.js" 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "start:debug", 7 | "group": "rebuild", 8 | "problemMatcher": [], 9 | "label": "nest debug", 10 | "detail": "nest start --debug --watch" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | WORKDIR /app/src 4 | 5 | COPY ./ /app/src 6 | 7 | RUN npm install && npm run build && rm node_modules -rf 8 | 9 | EXPOSE 3000 10 | 11 | CMD ["npm","run","start"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 w候人兮猗 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Fast-nest-temp 2 | 3 | ## 介绍 4 | 5 | 基于`Nest.js@7.x`快速构建 Web 应用 6 | 7 | ### 依赖 8 | 9 | - @nestjs/core 7.5.1 核心包 10 | - @nestjs/config 环境变量治理 11 | - @nestjs/swagger 生成接口文档 12 | - swagger-ui-express 装@nestjs/swagger 必装的包 处理接口文档样式 13 | - joi 校验参数 14 | - log4js 日志处理 15 | - helmet 处理基础 web 漏洞 16 | - compression 服务端压缩中间件 17 | - express-rate-limit 请求次数限制 18 | - typeorm 数据库 orm 框架 19 | - @nestjs/typeorm nest typeorm 集成 20 | - ejs 模版引擎 21 | - class-validator 校验参数 22 | - ioredis redis 客户端 23 | - nestjs-redis nest redis 配置模块 24 | - uuid uuid 生成器 25 | - @nestjs-modules/mailer 邮箱发送 26 | 27 | 28 | 29 | 30 | ### 如何使用 31 | 32 | - 复制根目录下`default.env`文件,重命名为`.env`文件,修改其配置 33 | - `yarn start:dev` 开始开发 34 | - `yarn start:debug` 开始debug模式 35 | - `yarn commit` 用于提交代码,默认遵循业内规则 36 | - `yarn start` 启动项目 37 | 38 | ### 约束 39 | 40 | - 接口返回值约束 `interface IHttpResponse` 41 | 42 | ```json 43 | { 44 | "result": null, 45 | "message": "", // 消息提示,错误消息 46 | "code": 0, // 业务状态码 47 | "path": "/url", // 接口请求地址 48 | "method": "GET", // 接口方法 49 | "timestamp": 1 // 接口响应时间 50 | } 51 | ``` 52 | 53 | - 接口 `HttpExceptionFilter` 过滤器 54 | - 业务状态码与`Http StatusCode`约定 55 | 56 | 无论接口是否异常或错误,`Http StatusCode`都为`200` 57 | 58 | ### 管道 59 | 60 | - 管道 `ParsePagePipe` 校验分页入参 61 | - 管道 `ValidationPipe` 结合 `DTO` 校验入参 62 | 63 | ### 过滤器 64 | 65 | - `HttpExceptionFilter` 异常过滤器 66 | 67 | 默认处理所有异常,返回`Http StatusCode`都为 200 68 | 69 | ### 拦截器 70 | 71 | - `LoggingInterceptor` 日志拦截器 72 | 73 | 处理日志,结合`log4js`使用 74 | 75 | - `TransformInterceptor` 数据转换拦截器 76 | 77 | 处理返回结果,返回结果如`THttpResponse` 78 | 79 | ### 日志处理 80 | 81 | - 默认输出 `logs/access logs/app-out logs/errors` 82 | - 可修改 `module/log4js` 下配置 83 | 84 | ### 守卫 85 | 86 | - `AuthGuard` 授权守卫 87 | 88 | 对于要登录才能访问的接口,请使用该守卫进行校验 89 | 90 | 在请求头中设置 `AuthToken : tokenxxx` 91 | 92 | ### 装饰器 93 | 94 | - `@CurrentUser` 95 | 96 | 获取当前登录用户信息 97 | 98 | ### 常见问题 99 | 100 | - 如何修改接口文档地址 101 | 102 | 设置`.env`文件内相应环境变量 103 | 104 | - 如何修改启动 banner 105 | 106 | 目前启动`banner`读取的是`src/assets/banner.txt`,自行修改该文件即可 107 | 108 | - 使用 log4js 作为默认日志库 109 | 110 | ```typescript 111 | const app = await NestFactory.create(AppModule, { 112 | logger: new Logger(), 113 | }); 114 | ``` 115 | 116 | 通过重写`LoggerService`上的相关方法,实现 `Nest` 日志自定义 117 | 118 | - 邮箱配置提示报错 119 | 120 | 因邮箱没区分环境,默认使用了一份配置,所以把邮箱配置忽略上传了,可参考根目录下 `default.email.env` 121 | 在项目`condif/env/` 下新建`email.env`文件 122 | 123 | - 验证码 124 | 125 | 简单起见,登录验证码直接使用图形验证码+Redis实现 126 | 注册验证码使用邮箱服务+mysql code表实现,为了安全建议邮箱验证码也使用Redis实现 127 | 128 | - 邮箱模版提示未找到文件 129 | 130 | 需要修改`nest-cli.json`配置`assets`属性 131 | 132 | 如: 133 | 134 | ```json 135 | { 136 | "collection": "@nestjs/schematics", 137 | "sourceRoot": "src", 138 | "compilerOptions": { 139 | "assets": [ 140 | { 141 | "include": "assets/email-template/**/*", 142 | "watchAssets": true 143 | } 144 | ] 145 | } 146 | } 147 | ``` 148 | -------------------------------------------------------------------------------- /default.env: -------------------------------------------------------------------------------- 1 | # ------- 环境变量模版 --------- 2 | 3 | # 服务启动端口 4 | SERVE_LISTENER_PORT=3000 5 | 6 | # Swagger 文档相关 7 | SWAGGER_UI_TITLE = Fast-nest-temp 接口文档 8 | SWAGGER_UI_TITLE_DESC = 接口文档 9 | SWAGGER_API_VERSION = 0.0.1 10 | SWAGGER_SETUP_PATH = api-docs 11 | SWAGGER_ENDPOINT_PREFIX = nest_api 12 | 13 | 14 | # 开发模式相关 15 | NODE_ENV=development 16 | 17 | # 应用配置 18 | 19 | # 数据库相关 20 | DB_TYPE = mysql 21 | DB_HOST = 127.0.0.1 22 | DB_PORT = 3306 23 | DB_DATABASE = fast_nest 24 | DB_USERNAME = root 25 | DB_PASSWORD = 123456 26 | DB_SYNCHRONIZE = 1 27 | DB_LOGGING = 1 28 | DB_TABLE_PREFIX = t_ 29 | 30 | # Redis相关 31 | REDIS_HOST = localhost 32 | REDIS_PORT = 6379 33 | REDIS_PASSWORD = 34 | 35 | # Token相关 36 | TOKEN_SECRET = secret 37 | TOKEN_EXPIRES = 7d 38 | 39 | # Email相关 40 | EMAIL_HOST = smtp.126.com 41 | EMAIL_PORT = 465 42 | EAMIL_AUTH_USER = xxxxx 43 | EMAIL_AUTH_PASSWORD = xxxxx 44 | EMAIL_FROM = "FAST_NEST_TEMP ROBOT" -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "assets": [ 6 | { 7 | "include": "assets/**/*", 8 | "watchAssets": true 9 | } 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-nest-temp", 3 | "version": "0.0.1", 4 | "author": "ahwgs ", 5 | "license": "MIT", 6 | "description": "", 7 | "private": true, 8 | "scripts": { 9 | "start": "node dist/main.js", 10 | "start:dev": "npm run prebuild && nest start --watch", 11 | "start:debug": "nest start --debug --watch", 12 | "prebuild": "rimraf dist", 13 | "build": "nest build", 14 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "commit": "git cz" 17 | }, 18 | "dependencies": { 19 | "@nestjs-modules/mailer": "^1.5.1", 20 | "@nestjs/common": "^7.5.1", 21 | "@nestjs/config": "^0.5.0", 22 | "@nestjs/core": "^7.5.1", 23 | "@nestjs/platform-express": "^7.5.1", 24 | "@nestjs/swagger": "^4.7.3", 25 | "@nestjs/typeorm": "7.1.4", 26 | "add": "^2.0.6", 27 | "bcrypt": "^5.0.0", 28 | "class-transformer": "^0.3.1", 29 | "class-validator": "^0.12.2", 30 | "compression": "^1.7.4", 31 | "ejs": "^3.1.5", 32 | "express-rate-limit": "^5.1.3", 33 | "helmet": "^4.2.0", 34 | "ioredis": "^4.19.2", 35 | "joi": "^17.3.0", 36 | "jsonwebtoken": "^8.5.1", 37 | "lodash": "^4.17.20", 38 | "log4js": "^6.3.0", 39 | "moment": "^2.29.1", 40 | "mysql": "^2.18.1", 41 | "nestjs-redis": "^1.2.8", 42 | "nodemailer": "^6.4.16", 43 | "reflect-metadata": "^0.1.13", 44 | "request-ip": "^2.1.3", 45 | "rimraf": "^3.0.2", 46 | "rxjs": "^6.6.3", 47 | "stacktrace-js": "^2.0.2", 48 | "svg-captcha": "^1.4.0", 49 | "swagger-ui-express": "^4.1.4", 50 | "typeorm": "0.2.28", 51 | "uuid": "^8.3.1", 52 | "yarn": "^1.22.10" 53 | }, 54 | "devDependencies": { 55 | "@commitlint/cli": "^11.0.0", 56 | "@commitlint/config-conventional": "^11.0.0", 57 | "@nestjs/cli": "^7.5.1", 58 | "@nestjs/schematics": "^7.1.3", 59 | "@nestjs/testing": "^7.5.1", 60 | "@types/ejs": "^3.0.5", 61 | "@types/express": "^4.17.8", 62 | "@types/hapi__joi": "^17.1.6", 63 | "@types/jsonwebtoken": "^8.5.0", 64 | "@types/lodash": "^4.14.165", 65 | "@types/node": "^14.14.6", 66 | "@types/supertest": "^2.0.10", 67 | "@typescript-eslint/eslint-plugin": "^4.6.1", 68 | "@typescript-eslint/parser": "^4.6.1", 69 | "commitizen": "^4.2.2", 70 | "cross-env": "^7.0.2", 71 | "cz-customizable": "^6.3.0", 72 | "eslint": "^7.12.1", 73 | "eslint-config-prettier": "^6.15.0", 74 | "eslint-plugin-prettier": "^3.1.4", 75 | "husky": "^4.3.0", 76 | "lint-staged": "^10.5.1", 77 | "prettier": "^2.1.2", 78 | "supertest": "^6.0.0", 79 | "ts-loader": "^8.0.8", 80 | "ts-node": "^9.0.0", 81 | "tsconfig-paths": "^3.9.0", 82 | "typescript": "^4.0.5" 83 | }, 84 | "husky": { 85 | "hooks": { 86 | "pre-commit": "lint-staged", 87 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS " 88 | } 89 | }, 90 | "lint-staged": { 91 | "src/**/*.{js,ts,tsx,jsx}": [ 92 | "prettier --write", 93 | "eslint --fix" 94 | ] 95 | }, 96 | "config": { 97 | "commitizen": { 98 | "path": "node_modules/cz-customizable" 99 | } 100 | }, 101 | "commitlint": { 102 | "extends": [ 103 | "@commitlint/config-conventional" 104 | ] 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /public/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 22 | 23 | 25 | 26 | 28 | image/svg+xml 29 | 31 | 32 | 33 | 34 | 35 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 |

<%= result %>

11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Render } from '@nestjs/common'; 2 | @Controller() 3 | export class AppController { 4 | @Get() 5 | @Render('index') 6 | root() { 7 | return 'Hello!'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AccountModule } from './modules/account/account.module'; 4 | import { CustomConfigModule } from './modules/base/config.module'; 5 | import { DataBaseModule } from './modules/base/database.module'; 6 | import { EmailModule } from './modules/base/email.module'; 7 | import { CustomRedisModule } from './modules/base/redis.module'; 8 | import { CommonModule } from './modules/common/common.module'; 9 | import { UserModule } from './modules/user/user.module'; 10 | 11 | @Module({ 12 | imports: [ 13 | CustomConfigModule, 14 | DataBaseModule, 15 | CustomRedisModule, 16 | EmailModule, 17 | CommonModule, 18 | UserModule, 19 | AccountModule, 20 | ], 21 | controllers: [AppController], 22 | }) 23 | export class AppModule {} 24 | -------------------------------------------------------------------------------- /src/assets/banner.txt: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////// 2 | // _ooOoo_ // 3 | // o8888888o // 4 | // 88" . "88 // 5 | // (| ^_^ |) // 6 | // O\ = /O // 7 | // ____/`---'\____ // 8 | // .' \\| |// `. // 9 | // / \\||| : |||// \ // 10 | // / _||||| -:- |||||- \ // 11 | // | | \\\ - /// | | // 12 | // | \_| ''\---/'' | | // 13 | // \ .-\__ `-` ___/-. / // 14 | // ___`. .' /--.--\ `. . ___ // 15 | // ."" '< `.___\_<|>_/___.' >'"". // 16 | // | | : `- \`.;`\ _ /`;.`/ - ` : | | // 17 | // \ \ `-. \_ __\ /__ _/ .-` / / // 18 | // ========`-.____`-.___\_____/___.-`____.-'======== // 19 | // `=---=' // 20 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // 21 | // 佛祖保佑 永无BUG 永不修改 // 22 | //////////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /src/assets/email-template/notice.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |

Welcome, Account <%= locals.account %> registration is successful

10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/email-template/register.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |

Welcome, your register code is <%= locals.code %>

10 | 11 | 12 | -------------------------------------------------------------------------------- /src/config/env/databse.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 数据库配置 3 | * @Author: ahwgs 4 | * @Date: 2020-11-20 20:07:54 5 | * @Last Modified by: ahwgs 6 | * @Last Modified time: 2020-11-30 14:38:52 7 | */ 8 | 9 | import { registerAs } from '@nestjs/config'; 10 | import * as path from 'path'; 11 | export type DataBaseType = 'mysql' | 'mariadb'; 12 | 13 | export interface EnvDataBaseOptions { 14 | /** 15 | * 数据库类型 16 | */ 17 | type: DataBaseType; 18 | /** 19 | * 数据库主机地址 20 | */ 21 | host: string; 22 | /** 23 | * 数据库端口 24 | */ 25 | port: number | string; 26 | 27 | /** 28 | * 数据库用户名 29 | */ 30 | username: string; 31 | 32 | /** 33 | * 数据库密码 34 | */ 35 | password: string; 36 | /** 37 | * 数据库名 38 | */ 39 | database: string; 40 | /** 41 | * 实体路径 42 | */ 43 | entities: any[]; 44 | 45 | /** 46 | * 日志是否开启 执行sql语句时候输出原生sql 47 | */ 48 | logging: string; 49 | 50 | /** 51 | * 是否同步true表示会自动将src/entity里面定义的数据模块同步到数据库生成数据表(已经存在的表的时候再运行会报错) 52 | */ 53 | synchronize: string; 54 | /** 55 | * 实体表 公共前缀 56 | */ 57 | entityPrefix: string; 58 | } 59 | 60 | // 实体文件应该使用js 61 | const entitiesPath = path.resolve('./**/*.entity.js'); 62 | 63 | export default registerAs( 64 | 'EnvDataBaseOptions', 65 | (): EnvDataBaseOptions => ({ 66 | type: process.env.DB_TYPE as DataBaseType, 67 | host: process.env.DB_HOST, 68 | port: process.env.DB_PORT, 69 | username: process.env.DB_USERNAME, 70 | password: process.env.DB_PASSWORD, 71 | database: process.env.DB_DATABASE, 72 | entities: [entitiesPath], 73 | logging: process.env.DB_LOGGING, 74 | synchronize: process.env.DB_SYNCHRONIZE, 75 | entityPrefix: process.env.DB_TABLE_PREFIX, 76 | }), 77 | ); 78 | -------------------------------------------------------------------------------- /src/config/env/email.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-03 17:08:08 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-04 00:22:02 6 | */ 7 | import { registerAs } from '@nestjs/config'; 8 | export interface EnvEmailOptions { 9 | host: string; 10 | port: string; 11 | user: string; 12 | password: string; 13 | from: string; 14 | } 15 | export default registerAs( 16 | 'EnvEmailOptions', 17 | (): EnvEmailOptions => ({ 18 | host: process.env.EMAIL_HOST, 19 | port: process.env.EMAIL_PORT, 20 | user: process.env.EAMIL_AUTH_USER, 21 | password: process.env.EMAIL_AUTH_PASSWORD, 22 | from: process.env.EMAIL_FROM, 23 | }), 24 | ); 25 | -------------------------------------------------------------------------------- /src/config/env/jwt.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-03 17:08:08 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-04 00:22:02 6 | */ 7 | import { registerAs } from '@nestjs/config'; 8 | export interface EnvJwtOptions { 9 | secret: string; 10 | expiresIn: string; 11 | } 12 | export default registerAs( 13 | 'EnvJwtOptions', 14 | (): EnvJwtOptions => ({ 15 | secret: process.env.TOKEN_SECRET, 16 | expiresIn: process.env.TOKEN_EXPIRES, 17 | }), 18 | ); 19 | -------------------------------------------------------------------------------- /src/config/env/redis.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-03 17:08:08 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-03 17:13:57 6 | */ 7 | import { registerAs } from '@nestjs/config'; 8 | export interface EnvRedisOptions { 9 | host: string; 10 | port: string | number; 11 | password: string; 12 | } 13 | export default registerAs( 14 | 'EnvRedisOptions', 15 | (): EnvRedisOptions => ({ 16 | host: process.env.REDIS_HOST, 17 | port: process.env.REDIS_PORT, 18 | password: process.env.REDIS_PASSWORD || '', 19 | }), 20 | ); 21 | -------------------------------------------------------------------------------- /src/config/env/swagger.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-11-16 14:50:29 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-11-30 14:38:59 6 | */ 7 | import { registerAs } from '@nestjs/config'; 8 | const VERSION = process.env.npm_package_version; 9 | export interface EnvSwaggerOptions { 10 | title: string; 11 | setupUrl: string; 12 | desc?: string; 13 | prefix: string; 14 | version: string; 15 | } 16 | export default registerAs( 17 | 'EnvSwaggerOptions', 18 | (): EnvSwaggerOptions => ({ 19 | title: process.env.SWAGGER_UI_TITLE, 20 | desc: process.env.SWAGGER_UI_TITLE_DESC, 21 | version: VERSION || process.env.SWAGGER_API_VERSION, 22 | setupUrl: process.env.SWAGGER_SETUP_PATH, 23 | prefix: process.env.SWAGGER_ENDPOINT_PREFIX, 24 | }), 25 | ); 26 | -------------------------------------------------------------------------------- /src/config/module/cmomon.text.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 公共文案 3 | * @Author: ahwgs 4 | * @Date: 2020-11-20 20:04:50 5 | * @Last Modified by: ahwgs 6 | * @Last Modified time: 2020-12-02 18:07:12 7 | */ 8 | 9 | interface IText { 10 | [propName: string]: string; 11 | } 12 | 13 | export const CommonText: IText = { 14 | REQUEST_ERROR: '请求失败', 15 | PARAMES_MUST_NUM: '需为整数,当前输入的为:', 16 | FRONT_DATE: '开始时间', 17 | END_DATE: '结束时间', 18 | REGISTER_CODE: '[Fast-Nest-Temp] Your Register Email Code', 19 | REGISTER_SUCCESS: '[Fast-Nest-Temp] Account registration is successful', 20 | }; 21 | -------------------------------------------------------------------------------- /src/config/module/get-banner.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 获取运行banner 3 | * @Author: ahwgs 4 | * @Date: 2020-11-20 20:05:09 5 | * @Last Modified by: ahwgs 6 | * @Last Modified time: 2020-11-20 20:05:30 7 | */ 8 | 9 | import * as fs from 'fs'; 10 | import * as path from 'path'; 11 | const bannerPath = path.join(process.cwd(), 'src/assets/banner.txt'); 12 | 13 | const getBanner = async () => { 14 | try { 15 | const result = await fs.readFileSync(bannerPath, 'utf-8'); 16 | return result; 17 | } catch (e) { 18 | console.log(e); 19 | } 20 | }; 21 | 22 | export const BannerLog = getBanner(); 23 | -------------------------------------------------------------------------------- /src/config/module/log4js.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-11-25 02:40:20 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-11-25 02:43:09 6 | */ 7 | const log4jsConfig = { 8 | appenders: { 9 | console: { 10 | type: 'console', // 会打印到控制台 11 | }, 12 | access: { 13 | type: 'dateFile', // 会写入文件,并按照日期分类 14 | filename: `logs/access/access.log`, // 日志文件名 15 | alwaysIncludePattern: true, 16 | pattern: 'yyyy-MM-dd', 17 | daysToKeep: 60, 18 | numBackups: 3, 19 | category: 'http', 20 | keepFileExt: true, // 是否保留文件后缀 21 | }, 22 | app: { 23 | type: 'dateFile', 24 | filename: `logs/app-out/app.log`, 25 | alwaysIncludePattern: true, 26 | layout: { 27 | type: 'pattern', 28 | pattern: 29 | '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', 30 | }, 31 | // 日志文件按日期(天)切割 32 | pattern: 'yyyy-MM-dd', 33 | daysToKeep: 60, 34 | numBackups: 3, 35 | keepFileExt: true, 36 | }, 37 | errorFile: { 38 | type: 'dateFile', 39 | filename: `logs/errors/error.log`, 40 | alwaysIncludePattern: true, 41 | layout: { 42 | type: 'pattern', 43 | pattern: 44 | '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', 45 | }, 46 | // 日志文件按日期(天)切割 47 | pattern: 'yyyy-MM-dd', 48 | daysToKeep: 60, 49 | // maxLogSize: 10485760, 50 | numBackups: 3, 51 | keepFileExt: true, 52 | }, 53 | errors: { 54 | type: 'logLevelFilter', 55 | level: 'ERROR', 56 | appender: 'errorFile', 57 | }, 58 | }, 59 | categories: { 60 | default: { 61 | appenders: ['console', 'app', 'errors'], 62 | level: 'DEBUG', 63 | }, 64 | info: { appenders: ['console', 'app', 'errors'], level: 'info' }, 65 | access: { appenders: ['console', 'app', 'errors'], level: 'info' }, 66 | http: { appenders: ['access'], level: 'DEBUG' }, 67 | }, 68 | pm2: true, // 使用 pm2 来管理项目时,打开 69 | pm2InstanceVar: 'INSTANCE_ID', // 会根据 pm2 分配的 id 进行区分,以免各进程在写日志时造成冲突 70 | }; 71 | 72 | export default log4jsConfig; 73 | -------------------------------------------------------------------------------- /src/config/module/redis-temp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-03 17:02:36 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-03 19:06:52 6 | */ 7 | 8 | export const RedisTemp = { 9 | IMAGE_CAPTCHA: 'IMAGE_CAPTCHA:', 10 | }; 11 | -------------------------------------------------------------------------------- /src/controllers/account/account.controller.ts: -------------------------------------------------------------------------------- 1 | import { CurrentUser } from '@/decorators/current.user'; 2 | import { AuthGuard } from '@/guard/auth.guard'; 3 | import { IAccountInfo } from '@/interfaces/account.interface'; 4 | import { AccountService } from '@/services/account/account.service'; 5 | import { Controller, Get, UseGuards } from '@nestjs/common'; 6 | import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; 7 | 8 | @ApiBearerAuth() 9 | @ApiTags('账号模块') 10 | @ApiBearerAuth() 11 | @Controller('account') 12 | @UseGuards(AuthGuard) 13 | export class AccountController { 14 | constructor(private readonly accountService: AccountService) {} 15 | 16 | /** 17 | * 获取用户信息 18 | * @param userId 19 | */ 20 | @ApiOperation({ summary: '用户信息', description: '或者当前用户信息' }) 21 | @Get('info') 22 | async getInfo(@CurrentUser('userId') userId: number): Promise { 23 | return this.accountService.getUserInfo(userId); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/controllers/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { RegisterDTO, LoginDTO, EmailDTO } from '@/dtos/user/user.dto'; 2 | import { UserService } from '@/services/user/user.service'; 3 | import { Controller, Post, Body, Get } from '@nestjs/common'; 4 | import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; 5 | 6 | /* 7 | * 用户模块控制器 8 | * @Author: ahwgs 9 | * @Date: 2020-11-21 14:46:44 10 | * @Last Modified by: ahwgs 11 | * @Last Modified time: 2020-12-03 16:59:51 12 | */ 13 | 14 | @ApiBearerAuth() 15 | @ApiTags('用户模块') 16 | @ApiBearerAuth() 17 | @Controller('user') 18 | export class UserController { 19 | constructor(private readonly userService: UserService) {} 20 | 21 | @ApiOperation({ summary: '注册', description: '账号注册' }) 22 | @Post('register') 23 | async register(@Body() body: RegisterDTO) { 24 | return this.userService.register(body); 25 | } 26 | 27 | @ApiOperation({ summary: '图形验证码', description: '账号注册' }) 28 | @Get('captcha') 29 | async imgCaptcha() { 30 | return this.userService.createImgCaptcha(); 31 | } 32 | 33 | @ApiOperation({ summary: '登录', description: '账号登录' }) 34 | @Post('login') 35 | async login(@Body() body: LoginDTO) { 36 | return this.userService.login(body); 37 | } 38 | 39 | @ApiOperation({ summary: '邮箱验证码', description: '邮箱验证码' }) 40 | @Post('email_code') 41 | async registerEmail(@Body() body: EmailDTO) { 42 | return this.userService.emailCode(body); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/decorators/current.user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | 3 | export const CurrentUser = createParamDecorator( 4 | (key: string, ctx: ExecutionContext) => { 5 | const request = ctx.switchToHttp().getRequest(); 6 | if (key && request.currentUser) { 7 | return request.currentUser[key] || ''; 8 | } else { 9 | return request.currentUser; 10 | } 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /src/decorators/ip.address.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator } from '@nestjs/common'; 2 | 3 | import * as requestIp from 'request-ip'; 4 | 5 | export const IpAddress = createParamDecorator((data, req) => { 6 | if (req.clientIp) { 7 | return req.clientIp; 8 | } 9 | return requestIp.getClientIp(req); // In case we forgot to include requestIp.mw() in main.ts 10 | }); 11 | -------------------------------------------------------------------------------- /src/dtos/common.dto.ts: -------------------------------------------------------------------------------- 1 | import { CommonText } from '@/config/module/cmomon.text'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | /* 5 | * @Author: ahwgs 6 | * @Date: 2020-11-26 18:59:19 7 | * @Last Modified by: ahwgs 8 | * @Last Modified time: 2020-11-26 19:01:52 9 | */ 10 | 11 | export class DateDto { 12 | @ApiProperty({ description: CommonText.FRONT_DATE }) 13 | frontDate?: Date; 14 | @ApiProperty({ description: CommonText.END_DATE }) 15 | endDate?: Date; 16 | } 17 | -------------------------------------------------------------------------------- /src/dtos/user/user.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-01 15:31:12 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-03 16:57:28 6 | */ 7 | 8 | import { 9 | IsEmail, 10 | IsNotEmpty, 11 | IsNumber, 12 | IsString, 13 | IsUUID, 14 | } from 'class-validator'; 15 | import { ApiProperty } from '@nestjs/swagger'; 16 | 17 | export class UserAccountDTO { 18 | @ApiProperty({ 19 | name: 'account', 20 | }) 21 | @IsNotEmpty({ message: '账号不能为空' }) 22 | @IsString({ message: '账号必须是 String 类型' }) 23 | readonly account: string; 24 | 25 | @ApiProperty({ 26 | name: 'password', 27 | }) 28 | @IsNotEmpty({ message: '密码不能为空' }) 29 | @IsString({ message: '密码必须是 String 类型' }) 30 | readonly password: string; 31 | 32 | @ApiProperty({ 33 | name: 'accountType', 34 | }) 35 | @IsNotEmpty({ message: '账号类型不能为空' }) 36 | @IsNumber( 37 | {}, 38 | { 39 | message: '账号类型只能是数字', 40 | }, 41 | ) 42 | readonly accountType: number; 43 | } 44 | export class LoginDTO extends UserAccountDTO { 45 | @ApiProperty({ 46 | name: 'code', 47 | }) 48 | @IsNotEmpty({ message: '图形验证码不能为空' }) 49 | @IsString({ message: '图形验证码必须是 String 类型' }) 50 | readonly code: string; 51 | 52 | @ApiProperty({ 53 | name: 'codeId', 54 | }) 55 | @IsNotEmpty({ message: '图形验证码唯一id不能为空' }) 56 | @IsUUID(4, { message: 'codeId不是UUID' }) 57 | readonly codeId: string; 58 | } 59 | 60 | export class RegisterDTO extends UserAccountDTO { 61 | @ApiProperty({ 62 | name: 'code', 63 | }) 64 | @IsNotEmpty({ message: '验证码不能为空' }) 65 | @IsString({ message: '验证码必须是 String 类型' }) 66 | readonly code: string; 67 | } 68 | 69 | export class EmailDTO { 70 | @ApiProperty({ 71 | name: 'email', 72 | }) 73 | @IsNotEmpty({ message: '邮箱不能为空' }) 74 | @IsEmail({}, { message: '请输入正确的邮箱' }) 75 | readonly email: string; 76 | } 77 | -------------------------------------------------------------------------------- /src/entities/public.entity.ts: -------------------------------------------------------------------------------- 1 | import { DelEnum } from '@/enum/common.enum'; 2 | import { 3 | CreateDateColumn, 4 | PrimaryGeneratedColumn, 5 | UpdateDateColumn, 6 | BaseEntity, 7 | Column, 8 | } from 'typeorm'; 9 | import { Exclude } from 'class-transformer'; 10 | 11 | export class PublicEntity extends BaseEntity { 12 | @PrimaryGeneratedColumn({ 13 | type: 'int', 14 | name: 'id', 15 | comment: '主键id', 16 | }) 17 | id: number; 18 | 19 | @Exclude() // 表示排除字段不返回给前端 20 | @Column({ 21 | type: 'tinyint', 22 | nullable: false, 23 | name: 'is_del', 24 | default: DelEnum.N, 25 | comment: '是否删除,1表示删除,0表示正常', 26 | }) 27 | isDel: number; 28 | 29 | @CreateDateColumn({ 30 | type: 'timestamp', 31 | nullable: false, 32 | name: 'created_at', 33 | comment: '创建时间', 34 | }) 35 | createdAt: Date; 36 | 37 | @UpdateDateColumn({ 38 | type: 'timestamp', 39 | nullable: false, 40 | name: 'updated_at', 41 | comment: '更新时间', 42 | }) 43 | updatedAt: Date; 44 | } 45 | -------------------------------------------------------------------------------- /src/entities/user-code.entity.ts: -------------------------------------------------------------------------------- 1 | import { PublicEntity } from './public.entity'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | @Entity({ name: 'user_code' }) 5 | export class UserCodeEntity extends PublicEntity { 6 | @Column({ 7 | name: 'code', 8 | comment: '验证码', 9 | nullable: false, 10 | default: '', 11 | type: 'varchar', 12 | length: '10', 13 | }) 14 | code: string; 15 | 16 | @Column({ 17 | name: 'ip', 18 | comment: '请求地址ip', 19 | nullable: false, 20 | default: '', 21 | type: 'varchar', 22 | length: '20', 23 | }) 24 | ip: string; 25 | 26 | @Column({ 27 | name: 'status', 28 | comment: '状态 0:未使用 1已使用', 29 | nullable: false, 30 | default: 0, 31 | type: 'tinyint', 32 | }) 33 | status: number; 34 | 35 | @Column({ 36 | type: 'varchar', 37 | name: 'account', 38 | comment: '账号', 39 | default: '', 40 | nullable: false, 41 | }) 42 | account: string; 43 | } 44 | -------------------------------------------------------------------------------- /src/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { PublicEntity } from './public.entity'; 2 | import { Column, Entity } from 'typeorm'; 3 | import { Exclude } from 'class-transformer'; 4 | import { AccountEnum, SexEnum } from '@/enum/user.enum'; 5 | 6 | /* 7 | * 用户实体类 8 | * @Author: ahwgs 9 | * @Date: 2020-11-20 20:05:46 10 | * @Last Modified by: ahwgs 11 | * @Last Modified time: 2020-12-03 16:30:40 12 | */ 13 | 14 | @Entity({ name: 'user' }) 15 | export class UserEntity extends PublicEntity { 16 | @Column({ 17 | type: 'varchar', 18 | name: 'account', 19 | comment: '账号', 20 | default: '', 21 | nullable: false, 22 | }) 23 | account: string; 24 | 25 | @Exclude() // 表示排除字段不返回给前端 26 | @Column({ 27 | type: 'varchar', 28 | name: 'password', 29 | comment: '密码', 30 | nullable: false, 31 | default: '', 32 | }) 33 | password: string; 34 | 35 | @Column({ 36 | type: 'tinyint', 37 | default: AccountEnum.EMAIL, 38 | comment: '账号类型 0:邮箱 1:手机号', 39 | name: 'account_type', 40 | nullable: false, 41 | }) 42 | accountType: number; 43 | 44 | @Column({ 45 | type: 'tinyint', 46 | default: SexEnum.UNKWON, 47 | nullable: false, 48 | comment: '性别 0:男 1:女 2:未知', 49 | }) 50 | sex: number; 51 | 52 | @Exclude() // 表示排除字段不返回给前端 53 | @Column({ 54 | name: 'password_slat', 55 | comment: '密码盐', 56 | type: 'varchar', 57 | nullable: false, 58 | }) 59 | passwordSalt: string; 60 | } 61 | -------------------------------------------------------------------------------- /src/enum/api-code.enum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 请求状态码 3 | * @export ApiCodeEnum 4 | * @enum {number} 5 | */ 6 | export enum ApiCodeEnum { 7 | /** 8 | * 请求成功状态码 9 | */ 10 | SUCCESS = 0, 11 | /** 12 | * 请求失败状态码 13 | */ 14 | ERROR = 2000, 15 | /** 16 | * 请求警告状态码 17 | */ 18 | WARN = 3000, 19 | /** 20 | * 需要登录 21 | */ 22 | SHOULD_LOGIN = 4001, 23 | } 24 | -------------------------------------------------------------------------------- /src/enum/common.enum.ts: -------------------------------------------------------------------------------- 1 | export enum DelEnum { 2 | Y = 1, 3 | N = 0, 4 | } 5 | 6 | export enum StatusEnum { 7 | Y = 1, 8 | N = 0, 9 | } 10 | -------------------------------------------------------------------------------- /src/enum/user.enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-11-26 18:41:26 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-11-26 18:49:18 6 | */ 7 | 8 | /** 9 | * 账号枚举 10 | */ 11 | export enum AccountEnum { 12 | EMAIL = 0, 13 | TEL, 14 | } 15 | 16 | /** 17 | * 性别枚举 18 | */ 19 | export enum SexEnum { 20 | M = 0, 21 | W, 22 | UNKWON, 23 | } 24 | -------------------------------------------------------------------------------- /src/exception/api-exception.ts: -------------------------------------------------------------------------------- 1 | import { ApiCodeEnum } from '@/enum/api-code.enum'; 2 | import { HttpException, HttpStatus } from '@nestjs/common'; 3 | 4 | /** 5 | * api请求异常类 6 | * 7 | * @export 8 | * @class ApiException 9 | * @extends {HttpException} 10 | */ 11 | export class ApiException extends HttpException { 12 | private errorMessage: string; 13 | private errorCode: ApiCodeEnum; 14 | 15 | constructor( 16 | errorMessage: string, 17 | errorCode: ApiCodeEnum, 18 | statusCode: HttpStatus = HttpStatus.OK, 19 | ) { 20 | super(errorMessage, statusCode); 21 | this.errorMessage = errorMessage; 22 | this.errorCode = errorCode; 23 | } 24 | getErrorCode(): ApiCodeEnum { 25 | return this.errorCode; 26 | } 27 | getErrorMessage(): string { 28 | return this.errorMessage; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { ApiCodeEnum } from '@/enum/api-code.enum'; 2 | import { ApiException } from '@/exception/api-exception'; 3 | import { CommonText } from '@/config/module/cmomon.text'; 4 | import { IHttpResponse } from '@/interfaces/http.interface'; 5 | 6 | import { 7 | ArgumentsHost, 8 | Catch, 9 | ExceptionFilter, 10 | HttpException, 11 | HttpStatus, 12 | } from '@nestjs/common'; 13 | 14 | @Catch() 15 | export class HttpExceptionFilter implements ExceptionFilter { 16 | catch(exception: HttpException, host: ArgumentsHost) { 17 | const ctx = host.switchToHttp(); 18 | const response = ctx.getResponse(); 19 | const request = ctx.getRequest(); 20 | const timestamp = Date.now(); 21 | let errorResponse: IHttpResponse = null; 22 | const message = exception.message; 23 | const path = request.url; 24 | const method = request.method; 25 | const result = null; 26 | if (exception instanceof ApiException) { 27 | const message = exception.getErrorMessage(); 28 | errorResponse = { 29 | result, 30 | code: exception.getErrorCode(), 31 | message, 32 | path, 33 | method, 34 | timestamp, 35 | }; 36 | } else { 37 | errorResponse = { 38 | result, 39 | message: 40 | typeof message === 'string' 41 | ? message || CommonText.REQUEST_ERROR 42 | : JSON.stringify(message), 43 | path, 44 | method, 45 | timestamp, 46 | code: ApiCodeEnum.ERROR, 47 | }; 48 | } 49 | 50 | response.status(HttpStatus.OK); 51 | response.header('Content-Type', 'application/json; charset=utf-8'); 52 | response.send(errorResponse); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/guard/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { ICurrentUser } from '@/interfaces/account.interface'; 2 | import { IToken } from '@/interfaces/user.interface'; 3 | import { ApiCodeEnum } from '@/enum/api-code.enum'; 4 | import { ApiException } from '@/exception/api-exception'; 5 | import { JwtService } from '@/services/common/jwt/jwt.service'; 6 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 7 | 8 | @Injectable() 9 | export class AuthGuard implements CanActivate { 10 | constructor(private readonly jwtService: JwtService) {} 11 | async canActivate(context: ExecutionContext): Promise { 12 | const request = context.switchToHttp().getRequest(); 13 | const requestToken = 14 | request.headers['authtoken'] || request.headers['AuthToken']; 15 | if (requestToken) { 16 | try { 17 | const ret = await this.jwtService.verifyToken(requestToken); 18 | const { sub, account } = ret as IToken; 19 | const currentUser: ICurrentUser = { 20 | userId: sub, 21 | account, 22 | }; 23 | request.currentUser = currentUser; 24 | } catch (e) { 25 | throw new ApiException('token格式不正确', ApiCodeEnum.ERROR); 26 | } 27 | } else { 28 | throw new ApiException('你还没登录,请先登录', ApiCodeEnum.SHOULD_LOGIN); 29 | } 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/interceptor/logger.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CallHandler, 3 | ExecutionContext, 4 | Injectable, 5 | NestInterceptor, 6 | } from '@nestjs/common'; 7 | import { Observable } from 'rxjs'; 8 | import { catchError, tap } from 'rxjs/operators'; 9 | import { Logger } from '@/utils/log4js'; 10 | import { ApiException } from '@/exception/api-exception'; 11 | @Injectable() 12 | export class LoggingInterceptor implements NestInterceptor { 13 | private genAccessLog(request, time, res, status, context): any { 14 | const log = { 15 | statusCode: status, 16 | responseTime: `${Date.now() - time}ms`, 17 | ip: request.ip, 18 | header: request.headers, 19 | query: request.query, 20 | params: request.params, 21 | body: request.body, 22 | response: res, 23 | }; 24 | Logger.access(JSON.stringify(log), `${context.getClass().name}`); 25 | } 26 | intercept(context: ExecutionContext, next: CallHandler): Observable { 27 | const request = context.switchToHttp().getRequest(); 28 | const response = context.switchToHttp().getResponse(); 29 | const status = response.statusCode; 30 | const now = Date.now(); 31 | 32 | return next.handle().pipe( 33 | tap((res) => { 34 | // 其他的都进access 35 | this.genAccessLog( 36 | request, 37 | `${Date.now() - now}ms`, 38 | res, 39 | status, 40 | context, 41 | ); 42 | }), 43 | catchError((err) => { 44 | if (err instanceof ApiException) { 45 | // 其他的都进access 46 | this.genAccessLog( 47 | request, 48 | `${Date.now() - now}ms`, 49 | err.getErrorMessage(), 50 | status, 51 | context, 52 | ); 53 | Logger.error(err); 54 | } else { 55 | Logger.error(err); 56 | } 57 | // 返回原异常 58 | throw err; 59 | }), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/interceptor/transform.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { ApiCodeEnum } from '@/enum/api-code.enum'; 2 | import { THttpResponse } from '@/interfaces/http.interface'; 3 | import { 4 | CallHandler, 5 | ExecutionContext, 6 | Injectable, 7 | NestInterceptor, 8 | } from '@nestjs/common'; 9 | import { Observable } from 'rxjs'; 10 | import { map } from 'rxjs/operators'; 11 | import * as express from 'express'; 12 | import { classToPlain } from 'class-transformer'; 13 | 14 | @Injectable() 15 | export class TransformInterceptor 16 | implements NestInterceptor> { 17 | intercept( 18 | context: ExecutionContext, 19 | next: CallHandler, 20 | ): Observable> { 21 | const httpArguments = context.switchToHttp(); 22 | const request: express.Request = httpArguments.getRequest(); 23 | const timestamp = Date.now(); 24 | return next.handle().pipe( 25 | map((data) => ({ 26 | result: classToPlain(data) || data, 27 | code: ApiCodeEnum.SUCCESS, 28 | message: 'success', 29 | timestamp, 30 | path: request.url, 31 | method: request.method, 32 | })), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/interfaces/account.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IAccountInfo { 2 | id: number; 3 | } 4 | 5 | export interface ICurrentUser { 6 | userId: string | number; 7 | account: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/common.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IAnyObject { 2 | [propsName: string]: any; 3 | } 4 | 5 | /** 6 | * 邮箱模版 7 | */ 8 | type EmailTemplateType = 'register' | 'notice'; 9 | 10 | /** 11 | * 邮箱参数 12 | */ 13 | export interface IEmailParams { 14 | to: string; 15 | title: string; 16 | content: string; 17 | template?: EmailTemplateType; 18 | context?: IAnyObject; 19 | } 20 | -------------------------------------------------------------------------------- /src/interfaces/http.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 带分页带返回类型定义 3 | */ 4 | export interface IHttpResultPagination { 5 | list: T; 6 | totalCount: number; 7 | } 8 | 9 | /** 10 | * 接口响应返回基础 11 | */ 12 | export interface IHttpResponseBase { 13 | message: string; 14 | code: number; 15 | path: string; 16 | method: string; 17 | timestamp: number; 18 | } 19 | 20 | export type THttpResponse = IHttpResponseBase & { 21 | result: T; 22 | }; 23 | 24 | export interface IHttpResponse extends IHttpResponseBase { 25 | result: any; 26 | } 27 | -------------------------------------------------------------------------------- /src/interfaces/user.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IToken { 2 | sub: number; 3 | account: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { LoggingInterceptor } from '@/interceptor/logger.interceptor'; 2 | import { TransformInterceptor } from '@/interceptor/transform.interceptor'; 3 | import { NestFactory } from '@nestjs/core'; 4 | import { AppModule } from './app.module'; 5 | import { ConfigService } from '@nestjs/config'; 6 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 7 | import * as helmet from 'helmet'; 8 | import * as compression from 'compression'; 9 | import * as rateLimit from 'express-rate-limit'; 10 | import * as path from 'path'; 11 | import { NestExpressApplication } from '@nestjs/platform-express'; 12 | import { renderFile } from 'ejs'; 13 | import { Logger } from '@/utils/log4js'; 14 | import { EnvSwaggerOptions } from '@/config/env/swagger.config'; 15 | import { HttpExceptionFilter } from '@/filters/http-exception.filter'; 16 | import { ValidationPipe } from '@/pipes/validation.pipe'; 17 | import { terminalHelpTextConsole } from '@/utils/terminal-help-text-console'; 18 | 19 | /** 20 | *  启动函数 21 | */ 22 | async function bootstrap() { 23 | try { 24 | const app = await NestFactory.create(AppModule, { 25 | logger: new Logger(), 26 | }); 27 | 28 | // app.use(logger); 29 | 30 | const configService = app.get(ConfigService); 31 | const swaggerOptions = configService.get( 32 | 'EnvSwaggerOptions', 33 | ); 34 | 35 | /** 36 | * 设置CORS 37 | * https://github.com/expressjs/cors#configuration-options 38 | */ 39 | app.enableCors({ 40 | origin: true, 41 | credentials: true, 42 | }); 43 | 44 | /** 45 | * web安全 46 | */ 47 | app.use(helmet()); 48 | 49 | /** 50 | * 压缩中间件 51 | */ 52 | app.use(compression()); 53 | 54 | // 给请求添加prefix 55 | app.setGlobalPrefix(swaggerOptions.prefix); 56 | 57 | /** 58 | * 限速 59 | */ 60 | app.use( 61 | rateLimit({ 62 | windowMs: 15 * 60 * 1000, // 15 minutes 63 | max: 100, // limit each IP to 100 requests per windowMs 64 | }), 65 | ); 66 | 67 | app.useStaticAssets(path.join(__dirname, '..', 'public')); 68 | app.setBaseViewsDir(path.join(__dirname, '..', 'public/views')); 69 | app.engine('html', renderFile); 70 | app.setViewEngine('ejs'); 71 | 72 | // 全局注册错误的过滤器(错误异常) 73 | app.useGlobalFilters(new HttpExceptionFilter()); 74 | 75 | // 全局注册拦截器 76 | app.useGlobalInterceptors( 77 | new TransformInterceptor(), 78 | new LoggingInterceptor(), 79 | ); 80 | 81 | // 全局注册管道 82 | app.useGlobalPipes(new ValidationPipe()); 83 | 84 | const options = new DocumentBuilder() 85 | .setTitle(swaggerOptions.title) 86 | .setDescription(swaggerOptions.desc) 87 | .setVersion(swaggerOptions.version) 88 | .addBearerAuth() 89 | .build(); 90 | const document = SwaggerModule.createDocument(app, options); 91 | 92 | SwaggerModule.setup(swaggerOptions.setupUrl, app, document, { 93 | customSiteTitle: swaggerOptions.title, 94 | swaggerOptions: { 95 | explorer: true, 96 | docExpansion: 'list', 97 | filter: true, 98 | showRequestDuration: true, 99 | syntaxHighlight: { 100 | active: true, 101 | theme: 'tomorrow-night', 102 | }, 103 | }, 104 | }); 105 | await app.listen(configService.get('SERVE_LISTENER_PORT')); 106 | } catch (err) { 107 | throw err; 108 | } 109 | } 110 | bootstrap() 111 | .then(() => { 112 | terminalHelpTextConsole(); 113 | }) 114 | .catch((e) => { 115 | console.log(e); 116 | Logger.error(e); 117 | }); 118 | -------------------------------------------------------------------------------- /src/modules/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CommonModule } from '../common/common.module'; 3 | import { EntityModule } from '../base/entitiy.module'; 4 | import { AccountService } from '@/services/account/account.service'; 5 | import { AccountController } from '@/controllers/account/account.controller'; 6 | @Module({ 7 | imports: [EntityModule, CommonModule], 8 | providers: [AccountService], 9 | controllers: [AccountController], 10 | exports: [AccountService], 11 | }) 12 | export class AccountModule {} 13 | -------------------------------------------------------------------------------- /src/modules/base/config.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import envSwaggerConfig from '@/config/env/swagger.config'; 3 | import envDataBaseConfig from '@/config/env/databse.config'; 4 | import envRedisConfig from '@/config/env/redis.config'; 5 | import envEmailConfig from '@/config/env/email.config'; 6 | import * as Joi from 'joi'; 7 | import { ConfigModule } from '@nestjs/config'; 8 | import envJwtConfig from '@/config/env/jwt.config'; 9 | @Module({ 10 | imports: [ 11 | ConfigModule.forRoot({ 12 | encoding: 'utf-8', 13 | load: [ 14 | envSwaggerConfig, 15 | envDataBaseConfig, 16 | envRedisConfig, 17 | envJwtConfig, 18 | envEmailConfig, 19 | ], 20 | expandVariables: true, // 开启嵌套变量 21 | ignoreEnvVars: true, 22 | validationSchema: Joi.object({ 23 | SERVE_LINTENER_PORT: Joi.number().default(3000), 24 | SWAGGER_SETUP_PATH: Joi.string().default(''), 25 | SWAGGER_ENDPOINT_PREFIX: Joi.string().default(''), 26 | SWAGGER_UI_TITLE: Joi.string().default(''), 27 | SWAGGER_UI_TITLE_DESC: Joi.string().default(''), 28 | SWAGGER_API_VERSION: Joi.string().default(''), 29 | NODE_ENV: Joi.string() 30 | .valid('development', 'production', 'test', 'provision') 31 | .default('development'), 32 | DB_TYPE: Joi.string().default('mysql'), 33 | DB_HOST: Joi.string().default('localhost'), 34 | DB_PORT: Joi.number().default(3306), 35 | DB_DATABASE: Joi.string().default(''), 36 | DB_USERNAME: Joi.string().default('root'), 37 | DB_PASSWORD: Joi.string().default(''), 38 | DB_SYNCHRONIZE: Joi.string().default('1'), // 1 true 0 false 39 | DB_LOGGING: Joi.string().default('1'), // 1 true 0 false 40 | DB_TABLE_PREFIX: Joi.string().default('t_'), // 1 true 0 false 41 | REDIS_HOST: Joi.string().default('localhost'), // 1 true 0 false 42 | REDIS_PORT: Joi.number().default(6379), 43 | REDIS_PASSWORD: Joi.string().default('').allow(''), 44 | TOKEN_SECRET: Joi.string().default('').allow(''), 45 | TOKEN_EXPIRES: Joi.string().default('').allow(''), 46 | EMAIL_HOST: Joi.string().default(''), 47 | EMAIL_PORT: Joi.string().default(''), 48 | EAMIL_AUTH_USER: Joi.string().default(''), 49 | EMAIL_AUTH_PASSWORD: Joi.string().default(''), 50 | EMAIL_FROM: Joi.string().default(''), 51 | validationOptions: { 52 | allowUnknown: false, // 控制是否允许环境变量中未知的键。默认为true。 53 | abortEarly: true, // 如果为true,在遇到第一个错误时就停止验证;如果为false,返回所有错误。默认为false。 54 | }, 55 | }), 56 | }), 57 | ], 58 | }) 59 | export class CustomConfigModule {} 60 | -------------------------------------------------------------------------------- /src/modules/base/database.module.ts: -------------------------------------------------------------------------------- 1 | import { EnvDataBaseOptions } from '@/config/env/databse.config'; 2 | import { Module } from '@nestjs/common'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | import { ConfigModule, ConfigService } from '@nestjs/config'; 5 | 6 | @Module({ 7 | imports: [ 8 | TypeOrmModule.forRootAsync({ 9 | imports: [ConfigModule], 10 | inject: [ConfigService], 11 | useFactory: (configService: ConfigService) => { 12 | const dataBaseOptions = configService.get( 13 | 'EnvDataBaseOptions', 14 | ); 15 | return { 16 | type: dataBaseOptions.type, 17 | host: dataBaseOptions.host, 18 | port: dataBaseOptions.port as number, 19 | username: dataBaseOptions.username, 20 | password: dataBaseOptions.password, 21 | database: dataBaseOptions.database, 22 | entities: dataBaseOptions.entities, 23 | synchronize: dataBaseOptions.synchronize === '1', 24 | logging: dataBaseOptions.logging === '1', 25 | entityPrefix: dataBaseOptions.entityPrefix, 26 | }; 27 | }, 28 | }), 29 | ], 30 | }) 31 | export class DataBaseModule {} 32 | -------------------------------------------------------------------------------- /src/modules/base/email.module.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule, ConfigService } from '@nestjs/config'; 2 | import { Module } from '@nestjs/common'; 3 | import { MailerModule } from '@nestjs-modules/mailer'; 4 | import { EjsAdapter } from '@nestjs-modules/mailer/dist/adapters/ejs.adapter'; 5 | import { EnvEmailOptions } from '@/config/env/email.config'; 6 | import * as path from 'path'; 7 | 8 | @Module({ 9 | imports: [ 10 | MailerModule.forRootAsync({ 11 | imports: [ConfigModule], 12 | inject: [ConfigService], 13 | useFactory: (config: ConfigService) => { 14 | const emailOptions = config.get('EnvEmailOptions'); 15 | return { 16 | transport: { 17 | host: emailOptions.host, 18 | port: emailOptions.port, 19 | auth: { 20 | user: emailOptions.user, 21 | pass: emailOptions.password, 22 | }, 23 | }, 24 | defaults: { 25 | from: emailOptions.from, 26 | }, 27 | template: { 28 | dir: path.join(__dirname, '../../assets/email-template'), 29 | adapter: new EjsAdapter(), 30 | options: { 31 | strict: true, 32 | }, 33 | }, 34 | }; 35 | }, 36 | }), 37 | ], 38 | }) 39 | export class EmailModule {} 40 | -------------------------------------------------------------------------------- /src/modules/base/entitiy.module.ts: -------------------------------------------------------------------------------- 1 | import { UserCodeEntity } from '@/entities/user-code.entity'; 2 | import { UserEntity } from '@/entities/user.entity'; 3 | import { Module } from '@nestjs/common'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | const entityList = [UserEntity, UserCodeEntity]; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature(entityList)], 9 | exports: [TypeOrmModule.forFeature(entityList)], 10 | }) 11 | export class EntityModule {} 12 | -------------------------------------------------------------------------------- /src/modules/base/redis.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-03 17:16:39 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-03 18:58:05 6 | */ 7 | import { Module } from '@nestjs/common'; 8 | import { RedisModule } from 'nestjs-redis'; 9 | import { ConfigModule, ConfigService } from '@nestjs/config'; 10 | import { EnvRedisOptions } from '@/config/env/redis.config'; 11 | 12 | @Module({ 13 | imports: [ 14 | RedisModule.forRootAsync({ 15 | imports: [ConfigModule], 16 | inject: [ConfigService], 17 | useFactory: (configService: ConfigService) => { 18 | const redisOptions = configService.get( 19 | 'EnvRedisOptions', 20 | ); 21 | return { 22 | host: redisOptions.host, 23 | port: redisOptions.port as number, 24 | password: redisOptions.password, 25 | db: 0, // default 26 | }; 27 | }, 28 | }), 29 | ], 30 | exports: [], 31 | }) 32 | export class CustomRedisModule {} 33 | -------------------------------------------------------------------------------- /src/modules/common/code.module.ts: -------------------------------------------------------------------------------- 1 | import { EmailCodeService } from '@/services/common/code/email-code.service'; 2 | import { ImageCaptchaService } from '@/services/common/code/img-captcha.service'; 3 | import { Module } from '@nestjs/common'; 4 | 5 | @Module({ 6 | imports: [], 7 | providers: [ImageCaptchaService, EmailCodeService], 8 | controllers: [], 9 | exports: [ImageCaptchaService, EmailCodeService], 10 | }) 11 | export class CodeModule {} 12 | -------------------------------------------------------------------------------- /src/modules/common/common.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CodeModule } from './code.module'; 3 | import { JwtModule } from './jwt.module'; 4 | import { RedisModule } from './redis.module'; 5 | 6 | /** 7 | * 基础服务模块 8 | */ 9 | @Module({ 10 | imports: [CodeModule, RedisModule, JwtModule], 11 | controllers: [], 12 | exports: [CodeModule, RedisModule, JwtModule], 13 | }) 14 | export class CommonModule {} 15 | -------------------------------------------------------------------------------- /src/modules/common/jwt.module.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule } from '@nestjs/config'; 2 | import { JwtService } from '@/services/common/jwt/jwt.service'; 3 | import { Module } from '@nestjs/common'; 4 | 5 | @Module({ 6 | imports: [ConfigModule], 7 | providers: [JwtService], 8 | exports: [JwtService], 9 | }) 10 | export class JwtModule {} 11 | -------------------------------------------------------------------------------- /src/modules/common/redis.module.ts: -------------------------------------------------------------------------------- 1 | import { RedisClientService } from '@/services/common/redis/redis-client.service'; 2 | import { RedisCacheService } from '@/services/common/redis/redis.cache.service'; 3 | import { Module } from '@nestjs/common'; 4 | 5 | @Module({ 6 | providers: [RedisClientService, RedisCacheService], 7 | exports: [RedisClientService, RedisCacheService], 8 | }) 9 | export class RedisModule {} 10 | -------------------------------------------------------------------------------- /src/modules/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { UserService } from '@/services/user/user.service'; 2 | import { Module } from '@nestjs/common'; 3 | import { UserController } from '@/controllers/user/user.controller'; 4 | import { CommonModule } from '../common/common.module'; 5 | import { EntityModule } from '../base/entitiy.module'; 6 | @Module({ 7 | imports: [EntityModule, CommonModule], 8 | providers: [UserService], 9 | controllers: [UserController], 10 | exports: [UserService], 11 | }) 12 | export class UserModule {} 13 | -------------------------------------------------------------------------------- /src/pipes/parse.page.pipe.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-11-20 20:04:41 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-02 10:15:08 6 | */ 7 | 8 | import { CommonText } from '@/config/module/cmomon.text'; 9 | import { ApiException } from '@/exception/api-exception'; 10 | import { ApiCodeEnum } from '@/enum/api-code.enum'; 11 | import { 12 | ArgumentMetadata, 13 | Injectable, 14 | PipeTransform, 15 | HttpStatus, 16 | } from '@nestjs/common'; 17 | 18 | @Injectable() 19 | export class ParsePagePipe implements PipeTransform { 20 | async transform(value: any, metadata: ArgumentMetadata) { 21 | const { data: key } = metadata; 22 | const val = parseFloat(value); 23 | if (isNaN(val) || typeof val !== 'number' || val <= 0) { 24 | throw new ApiException( 25 | `${key} ${CommonText.PARAMES_MUST_NUM}:${value}`, 26 | ApiCodeEnum.WARN, 27 | HttpStatus.OK, 28 | ); 29 | } 30 | return val; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-02 10:22:19 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-03 13:48:40 6 | */ 7 | 8 | import { 9 | ArgumentMetadata, 10 | Injectable, 11 | PipeTransform, 12 | HttpStatus, 13 | } from '@nestjs/common'; 14 | import { validate } from 'class-validator'; 15 | import { plainToClass } from 'class-transformer'; 16 | import { ApiException } from '@/exception/api-exception'; 17 | import { ApiCodeEnum } from '@/enum/api-code.enum'; 18 | 19 | @Injectable() 20 | export class ValidationPipe implements PipeTransform { 21 | async transform(value: any, metadata: ArgumentMetadata) { 22 | const { metatype } = metadata; 23 | // 如果没有传入验证规则,则不验证,直接返回数据 24 | if (!metatype || !this.toValidate(metatype)) { 25 | return value; 26 | } 27 | // 将对象转换为 Class 来验证 28 | const object = plainToClass(metatype, value); 29 | const errors = await validate(object); 30 | if (errors.length > 0) { 31 | //获取第一个错误并且返回 32 | const msg = Object.values(errors[0].constraints)[0]; 33 | // 统一抛出异常 34 | throw new ApiException(`${msg}`, ApiCodeEnum.WARN, HttpStatus.OK); 35 | } 36 | return value; 37 | } 38 | 39 | private toValidate(metatype: any): boolean { 40 | const types = [String, Boolean, Number, Array, Object]; 41 | return !types.includes(metatype); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/services/account/account.service.ts: -------------------------------------------------------------------------------- 1 | import { UserEntity } from '@/entities/user.entity'; 2 | import { IAccountInfo } from '@/interfaces/account.interface'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { InjectRepository } from '@nestjs/typeorm'; 5 | import { Repository } from 'typeorm'; 6 | 7 | @Injectable() 8 | export class AccountService { 9 | constructor( 10 | @InjectRepository(UserEntity) 11 | private readonly userRepo: Repository, 12 | ) {} 13 | 14 | /** 15 | * 获取用户信息 16 | */ 17 | getUserInfo(userId: number) { 18 | const result = {} as IAccountInfo; 19 | console.log('userId', userId); 20 | return result; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/services/common/code/email-code.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-12-05 00:02:03 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-05 00:03:39 6 | */ 7 | 8 | import { Injectable } from '@nestjs/common'; 9 | import { MailerService } from '@nestjs-modules/mailer'; 10 | import { IEmailParams } from '@/interfaces/common.interface'; 11 | 12 | @Injectable() 13 | export class EmailCodeService { 14 | constructor(private readonly mailerService: MailerService) {} 15 | /** 16 | * 邮箱发送 17 | * @param params IEmailParams 18 | */ 19 | public async sendEmail(params: IEmailParams) { 20 | const { to, title, content, template, context } = params; 21 | return await this.mailerService.sendMail({ 22 | to: to, 23 | subject: title, 24 | text: content, 25 | template, 26 | context, 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/services/common/code/img-captcha.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as svgCaptcha from 'svg-captcha'; 3 | 4 | @Injectable() 5 | export class ImageCaptchaService { 6 | /** 7 | * 生成图形验证码 8 | */ 9 | public createSvgCaptcha(length?: number) { 10 | const defaultLen = 4; 11 | const captcha: { data: any; text: string } = svgCaptcha.create({ 12 | size: length || defaultLen, 13 | fontSize: 50, 14 | width: 100, 15 | height: 34, 16 | ignoreChars: '0o1i', 17 | background: '#01458E', 18 | inverse: false, 19 | }); 20 | return captcha; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/services/common/jwt/jwt.service.ts: -------------------------------------------------------------------------------- 1 | import { EnvJwtOptions } from '@/config/env/jwt.config'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { IAnyObject } from '@/interfaces/common.interface'; 4 | import * as jwt from 'jsonwebtoken'; 5 | import { Injectable } from '@nestjs/common'; 6 | 7 | /** 8 | * jwt实现类 9 | */ 10 | @Injectable() 11 | export class JwtService { 12 | constructor(private readonly config: ConfigService) {} 13 | /** 14 | * 生成token 15 | * @param payload 16 | */ 17 | public sign(payload: string | IAnyObject | Buffer): string { 18 | const tokenOptions = this.config.get('EnvJwtOptions'); 19 | const secretOrPrivateKey = tokenOptions.secret; 20 | const options: jwt.SignOptions = { 21 | expiresIn: tokenOptions.expiresIn, 22 | }; 23 | return jwt.sign(payload, secretOrPrivateKey, options); 24 | } 25 | 26 | /** 27 | * 校验token 28 | * @param token 29 | */ 30 | public async verifyToken(token: string) { 31 | try { 32 | const tokenOptions = this.config.get('EnvJwtOptions'); 33 | const secretOrPrivateKey = tokenOptions.secret; 34 | return await jwt.verify(token, secretOrPrivateKey); 35 | } catch (e) { 36 | throw e; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/services/common/redis/redis-client.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { RedisService } from 'nestjs-redis'; 3 | import { Redis } from 'ioredis'; 4 | 5 | @Injectable() 6 | export class RedisClientService { 7 | public client: Redis; 8 | constructor(private redisService: RedisService) {} 9 | 10 | onModuleInit() { 11 | this.getClient(); 12 | } 13 | 14 | public getClient() { 15 | this.client = this.redisService.getClient(); 16 | } 17 | 18 | public async set( 19 | key: string, 20 | value: Record | string, 21 | second?: number, 22 | ) { 23 | value = JSON.stringify(value); 24 | // 如果没有传递时间就默认时间 25 | if (!second) { 26 | await this.client.setex(key, 24 * 60 * 60, value); // 秒为单位 27 | } else { 28 | await this.client.set(key, value, 'EX', second); 29 | } 30 | } 31 | 32 | public async get(key: string): Promise { 33 | const data = await this.client.get(key); 34 | if (data) { 35 | return JSON.parse(data); 36 | } else { 37 | return null; 38 | } 39 | } 40 | 41 | public async del(key: string): Promise { 42 | await this.client.del(key); 43 | } 44 | 45 | public async flushall(): Promise { 46 | await this.client.flushall(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/services/common/redis/redis.cache.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { RedisClientService } from './redis-client.service'; 3 | @Injectable() 4 | export class RedisCacheService extends RedisClientService { 5 | /** 6 | * 设置 7 | * @param key 8 | * @param value 9 | * @param second 10 | */ 11 | public async set(key: string, value: any, second?: number): Promise { 12 | value = JSON.stringify(value); 13 | if (!this.client) { 14 | this.getClient(); 15 | } 16 | if (!second) { 17 | await this.client.set(key, value); 18 | } else { 19 | await this.client.set(key, value, 'EX', second); 20 | } 21 | } 22 | 23 | public async get(key: string): Promise { 24 | if (!this.client) { 25 | this.getClient(); 26 | } 27 | const data = await this.client.get(key); 28 | if (data) { 29 | return JSON.parse(data); 30 | } else { 31 | return null; 32 | } 33 | } 34 | 35 | public async del(key: string): Promise { 36 | if (!this.client) { 37 | this.getClient(); 38 | } 39 | await this.client.del(key); 40 | } 41 | 42 | public async flushall(): Promise { 43 | if (!this.client) { 44 | this.getClient(); 45 | } 46 | await this.client.flushall(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/services/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { CommonText } from '@/config/module/cmomon.text'; 2 | import { DelEnum, StatusEnum } from '@/enum/common.enum'; 3 | import { RedisTemp } from '@/config/module/redis-temp'; 4 | import { RedisCacheService } from '@/services/common/redis/redis.cache.service'; 5 | import { UserEntity } from '@/entities/user.entity'; 6 | import { Injectable } from '@nestjs/common'; 7 | import { InjectRepository } from '@nestjs/typeorm'; 8 | import { Repository } from 'typeorm'; 9 | import { ApiException } from '@/exception/api-exception'; 10 | import { ImageCaptchaService } from '@/services/common/code/img-captcha.service'; 11 | import { RegisterDTO, LoginDTO, EmailDTO } from '@/dtos/user/user.dto'; 12 | import { ApiCodeEnum } from '@/enum/api-code.enum'; 13 | import * as bcrypt from 'bcrypt'; 14 | import { getUUID } from '@/utils/common'; 15 | import { Logger } from '@/utils/log4js'; 16 | import { IToken } from '@/interfaces/user.interface'; 17 | import { JwtService } from '@/services/common/jwt/jwt.service'; 18 | import { UserCodeEntity } from '@/entities/user-code.entity'; 19 | import { EmailCodeService } from '@/services/common/code/email-code.service'; 20 | import { IEmailParams } from '@/interfaces/common.interface'; 21 | import { AccountEnum } from '@/enum/user.enum'; 22 | /* 23 | * 用户模块服务类 24 | * @Author: ahwgs 25 | * @Date: 2020-11-21 14:46:51 26 | * @Last Modified by: ahwgs 27 | * @Last Modified time: 2020-12-04 16:11:08 28 | */ 29 | 30 | @Injectable() 31 | export class UserService { 32 | constructor( 33 | @InjectRepository(UserEntity) 34 | private readonly usersRepository: Repository, 35 | @InjectRepository(UserCodeEntity) 36 | private readonly usersCodeResp: Repository, 37 | private readonly imageCaptchaService: ImageCaptchaService, 38 | private readonly redisService: RedisCacheService, 39 | private readonly jwt: JwtService, 40 | private readonly email: EmailCodeService, 41 | ) {} 42 | 43 | /** 44 | * 生成token 45 | * @param payload 46 | */ 47 | private signToken(payload: IToken) { 48 | return this.jwt.sign(payload); 49 | } 50 | 51 | /** 52 | * 校验图形验证码是否正确 53 | * @param code 验证码 54 | * @param codeId 验证码唯一id 55 | */ 56 | private async chenckImageCaptcha(code: string, codeId: string) { 57 | const key = `${RedisTemp.IMAGE_CAPTCHA}${codeId}`; 58 | const data: string | null = await this.redisService.get(key); 59 | if (!data) { 60 | throw new ApiException('验证码已过期', ApiCodeEnum.WARN); 61 | } 62 | return code.toLocaleLowerCase() === data.toLocaleLowerCase(); 63 | } 64 | 65 | /** 66 | * 生成hash password 67 | * @param pwd 68 | * @param slat 69 | */ 70 | private async genHashPassword(pwd: string, slat: string) { 71 | return await bcrypt.hash(pwd, slat); 72 | } 73 | 74 | /** 75 | * 生成盐 76 | */ 77 | private async genSalt() { 78 | return await bcrypt.genSalt(10); 79 | } 80 | 81 | /** 82 | * 83 | * @param loginPwd 检查密码是否相同 84 | * @param sourcePwd 85 | */ 86 | private async checkPassword(loginPwd: string, sourcePwd: string) { 87 | return await bcrypt.compare(loginPwd, sourcePwd); 88 | } 89 | 90 | /** 91 | * 根据账号,账号类型查询用户 92 | * @param account 93 | * @param accountType 94 | */ 95 | private async findUser(account: string, accountType: number) { 96 | const user = await this.usersRepository.findOne({ 97 | where: { account, isDel: DelEnum.N, accountType }, 98 | }); 99 | return user; 100 | } 101 | 102 | /** 103 | * 获取图形验证码 104 | */ 105 | public async createImgCaptcha() { 106 | // 返回地址与id给前端 前端带过来校验 107 | const uuid = getUUID(); 108 | const captcha = this.imageCaptchaService.createSvgCaptcha(); 109 | const { data, text } = captcha || {}; 110 | Logger.info(`图形验证码:--->${uuid}----${text}`); 111 | const time = 5 * 60; 112 | this.redisService.set(`${RedisTemp.IMAGE_CAPTCHA}${uuid}`, text, time); 113 | // 存redis 114 | return { 115 | data, 116 | codeId: uuid, 117 | }; 118 | } 119 | 120 | private async findUserCode(code: string, account: string) { 121 | // 验证码表 没被使用的 122 | const result = await this.usersCodeResp.findOne({ 123 | where: { 124 | account, 125 | code, 126 | isDel: DelEnum.N, 127 | status: StatusEnum.N, 128 | }, 129 | }); 130 | // 如果验证码查出有 需要改状态 131 | if (result) { 132 | result.status = StatusEnum.Y; 133 | this.usersCodeResp.save(result); 134 | } 135 | return result; 136 | } 137 | 138 | /** 139 | * 注册 140 | * @param RegisterDTO 注册参数 141 | */ 142 | public async register(body: RegisterDTO) { 143 | const { account, accountType, password, code } = body; 144 | 145 | // 根据code 校验验证码 146 | const checkCode = await this.findUserCode(code, account); 147 | if (!checkCode) { 148 | throw new ApiException('验证码已失效', ApiCodeEnum.ERROR); 149 | } 150 | // 校验账户是否存在 151 | const check = await this.findUser(account, accountType); 152 | if (check) { 153 | throw new ApiException('当前账户已存在', ApiCodeEnum.ERROR); 154 | } 155 | const slat = await this.genSalt(); 156 | const hashPwd = await this.genHashPassword(password, slat); 157 | await this.usersRepository.insert({ 158 | account, 159 | accountType, 160 | password: hashPwd, 161 | passwordSalt: slat, 162 | }); 163 | if (accountType === AccountEnum.EMAIL) { 164 | this.email.sendEmail({ 165 | to: account, 166 | template: 'notice', 167 | title: CommonText.REGISTER_SUCCESS, 168 | content: '', 169 | context: { 170 | account, 171 | }, 172 | } as IEmailParams); 173 | } 174 | 175 | return true; 176 | } 177 | 178 | /** 179 | * 登录 180 | * @param body LoginDTO 181 | */ 182 | public async login(body: LoginDTO) { 183 | const { account, accountType, password, code, codeId } = body; 184 | // 校验图形验证码 185 | const checkCode = await this.chenckImageCaptcha(code, codeId); 186 | if (!checkCode) { 187 | throw new ApiException('图形验证码不正确', ApiCodeEnum.ERROR); 188 | } 189 | // 校验账户是否存在 190 | const user = await this.findUser(account, accountType); 191 | if (!user) { 192 | throw new ApiException('当前账户不存在', ApiCodeEnum.ERROR); 193 | } 194 | const { password: sourcePwd } = user; 195 | const checkPwd = await this.checkPassword(password, sourcePwd); 196 | if (!checkPwd) { 197 | throw new ApiException('请检查你的用户名与密码', ApiCodeEnum.ERROR); 198 | } 199 | // 登录成功 给token 200 | const { id } = user || {}; 201 | const tokenPayload: IToken = { 202 | account, 203 | sub: id, 204 | }; 205 | return await this.signToken(tokenPayload); 206 | } 207 | 208 | /** 209 | * 发送邮件 210 | * @param body 211 | */ 212 | public async emailCode(body: EmailDTO) { 213 | const { email } = body; 214 | let code = ''; 215 | // 查询有没有该账号并且还没被使用的code 216 | const checkCode = await this.usersCodeResp.findOne({ 217 | where: { 218 | account: email, 219 | isDel: DelEnum.N, 220 | status: StatusEnum.N, 221 | }, 222 | }); 223 | if (checkCode) { 224 | code = checkCode.code; 225 | } else { 226 | const captcha = this.imageCaptchaService.createSvgCaptcha(); 227 | const { text } = captcha || {}; 228 | // 自动生成 229 | code = text; 230 | } 231 | Logger.info(`邮箱验证码:--->${email}----${code}`); 232 | // 生成code 233 | const params: IEmailParams = { 234 | to: email, 235 | title: CommonText.REGISTER_CODE, 236 | content: code, 237 | template: 'register', 238 | context: { 239 | code: code, 240 | }, 241 | }; 242 | await this.email.sendEmail(params); 243 | // 发送成功 插库 244 | if (!checkCode) { 245 | this.usersCodeResp.insert({ 246 | code, 247 | account: email, 248 | status: StatusEnum.N, 249 | }); 250 | } 251 | return true; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | import * as uuid from 'uuid'; 2 | 3 | /** 4 | * 获取UUID 5 | */ 6 | export const getUUID = () => { 7 | return uuid.v4(); 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils/format-date.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-11-30 13:59:54 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-11-30 13:59:54 6 | */ 7 | -------------------------------------------------------------------------------- /src/utils/log4js.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-11-25 21:32:23 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-11-26 01:20:20 6 | */ 7 | import * as Path from 'path'; 8 | import * as Log4js from 'log4js'; 9 | import * as Util from 'util'; 10 | import * as moment from 'moment'; // 处理时间的工具 11 | import * as StackTrace from 'stacktrace-js'; 12 | import Chalk from 'chalk'; 13 | import log4jsConfig from '@/config/module/log4js'; 14 | import { LoggerService } from '@nestjs/common'; 15 | 16 | // 日志级别 17 | export enum LoggerLevel { 18 | ALL = 'ALL', 19 | MARK = 'MARK', 20 | TRACE = 'TRACE', 21 | DEBUG = 'DEBUG', 22 | INFO = 'INFO', 23 | WARN = 'WARN', 24 | ERROR = 'ERROR', 25 | FATAL = 'FATAL', 26 | OFF = 'OFF', 27 | } 28 | 29 | // 内容跟踪类 30 | export class ContextTrace { 31 | constructor( 32 | public readonly context: string, 33 | public readonly path?: string, 34 | public readonly lineNumber?: number, 35 | public readonly columnNumber?: number, 36 | ) {} 37 | } 38 | 39 | Log4js.addLayout('Awesome-nest', (logConfig: any) => { 40 | return (logEvent: Log4js.LoggingEvent): string => { 41 | let moduleName = ''; 42 | let position = ''; 43 | 44 | // 日志组装 45 | const messageList: string[] = []; 46 | logEvent.data.forEach((value: any) => { 47 | if (value instanceof ContextTrace) { 48 | moduleName = value.context; 49 | // 显示触发日志的坐标(行,列) 50 | if (value.lineNumber && value.columnNumber) { 51 | position = `${value.lineNumber}, ${value.columnNumber}`; 52 | } 53 | return; 54 | } 55 | 56 | if (typeof value !== 'string') { 57 | value = Util.inspect(value, false, 3, true); 58 | } 59 | 60 | messageList.push(value); 61 | }); 62 | 63 | // 日志组成部分 64 | const messageOutput: string = messageList.join(' '); 65 | const positionOutput: string = position ? ` [${position}]` : ''; 66 | const typeOutput = `[${logConfig.type}] ${logEvent.pid.toString()} - `; 67 | const dateOutput = `${moment(logEvent.startTime).format( 68 | 'YYYY-MM-DD HH:mm:ss', 69 | )}`; 70 | const moduleOutput: string = moduleName 71 | ? `[${moduleName}] ` 72 | : '[LoggerService] '; 73 | let levelOutput = `[${logEvent.level}] ${messageOutput}`; 74 | 75 | // 根据日志级别,用不同颜色区分 76 | switch (logEvent.level.toString()) { 77 | case LoggerLevel.DEBUG: 78 | levelOutput = Chalk.green(levelOutput); 79 | break; 80 | case LoggerLevel.INFO: 81 | levelOutput = Chalk.cyan(levelOutput); 82 | break; 83 | case LoggerLevel.WARN: 84 | levelOutput = Chalk.yellow(levelOutput); 85 | break; 86 | case LoggerLevel.ERROR: 87 | levelOutput = Chalk.red(levelOutput); 88 | break; 89 | case LoggerLevel.FATAL: 90 | levelOutput = Chalk.hex('#DD4C35')(levelOutput); 91 | break; 92 | default: 93 | levelOutput = Chalk.grey(levelOutput); 94 | break; 95 | } 96 | 97 | return `${Chalk.green(typeOutput)}${dateOutput} ${Chalk.yellow( 98 | moduleOutput, 99 | )}${levelOutput}${positionOutput}`; 100 | }; 101 | }); 102 | 103 | // 注入配置 104 | Log4js.configure(log4jsConfig); 105 | 106 | // 实例化 107 | const logger = Log4js.getLogger(); 108 | logger.level = LoggerLevel.TRACE; 109 | 110 | export class Logger implements LoggerService { 111 | log(...args) { 112 | logger.info(Logger.getStackTrace(), ...args); 113 | } 114 | error(message: string, trace: string, ...args) { 115 | logger.error(message, trace, ...args); 116 | } 117 | warn(...args) { 118 | logger.warn(Logger.getStackTrace(), ...args); 119 | } 120 | 121 | static trace(...args) { 122 | logger.trace(Logger.getStackTrace(), ...args); 123 | } 124 | 125 | static debug(...args) { 126 | logger.debug(Logger.getStackTrace(), ...args); 127 | } 128 | 129 | static log(...args) { 130 | logger.info(Logger.getStackTrace(), ...args); 131 | } 132 | 133 | static info(...args) { 134 | logger.info(Logger.getStackTrace(), ...args); 135 | } 136 | 137 | static warn(...args) { 138 | logger.warn(Logger.getStackTrace(), ...args); 139 | } 140 | 141 | static warning(...args) { 142 | logger.warn(Logger.getStackTrace(), ...args); 143 | } 144 | 145 | static error(...args) { 146 | logger.error(Logger.getStackTrace(), ...args); 147 | } 148 | 149 | static fatal(...args) { 150 | logger.fatal(Logger.getStackTrace(), ...args); 151 | } 152 | 153 | static access(message: string, ...args) { 154 | const loggerCustom = Log4js.getLogger('http'); 155 | loggerCustom.info(message, ...args); 156 | } 157 | 158 | // 日志追踪,可以追溯到哪个文件、第几行第几列 159 | static getStackTrace(deep = 2): string { 160 | const stackList: StackTrace.StackFrame[] = StackTrace.getSync(); 161 | const stackInfo: StackTrace.StackFrame = stackList[deep]; 162 | 163 | const lineNumber: number = stackInfo.lineNumber; 164 | const columnNumber: number = stackInfo.columnNumber; 165 | const fileName: string = stackInfo.fileName; 166 | const basename: string = Path.basename(fileName); 167 | return `${basename}(line: ${lineNumber}, column: ${columnNumber}): \n`; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/utils/terminal-help-text-console.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ahwgs 3 | * @Date: 2020-11-16 15:17:26 4 | * @Last Modified by: ahwgs 5 | * @Last Modified time: 2020-12-03 13:47:26 6 | */ 7 | import { BannerLog } from '@/config/module/get-banner'; 8 | import { Logger } from './log4js'; 9 | 10 | type paramType = { 11 | Port: string | number; 12 | DocUrl: string; 13 | ApiPrefix: string; 14 | }; 15 | /** 16 | * 打印相关的帮助信息到终端 17 | * @param params 18 | */ 19 | export function terminalHelpTextConsole(): void { 20 | const params = { 21 | Port: process.env.SERVE_LISTENER_PORT, 22 | DocUrl: process.env.SWAGGER_SETUP_PATH, 23 | ApiPrefix: process.env.SWAGGER_ENDPOINT_PREFIX, 24 | } as paramType; 25 | 26 | const Host = `http://localhost`; 27 | Logger.log(BannerLog); 28 | Logger.log('Swagger文档链接:', `${Host}:${params.Port}/${params.DocUrl}`); 29 | Logger.log('Restful接口链接:', `${Host}:${params.Port}/${params.ApiPrefix}`); 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./src", 13 | "incremental": true, 14 | "paths": { 15 | "@": ["./"], 16 | "@/*": ["./*"] 17 | }, 18 | } 19 | } 20 | --------------------------------------------------------------------------------