同时运行多个gin服务并使用不同的swagger文档
本文介绍了如何在一个项目内,起多个gin服务,并且实现每个服务展示不同的swag文档

前言

应用场景中,我们偶尔会遇到一个项目需要同时对外暴露多个端口,并提供不同的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生成的文档目录

img.png

/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用的时间还长,真希望能够帮助到一些人,不至于让自己的努力变成互联网上的垃圾

附件链接

本文示例源码

gin运行多个服务示例文档

swag-cli工具文档


Last modified on 2021-12-30