3.5.3 API 文档生成

3.5.3 API 文档生成 #

良好的 API 文档是成功 API 的关键要素。本节将介绍如何使用各种工具和技术自动生成、维护和发布高质量的 API 文档,包括 Swagger/OpenAPI、代码注释生成文档等方法。

Swagger/OpenAPI 文档生成 #

Swagger(现在称为 OpenAPI)是最流行的 API 文档标准。我们将使用 swaggo/swag 工具来自动生成文档。

安装和配置 #

# 安装 swag 命令行工具
go install github.com/swaggo/swag/cmd/swag@latest

# 安装 Gin Swagger 中间件
go get github.com/swaggo/gin-swagger
go get github.com/swaggo/files

基本配置 #

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/swaggo/gin-swagger"
    "github.com/swaggo/files"

    _ "your-project/docs" // 导入生成的文档
)

// @title           User Management API
// @version         1.0
// @description     A comprehensive user management API with authentication
// @termsOfService  http://swagger.io/terms/

// @contact.name   API Support
// @contact.url    http://www.swagger.io/support
// @contact.email  [email protected]

// @license.name  Apache 2.0
// @license.url   http://www.apache.org/licenses/LICENSE-2.0.html

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

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.

// @securityDefinitions.basic BasicAuth

func main() {
    r := gin.Default()

    // Swagger 文档路由
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

    // API 路由
    v1 := r.Group("/api/v1")
    {
        users := v1.Group("/users")
        {
            users.GET("", getUsers)
            users.POST("", createUser)
            users.GET("/:id", getUser)
            users.PUT("/:id", updateUser)
            users.DELETE("/:id", deleteUser)
        }

        auth := v1.Group("/auth")
        {
            auth.POST("/login", login)
            auth.POST("/register", register)
            auth.POST("/refresh", refreshToken)
        }
    }

    r.Run(":8080")
}

数据模型定义 #

// User 用户模型
type User struct {
    ID        uint      `json:"id" gorm:"primaryKey" example:"1"`
    Username  string    `json:"username" gorm:"uniqueIndex" example:"johndoe"`
    Email     string    `json:"email" gorm:"uniqueIndex" example:"[email protected]"`
    FullName  string    `json:"full_name" example:"John Doe"`
    Avatar    string    `json:"avatar" example:"https://example.com/avatar.jpg"`
    IsActive  bool      `json:"is_active" example:"true"`
    CreatedAt time.Time `json:"created_at" example:"2023-01-01T00:00:00Z"`
    UpdatedAt time.Time `json:"updated_at" example:"2023-01-01T00:00:00Z"`
} // @name User

// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
    Username string `json:"username" binding:"required,min=3,max=20" example:"johndoe"`
    Email    string `json:"email" binding:"required,email" example:"[email protected]"`
    FullName string `json:"full_name" binding:"required" example:"John Doe"`
    Password string `json:"password" binding:"required,min=6" example:"password123"`
} // @name CreateUserRequest

// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
    Email    *string `json:"email,omitempty" binding:"omitempty,email" example:"[email protected]"`
    FullName *string `json:"full_name,omitempty" example:"New Full Name"`
    Avatar   *string `json:"avatar,omitempty" example:"https://example.com/new-avatar.jpg"`
    IsActive *bool   `json:"is_active,omitempty" example:"false"`
} // @name UpdateUserRequest

// LoginRequest 登录请求
type LoginRequest struct {
    Username string `json:"username" binding:"required" example:"johndoe"`
    Password string `json:"password" binding:"required" example:"password123"`
} // @name LoginRequest

// LoginResponse 登录响应
type LoginResponse struct {
    Token        string    `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
    RefreshToken string    `json:"refresh_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
    User         User      `json:"user"`
    ExpiresAt    time.Time `json:"expires_at" example:"2023-01-01T01:00:00Z"`
} // @name LoginResponse

// ErrorResponse 错误响应
type ErrorResponse struct {
    Error   string            `json:"error" example:"Validation failed"`
    Message string            `json:"message" example:"Request validation failed"`
    Code    int              `json:"code" example:"400"`
    Details map[string]string `json:"details,omitempty"`
} // @name ErrorResponse

