前言
应用场景中,我们偶尔会遇到一个项目需要同时对外暴露多个端口,并提供不同的API服务, 目前在go语言圈子中,gin+swagger对外提供API是一个很棒的组合,gin的官方文档也提供了同时运行多个服务的示例,但是对于swag,目前网上的文章中, 多半都是简单的介绍gin如何整合swag,并没有太多的文章来介绍如何在一个项目中起多个互相不干扰的swag,鉴于最近遇到了这种需求场景,趟了一遍后,我觉着有必要分享一下我的实现。
准备工作
下载swag工具,本文使用的swag版本为1.7.8
$ go get -u github.com/swaggo/swag/cmd/swag
# 1.16 及以上版本
$ go install github.com/swaggo/swag/cmd/swag@latest
$ swag -version
swag.exe version v1.7.8
问题分析
首先,这个问题要分成两部分来看:
1:gin多服务
gin多服务这不是什么问题,官方就提供了相关的示例
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group
)
func router01() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return e
}
func router02() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return e
}
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
2:swag文档多份。
swag多服务文档,这涉及到两个地方,swag-cli工具的解析、api路由的挂载
swag-cli工具参数说明
swag init -h
NAME:
swag init - Create docs.go
USAGE:
swag init [command options] [arguments...]
OPTIONS:
--generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go")
--dir value, -d value API解析目录 (默认: "./")
--exclude value 解析扫描时排除的目录,多个目录可用逗号分隔(默认:空)
--propertyStrategy value, -p value 结构体字段命名规则,三种:snakecase,camelcase,pascalcase (默认: "camelcase")
--output value, -o value 文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs")
--parseVendor 是否解析vendor目录里的go源文件,默认不
--parseDependency 是否解析依赖目录中的go源文件,默认不
--markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录
--generatedTime 是否输出时间到输出文件docs.go的顶部,默认是
--codeExampleFiles value, --cef value 解析包含用于 x-codeSamples 扩展的代码示例文件的文件夹,默认禁用
--parseInternal 解析 internal 包中的go文件,默认禁用
--parseDepth value 依赖解析深度 (默认: 100)
--instanceName value 设置文档实例名 (默认: "swagger")
--generalInfo
参数为我们提供了不同入口定义的可能
--dir
,--exclude
参数为我们提供了不同api定义的扫描
--output
参数让我们能够把不同的文档生成到不同的地方
api路由挂载
下面是一个swaggo/gin-swagger提供的简单示例
package main
import (
"github.com/gin-gonic/gin"
docs "github.com/go-project-name/docs"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"net/http"
)
// @BasePath /api/v1
// PingExample godoc
// @Summary ping example
// @Schemes
// @Description do ping
// @Tags example
// @Accept json
// @Produce json
// @Success 200 {string} Helloworld
// @Router /example/helloworld [get]
func Helloworld(g *gin.Context) {
g.JSON(http.StatusOK,"helloworld")
}
func main() {
r := gin.Default()
docs.SwaggerInfo.BasePath = "/api/v1"
v1 := r.Group("/api/v1")
{
eg := v1.Group("/example")
{
eg.GET("/helloworld",Helloworld)
}
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
r.Run(":8080")
}
可以看到,gin加载swag的关键节点就在于ginSwagger.WrapHandler
,我们找到这个方法的源码,看到它其实是有一个配置入参的
通过官方文档,我找到了这几个配置入参说明
Option | Type | Default | Description |
---|---|---|---|
URL | string | “doc.json” | URL pointing to API definition |
DocExpantion | string | “list” | Controls the default expansion setting for the operations and tags. It can be ‘list’ (expands only the tags), ‘full’ (expands the tags and operations) or ‘none’ (expands nothing). |
DeepLinking | bool | true | If set to true, enables deep linking for tags and operations. See the Deep Linking documentation for more information. |
DefaultModelsExpandDepth | int | 1 | Default expansion depth for models (set to -1 completely hide the models). |
InstanceName | string | “swagger” | The instance name of the swagger document. If multiple different swagger instances should be deployed on one gin router, ensure that each instance has a unique name (use the –instanceName parameter to generate swagger documents with swag init). |
InstanceName: swagger文档的实例名称。如果在一个gin路由器上部署多个不同的swagger实例,请确保每个实例都有一个唯一的名称。
这是一个很关键的点,由于项目是一个,但是swag库是全局的,只有注册不同的实例才能分别加载成功,同时我们翻看上文,swag-cli中,也提供了--instanceName
参数,用于定义生成的文档实例名
撰写demo
文末提供了示例的源码,此处可以不用复制粘贴。
1、创建如下目录结构
/api/swa 服务A的API目录,里面定义了A相关的API
/api/swb 服务A的API目录,里面定义了B相关的API
/docs 为swag-cli生成的文档目录
/route 定义A/B服务的路由,以及swag入口文件
如上,分别在AB服务的路由swag加载处定义各自InstanceName,用于不同服务加载不同的swag
e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, func(c *ginSwagger.Config) {
c.InstanceName = "swa"
}))
e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, func(c *ginSwagger.Config) {
c.InstanceName = "swb"
}))
main.go 定义入口
package main
import (
"log"
"net/http"
"time"
"github.com/zhimiaox/gin-swagger/route"
"golang.org/x/sync/errgroup"
_ "github.com/zhimiaox/gin-swagger/docs/swa" // 导入swag文档
_ "github.com/zhimiaox/gin-swagger/docs/swb" // 导入swag文档
)
var g errgroup.Group
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: route.Swa(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: route.Swb(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
2、生成文档
# 请在根目录下执行
$ swag init --instanceName=swa --generalInfo=route/swa.go --exclude=api/swb --output docs/swa
$ swag init --instanceName=swb --generalInfo=route/swb.go --exclude=api/swa --output docs/swb
3、启动服务验证
总结
过程很简单,也没啥曲折,重点就在于去仔细看看文档,现在很多文档都有中文了,没有中文的,谷歌翻译也好用的很
很久不写这些玩意了,真的很耽误时间,就这点东西,比特么写4个demo用的时间还长,真希望能够帮助到一些人,不至于让自己的努力变成互联网上的垃圾
附件链接
Last modified on 2021-12-30