# API 文档生成规范 **版本**: 1.1 **最后更新**: 2026-01-24 ## 目录 - [核心原则](#核心原则) - [新增 Handler 检查清单](#新增-handler-检查清单) - [路由注册规范](#路由注册规范) - [DTO 规范](#dto-规范) - [文档生成流程](#文档生成流程) - [常见问题](#常见问题) --- ## 核心原则 ### ✅ 强制要求 **所有 HTTP 接口必须使用统一的 `Register()` 函数注册,以确保自动加入 OpenAPI 文档生成。** ```go // ✅ 正确:使用 Register() 函数 Register(router, doc, basePath, "POST", "/path", handler.Method, RouteSpec{ Summary: "操作说明", Tags: []string{"分类"}, Input: new(model.RequestDTO), Output: new(model.ResponseDTO), Auth: true, }) // ❌ 错误:直接注册(不会生成文档) router.Post("/path", handler.Method) ``` ### 为什么这样做? 1. **文档自动同步**:代码即文档,避免文档与实现脱节 2. **前后端协作**:生成标准 OpenAPI 规范,前端可直接导入 3. **API 测试**:Swagger UI / Postman 可直接使用 4. **类型安全**:通过 DTO 结构体自动生成准确的字段定义 --- ## 新增 Handler 检查清单 > ⚠️ **重要**: 新增 Handler 时,必须完成以下所有步骤,否则接口不会出现在 OpenAPI 文档中! ### 必须完成的 4 个步骤 | 步骤 | 文件位置 | 操作 | |------|---------|------| | 1️⃣ | `internal/bootstrap/types.go` | 在 `Handlers` 结构体中添加新 Handler 字段 | | 2️⃣ | `internal/bootstrap/handlers.go` | 实例化新 Handler | | 3️⃣ | `internal/routes/admin.go` | 调用路由注册函数 | | 4️⃣ | `cmd/api/docs.go` 和 `cmd/gendocs/main.go` | **添加 Handler 到文档生成器** | ### 详细说明 #### 步骤 1: 添加 Handler 字段 ```go // internal/bootstrap/types.go type Handlers struct { // ... 现有 Handler IotCard *admin.IotCardHandler // 新增 IotCardImport *admin.IotCardImportHandler // 新增 } ``` #### 步骤 2: 实例化 Handler ```go // internal/bootstrap/handlers.go func initHandlers(services *Services) *Handlers { return &Handlers{ // ... 现有 Handler IotCard: admin.NewIotCardHandler(services.IotCard), IotCardImport: admin.NewIotCardImportHandler(services.IotCardImport), } } ``` #### 步骤 3: 调用路由注册 ```go // internal/routes/admin.go func RegisterAdminRoutes(...) { // ... 现有路由 if handlers.IotCard != nil { registerIotCardRoutes(authGroup, handlers.IotCard, handlers.IotCardImport, doc, basePath) } } ``` #### 步骤 4: 更新文档生成器 ⚠️ 最容易遗漏! **必须同时更新两个文件:** ```go // cmd/api/docs.go func generateOpenAPIDocs(outputPath string, logger *zap.Logger) { handlers := &bootstrap.Handlers{ // ... 现有 Handler IotCard: admin.NewIotCardHandler(nil), // 添加 IotCardImport: admin.NewIotCardImportHandler(nil), // 添加 } // ... } ``` ```go // cmd/gendocs/main.go func generateAdminDocs(outputPath string) error { handlers := &bootstrap.Handlers{ // ... 现有 Handler IotCard: admin.NewIotCardHandler(nil), // 添加 IotCardImport: admin.NewIotCardImportHandler(nil), // 添加 } // ... } ``` ### 验证检查 完成上述步骤后,运行以下命令验证: ```bash # 1. 编译检查 go build ./... # 2. 重新生成文档 go run cmd/gendocs/main.go # 3. 验证接口是否出现在文档中 grep "你的接口路径" docs/admin-openapi.yaml ``` --- ## 路由注册规范 ### 1. 基本结构 所有路由注册必须在 `internal/routes/` 目录中完成: ``` internal/routes/ ├── registry.go # Register() 函数定义 ├── routes.go # 总入口 ├── admin.go # Admin 域路由 ├── h5.go # H5 域路由 ├── account.go # 账号管理路由 ├── role.go # 角色管理路由 └── ... ``` ### 2. 注册函数签名 ```go func registerXxxRoutes( api fiber.Router, // Fiber 路由组 h *admin.XxxHandler, // Handler 实例 doc *openapi.Generator, // 文档生成器(可能为 nil) basePath string, // 基础路径(如 "/api/admin") ) { // 路由注册逻辑 } ``` ### 3. RouteSpec 结构 ```go type RouteSpec struct { Summary string // 操作摘要(中文,简短,一行) Description string // 详细说明,支持 Markdown 语法(可选) Input interface{} // 请求参数 DTO Output interface{} // 响应结果 DTO Tags []string // 分类标签(用于文档分组) Auth bool // 是否需要认证 } ``` ### 4. Description 字段(Markdown 说明) `Description` 字段用于添加接口的详细说明,支持 **CommonMark Markdown** 语法。Apifox 等 OpenAPI 工具会正确渲染这些 Markdown 内容。 **使用场景**: - 业务规则说明 - 请求频率限制 - 注意事项 - 错误码说明 - 数据格式说明 **示例**: ```go Register(router, doc, basePath, "POST", "/login", handler.Login, RouteSpec{ Summary: "后台登录", Description: `## 登录说明 **请求频率限制**:每分钟最多 10 次 ### 注意事项 1. 密码错误 5 次后账号将被锁定 30 分钟 2. Token 有效期为 24 小时 ### 返回码说明 | 错误码 | 说明 | |--------|------| | 1001 | 用户名或密码错误 | | 1002 | 账号已被锁定 | `, Tags: []string{"认证"}, Input: new(dto.LoginRequest), Output: new(dto.LoginResponse), Auth: false, }) ``` **支持的 Markdown 语法**: - 标题:`#`、`##`、`###` - 列表:`-`、`1.` - 表格:`| 列1 | 列2 |` - 代码:`` `code` `` 和 ` ```code block``` ` - 强调:`**粗体**`、`*斜体*` - 链接:`[文本](url)` **最佳实践**: - 保持简洁,控制在 500 字以内 - 使用结构化的 Markdown(标题、列表、表格)提高可读性 - 避免使用 HTML 标签(兼容性较差) ### 5. 完整示例 ```go func registerShopRoutes(router fiber.Router, handler *admin.ShopHandler, doc *openapi.Generator, basePath string) { shops := router.Group("/shops") groupPath := basePath + "/shops" Register(shops, doc, groupPath, "GET", "", handler.List, RouteSpec{ Summary: "店铺列表", Tags: []string{"店铺管理"}, Input: new(model.ShopListRequest), Output: new(model.ShopPageResult), Auth: true, }) Register(shops, doc, groupPath, "POST", "", handler.Create, RouteSpec{ Summary: "创建店铺", Tags: []string{"店铺管理"}, Input: new(model.CreateShopRequest), Output: new(model.ShopResponse), Auth: true, }) Register(shops, doc, groupPath, "PUT", "/:id", handler.Update, RouteSpec{ Summary: "更新店铺", Tags: []string{"店铺管理"}, Input: new(model.UpdateShopParams), // 组合参数(路径 + Body) Output: new(model.ShopResponse), Auth: true, }) Register(shops, doc, groupPath, "DELETE", "/:id", handler.Delete, RouteSpec{ Summary: "删除店铺", Tags: []string{"店铺管理"}, Input: new(model.IDReq), // 仅路径参数 Output: nil, Auth: true, }) } ``` --- ## DTO 规范 ### 1. Description 标签(必须) **所有字段必须使用 `description` 标签,禁止使用行内注释。** ```go // ❌ 错误 type CreateShopRequest struct { ShopName string `json:"shop_name" validate:"required,min=1,max=100"` // 店铺名称 } // ✅ 正确 type CreateShopRequest struct { ShopName string `json:"shop_name" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"店铺名称"` } ``` ### 2. 枚举字段规范 **必须在 `description` 中列出所有可能值(中文)。** ```go type CreateShopRequest struct { Status int `json:"status" validate:"required,oneof=0 1" required:"true" description:"状态 (0:禁用, 1:启用)"` Level int `json:"level" validate:"required,min=1,max=7" required:"true" minimum:"1" maximum:"7" description:"店铺层级 (1-7级)"` } ``` ### 3. 验证标签与 OpenAPI 标签一致 | validate 标签 | OpenAPI 标签 | 说明 | |--------------|--------------|------| | `required` | `required:"true"` | 必填字段 | | `min=N,max=M` | `minimum:"N" maximum:"M"` | 数值范围 | | `min=N,max=M` (字符串) | `minLength:"N" maxLength:"M"` | 字符串长度 | | `len=N` | `minLength:"N" maxLength:"N"` | 固定长度 | | `oneof=A B C` | `description` 中说明 | 枚举值 | ### 4. 请求参数类型标签 ```go // Query 参数 type ListRequest struct { Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"` } // Path 参数 type IDReq struct { ID uint `path:"id" description:"ID" required:"true"` } // Body 参数(默认) type CreateRequest struct { Name string `json:"name" validate:"required" required:"true" description:"名称"` } ``` ### 5. 组合参数(路径 + Body) 对于 `PUT /:id` 类型的端点,需要创建组合参数 DTO: ```go // 定义在 internal/model/common.go type UpdateShopParams struct { IDReq // 路径参数 UpdateShopRequest // Body 参数 } ``` ### 6. 分页响应规范 ```go type ShopPageResult struct { Items []ShopResponse `json:"items" description:"店铺列表"` Total int64 `json:"total" description:"总记录数"` Page int `json:"page" description:"当前页码"` Size int `json:"size" description:"每页数量"` } ``` --- ## 文档生成流程 ### 1. 自动生成 ```bash # 方式1:独立生成工具 go run cmd/gendocs/main.go # 方式2:启动 API 服务时自动生成 go run cmd/api/main.go ``` 生成的文档位置: - `docs/admin-openapi.yaml` - 独立生成 - `logs/openapi.yaml` - 运行时生成 ### 2. 验证文档 ```bash # 1. 检查生成的路径数量 python3 -c " import yaml with open('docs/admin-openapi.yaml', 'r', encoding='utf-8') as f: doc = yaml.safe_load(f) paths = list(doc.get('paths', {}).keys()) print(f'总路径数: {len(paths)}') for p in sorted(paths): print(f' {p}') " # 2. 在 Swagger UI 中测试 # 访问 https://editor.swagger.io/ # 粘贴 docs/admin-openapi.yaml 内容 ``` ### 3. 更新文档生成器 如果新增了 Handler,需要在 `cmd/gendocs/main.go` 中添加: ```go // 3. 创建 Handler(使用 nil 依赖,因为只需要路由结构) newHandler := admin.NewXxxHandler(nil) handlers := &bootstrap.Handlers{ // ... 其他 Handler Xxx: newHandler, // 添加新 Handler } ``` --- ## 常见问题 ### Q1: 为什么我的接口没有出现在文档中? > ⚠️ **最常见原因**: 忘记在 `cmd/api/docs.go` 和 `cmd/gendocs/main.go` 中添加新 Handler! **检查清单(按优先级排序)**: 1. ✅ **【最常遗漏】** 是否在文档生成器中添加了 Handler? 必须同时检查两个文件: ```go // cmd/api/docs.go handlers := &bootstrap.Handlers{ Xxx: admin.NewXxxHandler(nil), // 是否添加? } // cmd/gendocs/main.go handlers := &bootstrap.Handlers{ Xxx: admin.NewXxxHandler(nil), // 是否添加? } ``` 2. ✅ 是否使用了 `Register()` 函数? ```go // ❌ 错误 router.Post("/path", handler.Method) // ✅ 正确 Register(router, doc, basePath, "POST", "/path", handler.Method, RouteSpec{...}) ``` 3. ✅ 路由注册函数是否接收了 `doc *openapi.Generator` 参数? ```go func registerXxxRoutes(router fiber.Router, handler *admin.XxxHandler, doc *openapi.Generator, basePath string) ``` 4. ✅ 是否调用了路由注册函数? - 检查 `internal/routes/admin.go` 中是否调用了 `registerXxxRoutes()` - 检查 `internal/routes/routes.go` 是否调用了 `RegisterAdminRoutes()` **快速定位问题**: ```bash # 检查 Handler 是否在文档生成器中注册 grep "NewXxxHandler" cmd/api/docs.go cmd/gendocs/main.go ``` ### Q2: 文档生成时报错 "undefined path parameter"? **原因**:路径参数(如 `/:id`)的 DTO 缺少对应字段。 **解决方案**:创建组合参数 DTO ```go // ❌ 错误:直接使用 Body DTO Register(router, doc, basePath, "PUT", "/:id", handler.Update, RouteSpec{ Input: new(model.UpdateShopRequest), // 缺少 id 参数 }) // ✅ 正确:使用组合参数 type UpdateShopParams struct { IDReq // 包含 id 参数 UpdateShopRequest // 包含 Body 参数 } Register(router, doc, basePath, "PUT", "/:id", handler.Update, RouteSpec{ Input: new(model.UpdateShopParams), }) ``` ### Q3: DTO 字段在文档中没有描述? **检查**: 1. ✅ 是否添加了 `description` 标签? ```go ShopName string `json:"shop_name" description:"店铺名称"` ``` 2. ✅ 是否使用了行内注释(不会被识别)? ```go // ❌ 错误 ShopName string `json:"shop_name"` // 店铺名称 // ✅ 正确 ShopName string `json:"shop_name" description:"店铺名称"` ``` ### Q4: 如何为新模块添加路由? **完整步骤**(共 6 步): 1. **创建 Handler**:`internal/handler/admin/xxx.go` 2. **添加到 Handlers 结构体**:`internal/bootstrap/types.go` ```go type Handlers struct { Xxx *admin.XxxHandler } ``` 3. **实例化 Handler**:`internal/bootstrap/handlers.go` ```go Xxx: admin.NewXxxHandler(services.Xxx), ``` 4. **创建路由文件**:`internal/routes/xxx.go` ```go func registerXxxRoutes(api fiber.Router, h *admin.XxxHandler, doc *openapi.Generator, basePath string) { // 使用 Register() 注册路由 } ``` 5. **调用路由注册**:`internal/routes/admin.go` ```go if handlers.Xxx != nil { registerXxxRoutes(authGroup, handlers.Xxx, doc, basePath) } ``` 6. **更新文档生成器**(⚠️ 两个文件都要改): - `cmd/api/docs.go` - `cmd/gendocs/main.go` ```go handlers := &bootstrap.Handlers{ Xxx: admin.NewXxxHandler(nil), } ``` 7. **验证**: ```bash go build ./... go run cmd/gendocs/main.go grep "/api/admin/xxx" docs/admin-openapi.yaml ``` ### Q5: 如何调试文档生成? ```bash # 1. 查看生成的 YAML 文件 cat docs/admin-openapi.yaml # 2. 验证 YAML 格式 python3 -c " import yaml with open('docs/admin-openapi.yaml', 'r', encoding='utf-8') as f: doc = yaml.safe_load(f) print('YAML 格式正确') " # 3. 检查特定路径 python3 -c " import yaml with open('docs/admin-openapi.yaml', 'r', encoding='utf-8') as f: doc = yaml.safe_load(f) path = '/api/admin/shops' if path in doc['paths']: import json print(json.dumps(doc['paths'][path], indent=2, ensure_ascii=False)) " ``` --- ## 参考资料 - [OpenAPI 3.0 规范](https://swagger.io/specification/) - [Swagger UI](https://swagger.io/tools/swagger-ui/) - [项目 DTO 规范](../AGENTS.md#dto-规范重要) - [已有实现示例](../internal/routes/account.go)