// SuccessResponse 成功响应
type SuccessResponse struct {
    Message string      `json:"message" example:"Operation completed successfully"`
    Data    interface{} `json:"data,omitempty"`
} // @name SuccessResponse

// PaginationMeta 分页元信息
type PaginationMeta struct {
    Total       int `json:"total" example:"100"`
    Page        int `json:"page" example:"1"`
    PerPage     int `json:"per_page" example:"10"`
    TotalPages  int `json:"total_pages" example:"10"`
    HasNext     bool `json:"has_next" example:"true"`
    HasPrev     bool `json:"has_prev" example:"false"`
} // @name PaginationMeta

// UsersListResponse 用户列表响应
type UsersListResponse struct {
    Users []User         `json:"users"`
    Meta  PaginationMeta `json:"meta"`
} // @name UsersListResponse

API 端点文档 #

// getUsers 获取用户列表
// @Summary      获取用户列表
// @Description  获取系统中的用户列表,支持分页和搜索
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        page     query     int     false  "页码"                default(1)
// @Param        per_page query     int     false  "每页数量"             default(10)
// @Param        search   query     string  false  "搜索关键词"
// @Param        is_active query    bool    false  "是否激活"
// @Param        sort     query     string  false  "排序字段"             Enums(id, username, email, created_at)
// @Param        order    query     string  false  "排序方向"             Enums(asc, desc)
// @Success      200      {object}  UsersListResponse
// @Failure      400      {object}  ErrorResponse
// @Failure      500      {object}  ErrorResponse
// @Security     ApiKeyAuth
// @Router       /users [get]
func getUsers(c *gin.Context) {
    // 解析查询参数
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "10"))
    search := c.Query("search")
    isActiveStr := c.Query("is_active")
    sort := c.DefaultQuery("sort", "id")
    order := c.DefaultQuery("order", "asc")

    // 验证参数
    if page < 1 {
        page = 1
    }
    if perPage < 1 || perPage > 100 {
        perPage = 10
    }

    var users []User
    var total int64

    query := db.Model(&User{})

    // 搜索过滤
    if search != "" {
        query = query.Where("username LIKE ? OR email LIKE ? OR full_name LIKE ?",
            "%"+search+"%", "%"+search+"%", "%"+search+"%")
    }

    // 状态过滤
    if isActiveStr != "" {
        if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
            query = query.Where("is_active = ?", isActive)
        }
    }

    // 计算总数
    query.Count(&total)

    // 排序和分页
    offset := (page - 1) * perPage
    query.Order(fmt.Sprintf("%s %s", sort, order)).
          Offset(offset).
          Limit(perPage).
          Find(&users)

    totalPages := int(math.Ceil(float64(total) / float64(perPage)))

    response := UsersListResponse{
        Users: users,
        Meta: PaginationMeta{
            Total:      int(total),
            Page:       page,
            PerPage:    perPage,
            TotalPages: totalPages,
            HasNext:    page < totalPages,
            HasPrev:    page > 1,
        },
    }

    c.JSON(http.StatusOK, response)
}

// getUser 获取单个用户
// @Summary      获取用户详情
// @Description  根据用户ID获取用户详细信息
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "用户ID"
// @Success      200  {object}  User
// @Failure      400  {object}  ErrorResponse
// @Failure      404  {object}  ErrorResponse
// @Failure      500  {object}  ErrorResponse
// @Security     ApiKeyAuth
// @Router       /users/{id} [get]
func getUser(c *gin.Context) {
    id, err := strconv.ParseUint(c.Param("id"), 10, 32)
    if err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "Invalid ID",
            Message: "User ID must be a valid number",
            Code:    http.StatusBadRequest,
        })
        return
    }

    var user User
    if err := db.First(&user, uint(id)).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            c.JSON(http.StatusNotFound, ErrorResponse{
                Error:   "User not found",
                Message: "The requested user does not exist",
                Code:    http.StatusNotFound,
            })
            return
        }

        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Database error",
            Message: "Failed to retrieve user",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    c.JSON(http.StatusOK, user)
}

