下载工作台
Gin Web 开发

OpenAPI 与 Swagger 文档

试读上半部分 · 解锁后可读全文

第 11 章 · OpenAPI 与 Swagger 文档

本章目标:使用 swaggo/swag 从注释生成 OpenAPI 3 文档;为 api-go-demo 商品与认证 API 添加完整注解;暴露 /swagger/index.html 交互调试;DTO 标注 slug、is_published、price 分;对照 fastapi-web 自动 /docsch16 答辩材料。

学时建议:3~4 小时(含 1.5 小时跟练)

前置:完成 gin-web ch10;Handler 与 DTO 已实现。


11.1 场景说明:可交付的 API 文档

api-go-demo 答辩与 ch16 毕业项目 需在线文档。FastAPI 自带 Swagger UI;Gin 需 swag 从 Go 注释生成 OpenAPI。

能力FastAPI svc-demoGin api-go-demo
交互文档/docs/swagger/index.html
SchemaPydantic 自动注释 + struct tag
导出/openapi.jsondocs/swagger.json
BearerOAuth2 方案@securityDefinitions.apikey
域名 api.example.com;文档 description 写明:价格单位为分(int64)

11.2 安装与工具链

go install github.com/swaggo/swag/cmd/swag@latest
go get github.com/swaggo/gin-swagger@v1.6.0
go get github.com/swaggo/files@v1.0.1

验证:

swag -v
# Swag Version: v1.16.x

Makefile 目标(推荐):

.PHONY: swagger
swagger:
	swag init -g cmd/server/main.go -o docs --parseDependency --parseInternal

CI 可选:提交前 make swagger 并检查 git diff docs/ 为空。


11.3 General API 注释(main.go)

// cmd/server/main.go

// @title           api-go-demo API
// @version         1.0
// @description     虚构商城 API。商品标识为 slug;上架字段 is_published;价格单位为分(int64,6800=68.00元)。
// @termsOfService  https://example.com/terms

// @contact.name   API Support
// @contact.url    https://example.com/support
// @contact.email  support@example.com

// @license.name  MIT
// @license.url   https://opensource.org/licenses/MIT

// @host      127.0.0.1:8080
// @BasePath  /api/v1

// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description 输入: Bearer {access_token}

package main

import (
    "example.com/api-go-demo/docs"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    // ...
)

func main() {
    // docs 包由 swag init 生成,需 blank import 触发 init
    _ = docs.SwaggerInfo
    // ...
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

生产环境可关闭 Swagger 路由:

if cfg.Env != "prod" {
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

11.4 DTO 与响应模型

swag 解析导出结构体与 json / example tag:

// internal/dto/product.go
package dto

type ProductResp struct {
    Slug        string `json:"slug" example:"go-handbook"`
    Name        string `json:"name" example:"Go 语言手册"`
    Price       int64  `json:"price" example:"6800"` // 分
    Stock       int    `json:"stock" example:"50"`
    IsPublished bool   `json:"is_published" example:"true"`
    Description string `json:"description,omitempty" example:"入门到进阶"`
}

type CreateProductReq struct {
    Slug        string `json:"slug" binding:"required,min=2,max=64" example:"go-handbook"`
    Name        string `json:"name" binding:"required" example:"Go 语言手册"`
    Price       int64  `json:"price" binding:"required,min=1" example:"6800"`
    Stock       int    `json:"stock" binding:"min=0" example:"50"`
    IsPublished bool   `json:"is_published" example:"false"`
    Description string `json:"description" example:""`
}

type UpdateProductReq struct {
    Name        *string `json:"name" example:"Go 手册第二版"`
    Price       *int64  `json:"price" example:"7200"`
    Stock       *int    `json:"stock" example:"100"`
    IsPublished *bool   `json:"is_published" example:"true"`
    Description *string `json:"description"`
}

type PageData struct {
    Items []ProductResp `json:"items"`
    Total int64         `json:"total" example:"42"`
    Page  int           `json:"page" example:"1"`
    Size  int           `json:"size" example:"20"`
}

Envelope(ch07):

// internal/response/envelope.go
type Envelope struct {
    Code    string      `json:"code" example:"OK"`
    Message string      `json:"message" example:"success"`
    Data    interface{} `json:"data"`
}

11.5 Handler 注解完整示例

商品

// ListProducts 商品列表(仅已发布)
// @Summary      分页列表已发布商品
// @Description  仅返回 is_published=true 的商品;price 单位为分
// @Tags         products
// @Produce      json
// @Param        page       query int false "页码" default(1) minimum(1)
// @Param        page_size  query int false "每页条数" default(20) minimum(1) maximum(100)
// @Success      200 {object} response.Envelope{data=dto.PageData}
// @Failure      500 {object} response.Envelope
// @Router       /products [get]
func (h *ProductHandler) ListProducts(c *gin.Context) { /* ... */ }

// GetProduct 按 slug 获取详情
// @Summary      商品详情
// @Description  按 slug 查询;未发布或不存在返回 404
// @Tags         products
// @Produce      json
// @Param        slug path string true "商品 slug" example(go-handbook)
// @Success      200 {object} response.Envelope{data=dto.ProductResp}
// @Failure      404 {object} response.Envelope
// @Router       /products/{slug} [get]
func (h *ProductHandler) GetProduct(c *gin.Context) { /* ... */ }

// CreateProduct 创建商品
// @Summary      创建商品(admin)
// @Description  需要 admin 角色;slug 唯一;price 为分
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        body body dto.CreateProductReq true "创建参数"
// @Security     BearerAuth
// @Success      201 {object} response.Envelope{data=dto.ProductResp}
// @Failure      400 {object} response.Envelope
// @Failure      401 {object} response.Envelope
// @Failure      403 {object} response.Envelope
// @Failure      409 {object} response.Envelope
// @Router       /products [post]
func (h *ProductHandler) CreateProduct(c *gin.Context) { /* ... */ }

// UpdateProduct 部分更新
// @Summary      更新商品(admin)
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        slug path string true "slug"
// @Param        body body dto.UpdateProductReq true "更新字段"
// @Security     BearerAuth
// @Success      200 {object} response.Envelope{data=dto.ProductResp}
// @Failure      404 {object} response.Envelope
// @Router       /products/{slug} [patch]
func (h *ProductHandler) UpdateProduct(c *gin.Context) { /* ... */ }

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。