Skip to content

NEST 全局九件套

九件套架构

  • 新建一个 src/common 文件夹
bash

── src
   ├── app.module.ts
   ├── common
   ├── globalcheck.exception.ts 管道抛出的异常
   ├── globalguard.exception.ts 守卫抛出的异常
   ├── global_check.filter.ts 捕捉管道抛出的异常
   ├── global_guard_check.filter.ts 捕捉守卫抛出的异常
   ├── global.filter.ts 全局过滤器 捕捉其他全局异常
   ├── global.guard.ts 全局守卫 验证jwt 等等
   ├── custom.interceptor.ts  自定义拦截器多了个custom字段.这样总的放行
   ├── global.interceptor.ts 全局拦截器 返回格式化数据
   └── global.pipe.ts 全局管道 验证参数等等
   ├── main.ts

globalcheck.exception.ts

ts
export class GlobalCheckException {
  message: string;
  constructor(message: string) {
    this.message = message;
  }
}

globalguard.exception.ts

ts
export class GlobalGuardException {
  message: string;
  constructor(message: string) {
    this.message = message;
  }
}

global_check.filter.ts

  • 管道使用 一般用于参数验证,或者判断用户权限 code 403
ts
/*
https://docs.nestjs.com/exception-filters#exception-filters-1
*/

import { ExceptionFilter, Catch, ArgumentsHost } from "@nestjs/common";

import { GlobalCheckException } from "./globalcheck.exception";

@Catch(GlobalCheckException)
export class GlobalExceptionsCheckFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = 200;
    // 错误内容
    const message =
      exception instanceof GlobalCheckException
        ? exception.message
        : exception.stack;

    response.status(status).json({
      code: 403,
      data: JSON.parse(message),
      message: "操作失败",
    });
  }
}

global_guard_check.filter.ts

  • 守卫使用 一般用于 token 验证,code 401
ts
/*
https://docs.nestjs.com/exception-filters#exception-filters-1
*/

import { ExceptionFilter, Catch, ArgumentsHost } from "@nestjs/common";

import { GlobalGuardException } from "./globalguard.exception";

@Catch(GlobalGuardException)
export class GlobalGuardCheckFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status = 200;

    // 错误内容
    const message =
      exception instanceof GlobalGuardException
        ? exception.message
        : exception.stack;

    response.status(status).json({
      code: 401,
      data: JSON.parse(message),
      message: "操作失败",
    });
  }
}

global.filter.ts

ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from "@nestjs/common";

@Catch()
export class GlobalExceptionsFilter implements ExceptionFilter {
  catch(exception: any, 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 instanceof HttpException ? exception.message : exception.stack;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: message,
      name: "验证拦截器",
    });
  }
}

global.guard.ts

  • 主要验证 token jwt
ts
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
} from "@nestjs/common";
import { GlobalCheckException } from "./globalcheck.exception";
import { ConfigService } from "@nestjs/config";
import { JwtServiceAll } from "../modules/jwt/jwt.service";
@Injectable()
export class GlobalGuard implements CanActivate {
  @Inject()
  private configService: ConfigService;
  @Inject()
  private jwtService: JwtServiceAll;
  async canActivate(context: ExecutionContext): Promise<boolean> {
    //权限验证
    const request = context.switchToHttp().getRequest();
    // 白名单
    const whiteList = this.configService.get("perm").router.whitelist;
    // 验证
    let item = { path: request.url, method: request.method };
    const isPublic = whiteList.some((content) => {
      if (content.path === item.path && content.method === item.method) {
        return true;
      }
    });
    if (isPublic) {
      return true;
    } else {
      // 判断
      const authorization = request.header("Authorization") || "";

      const bearer = authorization.split(" ");

      if (!bearer || bearer.length < 2) {
        throw new GlobalCheckException("登录 token 错误");
      }

      const token = bearer[1];
      try {
        const info = this.jwtService.verifyToken(token);
        // 我这里是解析出来后把userId赋值给request 这样控制器就拿到了
        (request as any).userId = info.id; // 换成你自己的payload对应字段
        return true;
      } catch (e) {
        const result = JSON.stringify([{ message: "token 错误" }]);
        throw new GlobalCheckException(result);
      }
    }
  }
}

custom.interceptor.ts

  • 自定义拦截器 用于返回特殊值
ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap, map } from "rxjs/operators";