// createUser 创建用户
// @Summary      创建新用户
// @Description  创建一个新的用户账户
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        user  body      CreateUserRequest  true  "用户信息"
// @Success      201   {object}  User
// @Failure      400   {object}  ErrorResponse
// @Failure      409   {object}  ErrorResponse
// @Failure      500   {object}  ErrorResponse
// @Security     ApiKeyAuth
// @Router       /users [post]
func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        var details map[string]string
        if validationErrors, ok := err.(validator.ValidationErrors); ok {
            details = make(map[string]string)
            for _, fieldError := range validationErrors {
                details[fieldError.Field()] = getValidationErrorMessage(fieldError)
            }
        }

        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "Validation failed",
            Message: "Request validation failed",
            Code:    http.StatusBadRequest,
            Details: details,
        })
        return
    }

    // 检查用户名和邮箱是否已存在
    var existingUser User
    if err := db.Where("username = ? OR email = ?", req.Username, req.Email).First(&existingUser).Error; err == nil {
        c.JSON(http.StatusConflict, ErrorResponse{
            Error:   "User already exists",
            Message: "Username or email already taken",
            Code:    http.StatusConflict,
        })
        return
    }

    // 密码哈希
    hashedPassword, err := hashPassword(req.Password)
    if err != nil {
        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Password processing failed",
            Message: "Failed to process password",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    user := User{
        Username: req.Username,
        Email:    req.Email,
        FullName: req.FullName,
        IsActive: true,
    }

    if err := db.Create(&user).Error; err != nil {
        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Database error",
            Message: "Failed to create user",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    c.Header("Location", fmt.Sprintf("/api/v1/users/%d", user.ID))
    c.JSON(http.StatusCreated, user)
}

// updateUser 更新用户
// @Summary      更新用户信息
// @Description  更新指定用户的信息
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        id    path      int                true  "用户ID"
// @Param        user  body      UpdateUserRequest  true  "更新的用户信息"
// @Success      200   {object}  User
// @Failure      400   {object}  ErrorResponse
// @Failure      404   {object}  ErrorResponse
// @Failure      409   {object}  ErrorResponse
// @Failure      500   {object}  ErrorResponse
// @Security     ApiKeyAuth
// @Router       /users/{id} [put]
func updateUser(c *gin.Context) {
    id, err := strconv.ParseUint(c.Param("id"), 10, 32)
    if err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "Invalid ID",
            Message: "User ID must be a valid number",
            Code:    http.StatusBadRequest,
        })
        return
    }

    var user User
    if err := db.First(&user, uint(id)).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            c.JSON(http.StatusNotFound, ErrorResponse{
                Error:   "User not found",
                Message: "The requested user does not exist",
                Code:    http.StatusNotFound,
            })
            return
        }

        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Database error",
            Message: "Failed to retrieve user",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    var req UpdateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "Validation failed",
            Message: err.Error(),
            Code:    http.StatusBadRequest,
        })
        return
    }

    // 检查邮箱是否被其他用户使用
    if req.Email != nil && *req.Email != user.Email {
        var existingUser User
        if err := db.Where("email = ? AND id != ?", *req.Email, user.ID).First(&existingUser).Error; err == nil {
            c.JSON(http.StatusConflict, ErrorResponse{
                Error:   "Email already taken",
                Message: "The email address is already in use",
                Code:    http.StatusConflict,
            })
            return
        }
    }

    // 更新字段
    updates := make(map[string]interface{})
    if req.Email != nil {
        updates["email"] = *req.Email
    }
    if req.FullName != nil {
        updates["full_name"] = *req.FullName
    }
    if req.Avatar != nil {
        updates["avatar"] = *req.Avatar
    }
    if req.IsActive != nil {
        updates["is_active"] = *req.IsActive
    }

    if err := db.Model(&user).Updates(updates).Error; err != nil {
        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Database error",
            Message: "Failed to update user",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    c.JSON(http.StatusOK, user)
}

