feat: 在 IoT 卡和设备列表响应中添加套餐系列名称字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m2s

主要变更:
- 在 StandaloneIotCardResponse 和 DeviceResponse 中添加 series_name 字段
- 在 iot_card 和 device service 中添加 loadSeriesNames 方法批量加载系列名称
- 更新相关方法以支持 series_name 的填充

其他变更:
- 新增 OpenSpec 测试生成和共识锁定 skill
- 新增 MCP 配置文件
- 更新 CLAUDE.md 项目规范文档

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 15:28:41 +08:00
parent dc84cef2ce
commit 8ab5ebc3af
9 changed files with 1149 additions and 12 deletions

211
CLAUDE.md
View File

@@ -86,9 +86,15 @@ Handler → Service → Store → Model
- 使用统一错误码系统
- Handler 层通过返回 `error` 传递给全局 ErrorHandler
#### 错误报错规范(必须遵守)
- Handler 层禁止直接返回/拼接底层错误信息给客户端(例如 `"参数验证失败: "+err.Error()``err.Error()`
- 参数校验失败:对外统一返回 `errors.New(errors.CodeInvalidParam)`(详细校验错误写日志)
- Service 层禁止对外返回 `fmt.Errorf(...)`,必须返回 `errors.New(...)``errors.Wrap(...)`
- 约定用法:`errors.New(code[, msg])``errors.Wrap(code, err[, msg])`
### 响应格式
- 所有 API 响应使用 `pkg/response/` 的统一格式
- 格式: `{code, message, data, timestamp}`
- 格式: `{code, msg, data, timestamp}`
### 常量管理
- 所有常量定义在 `pkg/constants/`
@@ -128,10 +134,67 @@ Handler → Service → Store → Model
## 测试要求
- 核心业务逻辑Service 层)测试覆盖率 ≥ 90%
- 所有 API 端点必须有集成测试
- 使用 table-driven tests
- 单元测试 < 100ms集成测试 < 1s
### 测试金字塔(新)
```
┌─────────────┐
│ E2E 测试 │ ← 手动/自动化 UI很少
─┴─────────────┴─
┌─────────────────┐
│ 业务流程测试 │ ← 15%:多 API 组合验证
│ tests/flows/ │ 来源Spec Business Flow
─┴─────────────────┴─
┌─────────────────────┐
│ 验收测试 │ ← 30%:单 API 契约验证
│ tests/acceptance/ │ 来源Spec Scenario
─┴─────────────────────┴─
┌───────────────────────────┐
│ 集成测试 │ ← 25%:组件集成
─┴───────────────────────────┴─
┌─────────────────────────────────┐
│ 单元测试(精简) │ ← 30%:仅复杂逻辑
└─────────────────────────────────┘
```
### 三层测试体系
| 层级 | 测试类型 | 来源 | 验证什么 | 位置 |
|------|---------|------|---------|------|
| **L1** | 验收测试 | Spec Scenario | 单 API 契约 | `tests/acceptance/` |
| **L2** | 流程测试 | Spec Business Flow | 业务场景完整性 | `tests/flows/` |
| **L3** | 单元测试 | 复杂逻辑 | 算法/规则正确性 | 模块内 `*_test.go` |
### 验收测试规范
- **来源于 Spec**:每个 Scenario 对应一个测试用例
- **测试先于实现**:在功能实现前生成,预期全部 FAIL
- **必须有破坏点**:每个测试注释说明什么代码变更会导致失败
- **使用 IntegrationTestEnv**:不要 mock 依赖
详见:[tests/acceptance/README.md](tests/acceptance/README.md)
### 流程测试规范
- **来源于 Spec Business Flow**:每个 Flow 对应一个测试
- **跨 API 验证**:多个 API 调用的组合行为
- **状态共享**:流程中的数据在 steps 之间传递
- **依赖声明**:每个 step 声明依赖哪些前置 step
详见:[tests/flows/README.md](tests/flows/README.md)
### 单元测试精简规则
**保留**
- ✅ 纯函数(计费计算、分佣算法)
- ✅ 状态机(订单状态流转)
- ✅ 复杂业务规则(层级校验、权限计算)
- ✅ 边界条件(时间、金额、精度)
**删除/不再写**
- ❌ 简单 CRUD已被验收测试覆盖
- ❌ DTO 转换
- ❌ 配置读取
- ❌ 重复测试同一逻辑
### ⚠️ 测试真实性原则(严格遵守)
@@ -165,6 +228,15 @@ handler.HandleIotCardImport(ctx, asynqTask) // 测试完整流程,验证真
**详细规范**: [docs/testing/test-connection-guide.md](docs/testing/test-connection-guide.md)
**⚠️ 运行测试必须先加载环境变量**:
```bash
# ✅ 正确
source .env.local && go test -v ./internal/service/xxx/...
# ❌ 错误(会因缺少配置而失败)
go test -v ./internal/service/xxx/...
```
**标准模板**:
```go
func TestXxx(t *testing.T) {
@@ -182,6 +254,23 @@ func TestXxx(t *testing.T) {
- `GetTestRedis(t)`: 获取全局 Redis 连接
- `CleanTestRedisKeys(t, rdb)`: 自动清理测试 Redis 键
**集成测试环境**HTTP API 测试):
```go
func TestAPI_Create(t *testing.T) {
env := testutils.NewIntegrationTestEnv(t)
t.Run("成功创建", func(t *testing.T) {
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/resources", jsonBody)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
})
}
```
- `NewIntegrationTestEnv(t)`: 创建完整测试环境事务、Redis、App、Token
- `AsSuperAdmin()`: 以超级管理员身份请求
- `AsUser(account)`: 以指定账号身份请求
**禁止使用(已移除)**:
-`SetupTestDB` / `TeardownTestDB` / `SetupTestDBWithStore`
@@ -227,6 +316,118 @@ func TestXxx(t *testing.T) {
8. ✅ 文档更新计划
9. ✅ 中文优先
## Code Review 检查清单
### 错误处理
- [ ] Service 层无 `fmt.Errorf` 对外返回
- [ ] Handler 层参数校验不泄露细节
- [ ] 错误码使用正确4xx vs 5xx
- [ ] 错误日志完整(包含上下文)
### 代码质量
- [ ] 遵循 Handler → Service → Store → Model 分层
- [ ] 函数长度 ≤ 100 行(核心逻辑 ≤ 50 行)
- [ ] 常量定义在 `pkg/constants/`
- [ ] 使用 Go 惯用法(非 Java 风格)
### 测试覆盖
- [ ] 核心业务逻辑测试覆盖率 ≥ 90%
- [ ] 所有 API 端点有集成测试
- [ ] 测试验证真实功能(不绕过核心逻辑)
### 文档和注释
- [ ] 所有注释使用中文
- [ ] 导出函数/类型有文档注释
- [ ] API 路径注释与真实路由一致
### 越权防护规范
**适用场景**:任何涉及跨用户、跨店铺、跨企业的资源访问
**三层防护机制**
1. **路由层中间件**(粗粒度拦截)
- 用于明显的权限限制(如企业账号禁止访问账号管理)
- 示例:
```go
group.Use(func(c *fiber.Ctx) error {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType == constants.UserTypeEnterprise {
return errors.New(errors.CodeForbidden, "无权限访问账号管理功能")
}
return c.Next()
})
```
2. **Service 层业务检查**(细粒度验证)
- 使用 `middleware.CanManageShop(ctx, targetShopID, shopStore)` 验证店铺权限
- 使用 `middleware.CanManageEnterprise(ctx, targetEnterpriseID, enterpriseStore, shopStore)` 验证企业权限
- 类型级权限检查(如代理不能创建平台账号)
- 示例见 `internal/service/account/service.go`
3. **GORM Callback 自动过滤**(兜底)
- 已有实现,自动应用到所有查询
- 代理用户:`WHERE shop_id IN (自己店铺+下级店铺)`
- 企业用户:`WHERE enterprise_id = 当前企业ID`
- 无需手动调用
**统一错误返回**
- 越权访问统一返回:`errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")`
- 不区分"不存在"和"无权限",防止信息泄露
### 审计日志规范
**适用场景**:任何敏感操作(账号管理、权限变更、数据删除等)
**使用方式**
1. **Service 层集成审计日志**
```go
type Service struct {
store *Store
auditService AuditServiceInterface
}
func (s *Service) SensitiveOperation(ctx context.Context, ...) error {
// 1. 执行业务操作
err := s.store.DoSomething(ctx, ...)
if err != nil {
return err
}
// 2. 记录审计日志(异步)
s.auditService.LogOperation(ctx, &model.OperationLog{
OperatorID: middleware.GetUserIDFromContext(ctx),
OperationType: "operation_type",
OperationDesc: "操作描述",
BeforeData: beforeData, // 变更前数据
AfterData: afterData, // 变更后数据
RequestID: middleware.GetRequestIDFromContext(ctx),
IPAddress: middleware.GetIPFromContext(ctx),
UserAgent: middleware.GetUserAgentFromContext(ctx),
})
return nil
}
```
2. **审计日志字段说明**
- `operator_id`, `operator_type`, `operator_name`: 操作人信息(必填)
- `target_*`: 目标资源信息(可选)
- `operation_type`: 操作类型create/update/delete/assign_roles等
- `operation_desc`: 操作描述(中文,便于查看)
- `before_data`, `after_data`: 变更数据JSON 格式)
- `request_id`, `ip_address`, `user_agent`: 请求上下文
3. **异步写入**
- 审计日志使用 Goroutine 异步写入
- 写入失败不影响业务操作
- 失败时记录 Error 日志,包含完整审计信息
**示例参考**`internal/service/account/service.go`
---
### ⚠️ 任务执行规范(必须遵守)
**提案中的 tasks.md 是契约,不可擅自变更:**