gin-Swagger 是个很好用的API文档生成工具,如果使用gin框架,只需要给API编写注释即可使用swag init命令生成API文档。但是gin-swagger使用的是Swagger2.0版本,不支持项目切换。而我又恰好遇到了一个项目中有多个子项目的情况,如果把所有API都放在一个文档里,查找起来就非常冗余,而且不支持多级目录,所以只能生成多个Swagger文档,使用不同的链接访问。
我们的需求
假设我们的项目有多个子项目或者有多个版本时,目录大致如下
- v1
- v2
- admin
我们要实现的效果是:
localhost:8080/swagger/v1/index.html
localhost:8080/swagger/admin/index.html
localhost:8080/swagger/v2/index.html
官方demo解析
首先官方其实提供了demo给我们,
我们只需要分别执行两次命令,即可生成两个API文档
swag i -g main.go -dir api/v1 --instanceName v1
swag i -g main.go -dir api/v2 --instanceName v2
解释下各个参数的意义:
-g 入口函数的位置,如果设置了dir,这个参数是dir的相对路径
-dir 需要生成文档的目录 默认是当前目录
-instanceName 需要生成文档的实例名称
-o 生成的文档的输出目录,默认是./docs
--parseDependency 解析依赖,默认是false,如果你指定了dir,不开启该参数的话,如果你的注释中有其他包的数据结构会导致生成失败
所以我们来看看官方demo的思路:
- 不同子项目的路由分别在不同的目录下,所以使用-dir参数,v1的API文档只会从api/v1目录下搜索
- v1
- api
main.go
- v2
- api
main.go
- -g指定的main.go并非是项目入口,而是注册router的入口,这个地方进行了项目的一些定义
package v1
import (
"github.com/gin-gonic/gin"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @BasePath /v1
func Register(router *gin.Engine) {
v1 := router.Group("v1")
v1.GET("/books", GetBooks)
}
- 使用instanceName参数,指定生成文档的实例名称,比如v1生成的文档就是v1_swagger.json
- docs
- v1_docs.json
- v2_docs.json
- v1_swagger.json
- v2_swagger.json
- v1_swagger.yaml
- v2_swagger.yaml
- 在代码中定义swagger 路由,注意_ “github.com/swaggo/gin-swagger/example/multiple/docs” 这一行不能缺少,相当于找到了文档所在目录
package main
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
v1 "github.com/swaggo/gin-swagger/example/multiple/api/v1"
v2 "github.com/swaggo/gin-swagger/example/multiple/api/v2"
_ "github.com/swaggo/gin-swagger/example/multiple/docs"
)
func main() {
// New gin router
router := gin.New()
// Register api/v1 endpoints
v1.Register(router)
router.GET("/swagger/v1/*any", ginSwagger.WrapHandler(swaggerFiles.NewHandler(), ginSwagger.InstanceName("v1")))
// Register api/v2 endpoints
v2.Register(router)
router.GET("/swagger/v2/*any", ginSwagger.WrapHandler(swaggerFiles.NewHandler(), ginSwagger.InstanceName("v2")))
// Listen and Server in
_ = router.Run()
}
最后运行项目,我们就可以通过http://localhost:8080/swagger/v1/index.html 和 http://localhost:8080/swagger/v2/index.html 来查看生成的两个版本的文档
更复杂的工程
然而我们实际上的工程要比这个demo更加复杂,这样的demo可能无非让我们实现我们的需求
例如我的项目中有多个子项目,启动主项目时同时启动多个子项目,也可以单独启动子项目
同时这些不同的包可能共用一些数据结构,比如返回数据Response{code: 200,msg: “ok”,data: “data”}
这时候很需要多个API文档,客户端只需要访问一个文档就可以了
- cmd
-v1
-main.go
-control
-main.go
-v2
-main.go
- internal
-v1
-api
-api.go
-control
-api
-api.go
-v2
-api
-api.go
- pkg
-serializer
- docs
-swag
那么我用来生成文档的命令是:
swag init -g cmd/v1/main.go -o ./docs/swag --instanceName v1
swag init -g ../../cmd/v2/main.go -d internal/v2 -o ./docs/swag --parseDependency --instanceName v2
swag init -g ../../cmd/control/main.go -d internal/control -o ./docs/swag --parseDependency --instanceName control
生成的目录结构如下
- docs
- swag
- v1_docs.json
- control_docs.json
- v2_docs.json
- v1_swagger.json
- control_swagger.json
- v2_swagger.json
- v1_swagger.yaml
- control_swagger.yaml
- v2_swagger.yaml
然后在代码中注册路由
package main
import (
_ "gin-swagger-demo/docs/swag"
control "gin-swagger-demo/internal/control/api"
v1 "gin-swagger-demo/internal/v1/api"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
v2 "github.com/swaggo/gin-swagger/example/multiple/api/v2"
)
// @title Test API
// @version 1.0
// @description Test Full API v1.0
// @contact.name -Control API
// @contact.url http://localhost:8081/swagger/control/index.html
func main() {
// New gin router
router := gin.New()
// Register v1 endpoints
v1.Register(router)
router.GET("/swagger/v1/*any", ginSwagger.WrapHandler(swaggerFiles.NewHandler(), ginSwagger.InstanceName("v1")))
// Register v2 endpoints
v2.Register(router)
router.GET("/swagger/v2/*any", ginSwagger.WrapHandler(swaggerFiles.NewHandler(), ginSwagger.InstanceName("v2")))
// Register control endpoints
control.Register(router)
router.GET("/swagger/control/*any", ginSwagger.WrapHandler(swaggerFiles.NewHandler(), ginSwagger.InstanceName("control")))
// Listen and Server in
_ = router.Run(":8081")
}
最后的效果如下:
打包时忽略swagger文档
我们一般在开发中需要swagger文档提供给其他同事查看,打包到生产环境时就不需要了。所以我们可以使用+build doc标签来忽略swagger文档的生成。
package main
import (
_ "gin-swagger-demo/docs/swag"
"github.com/gin-gonic/gin"
v2 "github.com/swaggo/gin-swagger/example/multiple/api/v2"
)
var swagHandler gin.HandlerFunc
// @title Test API
// @version 2.0
// @description Test API v2.0
// @contact.name -V1 API
// @contact.url http://localhost:8081/swagger/v1/index.html
func main() {
// New gin router
router := gin.New()
// Register v2 endpoints
v2.Register(router)
if swagHandler != nil {
router.GET("/swagger/v2/*any", swagHandler)
}
// Listen and Server in
_ = router.Run(":8081")
}
//go:build doc
// +build doc
package main
import (
"fmt"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
func init() {
fmt.Println("init swag")
swagHandler = ginSwagger.WrapHandler(swaggerFiles.NewHandler(), ginSwagger.InstanceName("v2"))
}
// 打包后体积约11M 无swag文档
go build -o v2.exe ./cmd/v2
// 打包后体积约26M 有swag文档
go build -o v2_doc.exe -tags "doc" ./cmd/v2
总结
虽然我们在实际开发中可能遇不到多个子项目的情况,但多个版本的情况还是挺常见的,也许后续gin-swagger可能升级到Swagger3.0后就可以支持多项目/多版本的切换,但在此之前你也许可以参照我的方案来实现这一需求。
我也把测试代码开源了,欢迎下载使用 gin-swagger-demo