go 使用 swagger 自动api文档

传送门:https://github.com/swaggo/swag/blob/master/README_zh-CN.md

go-swagger

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

  1. 安装swag工具

  2. 按照swagger要求给接口代码添加声明式注释,具体参照声明式注释格式。

  3. 使用swag工具扫描代码自动生成API接口文档数据

  4. 使用gin-swagger渲染在线接口文档页面

1. 安装swag工具

使用以下命令安装swag工具:

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

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解析目录 (默认: "./")
 --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的顶部,默认是

2. 添加注释

添加通用的API注释

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

package main

// @title xxx API (必填,缺少会有警告)
// @version 1.0 (必填)
// @description This is cxy api docs.
// @license.name Apache 2.0
// @contact.name go-swagger帮助文档
// @contact.url https://github.com/swaggo/swag/blob/master/README_zh-CN.md
// @host localhost:8080
// @BasePath /api/v1
// ... 其他选项看需求
func main() {
 r := gin.New()
 ...
 r.Run()
}

添加接口的注释

在你代码中处理请求的接口函数按如下方式写上注释:

// ShowAccount godoc
// @Summary      Show an account
// @Description  get string by ID
// @Tags         accounts
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "Account ID"
// @Success      200  {object}  model.Account
// @Failure      400  {object}  httputil.HTTPError
// @Failure      404  {object}  httputil.HTTPError
// @Failure      500  {object}  httputil.HTTPError
// @Router       /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
 id := ctx.Param("id")
 aid, err := strconv.Atoi(id)
 if err != nil {
 httputil.NewError(ctx, http.StatusBadRequest, err)
 return
 }
 account, err := model.AccountOne(aid)
 if err != nil {
 httputil.NewError(ctx, http.StatusNotFound, err)
 return
 }
 ctx.JSON(http.StatusOK, account)
}

3. 生成接口文档数据

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

报错:

2021/11/25 11:20:28 Generate swagger docs....
2021/11/25 11:20:28 Generate general API Info, search dir:./
2021/11/25 11:20:28 warning: failed to get package name in dir: ./, error: execute go list command, exit status 1, stdout:, stderr:no Go files in /home/song/appDisk/hupeng/Src/apollo
2021/11/25 11:20:28 cannot parse source files /home/song/appDisk/hupeng/Src/apollo/main.go: open /home/song/appDisk/hupeng/Src/apollo/main.go: no such file or directory

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

执行完上述命令后,如果写的注释格式没问题,此时在包含mai.go的目录下会多出一个docs文件夹。

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

4. 引入gin-swagger渲染文档数据

然后在项目代码引入gin-swagger相关内容:

apollo 项目的 ugc_server 的 main.go 文件

import (

 "apollo/docs" // 导入swagger文档用的
 gs "github.com/swaggo/gin-swagger"
 "github.com/swaggo/gin-swagger/swaggerFiles"
)

在注册http路由的地方,注册swagger api相关路由

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

有些通用的API注释,可以动态设置

生成的代码包docs导出SwaggerInfo变量,使用该变量可以通过编码的方式设置,这种方式可以更灵活比如可以根据环境写不同// @host的值等等

一般都是测试环境和本地使用,生产环境禁用Swagger

这样我们可以封装一个方法来调用:

func handleSwagger(r *gin.Engine, conf *config.Configuration) {
 if conf.Server.Env == config.EnvTest {
 docs.SwaggerInfo.Host = conf.Server.Host + conf.Server.Addr
 r.GET("/swagger/*any", gs.WrapHandler(swaggerFiles.Handler))
 }
}

注:如果把 // @version, // @title 也用 SwaggerInfo变量 来写,会有个警告。

