网络异常,图片无法展示
|
前言
swagger这东东,萝卜青菜各有所爱吧.
反正我呆的公司用这个,我用的也还行!
有兴趣的可以瞅瞅~
说说优点吧,
可以精确的展示每个字段意义,只要注解写的到位!
schema也能正常读取!还能直接测试接口!
效果图
以下就是配置好及写一些demo接口所展示的效果;
包括语法高亮,api分组,响应注解,api废弃,接口概述等~
网络异常,图片无法展示
|
实战
安装
# 前者是swagger的nest module,官方团队维护的 # 后者是适配express的swagger ui库 # 库用新不用旧,语法会有所差异! yarn add @nestjs/swagger swagger-ui-express
配置
抽离的环境变量(dev.local.env)
# ------- Node服务相关 --------------------- # Node服务启动监听的端口 SERVE_LISTENER_PORT=3000 # ------- Swagger相关 --------------------- # Swagge Api文档访问路径 SWAGGER_SETUP_PATH=api-docs # 标题及描述 SWAGGER_UI_TITLE=氚云3.0 BFF文档 SWAGGER_UI_TITLE_DESC=一点寒芒先到,随后枪出如龙 # API版本 SWAGGER_API_VERSION=1.0 # Swagger Api Prefix SWAGGER_ENDPOINT_PREFIX=api/v1 # ------- 开发模式相关 --------------------- NODE_ENV=development
用工厂函数组装配置!
import { registerAs } from '@nestjs/config'; export interface EnvSwaggerOptions { title: string; setupUrl: string; desc?: string; prefix: string; version: string; } export default registerAs( 'EnvSwaggerOptions', (): EnvSwaggerOptions => ({ title: process.env.SWAGGER_UI_TITLE, // swagger标题 desc: process.env.SWAGGER_UI_TITLE_DESC, // swagger描述 version: process.env.SWAGGER_API_VERSION, // swagger api 版本,自定义的 setupUrl: process.env.SWAGGER_SETUP_PATH, // UI文档路径 prefix: process.env.SWAGGER_ENDPOINT_PREFIX, // 接口聚合前缀,在nest用全局prefix,但是丢给swagger定义也不冲突 }), );
代码入口(main.ts)
熟悉的味道,还是把一些配置抽里成环境变量,
外部维护,通过注册中心使用~~
老规矩,从typescript声明入手~~~
import { INestApplication } from '@nestjs/common'; import { OpenAPIObject, SwaggerCustomOptions, SwaggerDocumentOptions } from './interfaces'; export declare class SwaggerModule { static createDocument(app: INestApplication, config: Omit<OpenAPIObject, 'paths'>, options?: SwaggerDocumentOptions): OpenAPIObject; static setup(path: string, app: INestApplication, document: OpenAPIObject, options?: SwaggerCustomOptions): void; private static setupExpress; private static setupFastify; } import { OpenAPIObject } from './interfaces'; import { ExternalDocumentationObject, SecuritySchemeObject, ServerVariableObject } from './interfaces/open-api-spec.interface'; export declare class DocumentBuilder { private readonly logger; private readonly document; setTitle(title: string): this; // 设置swagger ui标题 setDescription(description: string): this; // 设置swagger ui描述 setVersion(version: string): this; // 设置swagger ui版本 setTermsOfService(termsOfService: string): this; // 设置条例链接,可以单纯理解为一个外链 setContact(name: string, url: string, email: string): this; // 联系信息 setLicense(name: string, url: string): this; // 采用的协议,比如MIT等等 // 若是用到了外部nginx这类接口,这个可以拼接请求域 addServer(url: string, description?: string, variables?: Record<string, ServerVariableObject>): this; setExternalDoc(description: string, url: string): this; // 设置外部文档链接 setBasePath(path: string): this; // 可以理解为聚合前缀,在nest有自己的api可以用,可以忽略设置这个 addTag(name: string, description?: string, externalDocs?: ExternalDocumentationObject): this; // 添加swagger分类 addSecurity(name: string, options: SecuritySchemeObject): this; // 以下都是鉴权安全性相关的 addSecurityRequirements(name: string, requirements?: string[]): this; // ... addBearerAuth(options?: SecuritySchemeObject, name?: string): this;// Bearer 认证 addOAuth2(options?: SecuritySchemeObject, name?: string): this;// OAuth2 认证 addApiKey(options?: SecuritySchemeObject, name?: string): this;// addBasicAuth(options?: SecuritySchemeObject, name?: string): this;// 基础认证 addCookieAuth(cookieName?: string, options?: SecuritySchemeObject, securityName?: string): this; // Cookie 认证 build(): Omit<OpenAPIObject, 'components' | 'paths'>; // 读取设置好的配置构建出swagger的集中化配置 } export interface SwaggerDocumentOptions { include?: Function[]; // 手动指定包含的模块 extraModels?: Function[]; // 额外的model定义需和上面的关联,也就是存在include里面的 ignoreGlobalPrefix?: boolean; // 这个设置为true,会忽略setGlobalPrefix的设置 deepScanRoutes?: boolean; // 开启这个,只要是import的都会追加的索引的路由 // 操作id,可以通过这个工厂函数来改变id的定义(接口请求生成) // 默认走的是@default () => controllerKey_methodKey, 模块_方法 operationIdFactory?: (controllerKey: string, methodKey: string) => string; } export interface SwaggerCustomOptions { explorer?: boolean; // 开了没啥效果 swaggerOptions?: any; // swagger ui的配置 customCss?: string; // 自定义css customCssUrl?: string; // 自定义css 链接 customJs?: string; // 同上,js customfavIcon?: string;// 同上,小图标 swaggerUrl?: string; // swagger链接设置 customSiteTitle?: string; // 自定义网站标题 validatorUrl?: string; // 远程校验url,一般用不到 url?: string;// 指向API定义的URL(通常是swagger。json或swagger.yaml)。如果使用url或规范,将被忽略。 urls?: Record<'url' | 'name', string>[];// 没用过 }
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; import { ConfigService } from '@nestjs/config'; import { EnvSwaggerOptions } from './config/env/swagger.config'; import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from './common/pipes/validataion.pipe'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: false, logger: false, }); const configService = app.get(ConfigService); const swaggerOptions = configService.get<EnvSwaggerOptions>( 'EnvSwaggerOptions', ); // 设置全局请求访问前缀 app.setGlobalPrefix(swaggerOptions.prefix); const options = new DocumentBuilder() .setExternalDoc('xxxxx前端文档 ','htxxxxx') .setTitle(swaggerOptions.title) .setDescription(swaggerOptions.desc) .setVersion(swaggerOptions.version) .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup(swaggerOptions.setupUrl, app, document, { customSiteTitle: swaggerOptions.title, swaggerOptions: { explorer: true, docExpansion: 'list', filter: true, showRequestDuration: true, syntaxHighlight:{ active:true, theme:"tomorrow-night" } } }); await app.listen(configService.get('SERVE_LISTENER_PORT')); } bootstrap()
注解介绍
用于DTO的注解
也就是用来生成modal(字段的解释)
@ApiProperty ()
:@ApiProperty({ required:false})
就等同于下面,@ApiPropertyOptional()
: 这个是上个基础上把必填变成选填
我们看下typescript的声明~~~
// 相当直观,大多数类型的及区间的限制都能一目了然 export interface SchemaObject { nullable?: boolean; discriminator?: DiscriminatorObject; readOnly?: boolean; writeOnly?: boolean; xml?: XmlObject; externalDocs?: ExternalDocumentationObject; example?: any; examples?: any[]; deprecated?: boolean; type?: string; allOf?: (SchemaObject | ReferenceObject)[]; oneOf?: (SchemaObject | ReferenceObject)[]; anyOf?: (SchemaObject | ReferenceObject)[]; not?: SchemaObject | ReferenceObject; items?: SchemaObject | ReferenceObject; properties?: Record<string, SchemaObject | ReferenceObject>; additionalProperties?: SchemaObject | ReferenceObject | boolean; description?: string; format?: string; default?: any; title?: string; multipleOf?: number; maximum?: number; exclusiveMaximum?: boolean; minimum?: number; exclusiveMinimum?: boolean; maxLength?: number; minLength?: number; pattern?: string; maxItems?: number; minItems?: number; uniqueItems?: boolean; maxProperties?: number; minProperties?: number; required?: string[]; enum?: any[]; }
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export enum UserRole { Boss="后门", Admin = '管理员', User = '常规用户', } export class CreateAppDto { @ApiPropertyOptional({ description: '姓名', }) readonly name?: string; @ApiProperty({ description: '年龄', minimum: 0, maximum: 130 }) readonly age: number; @ApiPropertyOptional({ description: '爱好', }) readonly hobit: string; @ApiProperty({ enum: ['Boss', 'Admin', 'User']}) role: UserRole; }
用于业务逻辑的
这个就好多了,有具体到接口回来,异常等等;
常规Response用ApiResponse就能满足很多场景,
schame,状态码都能定义~~~
具体可以跳进去看typescript,我们举个栗子!
import { Controller, Get, Post, HttpCode, Body, Query } from '@nestjs/common'; import { ApiCreatedResponse, ApiHeader, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, } from '@nestjs/swagger'; import { CreateAppDto, FindOneParams, UserRole } from './app.dto'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @ApiHeader({ name: 'Authorization', description: 'Auth token', }) @ApiCreatedResponse({ description: '链接成功创建,其实就是201状态的描述', }) @Post('/post') @HttpCode(200) @ApiParam({ name: 'name', description: '名字', type: CreateAppDto }) postParams(@Body() param: CreateAppDto): string { return '测试参数' + JSON.stringify(param); } @Get('/user') @ApiOperation({ tags: ['获取用户信息'], description: '获取用户信息', deprecated: true, }) @ApiQuery({ name: 'id', description: '用户id' }) @ApiResponse({ description: '成功请求回来,其实就是200的描述', status: 200 }) @ApiInternalServerErrorResponse({ description: '服务端异常' }) updateApp(@Query() query: FindOneParams) { return JSON.stringify(query); } @Get('/netease-news/:id') @ApiOkResponse({ description: '成功请求回来' }) @ApiQuery({ name: 'id', description: '用户id', required: false }) async async(@Body() body) { const res = await this.appService.getNetEaseNew( 'https://anapioficeandfire.com/api/characters/583', { data: body }, ); return res.data; } @ApiQuery({ name: 'role', enum: UserRole }) @ApiOperation({ tags: ['返回角色信息'], description: '返回角色信息', }) @Get('/role') async filterByRole(@Query('role') role: UserRole = UserRole.User) { return role; } }