Golang使用swagger生成api接口文档

现在大部分应用都是前后端分离的项目,那么,前端和后端交互只有通过api接口文档来实现。swagger可以根据注释来生成api接口文档。

预备知识

  • gin
  • gorm
  • mysql

添加依赖

go get github.com/swaggo/swag/cmd/swag
go get github.com/gin-gonic/gin
go get github.com/go-sql-driver/mysql
go get gorm.io/gorm
go get gorm.io/gorm/logger
go get github.com/swaggo/gin-swagger
go get github.com/swaggo/files
swag.exe

gin+gorm创建一个CRUD项目

model.go

package main

import "github.com/jinzhu/gorm"

type Post struct {
	gorm.Model
	Title  string `gorm:"type:varchar(100);" josn:"title" example:"title" binding:"required"`
	Des    string `gorm:"type:varchar(100);" josn:"des" example:"desc" binding:"required"`
	Status string `gorm:"type:varchar(200);" josn:"status" example:"Active"`
}

type Response struct {
	Msg  string
	Data interface{}
}

api.go

package main

import (
	"net/http"
	"strconv"

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

//查询
func Posts(c *gin.Context) {
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
	offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))

	var posts []Post
	db.Limit(limit).Offset(offset).Find(&posts)
	c.JSON(http.StatusOK, gin.H{
		"messege": "",
		"data":    posts,
	})
}

func Show(c *gin.Context) {
	post := getById(c)
	c.JSON(http.StatusOK, gin.H{
		"messege": "",
		"data":    post,
	})
}

func Store(c *gin.Context) {
	var post Post
	if err := c.ShouldBindJSON(&post); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"messege": err.Error(),
			"data":    "",
		})
		return
	}
	post.Status = "Active"
	db.Create(&post)
}

//删除
func Delete(c *gin.Context) {
	post := getById(c)
	if post.ID == 0 {
		return
	}
	db.Unscoped().Delete(&post)
	c.JSON(http.StatusOK, gin.H{
		"messege": "deleted successfuly",
		"data":    "",
	})
}

//更新
func Update(c *gin.Context) {
	oldpost := getById(c)
	var newpost Post
	if err := c.ShouldBindJSON(&newpost); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"messege": err.Error(),
			"data":    "",
		})
		return
	}
	oldpost.Title = newpost.Title
	oldpost.Des = newpost.Des
	if newpost.Status != "" {
		oldpost.Status = newpost.Status
	}

	db.Save(&oldpost)

	c.JSON(http.StatusOK, gin.H{
		"messege": "Post has been updated",
		"data":    oldpost,
	})

}

//根据id查询
func getById(c *gin.Context) Post {
	id := c.Param("id")
	var post Post
	db.First(&post, id)
	if post.ID == 0 {
		c.JSON(http.StatusOK, gin.H{
			"messege": "Post not found",
			"data":    "",
		})
	}
	return post
}

main.go

package main

