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,也能在团队协作中发挥重要作用。