Swag将Go的注释转换为Swagger2.0文档。我们为流行的 Go Web Framework 创建了各种插件,这样可以与现有Go项目快速集成(使用Swagger UI)。
快速开始 #
- 将注释添加到API源代码中,请参阅声明性注释格式。
- 使用如下命令下载swag:
go install github.com/swaggo/swag/cmd/swag@latest
从源码开始构建的话,需要有Go环境(1.19及以上版本)。
或者从github的release页面下载预编译好的二进制文件。
- 在包含
main.go
文件的项目根目录运行swag init
。这将会解析注释并生成需要的文件(docs
文件夹和docs/docs.go
)。
swag init
确保导入了生成的docs/docs.go
文件,这样特定的配置文件才会被初始化。如果通用API注释没有写在main.go
中,可以使用-g
标识符来告知swag。
swag init -g http/api.go
- (可选) 使用
fmt
格式化 SWAG 注释。(请先升级到最新版本)
swag fmt
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源文件,默认不
--parseDependencyLevel, --pdl 对'--parseDependency'参数进行增强, 是否解析依赖目录中的go源文件, 0 不解析, 1 只解析对象模型, 2 只解析API, 3 对象模型和API都解析 (default: 0)
--markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录
--generatedTime 是否输出时间到输出文件docs.go的顶部,默认是
--codeExampleFiles value, --cef value 解析包含用于 x-codeSamples 扩展的代码示例文件的文件夹,默认禁用
--parseInternal 解析 internal 包中的go文件,默认禁用
--parseDepth value 依赖解析深度 (默认: 100)
--instanceName value 设置文档实例名 (默认: "swagger")
swag fmt -h
NAME:
swag fmt - format swag comments
USAGE:
swag fmt [command options] [arguments...]
OPTIONS:
--dir value, -d value API解析目录 (默认: "./")
--exclude value 解析扫描时排除的目录,多个目录可用逗号分隔(默认:空)
--generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go")
--help, -h show help (default: false)
实操(gin) #
- 使用
swag init
生成Swagger2.0文档后,导入如下代码包:
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files
- 在
main.go
源代码中添加通用的API注释:
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @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 localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
r := gin.Default()
c := controller.NewController()
v1 := r.Group("/api/v1")
{
accounts := v1.Group("/accounts")
{
accounts.GET(":id", c.ShowAccount)
accounts.GET("", c.ListAccounts)
accounts.POST("", c.AddAccount)
accounts.DELETE(":id", c.DeleteAccount)
accounts.PATCH(":id", c.UpdateAccount)
accounts.POST(":id/images", c.UploadAccountImage)
}
//...
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
//...
注意:当路由文件不在main函数时,通用注释也要写在main函数上面,否则无法生效。
此外,可以动态设置一些通用的API信息。生成的代码包docs
导出SwaggerInfo
变量,使用该变量可以通过编码的方式设置标题、描述、版本、主机和基础路径。使用Gin的示例:
package main
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/files"
"github.com/swaggo/gin-swagger"
"./docs" // docs is generated by Swag CLI, you have to import it.
)
// @contact.name API Support
// @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
func main() {
// programatically set swagger info
docs.SwaggerInfo.Title = "Swagger Example API"
docs.SwaggerInfo.Description = "This is a sample server Petstore server."
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.Host = "petstore.swagger.io"
docs.SwaggerInfo.BasePath = "/v2"
docs.SwaggerInfo.Schemes = []string{"http", "https"}
r := gin.New()
// use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run()
}
- 在
controller
代码中添加API操作注释:
package controller
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
"github.com/swaggo/swag/example/celler/model"
)
// 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)
}
// ListAccounts godoc
// @Summary List accounts
// @Description get accounts
// @Tags accounts
// @Accept json
// @Produce json
// @Param q query string false "name search by q" Format(email)
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
q := ctx.Request.URL.Query().Get("q")
accounts, err := model.AccountsAll(q)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, accounts)
}
//...
swag init
-
运行程序,然后在浏览器中访问 http://localhost:8080/swagger/index.html 。将看到Swagger 2.0 Api文档,如下所示:
http://127.0.0.1:8998/swagger/index.html //你定义的那个地址
踩坑 #
swag init --outputTypes json --parseDependency --parseInternal
参数 | 作用 |
---|---|
--outputTypes json |
指定生成 Swagger 文档为 JSON 格式(默认是 YAML)。 |
--parseDependency |
解析并包含代码中的外部依赖项,确保依赖类型在文档中被正确识别。 |
--parseInternal |
解析项目内部未导出的私有方法和类型,适用于需要生成更详细文档的场景。 |
swag init --parseDependency --parseInternal //否则对于外部引人结构体参数无效
- 读取文件
// VideoContent godoc
//
//@Description 获取视频内容(16进制)
//@Tags video
//@Produce json
//@Param cid query int64 true "cid"
//@Param eid query int64 true "eid"
//@Param path query string true "视频路径"
//@Param Range header string true "偏移"
//@Success 206 {file} []bute "分片视频内容"
//@Failure 500 {object} response.Response
//@Router /video/content [get]
func (v *VideoApi) VideoContent(c *gin.Context) {
cidStr := c.Query("cid")
eidStr := c.Query("eid")
videoPath := c.Query("path")
rangeStr := c.GetHeader("Range")
if cidStr == "" || eidStr == "" || videoPath == "" || rangeStr == "" {
response.ErrorParam(c, nil)
return
}
_, err := strconv.Atoi(cidStr)
if err != nil {
response.ErrorParam(c, err)
return
}
_, err = strconv.Atoi(eidStr)
if err != nil {
response.ErrorParam(c, err)
return
}
shortRange, found := strings.CutPrefix(rangeStr, "bytes=")
if !found {
response.ErrorParam(c, rangeStr)
return
}
rangeSlice := strings.Split(shortRange, "-")
if len(rangeSlice) != 2 {
response.ErrorParam(c, rangeStr)
return
}
startIndex, serr := strconv.ParseInt(rangeSlice[0], 10, 64)
endIndex, eerr := strconv.ParseInt(rangeSlice[1], 10, 64)
if serr != nil || eerr != nil {
response.ErrorParam(c, rangeStr)
return
}
content, fileSize, err := service.GetVideoContent(startIndex, endIndex, videoPath)
if err != nil {
if !strings.Contains(err.Error(), "invalid range") {
response.Fail(c, global.FileContentReadFail, err)
return
}
}
bytesCnt := int64(len(content))
extraHeader := map[string]string{"Accept-Ranges": "bytes", "Content-Range": fmt.Sprintf("bytes %d-%d/%d", startIndex, bytesCnt+startIndex-1, fileSize)}
c.DataFromReader(http.StatusPartialContent, bytesCnt, "application/octet-stream", bytes.NewReader(content), extraHeader)
}
// ObjectDetection godoc
//
// @Description 物体识别
// @Tags detection
// @Accept json
// @Produce json
// @Param body body param.ObjectDetectionParam true "创建记录的参数"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /object/detection [post]
func (d *DetectionApi) ObjectDetection(c *gin.Context, p *param.ObjectDetectionParam) {
type detectionStatus struct {
Status int
DetectionTid int32
}
responseData := new(detectionStatus)
if p.DetectionTid == 0 && !p.Cancel {
responseData.DetectionTid = service.GetFakeTaskId()
p.DetectionTid = responseData.DetectionTid
}
status, err := service.AiEngine.GetAiStartStatus(service.CModelTypeObjectDetection)
if err != nil {
response.Fail(c, global.ErrorUnknownMsg, err.Error())
return
}
err = service.AiEngine.ObjectDetection(p)
if err != nil {
response.Fail(c, global.ErrorUnknownMsg, err.Error())
return
}
responseData.Status = status
response.Success(c, global.CurdStatusOkMsg, responseData)
}
// GetObjectImage godoc
//
// @Description 获取识别图
// @Tags detection
// @Produce json
// @Param cid query int64 true "cid"
// @Param eid query int64 true "eid"
// @Param keyid query int64 false "时间区间记录主键id"
// @Param skwing query string false "时间偏移"
// @Param videopath query string true "视频路径"
// @Success 200 {object} response.Response{data=string}
// @Failure 500 {object} response.Response
// @Router /object/image [post]
func (d *DetectionApi) GetObjectImage(c *gin.Context, cid, eid, keyid int64, datatype, videoPath string, currentTime int64) {
objectImagePath, err := service.Detection.GetObjectImage(cid, eid, keyid, datatype, videoPath, currentTime)
if err != nil {
common.Log.Errorf("get objectImage err:%v", err)
response.Fail(c, global.ExitResordFailMsg, err.Error())
return
}
response.Success(c, global.CurdStatusOkMsg, objectImagePath)
}
// GetEvidencePartition godoc
//
// @Description 获取检材分区信息
// @Tags evidence
// @Accept json
// @Produce json
// @Param Cid query string true "记录id"
// @Param Eid query string true "检材id"
// @Param EvidencePath query string true "检材路径"
// @Success 200 {object} response.Response{data=[]vmodel.Partition}
// @Failure 400 {object} response.Response "未知错误"
// @Failure 500 {object} response.Response
// @Router /evidence/partition [get]
- router文件
package routers
import (
......
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"go.uber.org/zap"
)
func StartGin() *gin.Engine {
var router *gin.Engine
if !global.IsDebugMode() {
router = ReleaseRouter()
} else {
router = gin.Default()
//pprof包选择性关闭
//pprof.Register(router)
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
////是否允许跨域
if global.AllowCrossDomain {
router.Use(cors.Next())
}
store := sessions.NewCookieStore([]byte("gesecret"))
router.Use(sessions.Sessions("sid", store))
{
router.GET("/", func(c *gin.Context) { response.Success(c, vmodel.TaskStatusSuccess, "") })
}
fastdev.SetupRouters(router)
checkLicenseAPI := router.Group("/")
checkLicenseAPI.Use(CheckLicenes)
{
checkLicenseAPI.POST("record/create", validator.Create(global.WebPrefix+"CreateRecord")) //*
......
}
......
}