feat: 实现物联网卡独立管理和批量导入功能
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:
2026-01-24 11:03:43 +08:00
parent 6821e5abcf
commit a924e63e68
49 changed files with 7983 additions and 284 deletions

View File

@@ -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: 如何调试文档生成?