Files
junhong_cmp_fiber/AGENTS.md
2026-07-17 16:39:41 +08:00

348 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
# junhong_cmp_fiber 项目开发规范
**重要**: 本文件包含核心规范。详细规范已提取为 Skills在特定任务时按需加载。
## 专项规范 Skills按需加载
以下规范在相关任务时**自动触发**,无需手动加载:
| 任务类型 | 触发 Skill | 说明 |
|---------|-----------|------|
| 创建/修改 DTO 文件 | `dto-standards` | description 标签、枚举字段、验证标签规范 |
| 创建/修改 Model 模型 | `model-standards` | GORM 模型结构、字段标签、TableName 规范 |
| 注册 API 路由 / **新增 Handler** | `api-routing` | Register() 函数、RouteSpec、**文档生成器更新** |
| 测试接口/验证数据 | `db-validation` | PostgreSQL MCP 使用方法和验证示例 |
| 数据库迁移 | `db-migration` | 迁移命令、文件规范、执行流程、失败处理 |
| 维护规范文档 | `doc-management` | 规范文档流程和维护规则 |
| 编写 Go 代码注释/文档注释 | `comment-standards` | 包/结构体/接口/函数/内联注释完整规范与示例 |
| 创建涉及接口的 OpenSpec 提案 | `openspec-api-contract` | 探索阶段引导清单、提案必填章节与完成标准 |
---
### ⚠️ 新增 Handler 时必须同步更新文档生成器
新增 Handler 后,接口不会自动出现在 OpenAPI 文档中。**必须手动更新以下两个文件**
```go
// cmd/api/docs.go 和 cmd/gendocs/main.go
handlers := &bootstrap.Handlers{
// ... 添加新 Handler
NewHandler: admin.NewXxxHandler(nil),
}
```
**完整检查清单**: 参见 [`docs/api-documentation-guide.md`](docs/api-documentation-guide.md#新增-handler-检查清单)
---
## 语言要求
**必须遵守:**
- 永远用中文交互
- 注释必须使用中文
- 文档必须使用中文
- 日志消息必须使用中文
- 用户可见的错误消息必须使用中文
- 变量名、函数名、类型名必须使用英文(遵循 Go 命名规范)
- GIT提交的commit必须使用中文
## 技术栈
**必须严格遵守,禁止替代方案:**
| 类型 | 技术 |
|------|------|
| HTTP 框架 | Fiber v2.x |
| ORM | GORM v1.25.x |
| 配置管理 | Viper |
| 日志 | Zap + Lumberjack.v2 |
| JSON 序列化 | sonic优先encoding/json必要时 |
| 验证 | Validator |
| 任务队列 | Asynq v0.24.x |
| 数据库 | PostgreSQL 14+ |
| 缓存 | Redis 6.0+ |
**禁止:**
- 直接使用 `database/sql`(必须通过 GORM
- 使用 `net/http` 替代 Fiber
- 使用 `encoding/json` 替代 sonic除非必要
## 架构演进MVC 与 DDD 共存
项目采用**触碰式渐进迁移**,不安排一次性全仓库重构。详细规则见 `docs/7月迭代/独立方案/基础规范/DDD规范.md`
### 三条执行通道
- **复杂写操作**`Handler → Application UseCase → Domain → Repository/Infrastructure`。适用于状态机、金额、库存、并发不变量、策略和可靠事件。
- **简单写操作**`Handler → Application 事务脚本 → Persistence`。单表 CRUD 不强行创建聚合。
- **读取操作**`Handler → Query → GORM/DTO`。列表、详情、联表、统计、报表和导出不经过聚合根。
### 触碰式迁移约束
1. 禁止主动迁移当前需求未触碰的旧模块;简单字段、筛选和局部修复默认沿用旧结构。
2. 迁移单位是**完整用例**,不是整个模块、文件或数据表;一旦迁移,相关业务不变量必须完整收口,禁止一半留在旧 Service、一半放在 Domain。
3. 当前需求触碰复杂写逻辑时,只迁移完成该需求所需的最小完整业务边界;旧 Service 可暂时作为内部代码迁移门面调用新 UseCase但这不等于必须保留旧 HTTP 接口。
4. 当前需求触碰复杂只读逻辑时,只迁移该查询到 `internal/query/<context>`Query 可直接使用 GORM 做联表、聚合、权限过滤和 DTO 投影。
5. 不为追求 DDD 形式创建无业务价值的接口、工厂和目录;架构选择不明确时先阅读 DDD 规范并写明判断依据。
## 核心原则
### 错误处理
- 所有错误必须在 `pkg/errors/` 中定义
- 使用统一错误码系统
- Handler 层通过返回 `error` 传递给全局 ErrorHandler
#### 错误报错规范(必须遵守)
- Handler 层禁止直接返回/拼接底层错误信息给客户端(例如 `"参数验证失败: "+err.Error()``err.Error()`
- 参数校验失败:对外统一返回 `errors.New(errors.CodeInvalidParam)`(详细校验错误写日志)
- Service/Application/Domain/Query 层禁止向接口调用方直接返回 `fmt.Errorf(...)`,必须转换为 `errors.New(...)``errors.Wrap(...)`
- 约定用法:`errors.New(code[, msg])``errors.Wrap(code, err[, msg])`
### 响应格式
- 所有 API 响应使用 `pkg/response/` 的统一格式
- 格式: `{code, msg, data, timestamp}`
### 常量管理
- 所有常量定义在 `pkg/constants/`
- Redis key 使用函数生成: `Redis{Module}{Purpose}Key(params...)`
- 禁止硬编码字符串和 magic numbers
- **必须为所有常量添加中文注释**
### 枚举与状态字段(必须遵守)
**两个强制规则**
1. **int vs string**:状态类(生命周期)用 `int`,类型/方式类用 `string`
2. **DTO description 必须从 constants 原文抄写**,禁止凭记忆填写枚举值(历史上已有 description 与 constants 不一致导致前端显示错误的案例)
```go
// ✅ description 从 constants 原文抄,格式统一用冒号+逗号
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
StatusName string `json:"status_name" description:"状态名称(中文)"` // Response DTO 必须加
// ❌ 禁止:启用/禁用用 1=启用 2=禁用(全局约定是 0=禁用, 1=启用)
// ❌ 禁止description 枚举值与 constants 不一致
```
**完整规范**: 参见 [`docs/enum-status-standards.md`](docs/enum-status-standards.md)
### 注释规范
- **所有注释使用中文**,导出符号必须有文档注释(包、函数、类型、接口、常量)
- 复杂逻辑解释"为什么"而非"做了什么",禁止废话注释(复述代码本身)
- Handler 方法注释必须包含 HTTP 方法和路径(`// Create 创建账号` + `// POST /api/admin/accounts`
- 未导出函数:< 15 行可省略,≥ 15 行或非显而易见算法必须注释
- 修改代码时必须同步更新注释,过时注释比没有注释更有害
**详细规范与示例**:见 `comment-standards` skill
### Go 代码风格
- 使用 `gofmt` 格式化
- 遵循 [Effective Go](https://go.dev/doc/effective_go)
- 包名: 简短、小写、单数、无下划线
- 接口命名: 使用 `-er` 后缀Reader、Writer、Logger
## 数据库设计
**核心规则:**
- ❌ 禁止建立外键约束
- ❌ 禁止使用 GORM 关联关系标签foreignKey、hasMany、belongsTo
- ✅ 关联通过存储 ID 字段手动维护
- ✅ 关联数据在代码层面显式查询
## Go 惯用法 vs Java 风格
### ✅ Go 风格(推荐)
- 扁平化包结构(最多 2-3 层)
- 小而专注的接口1-3 个方法)
- 直接访问导出字段(不用 getter/setter
- 组合优于继承
- 显式错误返回和检查
### ❌ Java 风格(禁止)
- 过度抽象(不必要的接口、工厂)
- Getter/Setter 方法
- 深层继承层次
- 异常处理panic/recover
- 类型前缀IService、AbstractBase、ServiceImpl
## ⚠️ 测试禁令(强制执行)
**本项目禁止任何形式的自动化测试**(单元/集成/E2E/`*_test.go` 文件),规划和文档中也不讨论测试。
**唯一例外**:用户明确说"请写测试"时。
**替代验证**PostgreSQL MCP 手动验证数据、Postman/curl 手动测试 API。
## 性能要求
- API P95 响应时间 < 200ms
- API P99 响应时间 < 500ms
- 数据库查询 < 50ms
- 列表查询必须分页(默认 20最大 100
- 避免 N+1 查询,使用批量操作
## 文档要求
- 每个功能在 `docs/{feature-id}/` 创建总结文档
- 文档文件名和内容使用中文
- 同步更新 README.md
- 为导出的函数、类型编写文档注释
- 需要进入评审的标准/完整技术方案必须覆盖关键流程、前后端契约、异常闭环、发布回滚和待决策项;简单改动不强行画图。详细要求见 `docs/技术方案评审规范.md`
## 函数复杂度
- 函数长度 ≤ 100 行(核心逻辑建议 ≤ 50 行)
- `main()` 函数只做编排,不含具体实现
- 遵循单一职责原则
## 访问日志
- 所有 HTTP 请求记录到 `access.log`
- 记录完整的请求/响应(限制 50KB
- 包含: method, path, query, status, duration, request_id, ip, user_agent, user_id, bodies
- 使用 JSON 格式,配置自动轮转
## OpenSpec 工作流
创建提案前的检查清单:
1. ✅ 技术栈合规
2. ✅ 架构分层正确
3. ✅ 使用统一错误处理
4. ✅ 常量定义在 pkg/constants/
5. ✅ Go 惯用法(非 Java 风格)
6. ✅ 性能考虑
7. ✅ 文档更新计划
8. ✅ 中文优先
## Code Review 检查清单
### 错误处理
- [ ] Service/Application/Domain/Query 层无 `fmt.Errorf` 对外返回
- [ ] Handler 层参数校验不泄露细节
- [ ] 错误码使用正确4xx vs 5xx
- [ ] 错误日志完整(包含上下文)
### 代码质量
- [ ] 已按判断标准选择复杂写、简单写或 Query 通道,未扩大迁移范围
- [ ] 复杂写规则收口 Domain简单写使用 Application 事务脚本;只读逻辑不经过聚合根
- [ ] Query 负责读取权限、分页和 DTO 投影,写操作仍在 Domain 重新校验
- [ ] 函数长度 ≤ 100 行(核心逻辑 ≤ 50 行)
- [ ] 常量定义在 `pkg/constants/`
- [ ] 使用 Go 惯用法(非 Java 风格)
### 枚举与状态
- [ ] 状态类字段用 `int`,类型/方式类字段用 `string`
- [ ] 禁用/启用使用 `0=禁用, 1=启用`(禁止 `1=启用, 2=禁用`
- [ ] DTO description 枚举列表已从 `pkg/constants/` 原文抄写,无遗漏、无错误
- [ ] Response DTO 的 int 状态字段有对应的 `_name` 文字字段
### 文档和注释
- [ ] 所有注释使用中文
- [ ] 导出函数/类型有文档注释
- [ ] API 路径注释与真实路由一致
### 幂等性
- [ ] 创建类写操作有 Redis 业务键防重
- [ ] 状态变更使用条件更新(`WHERE status = expected`
- [ ] 余额/库存变更使用乐观锁version 字段)
- [ ] 分布式锁使用 `defer` 确保释放
- [ ] Redis Key 定义在 `pkg/constants/redis.go`
### 越权防护规范
**三层防护机制**(基础设施已就绪,无需自建):
1. **路由层中间件**:粗粒度拦截(如企业账号禁止访问账号管理)
2. **Service 层业务检查**`middleware.CanManageShop()` / `middleware.CanManageEnterprise()` 细粒度验证;示例见 `internal/service/account/service.go`
3. **GORM Callback 自动过滤**代理按店铺层级、企业按企业ID已自动应用无需手动调用
统一错误返回:`errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")`(不区分"不存在"与"无权限",防止信息泄露)
### 幂等性规范
写操作按场景选择策略:
- **状态流转操作** → 状态条件更新(首选)
- **创建类操作(无状态可依赖)** → Redis 业务键防重 + 分布式锁(三层检测)
- **余额/库存数值更新** → 乐观锁(`version` 字段)
```go
// 策略1示例首选通过 WHERE 条件确保幂等RowsAffected=0 说明已被处理
result := tx.Model(&model.Order{}).
Where("id = ? AND payment_status = ?", orderID, model.PaymentStatusPending).
Updates(map[string]any{"payment_status": model.PaymentStatusPaid})
if result.RowsAffected == 0 { /* 已处理,检查当前状态 */ }
```
- Redis Key 定义在 `pkg/constants/redis.go`,分布式锁必须用 `defer` 释放
- 参考实现:`internal/service/order/service.go`
### 异步任务载荷规范Asynq
**必须遵守:**
- ✅ 调用 `queueClient.EnqueueTask()` 时,`payload` 必须传入 **struct 或 map**
-**禁止传入 `[]byte`**`EnqueueTask` 内部统一调用 `sonic.Marshal`,若传入 `[]byte` 会被 base64 编码成字符串Handler 反序列化时类型不匹配直接崩溃
```go
// ✅ 正确:传 struct
queueClient.EnqueueTask(ctx, constants.TaskTypeXxx, MyPayload{OrderID: id})
// ❌ 错误:预先序列化后传 []byte二次 Marshal → base64 编码)
payloadBytes, _ := sonic.Marshal(MyPayload{OrderID: id})
queueClient.EnqueueTask(ctx, constants.TaskTypeXxx, payloadBytes)
```
直接用 `asynq.NewTask` 入队时(绕过 `EnqueueTask`)才需要自己 Marshal。
---
### 审计日志规范
**适用场景**:任何敏感操作(账号管理、权限变更、数据删除等)
- 旧模块在 Service 层、新 DDD 模块在 Application UseCase 中注入审计能力,操作成功后调用 `LogOperation()`
- 必填字段:`OperatorID``OperationType``OperationDesc``BeforeData``AfterData`
- 异步写入Goroutine写入失败不影响业务失败时记录 Error 日志
**示例参考**`internal/service/account/service.go`
---
### ⚠️ 任务执行规范(必须遵守)
**提案中的 tasks.md 是契约,不可擅自变更:**
| 规则 | 说明 |
|------|------|
| ❌ 禁止跳过任务 | 每个任务都是经过规划的,不能因为"简单"或"显而易见"而跳过 |
| ❌ 禁止简化任务 | 不能将多个任务合并或简化执行,除非获得明确许可 |
| ❌ 禁止自作主张优化 | 发现可以优化的地方,必须先询问是否可以调整 |
| ✅ 必须逐项完成 | 按照 tasks.md 中的顺序逐一执行并标记完成 |
| ✅ 必须询问后变更 | 如需调整任务(简化/跳过/合并/优化),先询问用户确认 |
**询问示例**
> "我注意到任务 2.1 和 2.2 可以合并为一步完成,是否可以这样优化?"
> "任务 3.1 在当前实现中可能不需要,是否可以跳过?"
---
**详细规范和 OpenSpec 工作流请查看**: `@/openspec/AGENTS.md`