• NestJS入坑


    请添加图片描述


    前言

        刚从后端开发转向前端开发时,整个人都感觉轻松不少🤪🤪🤪,不用在为繁杂的数据结构设计而头疼,只需要专注前端开发的感觉简直不要太舒服。

         但是慢慢的,发现联调接口时我是越来越难受。刚开始是后端参考UI,然后根据自己的理解出接口和文档,这种情况造就了提供的API和交互流程相差甚远(这我能忍,况且我本身之前就是后端开发😠😠😠)。后来就由我来出接口文档,后端直接按照文档返回数据接口,但是偏偏我就碰上了那么几个(还好几年开发经验呢💢💢💢)。

         当然这些都不是重点,既然不行,那我就自己上💪💪💪,原来的后端语言不考虑,只是写个接口服务而已,所以就瞄上了go和node,再权衡再三下,最终选了node(NestJS), 这才有了这篇文章。


    提示:咱们废话不多说,直接开始正题。本文默认NestJS项目已经创建完成。如需要了解如何创建,请参考👉👉👉NestJS中文文档👈👈👈


    Ⅰ 📖路由篇

    ① 路由前缀

    方式一:全局设置统一路由前缀
    实例全局统一添加api前缀,访问url由 http://localhost:3001/demo => http://localhost:3001/api/demo

    // main.ts
    ...
    async function bootstrap() {
    	const app = await NestFactory.create(AppModule);
    	...
    	app.setGlobalPrefix('api');
    	...
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方式二:局部设置统一路由前缀,@controller()或者请求方式的装饰器都可以。

    // demo.controller.ts
    
    // http://localhost:3001/demo/
    @controller('demo')
    export class DemoController {
    
    	// http://localhost:3001/demo/demo
    	@Get('demo')
    	getDemo():any {
    		return 'demo';
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    提示:路由配置可根据业务需求自由灵活组合使用。

    ② 路由参数

    这个在这就不再详述了,文档太多了,详细请参考👉👉👉NestJS-Route parameters👈👈👈

    ③ 参数验证

    使用nestjs的提供的开箱即用的内置管道: ValidationPipe以及 class-validator、class-transformer来实现。关于其它内置管道,感兴趣的可自行去了解。

    先安装class-validator和class-transformer扩展包

     yarn add class-validator class-transformer
    
    • 1
    // main.ts
    
    import { ValidationPipe } from '@nestjs/common';
    // ...
    async function bootstrap() {
    	// ...
    	// +++ 注册验证管道
        app.useGlobalPipes(new ValidationPipe());
    	// ...
    }
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建验证dto文件,在我看来所谓dto类似与验证规则集合体,更准确点就是php的验证场景。

    nest g d demo
    
    • 1
    // demo.dto.ts
    // class-validator还提供了很多内置规则,示例仅简单举例
    import {IsNumber, IsString} from 'class-validator';
    
    export class DemoDto {
      @IsNumber()
      id: number;
    
      @IsString()
      name: string;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // demo.controller.ts
    import { Controller, Get, Query } from '@nestjs/common';
    import { DemoDto } from './dto/Demo.dto';
    
    @Controller('demo')
    export class UserController {
    
        @Get('info')
        getUser(@Query() demoDto : DemoDto ) {
            return 'user info';
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    提示:这样就完成了都路由参数id:是否为数字, name:是否为字符串的验证

    ④ 连接数据库

    本文以Mysql为例,使用的TypeOrm

    # 扩展安装
    yarn add @nestjs/typeorm typeorm mysql2
    
    • 1
    • 2
    // app.module.ts
    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    
    @Module({
      imports: [
        TypeOrmModule.forRoot({
          type: 'mysql',
          host: 'localhost',
          port: 3306,
          username: 'root',
          password: 'root',
          database: 'test',
          entities: ['dist/**/*.entity{.ts,.js}'],
          synchronize: true,
        }),
      ],
    })
    export class AppModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1 创建一张简单的用户表:users(包含id,name, value字段)
    2 创建user表实体:user.entity.ts
    3 在user module中注册user 并重新导入以便其它模块也可以使用。

    // user.entity.ts
    import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
    
    @Entity('user')
    export class User {
        @PrimaryGeneratedColumn()
        id: number;
    
        @Column()
        name: string;
    
        @Column()
        value: string;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    // user.module.ts
    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { UserController } from './user.controller';
    import { UserService } from './user.service';
    import { User } from './user.entity';
    
    @Module({
      imports: [TypeOrmModule.forFeature([User])],
      controllers: [UserController],
      providers: [UserService]
    })
    export class UserModule {}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    // user.service.ts
    import { Injectable } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { Repository } from 'typeorm';
    // 引入实体表
    import { User } from './user.entity';
    
    @Injectable()
    export class UserService {
        constructor(
            @InjectRepository(User)
            private userReponsitory: Repository<User>,
        ) {}
    
        findAll() {
            return this.userReponsitory.find();
        }
    
        findOne() {
            return this.userReponsitory.count();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    // user.controller.ts
    import { Controller, Get} from '@nestjs/common';
    import { UserService } from './user.service';
    
    @Controller({
        path: 'user',
        version: '1'
    })
    export class UserController {
        constructor(private readonly userService: UserService) {}
    
        @Get('count')
        getcount() {
            return this.userService.findOne();
        }
    
        @Get('all')
        getAll() {
            return this.userService.findAll();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这样已经可以连接数据库了。关于typeOrm操作数据的方法,大家可到官网详细查看👉👉👉TyepOrm👈👈👈
    在这里插入图片描述


    Ⅱ 📖实用篇

    ① 版本控制

    NestJS内置了版本控制:支持4种类型
    1:URI Versioning(版本再URI内传递,这是默认的方式)
    2:Header Versioning (自定义请求标头指定版本)
    3:Media Type Versioning(请求标头指定版本Accept)
    4:Custom Versioning(自定义的版本指向)

    提示:本文示例将使用方式二的版本控制,即Header Versioning

    // main.ts
    import { VersioningType } from '@nestjs/common';
    
    async function bootstrap() {
    	// ...
    	app.enableVersioning({
    		// ... 设置默认版本号
            defaultVersion: '1',
            // ... 设置版本控制类型
            type: VersioningType.MEDIA_TYPE,
            // 在标头中,版本将与媒体类型分开,并带有分号 
            // ... Accept: application/json;v=1
    		key: 'v=',
        });
    	// ...
    }
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    // demo.controller.ts
    
    // 方式一
    // 请求http://localhost:3001/demo/version1, 如果没有再main.ts中设置默认版本的话,则在请求时需要在header增加 Accept:application/json;v=1
    @Controller('demo')
    export class DemoController {
    	@Get('version1')
    	getVersion1(): string {
    		return 'this is version 1'
    	}
    	
    	@Get('version2')
    	getVersion1(): string {
    		return 'this is version 2'
    	}
    }
    
    // 方式二
    // 效果同方法一
    @Controller({
    	path: 'demo',
    	version: '1'
    })
    export class DemoController {
    	getVersion1(): string {
    		return 'this is version 1'
    	}
    	
    	getVersion1(): string {
    		return 'this is version 2'
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    提示:当然NestJS也同时支持多个版本,可以将版本设置为数组形式即可['1', '2']. 想了解其它版本控制方式请参考
    👉👉👉NestJS-Versioning👈👈👈

    ② 统一数据返回格式(成功)

    使用interceptor(拦截器),对成功返回的数据进行统一处理并返回

    # 创建httpOk拦截器
    nest g itc httpOk
    
    • 1
    • 2
    // httpOk.interceptor.ts
    
    import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from '@nestjs/common';
    import { map } from 'rxjs/operators';
    import { Observable } from 'rxjs';
    
    @Injectable()
    export class HttpOkInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        return next.handle().pipe(
          map((data) => {
            return {
              data,
              code: 200,
              message: 'success',
            };
          }),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    // main.ts
    
    import { HttpOkInterceptor } from 'src/interceptor/http-ok.interceptor';
    // ...
    async function bootstrap() {
    	// ...
    	// +++ 全局注册响应拦截器
        app.useGlobalInterceptors(new HttpOkInterceptor());
    	// ...
    }
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    提示:😎😎😎这下API正常返回的数据格式都统一了😎😎😎

    ③ 统一返回数据格式(异常)

    统一了正常的数据格式,当然也不能少了异常情况下的数据格式,开整~~~
    这块使用NestJS内置的Exception Filters, 我们通过继承该异常筛选器并自定义自己的响应逻辑即可

    nest g f http-exception
    
    • 1
    // http-exception.filter.ts
    
    import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
    
    @Catch()
    export class HttpExceptionFilter implements ExceptionFilter {
        catch(exception: HttpException, host: ArgumentsHost) {
    		const ctx = host.switchToHttp();
    		const response = ctx.getResponse();
    		const status = exception.getStatus();
    		// 优先详细报错信息,其次异常报错信息
        	const message = exception.getResponse()['message'] || exception.message;
    
    		response.header('Content-Type', 'application/json; charset=utf-8');
    		response
    			.status(status)
    			.json({
    				code: status,
    				message,
    				data: {}
    			});
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    提示:大家可根据exception的一系列获取异常信息并根据需求自行组装异常信息的返回格式

    // main.ts
    
    import { HttpExceptionFilter} from './filters/http-exception.filter'';
    // ...
    async function bootstrap() {
    	// ...
        // +++ 全局注册异常过滤器
        app.useGlobalFilters(new HttpExceptionFilter());
    	// ...
    }
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Ⅲ 📖安全篇

    在我们真实使用场景时,接口的安全措施必不可少,比如鉴权、限流、以及跨域处理等

    跨域

    // main.ts
    
    // ...
    async function bootstrap() {
    	// ...
        // +++ 
        app.enableCors();
    	// ...
    }
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    提示:没错,你没有看错,就是这么简单粗暴😆😆😆。如果需要更进步设置,可以查看enableCors相关的CorsOptions,比如有 origin | methods | allowedHeaders | exposedHeaders | credentials 等等

    ② 限流

    yarn add @nestjs/throttler
    
    • 1

    这个就比较简单了,支持全局设置以及自定义的设置代理,具体查看官方文档👉👉👉NestJS-Rate limiting👈👈👈,上面介绍的很详细了。

    ③ 鉴权

    本文采用的pasword 以及bearer Token,并没有使用官方使用的基于用户/密码的验证机制。又想了解用户、密码验证的passport可去查看👉👉👉NestJS-Authentication👈👈👈

    yarn add @nestjs/passport passport passport-http-bearer @nestjs/jwt passport-jwt 
    
    • 1
    # 创建auth.service.ts
    nest g s auth
    
    • 1
    • 2
    // auth.service.ts
    
    import { Injectable } from '@nestjs/common';
    import { JwtService } from '@nestjs/jwt';
    
    @Injectable()
    export class AuthService {
        constructor (
            private readonly jwtService: JwtService,
        ) {}
    
    	// 生成Token
        async createToken() {
            console.log(123);
            const user = { ut: '123456'};
            return this.jwtService.sign(user);
        }
    
        // 验证Token
        async validateToken(token: any) {
            return {}
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    #  创建auth.controller.ts
    nest g co auth
    
    • 1
    • 2
    // auth.controller.ts
    import { Controller, Get, UseGuards } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    import { AuthService } from './auth.service';
    
    
    @Controller('auth')
    export class AuthController {
        constructor(
            private readonly authService: AuthService,
        ){}
    
        @Get('token')
        async getToken() {
            return await this.authService.createToken();
        }
    
        @Get('info')
        @UseGuards(AuthGuard('jwt'))
        getAaa() {
            return [1];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    # 创建auth.module.ts
    nest g mo auth
    
    • 1
    • 2
    // auth.module.ts
    import { Module } from '@nestjs/common';
    import { PassportModule } from '@nestjs/passport';
    import { JwtModule } from '@nestjs/jwt';
    import { JwtStrategy } from 'src/strategy/jwt.strategy';
    import { AuthController } from './auth.controller';
    import { AuthService } from './auth.service';
    
    @Module({
        imports: [
            PassportModule.register({
                defaultStrategy: 'jwt'
            }),
            JwtModule.register({
                secretOrPrivateKey: 'secretKey',
                signOptions: {
                    expiresIn: 3600,
                },
            }),
        ],
        controllers: [AuthController],
        providers: [AuthService, JwtStrategy],
        exports: [AuthService]
    })
    export class AuthModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    // 手动创建jwt.strategy.ts
    // jwt.strategy.ts
    
    import { ExtractJwt, Strategy } from 'passport-jwt';
    import { AuthService } from 'src/auth/auth.service';
    import { PassportStrategy } from '@nestjs/passport';
    import { Injectable, UnauthorizedException } from '@nestjs/common';
    
    @Injectable()
    export class JwtStrategy extends PassportStrategy(Strategy) {
        constructor(private readonly authService: AuthService) {
            super({
                jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
                secretOrKey: 'secretKey'
            })
        }
    
        async validate(payload: any) {
            const user = await this.authService.validateToken(payload);
            if (!user) {
              throw new UnauthorizedException();
            }
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    提示:此时访问http://localhost:3001/auth/token就可以获取到token了. 请求http://localhost:3001/auth/info,则会提示401:Not Unauthorized

    此时接口鉴权已经基本完成了,但是还有没有优化空间呢?答案是当然有的。下面我们来实现jwt Guard,来简化我们每次使用都必须要使用 @UseGuards(AuthGuard(‘jwt’))。

    # 创建auth.guard.ts
    nest g gu auth
    
    • 1
    • 2
    // auth.guard.ts
    import { Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    
    @Injectable()
    export class JwtAuthGuard extends AuthGuard('jwt') {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    // 调整auth.controller.ts
    // 其它用到的controller同样的操作
    
    // --- import { AuthGuard } from '@nestjs/passport';
    // +++ import { JwtAuthGuard } from './auth.guard';
    
    @Controller('auth')
    export class AuthController {
        @Get('info')
        // --- @UseGuards(AuthGuard('jwt'))
        // +++ @UseGuards(JwtAuthGuard)
        getAaa() {
            return [1];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    至此,接口应用的准备工作已经全部完成了,剩下的就是各自愉快的玩耍时间啦🎉🎉🎉


    下面放两张接口测试图供大家参考
    1 获取token在这里插入图片描述
    2 请求验证未通过
    在这里插入图片描述
    3 验证通过
    在这里插入图片描述

  • 相关阅读:
    【数据结构(邓俊辉)学习笔记】向量02——动态空间管理
    公司组织架构图怎么制作?
    Android 4.4 如何添加按键流程
    合并excel方法汇总
    USB PD快充协议
    JAVA计算机毕业设计诗歌分享平台源码+系统+mysql数据库+lw文档
    【云原生】SpringCloud-Spring Boot Starter使用测试
    【每日一道LeetCode】——面试题 17.04. 消失的数字、189. 轮转数组
    高创伺服驱动器CDHD2和sick伺服编码器hiperface通讯时的故障解决
    关于类的定义
  • 原文地址:https://blog.csdn.net/glx490676405/article/details/126557351