将 Swagger 添加到你的 Go API 中

从代码注释自动生成 OpenAPI 文档

目录

API 文档对于任何现代应用程序都至关重要,对于 Go APIs Swagger(OpenAPI)已成为行业标准。 对于 Go 开发人员来说,swaggo 提供了一种优雅的解决方案,可以直接从代码注释生成全面的 API 文档。

swagger api specs on agile board 这张漂亮的图片是由 AI model Flux 1 dev 生成的。

为什么 Swagger 对于 Go APIs 重要

在构建 REST APIs 时,随着代码的演变,文档往往会变得过时。Swagger 通过从源代码生成文档来解决这个问题,确保文档与实现保持同步。交互式 Swagger UI 允许开发人员直接从浏览器测试端点,显著提升了开发人员的体验。

对于构建微服务或公共 API 的团队,Swagger 文档成为以下方面的关键:

  • 客户端生成:自动生成多种语言的客户端库
  • 契约测试:根据定义的模式验证请求和响应
  • 团队协作:为 API 契约提供单一事实来源
  • 开发人员入职:新团队成员可以交互式地探索 API

使用 swaggo 入门

swaggo 库是为 Go 应用程序添加 Swagger 支持的最流行工具。它通过解析代码中的特殊注释并生成 OpenAPI 3.0 规范文件来工作。

安装

首先,安装 swag CLI 工具:

go install github.com/swaggo/swag/cmd/swag@latest

然后为你的框架添加适当的 Swagger 中间件包。对于 Gin:

go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files

对于 Echo:

go get -u github.com/swaggo/echo-swagger

对于 Fiber:

go get -u github.com/gofiber/swagger

基本配置

首先,在你的 main.go 文件中添加通用 API 信息。类似于你如何构建 Go 中的 REST API, 注释应清晰且描述性:

// @title           Product API
// @version         1.0
// @description     带有 Swagger 文档的产品管理 API
// @termsOfService  http://swagger.io/terms/

// @contact.name   API 支持
// @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.apikey Bearer
// @in header
// @name Authorization
// @description 类型 "Bearer" 后跟一个空格和 JWT 令牌。

func main() {
    // 你的应用程序代码
}

使用 Gin 框架实现

让我们使用 Gin 实现一个完整的示例。首先,使用结构标签定义你的数据模型:

type Product struct {
    ID          int     `json:"id" example:"1"`
    Name        string  `json:"name" example:"Laptop" binding:"required"`
    Description string  `json:"description" example:"高性能笔记本电脑"`
    Price       float64 `json:"price" example:"999.99" binding:"required,gt=0"`
    Stock       int     `json:"stock" example:"50"`
}

type ErrorResponse struct {
    Error   string `json:"error" example:"无效输入"`
    Message string `json:"message" example:"产品名称是必填项"`
}

现在注释你的处理函数。当使用 数据库操作 时,这些注释有助于记录数据流:

// GetProduct godoc
// @Summary      通过 ID 获取产品
// @Description  通过唯一标识符检索单个产品
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "产品 ID"
// @Success      200  {object}  Product
// @Failure      400  {object}  ErrorResponse
// @Failure      404  {object}  ErrorResponse
// @Router       /products/{id} [get]
func GetProduct(c *gin.Context) {
    id := c.Param("id")
    // 实现代码
    c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}

// CreateProduct godoc
// @Summary      创建新产品
// @Description  将新产品添加到目录中
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        product  body      Product  true  "产品对象"
// @Success      201      {object}  Product
// @Failure      400      {object}  ErrorResponse
// @Security     Bearer
// @Router       /products [post]
func CreateProduct(c *gin.Context) {
    var product Product
    if err := c.ShouldBindJSON(&product); err != nil {
        c.JSON(400, ErrorResponse{Error: "错误请求", Message: err.Error()})
        return
    }
    // 保存到数据库
    c.JSON(201, product)
}

生成文档

注释代码后,生成 Swagger 文档:

swag init