// deleteUser 删除用户
// @Summary      删除用户
// @Description  删除指定的用户账户
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        id  path  int  true  "用户ID"
// @Success      204 "No Content"
// @Failure      400 {object}  ErrorResponse
// @Failure      404 {object}  ErrorResponse
// @Failure      500 {object}  ErrorResponse
// @Security     ApiKeyAuth
// @Router       /users/{id} [delete]
func deleteUser(c *gin.Context) {
    id, err := strconv.ParseUint(c.Param("id"), 10, 32)
    if err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "Invalid ID",
            Message: "User ID must be a valid number",
            Code:    http.StatusBadRequest,
        })
        return
    }

    var user User
    if err := db.First(&user, uint(id)).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            c.JSON(http.StatusNotFound, ErrorResponse{
                Error:   "User not found",
                Message: "The requested user does not exist",
                Code:    http.StatusNotFound,
            })
            return
        }

        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Database error",
            Message: "Failed to retrieve user",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    if err := db.Delete(&user).Error; err != nil {
        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Database error",
            Message: "Failed to delete user",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    c.Status(http.StatusNoContent)
}

// login 用户登录
// @Summary      用户登录
// @Description  用户登录获取访问令牌
// @Tags         auth
// @Accept       json
// @Produce      json
// @Param        credentials  body      LoginRequest  true  "登录凭据"
// @Success      200          {object}  LoginResponse
// @Failure      400          {object}  ErrorResponse
// @Failure      401          {object}  ErrorResponse
// @Failure      500          {object}  ErrorResponse
// @Router       /auth/login [post]
func login(c *gin.Context) {
    var req LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "Validation failed",
            Message: err.Error(),
            Code:    http.StatusBadRequest,
        })
        return
    }

    // 验证用户凭据
    var user User
    if err := db.Where("username = ?", req.Username).First(&user).Error; err != nil {
        c.JSON(http.StatusUnauthorized, ErrorResponse{
            Error:   "Invalid credentials",
            Message: "Username or password is incorrect",
            Code:    http.StatusUnauthorized,
        })
        return
    }

    if !user.IsActive {
        c.JSON(http.StatusUnauthorized, ErrorResponse{
            Error:   "Account disabled",
            Message: "Your account has been disabled",
            Code:    http.StatusUnauthorized,
        })
        return
    }

    // 验证密码(这里简化处理)
    if !verifyPassword(req.Password, user.Password) {
        c.JSON(http.StatusUnauthorized, ErrorResponse{
            Error:   "Invalid credentials",
            Message: "Username or password is incorrect",
            Code:    http.StatusUnauthorized,
        })
        return
    }

    // 生成 JWT 令牌
    token, err := generateJWT(user.ID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Token generation failed",
            Message: "Failed to generate access token",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    refreshToken, err := generateRefreshToken(user.ID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "Token generation failed",
            Message: "Failed to generate refresh token",
            Code:    http.StatusInternalServerError,
        })
        return
    }

    response := LoginResponse{
        Token:        token,
        RefreshToken: refreshToken,
        User:         user,
        ExpiresAt:    time.Now().Add(24 * time.Hour),
    }

    c.JSON(http.StatusOK, response)
}

生成和发布文档 #

# 生成 Swagger 文档
swag init

# 这会在项目根目录生成 docs 文件夹,包含:
# - docs.go
# - swagger.json
# - swagger.yaml

自定义文档配置 #

// 自定义 Swagger 配置
func setupSwagger(r *gin.Engine) {
    // 自定义 Swagger 配置
    config := &ginSwagger.Config{
        URL:         "doc.json", // The url pointing to API definition
        DeepLinking: true,
        DocExpansion: "none",
        DefaultModelsExpandDepth: 1,
    }

    r.GET("/swagger/*any", ginSwagger.CustomWrapHandler(config, swaggerFiles.Handler))

    // 提供原始 JSON 和 YAML 格式
    r.Static("/docs", "./docs")

    // API 信息端点
    r.GET("/api/info", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "title":       "User Management API",
            "version":     "1.0.0",
            "description": "A comprehensive user management API",
            "docs_url":    "/swagger/index.html",
            "openapi_url": "/docs/swagger.json",
        })
    })
}

