# blog-nest **Repository Path**: jswgl/blog-nest ## Basic Information - **Project Name**: blog-nest - **Description**: 使用nest实现博客后端开发 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-08-12 - **Last Updated**: 2024-11-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ## mysql 列类型 int, bigint, bit, decimal, money, numeric, smallint, smallmoney, tinyint, float, real, date, datetime2, datetime, datetimeoffset, smalldatetime, time, char, varchar, text, nchar, nvarchar, ntext, binary, image, varbinary, hierarchyid, sql_variant, timestamp, uniqueidentifier, xml, geometry, geography, rowversion ## 表设计用到的知识 @Column 中可用选项列表: type: ColumnType - 列类型。其中之一在上面. name: string - 数据库表中的列名。 默认情况下,列名称是从属性的名称生成的。 你也可以通过指定自己的名称来更改它。 length: number - 列类型的长度。 例如,如果要创建varchar(150)类型,请指定列类型和长度选项。 width: number - 列类型的显示范围。 仅用于MySQL integer types(opens new window) onUpdate: string - ON UPDATE触发器。 仅用于 MySQL (opens new window). nullable: boolean - 在数据库中使列NULL或NOT NULL。 默认情况下,列是nullable:false。 update: boolean - 指示"save"操作是否更新列值。如果为false,则只能在第一次插入对象时编写该值。 默认值为"true"。 select: boolean - 定义在进行查询时是否默认隐藏此列。 设置为false时,列数据不会显示标准查询。 默认情况下,列是select:true default: string - 添加数据库级列的DEFAULT值。 primary: boolean - 将列标记为主要列。 使用方式和@ PrimaryColumn相同。 unique: boolean - 将列标记为唯一列(创建唯一约束)。 comment: string - 数据库列备注,并非所有数据库类型都支持。 precision: number - 十进制(精确数字)列的精度(仅适用于十进制列),这是为值存储的最大位数。仅用于某些列类型。 scale: number - 十进制(精确数字)列的比例(仅适用于十进制列),表示小数点右侧的位数,且不得大于精度。 仅用于某些列类型。 zerofill: boolean - 将ZEROFILL属性设置为数字列。 仅在 MySQL 中使用。 如果是true,MySQL 会自动将UNSIGNED属性添加到此列。 unsigned: boolean - 将UNSIGNED属性设置为数字列。 仅在 MySQL 中使用。 charset: string - 定义列字符集。 并非所有数据库类型都支持。 collation: string - 定义列排序规则。 enum: string[]|AnyEnum - 在enum列类型中使用,以指定允许的枚举值列表。 你也可以指定数组或指定枚举类。 asExpression: string - 生成的列表达式。 仅在MySQL (opens new window)中使用。 generatedType: "VIRTUAL"|"STORED" - 生成的列类型。 仅在MySQL (opens new window)中使用。 hstoreType: "object"|"string" -返回HSTORE列类型。 以字符串或对象的形式返回值。 仅在Postgres中使用。 array: boolean - 用于可以是数组的 postgres 列类型(例如 int []) transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } - 用于将任意类型EntityType的属性编组为数据库支持的类型DatabaseType。 ## 关于表的介绍 user:用户表,主要用于用户的登录和注册,发布文章、点赞、收藏、评论都需要用到 posts:帖子表也可以叫文章表 category:文章分类表 tags:文章的标签表 posts_tags:文章和标签的中间表 comment:评论表 like:点赞表 collect:收藏表 一、使用官方脚手架及构建一个项目 1、官网地址 2、使用脚手架构建一个初始项目 3、查看常见的命令 ➜ nest-learn git:(master) ✗ nest Usage: nest [options] Options: -v, --version Output the current version. -h, --help Output usage information. Commands: new|n [options] [name] Generate Nest application. build [options] [app] Build Nest application. start [options] [app] Run Nest application. generate|g [options] [name] [path] Generate a Nest element. Available schematics: ┌───────────────┬─────────────┐ │ name │ alias │ │ application │ application │ │ angular-app │ ng-app │ │ class │ cl │ │ configuration │ config │ │ controller │ co │ │ decorator │ d │ │ filter │ f │ │ gateway │ ga │ │ guard │ gu │ │ interceptor │ in │ │ interface │ interface │ │ middleware │ mi │ │ module │ mo │ │ pipe │ pi │ │ provider │ pr │ │ resolver │ r │ │ service │ s │ │ library │ lib │ │ sub-app │ app │ └───────────────┴─────────────┘ info|i Display Nest project details. update|u [options] Update Nest dependencies. add [options] Adds support for an external library to your project. ➜ nest-learn git:(master) ✗ 4、一般使用命令创建组件、服务、守卫、管道 nest g gu guard/auth [--no-spec] 5、直接启动项目 6、我们今天要实现的功能 image-20200214090051371 二、对项目的基本配置 1、使用.env文件存放敏感信息或者配置信息 安装依赖包 npm install dotenv npm install @types/dotenv -D 项目下创建.env的文件 在main.ts中使用 import 'dotenv/config'; import { NestFactory } from '@nestjs/core'; import { Logger } from '@nestjs/common'; import { AppModule } from './app.module'; const PORT = process.env.PORT || 8080; const PREFIX = process.env.PREFIX || '/'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 给请求添加prefix app.setGlobalPrefix(PREFIX); await app.listen(PORT, () => { Logger.log(`服务已经启动,请访问:http://wwww.localhost:${PORT}/${PREFIX}`); }); } bootstrap().catch(e => Logger.error(e)); 2、使用nest-config 配置 安装依赖包 npm install nestjs-config 项目根目录下创建一个config的目录 直接在app.module.ts中引入配置 import * as path from 'path'; import { Module } from '@nestjs/common'; import { ConfigModule } from 'nestjs-config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ ConfigModule.load(path.resolve(__dirname, 'config', '**/!(*.d).{ts,js}'), { modifyConfigName: (name: string) => name.replace('.config', ''), }), // 配置加载配置文件 ConfigModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule { } 3、配置引包别名(tsconfig.json文件中) // 配置引包的别名 "paths": { "@src/*": [ "src/*" ] } 4、配置跨域访问 const app = await NestFactory.create(AppModule, { cors: true, // 设置跨站访问 logger: false, }); 5、访问频率的限制 官网地址 安装包 npm install express-rate-limit 在main.ts中使用 // 访问频率限制 app.use( rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100, // 限制15分钟内最多只能访问100次 }), ); 6、web漏洞的配置 官网地址 安装包 npm install helmet 使用 // Web漏洞的 app.use(helmet()); 三、配置mysql 1、官网地址 2、安装的依赖包 npm install --save @nestjs/typeorm typeorm mysql 3、在.env文件中配置数据库的基本配置 // mysql数据库配置 DB_TYPE = mysql DB_HOST = localhost DB_USERNAME = root DB_PASSWORD = root DB_DATABASE = test DB_PORT = 3306 DB_SYNCHRONIZE = false DB_LOGGING = true 4、在config文件夹下创建一个database.config.ts文件 export default { type: process.env.DB_TYPE, host: process.env.DB_HOST, port: Number(process.env.DB_PORT), database: process.env.DB_DATABASE, username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, dropSchema: false, synchronize: false, logging: false, }; 5、在app.module.ts的文件中配置 import { ConfigModule, ConfigService } from 'nestjs-config'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ ... TypeOrmModule.forRootAsync({ useFactory: async (config: ConfigService) => ({ type: config.get('database.type'), host: config.get('database.host'), port: config.get('database.port'), username: config.get('database.username'), password: config.get('database.password'), database: config.get('database.database'), entities: ['./**/*.entity.ts', './**/*.entity.js'], synchronize: config.get('database.synchronize'), logging: config.get('database.logging'), }), inject: [ConfigService], }), .... ], ... }) export class AppModule { } 6、安装一个工具使数据库文件自动生成entity的文件 npm install typeorm-model-generator-cli 7、配置命令 "scripts": { ... "db": "typeormCli init" }, 8、创建一个数据库及数据表 CREATE TABLE `user` ( `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键id', `uuid` varchar(50) not null COMMENT 'uuid主键', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '用户密码', `mobile` varchar(11) DEFAULT NULL COMMENT '用户手机号码', `email` varchar(50) DEFAULT NULL COMMENT '用户邮箱', `status` tinyint(4) DEFAULT '1' COMMENT '用户状态', `platform` varchar(50) DEFAULT NULL COMMENT '平台', `is_super` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否为超级管理员', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', UNIQUE KEY `user_mobile_email` (`username`, `mobile`, `email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'; 9、运行命令生成实体类 image-20200214092433260 10、直接在app.controller.ts文件中写入测试代码 import { Controller, Get, Post, Body } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor (private readonly appService: AppService) { } @Get() async userList(): Promise { return this.appService.userList(); } @Post() async createUser(@Body() body: any): Promise { return this.appService.createUser(body); } } 11、在app.service.ts文件中写入代码 import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UserEntity } from './entities/user.entity'; @Injectable() export class AppService { constructor ( @InjectRepository(UserEntity) private readonly userRepository: Repository ) { } async userList(): Promise { return this.userRepository.find(); } async createUser(data: any): Promise { const user = await this.userRepository.create(data); return await this.userRepository.save(user); } } 12、在app.module.ts中引入实体类 imports: [ ... TypeOrmModule.forFeature([ UserEntity, ]) ], ... 13、运行项目 image-20200214094844158 14、注意点 在之前的版本中是不会出现这个问题,后来版本升级就出现了这个问题,如果你是使用的mongodb也不会出现这个问题,解决这个bug的方法 1、拷贝之前版本中的两个文件到项目中,使用nodemon启动 2、修改启动命令 "start:dev": "nodemon", 15、使用postman测试数据 四、配置静态资源 1、直接在main.ts中配置 import { NestFactory } from '@nestjs/core'; import { Logger } from '@nestjs/common'; import { NestExpressApplication } from '@nestjs/platform-express'; import { AppModule } from './app.module'; import { ValidationPipe } from './pipe/validation.pipe'; import adminConfig from './config/admin.config'; const PORT = process.env.PORT || 8080; async function bootstrap() { const app = await NestFactory.create(AppModule); //配置静态资源目录 app.useStaticAssets(path.join(__dirname, '..', 'public'), { prefix: adminConfig.staticPrefixPath, }); //配置模板引擎及模板的目录 app.setBaseViewsDir('views'); app.setViewEngine('ejs'); ... } 2、创建ejs模板 3、使用ejs模板