这会创建一个包含 swagger.jsonswagger.yaml 和 Go 文件的 docs 文件夹。导入并注册 Swagger 端点:

package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    
    _ "yourproject/docs" // 导入生成的文档
)

func main() {
    r := gin.Default()
    
    // API 路由
    v1 := r.Group("/api/v1")
    {
        v1.GET("/products/:id", GetProduct)
        v1.POST("/products", CreateProduct)
    }
    
    // Swagger 端点
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    
    r.Run(":8080")
}

现在访问你的交互式 API 文档:http://localhost:8080/swagger/index.html

使用 Echo 框架实现

Echo 用户遵循类似的模式,但使用 Echo 特定的中间件:

package main

import (
    "github.com/labstack/echo/v4"
    echoSwagger "github.com/swaggo/echo-swagger"
    
    _ "yourproject/docs"
)

func main() {
    e := echo.New()
    
    // API 路由
    api := e.Group("/api/v1")
    api.GET("/products/:id", getProduct)
    api.POST("/products", createProduct)
    
    // Swagger 端点
    e.GET("/swagger/*", echoSwagger.WrapHandler)
    
    e.Start(":8080")
}

使用 Fiber 框架实现

Fiber 的实现同样简单直接:

package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/swagger"
    
    _ "yourproject/docs"
)

func main() {
    app := fiber.New()
    
    // API 路由
    api := app.Group("/api/v1")
    api.Get("/products/:id", getProduct)
    api.Post("/products", createProduct)
    
    // Swagger 端点
    app.Get("/swagger/*", swagger.HandlerDefault)
    
    app.Listen(":8080")
}

高级 Swagger 注释

文档复杂的请求体

对于嵌套结构或数组:

type CreateOrderRequest struct {
    CustomerID int           `json:"customer_id" example:"123" binding:"required"`
    Items      []OrderItem   `json:"items" binding:"required,min=1"`
    ShippingAddress Address  `json:"shipping_address" binding:"required"`
}

type OrderItem struct {
    ProductID int `json:"product_id" example:"1" binding:"required"`
    Quantity  int `json:"quantity" example:"2" binding:"required,min=1"`
}

type Address struct {
    Street  string `json:"street" example:"123 Main St" binding:"required"`
    City    string `json:"city" example:"New York" binding:"required"`
    ZipCode string `json:"zip_code" example:"10001" binding:"required"`
}

// CreateOrder godoc
// @Summary      创建新订单
// @Description  使用多个项目和送货信息创建订单
// @Tags         orders
// @Accept       json
// @Produce      json
// @Param        order  body      CreateOrderRequest  true  "订单详情"
// @Success      201    {object}  Order
// @Failure      400    {object}  ErrorResponse
// @Failure      422    {object}  ErrorResponse
// @Security     Bearer
// @Router       /orders [post]
func CreateOrder(c *gin.Context) {
    // 实现
}

文档文件上传

// UploadImage godoc
// @Summary      上传产品图片
// @Description  为产品上传图片文件
// @Tags         products
// @Accept       multipart/form-data
// @Produce      json
// @Param        id    path      int   true  "产品 ID"
// @Param        file  formData  file  true  "图片文件"
// @Success      200   {object}  map[string]string
// @Failure      400   {object}  ErrorResponse
// @Security     Bearer
// @Router       /products/{id}/image [post]
func UploadImage(c *gin.Context) {
    file, _ := c.FormFile("file")
    // 处理上传
}

查询参数和分页

// ListProducts godoc
// @Summary      带分页列出产品
// @Description  获取带可选过滤的产品分页列表
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        page      query     int     false  "页码" default(1)
// @Param        page_size query     int     false  "每页项目数" default(10)
// @Param        category  query     string  false  "按类别过滤"
// @Param        min_price query     number  false  "最低价格"
// @Param        max_price query     number  false  "最高价格"
// @Success      200       {array}   Product
// @Failure      400       {object}  ErrorResponse
// @Router       /products [get]
func ListProducts(c *gin.Context) {
    // 带分页的实现
}

认证和安全

