go-swagger 集成 Knife4j 并支持 OpenApi3
go-swagger 集成 Knife4j 并支持 OpenApi3
相关源码
Knife4j
gin-swagger
swag
Fizz
集成 Knife4j 前端
go-swagger默认的经典页面较为简陋,Knife4j作为swagger的升级版则看着很舒服,操作方便,故想要将 Knife4j 的前端页面集成到 go 项目中,查找 Knife4j 仓库发现早已有人提出该问题(仅使用knife4j-openapi3-ui,如何搭配项目中的已经生成好的openapi.json使用),并且作者提供了仅集成前端的方法。
按照作者所说,拉取 knife4j-4.5.0 仓库源码,并在本地 npm run dev
运行 knife4j-vue3
后发现 doc.html 会首先请求 /services.json
文件,对比 Knife4j示例demo ,services.json
内容即作者所说的接口分组数据(以下数据url和location自定):
[
{
"name": "Knife4j示例",
"url": "/doc/swagger.json",
"swaggerVersion": "2.0",
"location": "/api/doc/swagger.json"
}
]
拉取 gin-swagger 代码,以此为测试的后台服务。
安装 gin-swagger\example\basic
示例相关依赖,并按照文档用 swag 生成 docs 目录下文件,核心为 OpenApi 描述文件 swagger.json
,然后修改 main.go
,增加一个 /services.json
路由处理(/api为vite配置的 VITE_APP_BASE_API 前缀)。
r.StaticFile("/services.json", "./docs/services.json")
然后将以上json存为 services.json 文件放到 docs 目录下,运行basic示例。
再次刷新前端发现还会请求 /api/doc/swagger.json
,再在后端 basic 中增加一条路由处理,让其指向 swag 生成的 swagger.json
r.StaticFile("/doc/swagger.json", "./docs/swagger.json")
再次运行,刷新前端,已经可以正确渲染出接口文档
至此第一步前端集成大功告成。
注意:
swag + knife4j-vue3
的方式代码中无需依赖 go-swagger
支持 OpenApi3
阅读文档发现 go-swagger 只支持 Swagger 2.0 ,且不打算支持 OpenApi3。而 go-swagger 是使用 swag 工具生成 OpenApi 描述文件 services.json
的。查看 swag 相关 issue,早已有人提出该问题,并且有几种解决方案:
- 使用 kin-openapi 这个库进行转换
- 使用
https://converter.swagger.io
上提供了 OpenApi2 到 OpenApi3 的转换接口 - 使用最新的
swag
代码,已经支持 OpenApi3,但存在bug,详见 kin-openapi 介绍
研究之下发现 kin-openapi 介绍了目前 go OpenApi3 生成的相关问题,并且提供了代码层面的转换方法,可以参考 https://github.com/getkin/kin-openapi/blob/master/openapi2conv/openapi2_conv_test.go 测试代码,暂时先不做测试,直接使用第三种方法,虽然可能存在bug,但最容易实现。
拉取 swag 源码 https://github.com/swaggo/swag ,切换分支进行编译尝试后发现 v2
分支支持生成 3.1 版本接口,需要如下参数
用编译的最新 swag.exe 重新生成 basic 的文档(swagv2.exe init --v3.1
),可以看到 swagger.json
已经变成了 "openapi": "3.1.0"
,注意:需要重新 go get github.com/swaggo/swag/v2
。
阅读 knife4j-vue3 代码发现其本身就支持编译为不依赖 springboot 的纯前端代码,只需要将 VITE_RELEASE_APP_TYPE
改为 Knife4jFront
即可:
# just a flag
ENV = 'development'
# base api
VITE_APP_BASE_API = '/api'
# 发行版本类型 可选值: SpringDocOpenApi | Knife4jSpringUi | Knife4jJFinal | Knife4jFront
VITE_RELEASE_APP_TYPE = 'Knife4jFront'
然后 npm run build
构建生产环境代码,将生成的 dist 下文件放到 example\basic\knife4j-vue3
下,修改静态资源路由,使得可以访问 doc.html 和 依赖的 webjars 目录:
func main() {
r := gin.New()
r.StaticFile("/doc.html", "./knife4j-vue3/doc.html")
r.StaticFile("/api/services.json", "./docs/services.json")
r.StaticFile("/api/doc/swagger.json", "./docs/swagger.json")
r.StaticFS("/webjars", http.Dir("./knife4j-vue3/webjars"))
r.GET("/v2/testapi/get-string-by-int/:some_id", api.GetStringByInt)
r.GET("/v2/testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString)
url := ginSwagger.URL("http://localhost:8080/swagger/swagger.json") // The url pointing to API definition
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
r.Run()
}
然后直接访问 http://localhost:8080/doc.html ,OpenApi3 的接口文档展示成功
后端目录结构:
其它方法
研究过程中也发现了另一种可以集成 Knife4j 的方式,可以查看 https://github.com/ccfish86/fizz 作者的仓库,通过另一种类似Java的思路,直接由代码生成 OpenApi3 文件,而不用通过 swag 工具手动执行,代码如下。
// ListFruitsParams represents the parameters that can
// be used to filter the fruit's market listing.
type ListFruitsParams struct {
Origin *string `query:"origin" description:"filter by fruit origin"`
PriceMin *float64 `query:"price_min" description:"filter by minimum inclusive price" validate:"omitempty,min=1"`
PriceMax *float64 `query:"price_max" description:"filter by maximum inclusive price" validate:"omitempty,max=15"`
}
grp.GET("", []fizz.OperationOption{
fizz.Summary("List the fruits of the market"),
fizz.Response("400", "Bad request", nil, nil, nil),
fizz.Header("X-Market-Listing-Size", "Listing size", fizz.Long),
}, tonic.Handler(ListFruits, 200))
作者将 gin 框架封装为 fizz
,并借助 tonic
实现了自动生成 OpenApi3,可以运行示例代码进行尝试。
相比之下 go-swagger
的方式步骤略微复杂,需要写很多类似Java注解的注释(注释比代码还多),但对代码原本逻辑无侵入性,。
fizz
的方式对现有代码逻辑侵入性较高,需要在结构体上增加额外tag,增加了代码复杂度。