代码生成文档 #

使用 go-swagger #

# 安装 go-swagger
go install github.com/go-swagger/go-swagger/cmd/swagger@latest

# 从代码生成 OpenAPI 规范
swagger generate spec -o ./swagger.json --scan-models

自定义文档生成器 #

package main

import (
    "encoding/json"
    "fmt"
    "go/ast"
    "go/parser"
    "go/token"
    "reflect"
    "strings"
)

// APIDocGenerator API 文档生成器
type APIDocGenerator struct {
    routes []RouteInfo
    models map[string]ModelInfo
}

type RouteInfo struct {
    Method      string            `json:"method"`
    Path        string            `json:"path"`
    Handler     string            `json:"handler"`
    Summary     string            `json:"summary"`
    Description string            `json:"description"`
    Tags        []string          `json:"tags"`
    Parameters  []ParameterInfo   `json:"parameters"`
    Responses   map[int]ResponseInfo `json:"responses"`
}

type ParameterInfo struct {
    Name        string `json:"name"`
    In          string `json:"in"` // query, path, header, body
    Type        string `json:"type"`
    Required    bool   `json:"required"`
    Description string `json:"description"`
    Example     string `json:"example,omitempty"`
}

type ResponseInfo struct {
    Description string      `json:"description"`
    Schema      interface{} `json:"schema,omitempty"`
    Example     interface{} `json:"example,omitempty"`
}

type ModelInfo struct {
    Name        string                 `json:"name"`
    Type        string                 `json:"type"`
    Properties  map[string]PropertyInfo `json:"properties"`
    Required    []string               `json:"required"`
    Description string                 `json:"description"`
}

type PropertyInfo struct {
    Type        string      `json:"type"`
    Format      string      `json:"format,omitempty"`
    Description string      `json:"description"`
    Example     interface{} `json:"example,omitempty"`
    Required    bool        `json:"required"`
}

// NewAPIDocGenerator 创建文档生成器
func NewAPIDocGenerator() *APIDocGenerator {
    return &APIDocGenerator{
        routes: make([]RouteInfo, 0),
        models: make(map[string]ModelInfo),
    }
}

// RegisterRoute 注册路由信息
func (g *APIDocGenerator) RegisterRoute(route RouteInfo) {
    g.routes = append(g.routes, route)
}

// RegisterModel 注册模型信息
func (g *APIDocGenerator) RegisterModel(name string, model interface{}) {
    modelInfo := g.analyzeModel(name, model)
    g.models[name] = modelInfo
}

// analyzeModel 分析模型结构
func (g *APIDocGenerator) analyzeModel(name string, model interface{}) ModelInfo {
    t := reflect.TypeOf(model)
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }

    properties := make(map[string]PropertyInfo)
    var required []string

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        jsonTag := field.Tag.Get("json")
        if jsonTag == "-" {
            continue
        }

        fieldName := field.Name
        if jsonTag != "" {
            parts := strings.Split(jsonTag, ",")
            if parts[0] != "" {
                fieldName = parts[0]
            }
        }

        property := PropertyInfo{
            Type:        getFieldType(field.Type),
            Description: field.Tag.Get("description"),
            Example:     field.Tag.Get("example"),
        }

        // 检查是否必需
        bindingTag := field.Tag.Get("binding")
        if strings.Contains(bindingTag, "required") {
            required = append(required, fieldName)
            property.Required = true
        }

        properties[fieldName] = property
    }

    return ModelInfo{
        Name:        name,
        Type:        "object",
        Properties:  properties,
        Required:    required,
        Description: fmt.Sprintf("%s model", name),
    }
}

// getFieldType 获取字段类型
func getFieldType(t reflect.Type) string {
    switch t.Kind() {
    case reflect.String:
        return "string"
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return "integer"
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        return "integer"
    case reflect.Float32, reflect.Float64:
        return "number"
    case reflect.Bool:
        return "boolean"
    case reflect.Slice, reflect.Array:
        return "array"
    case reflect.Map:
        return "object"
    case reflect.Struct:
        if t.String() == "time.Time" {
            return "string"
        }
        return "object"
    case reflect.Ptr:
        return getFieldType(t.Elem())
    default:
        return "string"
    }
}

