feat: 为OPENAPI添加统一返回数据结构

master
luoer 2023-08-02 17:27:38 +08:00
parent a110e3a59d
commit c89a732209
15 changed files with 135 additions and 10 deletions

View File

@ -10,8 +10,8 @@ import { DatabaseModule } from '@/database';
import { ValidationModule } from '@/common/validation';
import { AuthModule } from '@/modules/auth';
import { UserModule } from '@/modules/user';
import { ResponseModule } from './common/response';
import { SerializationModule } from './common/serialization';
import { ResponseModule } from '@/common/response';
import { SerializationModule } from '@/common/serialization';
@Module({
imports: [

View File

@ -3,6 +3,10 @@ import { LoggerService } from './logger.service';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LoggerInterceptor } from './logger.interceptor';
/**
*
* @description
*/
@Global()
@Module({
providers: [

View File

@ -1,11 +1,15 @@
import { Transform } from 'class-transformer';
import { IsNumber, IsOptional, Min } from 'class-validator';
/**
* DTO
* @example { page: 1, size: 10 }
*/
export class PaginationDto {
/**
*
* @example 1
*/
// @IsNumber()
@IsOptional()
@IsNumber()
@Min(1)
@ -14,6 +18,7 @@ export class PaginationDto {
/**
*
* @example 10
*/
@IsOptional()
@IsNumber()

View File

@ -4,6 +4,10 @@ import { AllExecptionFilter } from './notcaptured.filter';
import { HttpExecptionFilter } from './http.filter';
import { ResponseInterceptor } from './response.interceptor';
/**
*
* @description /HTTP/
*/
@Module({
providers: [
/**
@ -33,5 +37,3 @@ import { ResponseInterceptor } from './response.interceptor';
],
})
export class ResponseModule {}
export const a = 1;

View File

@ -1,6 +1,10 @@
import { ClassSerializerInterceptor, Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
/**
*
* @description
*/
@Module({
providers: [
/**

View File

@ -1,6 +1,10 @@
import { ConfigService } from '@/config';
import { ServeStaticModule as _ServeStaticModule } from '@nestjs/serve-static';
/**
*
* @see https://docs.nestjs.com/techniques/mvc#serve-static
*/
export const ServeStaticModule = _ServeStaticModule.forRootAsync({
useFactory: (config: ConfigService) => {
return [

View File

@ -1,7 +1,12 @@
import { INestApplication } from '@nestjs/common';
import { ConfigService } from '@/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
import { addResponseWrapper } from './util';
/**
* Swagger
* @param app
*/
export const initSwagger = (app: INestApplication) => {
const config = app.get(ConfigService);
const docConfig = new DocumentBuilder()
@ -16,7 +21,12 @@ export const initSwagger = (app: INestApplication) => {
.addTag('post', '文章管理')
.addTag('upload', '文件上传')
.build();
const document = SwaggerModule.createDocument(app, docConfig);
const options: SwaggerDocumentOptions = {
operationIdFactory(controllerKey, methodKey) {
return `${controllerKey}_${methodKey}`;
},
};
const document = addResponseWrapper(SwaggerModule.createDocument(app, docConfig, options));
SwaggerModule.setup(config.apiDocPrefix, app, document, {
jsonDocumentUrl: `${config.apiDocPrefix}.json`,
yamlDocumentUrl: `${config.apiDocPrefix}.yaml`,

View File

@ -0,0 +1,70 @@
import { OpenAPIObject } from '@nestjs/swagger';
/**
*
* @param doc OPENAPI
* @example
* ```json
* {
* "code": 2000,
* "message": "请求成功",
* "data": []
* }
* ```
* @returns
*/
export function addResponseWrapper(doc: OpenAPIObject) {
for (const path of Object.keys(doc.paths)) {
const pathItem = doc.paths[path];
if (!pathItem) {
continue;
}
for (const method of Object.keys(pathItem)) {
const responses = doc.paths[path][method].responses;
if (!responses) {
continue;
}
for (const status of Object.keys(responses)) {
const json = responses[status].content?.['application/json'];
if (!json) {
continue;
}
const schema = json.schema;
json.schema = {
allOf: [
{
$ref: '#/components/schemas/Response',
},
{
type: 'object',
description: '返回数据',
properties: {
data: schema,
},
},
],
};
}
}
}
doc.components.schemas.Response = {
type: 'object',
properties: {
code: {
type: 'integer',
description: '状态码',
example: 2000,
format: 'int32',
},
message: {
type: 'string',
description: '提示信息',
example: '请求成功',
},
},
required: ['code', 'message'],
};
return doc;
}

View File

@ -3,6 +3,10 @@ import { APP_FILTER, APP_PIPE } from '@nestjs/core';
import { validationPipeFactory } from './validation.pipe';
import { ValidationExecptionFilter } from './validation.filter';
/**
*
* @description
*/
@Module({
providers: [
/**

View File

@ -2,6 +2,10 @@ import { Global, Module } from '@nestjs/common';
import { ConfigModule as _ConfigModule } from '@nestjs/config';
import { ConfigService } from './config.service';
/**
*
* @description `@nestjs/config` 便
*/
@Global()
@Module({
imports: [

View File

@ -3,7 +3,12 @@ import { ConfigService as _ConfigService } from '@nestjs/config';
@Injectable()
export class ConfigService {
constructor(public config: _ConfigService) {}
constructor(
/**
* `@nestjs/config` ConfigService
*/
public config: _ConfigService,
) {}
/**
* get

View File

@ -4,6 +4,7 @@ import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
/**
*
* @description `typeorm`
*/
export const DatabaseModule = TypeOrmModule.forRootAsync({
useFactory: (config: ConfigService) => {

View File

@ -1 +1,6 @@
export class CreateUploadDto {}
import { ApiProperty } from '@nestjs/swagger';
export class CreateUploadDto {
@ApiProperty({ type: 'string', format: 'binary' })
file: any;
}

View File

@ -1,8 +1,9 @@
import { Controller, Delete, Get, Param, Patch, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger';
import { UploadService } from './upload.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { Respond } from '@/common/response';
import { CreateUploadDto } from './dto/create-upload.dto';
@ApiTags('upload')
@Controller('upload')
@ -12,6 +13,8 @@ export class UploadController {
@Post()
@UseInterceptors(FileInterceptor('file'))
@ApiOperation({ summary: '上传文件', operationId: 'upload' })
@ApiConsumes('multipart/form-data')
@ApiBody({ description: '文件', type: CreateUploadDto })
create(@UploadedFile() file: Express.Multer.File) {
return this.uploadService.create(file);
}

View File

@ -3,6 +3,10 @@ import { IsOptional, IsString } from 'class-validator';
import { PaginationDto } from '@/common/response';
export class FindUserDto extends IntersectionType(PaginationDto) {
/**
*
* @example '绝弹'
*/
@IsOptional()
@IsString()
nickname: string;