golang gin框架使用swagger生成接口文档

前言

一份清晰明了的接口文档能够极大地提高前后端双方的沟通效率和开发效率。

本文将介绍如何使用swagger生成接口文档。

swagger介绍

Swagger本质上是一种用于描述使用JSON表示的RESTful API的接口描述语言。Swagger与一组开源软件工具一起使用,以设计、构建、记录和使用RESTful Web服务。Swagger包括自动文档,代码生成和测试用例生成。

在前后端分离的项目开发过程中,如果后端同学能够提供一份清晰明了的接口文档,那么就能极大地提高大家的沟通效率和开发效率。可是编写接口文档历来都是令人头痛的,而且后续接口文档的维护也十分耗费精力。

最好是有一种方案能够既满足我们输出文档的需要又能随代码的变更自动更新,而Swagger正是那种能帮我们解决接口文档问题的工具。

这里以gin框架为例,使用gin-swagger库以使用Swagger 2.0自动生成RESTful API文档。

swag配置

swag 安装

$ go get -u github.com/swaggo/swag/cmd/swag

# 1.16 及以上版本
$ go install github.com/swaggo/swag/cmd/swag@latest

在包含 main.go 文件的项目根目录运行 swag init。这将会解析注释并生成需要的文件(docs 文件夹和 docs/docs.go)。

swag init 

swag init 默认会找当前目录下的 main.go 文件,如果不叫 main.go 也可以手动指定文件位置。

# -o 指定输出目录。
swag init -g cmd/api/api.go -o cmd/api/docs

需要注意的是:swag init 的时候需要在项目根目录下执行,否则无法检测到所有文件中的注释。

比如在 /xxx 目录下执行 swag init 就只能检测到 xxx 目录下的,如果还有和 xxx 目录同级或者更上层的目录中的代码都检测不到。

init 之后会生成一个 docs 文件夹,这里面就是接口描述文件,生成后还需要将其导入到 main.go 中。

main.go 中导入刚才生成的 docs

加上之前导入的 gin-swag 相关的两个包,一共新导入了三个包。

_ "xx/cmd/api/docs" // main 文件中导入 docs 包

ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"

最后则是在 router 中增加 swaggerhandler 了。

main.go 或其他地方增加一个 handler

engine := gin.New()
engine.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

项目运行起来后访问 ip:port/swagger/index.html 即可看到 API 文档。

如果注释有更新,需要重新生成 docs 并重启服务才会生效。

gin-swagger实战

想要使用gin-swagger为你的代码自动生成接口文档,一般需要下面三个步骤:

  1. 按照swagger要求给接口代码添加声明式注释,具体参照声明式注释格式
  2. 使用swag工具扫描代码自动生成API接口文档数据
  3. 使用gin-swagger渲染在线接口文档页面

第一步:添加注释

在程序入口main函数上以注释的方式写下项目相关介绍信息。

package main

// @title 这里写标题
// @version 1.0
// @description 这里写描述信息
// @termsOfService http://swagger.io/terms/

// @contact.name 这里写联系人信息
// @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

// @host 这里写接口服务的host
// @BasePath 这里写base path
func main() {
	r := gin.New()

	// liwenzhou.com ...

	r.Run()
}

在你代码中处理请求的接口函数(通常位于controller层)按如下方式写上注释:

// GetPostListHandler2 升级版帖子列表接口
// @Summary 升级版帖子列表接口
// @Description 可按社区按时间或分数排序查询帖子列表接口
// @Tags 帖子相关接口
// @Accept application/json
// @Produce application/json
// @Param Authorization header string false "Bearer 用户令牌"
// @Param object query models.ParamPostList false "查询参数"
// @Security ApiKeyAuth
// @Success 200 {object} _ResponsePostList
// @Router /posts2 [get]
func GetPostListHandler2(c *gin.Context) {
	// GET请求参数(query string):/api/v1/posts2?page=1&size=10&order=time
	// 初始化结构体时指定初始参数
	p := &models.ParamPostList{
		Page:  1,
		Size:  10,
		Order: models.OrderTime,
	}

	if err := c.ShouldBindQuery(p); err != nil {
		zap.L().Error("GetPostListHandler2 with invalid params", zap.Error(err))
		ResponseError(c, CodeInvalidParam)
		return
	}
	data, err := logic.GetPostListNew(p)
	// 获取数据
	if err != nil {
		zap.L().Error("logic.GetPostList() failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	ResponseSuccess(c, data)
	// 返回响应
}

上面注释中参数类型使用了 objectmodels.ParamPostList 具体定义如下:

// bluebell/models/params.go

// ParamPostList 获取帖子列表query string参数
type ParamPostList struct {
	CommunityID int64  `json:"community_id" form:"community_id"`   // 可以为空
	Page        int64  `json:"page" form:"page" example:"1"`       // 页码
	Size        int64  `json:"size" form:"size" example:"10"`      // 每页数据量
	Order       string `json:"order" form:"order" example:"score"` // 排序依据
}

响应数据类型也使用的 object,我个人习惯在controller层专门定义一个 docs_models.go 文件来存储文档中使用的响应数据model。

// bluebell/controller/docs_models.go

// _ResponsePostList 帖子列表接口响应数据
type _ResponsePostList struct {
	Code    ResCode                 `json:"code"`    // 业务响应状态码
	Message string                  `json:"message"` // 提示信息
	Data    []*models.ApiPostDetail `json:"data"`    // 数据
}

第二步:生成接口文档数据

编写完注释后,使用以下命令安装swag工具:

go get -u github.com/swaggo/swag/cmd/swag

Windows10 如果在goland中直接执行上述命令不生效,显示 'swag' 不是内部或外部命令,也不是可运行的程序或批处理文件,这时候可以在cmd小黑框执行,然后重启goland项目再执行 swag help,就可以看到swag已经成功安装上了。

第二步:生成接口文档数据

编写完注释后,使用以下命令安装swag工具:

go get -u github.com/swaggo/swag/cmd/swag

在项目根目录执行以下命令,使用swag工具生成接口文档数据。

swag init

执行完上述命令后,如果你写的注释格式没问题,此时你的项目根目录下会多出一个docs文件夹。

./docs
├── docs.go
├── swagger.json
└── swagger.yaml

第三步:引入gin-swagger渲染文档数据

然后在项目代码中注册路由的地方按如下方式引入gin-swagger相关内容:

import (
	// liwenzhou.com ...

	_ "bluebell/docs"  // 千万不要忘了导入把你上一步生成的docs

	gs "github.com/swaggo/gin-swagger"
	"github.com/swaggo/gin-swagger/swaggerFiles"

	"github.com/gin-gonic/gin"
)

注册swagger api相关路由

r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

把你的项目程序运行起来,打开浏览器访问 http://localhost:8080/swagger/index.html 就能看到Swagger 2.0 Api文档了。

gin-swagger 同时还提供了 DisablingWrapHandler 函数,方便我们通过设置某些环境变量来禁用Swagger。例如:

r.GET("/swagger/*any", gs.DisablingWrapHandler(swaggerFiles.Handler, "NAME_OF_ENV_VARIABLE"))

此时如果将环境变量 NAME_OF_ENV_VARIABLE设置为任意值,则 /swagger/*any 将返回404响应,就像未指定路由时一样。

注解示例

https://swagger.io/docs/

https://swaggo.github.io/swaggo.io/declarative_comments_format/

注解 描述
@Summary 摘要
@Produce API 可以产生的 MIME 类型的列表,MIME 类型你可以简单的理解为响应类型,例如:json、xml、html 等等
@Param 参数格式,从左到右分别为:参数名、入参类型、数据类型、是否必填、注释
@Success 响应成功,从左到右分别为:状态码、参数类型、数据类型、注释
@Failure 响应失败,从左到右分别为:状态码、参数类型、数据类型、注释
@Router 路由,从左到右分别为:路由地址,HTTP 方法
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
	_ "github/mwqnice/swag/docs" // 千万不要忘了导入把你上一步生成的docs
)

type Article struct{
	ID         uint32 `gorm:"primary_key" json:"id"`
	CreatedBy  string `json:"created_by"`
	ModifiedBy string `json:"modified_by"`
	CreatedOn  uint32 `json:"created_on"`
	ModifiedOn uint32 `json:"modified_on"`
	DeletedOn  uint32 `json:"deleted_on"`
	IsDel      uint8  `json:"is_del"`
	Title         string `json:"title"`
	Desc          string `json:"desc"`
	Content       string `json:"content"`
	CoverImageUrl string `json:"cover_image_url"`
	State         uint8  `json:"state"`
}

func NewArticle() Article {
	return Article{}
}

func main()  {
	r := gin.Default()
	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
	r.Run(":8088")
}

// @Summary 获取单个文章
// @Produce json
// @Param id path int true "文章ID"
// @Success 200 {object} Article "成功"
// @Failure 400 {object} string "请求错误"
// @Failure 500 {object} string "内部错误"
// @Router /api/v1/articles/{id} [get]
func (a Article) Get(c *gin.Context) {

}

// @Summary 获取多个文章
// @Produce json
// @Param name query string false "文章名称"
// @Param tag_id query int false "标签ID"
// @Param state query int false "状态"
// @Param page query int false "页码"
// @Param page_size query int false "每页数量"
// @Success 200 {object} Article "成功"
// @Failure 400 {object} string "请求错误"
// @Failure 500 {object} string "内部错误"
// @Router /api/v1/articles [get]
func (a Article) List(c *gin.Context) {
	return
}

// @Summary 创建文章
// @Produce json
// @Param tag_id body string true "标签ID"
// @Param title body string true "文章标题"
// @Param desc body string false "文章简述"
// @Param cover_image_url body string true "封面图片地址"
// @Param content body string true "文章内容"
// @Param created_by body int true "创建者"
// @Param state body int false "状态"
// @Success 200 {object} Article "成功"
// @Failure 400 {object} string "请求错误"
// @Failure 500 {object} string "内部错误"
// @Router /api/v1/articles [post]
func (a Article) Create(c *gin.Context) {

}

// @Summary 更新文章
// @Produce json
// @Param tag_id body string false "标签ID"
// @Param title body string false "文章标题"
// @Param desc body string false "文章简述"
// @Param cover_image_url body string false "封面图片地址"
// @Param content body string false "文章内容"
// @Param modified_by body string true "修改者"
// @Success 200 {object} Article "成功"
// @Failure 400 {object} string "请求错误"
// @Failure 500 {object} string "内部错误"
// @Router /api/v1/articles/{id} [put]
func (a Article) Update(c *gin.Context) {
	return
}

// @Summary 删除文章
// @Produce  json
// @Param id path int true "文章ID"
// @Success 200 {string} string "成功"
// @Failure 400 {object} string "请求错误"
// @Failure 500 {object} string "内部错误"
// @Router /api/v1/articles/{id} [delete]
func (a Article) Delete(c *gin.Context) {
	return
}
posted @ 2021-12-02 17:01  牛奔  阅读(4819)  评论(0编辑  收藏  举报