// GenerateOpenAPI 生成 OpenAPI 规范
func (g *APIDocGenerator) GenerateOpenAPI() map[string]interface{} {
    paths := make(map[string]interface{})

    for _, route := range g.routes {
        pathItem := make(map[string]interface{})

        operation := map[string]interface{}{
            "summary":     route.Summary,
            "description": route.Description,
            "tags":        route.Tags,
            "parameters":  g.convertParameters(route.Parameters),
            "responses":   g.convertResponses(route.Responses),
        }

        pathItem[strings.ToLower(route.Method)] = operation
        paths[route.Path] = pathItem
    }

    components := map[string]interface{}{
        "schemas": g.models,
    }

    return map[string]interface{}{
        "openapi": "3.0.0",
        "info": map[string]interface{}{
            "title":       "Generated API Documentation",
            "version":     "1.0.0",
            "description": "Auto-generated API documentation",
        },
        "paths":      paths,
        "components": components,
    }
}

// convertParameters 转换参数信息
func (g *APIDocGenerator) convertParameters(params []ParameterInfo) []map[string]interface{} {
    result := make([]map[string]interface{}, len(params))

    for i, param := range params {
        result[i] = map[string]interface{}{
            "name":        param.Name,
            "in":          param.In,
            "required":    param.Required,
            "description": param.Description,
            "schema": map[string]interface{}{
                "type":    param.Type,
                "example": param.Example,
            },
        }
    }

    return result
}

// convertResponses 转换响应信息
func (g *APIDocGenerator) convertResponses(responses map[int]ResponseInfo) map[string]interface{} {
    result := make(map[string]interface{})

    for code, response := range responses {
        codeStr := fmt.Sprintf("%d", code)
        result[codeStr] = map[string]interface{}{
            "description": response.Description,
            "content": map[string]interface{}{
                "application/json": map[string]interface{}{
                    "schema":  response.Schema,
                    "example": response.Example,
                },
            },
        }
    }

    return result
}

// SaveToFile 保存文档到文件
func (g *APIDocGenerator) SaveToFile(filename string) error {
    doc := g.GenerateOpenAPI()

    data, err := json.MarshalIndent(doc, "", "  ")
    if err != nil {
        return err
    }

    return ioutil.WriteFile(filename, data, 0644)
}

// 使用示例
func setupDocumentation() {
    generator := NewAPIDocGenerator()

    // 注册模型
    generator.RegisterModel("User", User{})
    generator.RegisterModel("CreateUserRequest", CreateUserRequest{})
    generator.RegisterModel("ErrorResponse", ErrorResponse{})

    // 注册路由
    generator.RegisterRoute(RouteInfo{
        Method:      "GET",
        Path:        "/api/v1/users",
        Handler:     "getUsers",
        Summary:     "Get users list",
        Description: "Retrieve a list of users with pagination",
        Tags:        []string{"users"},
        Parameters: []ParameterInfo{
            {Name: "page", In: "query", Type: "integer", Required: false, Description: "Page number"},
            {Name: "per_page", In: "query", Type: "integer", Required: false, Description: "Items per page"},
        },
        Responses: map[int]ResponseInfo{
            200: {Description: "Success", Schema: map[string]interface{}{"$ref": "#/components/schemas/User"}},
            400: {Description: "Bad Request", Schema: map[string]interface{}{"$ref": "#/components/schemas/ErrorResponse"}},
        },
    })

    // 生成并保存文档
    if err := generator.SaveToFile("api-docs.json"); err != nil {
        log.Printf("Failed to save documentation: %v", err)
    }
}

交互式文档 #

Swagger UI 自定义 #