import (
	"github.com/gin-gonic/gin"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// 实例化gorm.DB
var db *gorm.DB = nil
var err error

func main() {
	// 与数据库建立连接
	dsn := "root:960690@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})

	if err != nil {
		panic(err.Error())
	}

	db.AutoMigrate(&Post{})

	// 生成路由
	server := gin.Default()

	server.GET("/posts", Posts)
	server.GET("/posts/:id", Show)
	server.POST("/posts", Store)
	server.PATCH("/posts/:id", Update)
	server.DELETE("/posts/:id", Delete)
	
	server.Run()

}

安装swag工具

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

main函数添加注释

package main

import (
	_ "go_pro/docs" // 必须导入docs,否则将报错: Failed to load API definition

	"github.com/gin-gonic/gin"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// 实例化gorm.DB
var db *gorm.DB = nil
var err error

// @title        gin+gorm 测试swagger (必填)
// @version      1.0 (必填)
// @description  gin+gorm 测试swagger
// @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     /
func main() {
	// 与数据库建立连接
	dsn := "root:960690@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})

	if err != nil {
		panic(err.Error())
	}

	db.AutoMigrate(&Post{})

	server := gin.Default()

	// 生成路由
	server.GET("/posts", Posts)
	server.GET("/posts/:id", Show)
	server.POST("/posts", Store)
	server.PATCH("/posts/:id", Update)
	server.DELETE("/posts/:id", Delete)

	// 生成api文档
	server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	server.Run()

}

go-swapper注解规范说明:

// @Summary 摘要

// @Description 描述

// @Description 接口的详细描述

// @Id 全局标识符

// @Version 接口版本号

// @Tags 接口分组,相当于归类

// @Accept json 浏览器可处理数据类型

// @Produce json 设置返回数据的类型和编码

// @Param 参数格式 从左到右:参数名、入参类型、数据类型、是否必填和注释 例:id query int true “ID”

// @Success 响应成功 从左到右:状态码、参数类型、数据类型和注释 例:200 {string} string “success”

// @Failure 响应失败 从左到右:状态码、参数类型、数据类型和注释 例:400 {object} string “缺少参数 ID”

// @Router 路由: 地址和http方法 例:/api/user/{id} [get]

// @contact.name 接口联系人

// @contact.url 联系人网址

// @contact.email 联系人邮箱

增加token验证方法

// @securityDefinitions.apikey ApiKeyAuth 安全方式

// @in header token携带的位置,这里是在header中

// @name Authorization heaer中的名称

添加接口的注释

package main

import (
	"net/http"
	"strconv"

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

//查询
func Posts(c *gin.Context) {
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
	offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))

	var posts []Post
	db.Limit(limit).Offset(offset).Find(&posts)
	c.JSON(http.StatusOK, gin.H{
		"messege": "",
		"data":    posts,
	})
}

// @Summary     查询
// @Description 查询
// @Tags        posts
// @Accept      json
// @Produce     json
// @Param       id  path     int true "pid"
// @Success     200 {object} Response
// @Failure     400 {object} Response
// @Failure     404 {object} Response
// @Failure     500 {object} Response
// @Router      /posts/{id} [get]
func Show(c *gin.Context) {
	post := getById(c)
	c.JSON(http.StatusOK, gin.H{
		"messege": "",
		"data":    post,
	})
}

// @Summary     添加post
// @Description 添加post
// @Tags        posts
// @Accept      json
// @Produce     json
// @Param       content body     string true "json"
// @Success     200     {object} Response
// @Failure     400     {object} Response
// @Failure     404     {object} Response
// @Failure     500     {object} Response
// @Router      /posts [post]
func Store(c *gin.Context) {
	var post Post
	if err := c.ShouldBindJSON(&post); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"messege": err.Error(),
			"data":    "",
		})
		return
	}
	post.Status = "Active"
	db.Create(&post)
}

//删除
func Delete(c *gin.Context) {
	post := getById(c)
	if post.ID == 0 {
		return
	}
	db.Unscoped().Delete(&post)
	c.JSON(http.StatusOK, gin.H{
		"messege": "deleted successfuly",
		"data":    "",
	})
}

//更新
func Update(c *gin.Context) {
	oldpost := getById(c)
	var newpost Post
	if err := c.ShouldBindJSON(&newpost); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"messege": err.Error(),
			"data":    "",
		})
		return
	}
	oldpost.Title = newpost.Title
	oldpost.Des = newpost.Des
	if newpost.Status != "" {
		oldpost.Status = newpost.Status
	}

	db.Save(&oldpost)

	c.JSON(http.StatusOK, gin.H{
		"messege": "Post has been updated",
		"data":    oldpost,
	})

}

//根据id查询
func getById(c *gin.Context) Post {
	id := c.Param("id")
	var post Post
	db.First(&post, id)
	if post.ID == 0 {
		c.JSON(http.StatusOK, gin.H{
			"messege": "Post not found",
			"data":    "",
		})
	}
	return post
}

参数param的几种类型 :

query 形如 /user?userId=1016
body 需要将数据放到 body 中进行请求
formData multipart/form-data* 请求
path 形如 /user/1016
header header头信息

格式化swag注解

swag fmt

格式化后注解被美化了

生成文档

swag init

将会生成一个docs文件夹,下面生成3个文件:

  • docs\docs.go
  • docs\swagger.json
  • docs\swagger.yaml
swag init

测试

启动工程,在浏览器中输入地址:http://localhost:8080/swagger/index.html

在这里插入图片描述

POSTTry it outExecute

在这里插入图片描述

查看响应结果:

在这里插入图片描述

点击GET右侧下拉按钮,并输入pid进行查看:

在这里插入图片描述

在数据库中查看:

在这里插入图片描述

补充知识:Token验证

main函数注释追加:

// @securityDefinitions.apikey  ApiKeyAuth
// @in                          header
// @name                        Authorization

路由函数注释追加:

// @Security ApiKeyAuth

补充知识:上传文件

路由函数注释追加:

// @Accept   multipart/form-data
// @Param   file  formData  file      true  "file"

详情请见

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