解决通过 Nginx 无法打开 Knife4j 页面问题
问题描述
我们有一个项目使用了 Knife4j,依赖的版本为
1  | <dependency>  | 
这个版本的 Knife4j 使用了 SpringDoc,其获取 Swagger 配置的地址为 /v3/api-docs/swagger-config,获取文档的地址为 /v3/api-docs,如果项目有多个分组,获取单个分组,比如分组“1-动物”,的文档的地址为 /v3/api-docs/1-动物。
在项目部署时在项目的前面放置了一台 Nginx 反向代理服务器,配置了当访问路径为 /example 时会将请求转发到我们自己的项目
1  | location /example/ {  | 
当访问 http://localhost/example/doc.html 时,Knife4j 能正常的访问 Swagger 的配置,即 http://localhost/example/v3/api-docs/swagger-config,但是无法访问 http://localhost/v3/api-docs/1-动物,因此无法打开 Knife4j 文档页面。
分析问题
我们打开 Knife4j 的前端工程 knife4j-vue,在 BasicLayout.vue 在挂载完成后会调用 created() 方法
1  | created() {  | 
因为我们使用的是 SpringDoc,它将调用 initSpringDocOpenApi() 方法
1  | initSpringDocOpenApi() {  | 
在这个方法中会调用 initSwagger() 方法,这个方法的参数重有一个比较重要的是 springdoc: true
1  | initSwagger(options) {  | 
在 initSwagger() 方法中会创建 SwaggerBootstrapUi 对象,然后调用 main() 方法。
SwaggerBootstrapUi 对象定义在 Knife4jAsync.js 文件中,它的定义如下
1  | function SwaggerBootstrapUi(options) {  | 
我们看到在 this.springdoc 的值为 true 时,会从浏览器地址 http://localhost/example/doc.html 中截取出 http://localhost/example/ 部分作为 basePath,然后拼接上 v3/api-docs/swagger-config 部分得到 this.url 的值,即 http://localhost/example/v3/api-docs/swagger-config。这也就解释了为什么获取 Swagger 的配置没有问题。
从前面的分析我们知道在创建了 SwaggerBootstrapUi 对象后会调用它的 main() 方法,我们跟踪它的方法调用链直到 analysisGroup() 方法,在这个方法中调用前面组装的 this.url,即 http://localhost/example/v3/api-docs/swagger-config 获取分组信息
1  | SwaggerBootstrapUi.prototype.analysisGroup = function () {  | 
使用 this.url 作为参数调用 ajax() 方法获取的数据样本如下
1  | {  | 
在 analysisSpringDocOpenApiGroupSuccess() 中解析分组信息
1  | SwaggerBootstrapUi.prototype.analysisSpringDocOpenApiGroupSuccess = function (data) {  | 
这个方法比较长,只保留了我们关心的内容,做了这么几件事情,从返回的信息中 urls 中解析出 url,构建 SwaggerBootstrapUiInstance 对象信息。
接下来 analysisGroup() 方法会调用 createGroupElement() 方法
1  | SwaggerBootstrapUi.prototype.createGroupElement = function () {  | 
这个方法会使用 analysisGroup() 方法构建的第一个 SwaggerBootstrapUiInstance 对象,用它调用 analysisApi() 方法
1  | SwaggerBootstrapUi.prototype.analysisApi = function (instance) {  | 
这个方法使用 SwaggerBootstrapUiInstance 对象中的 url,即 /v3/api-docs/1-动物,自动拼接上域名/端口部分,即 http://loacalhost,组成完整的地址 http://loacalhost/v3/api-docs/1-动物 获取文档信息。
到这里我们就可以看到从 analysisSpringDocOpenApiGroupSuccess 到 createGroupElement 到 analysisApi 没有一个地方像 SwaggerBootstrapUi() 方法中一样去获取或构建 basePath 部分,即 http://loacalhost/example/,然后拼接 v3/api-docs/1-动物。
解决问题
那么我们怎么解决这个问题呢?一种方法是修改 knife4j-vue 的源码然后重新打包。这里介绍另外一种方法。
编译打包后的 Knife4j 的 doc.html 文件位于 knife4j-openapi3-ui 包下面,它引入了两个文件 webjars/js/app.51033393.js 和 webjars/js/chunk-vendors.d51cf6f8.js,它们都位于 knife4j-openapi3-ui 包下面。Vue 编译打包后的方法名不会变更,从上面的分析我们知道可以在 analysisSpringDocOpenApiGroupSuccess() 方法解析分组信息时把 basePath 拼接在 url 的前面。在 webjars/js/app.51033393.js 文件中可以找到这个方法,我们把它手动格式化一下
1  | ze.prototype.analysisSpringDocOpenApiGroupSuccess=function(e){  | 
参考 SwaggerBootstrapUi 构造方法中的做法,从浏览器地址中截取拼接 basePath
1  | const path = window.location.pathname;  | 
把它们加入 analysisSpringDocOpenApiGroupSuccess() 方法中,然后修改 url 和 location 属性的赋值,加上 basePath 前缀
1  | ze.prototype.analysisSpringDocOpenApiGroupSuccess=function(e){  | 
然后把修改后的文件按照 webjar 的方式放到自己项目的目录中,即自己项目的 resources/webjars/js 目录下,重启项目后就可以正常打开 Knife4j 的文档页面的,问题得到解决。