把你的项目程序运行起来,使用该服务自己的端口,打开浏览器访问[http://ip:port/swagger/index.html#/]就能看到Swagger 2.0 Api文档了。

image-20211125133216203.png

遇到的问题1:No operations defined in spec!

问题解决:到项目根目录下执行

swag init -g cmd/ugc_server/main.go

https://github.com/swaggo/gin-swagger/issues/99

swagger生成的接口文档页面示例

image-20211119210138366.png

遇到的问题2:把gin库从v1.6.3升级到了v1.7.4

问题解决:

go mod edit -replace=github.com/gin-gonic/gin@v1.7.4=github.com/gin-gonic/gin@v1.6.3

遇到的问题3:ParseComment error

ParseComment error in file /home/song/appDisk/hupeng/Src/apollo/internal/handler/device/laser_cutter_list.go :cannot find type definition: common.OSLang

依赖的vendor里面的类型不识别

问题解决:

加上 --parseVendor 参数,解析vendor目录里的go源文件

swag init --parseVendor -g cmd/ugc_server/main.go

ParseComment error in file /home/song/appDisk/hupeng/Src/apollo/internal/handler/device/laser_cutter_list.go :cannot find type definition: reflect.Kind

go语言本身的类型不识别

问题解决:

加上 --parseDependency 参数

时间太久加上 --parseDepth 1 参数,依赖解析深度1减少解析时间

swag init --parseVendor --parseInternal --parseDependency --parseDepth 1 -g cmd/ugc_server/main.go

时间减少很多,每次在10秒左右,

还有一种解决方法:使用swaggertype标签更改字段类型

ValueType     reflect.Kind      `json:"valueType" bson:"value_type" swaggertype:"integer"`
swag init --parseVendor -g cmd/ugc_server/main.go

时间快很多,大概在3秒左右

注解说明

@Tags: 分类信息,用逗号分割多个,写多个的话,在api文档里面会生成多个接口

@Summary: 操作的简短摘要。

@Accept json

@Produce json

@Param:参数信息,用空格分隔的参数。

param name,param type,data type,is mandatory?,comment,attribute(optional)
1.参数名,2.参数类型,3.参数数据类型,4.是否必须,5.参数描述,6.其他属性

1.参数名

参数名就是我们解释参数的名字。

2.参数类型,可以有的值是 query、path、body、header,formData

  • query 表示带在 url 之后的参数

  • path 表示请求路径上得参数

  • body 表示是一个 raw 数据请求,当AcceptJSON格式时,我们使用该字段指定接收的JSON类型

  • header 表示带在 header 信息中得参数

  • formData 表示是 post 请求的数据

3.参数数据类型

数据类型主要支持以下几种:

  • string (string)

  • integer (int, uint, uint32, uint64)

  • number (float32)

  • boolean (bool)

  • 自定义struct

4.是否必须

5.参数描述

就是参数的一些说明,注释

6.其他属性

除了上面这些属性外,我们还可以为该参数填写一些额外的属性,如枚举,默认值,值范围等。

例如:

枚举
// @Param enumstring query string false "string enums" Enums(A, B, C)
// @Param enumint query int false "int enums" Enums(1, 2, 3)
// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)

值添加范围
// @Param string query string false "string valid" minlength(5) maxlength(10)
// @Param int query int false "int valid" mininum(1) maxinum(10)

设置默认值
// @Param default query string false "string default" default(A)</pre>

代码示例

// @Summary 分类下的模型组列表
// @Tags 模型-V3版
// @Accept json
// @Produce json
// @Param cursor body string false "游标"
// @Param limit body integer true "每页请求数"
// @Param categoryId body string true "分类id"
// @Param filterType body integer false "过滤类型"
// @Response 200 {object} httpjson.Response
// @Router /api/cxy/v3/model/listCategory [post]
func ListCategory(ctx context.Context, originReq httpjson.IRequest) (interface{}, error){
}

评:这是方式行不通,每个字段都是一个json,测试时发现请求出去的数据就使用了最后一个字段的值
curl -X 'POST' \
 'http://172.29.99.145:20087/api/cxy/v3/model/listCategory' \
 -H 'accept: application/json' \
 -H 'Content-Type: application/json' \
 -d '0'
// @Summary 分类下的模型组列表
// @Tags 模型-V3版
// @Accept json
// @Produce json
// @Param cursor formData string false "游标"
// @Param limit formData integer true "每页请求数"
// @Param categoryId formData string true "分类id"
// @Param filterType formData integer false "过滤类型"
// @Response 200 {object} httpjson.Response
// @Router /api/cxy/v3/model/listCategory [post]
func ListCategory(ctx context.Context, originReq httpjson.IRequest) (interface{}, error){
}

评:这是方式行不通,这是表单提交的格式数据
curl -X 'POST' \
 'http://172.29.99.145:20087/api/cxy/v3/model/listCategory' \
 -H 'accept: application/json' \
 -H 'Content-Type: application/json' \
 -d 'cursor=%22%22&limit=10&categoryId=%221%22&filterType=0'

// @Summary 分类下的模型组列表
// @Tags 模型-V3版
// @Accept json
// @Produce json
// @Param req body ListCategoryReq true "req"
// @Response 200 {object} httpjson.Response
// @Router /api/cxy/v3/model/listCategory [post]
func ListCategory(ctx context.Context, originReq httpjson.IRequest) (interface{}, error){
}


评:这个可以顺利通过测试
curl -X 'POST' \
 'http://172.29.99.145:20087/api/cxy/v3/model/listCategory' \
 -H 'accept: application/json' \
 -H 'Content-Type: application/json' \
 -d '{
 "categoryId": "1",
 "cursor": "",
 "filterType": 0,
 "limit": 10,
 "reverse": false
}'

