Nest.js权限管理系统开发(五)返回格式化

返回格式化拦截器

在上一篇《Nest.js权限管理系统开发(四)Swagger API接入》中,我们在base.controller.ts中创建了多个接口,每个接口都有不同的返回类型。现实中我们往往需要统一返回数据的格式,例如:

{
  "code": 200,
  "msg": "ok",
  "data": "This action updates a #admin user"
}

next.js中我们可以通过返回格式拦截器对请求成功(状态码为 2xx)的数据进行一个格式化,同样的先执行

nest g interceptor common/interceptor/transform

创建一个拦截器,按照官网示例给的复制过来

import { CallHandler, ExecutionContext, NestInterceptor, Injectable } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { ResultData } from 'src/common/utils/result'

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
    const req = context.getArgByIndex(1).req
    return next.handle().pipe(
      map((data) => {
        return ResultData.ok(data)
      }),
    )
  }
}

main.ts中注册

import { TransformInterceptor } from './common/interceptor/transform/transform.interceptor';

app.useGlobalInterceptors(new TransformInterceptor());

返回异常过滤器

自定义HttpException

这样做之后我们会发现请求成功的 code 只能是 200,一般项目中请求成功还需要很多业务异常状态码返回给前端,所以我们需要新建一个抛出业务异常的类ApiException 我们先创建common/enums/code.enum.ts用于存放我们的业务状态码,这里简单写几个:

export enum ApiErrorCode {
    /** 公共错误 */
    /** 服务器出错 */
    SERVICE_ERROR = 500500,
    /** 数据为空 */
    DATA_IS_EMPTY = 100001,
    /** 参数有误 */
    PARAM_INVALID = 100002,
}

在common/filter/http-exception下新建api.exception.ts,创建一个ApiException类继承HttpException,接受三个参数错误信息,错误码code,http状态码(默认是200)

import { HttpException, HttpStatus } from '@nestjs/common';
import { ApiErrorCode } from '../../enum/code.enum';

export class ApiException extends HttpException {
  private errorMessage: string;
  private errorCode: ApiErrorCode;

  constructor(
    errorMessage: string,
    errorCode: ApiErrorCode,
    statusCode: HttpStatus = HttpStatus.OK,
  ) {
    super(errorMessage, statusCode);
    this.errorMessage = errorMessage;
    this.errorCode = errorCode;
  }

  getErrorCode(): ApiErrorCode {
    return this.errorCode;
  }

  getErrorMessage(): string {
    return this.errorMessage;
  }
}

然后我们可以在需要的地方抛出相应的异常了。

异常过滤器

抛出异常不是异常请求的最终归宿。当我们使用 NestJS 内置的异常处理HttpException,比如

throw new HttpException('您无权登录', HttpStatus.FORBIDDEN);

或者上面我们创建的ApiException,客户端就会收到

{
  "statusCode": 403,
  "message": "您无权登录"
}

但是这样不够灵活,所以我们可以新建一个异常过滤器进行自定义的操作:

nest g filter common/filter/http-exception

然后修改common/filter/http-exception/http-exception.filter.ts:

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    if (exception instanceof ApiException) {
      response.status(status).json({
        code: exception.getErrorCode(),
        msg: exception.getErrorMessage(),
      });
      return;
    }

    response.status(status).json({
      code: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      msg: exception.message,
    });
  }
}

最后在main.ts中进行注册

import { HttpExceptionFilter } from './common/filter/http-exception/http-exception.filter';


app.useGlobalFilters(new HttpExceptionFilter());

除了HttpException,我们也要过滤普通异常:

import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common'


@Catch()
export class ExceptionsFilter 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

    response.status(status).json({
      code: status,
      msg: `Service Error: ${exception}`,
    })
  }
}

Swagger返回类型修复

前面我们已经对返回正常数据进行格式化,并拦截处理了异常发生时的返回数据格式。因为我们通过拦截器对返回数据进行了包裹,那么在我们的接口里,我们只需要返回data部分即可,不需要创建并返回各种Response类了。但是这里有个问题,由于 TypeScript 不存储有关泛型或接口的元数据,因此当你在 DTO 中使用它们时,SwaggerModule 可能无法在运行时正确生成模型定义。所以我们前面并没有采用类似下面的类作为我们的返回类型:

