├── README.md ├── nestjs-template ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode │ └── settings.json ├── nest-cli.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── common │ │ ├── decorators │ │ │ ├── customize.ts │ │ │ ├── gql.user.ts │ │ │ └── ip.address.ts │ │ ├── filters │ │ │ └── http-exception.filter.ts │ │ ├── graqhql-exception.filter.ts │ │ ├── logger.middleware.ts │ │ ├── pipes │ │ │ └── validation.pipe.ts │ │ ├── public.entity.ts │ │ ├── public.module.ts │ │ └── transform.return.ts │ ├── config │ │ ├── database.ts │ │ ├── graphQL.ts │ │ ├── jwtSecret.ts │ │ ├── monitor.ts │ │ └── system.ts │ ├── main.ts │ ├── modules │ │ ├── auth │ │ │ ├── auth.controller.ts │ │ │ ├── auth.guard.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.resolver.spec.ts │ │ │ ├── auth.resolver.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto │ │ │ │ └── login.dto.ts │ │ │ ├── dtos │ │ │ │ ├── auth.module.ts │ │ │ │ └── login.input.ts │ │ │ ├── gql-auth.guard.ts │ │ │ ├── jwt.strategy.ts │ │ │ └── local.strategy.ts │ │ ├── graph │ │ │ ├── dtos │ │ │ │ └── graph.graphql.txt │ │ │ ├── graph.entity.ts │ │ │ ├── graph.module.ts │ │ │ ├── graph.resolver.spec.ts │ │ │ ├── graph.resolver.ts │ │ │ ├── graph.service.spec.ts │ │ │ └── graph.service.ts │ │ ├── user │ │ │ ├── dtos │ │ │ │ ├── create-user.input.ts │ │ │ │ ├── modify-user-pwd.input.ts │ │ │ │ ├── update-user.input.ts │ │ │ │ └── user.model.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.resolver.ts │ │ │ └── user.service.ts │ │ └── xxx │ │ │ ├── dto │ │ │ ├── create.user.dto.ts │ │ │ └── create.xxx.dto.ts │ │ │ ├── xxx.controller.spec.ts │ │ │ ├── xxx.controller.ts │ │ │ ├── xxx.entity.ts │ │ │ ├── xxx.graphql.txt │ │ │ ├── xxx.module.ts │ │ │ ├── xxx.resolver.ts │ │ │ ├── xxx.service.spec.ts │ │ │ └── xxx.service.ts │ └── schema.gql ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock └── swagger ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── nest-cli.json ├── package.json ├── public ├── a.jpg └── avatar.png ├── src ├── app.controller.spec.ts ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── common │ ├── decorators │ │ ├── customize.ts │ │ ├── ip.address.ts │ │ └── sessions.ts │ ├── filters │ │ └── http-exception.filter.ts │ ├── logger.middleware.ts │ ├── pipes │ │ └── validation.pipe.ts │ ├── public.entity.ts │ └── transform.return.ts ├── config │ ├── database.ts │ ├── jwtSecret.ts │ ├── monitor.ts │ ├── session.ts │ └── system.ts ├── main.ts └── modules │ ├── global │ ├── global.controller.spec.ts │ ├── global.controller.ts │ ├── global.module.ts │ ├── global.service.spec.ts │ └── global.service.ts │ ├── order │ ├── order.controller.spec.ts │ ├── order.controller.ts │ ├── order.module.ts │ ├── order.service.spec.ts │ └── order.service.ts │ └── user │ ├── dto │ └── index.ts │ ├── user.controller.spec.ts │ ├── user.controller.ts │ ├── user.entity.ts │ ├── user.module.ts │ ├── user.service.spec.ts │ └── user.service.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /README.md: -------------------------------------------------------------------------------- 1 | # nestjs-template 2 | nestjs模板构建、使用教程、功能的实现与封装 3 | 4 | > 前提需要安装Node.js环境 5 | 6 | ## 概览 7 | 8 | - [x] 定时执行任务 9 | - [x] 文件上传和下载 10 | - [x] 邮件服务(发送邮件) 11 | - [x] 任务队列 (排队处理数据) 12 | - [x] 监控服务器的性能、接口等 13 | - [x] 封装异常处理(统一返回错误格式) 14 | - [x] JWT鉴权,权限相关控制(注册/登陆) 15 | - [x] HttpModule请求第三方接口(Axios封装) 16 | - [x] 使用swagger生成API文档(提供给前端) 17 | - [x] 使用GraphQL做接口 (前端可自定义数据) 18 | - [x] 使用Typeorm操作数据库(Mysql的操作) 19 | - [x] logger监听接口请求(打印前端请求详情) 20 | - [x] 守卫验证角色 ( 判断是否有权限调用 ) 21 | - [x] pipe管道转换参数类型(转换前端传递的值) 22 | 23 | ...... 24 | 25 | - [语雀](https://www.yuque.com/nangdie/datrmc) 26 | - [Github](https://github.com/nangdie/nestjs-template) 27 | 28 | ## 1. 安装启动项目 29 | ```git 30 | npm i -g @nestjs/cli 31 | ``` 32 | ### 1.1 构建项目 33 | > 将 service-nest 替换成你自己的项目名称 34 | 35 | ```git 36 | nest new service-nest 37 | ``` 38 | 不出意外你的项目文件如下 39 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2991766/1607409404633-1d3f6622-5c60-4f26-8bde-eafe5afaf461.png#align=left&display=inline&height=388&margin=%5Bobject%20Object%5D&name=image.png&originHeight=388&originWidth=273&size=17783&status=done&style=none&width=273) 40 | ### 1.2 启动项目 41 | > 更多命令查看 package.json文件 42 | 43 | ```git 44 | yarn start 45 | or 46 | npm start 47 | 如你希望文件变化项目便自动重启,就在后面加:dev 48 | yarn start:dev 49 | or 50 | npm start:dev 51 | ``` 52 | > 访问 [http://localhost:3000/](http://localhost:3000/) ,当你看到 Hello World! ,说明已正常运行。 53 | 54 | ### 1.3 路由加前缀 55 | ```typescript 56 | import { NestFactory } from '@nestjs/core'; 57 | 58 | const app = await NestFactory.create(AppModule); 59 | app.setGlobalPrefix('api'); // 配置路由前缀 http://xxx:3000/api/* 60 | await app.listen(3000); 61 | ``` 62 | ## 3. 在src下多创建几个文件 63 | > 在src下几个重要的文件介绍 64 | 65 | | **文件夹/文件** | **说明** | 66 | | --- | --- | 67 | | main.ts 项目入口 | 整个项目的入口文件 | 68 | | app.module.ts 模块集合 | 所有项目模块的集合 | 69 | | modules 主要工作 | 路由模块存放/前端请求的接口可能都在这 | 70 | | common 公共函数 | 存放公共方法/模块/类 | 71 | | config 配置文件 | 存放固定参数配置文件,如:数据库/邮件/微信等 | 72 | | tasks 定时任务 | 存放定时任务,如:定时更新任务、爬取页面等 | 73 | | templates 模板文件 | 存放模板,如:邮件/HTML/短信模板等 | 74 | 75 | ## 4. 关于modules的几个约定文件 76 | | **文件** | **快捷命令** | **说明** | 77 | | --- | --- | --- | 78 | | controller | nest g co modules/ | 控制器处理get|put|post等请求的参数,定义路由 | 79 | | module | nest g mo modules/ | 负责功能模块引入、关联 | 80 | | service | nest g s modules/ | 负责返回结果、查询数据库、查询其他接口 | 81 | | resolver | nest g r modules/ | graphql | 82 | | entity | 不支持 | 管理数据库表结构 | 83 | | dto/ | 不支持 | 定义数据类型 | 84 | | *.spec.ts | 自动 | 相关测试文件 | 85 | | | | | 86 | 87 | ### 4.1 module 示例 88 | ```typescript 89 | import { Module } from '@nestjs/common'; 90 | import { TypeOrmModule } from '@nestjs/typeorm' 91 | import { XxxController } from './xxx.controller'; 92 | import { XxxService } from './xxx.service';; 93 | import { XxxEntity } from './xxx.entity'; 94 | 95 | @Module({ 96 | imports: [TypeOrmModule.forFeature([XxxEntity])], 97 | controllers: [XxxController], 98 | providers: [XxxService], 99 | exports: [ 100 | TypeOrmModule.forFeature([XxxEntity]), 101 | ], 102 | }) 103 | export class XxxModule { } 104 | ``` 105 | ### 4.2 entity 示例 106 | ```typescript 107 | import { Entity, PrimaryGeneratedColumn, ManyToOne, Column, BeforeInsert } from 'typeorm'; 108 | import { Exclude, Expose } from 'class-transformer'; 109 | 110 | 111 | @Entity({ name: 'xxx' }) // name 填入表名称,会自动创建这个表 112 | export class XxxEntity { 113 | 114 | @PrimaryGeneratedColumn({ 115 | comment: '自增ID' 116 | }) 117 | id: number; 118 | 119 | @Column('tinyint', { 120 | nullable: false, 121 | default: () => 0, 122 | name: 'is_admin', 123 | comment: '是否管理员? 1:是, 0:不是' 124 | }) 125 | is_admin: number; 126 | 127 | @Column({ 128 | length: 500, 129 | comment: '名字' 130 | }) 131 | name: string; 132 | 133 | @Column('text') 134 | description: string; 135 | 136 | @Column() 137 | filename: string; 138 | 139 | @Column('int', { 140 | nullable: false, 141 | default: () => 1, 142 | name: 'sort', 143 | comment: '排序' 144 | }) 145 | sort: number; 146 | 147 | @Column('varchar', { 148 | nullable: true, 149 | length: 100, 150 | name: 'url', 151 | comment: 'url地址' 152 | }) 153 | url: string | null; 154 | 155 | @Exclude() // 表示排除字段 156 | @Column('varchar', { 157 | nullable: false, 158 | length: 100, 159 | name: 'password', 160 | comment: '密码' 161 | }) 162 | password: string; 163 | 164 | // 插入数据库前先给密码加密 165 | @BeforeInsert() 166 | public makePassword() { 167 | // this.password = makePassword(this.password) 168 | } 169 | 170 | // 检查密码是否正确 171 | public checkPassword(password: string, sqlPassword: string) { 172 | // return checkPassword(password, sqlPassword); 173 | } 174 | 175 | // 重新定义返回数据结构, 注意: 会导致上面的Exclude和Expose失效 !!! 176 | public toResponseObject(isShowPassword = false): object { 177 | const { password, ...params } = this; 178 | if (isShowPassword) { 179 | return Object.assign(isShowPassword, { password }); 180 | } else { 181 | return params; 182 | } 183 | } 184 | 185 | } 186 | ``` 187 | ### 4.3 service 示例 188 | ```typescript 189 | import { Injectable } from '@nestjs/common'; 190 | import { InjectRepository } from '@nestjs/typeorm'; 191 | import { Repository } from 'typeorm'; 192 | import { CreateXxxDto } from './dto/create.xxx.dto'; 193 | import { XxxEntity } from './xxx.entity'; 194 | 195 | @Injectable() 196 | export class XxxService { 197 | constructor(@InjectRepository(XxxEntity) private readonly xxxRepository: Repository, ) { } 198 | 199 | get(id: string): string { 200 | return '使用了Get方法,传入ID为:' + id 201 | } 202 | create(body: CreateXxxDto): string { 203 | return '使用了post请求,传入内容' + JSON.stringify(body) 204 | } 205 | 206 | query(): Promise { 207 | return this.xxxRepository.find() // 查询数据库 208 | } 209 | } 210 | ``` 211 | ### 4.4 controller 示例 212 | ```typescript 213 | import { Controller, Get, Param, Body, Post } from '@nestjs/common'; 214 | import { XxxService } from './xxx.service'; 215 | import { CreateXxxDto } from './dto/create.xxx.dto'; 216 | 217 | @Controller('xxx') 218 | export class XxxController { 219 | constructor( 220 | private readonly xxxService: XxxService, 221 | ) { } 222 | 223 | @Get('all') 224 | query() { // 查询数据库 225 | return this.xxxService.query() 226 | } 227 | 228 | // 处理前端传过来的数据 229 | @Get(':id') 230 | get(@Param('id') id: string): string { 231 | return this.xxxService.get(id) 232 | } 233 | 234 | @Post() 235 | create(@Body() body: CreateXxxDto): string { 236 | return this.xxxService.create(body) 237 | } 238 | } 239 | ``` 240 | ### 4.5 dto/xxx 示例 241 | > dto/create.xxx.dto.ts 242 | 243 | ```typescript 244 | import { IsString, IsNotEmpty } from 'class-validator'; 245 | 246 | export class CreateXxxDto { 247 | @IsString({ message: '用户名必须为字符类型' }) 248 | @IsNotEmpty({ message: '用户名不能为空' }) 249 | readonly name: string 250 | 251 | 252 | @IsString({ message: '密码必须为字符串类型' }) 253 | @IsNotEmpty({ message: '密码不能为空' }) 254 | readonly pwd: string 255 | } 256 | ``` 257 | 258 | 259 | ## 5. 补充需要用到的依赖 260 | ```typescript 261 | npm i nestjs-config class-transformer class-validator 262 | ``` 263 | ### nestjs-config 示例 264 | > 将src/config/* 下面的文件导入 265 | 266 | ```typescript 267 | ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')), // 配置导入路径 268 | *.forRootAsync({ 269 | // useFactory 和 inject 一起使用 270 | useFactory: (config: ConfigService) => config.get('database'), 271 | inject: [ConfigService] 272 | }) 273 | // 相当于 274 | import xxx from "src/config/xxx"; 275 | ``` 276 | ### class-transformer 示例 277 | ```typescript 278 | import { Entity, PrimaryGeneratedColumn, ManyToOne, Column } from 'typeorm'; 279 | import { Exclude, Expose } from 'class-transformer'; 280 | 281 | @Entity({ name: 'xxx' }) // name 填入表名称,会自动创建这个表 282 | export class XxxEntity { 283 | @PrimaryGeneratedColumn({ 284 | comment: '自增ID' 285 | }) 286 | id: number; 287 | 288 | @Exclude() // 表示排除字段 289 | @Column('varchar', { 290 | nullable: false, 291 | length: 100, 292 | name: 'password', 293 | comment: '密码' 294 | }) 295 | password: string; 296 | 297 | } 298 | ``` 299 | ### class-validator 示例 300 | > 验证数据 301 | 302 | ```typescript 303 | import { IsString, IsNotEmpty } from 'class-validator'; 304 | 305 | export class CreateXxxDto { 306 | @IsString({ message: '用户名必须为字符类型' }) 307 | @IsNotEmpty({ message: '用户名不能为空' }) 308 | readonly name: string 309 | 310 | 311 | @IsString({ message: '密码必须为字符串类型' }) 312 | @IsNotEmpty({ message: '密码不能为空' }) 313 | readonly pwd: string 314 | } 315 | ``` 316 | ## 6. 路由模块导入到app.module.ts 317 | > 基本上所有提供给前端的路由都导入到src/app.module.ts 318 | 319 | ```typescript 320 | import { Module } from '@nestjs/common'; 321 | import { XxxModule } from './modules/xxx/xxx.module'; 322 | @Module({ 323 | imports: [ 324 | XxxModule 325 | ], 326 | }) 327 | export class AppModule { } 328 | ``` 329 | ## 总结 330 | ```git 331 | npm i -g @nestjs/cli 332 | nest new x 333 | yarn start:dev 334 | ``` 335 | ### 流程 336 | 337 | 1. yarn start:dev 338 | 1. 创建**logger**监听请求 339 | 1. 使用异常处理 **ExceptionFilter **封装统一格式 340 | 1. 添加**pipe**处理数据类型 341 | 1. **swagger **或者** GraphQL **提供API给前端 342 | 1. 构建路由常用四件套 [** controller + module + entity + service **] 343 | 1. **JWT** 或者**session** 登陆/注册 344 | 1. 守卫验证权限 345 | -------------------------------------------------------------------------------- /nestjs-template/.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/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /nestjs-template/.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 -------------------------------------------------------------------------------- /nestjs-template/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /nestjs-template/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib" 3 | } -------------------------------------------------------------------------------- /nestjs-template/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /nestjs-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "service-nest", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/common": "^7.5.5", 25 | "@nestjs/core": "^7.5.5", 26 | "@nestjs/graphql": "^7.9.1", 27 | "@nestjs/jwt": "^7.2.0", 28 | "@nestjs/passport": "^7.1.5", 29 | "@nestjs/platform-express": "^7.5.5", 30 | "@nestjs/swagger": "^4.7.5", 31 | "@nestjs/typeorm": "^7.1.5", 32 | "apollo-server-express": "^2.19.0", 33 | "bcrypt": "^5.0.0", 34 | "body-parser-xml": "^2.0.1", 35 | "class-transformer": "^0.3.1", 36 | "class-validator": "^0.12.2", 37 | "dayjs": "^1.9.7", 38 | "express-rate-limit": "^5.2.3", 39 | "graphql": "^15.4.0", 40 | "graphql-tools": "^7.0.2", 41 | "helmet": "^4.2.0", 42 | "mysql": "^2.18.1", 43 | "nestjs-config": "^1.4.7", 44 | "passport": "^0.4.1", 45 | "passport-jwt": "^4.0.0", 46 | "passport-local": "^1.0.0", 47 | "reflect-metadata": "^0.1.13", 48 | "request-ip": "^2.1.3", 49 | "rimraf": "^3.0.2", 50 | "rxjs": "^6.6.3", 51 | "swagger-ui-express": "^4.1.5", 52 | "typeorm": "^0.2.29" 53 | }, 54 | "devDependencies": { 55 | "@nestjs/cli": "^7.5.3", 56 | "@nestjs/schematics": "^7.2.5", 57 | "@nestjs/testing": "^7.5.5", 58 | "@types/express": "^4.17.9", 59 | "@types/jest": "26.0.18", 60 | "@types/node": "^14.14.12", 61 | "@types/passport-jwt": "^3.0.3", 62 | "@types/passport-local": "^1.0.33", 63 | "@types/supertest": "^2.0.10", 64 | "@typescript-eslint/eslint-plugin": "^4.9.1", 65 | "@typescript-eslint/parser": "^4.9.1", 66 | "eslint": "^7.15.0", 67 | "eslint-config-prettier": "^7.0.0", 68 | "eslint-plugin-import": "^2.22.1", 69 | "jest": "^26.6.3", 70 | "prettier": "^2.2.1", 71 | "supertest": "^6.0.1", 72 | "ts-jest": "26.4.4", 73 | "ts-loader": "^8.0.11", 74 | "ts-node": "^9.1.1", 75 | "tsconfig-paths": "^3.9.0", 76 | "typescript": "^4.1.2" 77 | }, 78 | "jest": { 79 | "moduleFileExtensions": [ 80 | "js", 81 | "json", 82 | "ts" 83 | ], 84 | "rootDir": "src", 85 | "testRegex": ".spec.ts$", 86 | "transform": { 87 | "^.+\\.(t|j)s$": "ts-jest" 88 | }, 89 | "coverageDirectory": "../coverage", 90 | "testEnvironment": "node" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /nestjs-template/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /nestjs-template/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /nestjs-template/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { resolve, join } from 'path'; 2 | import { Module } from '@nestjs/common'; 3 | import { APP_GUARD } from '@nestjs/core'; 4 | import { ConfigModule, ConfigService } from 'nestjs-config'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { GraphQLModule } from '@nestjs/graphql'; 7 | 8 | import { AppController } from './app.controller'; 9 | import { AppService } from './app.service'; 10 | import { XxxModule } from './modules/xxx/xxx.module'; 11 | // import { GraphModule } from './modules/graph/graph.module'; 12 | import { UsersModule } from './modules/user/user.module'; 13 | import { AuthModule } from './modules/auth/auth.module'; 14 | 15 | import { RoleAuthGuard } from './modules/auth/auth.guard'; 16 | 17 | @Module({ 18 | imports: [ 19 | ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')), 20 | TypeOrmModule.forRootAsync({ 21 | useFactory: (config: ConfigService) => config.get('database'), 22 | inject: [ConfigService], 23 | }), 24 | GraphQLModule.forRootAsync({ 25 | useFactory: (config: ConfigService) => config.get('graphQL'), 26 | inject: [ConfigService], 27 | }), 28 | 29 | // 路由模块 30 | AuthModule, 31 | XxxModule, 32 | UsersModule, // graphql 代码优先 33 | // GraphModule, // graphql 模式优先 34 | ], 35 | controllers: [AppController], 36 | providers: [ 37 | AppService, 38 | // 使用全局 JWT 守卫 ,默认开启,关闭时使用装饰器: @noAuth() 39 | // { 40 | // provide: APP_GUARD, 41 | // useClass: RoleAuthGuard, 42 | // }, 43 | ], 44 | }) 45 | export class AppModule {} 46 | 47 | /* 局部监听 */ 48 | // implements NestModule { 49 | // configure(consumer: MiddlewareConsumer) { 50 | // consumer.apply(LoggerMiddleware) 51 | // // 不监听的路由 52 | // .exclude() 53 | // // 需要监听的路由 54 | // .forRoutes({ 55 | // path: '*', 56 | // method: RequestMethod.ALL 57 | // }) 58 | // } 59 | // } 60 | -------------------------------------------------------------------------------- /nestjs-template/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nestjs-template/src/common/decorators/customize.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common' 2 | 3 | export const NoAuth = () => SetMetadata('no-auth', true); -------------------------------------------------------------------------------- /nestjs-template/src/common/decorators/gql.user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { GqlExecutionContext } from '@nestjs/graphql'; 3 | // 装饰器获取用户信息 4 | export const GqlCurrentUser = createParamDecorator( 5 | (data: unknown, context: ExecutionContext) => { 6 | const ctx = GqlExecutionContext.create(context); 7 | return ctx.getContext().req.user; 8 | }, 9 | ); -------------------------------------------------------------------------------- /nestjs-template/src/common/decorators/ip.address.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator } from '@nestjs/common'; 2 | import * as requestIp from 'request-ip'; 3 | 4 | /* 5 | * @Description: 自定义获取ip地址的装饰器 6 | * @Github: https://github.com/nangdie 7 | * @Email: nangdie.a@gmail.com 8 | */ 9 | export const IpAddress = createParamDecorator((_, req) => { 10 | if (req.clientIp) return req.clientIp; 11 | return requestIp.getClientIp(req); 12 | }); -------------------------------------------------------------------------------- /nestjs-template/src/common/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpException, 6 | Logger, 7 | HttpStatus, 8 | } from '@nestjs/common'; 9 | import { GqlArgumentsHost } from '@nestjs/graphql'; 10 | import * as dayjs from 'dayjs'; 11 | 12 | // 需要实现 ExceptionFilter 接口,里面有一个catch方法需要实现 13 | @Catch(HttpException) 14 | export class HttpExceptionFilter implements ExceptionFilter { 15 | catch(exception: HttpException, host: ArgumentsHost) { 16 | const ctx = host.switchToHttp(); //获取状态信息 17 | const response = ctx.getResponse(); 18 | const request = ctx.getRequest() 19 | const status = 20 | exception instanceof HttpException 21 | ? exception.getStatus() 22 | : HttpStatus.INTERNAL_SERVER_ERROR; 23 | const exceptionRes = exception.getResponse() as { error: string, message: string } 24 | if (response?.status) { 25 | const { error, message } = exceptionRes 26 | const errorResponse = { 27 | timestamp: dayjs().format('YYYY-MM-DD HH:mm:ss'), 28 | message: message || exceptionRes, 29 | path: request?.url, 30 | status, 31 | error, 32 | } 33 | Logger.error( 34 | `[${dayjs().format('YYYY-MM-DD HH:mm:ss')}] ${request?.method} ${request?.url}`, 35 | JSON.stringify(errorResponse), 36 | 'HttpExceptionFilter', 37 | ); 38 | response.status(status).json(errorResponse); 39 | } else { 40 | // 单独处理过滤graqhql的异常 41 | const gqlHost = GqlArgumentsHost.create(host); 42 | const httpRequest = this.getRequestFromCtx(ctx) 43 | exception.message = `${exceptionRes.message || ''} ${exceptionRes.error || ''}` 44 | // 异常错误 45 | let request: any = { 46 | graphql: { 47 | args: gqlHost.getArgs(), 48 | root: gqlHost.getRoot(), 49 | }, 50 | } 51 | try { 52 | if (httpRequest) { 53 | request = Object.assign(request, { 54 | url: httpRequest.url, 55 | body: httpRequest.body, 56 | query: httpRequest.query, 57 | params: httpRequest.params, 58 | headers: httpRequest.headers, 59 | currentUser: httpRequest.currentUser 60 | }) 61 | } 62 | request = JSON.stringify(request) 63 | Logger.error( 64 | `[${dayjs().format('YYYY-MM-DD HH:mm:ss')}] ${exception.message}`, 65 | JSON.stringify(request), 66 | 'GraphQLExceptionFilter' 67 | ) 68 | } catch { 69 | Logger.error( 70 | `[${dayjs().format('YYYY-MM-DD HH:mm:ss')}] ${exception.message}`, 71 | '处理graqhql异常时', 72 | 'GraphQLExceptionFilter' 73 | ) 74 | } 75 | return exception; 76 | } 77 | } 78 | 79 | getRequestFromCtx(ctx) { 80 | for (let arg of ctx.args) { 81 | if (arg && arg.request && arg.request?.url) { 82 | return arg.request 83 | } 84 | } 85 | } 86 | 87 | 88 | } -------------------------------------------------------------------------------- /nestjs-template/src/common/graqhql-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { Catch, ArgumentsHost, HttpException, Logger, HttpStatus, } from '@nestjs/common' 2 | import { GqlExceptionFilter, GqlArgumentsHost } from '@nestjs/graphql' 3 | import * as dayjs from 'dayjs'; 4 | 5 | // 过滤graqhql的异常 6 | @Catch(HttpException) 7 | export class GraphQLExceptionFilter implements GqlExceptionFilter { 8 | catch(exception: HttpException, host: ArgumentsHost) { 9 | const gqlHost = GqlArgumentsHost.create(host); 10 | const ctx = host.switchToHttp(); 11 | const httpRequest = this.getRequestFromCtx(ctx) 12 | const message = exception.getResponse() as { error: string, message: string } 13 | exception.message = `${message.message || ''} ${message.error || ''}` 14 | // 异常错误 15 | let request: any = { 16 | graphql: { 17 | args: gqlHost.getArgs(), 18 | root: gqlHost.getRoot(), 19 | }, 20 | } 21 | console.log(exception.message,'.exception.message') 22 | try { 23 | if (httpRequest) { 24 | request = Object.assign(request, { 25 | url: httpRequest.url, 26 | body: httpRequest.body, 27 | query: httpRequest.query, 28 | params: httpRequest.params, 29 | headers: httpRequest.headers, 30 | currentUser: httpRequest.currentUser 31 | }) 32 | } 33 | request = JSON.stringify(request) 34 | Logger.error( 35 | `[${dayjs().format('YYYY-MM-DD HH:mm:ss')}] ${exception.message}`, 36 | JSON.stringify(request), 37 | 'GraphQLExceptionFilter' 38 | ) 39 | } catch (error) { 40 | Logger.error( 41 | `[${dayjs().format('YYYY-MM-DD HH:mm:ss')}] ${exception.message}`, 42 | 'GraphQLExceptionFilter' 43 | ) 44 | } 45 | return exception; 46 | } 47 | 48 | getRequestFromCtx(ctx) { 49 | for (let arg of ctx.args) { 50 | if (arg && arg.request && arg.request?.url) { 51 | return arg.request 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /nestjs-template/src/common/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware, Logger } from '@nestjs/common'; 2 | import { Request, Response } from 'express'; 3 | import * as dayjs from "dayjs"; 4 | import { isEmpty } from 'class-validator'; 5 | 6 | @Injectable() // 使用Injectable装饰器 7 | export class LoggerMiddleware implements NestMiddleware { // 使用NestMiddleware实现 8 | use(req: Request, res: Response, next: Function) { // 重写方法 9 | const { method, path, baseUrl } = req; 10 | Logger.log(`[${dayjs().format('YYYY-MM-DD HH:mm:ss')}] ${method} ${baseUrl}`, 'LoggerMiddleware') 11 | // 必须调用next()将控制权传递给下一个中间件功能。否则请求将被挂起 12 | next(); 13 | } 14 | } 15 | 16 | 17 | // 全局监听 18 | export function LoggerGlobal(req: Request, res: Response, next: Function) { 19 | const { method, path } = req; 20 | Logger.log(`[${dayjs().format('YYYY-MM-DD HH:mm:ss')}] ${method} ${path}`, 'LoggerGlobal'); 21 | next(); 22 | }; 23 | -------------------------------------------------------------------------------- /nestjs-template/src/common/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentMetadata, HttpException, HttpStatus, Injectable, PipeTransform, Type, } from '@nestjs/common'; 2 | import { plainToClass } from 'class-transformer'; 3 | import { validate } from 'class-validator'; 4 | 5 | @Injectable() 6 | export class ValidationPipe implements PipeTransform { 7 | async transform(value: any, metadata: ArgumentMetadata) { 8 | const { metatype } = metadata; 9 | if (!metatype || !this.toValidate(metatype)) return value; 10 | const object = plainToClass(metatype, value); 11 | const errors = await validate(object); 12 | if (errors.length > 0) { 13 | // const errObj = {}; 14 | // errors.forEach(err => { 15 | // const { 16 | // property, 17 | // constraints, 18 | // } = err; 19 | // errObj[property] = Object.values(constraints); 20 | // }); 21 | const errObj = Object.values(errors[0].constraints)[0]; 22 | throw new HttpException( 23 | { message: '请求参数验证失败 ', error: errObj }, 24 | HttpStatus.BAD_REQUEST, 25 | ); 26 | } 27 | return value; 28 | } 29 | 30 | private toValidate(metatype: Type): boolean { 31 | const types = [String, Boolean, Number, Array, Object]; 32 | return !types.find(type => metatype === type); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nestjs-template/src/common/public.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; 2 | import { Exclude } from 'class-transformer'; 3 | 4 | export class PublicEntity extends BaseEntity { 5 | 6 | @PrimaryGeneratedColumn({ 7 | type: 'int', 8 | name: 'id', 9 | comment: '主键id' 10 | }) 11 | id: number; 12 | 13 | @Exclude() 14 | @Column('tinyint', { 15 | nullable: false, 16 | default: () => 0, 17 | name: 'is_del', 18 | comment: '是否删除,1表示删除,0表示正常' 19 | }) 20 | isDel: number; 21 | 22 | @CreateDateColumn({ 23 | type: 'timestamp', 24 | nullable: false, 25 | name: 'created_at', 26 | comment: '创建时间' 27 | }) 28 | createdAt: Date; 29 | 30 | @UpdateDateColumn({ 31 | type: 'timestamp', 32 | nullable: false, 33 | name: 'updated_at', 34 | comment: '更新时间', 35 | }) 36 | updatedAt: Date; 37 | } -------------------------------------------------------------------------------- /nestjs-template/src/common/public.module.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql' 2 | 3 | // @ObjectType() 4 | // export class ResultPublicModel { 5 | // @Field() 6 | // public readonly result: T 7 | 8 | // @Field() 9 | // public readonly code: number 10 | 11 | // @Field() 12 | // public readonly message: string 13 | 14 | // } 15 | 16 | @ObjectType() 17 | export class ResultPublicModel { 18 | @Field() 19 | public readonly code: number 20 | 21 | @Field() 22 | public readonly message: string 23 | 24 | } -------------------------------------------------------------------------------- /nestjs-template/src/common/transform.return.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | import { classToPlain } from 'class-transformer'; 5 | import systemConfig from "../config/system"; 6 | 7 | 8 | const transformValue = (result: any, code: number = 0, message: string = '请求成功') => { 9 | const { returnFormat } = systemConfig 10 | return { 11 | [returnFormat.result]: classToPlain(result), 12 | [returnFormat.code]: code, 13 | [returnFormat.message]: message, 14 | } 15 | } 16 | 17 | // 处理统一成功返回值 18 | @Injectable() 19 | export class TransformReturnInterceptor implements NestInterceptor { 20 | intercept(context: ExecutionContext, next: CallHandler): Observable { 21 | const host = context.switchToHttp(); 22 | const request = host.getRequest(); 23 | 24 | // 不需要格式化的接口 25 | if (['/api/status'].includes(request?.url)) { 26 | return next.handle(); 27 | } 28 | 29 | return next.handle().pipe(map(transformValue)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nestjs-template/src/config/database.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { TypeOrmModuleOptions } from '@nestjs/typeorm'; 3 | 4 | const database: TypeOrmModuleOptions = { 5 | type: 'mysql', 6 | host: 'localhost', 7 | port: 3306, 8 | username: 'root', 9 | password: 'root', 10 | database: 'nest', 11 | entities: [join(__dirname, '../', '**/**.entity{.ts,.js}')], //自动检测包含entity的文件,并引入 12 | synchronize: true, 13 | // logging: false, 是否打印记录 14 | }; 15 | 16 | export default database 17 | -------------------------------------------------------------------------------- /nestjs-template/src/config/graphQL.ts: -------------------------------------------------------------------------------- 1 | import { GqlModuleOptions } from "@nestjs/graphql" 2 | import { ValidationError } from 'apollo-server-express' 3 | import { join } from "path" 4 | 5 | const graphQl: GqlModuleOptions = { 6 | useGlobalPrefix: true, 7 | installSubscriptionHandlers: true, 8 | autoSchemaFile: join(process.cwd(), 'src/schema.gql'), 9 | sortSchema: true, 10 | typePaths: ['./**/*.gql'], 11 | // context: ({ req }) => ({ req }), 12 | context: ({ req }) => ({ jwt: req.headers.authorization }), 13 | formatError(error: ValidationError) { 14 | const { 15 | message, 16 | extensions: { code }, 17 | } = error 18 | return { 19 | code, 20 | message, 21 | timestamp: new Date(), 22 | } 23 | }, 24 | } 25 | 26 | export default graphQl -------------------------------------------------------------------------------- /nestjs-template/src/config/jwtSecret.ts: -------------------------------------------------------------------------------- 1 | const jwtSecret = { 2 | secret: 'xxx', // 密匙 3 | signOptions: { 4 | expiresIn: '1h' //失效时间 5 | }, 6 | } 7 | export default jwtSecret -------------------------------------------------------------------------------- /nestjs-template/src/config/monitor.ts: -------------------------------------------------------------------------------- 1 | // 查看监控信息的页面地址 localhost:3000/api/status 2 | export default { 3 | pageTitle: 'Nest.js 监控页面', 4 | port: 3000, // 端口要与service的端口一致 5 | path: '/status', 6 | ignoreStartsWith: '/healt/alive', 7 | spans: [ 8 | { 9 | interval: 1, 10 | retention: 60, 11 | }, 12 | { 13 | interval: 5, 14 | retention: 60, 15 | }, 16 | { 17 | interval: 15, 18 | retention: 60, 19 | }, 20 | ], 21 | chartVisibility: { // 监测项 22 | cpu: true, 23 | mem: true, 24 | load: true, 25 | responseTime: true, 26 | rps: true, 27 | statusCodes: true, 28 | }, 29 | healthChecks: [], 30 | }; -------------------------------------------------------------------------------- /nestjs-template/src/config/system.ts: -------------------------------------------------------------------------------- 1 | const system = { 2 | adminPath: 'admin', // 管理员路径 3 | staticPrefixPath: '', // 静态文件的前缀 4 | supportImgTypes: ['.png', '.jpg', '.gif', '.jpeg'], //支持的图片 5 | defaultAccount: 'admin', // 默认账号 6 | defaultPassword: '123456', // 默认密码 7 | returnFormat: { 8 | result: 'result', 9 | code: 'code', 10 | message: 'message' 11 | } 12 | } 13 | 14 | export default system -------------------------------------------------------------------------------- /nestjs-template/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import * as helmet from 'helmet'; 3 | import * as requestIp from 'request-ip'; 4 | import * as rateLimit from 'express-rate-limit'; 5 | import { Logger } from '@nestjs/common'; 6 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 7 | import { AppModule } from './app.module'; 8 | import { LoggerGlobal } from './common/logger.middleware'; 9 | import { HttpExceptionFilter } from './common/filters/http-exception.filter'; 10 | import { TransformReturnInterceptor } from './common/transform.return'; 11 | import { ValidationPipe } from './common/pipes/validation.pipe'; 12 | import { GraphQLExceptionFilter } from './common/graqhql-exception.filter'; 13 | 14 | const bodyParser = require('body-parser'); 15 | require('body-parser-xml')(bodyParser); 16 | 17 | const PORT = process.env.PORT || 3000; 18 | const PREFIX = 'api' 19 | const SWAGGER_V1 = `${PREFIX}/v1/swagger` 20 | async function bootstrap() { 21 | const app = await NestFactory.create(AppModule); 22 | app.setGlobalPrefix(PREFIX); 23 | 24 | const options = new DocumentBuilder() // 创建并配置文档信息 25 | .setTitle('标题') 26 | .setDescription('描述信息') 27 | .setVersion('1.0') 28 | .build(); 29 | const document = SwaggerModule.createDocument(app, options); 30 | // 会自动将所有路由显示出来 31 | SwaggerModule.setup(SWAGGER_V1, app, document); // api/swagger = API文档的路径,访问:http://localhost:3000/api/swagger 32 | 33 | // 跨域资源共享 34 | app.enableCors() 35 | 36 | // 限制访问频率 37 | app.use( 38 | rateLimit({ 39 | windowMs: 15 * 60 * 1000, // 15分钟 40 | max: 500, // 限制15分钟内最多只能访问500次 41 | }), 42 | ); 43 | 44 | app.use(bodyParser.xml({ 45 | limit: '1MB', // 拒绝大于1 MB的有效负载 46 | xmlParseOptions: { 47 | normalize: true, // 在文本节点内修剪空格 48 | normalizeTags: true, // 将标签转换为小写 49 | explicitArray: false // 如果> 1,则仅将节点放入数组 50 | } 51 | })); 52 | 53 | // web安全 54 | // app.use(helmet()) 55 | 56 | // 拦截处理-错误异常 57 | // graphQL专属异常过滤 58 | // app.useGlobalFilters(new GraphQLExceptionFilter()); 59 | app.useGlobalFilters(new HttpExceptionFilter()); 60 | 61 | // 拦截处理-返回格式 62 | app.useGlobalInterceptors(new TransformReturnInterceptor()); 63 | 64 | // 获取访问者ip地址 65 | app.use(requestIp.mw()); 66 | 67 | // 全局监听路由请求 68 | app.use(LoggerGlobal); 69 | 70 | // 使用管道验证数据 71 | app.useGlobalPipes(new ValidationPipe()); 72 | 73 | await app.listen(PORT, () => { 74 | Logger.log(`已启动,接口: http://localhost:${PORT}/${PREFIX} , API文档:http://localhost:${PORT}/${SWAGGER_V1}`); 75 | }); 76 | 77 | } 78 | 79 | bootstrap().catch(err => Logger.error('启动错误:', err)); 80 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Post, 6 | Request, 7 | UseGuards, 8 | } from '@nestjs/common'; 9 | import { AuthGuard } from '@nestjs/passport'; 10 | import { ApiHeader, ApiOperation, ApiTags } from '@nestjs/swagger'; 11 | import { AuthService } from './auth.service'; 12 | import { LoginDto } from './dto/login.dto'; 13 | import { NoAuth } from 'src/common/decorators/customize'; 14 | 15 | @Controller() 16 | @ApiTags('auto') 17 | export class AuthController { 18 | constructor(private readonly authService: AuthService) {} 19 | 20 | /** 21 | * [ 登录 ] 执行login返回 access_token 令牌,用于验证登陆 22 | * @param req 23 | * @param body 必须包含username和password,负责不会通过AuthGuard('local')。也可以修改LocalStrategy实现 24 | */ 25 | @UseGuards(AuthGuard('local')) 26 | @ApiOperation({ summary: '登陆', description: '执行登陆' }) 27 | @Post('login') 28 | // @NoAuth() 29 | async login(@Request() req, @Body() body: LoginDto) { 30 | const user = await this.authService.validateUser(body.name, body.password); 31 | return this.authService.createToken(user); 32 | } 33 | 34 | // [ 获取用户信息 ] 使用JWT验证。访问这个接口时会先验证access_token是否能够访问 35 | @ApiOperation({ summary: '当前用户信息', description: '当前用户信息' }) 36 | @ApiHeader({ 37 | name: 'Token', 38 | description: '验证用户的身份', 39 | required: true, 40 | }) 41 | @UseGuards(AuthGuard('jwt')) 42 | @Get('me') 43 | getProfile(@Request() req) { 44 | return req.user; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { Reflector } from '@nestjs/core'; 4 | import { AuthGuard, IAuthGuard } from '@nestjs/passport'; 5 | 6 | // 自定义身份认证,替代AuthGuard('jwt') 7 | 8 | @Injectable() 9 | export class RoleAuthGuard implements CanActivate { 10 | constructor(private readonly reflector: Reflector) {} 11 | canActivate( 12 | context: ExecutionContext, 13 | ): boolean | Promise | Observable { 14 | // 判断是否使用nnoAuth装饰器 15 | const noAuth = this.reflector.get('no-auth', context.getHandler()); 16 | const guard = RoleAuthGuard.getAuthGuard(noAuth); 17 | // Guard执行原来的canActivate方法 18 | return guard.canActivate(context); 19 | } 20 | 21 | private static getAuthGuard(noAuth: boolean): IAuthGuard { 22 | return noAuth ? new (AuthGuard('local'))() : new (AuthGuard('jwt'))(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { PassportModule } from '@nestjs/passport'; 4 | import { JwtModule } from '@nestjs/jwt'; 5 | import { AuthController } from './auth.controller'; 6 | import { LocalStrategy } from './local.strategy'; 7 | import { JwtStrategy } from './jwt.strategy'; 8 | import { ConfigService } from 'nestjs-config'; 9 | import { UsersModule } from '../user/user.module'; 10 | import { AuthResolver } from './auth.resolver'; 11 | 12 | @Module({ 13 | imports: [ 14 | UsersModule, // 引入user的module 15 | PassportModule, 16 | JwtModule.registerAsync({ 17 | useFactory: (config: ConfigService) => config.get('jwtSecret'), 18 | inject: [ConfigService], 19 | }), //导入配置 config/jwtSecret 20 | ], 21 | // controllers: [AuthController], 22 | providers: [ 23 | AuthResolver, 24 | AuthService, 25 | LocalStrategy, 26 | JwtStrategy, 27 | ], 28 | exports: [AuthService, LocalStrategy, JwtStrategy], 29 | }) 30 | export class AuthModule {} 31 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/auth.resolver.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthResolver } from './auth.resolver'; 3 | 4 | describe('AuthResolver', () => { 5 | let resolver: AuthResolver; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthResolver], 10 | }).compile(); 11 | 12 | resolver = module.get(AuthResolver); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(resolver).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/auth.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Query, Args } from '@nestjs/graphql'; 2 | import { AuthService } from './auth.service'; 3 | import { LoginInputDto } from './dtos/login.input'; 4 | import { NoAuth } from 'src/common/decorators/customize'; 5 | import { ResultAuthModule, ResultAuthToken } from './dtos/auth.module'; 6 | import { GqlCurrentUser } from 'src/common/decorators/gql.user'; 7 | import { UseGuards } from '@nestjs/common'; 8 | import { GqlAuthGuard } from './gql-auth.guard'; 9 | import { AuthGuard } from '@nestjs/passport'; 10 | 11 | @Resolver(() => LoginInputDto) 12 | export class AuthResolver { 13 | constructor(private readonly authService: AuthService) {} 14 | 15 | @Mutation(() => ResultAuthToken) 16 | async login(@Args('input') input: LoginInputDto) { 17 | const user = await this.authService.validateUser( 18 | input.name, 19 | input.password, 20 | ); 21 | return this.authService.createToken(user); 22 | } 23 | 24 | @Query(() => ResultAuthModule) 25 | @UseGuards(GqlAuthGuard) 26 | getProfile(@GqlCurrentUser() user: any) { 27 | return user; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | import { UserService } from '../user/user.service'; 4 | 5 | @Injectable() 6 | export class AuthService { 7 | constructor( 8 | private readonly usersService: UserService, // 引入用户的服务 9 | private readonly jwtService: JwtService, 10 | ) { } 11 | 12 | // 验证用户 13 | async validateUser(username: string, password: string): Promise { 14 | const user = await this.usersService.nameFindUser(username); 15 | if (!user) throw new HttpException({ 16 | message: "验证失败", 17 | error: '没有找到有效用户' 18 | }, HttpStatus.OK) 19 | if (!user.isValidPassword(password)) throw new HttpException({ 20 | message: "验证失败", 21 | error: '密码错误' 22 | }, HttpStatus.OK) 23 | return user 24 | } 25 | 26 | // 使用jwt加密用户的信息到access_token返回 27 | async createToken(user: any,): Promise<{ access_token: string }> { 28 | const { password, ...payload } = user 29 | return { access_token: this.jwtService.sign(payload) }; 30 | } 31 | } -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/dto/login.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; 3 | 4 | export class LoginDto { 5 | @IsNotEmpty({ message: '密码不能为空' }) 6 | @MaxLength(10, { message: '密码最长为10位数' }) 7 | @MinLength(6, { message: '密码最少6位数' }) 8 | @ApiPropertyOptional({ 9 | required: true, 10 | description: '密码', 11 | default: '123456', 12 | }) 13 | public readonly password: string; 14 | 15 | @IsNotEmpty({ message: '用户名不能为空' }) 16 | @IsString({ message: '用户名必须为字符串' }) 17 | @ApiPropertyOptional({ 18 | required: true, 19 | description: '用户名', 20 | default: 'user', 21 | }) 22 | public readonly name: string; 23 | 24 | // @IsNotEmpty({ message: '用户名2不能为空' }) 25 | // @IsString({ message: '用户名2必须为字符串' }) 26 | // @ApiPropertyOptional({ 27 | // required: true, 28 | // description: '用户名2', 29 | // default: 'user', 30 | // }) 31 | // public readonly username: string; 32 | } 33 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/dtos/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field } from '@nestjs/graphql'; 2 | import { ResultPublicModel } from 'src/common/public.module'; 3 | 4 | @ObjectType() 5 | export class authModule { 6 | @Field({ nullable: true, description: '用户ID' }) 7 | public readonly id: string; 8 | 9 | @Field({ nullable: true, description: '年龄' }) 10 | public readonly age: number; 11 | 12 | @Field({ nullable: true, description: '姓名' }) 13 | public readonly name: string; 14 | } 15 | 16 | @ObjectType() 17 | export class ResultAuthModule extends ResultPublicModel { 18 | @Field() 19 | public readonly result: authModule; 20 | } 21 | 22 | @ObjectType() 23 | export class ResultAuth { 24 | @Field() 25 | public readonly access_token: string; 26 | } 27 | 28 | @ObjectType() 29 | export class ResultAuthToken extends ResultPublicModel { 30 | @Field() 31 | public readonly result: ResultAuth; 32 | } 33 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/dtos/login.input.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; 2 | import { InputType, Field } from '@nestjs/graphql' 3 | 4 | @InputType() 5 | export class LoginInputDto { 6 | @IsNotEmpty({ message: '密码不能为空' }) 7 | @MaxLength(10, { message: '密码最长为10位数' }) 8 | @MinLength(6, { message: '密码最少6位数' }) 9 | @Field({ nullable: false, description: "用户名", defaultValue: 'user' }) 10 | public readonly password: string; 11 | 12 | @Field({ nullable: false, description: "用户名", defaultValue: '123456' }) 13 | @IsNotEmpty({ message: '用户名不能为空' }) 14 | @IsString({ message: '用户名必须为字符串' }) 15 | public readonly name: string; 16 | 17 | // @Field({ nullable: false, description: "用户名2", defaultValue: 'user' }) 18 | // @IsNotEmpty({ message: '用户名2不能为空' }) 19 | // @IsString({ message: '用户名2必须为字符串' }) 20 | // public readonly username: string; 21 | } 22 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/gql-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, ExecutionContext } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { GqlExecutionContext } from '@nestjs/graphql'; 4 | 5 | //gql的专属验证,替代AuthGuard('jwt') 6 | 7 | @Injectable() 8 | export class GqlAuthGuard extends AuthGuard('jwt') { 9 | getRequest(context: ExecutionContext) { 10 | const ctx = GqlExecutionContext.create(context); 11 | const user = ctx.getContext().req; 12 | return user 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Strategy, ExtractJwt } from 'passport-jwt'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import jwtSecret from '../../config/jwtSecret'; 5 | 6 | @Injectable() 7 | export class JwtStrategy extends PassportStrategy(Strategy) { 8 | constructor() { 9 | super({ 10 | jwtFromRequest: ExtractJwt.fromHeader('token'), 11 | ignoreExpiration: false, //是否忽略过期token 12 | secretOrKey: jwtSecret.secret, 13 | }); 14 | } 15 | 16 | async validate(payload: any) { 17 | return payload 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/auth/local.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Strategy } from 'passport-local'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable() 7 | export class LocalStrategy extends PassportStrategy(Strategy) { 8 | constructor(private readonly authService: AuthService) { 9 | // LocalStrategy默认只接收username和password,传入这些字段修改默认值 10 | super({ 11 | usernameField: 'name', 12 | passwordField: 'password', 13 | }); 14 | } 15 | 16 | async validate(username: string, password: string): Promise { 17 | const user = await this.authService.validateUser(username, password); 18 | if (!user) { 19 | throw new HttpException( 20 | { message: '授权失败', error: '请稍后在再试' }, 21 | HttpStatus.BAD_REQUEST, 22 | ); 23 | } 24 | return user; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/graph/dtos/graph.graphql.txt: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | createGraph(graph: GraphInput!): GraphData 3 | } 4 | 5 | type Query { 6 | findOneGraph(id:Int): GraphData 7 | } 8 | 9 | input GraphInput { 10 | name: String 11 | age: Int 12 | address:String 13 | } 14 | 15 | type Graph { 16 | id: Int 17 | name: String 18 | age: Int 19 | address:String 20 | } 21 | 22 | type GraphData { 23 | code: Int, 24 | result:Graph, 25 | message:String 26 | } -------------------------------------------------------------------------------- /nestjs-template/src/modules/graph/graph.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { PublicEntity } from 'src/common/public.entity'; 3 | 4 | @Entity('graph') 5 | export class Graph extends PublicEntity { 6 | 7 | @Column({ comment: '名称' }) 8 | name: string; 9 | 10 | @Column({ comment: '年龄' }) 11 | age: number; 12 | 13 | @Column({ comment: '地址' }) 14 | address: string; 15 | } -------------------------------------------------------------------------------- /nestjs-template/src/modules/graph/graph.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { Graph } from './graph.entity'; 5 | import { GraphService } from './graph.service'; 6 | import { GraphResolver } from './graph.resolver'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([Graph])], 10 | providers: [GraphResolver, GraphService], 11 | }) 12 | export class GraphModule { } 13 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/graph/graph.resolver.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GraphResolver } from './graph.resolver'; 3 | 4 | describe('GraphResolver', () => { 5 | let resolver: GraphResolver; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [GraphResolver], 10 | }).compile(); 11 | 12 | resolver = module.get(GraphResolver); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(resolver).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/graph/graph.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Args, Query } from '@nestjs/graphql'; 2 | import { Inject, Controller, UseGuards } from '@nestjs/common'; 3 | import { AuthGuard } from '@nestjs/passport'; 4 | import { Graph } from './graph.entity'; 5 | import { GraphService } from './graph.service'; 6 | 7 | @Resolver('graph') 8 | export class GraphResolver { 9 | constructor(@Inject(GraphService) private readonly graphService: GraphService, ) { } 10 | 11 | // 创建 12 | @Mutation('createGraph') 13 | createGraph(@Args('graph') graph: Graph): Promise { 14 | return this.graphService.createGraph(graph) 15 | } 16 | 17 | // 查询 18 | @Query('findOneGraph') 19 | findOneCat(@Args('id') id: number): Promise { 20 | return this.graphService.findOneGraph(id) 21 | } 22 | 23 | } 24 | 25 | 26 | // type Author { 27 | // id: Int! 28 | // firstName: String 29 | // lastName: String 30 | // posts: [Post] 31 | // } 32 | 33 | 34 | // input UpdateUserInput { 35 | // name: String 36 | // age: Int 37 | // address: String 38 | // } 39 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/graph/graph.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GraphService } from './graph.service'; 3 | 4 | describe('GraphService', () => { 5 | let service: GraphService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [GraphService], 10 | }).compile(); 11 | 12 | service = module.get(GraphService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/graph/graph.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | 5 | import { Graph } from './graph.entity'; 6 | 7 | @Injectable() 8 | export class GraphService { 9 | constructor( 10 | @InjectRepository(Graph) private readonly graphRepo: Repository, // 使用泛型注入对应类型的存储库实例 11 | ) { } 12 | 13 | createGraph(graph: Graph) { 14 | return this.graphRepo.save(graph) 15 | } 16 | 17 | findOneGraph(id: number) { 18 | return this.graphRepo.findOne(id) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/dtos/create-user.input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from '@nestjs/graphql' 2 | import { IsNotEmpty, MaxLength, MinLength } from 'class-validator' 3 | 4 | @InputType() 5 | export class CreateUserInput { 6 | 7 | @Field({ nullable: false, description: "用户名", defaultValue: 'zhangsan' }) 8 | public readonly name: string 9 | 10 | @Field({ nullable: false, description: "年龄" }) 11 | public readonly age: number 12 | 13 | @IsNotEmpty({ message: '密码不能为空' }) 14 | @MaxLength(10, { message: '密码最长为10位数' }) 15 | @MinLength(6, { message: '密码最少6位数' }) 16 | @Field({ nullable: false, description: '用户密码' }) 17 | public readonly password: string 18 | 19 | @Field({ nullable: true, description: "地址" }) 20 | public readonly address?: string 21 | 22 | } 23 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/dtos/modify-user-pwd.input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from '@nestjs/graphql' 2 | import { IsNotEmpty, MaxLength, MinLength } from 'class-validator' 3 | 4 | @InputType() 5 | export class ModifyUserPwdInput { 6 | 7 | @Field({ nullable: false }) 8 | public readonly id: string 9 | 10 | @IsNotEmpty({ message: '当前密码不能为空' }) 11 | @MaxLength(10, { message: '当前密码最长为10位数' }) 12 | @MinLength(6, { message: '当前密码最少6位数' }) 13 | @Field({ nullable: false }) 14 | public readonly password: string 15 | 16 | 17 | @IsNotEmpty({ message: '新密码不能为空' }) 18 | @MaxLength(10, { message: '新密码最长为10位数' }) 19 | @MinLength(6, { message: '新密码最少6位数' }) 20 | @Field({ nullable: false }) 21 | public readonly newPassword: string 22 | 23 | } 24 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/dtos/update-user.input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from '@nestjs/graphql' 2 | 3 | @InputType() 4 | export class UpdateUserInput { 5 | 6 | @Field({ nullable: true }) 7 | public readonly name?: string 8 | 9 | @Field({ nullable: true }) 10 | public readonly age?: number 11 | 12 | 13 | @Field({ nullable: true }) 14 | public readonly address?: string 15 | 16 | } 17 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/dtos/user.model.ts: -------------------------------------------------------------------------------- 1 | import { Field, ID, ObjectType } from '@nestjs/graphql' 2 | 3 | @ObjectType() 4 | export class UserModel { 5 | @Field(() => ID) 6 | public readonly id: string 7 | 8 | @Field() 9 | public readonly name: string 10 | 11 | @Field() 12 | public readonly age: number 13 | 14 | @Field() 15 | public readonly address: string 16 | 17 | } 18 | 19 | @ObjectType() 20 | export class ResultUserModel { 21 | @Field() 22 | public readonly result: UserModel 23 | 24 | @Field() 25 | public readonly code: number 26 | 27 | @Field() 28 | public readonly message: string 29 | 30 | } -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BeforeInsert, BeforeUpdate, Column, Entity } from 'typeorm'; 2 | import { PublicEntity } from 'src/common/public.entity'; 3 | import { hashSync, compareSync } from 'bcrypt' 4 | 5 | 6 | @Entity('user') 7 | export class User extends PublicEntity { 8 | 9 | @Column({ comment: '名称' }) 10 | name: string; 11 | 12 | @Column({ comment: '年龄' }) 13 | age: number; 14 | 15 | @Column({ comment: '地址', nullable: true }) 16 | address: string; 17 | 18 | @Column({ comment: '密码' }) 19 | password: string; 20 | 21 | /** 22 | * 插入数据库前先给密码加密 23 | * 必须要对模型进行更新,才能触发 24 | * https://github.com/typeorm/typeorm/blob/master/docs/listeners-and-subscribers.md#beforeupdate 25 | */ 26 | @BeforeInsert() 27 | @BeforeUpdate() 28 | public makePassword() { 29 | this.password = hashSync(this.password, 10) 30 | } 31 | 32 | // 密码与数据库是否一致 33 | public isValidPassword(password: string): boolean { 34 | return compareSync(password, this.password) 35 | } 36 | } -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { TypeOrmModule } from '@nestjs/typeorm' 3 | import { UserResolver } from './user.resolver' 4 | import { UserService } from './user.service' 5 | import { User } from './user.entity' 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([User])], 9 | providers: [UserResolver, UserService], 10 | exports: [UserService], 11 | }) 12 | export class UsersModule { } -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/user.resolver.ts: -------------------------------------------------------------------------------- 1 | import { UseGuards } from '@nestjs/common'; 2 | import { Args, Resolver, Mutation, Query, ID } from '@nestjs/graphql'; 3 | import { AuthGuard } from '@nestjs/passport'; 4 | import { ResultUserModel, UserModel } from './dtos/user.model'; 5 | import { UpdateUserInput } from './dtos/update-user.input'; 6 | import { UserService } from './user.service'; 7 | import { UsersModule } from './user.module'; 8 | import { CreateUserInput } from './dtos/create-user.input'; 9 | import { ModifyUserPwdInput } from './dtos/modify-user-pwd.input'; 10 | import { GqlAuthGuard } from '../auth/gql-auth.guard'; 11 | import { GqlCurrentUser } from 'src/common/decorators/gql.user'; 12 | 13 | @Resolver(() => UserModel) 14 | export class UserResolver { 15 | constructor(private readonly usersService: UserService) { 16 | this.usersService = usersService; 17 | } 18 | 19 | @Query(() => ResultUserModel) 20 | // @UseGuards(GqlAuthGuard) // graphql专属身份验证 21 | public async getAnnouncements( 22 | @Args({ name: 'id', type: () => ID }) id: string, 23 | @GqlCurrentUser() user: any, 24 | ): Promise { 25 | console.log(user, '当前用户'); 26 | return this.usersService.findOneUser(id); 27 | } 28 | 29 | @Mutation(() => ResultUserModel) 30 | public createUser( 31 | @Args('input') input: CreateUserInput, 32 | ): Promise { 33 | return this.usersService.createUser(input); 34 | } 35 | 36 | @Mutation(() => ResultUserModel) 37 | modifyPassword(@Args('input') input: ModifyUserPwdInput) { 38 | return this.usersService.modifyPassword(input); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { EntityManager, getManager, Repository } from 'typeorm'; 4 | 5 | import { User } from './user.entity'; 6 | import { UpdateUserInput } from './dtos/update-user.input'; 7 | import { CreateUserInput } from './dtos/create-user.input'; 8 | import { ModifyUserPwdInput } from './dtos/modify-user-pwd.input'; 9 | 10 | @Injectable() 11 | export class UserService { 12 | constructor( 13 | @InjectRepository(User) private readonly userRepo: Repository, // 使用泛型注入对应类型的存储库实例 14 | ) { } 15 | 16 | createUser(user: CreateUserInput) { 17 | return this.userRepo.save(this.userRepo.create(user)) 18 | } 19 | 20 | nameFindUser(name: string) { 21 | return this.userRepo.findOne({ 22 | where: { 23 | name 24 | } 25 | }) 26 | } 27 | 28 | findOneUser(id: string) { 29 | return this.userRepo.findOne(id) 30 | } 31 | 32 | async modifyPassword(user: ModifyUserPwdInput) { 33 | const currentUser = await this.userRepo.findOne(user.id) 34 | if (!currentUser) throw new HttpException({ 35 | message: "系统繁忙", 36 | }, HttpStatus.OK) 37 | const status = currentUser.isValidPassword(user.password) 38 | if (!status) throw new HttpException({ 39 | message: "当前密码不正确", 40 | }, HttpStatus.OK) 41 | currentUser.password = user.newPassword 42 | return this.userRepo.save(currentUser) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/dto/create.user.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsString, 3 | IsOptional, 4 | IsEnum, 5 | IsNumber, 6 | MaxLength, 7 | IsNotEmpty, 8 | MinLength, 9 | } from 'class-validator'; 10 | import { ApiPropertyOptional } from '@nestjs/swagger'; 11 | import { Transform } from 'class-transformer'; 12 | 13 | export class CreateUserDto { 14 | @ApiPropertyOptional({ 15 | required: true, 16 | description: '是否是管理员', 17 | enum: [0, 1], 18 | }) 19 | @IsEnum( 20 | { 普通用户: 0, 管理员: 1 }, 21 | { message: '是否管理员? 1:是, 0:不是' }, 22 | ) 23 | @IsNumber() 24 | @Transform((value) => parseInt(value, 10)) 25 | @IsNotEmpty({ message: '身份不能为空' }) 26 | readonly is_admin?: number; 27 | 28 | @ApiPropertyOptional({ required: true, description: '用户名' }) 29 | @MaxLength(10, { message: '名字最长10位数' }) 30 | @IsNotEmpty({ message: '用户名不能为空' }) 31 | @IsString({ message: '名字必须是字符串' }) 32 | readonly name: string; 33 | 34 | @ApiPropertyOptional({ required: false, description: '备注信息' }) 35 | @IsString({ message: '备注信息必须是字符串' }) 36 | @IsOptional() 37 | readonly description?: string; 38 | 39 | @ApiPropertyOptional({ required: true, description: '密码' }) 40 | @IsNotEmpty({ message: '密码不能为空' }) 41 | @MaxLength(10, { message: '密码最长为10位数' }) 42 | @MinLength(6, { message: '密码最少6位数' }) 43 | readonly password: string; 44 | } 45 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/dto/create.xxx.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateXxxDto { 4 | @IsString({ message: '用户名必须为字符类型' }) 5 | @IsNotEmpty({ message: '用户名不能为空' }) 6 | readonly name: string 7 | 8 | 9 | @IsString({ message: '密码必须为字符串类型' }) 10 | @IsNotEmpty({ message: '密码不能为空' }) 11 | readonly pwd: string 12 | } -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { XxxController } from './xxx.controller'; 3 | 4 | describe('XxxController', () => { 5 | let controller: XxxController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [XxxController], 10 | }).compile(); 11 | 12 | controller = module.get(XxxController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Param, 5 | Body, 6 | Post, 7 | ParseIntPipe, 8 | Delete, 9 | HttpCode, 10 | HttpStatus, 11 | HttpException, 12 | UsePipes, 13 | } from '@nestjs/common'; 14 | import { XxxService } from './xxx.service'; 15 | import { CreateXxxDto } from './dto/create.xxx.dto'; 16 | import { IpAddress } from 'src/common/decorators/ip.address'; 17 | import { ApiTags, ApiHeader, ApiQuery, ApiOperation } from '@nestjs/swagger'; 18 | import { CreateUserDto } from './dto/create.user.dto'; 19 | import { ValidationPipe } from 'src/common/pipes/validation.pipe'; 20 | 21 | @ApiTags('xxx') // 在swagger API文档添加标签名称,独立的一项列表 22 | @ApiHeader({ 23 | name: 'X-MyHeader', 24 | description: '自定义标题', 25 | }) 26 | @Controller('xxx') 27 | export class XxxController { 28 | constructor(private readonly xxxService: XxxService) {} 29 | 30 | @Post('login') 31 | login() { 32 | 33 | } 34 | 35 | // 创建 用户 36 | @ApiOperation({ summary: '创建用户', description: '创建用户' }) 37 | @Post('create') 38 | @HttpCode(HttpStatus.CREATED) 39 | async createUser(@Body() createUserDto: CreateUserDto): Promise { 40 | return this.xxxService.create(createUserDto); 41 | } 42 | 43 | // 错误的接口 44 | @ApiOperation({ summary: '错误的请求', description: '这个接口是错误的' }) 45 | @Get('error') 46 | error() { 47 | throw new HttpException( 48 | { 49 | status: HttpStatus.OK, 50 | message: '这个接口不可能正确', 51 | error: '错误的原因,本就是因错误而错误', 52 | }, 53 | HttpStatus.OK, 54 | ); 55 | } 56 | 57 | // 查询全部 58 | @Get('all') 59 | query(@IpAddress() ip: string) { 60 | // 查询数据库 61 | console.log('访问者IP:', ip); 62 | return this.xxxService.query(); 63 | } 64 | 65 | // 动态路由 66 | @Get(':id') 67 | get(@Param('id') id: string): Promise { 68 | return this.xxxService.get(id); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, BeforeInsert } from 'typeorm'; 2 | import { Exclude, Expose } from 'class-transformer'; 3 | import bcrypt from 'bcrypt' 4 | import { PublicEntity } from 'src/common/public.entity'; 5 | 6 | // name 填入表名称,会自动创建这个表 7 | @Entity({ name: 'xxx' }) 8 | export class XxxEntity extends PublicEntity { 9 | @Column('int', { 10 | nullable: false, 11 | default: () => 0, 12 | name: 'is_admin', 13 | comment: '是否管理员? 1:是, 0:不是', 14 | }) 15 | is_admin: number; 16 | 17 | @Column('varchar', { 18 | length: 10, 19 | comment: '名字', 20 | }) 21 | name: string; 22 | 23 | @Column('text', { 24 | nullable: true 25 | }) 26 | description: string; 27 | 28 | @Exclude() // 表示排除字段 29 | @Column('varchar', { 30 | nullable: false, 31 | length: 16, 32 | name: 'password', 33 | comment: '密码', 34 | }) 35 | password: string; 36 | 37 | 38 | // 插入数据库前先给密码加密 39 | @BeforeInsert() 40 | public makePassword() { 41 | return bcrypt.hashSync(this.password, 10) 42 | } 43 | 44 | // 密码与数据库是否一致 45 | public isValidPassword(password: string): boolean { 46 | return bcrypt.compareSync(password, this.password) 47 | } 48 | 49 | // 重新定义返回数据结构, 注意: 会导致上面的Exclude和Expose失效 !!! 50 | public toResponseObject(isShowPassword = false): object { 51 | const { password, ...params } = this; 52 | if (isShowPassword) { 53 | return Object.assign(isShowPassword, { password }); 54 | } else { 55 | return params; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.graphql.txt: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | createXxx(xxx: XxxInput!): XxxData 3 | } 4 | 5 | type Query { 6 | findOneXxx(id:Int): GraphData 7 | } 8 | 9 | input XxxInput { 10 | name: String 11 | is_admin: Int 12 | description:String 13 | password:String 14 | } 15 | 16 | type Xxx { 17 | id: Int 18 | name: String 19 | is_admin: Int 20 | description:String 21 | } 22 | 23 | type XxxData { 24 | code: Int, 25 | result:Xxx, 26 | message:String 27 | } -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { XxxController } from './xxx.controller'; 3 | import { XxxService } from './xxx.service'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { XxxEntity } from './xxx.entity'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([XxxEntity])], 9 | controllers: [XxxController], 10 | providers: [XxxService], 11 | exports: [ 12 | TypeOrmModule.forFeature([XxxEntity]), 13 | ], 14 | }) 15 | export class XxxModule { } -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Args, Query } from '@nestjs/graphql'; 2 | import { Inject } from '@nestjs/common'; 3 | import { XxxEntity } from './xxx.entity'; 4 | import { XxxService } from './xxx.service'; 5 | 6 | @Resolver('xxx') 7 | export class GraphResolver { 8 | constructor(@Inject(XxxService) private readonly xxxService: XxxService, ) { } 9 | 10 | // 创建 11 | @Mutation('createGraph') 12 | createXxx(@Args('graph') xxx: XxxEntity): Promise { 13 | return this.xxxService.create(xxx) 14 | } 15 | 16 | // 查询 17 | @Query('findOneGraph') 18 | findOneXxx(@Args('id') id: string): Promise { 19 | return this.xxxService.get(id) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { XxxService } from './xxx.service'; 3 | 4 | describe('XxxService', () => { 5 | let service: XxxService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [XxxService], 10 | }).compile(); 11 | 12 | service = module.get(XxxService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /nestjs-template/src/modules/xxx/xxx.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository, DeepPartial } from 'typeorm'; 4 | import { XxxEntity } from './xxx.entity'; 5 | import { CreateUserDto } from './dto/create.user.dto'; 6 | 7 | @Injectable() 8 | export class XxxService { 9 | constructor( 10 | @InjectRepository(XxxEntity) 11 | private readonly xxxRepository: Repository, 12 | ) {} 13 | 14 | get(id: string): Promise { 15 | return this.xxxRepository.findOne(id) 16 | } 17 | create(body: CreateUserDto): Promise { 18 | return this.xxxRepository.save(body); 19 | } 20 | 21 | query(): Promise { 22 | return this.xxxRepository.find(); // 查询数据库 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nestjs-template/src/schema.gql: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------ 2 | # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) 3 | # ------------------------------------------------------ 4 | 5 | type authModule { 6 | """年龄""" 7 | age: Float 8 | 9 | """用户ID""" 10 | id: String 11 | 12 | """姓名""" 13 | name: String 14 | } 15 | 16 | input CreateUserInput { 17 | """地址""" 18 | address: String 19 | 20 | """年龄""" 21 | age: Float! 22 | 23 | """用户名""" 24 | name: String = "zhangsan" 25 | 26 | """用户密码""" 27 | password: String! 28 | } 29 | 30 | input LoginInputDto { 31 | """用户名""" 32 | name: String = "123456" 33 | 34 | """用户名""" 35 | password: String = "user" 36 | } 37 | 38 | input ModifyUserPwdInput { 39 | id: String! 40 | newPassword: String! 41 | password: String! 42 | } 43 | 44 | type Mutation { 45 | createUser(input: CreateUserInput!): ResultUserModel! 46 | login(input: LoginInputDto!): ResultAuthToken! 47 | modifyPassword(input: ModifyUserPwdInput!): ResultUserModel! 48 | } 49 | 50 | type Query { 51 | getAnnouncements(id: ID!): ResultUserModel! 52 | getProfile: ResultAuthModule! 53 | } 54 | 55 | type ResultAuth { 56 | access_token: String! 57 | } 58 | 59 | type ResultAuthModule { 60 | code: Float! 61 | message: String! 62 | result: authModule! 63 | } 64 | 65 | type ResultAuthToken { 66 | code: Float! 67 | message: String! 68 | result: ResultAuth! 69 | } 70 | 71 | type ResultUserModel { 72 | code: Float! 73 | message: String! 74 | result: UserModel! 75 | } 76 | 77 | type UserModel { 78 | address: String! 79 | age: Float! 80 | id: ID! 81 | name: String! 82 | } 83 | -------------------------------------------------------------------------------- /nestjs-template/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /nestjs-template/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /nestjs-template/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /nestjs-template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2017", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true, 13 | "allowSyntheticDefaultImports": true 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | "dist" 18 | ], 19 | 20 | } -------------------------------------------------------------------------------- /swagger/.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 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 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 | -------------------------------------------------------------------------------- /swagger/.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 -------------------------------------------------------------------------------- /swagger/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /swagger/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/axios": "^0.0.1", 25 | "@nestjs/common": "^8.0.6", 26 | "@nestjs/core": "^8.0.6", 27 | "@nestjs/platform-express": "^8.0.6", 28 | "@nestjs/platform-fastify": "^8.0.6", 29 | "@nestjs/swagger": "^5.0.9", 30 | "@nestjs/typeorm": "^8.0.2", 31 | "bcrypt": "^5.0.1", 32 | "class-transformer": "^0.4.0", 33 | "class-validator": "^0.13.1", 34 | "dayjs": "^1.10.6", 35 | "express-session": "^1.17.2", 36 | "mysql": "^2.18.1", 37 | "nestjs-config": "^1.4.8", 38 | "nestjs-real-ip": "^2.0.0", 39 | "reflect-metadata": "^0.1.13", 40 | "rimraf": "^3.0.2", 41 | "rxjs": "^7.3.0", 42 | "swagger-ui-express": "^4.1.6", 43 | "typeorm": "^0.2.36" 44 | }, 45 | "devDependencies": { 46 | "@nestjs/cli": "^8.1.1", 47 | "@nestjs/schematics": "^8.0.2", 48 | "@nestjs/testing": "^8.0.6", 49 | "@types/express": "^4.17.13", 50 | "@types/express-session": "^1.17.4", 51 | "@types/jest": "^26.0.24", 52 | "@types/node": "^16.4.13", 53 | "@types/supertest": "^2.0.11", 54 | "@typescript-eslint/eslint-plugin": "^4.29.1", 55 | "@typescript-eslint/parser": "^4.29.1", 56 | "eslint": "^7.32.0", 57 | "eslint-config-prettier": "^8.3.0", 58 | "eslint-plugin-prettier": "^3.4.0", 59 | "jest": "^27.0.6", 60 | "prettier": "^2.3.2", 61 | "supertest": "^6.1.4", 62 | "ts-jest": "^27.0.4", 63 | "ts-loader": "^9.2.5", 64 | "ts-node": "^10.2.0", 65 | "tsconfig-paths": "^3.10.1", 66 | "typescript": "^4.3.5" 67 | }, 68 | "jest": { 69 | "moduleFileExtensions": [ 70 | "js", 71 | "json", 72 | "ts" 73 | ], 74 | "rootDir": "src", 75 | "testRegex": ".*\\.spec\\.ts$", 76 | "transform": { 77 | "^.+\\.(t|j)s$": "ts-jest" 78 | }, 79 | "collectCoverageFrom": [ 80 | "**/*.(t|j)s" 81 | ], 82 | "coverageDirectory": "../coverage", 83 | "testEnvironment": "node" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /swagger/public/a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nangdie/nestjs-template/9081eb012ca9a995f49e44c5b36d9d6bac03732d/swagger/public/a.jpg -------------------------------------------------------------------------------- /swagger/public/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nangdie/nestjs-template/9081eb012ca9a995f49e44c5b36d9d6bac03732d/swagger/public/avatar.png -------------------------------------------------------------------------------- /swagger/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /swagger/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Logger, Req, Res, Session } from '@nestjs/common'; 2 | import { RealIP } from 'nestjs-real-ip'; 3 | import { Request, Response } from "express"; 4 | 5 | import { AppService } from './app.service'; 6 | import { Sessions } from './common/decorators/sessions'; 7 | 8 | @Controller() 9 | export class AppController { 10 | constructor(private readonly appService: AppService) { } 11 | 12 | @Get() 13 | getHello(@RealIP() ip: string, @Session() session: Record, @Sessions('user') visits: string): string { 14 | console.log(visits, '自定义装饰器获取的值') 15 | session.visits = session.visits ? session.visits + 1 : 1; 16 | Logger.log(`访问者IP:${ip} , session内容: ${session.visits}`) 17 | return this.appService.getHello() + session.visits; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /swagger/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { ConfigModule, ConfigService } from 'nestjs-config'; 4 | // import { NestSessionOptions, SessionModule } from 'nestjs-session'; 5 | import { resolve } from 'path'; 6 | 7 | import { AppController } from './app.controller'; 8 | import { AppService } from './app.service'; 9 | import { UserModule } from './modules/user/user.module'; 10 | import { OrderModule } from './modules/order/order.module'; 11 | import { GlobalModule } from './modules/global/global.module'; 12 | 13 | @Module({ 14 | imports: [ 15 | ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')), 16 | TypeOrmModule.forRootAsync({ 17 | useFactory: (config: ConfigService) => config.get('database'), 18 | inject: [ConfigService], 19 | }), 20 | UserModule, 21 | OrderModule, 22 | GlobalModule, 23 | // SessionModule.forRootAsync({ 24 | // imports: [ConfigModule], 25 | // inject: [ConfigService], 26 | // useFactory: async (config: ConfigService): Promise => config.get('session'), 27 | // }), 28 | 29 | // SessionModule.forRoot({ 30 | // session: { 31 | // saveUninitialized: true, 32 | // resave: false, 33 | // // secret: 'keyboard cat', 34 | // secret: 'aF,.j)wBhq+E9n#aHHZ91Ba!VaoMfC', 35 | // }, 36 | // }), 37 | ], 38 | controllers: [AppController], 39 | providers: [AppService], 40 | }) 41 | export class AppModule { } 42 | -------------------------------------------------------------------------------- /swagger/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World! - nestjs 启动成功'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /swagger/src/common/decorators/customize.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common' 2 | 3 | export const NoAuth = () => SetMetadata('no-auth', true); -------------------------------------------------------------------------------- /swagger/src/common/decorators/ip.address.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator } from '@nestjs/common'; 2 | // import * as requestIp from 'request-ip'; 3 | 4 | // export const IpAddress = createParamDecorator((_, req) => { 5 | // if (req.clientIp) return req.clientIp; 6 | // return requestIp.getClientIp(req); 7 | // }); -------------------------------------------------------------------------------- /swagger/src/common/decorators/sessions.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 3 | 4 | export const Sessions = createParamDecorator( 5 | (data: string, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return data ? request.session?.[data] : request.session; 8 | }, 9 | ); -------------------------------------------------------------------------------- /swagger/src/common/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpException, 6 | Logger, 7 | HttpStatus, 8 | } from '@nestjs/common'; 9 | import * as dayjs from 'dayjs'; 10 | 11 | // 需要实现 ExceptionFilter 接口,里面有一个catch方法需要实现 12 | @Catch(HttpException) 13 | export class HttpExceptionFilter implements ExceptionFilter { 14 | catch(exception: HttpException, host: ArgumentsHost) { 15 | const ctx = host.switchToHttp(); //获取状态信息 16 | const response = ctx.getResponse(); 17 | const request = ctx.getRequest() 18 | const status = 19 | exception instanceof HttpException 20 | ? exception.getStatus() 21 | : HttpStatus.INTERNAL_SERVER_ERROR; 22 | const exceptionRes = exception.getResponse() as { error: string, message: string } 23 | 24 | const { error, message } = exceptionRes 25 | const errorResponse = { 26 | timestamp: dayjs().format('YYYY-MM-DD HH:mm:ss'), 27 | message: message || exceptionRes, 28 | path: request?.url, 29 | status, 30 | error, 31 | } 32 | Logger.error( 33 | `${request?.method} ${request?.url}`, 34 | JSON.stringify(errorResponse), 35 | 'HttpExceptionFilter', 36 | ); 37 | response.status(status).json(errorResponse); 38 | } 39 | } -------------------------------------------------------------------------------- /swagger/src/common/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware, Logger } from '@nestjs/common'; 2 | import { Request, Response } from 'express'; 3 | import * as dayjs from "dayjs"; 4 | import { isEmpty } from 'class-validator'; 5 | 6 | /** 7 | * 局部监听客户端访问日志 8 | */ 9 | @Injectable() // 使用Injectable装饰器 10 | export class LoggerMiddleware implements NestMiddleware { // 使用NestMiddleware实现 11 | oldPath: string 12 | number = 1 13 | use(req: Request, res: Response, next: Function) { // 重写方法 14 | const { method, path, baseUrl } = req; 15 | if (path === this.oldPath) this.number++ 16 | else this.number = 1 17 | this.oldPath = path 18 | Logger.log(`${method} ${baseUrl} * ${this.number}`, 'LoggerMiddleware') 19 | // 必须调用next()将控制权传递给下一个中间件功能。否则请求将被挂起 20 | next(); 21 | } 22 | } 23 | 24 | 25 | /** 26 | * 全局监听客户端访问日志 27 | */ 28 | export const LoggerGlobal = function () { 29 | let oldPath: string 30 | let number = 1 31 | return (req: Request, res: Response, next: Function) => { 32 | const { method, path } = req; 33 | if (path === oldPath) number++ 34 | else number = 1 35 | oldPath = path 36 | Logger.log(`${method} ${path} * ${number}`, 'LoggerGlobal'); 37 | next(); 38 | }; 39 | }() -------------------------------------------------------------------------------- /swagger/src/common/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentMetadata, HttpException, HttpStatus, Injectable, PipeTransform, Type, } from '@nestjs/common'; 2 | import { plainToClass } from 'class-transformer'; 3 | import { validate } from 'class-validator'; 4 | 5 | @Injectable() 6 | export class ValidationPipe implements PipeTransform { 7 | async transform(value: any, metadata: ArgumentMetadata) { 8 | const { metatype } = metadata; 9 | if (!metatype || !this.toValidate(metatype)) return value; 10 | const object = plainToClass(metatype, value); 11 | const errors = await validate(object); 12 | if (errors.length > 0) { 13 | // const errObj = {}; 14 | // errors.forEach(err => { 15 | // const { 16 | // property, 17 | // constraints, 18 | // } = err; 19 | // errObj[property] = Object.values(constraints); 20 | // }); 21 | const errObj = Object.values(errors[0].constraints)[0]; 22 | throw new HttpException( 23 | { message: '请求参数验证失败 ', error: errObj }, 24 | HttpStatus.BAD_REQUEST, 25 | ); 26 | } 27 | return value; 28 | } 29 | 30 | private toValidate(metatype: Type): boolean { 31 | const types = [String, Boolean, Number, Array, Object]; 32 | return !types.find(type => metatype === type); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /swagger/src/common/public.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; 2 | import { Exclude } from 'class-transformer'; 3 | import { ApiProperty } from '@nestjs/swagger'; 4 | 5 | export class PublicEntity extends BaseEntity { 6 | 7 | @PrimaryGeneratedColumn('uuid', { 8 | name: 'id', 9 | comment: '主键id', 10 | }) 11 | id: string; 12 | 13 | 14 | @Exclude() 15 | @Column('tinyint', { 16 | nullable: false, 17 | default: () => 0, 18 | name: 'is_del', 19 | comment: '是否删除,1表示删除,0表示正常' 20 | }) 21 | isDel?: number; 22 | 23 | @CreateDateColumn({ 24 | type: 'timestamp', 25 | nullable: false, 26 | name: 'created_at', 27 | comment: '创建时间' 28 | }) 29 | createdAt?: Date; 30 | 31 | @UpdateDateColumn({ 32 | type: 'timestamp', 33 | nullable: false, 34 | name: 'updated_at', 35 | comment: '更新时间', 36 | }) 37 | updatedAt?: Date; 38 | 39 | @ApiProperty({ 40 | nullable: true, 41 | default: "", 42 | description: '备注信息' 43 | }) 44 | @Column({ 45 | nullable: true, 46 | comment: "备注信息" 47 | }) 48 | remark?: string 49 | 50 | @ApiProperty({ 51 | nullable: true, 52 | description: '排序 - 控制是否置顶' 53 | }) 54 | @Column('tinyint', { 55 | nullable: true, 56 | default: () => 0, 57 | name: 'sort', 58 | comment: '排序 - 控制是否置顶' 59 | }) 60 | sort?: number; 61 | } -------------------------------------------------------------------------------- /swagger/src/common/transform.return.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | import { classToPlain } from 'class-transformer'; 5 | import systemConfig from "../config/system"; 6 | 7 | 8 | const transformValue = (result: any, code: number = 0, message: string = '请求成功') => { 9 | const { returnFormat } = systemConfig 10 | return { 11 | [returnFormat.result]: classToPlain(result), 12 | [returnFormat.code]: code, 13 | [returnFormat.message]: message, 14 | } 15 | } 16 | 17 | /** 18 | * 拦截返回数据,将数据统一修改成指定格式在进行返回 19 | */ 20 | @Injectable() 21 | export class TransformReturnInterceptor implements NestInterceptor { 22 | intercept(context: ExecutionContext, next: CallHandler): Observable { 23 | const host = context.switchToHttp(); 24 | const request = host.getRequest(); 25 | 26 | // 不需要格式化的接口 27 | if (['/api/status'].includes(request?.url)) { 28 | return next.handle(); 29 | } 30 | 31 | return next.handle().pipe(map(transformValue)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /swagger/src/config/database.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { TypeOrmModuleOptions } from '@nestjs/typeorm'; 3 | 4 | const database: TypeOrmModuleOptions = { 5 | type: 'mysql', 6 | host: 'localhost', 7 | port: 3306, 8 | username: 'root', 9 | password: 'root', 10 | database: 'nest-swagger', 11 | entities: [join(__dirname, '../', '**/**.entity{.ts,.js}')], //自动检测包含entity的文件,并引入 12 | synchronize: true, 13 | // logging: false, 是否打印记录 14 | }; 15 | 16 | export default database 17 | -------------------------------------------------------------------------------- /swagger/src/config/jwtSecret.ts: -------------------------------------------------------------------------------- 1 | const jwtSecret = { 2 | secret: 'xxx', // 密匙 3 | signOptions: { 4 | expiresIn: '1h' //失效时间 5 | }, 6 | } 7 | export default jwtSecret -------------------------------------------------------------------------------- /swagger/src/config/monitor.ts: -------------------------------------------------------------------------------- 1 | // 查看监控信息的页面地址 localhost:3000/api/status 2 | export default { 3 | pageTitle: 'Nest.js 监控页面', 4 | port: 3000, // 端口要与service的端口一致 5 | path: '/status', 6 | ignoreStartsWith: '/healt/alive', 7 | spans: [ 8 | { 9 | interval: 1, 10 | retention: 60, 11 | }, 12 | { 13 | interval: 5, 14 | retention: 60, 15 | }, 16 | { 17 | interval: 15, 18 | retention: 60, 19 | }, 20 | ], 21 | chartVisibility: { // 监测项 22 | cpu: true, 23 | mem: true, 24 | load: true, 25 | responseTime: true, 26 | rps: true, 27 | statusCodes: true, 28 | }, 29 | healthChecks: [], 30 | }; -------------------------------------------------------------------------------- /swagger/src/config/session.ts: -------------------------------------------------------------------------------- 1 | import { SessionOptions } from "express-session"; 2 | 3 | 4 | const sessionConfig: SessionOptions = { 5 | 6 | saveUninitialized: true, 7 | resave: false, 8 | // secret: 'keyboard cat', 9 | secret: 'aF,.j)wBhq+E9n#aHHZ91Ba!VaoMfC', 10 | // cookie: { maxAge: 60 * 1000 } 11 | } 12 | 13 | export default sessionConfig -------------------------------------------------------------------------------- /swagger/src/config/system.ts: -------------------------------------------------------------------------------- 1 | const system = { 2 | adminPath: 'admin', // 管理员路径 3 | staticPrefixPath: '', // 静态文件的前缀 4 | supportImgTypes: ['.png', '.jpg', '.gif', '.jpeg'], //支持的图片 5 | defaultAccount: 'admin', // 默认账号 6 | defaultPassword: '123456', // 默认密码 7 | returnFormat: { 8 | result: 'result', 9 | code: 'code', 10 | message: 'message' 11 | } 12 | } 13 | 14 | export default system -------------------------------------------------------------------------------- /swagger/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 4 | 5 | import { AppModule } from './app.module'; 6 | import { HttpExceptionFilter } from './common/filters/http-exception.filter'; 7 | import { LoggerGlobal } from './common/logger.middleware'; 8 | import { ValidationPipe } from './common/pipes/validation.pipe'; 9 | import { TransformReturnInterceptor } from './common/transform.return'; 10 | import * as session from 'express-session'; 11 | import { NestExpressApplication } from '@nestjs/platform-express'; 12 | import { join } from 'path'; 13 | 14 | const PORT = process.env.PORT || 3000; 15 | const PREFIX = 'api' 16 | const SWAGGER_V1 = `${PREFIX}/v1/swagger` 17 | 18 | async function bootstrap() { 19 | const app = await NestFactory.create(AppModule); 20 | // 访问静态文件 21 | app.useStaticAssets(join(__dirname, '..', 'public')); 22 | app.setBaseViewsDir(join(__dirname, '..', 'views')); 23 | app.enableShutdownHooks(); // 开启监听生命周期 24 | 25 | app.setGlobalPrefix(PREFIX); 26 | const options = new DocumentBuilder() // 创建并配置文档信息 27 | .setTitle('标题') 28 | .setDescription('描述信息') 29 | .setVersion('1.0') 30 | .build(); 31 | const document = SwaggerModule.createDocument(app, options); 32 | // 会自动将所有路由显示出来 33 | SwaggerModule.setup(SWAGGER_V1, app, document); // api/swagger = API文档的路径,访问:http://localhost:3000/api/swagger 34 | 35 | app.use( 36 | session({ 37 | secret: 'my-secret', 38 | resave: false, 39 | saveUninitialized: false, 40 | }), 41 | ); 42 | 43 | // 跨域资源共享 44 | app.enableCors() 45 | 46 | // 全局监听请求的接口 47 | app.use(LoggerGlobal); 48 | // 使用管道验证数据 49 | app.useGlobalPipes(new ValidationPipe()); 50 | // 拦截错误的请求,返回统一格式 51 | app.useGlobalFilters(new HttpExceptionFilter()); 52 | // 拦截返回数据-返回统一格式 53 | app.useGlobalInterceptors(new TransformReturnInterceptor()); 54 | 55 | 56 | 57 | await app.listen(PORT, () => { 58 | Logger.debug(` 59 | 测试页面:http://localhost:${PORT} 60 | 启动成功:http://localhost:${PORT}/${PREFIX} 61 | API 文档:http://localhost:${PORT}/${SWAGGER_V1}`); 62 | 63 | }); 64 | } 65 | bootstrap(); 66 | -------------------------------------------------------------------------------- /swagger/src/modules/global/global.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GlobalController } from './global.controller'; 3 | 4 | describe('GlobalController', () => { 5 | let controller: GlobalController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [GlobalController], 10 | }).compile(); 11 | 12 | controller = module.get(GlobalController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /swagger/src/modules/global/global.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | 3 | @Controller('global') 4 | export class GlobalController {} 5 | -------------------------------------------------------------------------------- /swagger/src/modules/global/global.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { HttpModule } from "@nestjs/axios"; 3 | import { GlobalController } from './global.controller'; 4 | import { GlobalService } from './global.service'; 5 | 6 | @Global() 7 | @Module({ 8 | imports: [ 9 | HttpModule.register({ 10 | timeout: 5000, 11 | maxRedirects: 5, 12 | }), 13 | ], 14 | controllers: [GlobalController], 15 | providers: [GlobalService] 16 | }) 17 | export class GlobalModule { } 18 | -------------------------------------------------------------------------------- /swagger/src/modules/global/global.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GlobalService } from './global.service'; 3 | 4 | describe('GlobalService', () => { 5 | let service: GlobalService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [GlobalService], 10 | }).compile(); 11 | 12 | service = module.get(GlobalService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /swagger/src/modules/global/global.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { HttpService } from "@nestjs/axios"; 3 | import { AxiosResponse } from 'axios' 4 | import { Observable } from 'rxjs' 5 | 6 | 7 | @Injectable() 8 | export class GlobalService { 9 | constructor(private httpService: HttpService) { 10 | } 11 | 12 | async login() { 13 | const result = await this.httpService.request>>({ 14 | url: 'https://xxx', 15 | method: 'POST', 16 | params: { 17 | 18 | } 19 | }) 20 | const value = await result.toPromise() 21 | console.log(value, '登录') 22 | 23 | } 24 | 25 | uploadOrder() { 26 | 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /swagger/src/modules/order/order.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OrderController } from './order.controller'; 3 | 4 | describe('OrderController', () => { 5 | let controller: OrderController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [OrderController], 10 | }).compile(); 11 | 12 | controller = module.get(OrderController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /swagger/src/modules/order/order.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | 3 | @Controller('order') 4 | export class OrderController {} 5 | -------------------------------------------------------------------------------- /swagger/src/modules/order/order.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { OrderController } from './order.controller'; 3 | import { OrderService } from './order.service'; 4 | 5 | @Module({ 6 | controllers: [OrderController], 7 | providers: [OrderService] 8 | }) 9 | export class OrderModule {} 10 | -------------------------------------------------------------------------------- /swagger/src/modules/order/order.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OrderService } from './order.service'; 3 | 4 | describe('OrderService', () => { 5 | let service: OrderService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OrderService], 10 | }).compile(); 11 | 12 | service = module.get(OrderService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /swagger/src/modules/order/order.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class OrderService { 5 | save() { 6 | 7 | } 8 | 9 | get() { 10 | 11 | } 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /swagger/src/modules/user/dto/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { SaveOptions, RemoveOptions } from "typeorm"; 3 | import { UserEntity } from "../user.entity"; 4 | 5 | export class CreateUserDto extends UserEntity { 6 | 7 | } 8 | 9 | 10 | export class FindUserDto implements Partial{ 11 | @ApiProperty({ 12 | required: false, 13 | description: '关键字查询' 14 | }) 15 | nickname?: string 16 | @ApiProperty({ 17 | required: false, 18 | description: '关键字查询' 19 | }) 20 | name?: string 21 | @ApiProperty({ 22 | required: false, 23 | description: '关键字查询' 24 | }) 25 | id?: string 26 | 27 | } -------------------------------------------------------------------------------- /swagger/src/modules/user/user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserController } from './user.controller'; 3 | 4 | describe('UserController', () => { 5 | let controller: UserController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [UserController], 10 | }).compile(); 11 | 12 | controller = module.get(UserController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /swagger/src/modules/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, HttpException, HttpStatus, Param, Post, Query } from '@nestjs/common'; 2 | import { ApiHeader, ApiQuery, ApiResponse, ApiTags, OmitType } from '@nestjs/swagger'; 3 | import { CreateUserDto, FindUserDto } from './dto'; 4 | import { UserEntity } from './user.entity'; 5 | import { UserService } from './user.service'; 6 | 7 | @Controller('user') 8 | @ApiTags('user') 9 | export class UserController { 10 | constructor(private readonly userService: UserService) { 11 | 12 | } 13 | 14 | @Post() 15 | async create(@Body() body: CreateUserDto) { 16 | try { 17 | const user = await this.userService.create(body) 18 | return user 19 | } catch (error) { 20 | throw new HttpException( 21 | { message: '创建用户失败', error: error.message }, 22 | HttpStatus.INTERNAL_SERVER_ERROR, 23 | ); 24 | } 25 | } 26 | 27 | @Get('all') 28 | @ApiQuery({ 29 | type: FindUserDto 30 | }) 31 | @ApiResponse({ // 处理响应 32 | status: 200, 33 | description: '返回用户信息对象', 34 | type: [UserEntity] 35 | }) 36 | async findAll(@Query() query: FindUserDto) { 37 | return this.userService.findAll() 38 | } 39 | 40 | @Get(':id') 41 | @ApiResponse({ // 处理响应 42 | status: 200, 43 | description: '返回用户信息对象', 44 | type: UserEntity 45 | }) 46 | async findOne(@Param('id') id: string) { 47 | return this.userService.findOne(id) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /swagger/src/modules/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, BeforeInsert, AfterLoad } from 'typeorm'; 2 | import { PublicEntity } from 'src/common/public.entity'; 3 | import { ApiProperty } from '@nestjs/swagger'; 4 | import { IsNotEmpty, MaxLength, MinLength } from 'class-validator'; 5 | import { Logger } from '@nestjs/common'; 6 | import * as bcrypt from 'bcrypt' 7 | 8 | @Entity({ name: 'user' }) 9 | export class UserEntity extends PublicEntity { 10 | @ApiProperty({ 11 | description: '真实姓名', 12 | default: '张三' 13 | }) 14 | @Column({ 15 | length: 10, 16 | comment: '真实姓名' 17 | }) 18 | name: string; 19 | 20 | @ApiProperty({ 21 | nullable: true, 22 | default: "", 23 | description: '所属部门ID' 24 | }) 25 | @Column({ 26 | nullable: true, 27 | comment: '所属部门ID', 28 | }) 29 | classifyId?: string; 30 | 31 | @ApiProperty({ 32 | nullable: true, 33 | default: "张Ⅲ的别名", 34 | description: '别名、昵称' 35 | }) 36 | @Column({ 37 | nullable: true, 38 | length: 500, 39 | comment: '别名、昵称', 40 | default: '别名' 41 | }) 42 | nickname?: string; 43 | 44 | 45 | @ApiProperty({ 46 | nullable: true, 47 | description: '年龄', 48 | default: 18 49 | }) 50 | @Column('tinyint', { 51 | nullable: true, 52 | comment: '年龄' 53 | }) 54 | age?: number; 55 | 56 | 57 | @ApiProperty({ 58 | nullable: true, 59 | default: 'http://localhost:3000/avatar.png', 60 | description: '头像URL' 61 | }) 62 | @Column('varchar', { 63 | nullable: true, 64 | default: 'http://localhost:3000/avatar.png', 65 | comment: '头像URL' 66 | }) 67 | avatar?: string; 68 | 69 | 70 | @ApiProperty({ 71 | nullable: true, 72 | description: '是否管理员? 1:是, 0:不是' 73 | }) 74 | @Column('tinyint', { 75 | nullable: false, 76 | default: () => 0, 77 | name: 'is_admin', 78 | comment: '是否管理员? 1:是, 0:不是' 79 | }) 80 | is_admin: number; 81 | 82 | @IsNotEmpty({ message: '密码不能为空' }) 83 | @MaxLength(10, { message: '密码最长为10位数' }) 84 | @MinLength(3, { message: '密码最少3位数' }) 85 | @ApiProperty({ default: '123456', description: '密码' }) 86 | @Column('varchar', { 87 | length: 100, 88 | comment: '密码', 89 | select: false, 90 | }) 91 | password: string; 92 | 93 | /** 94 | * 插入数据库前先给密码加密 95 | * 想要触发BeforeInsert; 使用xx.create(xx), 然后调用XxxEntity.save() 保存 96 | */ 97 | @BeforeInsert() 98 | public makePassword() { 99 | const password = bcrypt.hashSync(this.password, 10) 100 | this.password = password 101 | } 102 | 103 | @AfterLoad() // 查询时隐藏密码 104 | hidePassword() { 105 | // delete this.password 106 | } 107 | 108 | // 密码与数据库是否一致 109 | public isValidPassword(password: string): boolean { 110 | return bcrypt.compareSync(password, this.password) 111 | } 112 | 113 | // 重新定义返回数据结构, 注意: 会导致上面的Exclude和Expose失效 !!! 114 | public toResponseObject(isShowPassword = false): object { 115 | const { password, ...params } = this; 116 | if (isShowPassword) { 117 | return Object.assign(isShowPassword, { password }); 118 | } else { 119 | return params; 120 | } 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /swagger/src/modules/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { UserController } from './user.controller'; 4 | import { UserEntity } from './user.entity'; 5 | import { UserService } from './user.service'; 6 | 7 | @Module({ 8 | imports: [ 9 | TypeOrmModule.forFeature([UserEntity]) 10 | ], 11 | controllers: [UserController], 12 | providers: [UserService] 13 | }) 14 | export class UserModule { } 15 | -------------------------------------------------------------------------------- /swagger/src/modules/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserService } from './user.service'; 3 | 4 | describe('UserService', () => { 5 | let service: UserService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UserService], 10 | }).compile(); 11 | 12 | service = module.get(UserService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /swagger/src/modules/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { CreateUserDto } from './dto'; 5 | import { UserEntity } from './user.entity'; 6 | 7 | @Injectable() 8 | export class UserService { 9 | constructor(@InjectRepository(UserEntity) private readonly userRepository: Repository) { 10 | 11 | } 12 | async create(body: UserEntity) { 13 | const user = await this.userRepository.create(body) 14 | return user.save() 15 | } 16 | 17 | async findOne(id: string) { 18 | return this.userRepository.findOne(id) 19 | } 20 | 21 | findAll() { 22 | return this.userRepository.find() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /swagger/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /swagger/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /swagger/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /swagger/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": "./", 13 | "incremental": true 14 | } 15 | } 16 | --------------------------------------------------------------------------------