@Injectable()
export class CustomInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log("Beforetest...");
    return next.handle().pipe(
      map((data) => {
        console.log("Aftertest...");
        let { custom, ...result } = data;
        if (custom) {
          return {
            custom: true,
            data: result.data,
            code: 200,
            message: "",
            ...result,
          };
        } else {
          return data;
        }
      })
    );
  }
}

global.interceptor.ts

  • 全局拦截器
ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap, map } from "rxjs/operators";

@Injectable()
export class GlobalInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log("Before...");
    return next.handle().pipe(
      map((data) => {
        console.log("After全局...");
        let { code, message, custom, ...result } = data;
        // 局部返回什么就是什么
        if (custom) {
          let { custom, ...result } = data;
          // 取消掉custom属性
          return result;
        }
        // 剩下的是全局返回
        // 控制器里面就可以返回 {code:200,data:xxx}
        let resultmessage = "";
        let resultcode = code || 200;
        if (resultcode == 200) {
          resultmessage = "操作成功";
        } else {
          resultmessage = "操作失败";
        }
        return {
          code: resultcode,
          message: resultmessage,
          data: result.data,
        };
      })
    );
  }
}

global.pipe.ts

  • 全局管道 验证参数 抛出异常
ts
import { PipeTransform, Injectable, ArgumentMetadata } from "@nestjs/common";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
// 引入我自己验证的异常类
import { GlobalCheckException } from "./globalcheck.exception";
@Injectable()
export class GlobalPipe implements PipeTransform {
  // 校验类型
  private toValidate(metatype: Function): boolean {
    // 其他类型不验证
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
  async transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;

    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    // 变成对象,把规则和值 组合成验证的对象
    const object = plainToInstance(metatype, value);
    // 验证 errors 是个数组
    const errors = await validate(object);

    if (errors.length > 0) {
      // 第一种只返回第一个
      if (errors.length == 1) {
        for (let arr in errors[0].constraints) {
          const result = [
            {
              message: `${errors[0].constraints[arr]}`,
              field: errors[0].property,
            },
          ];
          throw new GlobalCheckException(JSON.stringify(result));
        }
      }

      // 第二种返回所有
      let result: { message: string; field: string }[] = [];
      errors.forEach((item) => {
        for (let arr in item.constraints) {
          result.push({
            message: item.constraints[arr],
            field: item.property,
          });
        }
      });
      throw new GlobalCheckException(JSON.stringify(result));
    }
    return value;
  }
}

app.module.ts

ts
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE, APP_GUARD } from "@nestjs/core";
import { GlobalExceptionsFilter } from "./common/global.filter";
import { GlobalExceptionsCheckFilter } from "./common/global_check.filter";
import { GlobalGuardCheckFilter } from "./common/global_guard_check.filter";
import { GlobalInterceptor } from "./common/global.interceptor";
import { GlobalPipe } from "./common/global.pipe";
import { GlobalGuard } from "./common/global.guard";
import { CustomInterceptor } from "./common/custom.interceptor";
import { Module } from "@nestjs/common";
import configuration from "./config/index";
import { ConfigModule } from "@nestjs/config";

@Module({
  imports: [
    // 配置模块
    ConfigModule.forRoot({
      cache: true,
      load: [configuration],
      isGlobal: true,
    }),
  ],
  controllers: [],
  providers: [
    // 全局异常过滤器(他负责兜底 处理其他异常)
    {
      provide: APP_FILTER,
      useClass: GlobalExceptionsFilter,
    },
    // 检查管道过滤器
    {
      provide: APP_FILTER,
      useClass: GlobalExceptionsCheckFilter,
    },
    // 守卫过滤器
    {
      provide: APP_FILTER,
      useClass: GlobalGuardCheckFilter,
    },
    // 全局统一格式拦截器
    {
      provide: APP_INTERCEPTOR,
      useClass: GlobalInterceptor,
    },

    // 自定义拦截器
    {
      provide: APP_INTERCEPTOR,
      useClass: CustomInterceptor,
    },
    // 全局管道
    {
      provide: APP_PIPE,
      useClass: GlobalPipe,
    },
    // 全局守卫
    {
      provide: APP_GUARD,
      useClass: GlobalGuard,
    },
  ],
})
export class AppModule {}