feat: 实现物联网卡独立管理和批量导入功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m42s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m42s
新增物联网卡独立管理模块,支持单卡查询、批量导入和状态管理。主要变更包括: 功能特性: - 新增物联网卡 CRUD 接口(查询、分页列表、删除) - 支持 CSV/Excel 批量导入物联网卡 - 实现异步导入任务处理和进度跟踪 - 新增 ICCID 号码格式校验器(支持 Luhn 算法) - 新增 CSV 文件解析工具(支持编码检测和错误处理) 数据库变更: - 移除 iot_card 和 device 表的 owner_id/owner_type 字段 - 新增 iot_card_import_task 导入任务表 - 为导入任务添加运营商类型字段 测试覆盖: - 新增 IoT 卡 Store 层单元测试 - 新增 IoT 卡导入任务单元测试 - 新增 IoT 卡集成测试(包含导入流程测试) - 新增 CSV 工具和 ICCID 校验器测试 文档更新: - 更新 OpenAPI 文档(新增 7 个 IoT 卡接口) - 归档 OpenSpec 变更提案 - 更新 API 文档规范和生成器指南 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
# API 文档生成规范
|
||||
|
||||
**版本**: 1.0
|
||||
**最后更新**: 2026-01-21
|
||||
**版本**: 1.1
|
||||
**最后更新**: 2026-01-24
|
||||
|
||||
## 目录
|
||||
|
||||
- [核心原则](#核心原则)
|
||||
- [新增 Handler 检查清单](#新增-handler-检查清单)
|
||||
- [路由注册规范](#路由注册规范)
|
||||
- [DTO 规范](#dto-规范)
|
||||
- [文档生成流程](#文档生成流程)
|
||||
@@ -42,6 +43,102 @@ router.Post("/path", handler.Method)
|
||||
|
||||
---
|
||||
|
||||
## 新增 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. 基本结构
|
||||
@@ -265,9 +362,26 @@ handlers := &bootstrap.Handlers{
|
||||
|
||||
### Q1: 为什么我的接口没有出现在文档中?
|
||||
|
||||
**检查清单**:
|
||||
> ⚠️ **最常见原因**: 忘记在 `cmd/api/docs.go` 和 `cmd/gendocs/main.go` 中添加新 Handler!
|
||||
|
||||
1. ✅ 是否使用了 `Register()` 函数?
|
||||
**检查清单(按优先级排序)**:
|
||||
|
||||
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)
|
||||
@@ -276,22 +390,21 @@ handlers := &bootstrap.Handlers{
|
||||
Register(router, doc, basePath, "POST", "/path", handler.Method, RouteSpec{...})
|
||||
```
|
||||
|
||||
2. ✅ 路由注册函数是否接收了 `doc *openapi.Generator` 参数?
|
||||
3. ✅ 路由注册函数是否接收了 `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()`
|
||||
|
||||
**快速定位问题**:
|
||||
```bash
|
||||
# 检查 Handler 是否在文档生成器中注册
|
||||
grep "NewXxxHandler" cmd/api/docs.go cmd/gendocs/main.go
|
||||
```
|
||||
|
||||
### Q2: 文档生成时报错 "undefined path parameter"?
|
||||
|
||||
**原因**:路径参数(如 `/:id`)的 DTO 缺少对应字段。
|
||||
@@ -335,23 +448,51 @@ Register(router, doc, basePath, "PUT", "/:id", handler.Update, RouteSpec{
|
||||
|
||||
### Q4: 如何为新模块添加路由?
|
||||
|
||||
**步骤**:
|
||||
**完整步骤**(共 6 步):
|
||||
|
||||
1. 创建路由文件 `internal/routes/xxx.go`
|
||||
2. 定义注册函数:
|
||||
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() 注册路由
|
||||
}
|
||||
```
|
||||
3. 在 `internal/routes/admin.go` 中调用:
|
||||
|
||||
5. **调用路由注册**:`internal/routes/admin.go`
|
||||
```go
|
||||
if handlers.Xxx != nil {
|
||||
registerXxxRoutes(authGroup, handlers.Xxx, doc, basePath)
|
||||
}
|
||||
```
|
||||
4. 在 `cmd/gendocs/main.go` 中添加 Handler
|
||||
5. 重新生成文档验证
|
||||
|
||||
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: 如何调试文档生成?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user