前言
因为后端用java写的项目用到了swagger来生成文档,用起来真的一言难尽,但是为了快速开发,很多时候并没有太多时间来单独写一份api文档,更别说长期更新维护了。所以用代码来生成文档,好像就成了一种需求。
刚好有机会我能全栈负责一个项目,当然要搞起来啊,虽然不用,但是这不就是成为项目经验的一种了。
开搞。
安装依赖并封装启动相关代码
pnpm i @nestjs/swagger
安装后我们先去环境变量文件新增几个配置项,用于开发和生产环境使用。
# 是否启用swagger
SWAGGER_ENABLED=true
# swagger 标题
SWAGGER_TITLE="API 文档"
# swagger 描述
SWAGGER_DESCRIPTION="文档描述"
当我们在生产环境,就可以将SWAGGER_ENABLED
配置成false
来关闭生成文档,一劳永逸。
接着创建一个文件:swagger/index.ts
;专门来处理初始化文档。
import type { INestApplication } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
export interface InitOptions {
app: INestApplication;
configService: ConfigService;
}
export function initSwaggerDocument(options: InitOptions) {
const { app, configService } = options;
const title = configService.get("SWAGGER_TITLE");
const description = configService.get("SWAGGER_DESCRIPTION");
const swaggerOptions = new DocumentBuilder()
.setTitle(title)
.setDescription(description)
// .addBasicAuth() // 账号密码认证
.addBearerAuth() // 令牌认证
.build();
const document = SwaggerModule.createDocument(app, swaggerOptions);
SwaggerModule.setup("docs", app, document, {
jsonDocumentUrl: "/swagger/json",
swaggerOptions: {
persistAuthorization: true
}
});
}
环境变量我们需要通过configService
来获取,这个是nestjs中最常用的一个模块,就不多解释了。
如果需要账号密码登录认证,可以开启注释addBasicAuth
这块。不过一般都是令牌认证就是了,配置后就可以在文档页面用Try it out按钮来直接发起api请求了。
"docs"
表示文档的路径,比如你的项目启动地址是:http://localhost:3000
,那么你的文档地址就是:
http://localhost:3000/docs
注意这个文档路径访问不会受到Nestjs的GlobalPrefix
影响。
启动封装完毕后我们去Nestjs的main文件中启动。
// main.ts
import { initSwaggerDocument } from "@/swagger";
import { ConfigService } from "@nestjs/config";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
const configService = app.get(ConfigService);
// Swagger
const openSwagger = configService.get("SWAGGER_ENABLED") === "true";
if (openSwagger) {
initSwaggerDocument({
app,
configService
});
}
// 端口
await app.listen(3000);
}
bootstrap();
这样就行了,我们的初始化就配置完成了。
自动抓取类型和注释生成文档
如果你不使用自动,那么就需要手动添加非常多的装饰器,装饰器里面还要写对应的文案解释,但是我们一般都有注释,这就导致要写两遍,非常难受,我个人是很喜欢自动抓取的,第一避免了重复解释,第二是省略了大量装饰器,使得代码更专注于业务实现。
举个例子:
export class CreateUserDto {
/** 注释解释 */
@ApiProperty({description: "解释"})
email: string;
@ApiProperty(...)
password: string;
@ApiProperty({ enum: RoleEnum, default: [], isArray: true })
roles: RoleEnum[] = [];
@ApiProperty({ required: false, default: true })
isEnabled?: boolean = true;
}
如果开启后:
export class CreateUserDto {
/** 注释解释 */
email: string;
password: string;
roles: RoleEnum[] = [];
isEnabled?: boolean = true;
}
自动抓类型,如果属性存在注释,注释就是description,如不开启自动抓取,你可能需要声明好多装饰器来完善文档。
开启方式也很简单,我们找到项目中的nest-cli.json
文件。
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"classValidatorShim": true,
"introspectComments": true
}
}
]
}
}
添加plugins
属性,里面内容如上,options可以自定义一些配置:
introspectComments
:如果设置为 true,插件将根据注释生成属性描述和示例值。classValidatorShim
:如果设置为 true,模块将重用 class-validator 验证装饰器(例如 @Max(10) 将 max: 10 添加到模式定义中)
基本上配置这两个就满足大部分需求了,如果你有更复杂的场景,可以看看这篇文章:CLI Plugin
再看一个控制器的示例:
import type { UserData } from "@/common/decorators";
import { User } from "@/common/decorators";
import { Body, Controller, Get, HttpStatus, Param } from "@nestjs/common";
import { ApiResponse, ApiTags } from "@nestjs/swagger";
import { TaskService } from "./task.service";
import { TaskDetail } from "./entities/task-detail.entity";
@Controller("task")
@ApiTags("task")
export class TaskController {
constructor(private readonly taskService: TaskService) {}
/** 获取任务详情 */
@Get(":taskId")
@ApiResponse({ status: HttpStatus.OK, type: TaskDetail })
getTaskDetail(@Param("taskId") taskId: number, @User() user: UserData) {
return this.taskService.getTaskDetail(taskId, user);
}
}
可以看到我们需要使用@ApiResponse
装饰器来告知swagger我这个控制器的返回值和状态码,其中type就是返回值,当然你也可以通过配置schema
属性来自定义,但是就相当麻烦了,使用type是一个比较便捷的方式,TaskDetail
就是getTaskDetail
最终renturn出去的值。
即便这样也是很繁琐的,这个控制器还算简单,如果你还有dto文件,那么还得使用ApiQuery
或者ApiBody
装饰器来声明这个接口的参数。
如果使用了自动抓取,那么就非常简单了。
import type { UserData } from "@/common/decorators";
import { User } from "@/common/decorators";
import { Body, Controller, Get, HttpStatus, Param } from "@nestjs/common";
import { ApiTags } from "@nestjs/swagger";
import { TaskService } from "./task.service";
@Controller("task")
@ApiTags("task")
export class TaskController {
constructor(private readonly taskService: TaskService) {}
/** 获取任务详情 */
@Get(":taskId")
getTaskDetail(@Param("taskId") taskId: number, @User() user: UserData) {
return this.taskService.getTaskDetail(taskId, user);
}
}
代码上可以省略这些装饰器,从而更利于阅读,当然你想使用装饰器,也是可以自行添加的,并不会有什么影响。
与helmet安全头信息插件冲突
如果你在Nestjs中使用了helmet插件来使得头信息更加安全,那么访问swagger文档的时候,多半会遇到无法打开的情况,造成这种情况的原因是contentSecurityPolicy
这个配置项的问题。
官方建议这么配置:
app.register(helmet, {
contentSecurityPolicy: {
directives: {
defaultSrc: [`'self'`],
styleSrc: [`'self'`, `'unsafe-inline'`],
imgSrc: [`'self'`, 'data:', 'validator.swagger.io'],
scriptSrc: [`'self'`, `https: 'unsafe-inline'`],
},
},
});
但是我个人更倾向于这样:
// 头信息安全
app.use(
helmet({
crossOriginResourcePolicy: { policy: "cross-origin" },
contentSecurityPolicy: !configService.get("SWAGGER_ENABLED") === "true"
})
);
直接设置布尔值更好,且只有在swagger开启的时候才设置false。
crossOriginResourcePolicy
有可能也会影响到文档打开,如果遇到了问题,可以尝试配置成我这样。
总结
以上就是关于如何在项目中开启swagger的教程,当然它并没有详细的告诉你我怎么去使用swagger的各种装饰器,比如如何隐藏返回对象中的个别属性,这些都属于很细节的东西,如果你想要了解这些,就应该去看一下官方的文档来学习使用技巧,本篇文章只是帮助你快速开启功能,它不应该注重太多的细节。
评论(0)