All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 43s
1458 lines
56 KiB
Markdown
1458 lines
56 KiB
Markdown
# 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" 约束遵守(无批量查询、无设备真实测试、无第三方重试库)
|