侧边栏壁纸
博主头像
神奇的程序员

今天的努力只为未来

  • 累计撰写 154 篇文章
  • 累计创建 25 个标签
  • 累计收到 78 条评论

目 录CONTENT

文章目录

Nest集成Swagger并部署至YAPI

神奇的程序员
2022-03-21 / 0 评论 / 2 点赞 / 250 阅读 / 6,139 字 正在检测是否收录...

前言

前几天在项目中集成了swagger,一切准备就绪打算将其部署到服务器时发现并不顺利,访问的时候页面白屏,由于我的nest项目采用的是单文件部署,互联网上没有找到相关的解决方案,于是我就成了第一个吃螃蟹的人。

经过一番折腾后,终于解决了这个问题,本文就跟大家分享下我的解决方案,欢迎各位感兴趣的开发者阅读本文。

集成Swagger

首先,我们通过yarn安装三个依赖包,如下所示:

yarn add @nestjs/swagger swagger-ui-express fastify-swagger

安装完成后,我们打开项目的入口文件main.ts添加如下所示的代码:

import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";

async function bootstrap() {
  // ****  其它代码省略  ****
  const config = new DocumentBuilder()
    .setTitle("nest-demo")
    .setDescription("nest-demo项目的API使用文档")
    .setVersion("1.0.0")
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup("api", app, document);
}

接下来,我们启动项目,在浏览器访问http://127.0.0.1:3000/api,显示的界面如下所示:

  • default选项列出了我们项目中的所有接口

image-20220317211550995

通过注解编写接口文档

在@nestjs/swagger库中,它提供了丰富的依赖供我们使用, 为我们生成友好的接口文档,接下来我们列举几个较为常用的注解:

  • @ApiTags注解,用于对controller层进行描述。
  • @ApiOperation注解,用于对controller中的具体接口进行描述。
  • @ApiProperty注解,用于对dto层的参数进行描述。
  • @ApiResponse注解,用于对接口的返回数据进行描述。

关于上述各个注解的具体使用方法可参考我的项目代码,如下所示:

经过上述配置后 ,最终访问效果如下所示:

image-20220317224923516

有关swagger注解的更多使用方法请移步:OpenAPI (Swagger)

响应数据中嵌套对象的声明

上个章节中,我们提到了@ApiResponse注解,它是用于对接口的响应值进行描述的,但是它默认情况下只能描述非嵌套对象的数据。

本章节我们来看看它的高级用法,用它来处理嵌套对象。

首先,我们来看下需求:在VO层中,接口调用成功时,会返回一个data字段,这个字段是一个object类型的数据,会根据具体的业务需求来传递对应的字段。

我们准备两个文件:ResultVO.tsDraftConfigVO

ResultVO的代码如下所示:

  • 我们对其他两个字段都使用@ApiProperty进行了作用描述,绕开了data字段
  • data字段就是我们的动态字段
export class ResultVO<T> {
  @ApiProperty({ example: "0", description: "接口状态码", type: "number" })
  private code!: number;
  @ApiProperty({ example: "接口调用成功", description: "调用状态" })
  private msg!: string;
  private data!: T | null;
}

DraftConfigVO的代码如下所示:

  • 包含一个draftConfig字段,对其进行了描述
export class DraftConfigVO {
  @ApiProperty({ example: "var config = {}", description: "配置数据" })
  private draftConfig!: string;

  public setDraftConfig(draftConfig: string): void {
    this.draftConfig = "var config = " + draftConfig;
  }

  public getDraftConfig(): string {
    return this.draftConfig;
  }
}

在文档的高级主题:通用ApiResponse章节我们找到了方案,依据文档所述,我们创建一个装饰器ApiDataResponse,代码如下所示:

  • ResultVO 就是我们刚才所创建的文件
  • data 字段就是我们在ResultVO中空出来的动态字段
  • 使用getSchemaPath方法来加载VO类
export const ApiDataResponse = <TModel extends Type<any>>(model: TModel) => {
  return applyDecorators(
    ApiOkResponse({
      schema: {
        allOf: [
          { $ref: getSchemaPath(ResultVO) },
          {
            properties: {
              data: {
                type: "object",
                $ref: getSchemaPath(model)
              }
            }
          }
        ]
      }
    })
  );
};

一切准备就绪后,接下来,我们就可以使用这个注解了,在方法前添加,如下所示:

@Controller("home")
export class AppController {
  /**其他代码省略**/
  @Post("setTitle")
  @ApiDataResponse(DraftConfigVO)
  setTitle(@Body() data: AppDto): VOUtils {
    // 客户端传入的数据
    console.log(data);
    return this.appService.setTitle();
  }
}

启动项目,访问后,我傻眼了,报错:Resolver error at paths./home/setTitle.post.responses.200.content.application/json.schema.allOf.0.$refCould not resolve reference:

image-20220322142419949

经过一番排查与摸索后,我发现那两个VO类并没有在项目中引用,不能生成一个相应的模型定义,所以导致它找不到而报错。

在互联网进行了一波检索,没有找到答案,只好求助了一波网友,知道了@ApiExtraModels这个注解。

image-20220322143339073

翻了下这个注解所对应的文档: 额外模型,看完后,我恍然大悟,跟着上面自定义的注解一起使用比较繁琐,所以我才用了文档中说的:将extraModels属性传递给SwaggerModule#createDocument()方法,我们在项目的入口文件添加如下所示的代码:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
	const document = SwaggerModule.createDocument(app, config, {
    // 项目内未使用,但是要被swagger模块使用的类,需要在此处进行声明
    extraModels: [ResultVO, DraftConfigVO]
  });
}