import { ApiProperty } from '@nestjs/swagger'

export class ResultData<T> {
  constructor(code = 200, msg?: string, data?: T) {
    this.code = code
    this.msg = msg || 'ok'
    this.data = data || undefined
  }

  @ApiProperty({ type: 'number', default: 200 })
  code: number

  @ApiProperty({ type: 'string', default: 'ok' })
  msg?: string

  @ApiProperty()
  data?: T
}

回到我们的例子中,要在不创建LoginResponse,只创建它的data的类型的情况下,如何实现等同于下面效果的Swagger注解:

 @ApiOkResponse({ description: '登录成功返回', type: LoginResponse })

我们需要自定义一个装饰器,创建common/decorators/result.decorator.ts:

import { Type, applyDecorators } from '@nestjs/common'
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger'
import { ResultData } from '../utils/result'

const baseTypeNames = ['String', 'Number', 'Boolean']
/**
 * 封装 swagger 返回统一结构
 * 支持复杂类型 {  code, msg, data }
 * @param model 返回的 data 的数据类型
 * @param isArray data 是否是数组
 * @param isPager 设置为 true, 则 data 类型为 { list, total } , false data 类型是纯数组
 */
export const ApiResult = <TModel extends Type<any>>(model?: TModel, isArray?: boolean, isPager?: boolean) => {
  let items = null
  const modelIsBaseType = model && baseTypeNames.includes(model.name)
  if (modelIsBaseType) {
    items = { type: model.name.toLocaleLowerCase() }
  } else if(model) {
    items = { $ref: getSchemaPath(model) }
  }
  let prop = {}
  if (isArray && isPager) {
    prop = {
      type: 'object',
      properties: {
        list: {
          type: 'array',
          items,
        },
        total: {
          type: 'number',
          default: 0,
        },
      },
    }
  } else if (isArray) {
    prop = {
      type: 'array',
      items,
    }
  } else if (items) {
    prop = items
  } else {
    prop = { type: 'null', default: null }
  }
  return applyDecorators(
    ApiExtraModels(...(model && !modelIsBaseType ? [ResultData, model] : [ResultData])),
    ApiOkResponse({
      schema: {
        allOf: [
          { $ref: getSchemaPath(ResultData) },
          {
            properties: {
              data: prop,
            },
          },
        ],
      },
    }),
  )
}

然后将ApiResult装饰器应用到接口方法上:

 @Post('login')
  @ApiOperation({ summary: '登录' })
  @ApiResult(CreateTokenDto)
  async login(@Body() dto: LoginUser): Promise<CreateTokenDto> {
    return await this.userService.login(dto.account, dto.password)
  }

重新运行项目,我们看到返回数据已经显示完整了:

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nest.js 中实现接口的权限控制可以通过自定义 Guard 实现。Guard 是 Nest.js 中用于控制路由访问权限的模块,它可以在请求路由处理之前对请求进行拦截并进行验证。以下是一个简单的示例: 首先,你需要创建一个 AuthGuard 类,用于对请求进行验证: ```typescript import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { // 在这里进行权限验证,如果通过则返回 true,否则返回 false return true; } } ``` 然后,在需要进行权限控制的路由上使用 AuthGuard: ```typescript import { Controller, Get, UseGuards } from '@nestjs/common'; import { AuthGuard } from './auth.guard'; @Controller('users') export class UsersController { @Get() @UseGuards(AuthGuard) findAll(): string { return 'This action returns all users'; } } ``` 在这个示例中,我们在 `findAll` 方法上使用了 `@UseGuards(AuthGuard)` 装饰器,表示需要通过 AuthGuard 进行权限验证。当请求到达 `findAll` 方法时,Nest.js 会先调用 AuthGuard 的 `canActivate` 方法进行权限验证。如果 `canActivate` 返回 true,则请求会继续被处理;否则,请求将被拒绝。 你可以在 `canActivate` 方法中实现任意的权限验证逻辑,例如验证用户是否拥有访问该接口的权限、验证请求中的 token 是否合法等等。如果验证成功,`canActivate` 方法应该返回 true;否则,返回 false。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值