# API 文档生成规范 **版本**: 1.0 **最后更新**: 2026-01-21 ## 目录 - [核心原则](#核心原则) - [路由注册规范](#路由注册规范) - [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 结构体自动生成准确的字段定义 --- ## 路由注册规范 ### 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 // 操作摘要(中文,简短) Input interface{} // 请求参数 DTO Output interface{} // 响应结果 DTO Tags []string // 分类标签(用于文档分组) Auth bool // 是否需要认证 } ``` ### 4. 完整示例 ```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: 为什么我的接口没有出现在文档中? **检查清单**: 1. ✅ 是否使用了 `Register()` 函数? ```go // ❌ 错误 router.Post("/path", handler.Method) // ✅ 正确 Register(router, doc, basePath, "POST", "/path", handler.Method, RouteSpec{...}) ``` 2. ✅ 路由注册函数是否接收了 `doc *openapi.Generator` 参数? ```go func registerXxxRoutes(router fiber.Router, handler *admin.XxxHandler, doc *openapi.Generator, basePath string) ``` 3. ✅ 是否在 `cmd/gendocs/main.go` 中创建了 Handler? ```go handlers := &bootstrap.Handlers{ Xxx: admin.NewXxxHandler(nil), } ``` 4. ✅ 是否调用了路由注册函数? - 检查 `internal/routes/admin.go` 中是否调用了 `registerXxxRoutes()` - 检查 `internal/routes/routes.go` 是否调用了 `RegisterAdminRoutes()` ### 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: 如何为新模块添加路由? **步骤**: 1. 创建路由文件 `internal/routes/xxx.go` 2. 定义注册函数: ```go func registerXxxRoutes(api fiber.Router, h *admin.XxxHandler, doc *openapi.Generator, basePath string) { // 使用 Register() 注册路由 } ``` 3. 在 `internal/routes/admin.go` 中调用: ```go if handlers.Xxx != nil { registerXxxRoutes(authGroup, handlers.Xxx, doc, basePath) } ``` 4. 在 `cmd/gendocs/main.go` 中添加 Handler 5. 重新生成文档验证 ### 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)