前言

因为后端用java写的项目用到了swagger来生成文档,用起来真的一言难尽,但是为了快速开发,很多时候并没有太多时间来单独写一份api文档,更别说长期更新维护了。所以用代码来生成文档,好像就成了一种需求。

刚好有机会我能全栈负责一个项目,当然要搞起来啊,虽然不用,但是这不就是成为项目经验的一种了。

开搞。

安装依赖并封装启动相关代码

bash
复制代码
pnpm i @nestjs/swagger

安装后我们先去环境变量文件新增几个配置项,用于开发和生产环境使用。

复制代码
# 是否启用swagger
SWAGGER_ENABLED=true
# swagger 标题
SWAGGER_TITLE="API 文档"
# swagger 描述
SWAGGER_DESCRIPTION="文档描述"

当我们在生产环境,就可以将SWAGGER_ENABLED配置成false来关闭生成文档,一劳永逸。

接着创建一个文件:swagger/index.ts;专门来处理初始化文档。

typescript
复制代码
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,那么你的文档地址就是:

bash
复制代码
http://localhost:3000/docs

注意这个文档路径访问不会受到Nestjs的GlobalPrefix影响。

启动封装完毕后我们去Nestjs的main文件中启动。

typescript
复制代码
// 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();

这样就行了,我们的初始化就配置完成了。

自动抓取类型和注释生成文档

如果你不使用自动,那么就需要手动添加非常多的装饰器,装饰器里面还要写对应的文案解释,但是我们一般都有注释,这就导致要写两遍,非常难受,我个人是很喜欢自动抓取的,第一避免了重复解释,第二是省略了大量装饰器,使得代码更专注于业务实现。

举个例子:

typescript
复制代码
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;
}

如果开启后:

typescript
复制代码

export class CreateUserDto {
  /** 注释解释 */
  email: string;
  password: string;
  roles: RoleEnum[] = [];
  isEnabled?: boolean = true;
}

自动抓类型,如果属性存在注释,注释就是description,如不开启自动抓取,你可能需要声明好多装饰器来完善文档。

开启方式也很简单,我们找到项目中的nest-cli.json文件。

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

再看一个控制器的示例:

typescript
复制代码
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装饰器来声明这个接口的参数。

如果使用了自动抓取,那么就非常简单了。

typescript
复制代码
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这个配置项的问题。

官方建议这么配置:

typescript
复制代码
app.register(helmet, {
  contentSecurityPolicy: {
    directives: {
      defaultSrc: [`'self'`],
      styleSrc: [`'self'`, `'unsafe-inline'`],
      imgSrc: [`'self'`, 'data:', 'validator.swagger.io'],
      scriptSrc: [`'self'`, `https: 'unsafe-inline'`],
    },
  },
});

但是我个人更倾向于这样:

typescript
复制代码
// 头信息安全
app.use(
    helmet({
        crossOriginResourcePolicy: { policy: "cross-origin" },
        contentSecurityPolicy: !configService.get("SWAGGER_ENABLED") ===  "true"
    })
);

直接设置布尔值更好,且只有在swagger开启的时候才设置false。

crossOriginResourcePolicy有可能也会影响到文档打开,如果遇到了问题,可以尝试配置成我这样。

总结

以上就是关于如何在项目中开启swagger的教程,当然它并没有详细的告诉你我怎么去使用swagger的各种装饰器,比如如何隐藏返回对象中的个别属性,这些都属于很细节的东西,如果你想要了解这些,就应该去看一下官方的文档来学习使用技巧,本篇文章只是帮助你快速开启功能,它不应该注重太多的细节。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。