如果 class 在我将它变成一个函数并将其应用于 NestJS 中的 Swagger 响应时无法正常工作怎么办?
What if the class is not working properly when i make it into a function and apply it to Swagger's response in NestJS?
请理解我的英语很差。
示例代码:Github
@Injectable()
export class FAQService {
constructor(private faqRepository: FAQRepository) {}
async getFAQ(options: Options): Promise<GetFAQResponse> {
const response = await paginate<FAQEntity>(this.faqRepository, options);
return { data: response, statusCode: 200 };
}
}
在我们做的项目中,return值的形状设置为{data: response, statusCode: 200}
,如上面的方法所示。
这种形式在所有模块的所有服务中重复出现,当 response
从服务方法中 return 时,拦截器更改为 {data: response, statusCode: 200}
。
在这里,我有一个问题,如果拦截器进行相应的处理,它不适用于swagger。
为了解决这个问题,我创建了一个自定义装饰器,如下所示。
// http-method.decorator.ts
import { ApiOkResponse, ApiOperation, ApiProperty } from '@nestjs/swagger';
import { Get as NestGet, applyDecorators } from '@nestjs/common';
class BaseResponse {
@ApiProperty()
data: any;
@ApiProperty()
statusCode: number;
}
export interface IEndpointParams {
route: string;
summary: string;
type?: Type<unknown> | Function | [Function] | string;
}
const mapResponse = <T>(type: T) => {
class Response extends BaseResponse {
@ApiProperty({ type })
data: T;
}
return Response;
};
export function Get(params: IEndpointParams) {
const { route, summary, type } = params;
return applyDecorators(NestGet(route), ApiOperation({ summary }), ApiOkResponse({ type: mapResponse(type) }));
}
它被应用到控制器如下。
@Controller('faq')
@ApiTags('FAQ API')
@ApiSecurity('Authorization')
export class FAQController {
constructor(private faqService: FAQService) {}
@Get({ route: '', summary: 'List Of FAQ', type: GetFAQResponse })
async faqs(@PaginationQuery() query: PaginationQuery): Promise<GetFAQResponse> {
return await this.faqService.getFAQ(query);
}
}
然后我创建了一个拦截器并将其应用于应用程序。
// http-response.interceptor.ts
@Injectable()
export class HttpResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<BaseResponse> {
const ctx = context.switchToHttp();
const response = ctx.getResponse<Response>();
return next.handle().pipe(map((data) => ({ data, statusCode: response.statusCode })));
}
}
// app.module.ts
import { APP_INTERCEPTOR } from '@nestjs/core';
...
import { HttpResponseInterceptor } from './common/http-response.interceptor';
@Module({
imports: [
...
FaqModule,
TradeModule,
BannersModule,
NoticeModule,
],
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: HttpResponseInterceptor,
},
],
})
export class AppModule {
...
}
如上所述,swagger 也显示了 {data: response, statusCode: 200}
,但问题是 swagger 中的所有端点都应用了 AppModule
中最后导入模块控制器的最后一个方法的类型(这里,NoticeModule
)。
// faq.controller.ts
@Controller('faq')
@ApiTags('FAQ API')
@ApiSecurity('Authorization')
export class FAQController {
constructor(private faqService: FAQService) {}
@Get({ route: '', summary: 'List Of FAQ', type: GetFAQResponse })
async faqs(@PaginationQuery() query: PaginationQuery): Promise<GetFAQResponse> {
return await this.faqService.getFAQ(query);
}
}
// notice.controller.ts
@Controller('notice')
@ApiTags('Notice API')
@ApiSecurity('Authorization')
export class NoticeController {
constructor(private noticeService: NoticeService) {}
@Get({ route: '', summary: 'List Of FAQ', type: GetNoticeResponse })
async notices(@PaginationQuery() query: PaginationQuery): Promise<GetNoticeResponse> {
return await this.noticeService.getNotice(query);
}
}
假设FAQ Controller
和Notice Controller
如上所述。现在 Notice Module
是在 AppModule
中最后导入的,GetNoticeResponse
反映在 faq endpoint 的 swagger 文档中,而不是 GetFAQResponse
。问题好像出在mapResponse
函数中,但我不知道为什么。
我找到了答案。
通过mapResponse
函数生成的class名称重叠,最后一个class反映在Swagger
因此,将代码修改如下,以防止名称重叠。
const mapResponse = <T>(type: T) => {
if (!type) return type;
const descriptor = Object.getOwnPropertyDescriptor(type, 'name');
class WrappedResponse extends BaseResponse {
@ApiProperty({ type })
data: T;
}
Object.defineProperty(WrappedResponse, 'name', {
value: `Wrapped${descriptor.value}`,
});
return WrappedResponse;
};
请理解我的英语很差。
示例代码:Github
@Injectable()
export class FAQService {
constructor(private faqRepository: FAQRepository) {}
async getFAQ(options: Options): Promise<GetFAQResponse> {
const response = await paginate<FAQEntity>(this.faqRepository, options);
return { data: response, statusCode: 200 };
}
}
在我们做的项目中,return值的形状设置为{data: response, statusCode: 200}
,如上面的方法所示。
这种形式在所有模块的所有服务中重复出现,当 response
从服务方法中 return 时,拦截器更改为 {data: response, statusCode: 200}
。
在这里,我有一个问题,如果拦截器进行相应的处理,它不适用于swagger。
为了解决这个问题,我创建了一个自定义装饰器,如下所示。
// http-method.decorator.ts
import { ApiOkResponse, ApiOperation, ApiProperty } from '@nestjs/swagger';
import { Get as NestGet, applyDecorators } from '@nestjs/common';
class BaseResponse {
@ApiProperty()
data: any;
@ApiProperty()
statusCode: number;
}
export interface IEndpointParams {
route: string;
summary: string;
type?: Type<unknown> | Function | [Function] | string;
}
const mapResponse = <T>(type: T) => {
class Response extends BaseResponse {
@ApiProperty({ type })
data: T;
}
return Response;
};
export function Get(params: IEndpointParams) {
const { route, summary, type } = params;
return applyDecorators(NestGet(route), ApiOperation({ summary }), ApiOkResponse({ type: mapResponse(type) }));
}
它被应用到控制器如下。
@Controller('faq')
@ApiTags('FAQ API')
@ApiSecurity('Authorization')
export class FAQController {
constructor(private faqService: FAQService) {}
@Get({ route: '', summary: 'List Of FAQ', type: GetFAQResponse })
async faqs(@PaginationQuery() query: PaginationQuery): Promise<GetFAQResponse> {
return await this.faqService.getFAQ(query);
}
}
然后我创建了一个拦截器并将其应用于应用程序。
// http-response.interceptor.ts
@Injectable()
export class HttpResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<BaseResponse> {
const ctx = context.switchToHttp();
const response = ctx.getResponse<Response>();
return next.handle().pipe(map((data) => ({ data, statusCode: response.statusCode })));
}
}
// app.module.ts
import { APP_INTERCEPTOR } from '@nestjs/core';
...
import { HttpResponseInterceptor } from './common/http-response.interceptor';
@Module({
imports: [
...
FaqModule,
TradeModule,
BannersModule,
NoticeModule,
],
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: HttpResponseInterceptor,
},
],
})
export class AppModule {
...
}
如上所述,swagger 也显示了 {data: response, statusCode: 200}
,但问题是 swagger 中的所有端点都应用了 AppModule
中最后导入模块控制器的最后一个方法的类型(这里,NoticeModule
)。
// faq.controller.ts
@Controller('faq')
@ApiTags('FAQ API')
@ApiSecurity('Authorization')
export class FAQController {
constructor(private faqService: FAQService) {}
@Get({ route: '', summary: 'List Of FAQ', type: GetFAQResponse })
async faqs(@PaginationQuery() query: PaginationQuery): Promise<GetFAQResponse> {
return await this.faqService.getFAQ(query);
}
}
// notice.controller.ts
@Controller('notice')
@ApiTags('Notice API')
@ApiSecurity('Authorization')
export class NoticeController {
constructor(private noticeService: NoticeService) {}
@Get({ route: '', summary: 'List Of FAQ', type: GetNoticeResponse })
async notices(@PaginationQuery() query: PaginationQuery): Promise<GetNoticeResponse> {
return await this.noticeService.getNotice(query);
}
}
假设FAQ Controller
和Notice Controller
如上所述。现在 Notice Module
是在 AppModule
中最后导入的,GetNoticeResponse
反映在 faq endpoint 的 swagger 文档中,而不是 GetFAQResponse
。问题好像出在mapResponse
函数中,但我不知道为什么。
我找到了答案。
通过mapResponse
函数生成的class名称重叠,最后一个class反映在Swagger
因此,将代码修改如下,以防止名称重叠。
const mapResponse = <T>(type: T) => {
if (!type) return type;
const descriptor = Object.getOwnPropertyDescriptor(type, 'name');
class WrappedResponse extends BaseResponse {
@ApiProperty({ type })
data: T;
}
Object.defineProperty(WrappedResponse, 'name', {
value: `Wrapped${descriptor.value}`,
});
return WrappedResponse;
};