$ npm install @midwayjs/swagger@3 --save $ npm install swagger-ui-dist --save-dev
如果想要在服务器上输出 Swagger API 页面,则需要将 swagger-ui-dist
安装到依赖中。
$ npm install swagger-ui-dist --save
或者在 package.json
中增加如下依赖后,重新安装。
{ "dependencies": { "@midwayjs/swagger": "^3.0.0", // 如果你希望在服务器上使用 "swagger-ui-dist": "^4.2.1", // ... }, "devDependencies": { // 如果你不希望在服务器上使用 "swagger-ui-dist": "^4.2.1", // ... } }
在 configuration.ts
中增加组件。
import { Configuration } from '@midwayjs/core'; import * as swagger from '@midwayjs/swagger'; @Configuration({ imports: [ // ... swagger ] }) export class MainConfiguration { }
可以配置启用的环境,比如下面的代码指的是 只在 local 环境下启用。
import { Configuration } from '@midwayjs/core'; import * as swagger from '@midwayjs/swagger'; @Configuration({ imports: [ // ... { component: swagger, enabledEnvironment: ['local'] } ] }) export class MainConfiguration { }
然后启动项目,访问地址:
路径可以通过 swaggerPath
参数配置。
Swagger 组件会识别各个 @Controller
中每个路由方法的 @Body()
、@Query()
、@Param()
装饰器,提取路由方法参数和类型。
比如下面的代码:
@Get('/') async home( @Query('uid') uid: number, @Query('tid') tid: string, @Query('isBoolean') isBoolean: boolean, ) { // ... }
我们常在参数使用对象,并使用定义好的类作为类型,这个时候 swagger 组件也能自动识别,同时也能和普通的类型进行组合识别。
比如下面的代码:
@Post('/:id', { summary: 'test'}) async create(@Body() createCatDto: CreateCatDto, @Param('id') id: number) { // ... }
CreateCatDto
类型的定义如下,我们使用 ApiProperty
将其中的每个属性都进行了定义。
import { ApiProperty } from "@midwayjs/swagger"; export class CreateCatDto { @ApiProperty({ example: 'Kitty', description: 'The name of the Catname'}) name: string; @ApiProperty({ example: '1', description: 'The name of the Catage'}) age: number; @ApiProperty({ example: 'bbbb', description: 'The name of the Catbreed'}) breed: string; }
通过给 @ApiProperty(...)
装饰器中设置 type,我们可以定义常见的类型。
大多数情况下,基础类型无需显式声明 type
,可自动识别。
字符串
@ApiProperty({ type: 'string', // ... }) name: string;
布尔类型
@ApiProperty({ type: 'boolean', example: 'true', // ... }) isPure: boolean;
数字类型
@ApiProperty({ type: 'number', example: '1', description: 'The name of the Catage' }) age: number;
此外,也可以使用 format 字段来定义更为精确的长度。
@ApiProperty({ type: 'integer', format: 'int32', example: '1', description: 'The name of the Catage' }) age: number;
如果是数组类型,我们可以配置 type 字段来定义,同时通过 items
的 type
来指定类型。
@ApiProperty({ type: 'array', items: { type: 'string', }, example: ['1'], description: 'The name of the Catage' }) breeds: string[];
如果是枚举类型,可以通过配置 enmu 字段来定义。
enum HelloWorld { One = 'One', Two = 'Two', Three = 'Three', } @ApiProperty({ enum: ['One', 'Two', 'Three'], description: 'The name of the Catage' }) hello: HelloWorld;
如果某个属性的类型是个现有的复杂类型,我们可以使用 type
来指定这个复杂的类型。
export class Cat { /** * The name of the Catcomment * @example Kitty */ @ApiProperty({ example: 'Kitty', description: 'The name of the Cat'}) name: string; @ApiProperty({ example: 1, description: 'The age of the Cat' }) age: number; @ApiProperty({ example: '2022-12-12 11:11:11', description: 'The age of the CatDSate' }) agedata?: Date; @ApiProperty({ example: 'Maine Coon', description: 'The breed of the Cat', }) breed: string; } export class CreateCatDto { // ... @ApiProperty({ type: Cat, // 这里无需指定 example }) related: Cat; }
如果某个属性的类型是个复杂的数组类型,写法略有不同。
首先type
必须声明为 array
,除了设置type
,我们还可以使用 getSchemaPath
方法额外导入一个不同的类型(上面的复杂对象也可以使用它设置$ref)。
此外,如果 Cat
类型没有在其他属性的 type
字段中声明过,需要使用 @ApiExtraModel
装饰器额外声明引入外部类型。
import { ApiProperty, getSchemaPath, ApiExtraModel } from '@midwayjs/swagger'; class Cat { // ... } @ApiExtraModel(Cat) export class CreateCatDto { // ... @ApiProperty({ type: 'array', items: { $ref: getSchemaPath(Cat), } }) relatedList: Cat[]; }
当类之间具有循环依赖关系时,请使用惰性函数提供类型信息。
比如 type
字段的循环。
class Photo { // ... @ApiProperty({ type: () => Album }) album: Album; } class Album { // ... @ApiProperty({ type: () => Photo }) photo: Photo; }
getSchemaPath
也可以使用。
export class CreateCatDto { // ... @ApiProperty({ type: 'array', items: { $ref: () => getSchemaPath(Cat) } }) relatedList: Cat[]; }
OpenAPI 定义的 paths 就是各个路由路径,且每个路由路径都有 HTTP 方法的定义,比如 GET、POST、DELETE、PUT 等。
使用 @ApiQuery
来定义 Query 数据。
基础使用,会自动识别 @Query
装饰器。
@Get('/get_user') async getUser(@Query('name') name: string) { return 'hello'; }
如果 @Query
以对象形式,需要在 @ApiQuery
指定一个 name 参数,对象类型需要配合 @ApiProperty
使用,否则表单会变为只读形式。
export class UserDTO { @ApiProperty() name: string; } @Get('/get_user') @ApiQuery({ name: 'query' }) async getUser(@Query() dto: UserDTO) { // ... }
使用 @ApiBody
来定义 Body 数据。
@Body
对象类型需要配合 @ApiProperty
使用。
export class UserDTO { @ApiProperty() name: string; } @Post('/update_user') async upateUser(@Body() dto: UserDTO) { // ... }
如需其他细节,请使用 @ApiBody
增强。
注意,Swagger 规定,Body
定义只能存在一个,如果配置了 @ApiBody
,则类型提取的数据会自动被覆盖。
比如下面示例中,Body
的类型会被替换为 Cat
。
@ApiBody({ type: Cat }) async upateUser(@Body() dto: UserDTO) { // ... }
文件上传是 Post 请求中较为特殊的一类场景。
可以通过在 DTO 中定义属性来实现多个文件以及 Fields
的类型。
import { ApiProperty, BodyContentType } from "@midwayjs/swagger"; export class CreateCatDto { // ... @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary', } }) files: any; } // ... @Post('/test1') @ApiBody({ contentType: BodyContentType.Multipart, schema: { type: CreateCatDto, } }) async upload1(@Files() files, @Fields() fields) { // ... }
如果不需要多个文件,使用 schema 定义即可。
export class CreateCatDto { // ... @ApiProperty({ type: 'string', format: 'binary', }) file: any; }
通过 @ApiHeader({...})
装饰器来定义 Header 参数。
@ApiHeader({ name: 'x-test-one', description: 'this is test one' }) @ApiTags(['hello']) @Controller('/hello') export class HelloController {}
可以使用 @ApiResponse({...})
来自定义请求 Response。
@Get('/:id') @ApiResponse({ status: 200, description: 'The found record', type: Cat, }) findOne(@Param('id') id: string, @Query('test') test: any): Cat { return this.catsService.findOne(+id); }
还提供了其他不需要设置 status 的装饰器:
@ApiOkResponse()
@ApiCreatedResponse()
@ApiAcceptedResponse()
@ApiNoContentResponse()
@ApiMovedPermanentlyResponse()
@ApiBadRequestResponse()
@ApiUnauthorizedResponse()
@ApiNotFoundResponse()
@ApiForbiddenResponse()
@ApiMethodNotAllowedResponse()
@ApiNotAcceptableResponse()
@ApiRequestTimeoutResponse()
@ApiConflictResponse()
@ApiTooManyRequestsResponse()
@ApiGoneResponse()
@ApiPayloadTooLargeResponse()
@ApiUnsupportedMediaTypeResponse()
@ApiUnprocessableEntityResponse()
@ApiInternalServerErrorResponse()
@ApiNotImplementedResponse()
@ApiBadGatewayResponse()
@ApiServiceUnavailableResponse()
@ApiGatewayTimeoutResponse()
@ApiDefaultResponse()
HTTP 请求返回的数据模型定义也可以通过指定 type,当然这个数据模型需要通过装饰器 @ApiProperty
来描述各个字段。
import { ApiProperty } from '@midwayjs/swagger'; export class Cat { @ApiProperty({ example: 'Kitty', description: 'The name of the Cat'}) name: string; @ApiProperty({ example: 1, description: 'The age of the Cat' }) age: number; @ApiProperty({ example: 'Maine Coon', description: 'The breed of the Cat', }) breed: string; }
Swagger 还支持带前缀 x-
的扩展字段,可以使用 @ApiExtension(x-..., {...})
装饰器。
@ApiExtension('x-hello', { hello: 'world' })
当不希望通过 type 来定义 model 类型时,我们可以通过在 Controller 中或者 Model Class 中加入 @ApiExtraModel
来增加额外的 schema
类型描述。
@ApiExtraModel(TestExtraModel) @Controller() class HelloController { @Post('/:id', { summary: 'test'}) @ApiResponse({ status: 200, content: { 'application/json': { schema: { properties: { data: { '$ref': getSchemaPath(TestExtraModel)} } } } } }) async create(@Body() createCatDto: CreateCatDto, @Param('id') id: number): Promise<Cat> { return this.catsService.create(createCatDto); } } // or @ApiExtraModel(TestExtraModel) class TestModel { @ApiProperty({ item: { $ref: getSchemaPath(TestExtraModel) }, description: 'The name of the Catage' }) one: TestExtraModel; }
Swagger 本身不支持泛型数据,泛型作为 Typescript 的一种类型,会在构建期抹掉,在运行时无法读取。
我们可以用一些取巧的方式来定义。
比如,我们需要将返回值增加一些通用的包裹结构。
{ code: 200, message: 'xxx', data: any }
为此,我们可以编写一个方法,入参是返回的 data,返回一个包裹的类。
import { Type } from '@midwayjs/swagger'; type Res<T> = { code: number; message: string; data: T; } export function SuccessWrapper<T>(ResourceCls: Type<T>): Type<Res<T>> { class Successed { @ApiProperty({ description: '状态码' }) code: number; @ApiProperty({ description: '消息' }) message: string; @ApiProperty({ type: ResourceCls, }) data: T; } return Successed; }
我们可以基于这个方法,来实现我们自己的返回类。
class ViewCat extends SuccessWrapper<Cat>(Cat) {}
在使用的时候,可以直接指定这个类即可。
@Get('/:id') @ApiResponse({ status: 200, description: 'The found record', type: ViewCat, }) async findOne(@Param('id') id: string, @Query('test') test: any): ViewCat { // ... }
Swagger 可以对每个路由添加标签,进行分组。
标签添加有两种形式。
默认情况下,框架会根据 Controller 的路径来生成标签,比如下面的代码,会生成一个 hello
的标签,这个标签会应用到这个控制器所有的路由上。
@Controller('/hello') export class HelloController {}
如果需要自定义标签,可以通过 @ApiTags([...])
来自定义 Controller 标签。
@ApiTags(['hello']) @Controller('/hello') export class HelloController {}
从 v3.17.3
开始,可以通过配置 isGenerateTagForController
来控制是否自动生成 Controller 标签。
// src/config/config.default.ts export default { swagger: { isGenerateTagForController: false } }
可以通过配置给 Tag 添加描述。
// src/config/config.default.ts export default { swagger: { tags: [ { name: 'api', description: 'API Document' }, { name: 'hello', description: 'Other Router' }, ] } }
组件可以通过添加授权验证配置来设置验证方式,我们支持配置 basic
、bearer
、cookie
、oauth2
、apikey
、custom
。
启用 basic 验证
// src/config/config.default.ts export default { // ... swagger: { auth: { authType: 'basic', }, }, }
关联 Controller
@ApiBasicAuth() @Controller('/hello') export class HelloController {}
启用 bearer 验证(bearerFormat 为 JWT)
// src/config/config.default.ts export default { // ... swagger: { auth: { authType: 'bearer', }, }, }
关联 Controller
@ApiBearerAuth() @Controller('/hello') export class HelloController {}
启用 oauth2 验证
// src/config/config.default.ts export default { // ... swagger: { auth: { authType: 'oauth2', flows: { implicit: { authorizationUrl: 'http://example.org/api/oauth/dialog', scopes: { 'write:pets': 'modify pets in your account', 'read:pets': 'read your pets' } }, authorizationCode: { authorizationUrl: 'https://example.com/api/oauth/dialog', tokenUrl: 'https://example.com/api/oauth/token', scopes: { 'write:pets': 'modify pets in your account', 'read:pets': 'read your pets' } }, }, }, }, }
关联 Controller
@ApiOAuth2() @Controller('/hello') export class HelloController {}
启用 cookie 验证
// src/config/config.default.ts export default { // ... swagger: { auth: { authType: 'cookie', securityName: 'testforcookie', cookieName: 'connect.sid', }, }, }
关联 Controller
@ApiCookieAuth('testforcookie') @Controller('/hello') export class HelloController {}
启用 cookie 验证
// src/config/config.default.ts export default { // ... swagger: { auth: { authType: 'apikey', name: 'api_key' }, }, }
关联 Controller
@ApiSecurity('api_key') @Controller('/hello') export class HelloController {}
自定义验证方式,需要自己设计参数配置
// src/config/config.default.ts export default { // ... swagger: { auth: { authType: 'custom', name: 'mycustom' // ... }, }, }
关联 Controller
@ApiSecurity('mycustom') @Controller('/hello') export class HelloController {}
配置 @ApiExcludeController
可以忽略整个 Controller 的路由。
@ApiExcludeController() @Controller('/hello') export class HelloController {}
配置 @ApiExcludeEndpoint
可以忽略单个路由。
@Controller('/hello') export class HelloController { @ApiExcludeEndpoint() @Get() async getUser() { // ... } }
如果需要满足更加动态的场景,可以通过配置路由过滤器来批量过滤。
// src/config/config.default.ts import { RouterOption } from '@midwayjs/core'; export default { // ... swagger: { routerFilter: (url: string, options: RouterOption) => { return url === '/hello/getUser'; } }, }
routerFilter
用来传入一个过滤函数,包含 url
和 routerOptions
两个参数。routerOptions
中包含了路由基础信息。
每当匹配到一个路由时,会自动执行 routerFilter
方法,当 routerFilter
返回 true 时,代表这个路由将会被过滤。
Swagger 组件提供了和 OpenAPI 一致的参数配置能力,可以通过自定义配置来实现。
配置项如下:
/** * see https://swagger.io/specification/ */ export interface SwaggerOptions { /** * 默认值: My Project * https://swagger.io/specification/#info-object title 字段 */ title?: string; /** * 默认值: This is a swagger-ui for midwayjs project * https://swagger.io/specification/#info-object description 字段 */ description?: string; /** * 默认值: 1.0.0 * https://swagger.io/specification/#info-object version 字段 */ version?: string; /** * https://swagger.io/specification/#info-object contact 字段 */ contact?: ContactObject; /** * https://swagger.io/specification/#info-object license 字段 */ license?: LicenseObject; /** * https://swagger.io/specification/#info-object termsOfService 字段 */ termsOfService?: string; /** * https://swagger.io/specification/#openapi-object externalDocs 字段 */ externalDocs?: ExternalDocumentationObject; /** * https://swagger.io/specification/#openapi-object servers 字段 */ servers?: Array<ServerObject>; /** * https://swagger.io/specification/#openapi-object tags 字段 */ tags?: Array<TagObject>; /** * 可以参考 https://swagger.io/specification/#security-scheme-object */ auth?: AuthOptions | AuthOptions[]; /** * 默认值: /swagger-ui * 访问 swagger ui 的路径 */ swaggerPath?: string; /** * 对路由 tag 进行 ascii 排序 * 可以使用 1-xxx、2-xxx、3-xxx 来定义 tag */ tagSortable?: boolean; /** * UI 展示中需要用到的配置 * 可以参考 https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md#display */ displayOptions?: { deepLinking?: boolean; displayOperationId?: boolean; defaultModelsExpandDepth?: number; defaultModelExpandDepth?: number; defaultModelRendering?: 'example' | 'model'; displayRequestDuration?: boolean; docExpansion?: 'list' | 'full' | 'none'; filter?: boolean | string; maxDisplayedTags?: number; showExtensions?: boolean; showCommonExtensions?: boolean; useUnsafeMarkdown?: boolean; tryItOutEnabled?: boolean; }; documentOptions?: { /** * 自定义 operationIdFactory,用于生成 operationId * @default () => controllerKey_webRouter.methodKey */ operationIdFactory?: ( controllerKey: string, webRouter: RouterOption ) => string; }; } /** * 继承自 https://swagger.io/specification/#security-scheme-object */ export interface AuthOptions extends Omit<SecuritySchemeObject, 'type'> { /** * 验权类型 * basic => http basic 验证 * bearer => http jwt 验证 * cookie => cookie 方式验证 * oauth2 => 使用 oauth2 * apikey => apiKey * custom => 自定义方式 */ authType: AuthType; /** * https://swagger.io/specification/#security-scheme-object type 字段 */ type?: SecuritySchemeType; /** * authType = cookie 时可以修改,通过 ApiCookie 装饰器关联的名称 */ securityName?: string; /** * authType = cookie 时可以修改,cookie 的名称 */ cookieName?: string; }
https://www.midwayjs.org/docs/extensions/swagger