在你的 API 中记录不同的认证方法。对于 多租户应用程序,适当的认证文档至关重要:

Bearer Token 认证

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description 类型 "Bearer" 后跟一个空格和 JWT 令牌。

API Key 认证

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description 认证用的 API 密钥

OAuth2 认证

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write 授予写入权限
// @scope.admin 授予读写管理信息的权限

Basic 认证

// @securityDefinitions.basic BasicAuth

将安全应用到特定端点:

// @Security Bearer
// @Security ApiKeyAuth

自定义 Swagger UI

你可以自定义 Swagger UI 的外观和行为:

// 自定义配置
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))

// 带自定义标题
r.GET("/swagger/*any", ginSwagger.WrapHandler(
    swaggerFiles.Handler,
    ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
    ginSwagger.DefaultModelsExpandDepth(-1),
))

在生产环境中禁用 Swagger:

if os.Getenv("ENV") != "production" {
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

与 CI/CD 集成

在 CI/CD 管道中自动化 Swagger 文档生成:

# GitHub Actions 示例
name: Generate Swagger Docs
on: [push]

jobs:
  swagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: 设置 Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: 安装 swag
        run: go install github.com/swaggo/swag/cmd/swag@latest
      
      - name: 生成 Swagger 文档
        run: swag init
      
      - name: 提交文档
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add docs/
          git commit -m "更新 Swagger 文档" || exit 0
          git push          

最佳实践

1. 保持注释风格一致

在所有端点中保持一致的格式:

// HandlerName godoc
// @Summary      简要描述(少于 50 个字符)
// @Description  端点功能的详细描述
// @Tags         资源名称
// @Accept       json
// @Produce      json
// @Param        name  location  type  required  "描述"
// @Success      200   {object}  ResponseType
// @Failure      400   {object}  ErrorResponse
// @Router       /path [method]

2. 使用描述性示例

添加现实的示例以帮助 API 使用者:

type User struct {
    ID        int       `json:"id" example:"1"`
    Email     string    `json:"email" example:"user@example.com"`
    CreatedAt time.Time `json:"created_at" example:"2025-01-15T10:30:00Z"`
}

3. 文档所有响应码

包含所有可能的 HTTP 状态码:

// @Success      200  {object}  Product
// @Success      201  {object}  Product
// @Failure      400  {object}  ErrorResponse "错误请求"
// @Failure      401  {object}  ErrorResponse "未授权"
// @Failure      403  {object}  ErrorResponse "禁止"
// @Failure      404  {object}  ErrorResponse "未找到"
// @Failure      422  {object}  ErrorResponse "验证错误"
// @Failure      500  {object}  ErrorResponse "内部服务器错误"

4. 版本化你的 API

在基础路径中使用适当的版本:

// @BasePath  /api/v1

并相应地组织代码:

v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")

5. 分组相关端点

使用标签逻辑地组织端点:

// @Tags products
// @Tags orders
// @Tags users

6. 保持文档更新

在每次提交之前运行 swag init 或将其集成到构建过程中:

#!/bin/bash
# pre-commit hook
swag init
git add docs/

测试 Swagger 文档

在使用 AWS Lambda 等无服务器架构 时,测试 API 文档变得更加重要:

func TestSwaggerGeneration(t *testing.T) {
    // 验证 swagger.json 是否存在
    _, err := os.Stat("./docs/swagger.json")
    if err != nil {
        t.Fatal("未找到 swagger.json,运行 'swag init'")
    }
    
    // 验证 swagger 端点是否响应
    r := setupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
    r.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
}

常见问题和解决方案

文档未更新

如果更改未显示,请确保重新生成文档:

swag init --parseDependency --parseInternal

--parseDependency 标志解析外部依赖,--parseInternal 标志解析内部包。

未识别的自定义类型

对于来自外部包的类型,使用 swaggertype 标签:

type CustomTime struct {
    time.Time
}

func (CustomTime) SwaggerDoc() map[string]string {
    return map[string]string{
        "time": "RFC3339 时间戳",
    }
}

或者使用 swaggertype 标签:

type Product struct {
    ID        int        `json:"id"`
    UpdatedAt CustomTime `json:"updated_at" swaggertype:"string" format:"date-time"`
}

数组和枚举

文档数组类型和枚举:

type Filter struct {
    Status []string `json:"status" enums:"active,inactive,pending"`
    Tags   []string `json:"tags"`
}

// @Param  status  query  string  false  "状态过滤器" Enums(active, inactive, pending)

其他方法

虽然 swaggo 是最受欢迎的选择,但还有其他选项:

go-swagger

一个功能更丰富但更复杂的替代方案:

brew install go-swagger
swagger generate spec -o ./swagger.json

手动 OpenAPI 文件

对于完全控制,手动编写 OpenAPI 规范文件:

openapi: 3.0.0
info:
  title: Product API
  version: 1.0.0
paths:
  /products:
    get:
      summary: 列出产品
      responses:
        '200':
          description: 成功

然后使用:

r.StaticFile("/openapi.yaml", "./openapi.yaml")

与 AI 和 LLMs 集成

当构建与 AI 服务集成的 API 时,适当的文档变得至关重要。例如,当使用 结构化 LLM 输出 时,Swagger 帮助记录复杂的请求和响应模式:

type LLMRequest struct {
    Prompt      string            `json:"prompt" example:"总结这段文字"`
    Model       string            `json:"model" example:"qwen2.5:latest"`
    Temperature float64           `json:"temperature" example:"0.7" minimum:"0" maximum:"2"`
    MaxTokens   int               `json:"max_tokens" example:"1000" minimum:"1"`
    Schema      map[string]interface{} `json:"schema,omitempty"`
}

// GenerateStructured godoc
// @Summary      生成结构化 LLM 输出
// @Description  生成带约束输出模式的文本
// @Tags         llm
// @Accept       json
// @Produce      json
// @Param        request  body      LLMRequest  true  "LLM 参数"
// @Success      200      {object}  map[string]interface{}
// @Failure      400      {object}  ErrorResponse
// @Router       /llm/generate [post]
func GenerateStructured(c *gin.Context) {
    // 实现
}

性能考虑

Swagger 文档对性能影响很小:

  • 构建时间swag init 对于大多数项目需要 1-3 秒
  • 运行时:文档在启动时加载一次
  • 内存:通常增加 1-2MB 的二进制文件大小
  • 响应时间:对 API 端点本身没有影响

对于非常大的 API(100+ 端点),可以考虑:

  • 将文档拆分为多个 Swagger 文件
  • 延迟加载 Swagger UI 资源
  • 从单独的服务提供文档

安全考虑

当公开 Swagger 文档时:

  1. 在生产环境中禁用(如果 API 是内部的):
if os.Getenv("ENV") == "production" {
    // 不注册 Swagger 端点
    return
}
  1. 添加认证
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. 限制端点访问频率
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. 永远不要暴露内部细节
  • 不要记录内部端点
  • 避免直接暴露数据库模式
  • 在文档中清理错误消息

结论

将 Swagger 文档添加到你的 Go API 中,将开发人员体验从猜测转变为引导探索。swaggo 库通过从代码注释生成全面的 OpenAPI 文档,使这个过程变得简单直接。

关键要点:

  • 从基本注释开始,逐步扩展
  • 通过 CI/CD 保持文档与代码同步
  • 在开发过程中使用 Swagger UI 进行交互式测试
  • 彻底记录认证、错误和边缘情况
  • 考虑在公开文档时的安全影响

无论你是构建微服务、公共 API 还是内部工具,Swagger 文档都能在减少支持负担、加快入职速度和改善 API 设计方面带来回报。学习注释语法的初始投资很快就会成为常规,自动化生成确保文档永远不会落后于实现。

对于 Go 开发人员,强类型、代码生成和 swaggo 的注释系统相结合,创造了一个强大的工作流程,使 API 文档成为开发过程的一部分,而不是一个后顾之忧。

有用的链接

外部资源