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)
  }

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/409740.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【零基础】VOSviewer小白入门第一课

官网安装&#xff1a;VOSviewer - Visualizing scientific landscapes 安装完成后即可以打开VOSviewer: 在 wos of science 中搜索关键词&#xff1a;lawdata 选择导出&#xff0c;按照plain text file格式导出&#xff0c;可以到处1000个。选择all record 得到下图 读取vosvi…

Linux安装jdk、tomcat、MySQL离线安装与启动

一、JDK和Tomcat的安装 1.JDK安装 直接上传到Linux服务器的&#xff0c;上传jdk、tomcat安装包 解压JDK安装包 //解压jdk tar -zxvf jdk-8u151-linux-x64.tar.gz 置环境变量(JAVA_HOME和PATH) vim /etc/profile 在文件末尾添加以下内容&#xff1a; //java environment expo…

设计模式学习笔记 - 面向对象 - 8.实践:贫血模型和充血模型的原理及实践

1.Web开发常用的贫血MVC架构违背OOP吗&#xff1f; 前面我们依据讲过了面向对象四大特性、接口和抽象类、面向对象和面向过程编程风格&#xff0c;基于接口而非实现编程和多用组合少用继承设计思想。接下来&#xff0c;通过实战来学习如何将这些理论应用到实际的开发中。 大部…

WiFi又演进了,这次是WiFi 7

现在很多笔记本laptop、电视TV、手机Phone,甚至车机IVI都有了WiFi和蓝牙BT的接入功能。 不管WiFi、蓝牙BlueTooth、NBIoT、ZigBee等等无线的技术、无线通信模块的技术,其本质都是在无线频谱上以某种频段某种调制方式传输某个协议的数据进行通信,所以通信标准的演进就决定着…

Linux之vim的使用详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.vim简介 二.vim的基本概念 三.vim的基本操作 3.1准备 …

Kafka:kafka的主从模式和故障切换 ②

一、Kafka整体架构图 二、Kafka原题回答 Kafka集群有主从模式吗&#xff1f; Kafka集群实际上并没有严格意义上的主从模式。Kafka的设计是基于分布式的&#xff0c;每个Topic都会切分为多个Partition&#xff0c;每个Partition都有一个Leader和多个Follower。 所有的读写操作…

计算机网络面经-TCP的拥塞控制

写在前边 前边我们分享了网络分层协议、TCP 三次握手、TCP 四次分手。今天我们继续深入分享一下 TCP 中的拥塞控制。 对于 TCP 的拥塞控制,里边设计到很多细节,平平无奇的羊希望通过这一节能够将这部分内容串通起来,能够让你更深刻的记忆这部分内容。 思维导图 1、什么…

AIGC专栏9——Scalable Diffusion Models with Transformers (DiT)结构解析

AIGC专栏9——Scalable Diffusion Models with Transformers &#xff08;DiT&#xff09;结构解析 学习前言源码下载地址网络构建一、什么是Diffusion Transformer (DiT)二、DiT的组成三、生成流程1、采样流程a、生成初始噪声b、对噪声进行N次采样c、单次采样解析I、预测噪声I…

Spring的另一大的特征:AOP

目录 AOP &#xff08;Aspect Oriented Programming&#xff09;AOP 入门案例&#xff08;注解版&#xff09;AOP 工作流程——代理AOP切入点表达式AOP 通知类型AOP通知获取数据获取切入点方法的参数获取切入点方法返回值获取切入点方法运行异常信息 百度网盘分享链接输入密码数…

【Linux基础】Linux自动化构建工具make/makefile

背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需要后…

性格正直的人适合什么职业?

有信仰&#xff0c;有责任&#xff0c;有骨气&#xff0c;有尊严&#xff0c;这应该是大多数人对正直的人的理解&#xff0c;他们的心中有信仰&#xff0c;肩上有责任&#xff0c;灵魂有骨气&#xff0c;头上有尊严&#xff0c;不管在什么时候都能够坚守道德准则&#xff0c;不…

【文生视频】Diffusion Transformer:OpenAI Sora 原理、Stable Diffusion 3 同源技术

文生视频 Diffusion Transformer&#xff1a;Sora 核心架构、Stable Diffusion 3 同源技术 提出背景变换器的引入Diffusion Transformer (DiT)架构Diffusion Transformer (DiT)总结 OpenAI Sora 设计思路阶段1: 数据准备和预处理阶段2: 架构设计阶段3: 输入数据的结构化阶段4: …

蓝桥杯算法赛 第 6 场 小白入门赛 解题报告 | 珂学家 | 简单场 + 元宵节日快乐

前言 整体评价 因为适逢元宵节&#xff0c;所以这场以娱乐为主。 A. 元宵节快乐 题型: 签到 节日快乐&#xff0c;出题人也说出来自己的心愿, 祝大家AK快乐! import java.util.Scanner;public class Main {public static void main(String[] args) {System.out.println(&qu…

信息抽取(UIE):使用自然语言处理技术提升证券投资决策效率

一、引言 在当今快速变化的证券市场中&#xff0c;信息的价值不言而喻。作为一名资深项目经理&#xff0c;我曾领导一个关键项目&#xff0c;旨在通过先进的信息抽取技术&#xff0c;从海量的文本数据中提取关键事件&#xff0c;如企业并购、新产品发布以及政策环境的变动。这些…

学会字符转换

字符转换 题目描述&#xff1a;解法思路&#xff1a;解法代码&#xff1a;运行结果&#xff1a; 题目描述&#xff1a; 输入⼀一个字符串&#xff0c;将字符串中大写字母全部转为小写字母&#xff0c;小写字母转成大写字母&#xff0c;其他字符保持不变。注&#xff1a;字符串…

typescript使用解构传参

看下面这个函数 interface Student {id: number;name: string;class: string;sex: string;}function matriculation(student: Student) {//...}我们要调用它,就需要传递一个实现了Student约束的对象进去 interface Student {id: number;name: string;class: string;sex: string…

音视频数字化(数字与模拟-电视)

上一篇文章【音视频数字化(数字与模拟-音频广播)】谈了音频的广播,这次我们聊电视系统,这是音频+视频的采集、传输、接收系统,相对比较复杂。 音频系统的广播是将声音转为电信号,再调制后发射出去,利用“共振”原理,收音机接收后解调,将音频信号还原再推动扬声器,我…

Liunx--nginx负载均衡--前后端分离项目部署

一.nginx简介 Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;它以其轻量级、占用资源少、并发能力强而广受欢迎。 详细介绍 开发背景与特点&#xff1a;Nginx由俄罗斯人Igor Sysoev开发&#xff0c;它是一个自由的、开源的软件。Nginx设计上注重性能和效率&#xff0c;能…

数据库安全性与完整性设计

文章标签集合[数据库安全,数据敏感,通信安全,MD5,盐加密] 1 系统设计 1.1设计目标 &#xff08;1&#xff09;确定系统中需要保护的敏感数据和通信内容&#xff1b; &#xff08;2&#xff09;设计合适的签名、加密和解密算法&#xff1b; &#xff08;3&#xff09;实现…

docker-compose 搭建laravel环境

laravel环境包含nginx,mysql,php7.4,redis 一、安装好docker后pull镜像 1.nginx镜像 docker pull nginx:latest单独启动容器 docker run --name nginx -p 80:80 -d nginx 2.php镜像 docker pull php:7.4-fpm3.mysql镜像 docker pull mysql:5.74.redis镜像 docker pull r…