将 Swagger 添加到你的 Go API 中
从代码注释自动生成 OpenAPI 文档
API 文档对于任何现代应用程序都至关重要,对于 Go APIs Swagger(OpenAPI)已成为行业标准。 对于 Go 开发人员来说,swaggo 提供了一种优雅的解决方案,可以直接从代码注释生成全面的 API 文档。
这张漂亮的图片是由 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.json、swagger.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 文档时:
- 在生产环境中禁用(如果 API 是内部的):
if os.Getenv("ENV") == "production" {
// 不注册 Swagger 端点
return
}
- 添加认证:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
- 限制端点访问频率:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
- 永远不要暴露内部细节:
- 不要记录内部端点
- 避免直接暴露数据库模式
- 在文档中清理错误消息
结论
将 Swagger 文档添加到你的 Go API 中,将开发人员体验从猜测转变为引导探索。swaggo 库通过从代码注释生成全面的 OpenAPI 文档,使这个过程变得简单直接。
关键要点:
- 从基本注释开始,逐步扩展
- 通过 CI/CD 保持文档与代码同步
- 在开发过程中使用 Swagger UI 进行交互式测试
- 彻底记录认证、错误和边缘情况
- 考虑在公开文档时的安全影响
无论你是构建微服务、公共 API 还是内部工具,Swagger 文档都能在减少支持负担、加快入职速度和改善 API 设计方面带来回报。学习注释语法的初始投资很快就会成为常规,自动化生成确保文档永远不会落后于实现。
对于 Go 开发人员,强类型、代码生成和 swaggo 的注释系统相结合,创造了一个强大的工作流程,使 API 文档成为开发过程的一部分,而不是一个后顾之忧。
有用的链接
- Go 快速参考
- 在 Go 中构建 REST APIs
- 比较 Go ORMs for PostgreSQL: GORM vs Ent vs Bun vs sqlc
- 多租户数据库模式与 Go 示例
- LLMs 与结构化输出:Ollama、Qwen3 与 Python 或 Go
- AWS Lambda 性能:JavaScript vs Python vs Go