前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nest.js 添加 swagger 响应数据文档

nest.js 添加 swagger 响应数据文档

作者头像
愧怍
发布2023-10-21 13:18:08
2620
发布2023-10-21 13:18:08
举报

基本使用

通常情况下,在 nest.js 的 swagger 页面文档中的响应数据文档默认如下

此时要为这个控制器添加响应数据文档的话,只需要先声明 数据的类型,然后通过@ApiResponse 装饰器添加到该控制器上即可,举例说明

todo.entity.ts

代码语言:javascript
复制
@Entity('todo')
export class TodoEntity {
  @Column()
  @ApiProperty({ description: 'todo' })
  value: string

  @ApiProperty({ description: 'todo' })
  @Column({ default: false })
  status: boolean
}

todo.controller.ts

代码语言:javascript
复制
  @Get()
  @ApiOperation({ summary: '获取Todo详情' })
  @ApiResponse({ type: [TodoEntity] })
  async list(): Promise<TodoEntity[]> {
    return this.todoService.list();
  }


  @Get(':id')
  @ApiOperation({ summary: '获取Todo详情' })
  @ApiResponse({ type: TodoEntity })
  async info(@IdParam() id: number): Promise<TodoEntity> {
    return this.todoService.detail(id);
  }

此时对应的文档数据如下显示

image-20230718012234692
image-20230718012234692

如果你想要自定义返回的数据,而不是用 entity 对象的话,可以按照如下定义

todo.model.ts

代码语言:javascript
复制
export class Todo {
  @ApiProperty({ description: 'todo' })
  value: string

  @ApiProperty({ description: 'todo' })
  status: boolean
}

然后将 @ApiResponse({ type: TodoEntity }) 中的 TodoEntity 替换 Todo 即可。

自定义返回数据

然而通常情况下,都会对返回数据进行一层包装,如

代码语言:javascript
复制
{
  "data": [
    {
      "name": "string"
    }
  ],
  "code": 200,
  "message": "success"
}

其中 data 数据就是原始数据。要实现这种数据结构字段,首先定义一个自定义类用于包装,如

res.model.ts

代码语言:javascript
复制
export class ResOp<T = any> {
  @ApiProperty({ type: 'object' })
  data?: T

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

  @ApiProperty({ type: 'string', default: 'success' })
  message: string

  constructor(code: number, data: T, message = 'success') {
    this.code = code
    this.data = data
    this.message = message
  }
}

接着在定义一个拦截器,将 data 数据用 ResOp 包装,如下拦截器代码如下

transform.interceptor.ts

代码语言:javascript
复制
export class TransformInterceptor implements NestInterceptor {
  constructor(private readonly reflector: Reflector) {}

  intercept(
    context: ExecutionContext,
    next: CallHandler<any>,
  ): Observable<any> {
    return next.handle().pipe(
      map(data => {
        const response = context.switchToHttp().getResponse<FastifyReply>()
        response.header('Content-Type', 'application/json; charset=utf-8')
        return new ResOp(HttpStatus.OK, data ?? null)
      }),
    )
  }
}

此时返回的数据都会转换为 { "data": { }, "code": 200, "message": "success" } 的形式,这部分不为就本文重点,就不赘述了。

回到 Swagger 文档中,只需将 @ApiResponse({ type: TodoEntity }) 改写成 @ApiResponse({ type: ResOp<TodoEntity> }),就可以实现下图需求。

image-20230718012618710
image-20230718012618710

自定义 Api 装饰器

然而对于庞大的业务而言,使用 @ApiResponse({ type: ResOp<TodoEntity> })的写法,肯定不如 @ApiResponse({ type: TodoEntity })来的高效,有没有什么办法能够用后者的写法,却能达到前者的效果,答案是肯定有的。

这里需要先自定义一个装饰器,命名为 ApiResult,完整代码如下

api-result.decorator.ts

代码语言:javascript
复制
import { Type, applyDecorators, HttpStatus } from '@nestjs/common'
import { ApiExtraModels, ApiResponse, getSchemaPath } from '@nestjs/swagger'

import { ResOp } from '@/common/model/response.model'

const baseTypeNames = ['String', 'Number', 'Boolean']

/**
 * @description: 生成返回结果装饰器
 */
export const ApiResult = <TModel extends Type<any>>({
  type,
  isPage,
  status,
}: {
  type?: TModel | TModel[]
  isPage?: boolean
  status?: HttpStatus
}) => {
  let prop = null

  if (Array.isArray(type)) {
    if (isPage) {
      prop = {
        type: 'object',
        properties: {
          items: {
            type: 'array',
            items: { $ref: getSchemaPath(type[0]) },
          },
          meta: {
            type: 'object',
            properties: {
              itemCount: { type: 'number', default: 0 },
              totalItems: { type: 'number', default: 0 },
              itemsPerPage: { type: 'number', default: 0 },
              totalPages: { type: 'number', default: 0 },
              currentPage: { type: 'number', default: 0 },
            },
          },
        },
      }
    } else {
      prop = {
        type: 'array',
        items: { $ref: getSchemaPath(type[0]) },
      }
    }
  } else if (type) {
    if (type && baseTypeNames.includes(type.name)) {
      prop = { type: type.name.toLocaleLowerCase() }
    } else {
      prop = { $ref: getSchemaPath(type) }
    }
  } else {
    prop = { type: 'null', default: null }
  }

  const model = Array.isArray(type) ? type[0] : type

  return applyDecorators(
    ApiExtraModels(model),
    ApiResponse({
      status,
      schema: {
        allOf: [
          { $ref: getSchemaPath(ResOp) },
          {
            properties: {
              data: prop,
            },
          },
        ],
      },
    }),
  )
}

其核心代码就是在 @ApiResponse 上进行扩展,这一部分代码在官方文档: advanced-generic-apiresponse 中提供相关示例,这里我简单说明下:

{ $ref: getSchemaPath(ResOp) } 表示原始数据,要被“塞”到那个类下,而第二个参数 properties: { data: prop } 则表示 ResOpdata 属性要如何替换,替换的部分则由 prop 变量决定,只需要根据实际需求构造相应的字段结构。

由于有些类没有被任何控制器直接引用, SwaggerModule SwaggerModule 还无法生成相应的模型定义,所以需要 @ApiExtraModels(model) 将其额外导入。

此时只需要将 @ApiResponse({ type: TodoEntity }) 改写为 @ApiResult({ type: TodoEntity }),就可达到最终目的。

不过我还对其进行扩展,使其能够返回分页数据格式,具体根据实际数据而定,演示效果如下图:

image-20230718023729609
image-20230718023729609

导入第三方接口管理工具

通过上述的操作后,此时记下项目的 swagger-ui 地址,例如 http://127.0.0.1:5001/api-docs, 此时再后面添加-json,即 http://127.0.0.1:5001/api-docs-json 所得到的数据便可导入到第三方的接口管理工具,就能够很好的第三方的接口协同,接口测试等功能。

image-20230718022612215
image-20230718022612215
image-20230718022446188
image-20230718022446188
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-7-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本使用​
  • 自定义返回数据​
  • 自定义 Api 装饰器​
  • 导入第三方接口管理工具​
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档