用户页面

<%for(var i=0;i
  • <%=user[i].username%>
  • <%}%> 4、渲染模板 @Get('user') @Render('user') async user() { const user = await this.appService.userList(); return { user, } } 5、注意点 如果使用了@Response() res: any就不要使用return 五、服务器端接收客户端的数据 1、常见的装饰器官网地址 No. 名字 字段说明(参考express框架字段) 1 @Request() req 获取到req请求的参数 2 @Response() res 使用了res就不使用使用return返回值需要使用res.send() 3 @Next() next 4 @Session() req.session 5 @Param(key?: string) req.params / req.params[key] 获取动态路由的参数 6 @Body(key?: string) req.body / req.body[key] 获取post请求提交的参数 7 @Query(key?: string) req.query / req.query[key] 获取get请求query的参数 8 @Headers(name?: string) req.headers / req.headers[name] 获取请求头的参数 2、关于@Query()获取全部的参数 @Controller('user') export class UserController { @Get() index(@Query() query) { console.log(query); // 输出结果:{ name: 'hello', age: '20' } return "你好"; } } // 浏览器访问的url地址:http://localhost:4000/user?name=hello&age=2 3、在Query()中带参数并且判断参数类型 @Controller('user') export class UserController { @Get() index( @Query('age', new ParseIntPipe()) age: number, @Query('name') name: string ) { console.log(age, name); return "你好"; } } // 浏览器访问的url地址:http://localhost:4000/user?name=hello&age=20 4、@Param参数的获取 @Get(":id") userArticle(@Param() params) { console.log(params); // 输出{ id: '2' } return "用户详情" } // 浏览器访问的url地址:http://localhost:4000/user/2 5、@Param单独接受参数 @Get(":id") userArticle(@Param('id', new ParseIntPipe()) id: number) { console.log(id); return "用户详情" } // 浏览器访问的url地址:http://localhost:4000/user/2 6、@Body()接受post提交过来的数据(一次性接收全部的,也可以在@Body()中加参数类似上面的方式一样的校验传递过来的参数[仅仅是针对参数比较少的情况下]) @Post() addUser( @Body() body ) { console.log(body); return body } // 使用postman提交post请求地址:http://localhost:4000/user/ 六、改正项目目录 1、创建各个文件夹 2、测试服务器请求 七、对客户端传递的数据进行校验并且拦截错误返回到前端 1、官网地址 2、安装包 npm i --save class-validator class-transformer 3、书写dto的文件 4、测试 image-20200214112358112 image-20200214112340606 5、拦截错误 创建过滤器 nest g f filters/HttpError 关于过滤器的文件代码 import { ArgumentsHost, Catch, ExceptionFilter, Logger, HttpException, HttpStatus } from '@nestjs/common'; import { formatDate } from '@src/utils'; @Catch() export class HttpErrorFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; const message = exception.message || exception.message.message || exception.message.error || null; Logger.log(message, '错误提示'); const errorResponse = { status, result: { error: message, // 获取全部的错误信息 }, message: (typeof message == 'string') ? (message || '请求失败') : JSON.stringify(message), code: 1, // 自定义code path: request.url, // 错误的url地址 method: request.method, // 请求方式 timestamp: new Date().toLocaleDateString(), // 错误的时间 }; // 打印日志 Logger.error( `【${formatDate(Date.now())}】${request.method} ${request.url}`, JSON.stringify(errorResponse), 'HttpExceptionFilter', ); // 设置返回的状态码、请求头、发送错误信息 response.status(status); response.header('Content-Type', 'application/json; charset=utf-8'); response.send(errorResponse); } } 在app.module.ts找中使用 ... providers: [ { provide: APP_FILTER, useClass: HttpErrorFilter } ], ... 测试错误提示 image-20200214113522873 6、使用管道对返回的错误处理 使用命令创建管道 nest g pi pipe/validation 在管道的代码 import { ArgumentMetadata, Injectable, PipeTransform, Logger, HttpException, HttpStatus, } from '@nestjs/common'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; import * as _ from 'lodash'; @Injectable() export class ValidationPipe implements PipeTransform { async transform(value: any, metadata: ArgumentMetadata) { const { metatype } = metadata; if (!metatype || !this.toValidate(metatype)) { return value; } const object = plainToClass(metatype, value); const errors = await validate(object); Logger.log(errors); if (errors.length > 0) { // 遍历全部的错误信息,返回给前端 const errorMessage = errors.map(item => { return { currentValue: item.value === undefined ? '' : item.value, [item.property]: _.values(item.constraints)[0], }; }); // 统一抛出异常 throw new HttpException( { message: errorMessage }, HttpStatus.OK, ); } return value; } private toValidate(metatype: any): boolean { const types = [String, Boolean, Number, Array, Object]; return !types.includes(metatype); } } 使用管道 providers: [ { provide: APP_FILTER, useClass: HttpErrorFilter }, { provide: APP_PIPE, useClass: ValidationPipe } ], 错误提示 image-20200214114312431 八、使用拦截器对返回数据的转换 1、之前原始的返回 image-20200214114652111 2、拦截器官网地址 3、创建一个拦截器 nest g in interfaces/transform/transform 4、拦截器的代码 import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { classToPlain } from 'class-transformer'; @Injectable() export class TransformInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { return next.handle().pipe( map((data: any) => { return { result: data, code: 0, message: '请求成功', }; }), ); } } 5、使用拦截器 providers: [ ... { provide: APP_INTERCEPTOR, useClass: TransformInterceptor } ], 6、测试效果 image-20200214115312839 九、实体类的钩子函数的使用及排除字段 1、用户的实体类中,我们新增数据的时候密码要加密转换为密文,新增的时候不返回token,只有用户登录的时候返回token等一系列的操作 2、安装几个包 npm install jsonwebtoken // jwt 授权认证 npm install node-auth0 // 密码加密模块 3、对用户的实体类的修改 import { Column, Entity, PrimaryGeneratedColumn, BeforeInsert, BeforeUpdate } from 'typeorm'; import { Exclude, Expose } from 'class-transformer'; import * as jwt from 'jsonwebtoken'; import NodeAuth from 'node-auth0'; @Entity('user') export class UserEntity { @Exclude() private nodeAuth: NodeAuth; constructor () { this.nodeAuth = new NodeAuth(); } @PrimaryGeneratedColumn({ type: 'int', name: 'id', comment: '主键id', }) id: number; @Column('varchar', { nullable: false, primary: true, generated: 'uuid', length: 50, name: 'uuid', comment: 'uuid', }) uuid: string; @Column('varchar', { nullable: false, length: 50, name: 'username', comment: '用户名', }) username: string; @Exclude() // 表示排除字段不返回给前端 @Column('varchar', { nullable: false, length: 100, name: 'password', comment: '用户密码', }) password: string; @Column('varchar', { nullable: true, length: 11, name: 'mobile', comment: '用户手机号码', }) mobile: string | null; @Column('varchar', { nullable: true, length: 50, name: 'email', comment: '用户邮箱', }) email: string | null; @Exclude() @Column('tinyint', { nullable: true, default: () => "'1'", name: 'status', comment: '状态', }) status: number | null; @Column('varchar', { nullable: false, length: 50, name: 'platform', comment: '平台', }) platform: string; @Column('tinyint', { nullable: true, default: () => 0, name: 'is_super', comment: '是否为超级管理员', }) isSuper: number | null; @Column('timestamp', { nullable: false, default: () => 'CURRENT_TIMESTAMP', name: 'created_at', comment: '创建时间', }) createdAt: Date; @Column('timestamp', { nullable: false, default: () => 'CURRENT_TIMESTAMP', name: 'updated_at', comment: '最后更新时间', }) updatedAt: Date; @Expose() // 表示根据现有的字段生成一个新的字段 get is_active(): string { return this.status ? '有效' : '无效'; } /** * @Author: 水痕 * @Date: 2020-01-23 09:02:59 * @LastEditors: 水痕 * @Description: 插件数据库前先给密码加密 * @param {type} * @return: */ @BeforeInsert() makePassword() { this.password = this.nodeAuth.makePassword(this.password); } /** * @Author: 水痕 * @Date: 2020-01-23 09:03:45 * @LastEditors: 水痕 * @Description: 检查密码是否正确 * @param {type} * @return: */ checkPassword(password: string, sqlPassword: string) { return this.nodeAuth.checkPassword(password, sqlPassword); } /** * @Author: 水痕 * @Date: 2020-01-23 09:04:38 * @LastEditors: 水痕 * @Description: 生产token签名 * @param {type} * @return: */ @Expose() private get token() { const { id, uuid, username, mobile, email, isSuper } = this; // 生成签名 return jwt.sign( { id, uuid, username, mobile, email, isSuper, }, process.env.SECRET, // 加盐 { expiresIn: '7d', // 过期时间 }, ); } /** * @Author: 水痕 * @Date: 2020-01-23 09:19:49 * @LastEditors: 水痕 * @Description: 定义返回数据,用了这个函数后上面的Exclude和Expose就失效了 * @param {type} * @return: */ public toResponseObject(isShowToken: boolean = true): object { const { nodeAuth, password, token, ...params } = this; if (isShowToken) { return { token, ...params, }; } else { return { ...params, }; } } } 4、创建用户的时候返回 @Post() @HttpCode(HttpStatus.CREATED) async createUser(@Body() body: UserCreateDto): Promise { const user = await this.userService.createUser(body); return user.toResponseObject(false); } 5、用户登录的时候返回 async login(data: LoginDto): Promise { const { username, password } = data; let user: any; if (this.validator.isMobilePhone(username, 'zh-CN')) { user = await this.userRepository.findOne({ where: { mobile: username } }); } else if (this.validator.isEmail(username)) { user = await this.userRepository.findOne({ where: { email: username } }); } else { user = await this.userRepository.findOne({ where: { username } }); } if (user && this.toolsService.checkPassword(password, user.password)) { return user.toResponseObject(); } else { throw new HttpException('请检查你的用户名与密码', HttpStatus.OK); } } image-20200214130605171 image-20200214130621126 十、守卫的使用 1、官网地址 2、我们这就不使用官网的案例,直接使用jwt方式进行守卫 3、创建一个守卫 nest g gu guard/auth --no-spec 4、守卫的代码 import { Injectable, CanActivate, Logger, HttpException, HttpStatus, ExecutionContext, } from '@nestjs/common'; import * as jwt from 'jsonwebtoken'; import { InjectConfig, ConfigService } from 'nestjs-config'; import { getUrlQuery } from '@src/utils'; @Injectable() export class AuthGuard implements CanActivate { constructor ( @InjectConfig() private readonly configService: ConfigService, ) { } async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const token = context.switchToRpc().getData().headers.token || context.switchToHttp().getRequest().body.token || getUrlQuery(request.url, 'token'); Logger.log(`当前的token: ${token}`, 'AuthGuard'); // 如果白名单里面有的url或者是前端的就不拦截 if (this.hasUrl(this.configService.get('admin.whiteUrl'), request.url) || request.url.startsWith('/api/v1/front')) { return true; } if (token) { try { const user = await this.verifyToken(token, process.env.SECRET); request.user = user; return true; } catch (e) { throw new HttpException('没有授权不能访问,请先登录', HttpStatus.UNAUTHORIZED); } } else { throw new HttpException('没有授权不能访问,请先登录', HttpStatus.UNAUTHORIZED); } } /** * @param {token}: token * @param {secret}: secret * @return: * @Description: 校验用户传递过来的token * @Author: 水痕 * @LastEditors: 水痕 * @Date: 2019-07-31 12:56:01 */ private verifyToken(token: string, secret: string): Promise { return new Promise((resolve, reject) => { jwt.verify(token, secret, (error, payload) => { if (error) { console.log('-----------error start--------------'); console.log(error); console.log('-----------error end--------------'); reject(error); } else { resolve(payload); } }); }); } /** * @param {string[]} urlList url列表 * @param {url} url 当前要判断的url列表 * @return: * @Description: 判断一个url列表中是否包含一个url * @Author: 水痕 * @LastEditors: 水痕 * @Date: 2019-08-07 14:28:11 */ private hasUrl(urlList: string[], url: string): boolean { let flag = false; for (const item of urlList) { if (Object.is(item.replace(/\//gi, ''), url.replace(/\//gi, ''))) { flag = true; } } return flag; } } 5、在app.module.ts配置守卫 providers: [ ... { provide: APP_GUARD, useClass: AuthGuard, }, ], 6、测试获取用户数据 image-20200214132625274 7、加上请求头 十一、swagger文档的使用 1、官网地址 2、安装包 npm install --save @nestjs/swagger swagger-ui-express 3、main.ts文件的配置 // 配置api文档信息 const options = new DocumentBuilder() .setTitle('nestjs api文档') .setDescription('nestjs api接口文档') .setBasePath(PREFIX) .addBearerAuth({ type: 'apiKey', in: 'header', name: 'token' }) .setVersion('0.0.1') .build(); const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup(`${PREFIX}/docs`, app, document); 4、在在控制器中配置 @ApiTags('用户登录') @Controller('login') export class LoginController { constructor ( private readonly userService: UserService ) { } @ApiOperation({ summary: '用户登录', description: '用户名可以是手机号码、邮箱、用户名', }) @ApiCreatedResponse({ type: LoginDto, description: '用户登录DTO' }) @ApiOkResponse({ type: UserRep }) @Post() @HttpCode(HttpStatus.OK) async login(@Body() body: LoginDto): Promise { return await this.userService.login(body); } } 5、在dto上配置 import { IsString, IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class LoginDto { @ApiProperty({ required: true, description: '用户名' }) @IsString({ message: '用户名必须为字符类型' }) @IsNotEmpty({ message: '姓名不能为空' }) readonly username: string; @ApiProperty({ required: true, description: '密码' }) @IsString({ message: '密码必须为字符串类型' }) @IsNotEmpty({ message: '密码不能为空' }) readonly password: string; } 6、预览效果 image-20200214141231200 十二、打包上线 1、直接运行命令打包 npm run build 2、直接跑服务 npm run start:prod image-20200214143146989 3、不幸的直接报错了 4、处理这个错误的方式 安装包 npm install cross-env 修改启动的脚本 "start:dev": "cross-env NODE_ENV=development nodemon", "start:prod": "cross-env NODE_ENV=production node dist/main.js", 修改app.module.ts文件 const entitiesPath = process.env.NODE_ENV === 'production' ? path.resolve('./**/*.entity.js') : path.resolve('./**/*.entity.ts'); Logger.log(process.env.NODE_ENV, '当前环境'); image-20200214143645465 5、使用pm2部署项目 export declare enum HttpStatus { CONTINUE = 100, SWITCHING_PROTOCOLS = 101, PROCESSING = 102, EARLYHINTS = 103, OK = 200, CREATED = 201, ACCEPTED = 202, NON_AUTHORITATIVE_INFORMATION = 203, NO_CONTENT = 204, RESET_CONTENT = 205, PARTIAL_CONTENT = 206, AMBIGUOUS = 300, MOVED_PERMANENTLY = 301, FOUND = 302, SEE_OTHER = 303, NOT_MODIFIED = 304, TEMPORARY_REDIRECT = 307, PERMANENT_REDIRECT = 308, BAD_REQUEST = 400, UNAUTHORIZED = 401, PAYMENT_REQUIRED = 402, FORBIDDEN = 403, NOT_FOUND = 404, METHOD_NOT_ALLOWED = 405, NOT_ACCEPTABLE = 406, PROXY_AUTHENTICATION_REQUIRED = 407, REQUEST_TIMEOUT = 408, CONFLICT = 409, GONE = 410, LENGTH_REQUIRED = 411, PRECONDITION_FAILED = 412, PAYLOAD_TOO_LARGE = 413, URI_TOO_LONG = 414, UNSUPPORTED_MEDIA_TYPE = 415, REQUESTED_RANGE_NOT_SATISFIABLE = 416, EXPECTATION_FAILED = 417, I_AM_A_TEAPOT = 418, MISDIRECTED = 421, UNPROCESSABLE_ENTITY = 422, FAILED_DEPENDENCY = 424, TOO_MANY_REQUESTS = 429, INTERNAL_SERVER_ERROR = 500, NOT_IMPLEMENTED = 501, BAD_GATEWAY = 502, SERVICE_UNAVAILABLE = 503, GATEWAY_TIMEOUT = 504, HTTP_VERSION_NOT_SUPPORTED = 505 }