返回 AiToEarn
api-doc.decorator.ts
根目录 / project / aitoearn-backend / libs / common / src / decorators / api-doc.decorator.ts
1 import type { Type } from '@nestjs/common'
2 import type { ReferenceObject, SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'
3 import type { ZodType } from 'zod'
4 import { applyDecorators } from '@nestjs/common'
5 import { ApiBody, ApiExtraModels, ApiOperation, ApiQuery, ApiResponse } from '@nestjs/swagger'
6 import { z } from 'zod'
7 import { zodToJsonSchemaOptions } from '../utils'
8
9 export interface ApiDocOptions {
10 /**
11 * 接口摘要
12 */
13 summary: string
14
15 /**
16 * 接口详细描述
17 */
18 description?: string
19
20 /**
21 * 请求体 DTO Schema(可选)
22 */
23 body?: ZodType
24
25 /**
26 * 请求参数 DTO Schema(可选)
27 */
28 query?: ZodType
29
30 /**
31 * 响应 VO 类型
32 */
33 response?: Type | [Type] | ZodType
34 }
35
36 /**
37 * Swagger 文档装饰器
38 * 用于统一生成接口文档,支持分页响应和请求体验证
39 *
40 * @param options 装饰器选项
41 */
42 export function ApiDoc(options: ApiDocOptions) {
43 const {
44 summary,
45 description,
46 body,
47 query,
48 response,
49 } = options
50
51 const responseType = Array.isArray(response) ? response[0] : response
52
53 const decorators: MethodDecorator[] = [
54 ApiOperation({
55 summary,
56 description,
57 }),
58 ]
59
60 if (responseType && typeof responseType === 'function') {
61 decorators.push(ApiExtraModels(responseType))
62 }
63
64 if (body) {
65 const meta = z.globalRegistry.get(body)
66 let schemaObject: SchemaObject | ReferenceObject
67 if (meta && meta.id) {
68 schemaObject = {
69 $ref: `#/components/schemas/${meta.id}`,
70 }
71 }
72 else {
73 schemaObject = z.toJSONSchema(body, { ...zodToJsonSchemaOptions, io: 'input' }) as SchemaObject
74 }
75 decorators.push(
76 ApiBody({
77 schema: schemaObject,
78 }),
79 )
80 }
81 if (query) {
82 const meta = z.globalRegistry.get(query)
83 let schemaObject: SchemaObject | ReferenceObject
84 if (meta && meta.id) {
85 schemaObject = {
86 $ref: `#/components/schemas/${meta.id}`,
87 }
88 }
89 else {
90 schemaObject = z.toJSONSchema(query, { ...zodToJsonSchemaOptions, io: 'input' }) as SchemaObject
91 }
92 decorators.push(
93 ApiQuery({
94 schema: schemaObject,
95 }),
96 )
97 }
98
99 let dataSchema: SchemaObject | ReferenceObject | undefined
100 if (responseType) {
101 if (typeof responseType === 'function') {
102 dataSchema = Array.isArray(response)
103 ? {
104 type: 'array',
105 items: {
106 $ref: `#/components/schemas/${responseType.name}`,
107 },
108 }
109 : {
110 $ref: `#/components/schemas/${responseType.name}`,
111 }
112 }
113 else {
114 const meta = z.globalRegistry.get(responseType)
115 let schemaObject: SchemaObject | ReferenceObject
116 if (meta && meta.id) {
117 schemaObject = {
118 $ref: `#/components/schemas/${meta.id}`,
119 }
120 }
121 else {
122 schemaObject = z.toJSONSchema(responseType, { ...zodToJsonSchemaOptions, io: 'output' }) as SchemaObject
123 }
124 dataSchema = schemaObject
125 }
126 }
127
128 decorators.push(
129 ApiResponse({
130 status: 'default',
131 schema: {
132 type: 'object',
133 properties: {
134 ...(dataSchema ? { data: dataSchema } : {}),
135 code: {
136 type: 'number',
137 description: '错误码',
138 },
139 message: {
140 type: 'string',
141 description: '错误消息',
142 },
143 requestId: {
144 type: 'string',
145 description: '请求 ID',
146 },
147 },
148 required: ['data', 'code', 'message'],
149 },
150 }),
151 )
152
153 return applyDecorators(...decorators)
154 }
155
155 lines TYPESCRIPT