golang 引入swagger(iris、gin)
在开发过程中,我们不免需要调试我们的接口,但是有些接口测试工具无法根据我们的接口变化而动态变化。文档和代码是分离的。总是出现文档和代码不同步的情况。这个时候就可以在我们项目中引入swagger,方便后期维护以及他人快速上手项目
0 下载swagger
# 1 安装swagger
# 在go.mod目录所在位置执行命令
go get -u github.com/swaggo/swag/cmd/swag
# 查看是否安装成功
swag -v
# 如果发现报错:zsh: command not found: swag,则需要手动编译生成swag
cd $GOPATH/pkg/mod/github.com/swaggo/swag@v1.16.2/cmd/swag/
sudo go build
sudo mv swag $GOPATH/bin
# 查看是否安装成功
swag -v
1 iris引入swagger
①导入iris-contrib/swagger依赖
//安装swagger扩展,本项目使用的是iris最新版(iris/v12),因此需要安装iris-swagger/v12扩展
go get -v "github.com/iris-contrib/swagger/swaggerFiles"
go get -v "github.com/iris-contrib/swagger/v12"
②添加对应swagger注解 & swag init生成docs
在项目对应文件添加swagger注解,并通过swag init生成docs
注意
:如果代码中的swagger注释有修改,需要重新执行swag init生成文档
例如:我给controller添加注解:
package controller
import (
"encoding/json"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"myTest/demo_home/swagger_demo/iris/model"
"net/http"
)
type UserController struct {
Ctx iris.Context
}
func (u *UserController) BeforeActivation(b mvc.BeforeActivation) {
b.Handle(http.MethodGet, "/getAll", "GetAllUsers")
}
// GetAllUsers @Summary 获取用户信息
// @Description 获取所有用户信息
// @Tags 用户
// @Accept json
// @Produce json
// @Router /user/getAll [get]
func (u *UserController) GetAllUsers() mvc.Result {
//手动模拟从数据库查询到user信息
resp := new(mvc.Response)
resp.ContentType = "application/json"
user1 := new(model.User)
user1.Name = "zhangsan"
user1.Age = 20
user2 := new(model.User)
user2.Name = "li4"
user2.Age = 28
users := []model.User{*user1, *user2}
marshal, _ := json.Marshal(users)
resp.Content = marshal
resp.Code = http.StatusOK
return resp
}
//在项目根目录执行swag init ( 默认会找当前目录下的 main.go 文件,如果不叫 main.go 也可以-g手动指定文件位置。)
swag init # swag init -g cmd/api/api.go -o cmd/api/docs (-o指定docs生成位置)
③main.go中引入swag生成doc包
在 main.go 中导入刚才生成的 docs 包
package main
import (
"github.com/iris-contrib/swagger/v12"
"github.com/iris-contrib/swagger/v12/swaggerFiles"
"github.com/kataras/iris/v12"
"myTest/demo_home/swagger_demo/iris/controller"
_ "myTest/demo_home/swagger_demo/iris/docs" //引入docs包
)
func main() {
app := iris.New()
controller.InitControllers(app)
config := &swagger.Config{
URL: "http://localhost:8080/swagger/doc.json", //The url pointing to API definition
}
app.Get("/swagger/{any}", swagger.CustomWrapHandler(config, swaggerFiles.Handler))
app.Listen(":8080")
}
④运行程序访问ip:port/swagger/index.html页面
运行main.go,浏览器输入:http://localhost:8080/swagger/index.html
全部代码
Github:
https://github.com/ziyifast/ziyifast-code_instruction/tree/main/swagger_demo
项目结构:
main.go
package main
import (
"github.com/iris-contrib/swagger/v12"
"github.com/iris-contrib/swagger/v12/swaggerFiles"
"github.com/kataras/iris/v12"
"myTest/demo_home/swagger_demo/iris/controller"
_ "myTest/demo_home/swagger_demo/iris/docs" # 引入生成的docs包
)
func main() {
app := iris.New()
controller.InitControllers(app)
config := &swagger.Config{
URL: "http://localhost:8080/swagger/doc.json", //The url pointing to API definition
}
app.Get("/swagger/{any}", swagger.CustomWrapHandler(config, swaggerFiles.Handler))
app.Listen(":8080")
}
controller/controllers.go
package controller
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func InitControllers(app *iris.Application) {
myMvc := mvc.New(app.Party("/user"))
myMvc.Handle(new(UserController))
}
controller/user_controller.go
package controller
import (
"encoding/json"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"myTest/demo_home/swagger_demo/iris/model"
"net/http"
)
type UserController struct {
Ctx iris.Context
}
func (u *UserController) BeforeActivation(b mvc.BeforeActivation) {
b.Handle(http.MethodGet, "/getAll", "GetAllUsers")
}
// GetAllUsers @Summary 获取用户信息
// @Description 获取所有用户信息
// @Tags 用户
// @Accept json
// @Produce json
// @Router /user/getAll [get]
func (u *UserController) GetAllUsers() mvc.Result {
//手动模拟从数据库查询到user信息
resp := new(mvc.Response)
resp.ContentType = "application/json"
user1 := new(model.User)
user1.Name = "zhangsan"
user1.Age = 20
user2 := new(model.User)
user2.Name = "li4"
user2.Age = 28
users := []model.User{*user1, *user2}
marshal, _ := json.Marshal(users)
resp.Content = marshal
resp.Code = http.StatusOK
return resp
}
2 gin引入swagger
①导入swaggo/gin-swagger依赖
// 引入gin及gin-swagger依赖
go get "github.com/gin-gonic/gin"
go get "github.com/swaggo/gin-swagger/swaggerFiles"
go get "github.com/swaggo/gin-swagger"
②添加对应swagger注解 & swag init生成docs
注意
:如果代码中的swagger注释有修改,需要重新执行swag init生成文档
user_controller.go
package controller
import (
ret "myTest/demo_home/swagger_demo/gin/response"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
// Hello 测试
// @Summary 测试SayHello
// @Description 向你说Hello
// @Tags 测试
// @Accept json
// @Produce json
// @Param who query string true "人名"
// @Success 200 {string} string "{"msg": "hello lixd"}"
// @Failure 400 {string} string "{"msg": "who are you"}"
// @Router /hello [get]
func Hello(c *gin.Context) {
who := c.Query("who")
if who == "" {
c.JSON(http.StatusBadRequest, gin.H{"msg": "who are u?"})
return
}
c.JSON(http.StatusOK, gin.H{"msg": "hello " + who})
}
type LoginReq struct {
Username string `json:"username"`
Password string `json:"password"`
}
type LoginResp struct {
Token string `json:"token"`
}
// Login 登陆
// @Summary 登陆
// @Tags 登陆注册
// @Description 登入
// @Accept json
// @Produce json
// @Param user body LoginReq true "用户名密码"
// @Success 200 {object} ret.Result{data=LoginResp} "token"
// @Failure 400 {object} ret.Result "错误提示"
// @Router /login [post]
func Login(c *gin.Context) {
var m LoginReq
if err := c.ShouldBind(&m); err != nil {
c.JSON(http.StatusBadRequest, ret.Fail("参数错误"))
return
}
if m.Username == "admin" && m.Password == "123456" {
resp := LoginResp{Token: strconv.Itoa(int(time.Now().Unix()))}
c.JSON(http.StatusOK, ret.Success(resp))
return
}
c.JSON(http.StatusUnauthorized, ret.Fail("user or password error"))
}
③main.go中引入swag生成doc包
package main
import (
"github.com/gin-gonic/gin"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"myTest/demo_home/swagger_demo/gin/controller"
_ "myTest/demo_home/swagger_demo/gin/docs"
)
var swagHandler gin.HandlerFunc
// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @termsOfService https://lixueduan.com
// @contact.name lixd
// @contact.url https://lixueduan.com
// @contact.email xueduan.li@gmail.com
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// SwaggerUI: http://localhost:8080/swagger/index.html
func main() {
e := gin.Default()
v1 := e.Group("/api/v1")
{
v1.GET("/hello", controller.Hello)
v1.POST("/login", controller.Login)
}
e.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
if swagHandler != nil {
e.GET("/swagger/*any", swagHandler)
}
if err := e.Run(":8080"); err != nil {
panic(err)
}
}
resp.go:
// Package ret 统一返回结构
package ret
import (
"net/http"
)
const (
MsgSuccess = "success"
MsgFail = "fail"
)
type Result struct {
Code int `json:"code"`
Data interface{} `json:"data"`
Msg string `json:"msg"`
}
func Success(data interface{}, msg ...string) *Result {
var m = MsgSuccess
if len(msg) > 0 {
m = msg[0]
}
return &Result{
Code: http.StatusOK,
Data: data,
Msg: m,
}
}
func Fail(msg ...string) *Result {
var m = MsgFail
if len(msg) > 0 {
m = msg[0]
}
return &Result{
Code: http.StatusBadRequest,
Data: "",
Msg: m,
}
}
④运行程序访问ip:port/swagger/index.html
http://localhost:8080/swagger/index.html
全部代码
地址:
https://github.com/ziyifast/ziyifast-code_instruction/tree/main/swagger_demo
main.go
package main
import (
"github.com/gin-gonic/gin"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"myTest/demo_home/swagger_demo/gin/controller"
_ "myTest/demo_home/swagger_demo/gin/docs"
)
var swagHandler gin.HandlerFunc
// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @contact.name lixd
// @contact.name ziyi
// @contact.url https://github.com/ziyifast/ziyifast-code_instruction/tree/main/swagger_demo
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// SwaggerUI: http://localhost:8080/swagger/index.html
func main() {
e := gin.Default()
v1 := e.Group("/api/v1")
{
v1.GET("/hello", controller.Hello)
v1.POST("/login", controller.Login)
}
e.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
if swagHandler != nil {
e.GET("/swagger/*any", swagHandler)
}
if err := e.Run(":8080"); err != nil {
panic(err)
}
}
controller/user_controller.go
package controller
import (
ret "myTest/demo_home/swagger_demo/gin/response"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
// Hello 测试
// @Summary 测试SayHello
// @Description 向你说Hello
// @Tags 测试
// @Accept json
// @Produce json
// @Param who query string true "人名"
// @Success 200 {string} string "{"msg": "hello lixd"}"
// @Failure 400 {string} string "{"msg": "who are you"}"
// @Router /hello [get]
func Hello(c *gin.Context) {
who := c.Query("who")
if who == "" {
c.JSON(http.StatusBadRequest, gin.H{"msg": "who are u?"})
return
}
c.JSON(http.StatusOK, gin.H{"msg": "hello " + who})
}
type LoginReq struct {
Username string `json:"username"`
Password string `json:"password"`
}
type LoginResp struct {
Token string `json:"token"`
}
// Login 登陆
// @Summary 登陆
// @Tags 登陆注册
// @Description 登入
// @Accept json
// @Produce json
// @Param user body LoginReq true "用户名密码"
// @Success 200 {object} ret.Result{data=LoginResp} "token"
// @Failure 400 {object} ret.Result "错误提示"
// @Router /login [post]
func Login(c *gin.Context) {
var m LoginReq
if err := c.ShouldBind(&m); err != nil {
c.JSON(http.StatusBadRequest, ret.Fail("参数错误"))
return
}
if m.Username == "admin" && m.Password == "123456" {
resp := LoginResp{Token: strconv.Itoa(int(time.Now().Unix()))}
c.JSON(http.StatusOK, ret.Success(resp))
return
}
c.JSON(http.StatusUnauthorized, ret.Fail("user or password error"))
}
response/response.go
// Package ret 统一返回结构
package ret
import (
"net/http"
)
const (
MsgSuccess = "success"
MsgFail = "fail"
)
type Result struct {
Code int `json:"code"`
Data interface{} `json:"data"`
Msg string `json:"msg"`
}
func Success(data interface{}, msg ...string) *Result {
var m = MsgSuccess
if len(msg) > 0 {
m = msg[0]
}
return &Result{
Code: http.StatusOK,
Data: data,
Msg: m,
}
}
func Fail(msg ...string) *Result {
var m = MsgFail
if len(msg) > 0 {
m = msg[0]
}
return &Result{
Code: http.StatusBadRequest,
Data: "",
Msg: m,
}
}
3 注解
3.1 swagger主文件注解-通用API信息
注释 | 说明 | 示例 |
---|---|---|
title | 必填 应用程序的名称 | // @title Swagger Example API |
version | 必填 提供应用程序API的版本。 | // @version 1.0 |
description | 应用程序的简短描述。 | // @description This is a sample server celler server. |
tag.name | 标签的名称。 | // @tag.name This is the name of the tag |
tag.description | 标签的描述。 | // @tag.description Cool Description |
tag.docs.url | 标签的外部文档的URL。 | // @tag.docs.url https://example.com |
tag.docs.description | 标签的外部文档说明。 | // @tag.docs.description Best example documentation |
termsOfService | API的服务条款。 | // @termsOfService http://swagger.io/terms/ |
contact.name | 公开的API的联系信息。 | // @contact.name API Support |
contact.url | 联系信息的URL。 必须采用网址格式。 | // @contact.url |
contact.email | 联系人/组织的电子邮件地址。 必须采用电子邮件地址的格式。 | // @contact.email support@swagger.io |
license.name | 必填 用于API的许可证名称。 | // @license.name Apache 2.0 |
license.url | 用于API的许可证的URL。 必须采用网址格式。 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html |
host | 运行API的主机(主机名或IP地址)。 | // @host localhost:8080 |
BasePath | 运行API的基本路径。 | // @BasePath /api/v1 |
accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“Mime类型”中所述。 | // @accept json |
produce | API可以生成的MIME类型的列表。值必须如“Mime类型”中所述。 | // @produce json |
query.collection.format | 请求URI query里数组参数的默认格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。 | // @query.collection.format multi |
schemes | 用空格分隔的请求的传输协议。 | // @schemes http https |
x-name | 扩展的键必须以x-开头,并且只能使用json值 | // @x-example-key {“key”: “value”} |
通用api信息,部分可以是在docs包里生成的,可以在项目启动的时候,或者在注册swagger路由的时候,修改掉部分信息,或者动态注入部分不固定的值,比如项目的基础路径:BasePath
func NewRouter() *gin.Engine {
gin.SetMode("debug")
engine := gin.New()
docs.SwaggerInfo.BasePath = "/api/v2"
engine.POST("/", v1.GetWord)
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
engine.GET("/log/:id", client.ReadLokiLog)
return engine
}
3.2 单个API样例
注释 | 样例 |
---|---|
description | 操作行为的详细说明。 |
description.markdown | 应用程序的简短描述。该描述将从名为endpointname.md的文件中读取。 |
id | 用于标识操作的唯一字符串。在所有API操作中必须唯一。 |
tags | 每个API操作的标签列表,以逗号分隔。 |
summary | 该操作的简短摘要。 |
accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“Mime类型”中所述。 |
produce | API可以生成的MIME类型的列表。值必须如“Mime类型”中所述。 |
param | 用空格分隔的参数。param name,param type,data type,is mandatory?,comment attribute(optional) |
security | 每个API操作的安全性。 |
success | 以空格分隔的成功响应。return code,{param type},data type,comment |
failure | 以空格分隔的故障响应。return code,{param type},data type,comment |
response | 与success、failure作用相同 |
header | 以空格分隔的头字段。 return code,{param type},data type,comment |
router | 以空格分隔的路径定义。 path,[httpMethod] |
x-name | 扩展字段必须以x-开头,并且只能使用json值。 |
// @Summary 测试swagger
// @Tags test
// @version 1.0
// @Success 200 object FinalResult{data=v1.Application} 成功后返回值
// @Failure 500 object FinalResult 添加失败
// @Router / [get]
func GetWord(ctx *gin.Context) {
application := &Application{Id: 1}
err := ctx.BindJSON(application)
if err != nil {
ctx.JSON(500, "")
}
ctx.JSON(200, SuccessResult(application))
}
summary 是这个api的名字,可以显示在yapi的名称
tag 是这个api所在的分组
success 支持组合嵌套
param 说明了api需要的请求参数
param的类型支持:
- query
- path
- header
- body
- formData
如果我们需要给字段添加注释,直接在字段后面添加即可
直接在参数属性后面增加注释,也可以指定参数的名称说明描述
type Application struct {
Id int `json:"id" example:"2"` // 环境ID
Name string `json:"name" example:"环境一"` // Name 环境名称
}
忽略某个字段:
type Account struct {
ID string `json:"id"`
Name string `json:"name"`
Ignored int `swaggerignore:"true"`
}
注意
:如果代码中的swagger注释有修改,需要重新执行swag init生成文档
参考:https://blog.csdn.net/qq_38371367/article/details/123005909
bug
1 unknown field LeftDelim in struct literal of type "github.com/swaggo/swag
注意:
如果遇到报错:
docs/docs.go:30:2: unknown field LeftDelim in struct literal of type “github.com/swaggo/swag”.Spec
可能是由于swag版本过低导致,升级版本即可:go get -u -v github.com/swaggo/swag/cmd/swag
2 添加了swag 注解,访问页面成功,但没有对应的方法
重新执行
swag init
,然后重新启动项目
- 如果代码中的swagger注解有修改,需要重新执行swag init生成文档