feat: 在 IoT 卡和设备列表响应中添加套餐系列名称字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m2s
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:
211
CLAUDE.md
211
CLAUDE.md
@@ -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 是契约,不可擅自变更:**
|
||||
|
||||
Reference in New Issue
Block a user