优点:
请求参数不用一个个的写了,基本上来说是一个固定格式

缺点:
1.请求参数里面body里面的数据不能指定每个字段的是否必填,只能通过注释来显示
2.返回参数不能体现真实的返回具体数据,只是一个外壳数据,解决方法如下
type ListCategoryRspData struct {
 List       []*model3.ModelListItem `json:"list" `
 NextCursor string                  `json:"nextCursor" `
}

// @Summary 分类下的模型组列表
// @Tags 模型-V3版
// @Accept json
// @Produce json
// @Param req body ListCategoryReq true "req"
// @Response 200 {object} httpjson.Response{result=ListCategoryRspData}
// @Router /api/cxy/v3/model/listCategory [post]
func ListCategory(ctx context.Context, originReq httpjson.IRequest) (interface{}, error){
}

评:这个可以顺利通过测试
优点:
请求参数不用一个个的写了,基本上来说是一个固定格式
能够返回每个接口的具体数据了

缺点:
1.请求参数里面body里面的数据不能指定每个字段的是否必填,只能通过注释来显示

swagger 还有一个缺点就是前后台接口都在一个文档中,没法分文档

swagger 优点不用手动维护接口文档了

权限问题

swagger除了可以查看文档外,还可以直接try it out 调用接口,有些接口是需要登录后才能调用有个token鉴权。

校验token 的api接口 header中必须有正确的 _CXY_TOKEN 和 ** _CXY_UID** 值

可以通过添加通用的API注释来设置全局接口的header值

// @securityDefinitions.apikey cxy_token
// @name __CXY_TOKEN_
// @in header
// @securityDefinitions.apikey cxy_uid
// @name __CXY_UID_
// @in header

然后在需要登录的接口注释上增加

// @Security cxy_token
// @Security cxy_uid</pre>

这样就可以在调用接口前先设置token和uid了

image-20211125135401467.png

安装插件 Swagger Viewer

  1. 在线安装

直接在vscode插件管理器中搜 Swagger Viewer 找到点击安装即可

  1. 离线包下载地址: https://marketplace.visualstudio.com/vscode

离线安装方法:

第一步,将扩展文件*.vsix放置在VS Code安装目录下的bin目录中,并在此目录Shift+鼠标右键,打开命令窗口

第二步:输入下面code --install ... 安装即可

C:\Users\Administrator\AppData\Local\Programs\Microsoft VS Code\bin>
code --install-extension Arjun.swagger-viewer-3.1.2.vsix</pre>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容