完整代码

上述示例的完整代码,如下所示:

部署至服务器

接下来,我们要做的就是将项目打包部署到服务器了,本项目采用的是单文件构建法,对此不了解的开发者请移步:Nest项目部署的最佳方式

构建时遇到的问题

因为集成了swagger进来,在打包时终端报错了ERROR in ./node_modules/@nestjs/mapped-types/dist/type-helpers.utils.js 69:27-63 Module not found: Error: Can't resolve 'class-transformer/storage' in ...

经过一番查找后,在mapped-types仓库的Issues中找到了答案,需要在webpack.config.js中的lazyImports中加入class-transformer/storage,打包的时候即可将其忽略,部分代码如下所示,完整代码请移步:

module.exports = {
  entry: "./src/main",
  target: "node",
  // ** 其他配置代码省略 **
  plugins: [
    // 需要进行忽略的插件
    new webpack.IgnorePlugin({
        checkResource(resource) {
        const lazyImports = [
          "@nestjs/microservices",
          "@nestjs/microservices/microservices-module",
          "@nestjs/websockets/socket-module",
          "cache-manager",
          "class-validator",
          "class-transformer",
          "class-transformer/storage"
        ];
    })
}

完整代码请移步:webpack.config.js

部署时遇到的问题

我们将项目部署到服务器,启动后,在浏览器通过127.0.0.1:3000/api访问swagger时发现页面一片空白,打开控制台后发现它的一些资源文件404了。

image-20220318072947623

这可真是个棘手的问题,直觉告诉我肯定是因为我配置了单文件部署才导致的,我在求助了很多人,查了很多资料后,发现他们都没像我这么玩过,他们都是在服务器上npm install依赖来跑nest项目的。

真是糟了个大糕🤡,我成了第一个吃螃蟹的人,前面一片蓝海等着我去探索。

经过一番思考后,应该是因为webpack把所有依赖都打包进main.js了,swagger-ui引用的文件应该是相对路径的,所以才导致了404问题,抱着这个疑问,我打开了swagger-ui-express的源码,在index.js中发现了猫腻:它果然是引入的相对路径。

image-20220318074256928

既然是相对路径,它自己的包下面又没有这个文件,那么它肯定是从别的包引入的。

image-20220318074447502

继续查阅源码后,我发现它还require了一个swagger-ui-dist

image-20220318074604930

果然,它所依赖的资源包都在这个目录下,他为什么要这么做呢?我又抱着疑问打开了swagger-ui仓库,在docs/usage/installation.md中它讲述了原因,提供了webpack的配置方案。

image-20220318075453246

打开链接所指向的项目后,在webpack的配置文件中我看到了copy-webpack-plugin插件,此时我茅塞顿开,它的做法就是将swagger-ui-dist的文件拷贝到dist下,这样就解决了它相对路径找不到文件的问题。

方案有了,那么就可以愉快的写出代码了,如下所示:

const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
  entry: "./src/main",
  target: "node",
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        // 拷贝swagger相关的文件
        {
          from: __dirname + "/node_modules/swagger-ui-dist/",
          to: "./"
        }
      ]
    })
  ]
}

重新构建后,我们发现dist目录下多了swagger-ui-dist的文件,我们启动项目,重新在浏览器发现已经能正常看到swagger的界面了。

image-20220318111124109

细心的开发者可能发现swagger-ui-dist目录下有很多无用文件,污染了我们的dist目录,我们需要将这些无用的文件在打包后清理掉。

image-20220318112446885

接下来就该clean-webpack-plugin插件登场了,我们在webpack的配置文件中加入下述代码:

  • cleanAfterEveryBuildPatterns 意为在构建完成之后删除文件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  entry: "./src/main",
  target: "node",
  plugins: [
    // ** 其它代码省略 **
    // 删除多余的文件
    new CleanWebpackPlugin({
      cleanAfterEveryBuildPatterns: [
        __dirname + "/dist/*.html",
        __dirname + "/dist/*.map",
        __dirname + "/dist/*.md",
        __dirname + "/dist/*.json",
        __dirname + "/dist/index.js",
        __dirname + "/dist/LICENSE",
        __dirname + "/dist/NOTICE"
      ]
    })
  ]
}

现在dist目录看起来就舒服多了😼

image-20220318114857681

注意:copy-webpack-plugin、clean-webpack-plugin需要用yarn自行安装到devDependencies依赖中。

完整代码请移步:webpack.config.js

部署至YAPI

最后,我们在yapi的数据管理模块,导入swagger数据过来,本以为很顺利,结果它报错:返回数据格式不是JSON。

image-20220318113759221

翻阅文档后,我找到了方案,原来是要在地址后面加-jsonimage-20220318114005725

加了之后,就能顺利的导入到yapi了,大功告成🤗

image-20220318114103459

做第一个吃螃蟹的人太难了,为了解决这个问题,我在浏览器已经不知不觉的开了这么多标签页了🌚

image-20220318114447903

项目代码

本文所使用的完整代码,请移步项目的GitHub仓库:nest-project

写在最后

至此,文章就分享完毕了。

我是神奇的程序员,一位前端开发工程师。

如果你对我感兴趣,请移步我的个人网站,进一步了解。

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于神奇的程序员公众号,未经许可禁止转载💌
2

评论区