前言
swagger这东东,萝卜青菜各有所爱吧. 反正我呆的公司用这个,我用的也还行! 有兴趣的可以瞅瞅~ 说说优点吧, 可以精确的展示每个字段意义,只要注解写的到位! schema也能正常读取!还能直接测试接口!
效果图
以下就是配置好及写一些demo接口所展示的效果;
包括语法高亮,api分组,响应注解,api废弃,接口概述等
![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2c48d018a2724b72a54bc1987f597b9ftplv-k3u1fbpfcp-zoom-1.image)
实战
安装
代码语言:javascript复制# 前者是swagger的nest module,官方团队维护的
# 后者是适配express的swagger ui库
# 库用新不用旧,语法会有所差异!
yarn add @nestjs/swagger swagger-ui-express
配置
抽离的环境变量(dev.local.env)
代码语言:javascript复制# ------- 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
用工厂函数组装配置!
代码语言:javascript复制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声明入手~~~
代码语言:javascript复制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>[];// 没用过
}
代码语言:javascript复制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的声明~~~
代码语言:javascript复制// 相当直观,大多数类型的及区间的限制都能一目了然
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[];
}
代码语言:javascript复制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,我们举个栗子!
代码语言:javascript复制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;
}
}
总结
到此,基本可以边写代码及文档输出了, 只要注解用的好,swagger都能如期输出! 有不对之处请留言,会及时修正!谢谢阅读~