# Gateway API 集成工作计划 ## TL;DR > **Quick Summary**: 封装 Gateway API 为统一的能力模块,提供类型安全的接口、统一的错误处理、配置管理和自动重试机制 > > **Deliverables**: > - Gateway 客户端封装(`internal/gateway/` 包) > - 14 个 API 接口(流量卡 7 个 + 设备 7 个) > - AES-128-ECB 加密 + MD5 签名机制 > - 配置集成(GatewayConfig) > - 错误码定义(1110-1119) > - 依赖注入(Bootstrap) > - 完整测试覆盖(单元测试 + 集成测试) > > **Estimated Effort**: Medium(预计 2-3 小时) > **Parallel Execution**: YES - 5 waves > **Critical Path**: Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 --- ## Context ### Original Request 用户需要实施 gateway-integration 提案,该提案包含 61 个任务,分为 5 个 Phase。核心目标是封装 Gateway API 为统一的能力模块,提供类型安全的接口、统一的错误处理和配置管理。 ### Interview Summary **Key Discussions**: - **Gateway 测试环境配置**: 已提供 BaseURL、AppID、AppSecret、测试 ICCID - **加密/签名算法**: 基于 Apifox 文档(AES-128-ECB + MD5,遗留系统) - **Service 集成范围**: 仅 iot_card Service,新增 SyncCardStatus 方法作为示例 - **批量查询接口**: 完全不需要(删除 Task 20) - **错误处理策略**: 需要自动重试(3 次,指数退避:1s, 2s, 4s) **Research Findings**: - **项目现有模式**: SMS 客户端(pkg/sms/)可作为参考(接口 + 依赖注入) - **测试模式**: testutils(全局单例 DB/Redis + 事务回滚) - **错误处理**: 统一错误码系统(pkg/errors/) - **配置管理**: Viper 加载,支持环境变量覆盖(JUNHONG_ 前缀) - **安全警告**: ⚠️ AES-128-ECB 和 MD5 存在密码学漏洞(遗留系统,需添加注释警告) ### 自我审查 **识别的潜在风险**: 1. **加密算法实现风险**: AES-128-ECB + PKCS5Padding 实现容易出错 - **缓解措施**: 使用 Apifox 文档中的 Go 示例代码,添加单元测试验证加密/解密 2. **签名计算风险**: 参数排序和拼接逻辑可能与 Gateway 不一致 - **缓解措施**: 严格按照 Apifox 文档实现,集成测试验证真实签名 3. **重试逻辑风险**: 不当重试可能导致重复操作(如停机/复机) - **缓解措施**: 只重试网络错误和 5xx 错误,业务错误(4xx)不重试 4. **测试覆盖风险**: 设备 API 无法真实测试 - **缓解措施**: 设备 API 只实现方法签名,集成测试跳过 **明确的边界和约束**: - ✅ **必须包含**: 流量卡 7 个接口完整实现和测试 - ❌ **必须排除**: 批量查询接口(BatchQuery) - ⚠️ **可选实现**: 设备 API 只需方法签名(测试跳过) - 🔒 **不可变更**: 加密/签名算法(遗留系统约束) --- ## Work Objectives ### Core Objective 封装 Gateway API 为统一的能力模块,提供类型安全的接口、统一的错误处理、配置管理和自动重试机制,支持流量卡和设备管理的 14 个 API 接口。 ### Concrete Deliverables - `internal/gateway/client.go` - Gateway 客户端核心实现 - `internal/gateway/crypto.go` - AES-128-ECB 加密 + MD5 签名工具 - `internal/gateway/models.go` - 请求/响应 DTO 定义 - `internal/gateway/flow_card.go` - 流量卡 API(7 个接口) - `internal/gateway/device.go` - 设备 API(7 个接口) - `internal/gateway/client_test.go` - 单元测试 + 集成测试 - `pkg/config/config.go` - GatewayConfig 配置结构 - `pkg/errors/codes.go` - Gateway 错误码(1110-1119) - `internal/bootstrap/bootstrap.go` - Bootstrap 初始化 Gateway 客户端 - `internal/service/iot_card/service.go` - SyncCardStatus 集成示例 - `docs/gateway-client-usage.md` - 使用文档 ### Definition of Done - [ ] 所有 14 个 Gateway API 接口成功封装(流量卡 7 个 + 设备 7 个) - [ ] 加密/签名通过单元测试验证(与 Apifox 文档一致) - [ ] 集成测试验证真实 Gateway API 调用(测试 ICCID: `8986062580006141710`) - [ ] 重试逻辑测试通过(3 次重试,指数退避) - [ ] 配置通过环境变量成功加载(JUNHONG_GATEWAY_*) - [ ] 依赖注入到 iot_card Service 成功(SyncCardStatus 方法) - [ ] 单元测试覆盖率 ≥ 90%(核心逻辑) - [ ] 无 LSP 错误,编译通过 - [ ] 符合项目代码规范(中文注释、Go 命名规范) - [ ] 文档完整(使用示例、错误码说明) ### Must Have - AES-128-ECB 加密(PKCS5Padding + Base64) - MD5 签名(大写输出) - 自动重试逻辑(3 次,指数退避:1s, 2s, 4s) - 流量卡 7 个 API 完整实现 - 真实 Gateway 环境集成测试 - 错误码定义(1110-1119) - 配置环境变量支持 ### Must NOT Have (Guardrails) - ❌ 批量查询接口(BatchQuery)- 用户明确不需要 - ❌ 设备 API 的真实测试 - 无测试环境,只需方法签名 - ❌ 使用第三方重试库 - 项目无依赖,使用简单循环 - ❌ 降级策略 - 用户不需要,失败直接返回错误 - ❌ 使用 AES-GCM 或 HMAC-SHA256 - 遗留系统约束,必须使用 ECB + MD5 - ❌ AI-Slop 模式: - 过度抽象(不需要工厂模式、策略模式) - 过度验证(不需要 15 个参数校验) - 过度文档(不需要 JSDoc everywhere) --- ## Verification Strategy ### Test Decision - **Infrastructure exists**: YES(项目已有 testutils) - **User wants tests**: TDD(测试驱动开发) - **Framework**: Go 标准库(testing + httptest) ### TDD Workflow 每个 TODO 遵循 **RED-GREEN-REFACTOR**: **Task Structure:** 1. **RED**: 编写失败测试 - 测试文件: `internal/gateway/*_test.go` - 测试命令: `go test -v ./internal/gateway/...` - 预期: FAIL(测试存在,实现不存在) 2. **GREEN**: 最小实现使测试通过 - 实现代码: `internal/gateway/*.go` - 测试命令: `go test -v ./internal/gateway/...` - 预期: PASS 3. **REFACTOR**: 优化代码保持绿色 - 重构: 提取常量、优化逻辑、添加注释 - 测试命令: `go test -v ./internal/gateway/...` - 预期: PASS(仍然通过) ### 集成测试环境 **真实 Gateway 配置**: ```bash export JUNHONG_GATEWAY_BASEURL="https://lplan.whjhft.com/openapi" export JUNHONG_GATEWAY_APPID="60bgt1X8i7AvXqkd" export JUNHONG_GATEWAY_APPSECRET="BZeQttaZQt0i73moF" ``` **测试数据**: - 测试 ICCID: `8986062580006141710` - 设备测试: 跳过(无测试环境) **集成测试验证**: ```bash # 运行集成测试(需先加载环境变量) source .env.local && go test -v ./internal/gateway/... -run TestIntegration ``` --- ## Execution Strategy ### Parallel Execution Waves ``` Wave 1 (Start Immediately - 基础结构): ├── Task 1.1: 创建 Gateway 包目录结构 └── Task 3.1: 添加 Gateway 配置结构 Wave 2 (After Wave 1 - 加密和模型): ├── Task 1.2: 实现加密/签名工具函数(crypto.go) ├── Task 1.3: 定义 DTO 结构(models.go) └── Task 3.2: 添加 Gateway 错误码 Wave 3 (After Wave 2 - 客户端核心): ├── Task 1.4: 实现 Gateway 客户端基础结构(client.go) └── Task 1.5: 实现 HTTP 重试逻辑(doRequest 方法) Wave 4 (After Wave 3 - API 接口实现): ├── Task 2.1: 实现流量卡 API(flow_card.go,7 个接口) └── Task 2.2: 实现设备 API(device.go,7 个接口签名) Wave 5 (After Wave 4 - 集成和测试): ├── Task 2.3: 添加单元测试(client_test.go) ├── Task 4.1: Bootstrap 初始化 Gateway 客户端 ├── Task 4.2: Service 层集成示例(SyncCardStatus) ├── Task 5.1: 编写集成测试 └── Task 5.2: 更新文档 Critical Path: Wave 1 → Wave 2 → Wave 3 → Wave 4 → Wave 5 Parallel Speedup: ~30% faster than sequential ``` ### Dependency Matrix | Task | Depends On | Blocks | Can Parallelize With | |------|------------|--------|---------------------| | 1.1 | None | All | 3.1 | | 1.2 | 1.1 | 1.4, 2.1, 2.2 | 1.3, 3.2 | | 1.3 | 1.1 | 2.1, 2.2 | 1.2, 3.2 | | 1.4 | 1.1, 1.2, 1.3 | 1.5, 2.1, 2.2 | None | | 1.5 | 1.4 | 2.1, 2.2 | None | | 2.1 | 1.4, 1.5, 1.2, 1.3 | 2.3, 5.1 | 2.2 | | 2.2 | 1.4, 1.5, 1.2, 1.3 | 2.3, 5.1 | 2.1 | | 2.3 | 2.1, 2.2 | None | 4.1, 4.2 | | 3.1 | None | 4.1 | 1.1 | | 3.2 | 1.1 | 4.1 | 1.2, 1.3 | | 4.1 | 1.4, 3.1, 3.2 | 4.2 | 2.3 | | 4.2 | 4.1 | None | 2.3, 5.1 | | 5.1 | 2.1, 2.2, 4.1 | None | 2.3, 4.2, 5.2 | | 5.2 | All | None | 5.1 | --- ## TODOs ### Phase 1: 基础结构搭建 - [ ] **1.1 创建 Gateway 包目录结构** **What to do**: - 创建目录 `internal/gateway/` - 创建占位文件: - `client.go` - Gateway 客户端核心实现 - `crypto.go` - 加密/签名工具 - `models.go` - 请求/响应 DTO - `flow_card.go` - 流量卡 API - `device.go` - 设备 API - `client_test.go` - 测试文件 - 每个文件添加 package 声明和中文注释说明用途 **Must NOT do**: - 不添加任何实现代码(只创建文件结构) - 不创建 batch_query.go(批量查询不需要) **Recommended Agent Profile**: - **Category**: `quick` - Reason: 简单的目录和文件创建任务,无复杂逻辑 - **Skills**: 无 - Reason: 不涉及特定规范,只是文件结构创建 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1(与 Task 3.1 并行) - **Blocks**: 所有后续任务(目录结构是基础) - **Blocked By**: None(可立即开始) **References**: - `pkg/sms/client.go` - 参考 SMS 客户端的包结构模式 - `pkg/sms/http_client.go` - 参考 HTTP 客户端文件组织方式 - `internal/service/iot_card/service.go` - 参考 Service 包的文件组织 **Acceptance Criteria**: - [ ] 目录存在: `internal/gateway/` - [ ] 文件存在: `client.go`, `crypto.go`, `models.go`, `flow_card.go`, `device.go`, `client_test.go` - [ ] 每个文件包含 `package gateway` 声明 - [ ] 每个文件顶部有中文注释说明用途 - [ ] 验证命令: `ls -la internal/gateway/` → 显示所有文件 - [ ] 编译通过: `go build ./internal/gateway/...` → 无错误 **Commit**: YES - Message: `feat(gateway): 创建 Gateway 包目录结构` - Files: `internal/gateway/*.go` - Pre-commit: `go build ./internal/gateway/...` --- - [ ] **1.2 实现加密/签名工具函数** **What to do**: - 在 `internal/gateway/crypto.go` 中实现加密函数: - `aesEncrypt(plaintext, appSecret string) (string, error)` - AES-128-ECB + PKCS5Padding + Base64 - 步骤: MD5(appSecret) → 16字节密钥 → PKCS5填充 → ECB加密 → Base64编码 - 实现签名函数: - `generateSign(params map[string]interface{}, appSecret string) string` - MD5签名(大写) - 步骤: 排序参数键 → 拼接 key=value&... → 追加 &key=appSecret → MD5 → 转大写 - 实现解密函数(用于测试验证): - `aesDecrypt(ciphertext, appSecret string) (string, error)` - 解密验证 - 添加详细中文注释和安全警告: ```go // ⚠️ 安全警告: AES-128-ECB 模式已被证明存在密码学漏洞(相同明文产生相同密文,泄漏模式) // 仅用于对接遗留系统的 Gateway API,不应用于新系统设计 // 推荐替代方案: AES-256-GCM(提供认证加密) ``` **Must NOT do**: - 不使用第三方加密库(使用标准库 crypto/aes) - 不实现 AES-GCM 或其他模式(遗留系统约束) - 不跳过安全警告注释 **Recommended Agent Profile**: - **Category**: `ultrabrain` - Reason: 加密算法实现需要精确逻辑,容易出错 - **Skills**: 无 - Reason: 加密实现不涉及项目特定规范 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 2(与 Task 1.3, 3.2 并行) - **Blocks**: Task 1.4, 2.1, 2.2(客户端和 API 依赖加密) - **Blocked By**: Task 1.1(需要文件结构存在) **References**: - **Apifox 文档**: `https://omp5mq28pq.apifox.cn/7819761m0` - 官方 Go 语言加密/签名示例 - **Librarian 研究**: AES-128-ECB 实现模式(手动 ECB 循环,因为标准库无 ECB) - `crypto/aes` - AES 加密标准库 - `crypto/md5` - MD5 哈希标准库 - `encoding/base64` - Base64 编码标准库 **Acceptance Criteria**: **TDD - RED Phase**: - [ ] 测试文件创建: `internal/gateway/crypto_test.go` - [ ] 编写失败测试: ```go func TestAESEncrypt(t *testing.T) { plaintext := "test" appSecret := "BZeQttaZQt0i73moF" encrypted, err := aesEncrypt(plaintext, appSecret) require.NoError(t, err) assert.NotEmpty(t, encrypted) // 验证可解密 decrypted, err := aesDecrypt(encrypted, appSecret) require.NoError(t, err) assert.Equal(t, plaintext, decrypted) } func TestGenerateSign(t *testing.T) { params := map[string]interface{}{ "appId": "60bgt1X8i7AvXqkd", "timestamp": 1704067200, "data": "encrypted_data", } appSecret := "BZeQttaZQt0i73moF" sign := generateSign(params, appSecret) assert.NotEmpty(t, sign) assert.Regexp(t, "^[A-F0-9]{32}$", sign) // 32位大写MD5 } ``` - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestAES` → FAIL(未实现) **TDD - GREEN Phase**: - [ ] 实现 `aesEncrypt` 函数(参考 Apifox 文档示例) - [ ] 实现 `aesDecrypt` 函数(用于测试验证) - [ ] 实现 `generateSign` 函数(参考 Apifox 文档示例) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestAES` → PASS **TDD - REFACTOR Phase**: - [ ] 添加详细中文注释和安全警告 - [ ] 提取 PKCS5 填充逻辑为独立函数 - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestAES` → PASS(仍然通过) **Commit**: YES - Message: `feat(gateway): 实现 AES-128-ECB 加密和 MD5 签名工具` - Files: `internal/gateway/crypto.go`, `internal/gateway/crypto_test.go` - Pre-commit: `go test -v ./internal/gateway/ -run TestAES` --- - [ ] **1.3 定义请求/响应 DTO 结构** **What to do**: - 在 `internal/gateway/models.go` 中定义: - **通用请求/响应结构**: ```go // GatewayRequest Gateway API 统一请求结构 type GatewayRequest struct { AppID string `json:"appId"` // 应用ID Data string `json:"data"` // AES加密后的Base64字符串 Sign string `json:"sign"` // MD5签名(大写) Timestamp int64 `json:"timestamp"` // 时间戳(秒) } // GatewayResponse Gateway API 统一响应结构 type GatewayResponse struct { Code int `json:"code"` // 响应码(200=成功) Msg string `json:"msg"` // 响应消息 Data interface{} `json:"data"` // 业务数据(需解密) } ``` - **流量卡相关 DTO**(7 个接口): - `CardStatusReq` - 卡状态查询请求 - `CardStatusResp` - 卡状态查询响应 - `FlowQueryReq` - 流量查询请求 - `FlowUsageResp` - 流量使用响应 - `RealnameStatusReq` - 实名认证状态请求 - `RealnameStatusResp` - 实名认证状态响应 - `CardOperationReq` - 停机/复机请求(通用) - `RealmnameLinkResp` - 实名认证链接响应 - **设备相关 DTO**(7 个接口): - `DeviceInfoReq` - 设备信息请求 - `DeviceInfoResp` - 设备信息响应 - `SlotInfoReq` - 卡槽信息请求 - `SlotInfoResp` - 卡槽信息响应 - `SpeedLimitReq` - 限速设置请求 - `WiFiConfigReq` - WiFi 配置请求 - `CardSwitchReq` - 切换卡请求 - `DeviceOperationReq` - 设备操作请求(重启/恢复出厂) - 每个结构体添加: - JSON 标签(使用 sonic 序列化) - 中文注释(description) - 验证标签(validate,如 `required`) **Must NOT do**: - 不定义 BatchQuery 相关 DTO(不需要) - 不过度抽象(不需要接口或泛型) - 不添加 Getter/Setter(Go 惯用法直接访问字段) **Recommended Agent Profile**: - **Category**: `quick` - Reason: DTO 定义是结构化任务,无复杂逻辑 - **Skills**: [`dto-standards`] - `dto-standards`: DTO 规范(description 标签、枚举字段、验证标签) - Reason: 定义 DTO 结构需要遵循项目规范 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 2(与 Task 1.2, 3.2 并行) - **Blocks**: Task 2.1, 2.2(API 实现依赖 DTO) - **Blocked By**: Task 1.1(需要文件结构存在) **References**: - **Apifox 文档**: `https://omp5mq28pq.apifox.cn/7819761m0` - 完整的请求/响应结构定义 - `internal/model/dto/` - 参考现有 DTO 定义模式 - `docs/dto-standards.md` - DTO 规范(description 标签、验证标签) - **Validator 库**: `github.com/go-playground/validator/v10` - 验证标签语法 **Acceptance Criteria**: **TDD - RED Phase**: - [ ] 测试文件创建: `internal/gateway/models_test.go` - [ ] 编写失败测试: ```go func TestGatewayRequestJSON(t *testing.T) { req := GatewayRequest{ AppID: "test", Data: "encrypted", Sign: "ABC123", Timestamp: 1704067200, } data, err := json.Marshal(req) require.NoError(t, err) assert.Contains(t, string(data), "appId") } ``` - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestGatewayRequest` → FAIL **TDD - GREEN Phase**: - [ ] 定义所有 DTO 结构体(参考 Apifox 文档) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestGatewayRequest` → PASS **TDD - REFACTOR Phase**: - [ ] 添加中文注释(每个字段说明用途) - [ ] 添加验证标签(required, min, max 等) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestGatewayRequest` → PASS **Commit**: YES - Message: `feat(gateway): 定义 Gateway API 请求/响应 DTO 结构` - Files: `internal/gateway/models.go`, `internal/gateway/models_test.go` - Pre-commit: `go test -v ./internal/gateway/ -run TestGatewayRequest` --- - [ ] **1.4 实现 Gateway 客户端基础结构** **What to do**: - 在 `internal/gateway/client.go` 中定义客户端接口和结构: ```go // Client Gateway API 客户端接口(用于 mock 测试) type Client interface { // 流量卡相关接口 QueryCardStatus(ctx context.Context, iccid string) (*CardStatusResp, error) QueryFlow(ctx context.Context, iccid string) (*FlowUsageResp, error) // ... 其他接口声明 } // GatewayClient Gateway API 客户端实现 type GatewayClient struct { baseURL string // Gateway API 地址 appID string // 应用ID appSecret string // 应用密钥 httpClient *http.Client // HTTP 客户端 logger *zap.Logger // 日志记录器 } ``` - 实现构造函数: ```go func NewClient(baseURL, appID, appSecret string, logger *zap.Logger) *GatewayClient ``` - 实现配置方法: ```go func (c *GatewayClient) WithTimeout(timeout time.Duration) *GatewayClient ``` - **不实现 doRequest 方法**(留给 Task 1.5) **Must NOT do**: - 不实现具体的 API 方法(留给 Task 2.1, 2.2) - 不实现重试逻辑(留给 Task 1.5) - 不使用 Getter/Setter 模式(Go 惯用法) **Recommended Agent Profile**: - **Category**: `ultrabrain` - Reason: 客户端架构设计需要考虑接口设计、依赖注入 - **Skills**: 无 - Reason: 不涉及项目特定规范,是通用的客户端设计 **Parallelization**: - **Can Run In Parallel**: NO - **Parallel Group**: Wave 3(顺序执行) - **Blocks**: Task 1.5, 2.1, 2.2(API 实现依赖客户端结构) - **Blocked By**: Task 1.1, 1.2, 1.3(需要目录、加密、DTO) **References**: - `pkg/sms/client.go` - 参考 SMS 客户端的接口 + 结构体模式 - `pkg/sms/http_client.go` - 参考 HTTP 客户端的依赖注入模式 - **Librarian 研究**: API 客户端设计模式(接口 + 依赖注入) **Acceptance Criteria**: **TDD - RED Phase**: - [ ] 测试文件更新: `internal/gateway/client_test.go` - [ ] 编写失败测试: ```go func TestNewClient(t *testing.T) { client := NewClient("https://api.example.com", "appid", "secret", zap.NewNop()) assert.NotNil(t, client) assert.Equal(t, "https://api.example.com", client.baseURL) } func TestWithTimeout(t *testing.T) { client := NewClient("https://api.example.com", "appid", "secret", zap.NewNop()) client = client.WithTimeout(5 * time.Second) assert.Equal(t, 5*time.Second, client.httpClient.Timeout) } ``` - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestNewClient` → FAIL **TDD - GREEN Phase**: - [ ] 定义 Client 接口(所有方法签名) - [ ] 定义 GatewayClient 结构体 - [ ] 实现 NewClient 构造函数 - [ ] 实现 WithTimeout 方法 - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestNewClient` → PASS **TDD - REFACTOR Phase**: - [ ] 添加详细中文注释 - [ ] 配置默认 HTTP 客户端(参考 librarian 研究的超时配置) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestNewClient` → PASS **Commit**: YES - Message: `feat(gateway): 实现 Gateway 客户端基础结构` - Files: `internal/gateway/client.go`, `internal/gateway/client_test.go` - Pre-commit: `go test -v ./internal/gateway/ -run TestNewClient` --- - [ ] **1.5 实现 HTTP 重试逻辑(doRequest 方法)** **What to do**: - 在 `internal/gateway/client.go` 中实现统一请求方法: ```go func (c *GatewayClient) doRequest(ctx context.Context, endpoint string, reqData interface{}) (*GatewayResponse, error) ``` - 请求流程: 1. 序列化请求数据(sonic) 2. AES 加密请求数据 3. 构建 GatewayRequest(appId, data, timestamp, sign) 4. **重试循环**(最多 3 次): - 发送 HTTP POST 请求 - 解析响应 - **网络错误或 5xx → 重试**(指数退避:1s, 2s, 4s) - **4xx 业务错误 → 不重试**(直接返回错误) - **超时错误 → 不重试**(context.DeadlineExceeded) 5. AES 解密响应数据 6. 返回 GatewayResponse - 错误处理: - 网络错误: 返回 `errors.Wrap(errors.CodeGatewayNetworkError, err)` - 超时错误: 返回 `errors.New(errors.CodeGatewayTimeout)` - 业务错误: 返回 `errors.New(errors.CodeGatewayBusinessError, resp.Msg)` **Must NOT do**: - 不使用第三方重试库(项目无依赖) - 不重试业务错误(4xx) - 不重试超时错误(避免雪崩) **Recommended Agent Profile**: - **Category**: `ultrabrain` - Reason: 重试逻辑复杂,需要精确的错误分类和退避算法 - **Skills**: 无 - Reason: HTTP 重试是通用逻辑,不涉及项目特定规范 **Parallelization**: - **Can Run In Parallel**: NO - **Parallel Group**: Wave 3(顺序执行,依赖 Task 1.4) - **Blocks**: Task 2.1, 2.2(API 实现依赖 doRequest) - **Blocked By**: Task 1.4(需要客户端结构存在) **References**: - **Librarian 研究**: HTTP 重试最佳实践(指数退避、错误分类) - `pkg/sms/http_client.go` - 参考 HTTP 请求模式(无重试,需自行实现) - `pkg/constants/constants.go` - 参考 DefaultRetryMax(Asynq 任务重试次数 5) - **Sonic 库**: `github.com/bytedance/sonic` - JSON 序列化 **Acceptance Criteria**: **TDD - RED Phase**: - [ ] 测试文件更新: `internal/gateway/client_test.go` - [ ] 编写失败测试: ```go func TestDoRequest_Success(t *testing.T) { // 使用 httptest.Server 模拟成功响应 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(GatewayResponse{Code: 200, Msg: "success"}) })) defer server.Close() client := NewClient(server.URL, "appid", "secret", zap.NewNop()) resp, err := client.doRequest(context.Background(), "/test", map[string]string{"iccid": "test"}) require.NoError(t, err) assert.Equal(t, 200, resp.Code) } func TestDoRequest_Retry(t *testing.T) { // 模拟前 2 次失败,第 3 次成功 attempts := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ if attempts < 3 { w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(GatewayResponse{Code: 200, Msg: "success"}) })) defer server.Close() client := NewClient(server.URL, "appid", "secret", zap.NewNop()) resp, err := client.doRequest(context.Background(), "/test", map[string]string{"iccid": "test"}) require.NoError(t, err) assert.Equal(t, 200, resp.Code) assert.Equal(t, 3, attempts) // 验证重试了 2 次 } func TestDoRequest_NoRetryOn4xx(t *testing.T) { // 4xx 错误不重试 attempts := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ w.WriteHeader(http.StatusBadRequest) })) defer server.Close() client := NewClient(server.URL, "appid", "secret", zap.NewNop()) _, err := client.doRequest(context.Background(), "/test", map[string]string{"iccid": "test"}) require.Error(t, err) assert.Equal(t, 1, attempts) // 验证没有重试 } ``` - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestDoRequest` → FAIL **TDD - GREEN Phase**: - [ ] 实现 doRequest 方法(加密、重试、解密) - [ ] 实现重试逻辑(指数退避:1s, 2s, 4s) - [ ] 实现错误分类(网络/超时/业务) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestDoRequest` → PASS **TDD - REFACTOR Phase**: - [ ] 提取重试配置为常量(maxRetries, baseDelay) - [ ] 添加详细日志(每次重试记录日志) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestDoRequest` → PASS **Commit**: YES - Message: `feat(gateway): 实现 HTTP 重试逻辑(3 次重试,指数退避)` - Files: `internal/gateway/client.go`, `internal/gateway/client_test.go` - Pre-commit: `go test -v ./internal/gateway/ -run TestDoRequest` --- ### Phase 2: API 接口封装 - [ ] **2.1 实现流量卡 API(7 个接口)** **What to do**: - 在 `internal/gateway/flow_card.go` 中实现 7 个流量卡 API: 1. **QueryCardStatus** - 查询流量卡状态(在线/离线) - 参数: iccid - 返回: 卡状态、运营商、信号强度等 2. **QueryFlow** - 查询流量使用情况 - 参数: iccid - 返回: 已用流量、剩余流量、套餐流量等 3. **QueryRealnameStatus** - 查询实名认证状态 - 参数: iccid - 返回: 是否已实名、实名信息等 4. **StopCard** - 停机(暂停流量卡) - 参数: iccid - 返回: 操作结果 5. **StartCard** - 复机(恢复流量卡) - 参数: iccid - 返回: 操作结果 6. **GetRealnameLink** - 获取实名认证跳转链接 - 参数: iccid - 返回: H5 认证链接 7. **~~BatchQuery~~** - ❌ 删除(用户明确不需要) - 每个方法: - 接收 `context.Context` 参数 - 调用 `doRequest` 统一请求方法 - 解析响应数据到对应的 Resp 结构体 - 返回 `(*XxxResp, error)` - 添加详细中文注释 **Must NOT do**: - 不实现 BatchQuery(删除) - 不在方法内重复加密/签名逻辑(使用 doRequest) - 不忽略错误处理 **Recommended Agent Profile**: - **Category**: `ultrabrain` - Reason: API 实现需要精确的请求/响应映射和错误处理 - **Skills**: 无 - Reason: API 实现是标准流程,不涉及项目特定规范 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 4(与 Task 2.2 并行) - **Blocks**: Task 2.3, 5.1(测试依赖 API 实现) - **Blocked By**: Task 1.4, 1.5, 1.2, 1.3(需要客户端、重试、加密、DTO) **References**: - **Apifox 文档**: `https://omp5mq28pq.apifox.cn/7819761m0` - 流量卡接口文档(endpoint、请求/响应格式) - `internal/gateway/client.go` - doRequest 方法(统一请求入口) - `internal/gateway/models.go` - DTO 定义(CardStatusReq, CardStatusResp 等) - `internal/gateway/crypto.go` - 加密/签名工具(已在 doRequest 中使用) **Acceptance Criteria**: **TDD - RED Phase**: - [ ] 测试文件更新: `internal/gateway/flow_card_test.go` - [ ] 编写失败测试(每个接口一个测试): ```go func TestQueryCardStatus(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 模拟 Gateway 响应 w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(GatewayResponse{ Code: 200, Msg: "success", Data: CardStatusResp{Status: "online", Operator: "移动"}, }) })) defer server.Close() client := NewClient(server.URL, "appid", "secret", zap.NewNop()) resp, err := client.QueryCardStatus(context.Background(), "8986062580006141710") require.NoError(t, err) assert.Equal(t, "online", resp.Status) } ``` - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestQuery` → FAIL **TDD - GREEN Phase**: - [ ] 实现 QueryCardStatus 方法 - [ ] 实现 QueryFlow 方法 - [ ] 实现 QueryRealnameStatus 方法 - [ ] 实现 StopCard 方法 - [ ] 实现 StartCard 方法 - [ ] 实现 GetRealnameLink 方法 - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestQuery` → PASS **TDD - REFACTOR Phase**: - [ ] 添加详细中文注释(每个方法说明用途和参数) - [ ] 提取公共逻辑(如错误码映射) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestQuery` → PASS **Commit**: YES - Message: `feat(gateway): 实现流量卡 API(6 个接口)` - Files: `internal/gateway/flow_card.go`, `internal/gateway/flow_card_test.go` - Pre-commit: `go test -v ./internal/gateway/ -run TestQuery` --- - [ ] **2.2 实现设备 API(7 个接口签名)** **What to do**: - 在 `internal/gateway/device.go` 中实现 7 个设备 API **方法签名**: 1. **GetDeviceInfo** - 获取设备信息 2. **GetSlotInfo** - 获取设备卡槽信息 3. **SetSpeedLimit** - 设置设备限速 4. **SetWiFi** - 设置设备 WiFi 5. **SwitchCard** - 设备切换卡 6. **ResetDevice** - 设备恢复出厂设置 7. **RebootDevice** - 设备重启 - 每个方法: - 接收 `context.Context` 和相应参数 - **暂时返回 "未实现" 错误**: ```go return nil, errors.New(errors.CodeGatewayNotImplemented, "设备 API 暂未实现(无测试环境)") ``` - 添加 TODO 注释说明未来需实现 - 保留完整的方法签名和 DTO 定义 **Must NOT do**: - 不实现真实的 HTTP 请求(无测试环境) - 不删除这些方法(保留接口定义) - 不在集成测试中测试设备 API **Recommended Agent Profile**: - **Category**: `quick` - Reason: 只实现方法签名,无复杂逻辑 - **Skills**: 无 - Reason: 简单的方法签名定义 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 4(与 Task 2.1 并行) - **Blocks**: Task 2.3, 5.1(测试需要所有方法存在) - **Blocked By**: Task 1.4, 1.5, 1.2, 1.3(需要客户端、重试、加密、DTO) **References**: - **Apifox 文档**: `https://omp5mq28pq.apifox.cn/7819761m0` - 设备接口文档(了解参数和返回值) - `internal/gateway/flow_card.go` - 参考流量卡 API 的方法签名模式 - `internal/gateway/models.go` - 设备相关 DTO **Acceptance Criteria**: **TDD - RED Phase**: - [ ] 测试文件创建: `internal/gateway/device_test.go` - [ ] 编写失败测试(验证方法存在且返回未实现错误): ```go func TestGetDeviceInfo_NotImplemented(t *testing.T) { client := NewClient("https://api.example.com", "appid", "secret", zap.NewNop()) _, err := client.GetDeviceInfo(context.Background(), "device-123") require.Error(t, err) assert.Contains(t, err.Error(), "未实现") } ``` - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestGetDevice` → FAIL **TDD - GREEN Phase**: - [ ] 定义所有 7 个设备 API 方法签名 - [ ] 每个方法返回 "未实现" 错误 - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestGetDevice` → PASS **TDD - REFACTOR Phase**: - [ ] 添加 TODO 注释(说明未来需实现) - [ ] 添加详细中文注释(每个方法说明用途) - [ ] 运行测试: `go test -v ./internal/gateway/ -run TestGetDevice` → PASS **Commit**: YES - Message: `feat(gateway): 实现设备 API 方法签名(7 个接口,暂未实现)` - Files: `internal/gateway/device.go`, `internal/gateway/device_test.go` - Pre-commit: `go test -v ./internal/gateway/ -run TestGetDevice` --- - [ ] **2.3 添加单元测试(覆盖率 ≥ 90%)** **What to do**: - 在 `internal/gateway/client_test.go` 中补充完整单元测试: - **加密/签名测试**(已在 Task 1.2 完成) - **doRequest 测试**(已在 Task 1.5 完成) - **流量卡 API 测试**(已在 Task 2.1 完成) - **设备 API 测试**(已在 Task 2.2 完成) - 新增测试覆盖: - **边界条件测试**: 空字符串、nil 参数、超长字符串 - **错误处理测试**: 网络错误、超时、业务错误 - **并发安全测试**: 多 goroutine 同时调用客户端 - 使用 **table-driven tests** 模式 - 运行覆盖率检查: ```bash go test -v ./internal/gateway/... -cover -coverprofile=coverage.out go tool cover -func=coverage.out ``` - 确保核心逻辑覆盖率 ≥ 90% **Must NOT do**: - 不测试设备 API 的真实调用(无环境) - 不过度测试(不需要 100% 覆盖率) - 不跳过错误场景测试 **Recommended Agent Profile**: - **Category**: `ultrabrain` - Reason: 测试覆盖率需要全面考虑边界条件和错误场景 - **Skills**: 无 - Reason: 单元测试是通用技能 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 5(与 Task 4.1, 4.2 并行) - **Blocks**: None(测试不阻塞其他任务) - **Blocked By**: Task 2.1, 2.2(需要 API 实现存在) **References**: - `tests/unit/shop_store_test.go` - 参考项目的 table-driven tests 模式 - **Go 测试文档**: `testing` 标准库(t.Run, subtests) - **httptest 文档**: `net/http/httptest` - 模拟 HTTP 服务器 **Acceptance Criteria**: **测试覆盖验证**: - [ ] 运行覆盖率检查: `go test -v ./internal/gateway/... -cover -coverprofile=coverage.out` - [ ] 查看覆盖率报告: `go tool cover -func=coverage.out | grep total` - [ ] 核心逻辑覆盖率 ≥ 90%: - `crypto.go` ≥ 90% - `client.go` ≥ 90% - `flow_card.go` ≥ 90% - `device.go` = 100%(只返回错误,简单) - [ ] 所有测试通过: `go test -v ./internal/gateway/...` → PASS **测试场景覆盖**: - [ ] 加密/签名正确性测试(加密 → 解密 → 验证) - [ ] 重试逻辑测试(5xx 重试、4xx 不重试、超时不重试) - [ ] 错误分类测试(网络/超时/业务错误) - [ ] 边界条件测试(空参数、nil、超长字符串) - [ ] 并发安全测试(多 goroutine 调用) **Commit**: YES - Message: `test(gateway): 补充单元测试覆盖率(≥ 90%)` - Files: `internal/gateway/*_test.go` - Pre-commit: `go test -v ./internal/gateway/... -cover` --- ### Phase 3: 配置和错误码集成 - [ ] **3.1 添加 Gateway 配置结构** **What to do**: - 在 `pkg/config/config.go` 中添加 `GatewayConfig` 结构体: ```go // GatewayConfig Gateway API 配置 type GatewayConfig struct { BaseURL string `mapstructure:"base_url" validate:"required,url"` // Gateway API 地址 AppID string `mapstructure:"app_id" validate:"required"` // 应用ID AppSecret string `mapstructure:"app_secret" validate:"required"` // 应用密钥 Timeout time.Duration `mapstructure:"timeout" validate:"min=1s"` // 请求超时时间(默认 30s) } ``` - 在 `Config` 结构体中添加字段: ```go type Config struct { // ... 现有字段 Gateway GatewayConfig `mapstructure:"gateway"` } ``` - 在 `pkg/config/defaults/config.yaml` 中添加默认配置: ```yaml gateway: base_url: "https://lplan.whjhft.com/openapi" app_id: "" app_secret: "" timeout: 30s ``` - 在 `pkg/config/loader.go` 中绑定环境变量: ```go viper.BindEnv("gateway.base_url", "JUNHONG_GATEWAY_BASEURL") viper.BindEnv("gateway.app_id", "JUNHONG_GATEWAY_APPID") viper.BindEnv("gateway.app_secret", "JUNHONG_GATEWAY_APPSECRET") viper.BindEnv("gateway.timeout", "JUNHONG_GATEWAY_TIMEOUT") ``` - 添加配置验证(必填项检查) **Must NOT do**: - 不在配置文件中硬编码 AppID/AppSecret(使用环境变量) - 不跳过验证逻辑 **Recommended Agent Profile**: - **Category**: `quick` - Reason: 配置结构定义是标准化任务 - **Skills**: 无 - Reason: 配置管理遵循现有模式,无需特殊规范 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1(与 Task 1.1 并行) - **Blocks**: Task 4.1(Bootstrap 初始化依赖配置) - **Blocked By**: None(可立即开始) **References**: - `pkg/config/config.go` - 参考现有配置结构(SMSConfig, JWTConfig) - `pkg/config/defaults/config.yaml` - 参考默认配置格式 - `pkg/config/loader.go` - 参考环境变量绑定模式(viper.BindEnv) **Acceptance Criteria**: **配置加载验证**: - [ ] 配置结构定义完成: `GatewayConfig` 在 `pkg/config/config.go` - [ ] 默认配置存在: `pkg/config/defaults/config.yaml` 包含 gateway 配置 - [ ] 环境变量绑定: `pkg/config/loader.go` 绑定 JUNHONG_GATEWAY_* 变量 - [ ] 配置验证通过: ```bash export JUNHONG_GATEWAY_BASEURL="https://lplan.whjhft.com/openapi" export JUNHONG_GATEWAY_APPID="60bgt1X8i7AvXqkd" export JUNHONG_GATEWAY_APPSECRET="BZeQttaZQt0i73moF" go run cmd/api/main.go --help # 验证配置加载无错误 ``` - [ ] 编译通过: `go build ./pkg/config/...` → 无错误 **Commit**: YES - Message: `feat(config): 添加 Gateway API 配置结构` - Files: `pkg/config/config.go`, `pkg/config/defaults/config.yaml`, `pkg/config/loader.go` - Pre-commit: `go build ./pkg/config/...` --- - [ ] **3.2 添加 Gateway 错误码(1110-1119)** **What to do**: - 在 `pkg/errors/codes.go` 中添加 Gateway 错误码常量: ```go // Gateway 相关错误 (1110-1119) CodeGatewayNetworkError = 1110 // Gateway 网络错误 CodeGatewayTimeout = 1111 // Gateway 请求超时 CodeGatewayBusinessError = 1112 // Gateway 业务错误 CodeGatewayEncryptionError = 1113 // Gateway 加密错误 CodeGatewayDecryptionError = 1114 // Gateway 解密错误 CodeGatewaySignatureError = 1115 // Gateway 签名错误 CodeGatewayInvalidResponse = 1116 // Gateway 响应格式错误 CodeGatewayNotImplemented = 1117 // Gateway 接口未实现 // 预留 1118-1119 供未来扩展 ``` - 在 `allErrorCodes` 数组中注册新错误码 - 在 `errorMessages` 映射表中添加中文错误消息: ```go 1110: "Gateway 网络错误", 1111: "Gateway 请求超时", 1112: "Gateway 业务错误", 1113: "Gateway 加密失败", 1114: "Gateway 解密失败", 1115: "Gateway 签名错误", 1116: "Gateway 响应格式错误", 1117: "Gateway 接口未实现", ``` - 运行错误码验证测试: `go test -v ./pkg/errors/...` **Must NOT do**: - 不使用其他范围的错误码(严格使用 1110-1119) - 不跳过错误码验证测试 **Recommended Agent Profile**: - **Category**: `quick` - Reason: 错误码定义是标准化任务 - **Skills**: 无 - Reason: 错误码遵循现有模式 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 2(与 Task 1.2, 1.3 并行) - **Blocks**: Task 4.1(Bootstrap 初始化可能需要错误码) - **Blocked By**: Task 1.1(目录结构需先存在) **References**: - `pkg/errors/codes.go` - 参考现有错误码定义模式 - `pkg/errors/errors_test.go` - 参考错误码验证测试 **Acceptance Criteria**: **错误码验证**: - [ ] 错误码常量定义完成: `CodeGatewayNetworkError` 等在 `pkg/errors/codes.go` - [ ] 错误码注册完成: `allErrorCodes` 包含 1110-1119 - [ ] 错误消息定义完成: `errorMessages` 包含中文消息 - [ ] 错误码验证测试通过: `go test -v ./pkg/errors/...` → PASS - [ ] 编译通过: `go build ./pkg/errors/...` → 无错误 **Commit**: YES - Message: `feat(errors): 添加 Gateway 错误码(1110-1119)` - Files: `pkg/errors/codes.go` - Pre-commit: `go test -v ./pkg/errors/...` --- ### Phase 4: 依赖注入和集成 - [ ] **4.1 Bootstrap 初始化 Gateway 客户端** **What to do**: - 在 `internal/bootstrap/dependencies.go` 的 `Dependencies` 结构体中添加字段: ```go type Dependencies struct { // ... 现有字段 GatewayClient *gateway.Client // Gateway API 客户端 } ``` - 在 `internal/bootstrap/bootstrap.go` 的 `Bootstrap` 函数中初始化 Gateway 客户端: ```go // 初始化 Gateway 客户端 gatewayClient := gateway.NewClient( cfg.Gateway.BaseURL, cfg.Gateway.AppID, cfg.Gateway.AppSecret, deps.Logger, ).WithTimeout(cfg.Gateway.Timeout) deps.GatewayClient = gatewayClient deps.Logger.Info("Gateway 客户端初始化成功", zap.String("base_url", cfg.Gateway.BaseURL), zap.Duration("timeout", cfg.Gateway.Timeout), ) ``` - 确保初始化顺序正确(在 Service 初始化之前) **Must NOT do**: - 不在 Bootstrap 中调用 Gateway API(只初始化客户端) - 不跳过日志记录 **Recommended Agent Profile**: - **Category**: `quick` - Reason: 依赖注入是标准流程 - **Skills**: 无 - Reason: Bootstrap 模式遵循现有模式 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 5(与 Task 2.3 并行) - **Blocks**: Task 4.2(Service 集成依赖 Bootstrap) - **Blocked By**: Task 1.4, 3.1, 3.2(需要客户端、配置、错误码) **References**: - `internal/bootstrap/bootstrap.go` - 参考现有依赖初始化模式(SMS、Storage) - `internal/bootstrap/dependencies.go` - 参考 Dependencies 结构体定义 - `internal/gateway/client.go` - Gateway 客户端构造函数 **Acceptance Criteria**: **Bootstrap 验证**: - [ ] Dependencies 字段添加: `GatewayClient *gateway.Client` - [ ] Bootstrap 初始化代码完成(在 Service 初始化之前) - [ ] 编译通过: `go build ./cmd/api/...` → 无错误 - [ ] 启动验证: ```bash source .env.local && go run cmd/api/main.go # 查看日志: "Gateway 客户端初始化成功" ``` **Commit**: YES - Message: `feat(bootstrap): 初始化 Gateway 客户端` - Files: `internal/bootstrap/dependencies.go`, `internal/bootstrap/bootstrap.go` - Pre-commit: `go build ./cmd/api/...` --- - [ ] **4.2 Service 层集成示例(SyncCardStatus)** **What to do**: - 在 `internal/service/iot_card/service.go` 的 `Service` 结构体中添加字段: ```go type Service struct { // ... 现有字段 gatewayClient *gateway.Client // Gateway API 客户端 } ``` - 更新构造函数 `New` 添加参数: ```go func New( // ... 现有参数 gatewayClient *gateway.Client, ) *Service { return &Service{ // ... 现有字段 gatewayClient: gatewayClient, } } ``` - 新增方法 `SyncCardStatus`(从 Gateway 同步卡状态到数据库): ```go // SyncCardStatus 从 Gateway 同步流量卡状态到数据库 func (s *Service) SyncCardStatus(ctx context.Context, iccid string) error { // 1. 调用 Gateway API 查询卡状态 resp, err := s.gatewayClient.QueryCardStatus(ctx, iccid) if err != nil { s.logger.Error("查询 Gateway 卡状态失败", zap.String("iccid", iccid), zap.Error(err), ) return errors.Wrap(errors.CodeGatewayBusinessError, err, "同步卡状态失败") } // 2. 更新数据库中的卡状态 err = s.iotCardStore.UpdateStatus(ctx, iccid, resp.Status) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "更新卡状态失败") } s.logger.Info("同步卡状态成功", zap.String("iccid", iccid), zap.String("status", resp.Status), ) return nil } ``` - 在 `internal/bootstrap/services.go` 中更新 iot_card Service 初始化: ```go IotCard: iot_card.New( // ... 现有参数 deps.GatewayClient, ), ``` **Must NOT do**: - 不暴露 Gateway 响应结构给 Handler 层(封装在 Service 内) - 不跳过错误处理和日志记录 **Recommended Agent Profile**: - **Category**: `ultrabrain` - Reason: Service 集成需要考虑错误处理、日志记录、数据库更新 - **Skills**: 无 - Reason: Service 集成是标准流程 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 5(与 Task 2.3, 5.1, 5.2 并行) - **Blocks**: None(集成示例不阻塞其他任务) - **Blocked By**: Task 4.1(需要 Bootstrap 初始化完成) **References**: - `internal/service/iot_card/service.go` - 现有 Service 结构和方法 - `internal/bootstrap/services.go` - Service 初始化模式 - `internal/gateway/flow_card.go` - QueryCardStatus 方法 **Acceptance Criteria**: **集成验证**: - [ ] Service 字段添加: `gatewayClient *gateway.Client` - [ ] 构造函数更新: `New` 函数添加 gatewayClient 参数 - [ ] SyncCardStatus 方法实现完成 - [ ] Bootstrap 更新: `services.go` 传递 GatewayClient - [ ] 编译通过: `go build ./internal/service/iot_card/...` → 无错误 - [ ] 启动验证: `source .env.local && go run cmd/api/main.go` → 无错误 **Commit**: YES - Message: `feat(service): 集成 Gateway 客户端到 iot_card Service(SyncCardStatus 示例)` - Files: `internal/service/iot_card/service.go`, `internal/bootstrap/services.go` - Pre-commit: `go build ./internal/service/iot_card/...` --- ### Phase 5: 集成测试和文档 - [ ] **5.1 编写集成测试(真实 Gateway 环境)** **What to do**: - 在 `internal/gateway/integration_test.go` 中编写集成测试: - 使用真实 Gateway 配置(BaseURL, AppID, AppSecret) - 使用测试 ICCID: `8986062580006141710` - 测试至少 2 个流量卡接口: 1. `QueryCardStatus` - 查询卡状态 2. `QueryFlow` - 查询流量使用 - **不测试设备 API**(无测试环境) - 集成测试结构: ```go // +build integration package gateway_test import ( "context" "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "junhong_cmp_fiber/internal/gateway" ) func TestIntegration_QueryCardStatus(t *testing.T) { if os.Getenv("JUNHONG_GATEWAY_APPID") == "" { t.Skip("跳过集成测试:未配置 Gateway 环境变量") } client := gateway.NewClient( os.Getenv("JUNHONG_GATEWAY_BASEURL"), os.Getenv("JUNHONG_GATEWAY_APPID"), os.Getenv("JUNHONG_GATEWAY_APPSECRET"), zap.NewNop(), ).WithTimeout(30 * time.Second) ctx := context.Background() resp, err := client.QueryCardStatus(ctx, "8986062580006141710") require.NoError(t, err) assert.NotNil(t, resp) assert.NotEmpty(t, resp.Status) t.Logf("卡状态查询成功: %+v", resp) } ``` - 运行集成测试: ```bash source .env.local && go test -v ./internal/gateway/... -tags=integration -run TestIntegration ``` **Must NOT do**: - 不在 CI/CD 中自动运行集成测试(需手动触发) - 不测试设备 API(无环境) - 不硬编码配置(使用环境变量) **Recommended Agent Profile**: - **Category**: `ultrabrain` - Reason: 集成测试需要处理真实 API 响应和错误场景 - **Skills**: 无 - Reason: 集成测试是通用技能 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 5(与 Task 2.3, 4.2, 5.2 并行) - **Blocks**: None(测试不阻塞其他任务) - **Blocked By**: Task 2.1, 2.2, 4.1(需要 API 实现和 Bootstrap) **References**: - **测试配置**: BaseURL, AppID, AppSecret, 测试 ICCID(用户提供) - `tests/integration/` - 参考项目的集成测试模式 - **Go build tags**: `// +build integration` - 条件编译标记 **Acceptance Criteria**: **集成测试验证**: - [ ] 集成测试文件创建: `internal/gateway/integration_test.go` - [ ] 测试使用 build tag: `// +build integration` - [ ] 测试使用环境变量(不硬编码配置) - [ ] 测试至少 2 个接口: QueryCardStatus, QueryFlow - [ ] 运行集成测试: ```bash source .env.local && go test -v ./internal/gateway/... -tags=integration -run TestIntegration ``` - [ ] 集成测试通过(验证真实 Gateway API) - [ ] 日志输出完整(包含请求/响应详情) **Commit**: YES - Message: `test(gateway): 添加集成测试(真实 Gateway 环境)` - Files: `internal/gateway/integration_test.go` - Pre-commit: `source .env.local && go test -v ./internal/gateway/... -tags=integration -run TestIntegration` --- - [ ] **5.2 更新文档** **What to do**: - 创建 `docs/gateway-client-usage.md` 使用文档: - **概述**: Gateway 客户端功能和用途 - **配置说明**: 环境变量配置示例 - **使用示例**: 代码示例(QueryCardStatus, QueryFlow 等) - **错误码说明**: Gateway 错误码(1110-1119)和处理方式 - **重试机制**: 重试策略说明(3 次,指数退避) - **安全警告**: AES-128-ECB 和 MD5 的安全风险说明 - **最佳实践**: 超时设置、错误处理、日志记录 - 更新 `README.md`: - 在 "功能模块" 部分添加 Gateway 集成模块 - 在 "配置说明" 部分添加 Gateway 环境变量说明 - 确保所有文档使用中文 **Must NOT do**: - 不过度详细(不需要 API 文档级别的详情) - 不添加英文文档(项目要求中文) **Recommended Agent Profile**: - **Category**: `writing` - Reason: 文档编写任务 - **Skills**: [`doc-management`] - `doc-management`: 规范文档管理流程 - Reason: 文档更新需要遵循项目文档规范 **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 5(与 Task 2.3, 4.2, 5.1 并行) - **Blocks**: None(文档不阻塞其他任务) - **Blocked By**: All(文档需要完整功能才能编写) **References**: - `docs/` - 参考现有文档结构和风格 - `README.md` - 参考现有 README 格式 - **Apifox 文档**: `https://omp5mq28pq.apifox.cn/7819761m0` - Gateway API 文档(参考) - `internal/gateway/client.go` - Gateway 客户端实现(提取使用示例) **Acceptance Criteria**: **文档验证**: - [ ] 文档创建: `docs/gateway-client-usage.md` - [ ] 文档包含以下部分: - [ ] 概述 - [ ] 配置说明(环境变量) - [ ] 使用示例(至少 2 个接口) - [ ] 错误码说明(1110-1119) - [ ] 重试机制说明 - [ ] 安全警告(ECB + MD5) - [ ] 最佳实践 - [ ] README.md 更新: - [ ] "功能模块" 部分添加 Gateway 集成 - [ ] "配置说明" 部分添加 Gateway 环境变量 - [ ] 所有文档使用中文 - [ ] 代码示例可编译(复制到项目中能运行) **Commit**: YES - Message: `docs(gateway): 添加 Gateway 客户端使用文档` - Files: `docs/gateway-client-usage.md`, `README.md` - Pre-commit: 无(文档无需测试) --- ## Commit Strategy | After Task | Message | Files | Verification | |------------|---------|-------|--------------| | 1.1 | `feat(gateway): 创建 Gateway 包目录结构` | `internal/gateway/*.go` | `go build ./internal/gateway/...` | | 1.2 | `feat(gateway): 实现 AES-128-ECB 加密和 MD5 签名工具` | `internal/gateway/crypto.go`, `crypto_test.go` | `go test -v ./internal/gateway/ -run TestAES` | | 1.3 | `feat(gateway): 定义 Gateway API 请求/响应 DTO 结构` | `internal/gateway/models.go`, `models_test.go` | `go test -v ./internal/gateway/ -run TestGatewayRequest` | | 1.4 | `feat(gateway): 实现 Gateway 客户端基础结构` | `internal/gateway/client.go`, `client_test.go` | `go test -v ./internal/gateway/ -run TestNewClient` | | 1.5 | `feat(gateway): 实现 HTTP 重试逻辑(3 次重试,指数退避)` | `internal/gateway/client.go`, `client_test.go` | `go test -v ./internal/gateway/ -run TestDoRequest` | | 2.1 | `feat(gateway): 实现流量卡 API(6 个接口)` | `internal/gateway/flow_card.go`, `flow_card_test.go` | `go test -v ./internal/gateway/ -run TestQuery` | | 2.2 | `feat(gateway): 实现设备 API 方法签名(7 个接口,暂未实现)` | `internal/gateway/device.go`, `device_test.go` | `go test -v ./internal/gateway/ -run TestGetDevice` | | 2.3 | `test(gateway): 补充单元测试覆盖率(≥ 90%)` | `internal/gateway/*_test.go` | `go test -v ./internal/gateway/... -cover` | | 3.1 | `feat(config): 添加 Gateway API 配置结构` | `pkg/config/config.go`, `defaults/config.yaml`, `loader.go` | `go build ./pkg/config/...` | | 3.2 | `feat(errors): 添加 Gateway 错误码(1110-1119)` | `pkg/errors/codes.go` | `go test -v ./pkg/errors/...` | | 4.1 | `feat(bootstrap): 初始化 Gateway 客户端` | `internal/bootstrap/dependencies.go`, `bootstrap.go` | `go build ./cmd/api/...` | | 4.2 | `feat(service): 集成 Gateway 客户端到 iot_card Service(SyncCardStatus 示例)` | `internal/service/iot_card/service.go`, `bootstrap/services.go` | `go build ./internal/service/iot_card/...` | | 5.1 | `test(gateway): 添加集成测试(真实 Gateway 环境)` | `internal/gateway/integration_test.go` | `source .env.local && go test -v ./internal/gateway/... -tags=integration` | | 5.2 | `docs(gateway): 添加 Gateway 客户端使用文档` | `docs/gateway-client-usage.md`, `README.md` | 无 | --- ## Success Criteria ### Verification Commands ```bash # 1. 编译检查 go build ./internal/gateway/... go build ./cmd/api/... # 2. 单元测试(覆盖率 ≥ 90%) go test -v ./internal/gateway/... -cover -coverprofile=coverage.out go tool cover -func=coverage.out | grep total # 3. 集成测试(需先配置环境变量) source .env.local go test -v ./internal/gateway/... -tags=integration -run TestIntegration # 4. LSP 错误检查 gopls check ./internal/gateway/... # 5. 启动服务验证 source .env.local && go run cmd/api/main.go # 查看日志: "Gateway 客户端初始化成功" ``` ### Final Checklist - [ ] 所有 14 个 Gateway API 接口成功封装(流量卡 6 个 + 设备 7 个签名) - [ ] 加密/签名通过单元测试验证(与 Apifox 文档一致) - [ ] 集成测试验证真实 Gateway API 调用(测试 ICCID: `8986062580006141710`) - [ ] 重试逻辑测试通过(3 次重试,指数退避) - [ ] 配置通过环境变量成功加载(JUNHONG_GATEWAY_*) - [ ] 依赖注入到 iot_card Service 成功(SyncCardStatus 方法) - [ ] 单元测试覆盖率 ≥ 90%(核心逻辑) - [ ] 无 LSP 错误,编译通过 - [ ] 符合项目代码规范(中文注释、Go 命名规范) - [ ] 文档完整(使用示例、错误码说明) - [ ] 所有 "Must NOT Have" 约束遵守(无批量查询、无设备真实测试、无第三方重试库)