// 自定义 Swagger UI
func customSwaggerHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        html := `
<!DOCTYPE html>
<html>
<head>
    <title>API Documentation</title>
    <link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/swagger-ui.css" />
    <style>
        .swagger-ui .topbar { display: none; }
        .swagger-ui .info { margin: 20px 0; }
        .swagger-ui .info .title { color: #3b4151; }
    </style>
</head>
<body>
    <div id="swagger-ui"></div>
    <script src="https://unpkg.com/[email protected]/swagger-ui-bundle.js"></script>
    <script>
        SwaggerUIBundle({
            url: '/docs/swagger.json',
            dom_id: '#swagger-ui',
            presets: [
                SwaggerUIBundle.presets.apis,
                SwaggerUIBundle.presets.standalone
            ],
            layout: "BaseLayout",
            deepLinking: true,
            showExtensions: true,
            showCommonExtensions: true,
            defaultModelsExpandDepth: 2,
            defaultModelExpandDepth: 2,
            docExpansion: "list",
            filter: true,
            tryItOutEnabled: true,
            requestInterceptor: function(request) {
                // 添加认证头
                const token = localStorage.getItem('api_token');
                if (token) {
                    request.headers['Authorization'] = 'Bearer ' + token;
                }
                return request;
            }
        });
    </script>
</body>
</html>`

        c.Header("Content-Type", "text/html")
        c.String(http.StatusOK, html)
    }
}

Redoc 集成 #

func redocHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        html := `
<!DOCTYPE html>
<html>
<head>
    <title>API Documentation - ReDoc</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
    <style>
        body { margin: 0; padding: 0; }
    </style>
</head>
<body>
    <redoc spec-url='/docs/swagger.json'></redoc>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/bundles/redoc.standalone.js"></script>
</body>
</html>`

        c.Header("Content-Type", "text/html")
        c.String(http.StatusOK, html)
    }
}

文档版本管理 #

// 多版本文档管理
type DocumentationManager struct {
    versions map[string]*APIDocGenerator
}

func NewDocumentationManager() *DocumentationManager {
    return &DocumentationManager{
        versions: make(map[string]*APIDocGenerator),
    }
}

func (dm *DocumentationManager) RegisterVersion(version string, generator *APIDocGenerator) {
    dm.versions[version] = generator
}

func (dm *DocumentationManager) GetVersions() []string {
    versions := make([]string, 0, len(dm.versions))
    for version := range dm.versions {
        versions = append(versions, version)
    }
    return versions
}

func (dm *DocumentationManager) GenerateVersionedDocs(version string) (map[string]interface{}, error) {
    generator, exists := dm.versions[version]
    if !exists {
        return nil, fmt.Errorf("version %s not found", version)
    }

    return generator.GenerateOpenAPI(), nil
}

// 版本化文档端点
func setupVersionedDocs(r *gin.Engine, dm *DocumentationManager) {
    docs := r.Group("/docs")
    {
        // 文档版本列表
        docs.GET("/versions", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{
                "versions": dm.GetVersions(),
            })
        })

        // 特定版本的文档
        docs.GET("/:version/swagger.json", func(c *gin.Context) {
            version := c.Param("version")
            doc, err := dm.GenerateVersionedDocs(version)
            if err != nil {
                c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
                return
            }
            c.JSON(http.StatusOK, doc)
        })

        // 版本化 Swagger UI
        docs.GET("/:version", func(c *gin.Context) {
            version := c.Param("version")
            html := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
    <title>API Documentation - %s</title>
    <link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/swagger-ui.css" />
</head>
<body>
    <div id="swagger-ui"></div>
    <script src="https://unpkg.com/[email protected]/swagger-ui-bundle.js"></script>
    <script>
        SwaggerUIBundle({
            url: '/docs/%s/swagger.json',
            dom_id: '#swagger-ui',
            presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.presets.standalone],
            layout: "BaseLayout"
        });
    </script>
</body>
</html>`, version, version)

            c.Header("Content-Type", "text/html")
            c.String(http.StatusOK, html)
        })
    }
}

通过这些工具和技术,你可以创建出专业、完整、易于维护的 API 文档,大大提升 API 的可用性和开发者体验。良好的文档不仅能帮助其他开发者快速理解和使用你的 API,也能在团队协作中发挥重要作用。