docs: 更新 AGENTS.md 和 CLAUDE.md
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 6m28s
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 6m28s
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
39
AGENTS.md
39
AGENTS.md
@@ -38,6 +38,7 @@ handlers := &bootstrap.Handlers{
|
|||||||
## 语言要求
|
## 语言要求
|
||||||
|
|
||||||
**必须遵守:**
|
**必须遵守:**
|
||||||
|
|
||||||
- 永远用中文交互
|
- 永远用中文交互
|
||||||
- 注释必须使用中文
|
- 注释必须使用中文
|
||||||
- 文档必须使用中文
|
- 文档必须使用中文
|
||||||
@@ -63,6 +64,7 @@ handlers := &bootstrap.Handlers{
|
|||||||
| 缓存 | Redis 6.0+ |
|
| 缓存 | Redis 6.0+ |
|
||||||
|
|
||||||
**禁止:**
|
**禁止:**
|
||||||
|
|
||||||
- 直接使用 `database/sql`(必须通过 GORM)
|
- 直接使用 `database/sql`(必须通过 GORM)
|
||||||
- 使用 `net/http` 替代 Fiber
|
- 使用 `net/http` 替代 Fiber
|
||||||
- 使用 `encoding/json` 替代 sonic(除非必要)
|
- 使用 `encoding/json` 替代 sonic(除非必要)
|
||||||
@@ -83,21 +85,25 @@ Handler → Service → Store → Model
|
|||||||
## 核心原则
|
## 核心原则
|
||||||
|
|
||||||
### 错误处理
|
### 错误处理
|
||||||
|
|
||||||
- 所有错误必须在 `pkg/errors/` 中定义
|
- 所有错误必须在 `pkg/errors/` 中定义
|
||||||
- 使用统一错误码系统
|
- 使用统一错误码系统
|
||||||
- Handler 层通过返回 `error` 传递给全局 ErrorHandler
|
- Handler 层通过返回 `error` 传递给全局 ErrorHandler
|
||||||
|
|
||||||
#### 错误报错规范(必须遵守)
|
#### 错误报错规范(必须遵守)
|
||||||
|
|
||||||
- Handler 层禁止直接返回/拼接底层错误信息给客户端(例如 `"参数验证失败: "+err.Error()`、`err.Error()`)
|
- Handler 层禁止直接返回/拼接底层错误信息给客户端(例如 `"参数验证失败: "+err.Error()`、`err.Error()`)
|
||||||
- 参数校验失败:对外统一返回 `errors.New(errors.CodeInvalidParam)`(详细校验错误写日志)
|
- 参数校验失败:对外统一返回 `errors.New(errors.CodeInvalidParam)`(详细校验错误写日志)
|
||||||
- Service 层禁止对外返回 `fmt.Errorf(...)`,必须返回 `errors.New(...)` 或 `errors.Wrap(...)`
|
- Service 层禁止对外返回 `fmt.Errorf(...)`,必须返回 `errors.New(...)` 或 `errors.Wrap(...)`
|
||||||
- 约定用法:`errors.New(code[, msg])`、`errors.Wrap(code, err[, msg])`
|
- 约定用法:`errors.New(code[, msg])`、`errors.Wrap(code, err[, msg])`
|
||||||
|
|
||||||
### 响应格式
|
### 响应格式
|
||||||
|
|
||||||
- 所有 API 响应使用 `pkg/response/` 的统一格式
|
- 所有 API 响应使用 `pkg/response/` 的统一格式
|
||||||
- 格式: `{code, msg, data, timestamp}`
|
- 格式: `{code, msg, data, timestamp}`
|
||||||
|
|
||||||
### 常量管理
|
### 常量管理
|
||||||
|
|
||||||
- 所有常量定义在 `pkg/constants/`
|
- 所有常量定义在 `pkg/constants/`
|
||||||
- Redis key 使用函数生成: `Redis{Module}{Purpose}Key(params...)`
|
- Redis key 使用函数生成: `Redis{Module}{Purpose}Key(params...)`
|
||||||
- 禁止硬编码字符串和 magic numbers
|
- 禁止硬编码字符串和 magic numbers
|
||||||
@@ -177,6 +183,7 @@ func (s *UsageService) ActivateByRealname(ctx context.Context, cardID uint) erro
|
|||||||
#### 未导出符号的注释
|
#### 未导出符号的注释
|
||||||
|
|
||||||
未导出(小写)的函数/方法:
|
未导出(小写)的函数/方法:
|
||||||
|
|
||||||
- **简单逻辑**(< 15 行):可以不加注释
|
- **简单逻辑**(< 15 行):可以不加注释
|
||||||
- **复杂逻辑**(≥ 15 行)或 **非显而易见的算法**:必须加注释
|
- **复杂逻辑**(≥ 15 行)或 **非显而易见的算法**:必须加注释
|
||||||
|
|
||||||
@@ -199,6 +206,7 @@ func (s *Service) buildPermissionTree(permissions []*model.Permission) []*dto.Pe
|
|||||||
| 临时方案/兼容逻辑 | 标注 TODO 或说明背景 |
|
| 临时方案/兼容逻辑 | 标注 TODO 或说明背景 |
|
||||||
|
|
||||||
**✅ 好的内联注释(解释为什么)**:
|
**✅ 好的内联注释(解释为什么)**:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 使用 Redis 分布式锁防止并发重复创建,锁超时 10 秒
|
// 使用 Redis 分布式锁防止并发重复创建,锁超时 10 秒
|
||||||
if !s.acquireLock(ctx, lockKey, 10*time.Second) {
|
if !s.acquireLock(ctx, lockKey, 10*time.Second) {
|
||||||
@@ -212,6 +220,7 @@ if err := s.freezeCommission(ctx, tx, orderID); err != nil {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**❌ 废话注释(禁止)**:
|
**❌ 废话注释(禁止)**:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 获取用户ID ← 禁止:代码本身已经很清楚
|
// 获取用户ID ← 禁止:代码本身已经很清楚
|
||||||
userID := middleware.GetUserIDFromContext(ctx)
|
userID := middleware.GetUserIDFromContext(ctx)
|
||||||
@@ -248,6 +257,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Go 代码风格
|
### Go 代码风格
|
||||||
|
|
||||||
- 使用 `gofmt` 格式化
|
- 使用 `gofmt` 格式化
|
||||||
- 遵循 [Effective Go](https://go.dev/doc/effective_go)
|
- 遵循 [Effective Go](https://go.dev/doc/effective_go)
|
||||||
- 包名: 简短、小写、单数、无下划线
|
- 包名: 简短、小写、单数、无下划线
|
||||||
@@ -256,6 +266,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
## 数据库设计
|
## 数据库设计
|
||||||
|
|
||||||
**核心规则:**
|
**核心规则:**
|
||||||
|
|
||||||
- ❌ 禁止建立外键约束
|
- ❌ 禁止建立外键约束
|
||||||
- ❌ 禁止使用 GORM 关联关系标签(foreignKey、hasMany、belongsTo)
|
- ❌ 禁止使用 GORM 关联关系标签(foreignKey、hasMany、belongsTo)
|
||||||
- ✅ 关联通过存储 ID 字段手动维护
|
- ✅ 关联通过存储 ID 字段手动维护
|
||||||
@@ -264,6 +275,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
## Go 惯用法 vs Java 风格
|
## Go 惯用法 vs Java 风格
|
||||||
|
|
||||||
### ✅ Go 风格(推荐)
|
### ✅ Go 风格(推荐)
|
||||||
|
|
||||||
- 扁平化包结构(最多 2-3 层)
|
- 扁平化包结构(最多 2-3 层)
|
||||||
- 小而专注的接口(1-3 个方法)
|
- 小而专注的接口(1-3 个方法)
|
||||||
- 直接访问导出字段(不用 getter/setter)
|
- 直接访问导出字段(不用 getter/setter)
|
||||||
@@ -271,6 +283,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
- 显式错误返回和检查
|
- 显式错误返回和检查
|
||||||
|
|
||||||
### ❌ Java 风格(禁止)
|
### ❌ Java 风格(禁止)
|
||||||
|
|
||||||
- 过度抽象(不必要的接口、工厂)
|
- 过度抽象(不必要的接口、工厂)
|
||||||
- Getter/Setter 方法
|
- Getter/Setter 方法
|
||||||
- 深层继承层次
|
- 深层继承层次
|
||||||
@@ -282,6 +295,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
**本项目不使用任何形式的自动化测试代码。**
|
**本项目不使用任何形式的自动化测试代码。**
|
||||||
|
|
||||||
**绝对禁止:**
|
**绝对禁止:**
|
||||||
|
|
||||||
- ❌ **禁止编写单元测试** - 无论任何场景
|
- ❌ **禁止编写单元测试** - 无论任何场景
|
||||||
- ❌ **禁止编写集成测试** - 无论任何场景
|
- ❌ **禁止编写集成测试** - 无论任何场景
|
||||||
- ❌ **禁止编写验收测试** - 无论任何场景
|
- ❌ **禁止编写验收测试** - 无论任何场景
|
||||||
@@ -292,15 +306,18 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
- ❌ **禁止在文档中提及测试要求** - 规范、设计文档均不讨论测试
|
- ❌ **禁止在文档中提及测试要求** - 规范、设计文档均不讨论测试
|
||||||
|
|
||||||
**唯一例外:**
|
**唯一例外:**
|
||||||
|
|
||||||
- ✅ **仅当用户明确要求**时才编写测试代码
|
- ✅ **仅当用户明确要求**时才编写测试代码
|
||||||
- ✅ 用户必须主动说明"请写测试"或"需要测试"
|
- ✅ 用户必须主动说明"请写测试"或"需要测试"
|
||||||
|
|
||||||
**原因说明:**
|
**原因说明:**
|
||||||
|
|
||||||
- 业务系统的正确性通过人工验证和生产环境监控保证
|
- 业务系统的正确性通过人工验证和生产环境监控保证
|
||||||
- 测试代码的维护成本高于价值
|
- 测试代码的维护成本高于价值
|
||||||
- 快速迭代优先于测试覆盖率
|
- 快速迭代优先于测试覆盖率
|
||||||
|
|
||||||
**替代方案:**
|
**替代方案:**
|
||||||
|
|
||||||
- 使用 PostgreSQL MCP 工具手动验证数据
|
- 使用 PostgreSQL MCP 工具手动验证数据
|
||||||
- 使用 Postman/curl 手动测试 API
|
- 使用 Postman/curl 手动测试 API
|
||||||
- 依赖生产环境日志和监控发现问题
|
- 依赖生产环境日志和监控发现问题
|
||||||
@@ -349,23 +366,27 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
## Code Review 检查清单
|
## Code Review 检查清单
|
||||||
|
|
||||||
### 错误处理
|
### 错误处理
|
||||||
|
|
||||||
- [ ] Service 层无 `fmt.Errorf` 对外返回
|
- [ ] Service 层无 `fmt.Errorf` 对外返回
|
||||||
- [ ] Handler 层参数校验不泄露细节
|
- [ ] Handler 层参数校验不泄露细节
|
||||||
- [ ] 错误码使用正确(4xx vs 5xx)
|
- [ ] 错误码使用正确(4xx vs 5xx)
|
||||||
- [ ] 错误日志完整(包含上下文)
|
- [ ] 错误日志完整(包含上下文)
|
||||||
|
|
||||||
### 代码质量
|
### 代码质量
|
||||||
|
|
||||||
- [ ] 遵循 Handler → Service → Store → Model 分层
|
- [ ] 遵循 Handler → Service → Store → Model 分层
|
||||||
- [ ] 函数长度 ≤ 100 行(核心逻辑 ≤ 50 行)
|
- [ ] 函数长度 ≤ 100 行(核心逻辑 ≤ 50 行)
|
||||||
- [ ] 常量定义在 `pkg/constants/`
|
- [ ] 常量定义在 `pkg/constants/`
|
||||||
- [ ] 使用 Go 惯用法(非 Java 风格)
|
- [ ] 使用 Go 惯用法(非 Java 风格)
|
||||||
|
|
||||||
### 文档和注释
|
### 文档和注释
|
||||||
|
|
||||||
- [ ] 所有注释使用中文
|
- [ ] 所有注释使用中文
|
||||||
- [ ] 导出函数/类型有文档注释
|
- [ ] 导出函数/类型有文档注释
|
||||||
- [ ] API 路径注释与真实路由一致
|
- [ ] API 路径注释与真实路由一致
|
||||||
|
|
||||||
### 幂等性
|
### 幂等性
|
||||||
|
|
||||||
- [ ] 创建类写操作有 Redis 业务键防重
|
- [ ] 创建类写操作有 Redis 业务键防重
|
||||||
- [ ] 状态变更使用条件更新(`WHERE status = expected`)
|
- [ ] 状态变更使用条件更新(`WHERE status = expected`)
|
||||||
- [ ] 余额/库存变更使用乐观锁(version 字段)
|
- [ ] 余额/库存变更使用乐观锁(version 字段)
|
||||||
@@ -381,6 +402,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
1. **路由层中间件**(粗粒度拦截)
|
1. **路由层中间件**(粗粒度拦截)
|
||||||
- 用于明显的权限限制(如企业账号禁止访问账号管理)
|
- 用于明显的权限限制(如企业账号禁止访问账号管理)
|
||||||
- 示例:
|
- 示例:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
group.Use(func(c *fiber.Ctx) error {
|
group.Use(func(c *fiber.Ctx) error {
|
||||||
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
||||||
@@ -404,6 +426,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
- 无需手动调用
|
- 无需手动调用
|
||||||
|
|
||||||
**统一错误返回**:
|
**统一错误返回**:
|
||||||
|
|
||||||
- 越权访问统一返回:`errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")`
|
- 越权访问统一返回:`errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")`
|
||||||
- 不区分"不存在"和"无权限",防止信息泄露
|
- 不区分"不存在"和"无权限",防止信息泄露
|
||||||
|
|
||||||
@@ -522,6 +545,7 @@ func RedisOrderCreateLockKey(carrierType string, carrierID uint) string
|
|||||||
**使用方式**:
|
**使用方式**:
|
||||||
|
|
||||||
1. **Service 层集成审计日志**:
|
1. **Service 层集成审计日志**:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store *Store
|
store *Store
|
||||||
@@ -585,3 +609,18 @@ func RedisOrderCreateLockKey(carrierType string, carrierID uint) string
|
|||||||
> "任务 3.1 在当前实现中可能不需要,是否可以跳过?"
|
> "任务 3.1 在当前实现中可能不需要,是否可以跳过?"
|
||||||
|
|
||||||
**详细规范和 OpenSpec 工作流请查看**: `@/openspec/AGENTS.md`
|
**详细规范和 OpenSpec 工作流请查看**: `@/openspec/AGENTS.md`
|
||||||
|
|
||||||
|
# English Learning Mode
|
||||||
|
|
||||||
|
The user is learning English through practical use. Apply these rules in every conversation:
|
||||||
|
|
||||||
|
1. **Always respond in Chinese** — regardless of whether the user writes in English or Chinese.
|
||||||
|
|
||||||
|
2. **When the user writes in English**, append a one-line correction at the end of your response in this format:
|
||||||
|
→ `[natural version of what they wrote]`
|
||||||
|
No explanation needed — just the corrected phrase.
|
||||||
|
|
||||||
|
3. **When the user mixes Chinese into English** (e.g., "I want to 实现 dark mode"), translate the Chinese word/phrase inline and continue naturally. Do not make a
|
||||||
|
big deal of it.
|
||||||
|
|
||||||
|
4. **Never interrupt the flow** to give grammar lessons. Corrections are silent and brief — the user's focus is on the task, not the language.
|
||||||
|
|||||||
39
CLAUDE.md
39
CLAUDE.md
@@ -38,6 +38,7 @@ handlers := &bootstrap.Handlers{
|
|||||||
## 语言要求
|
## 语言要求
|
||||||
|
|
||||||
**必须遵守:**
|
**必须遵守:**
|
||||||
|
|
||||||
- 永远用中文交互
|
- 永远用中文交互
|
||||||
- 注释必须使用中文
|
- 注释必须使用中文
|
||||||
- 文档必须使用中文
|
- 文档必须使用中文
|
||||||
@@ -63,6 +64,7 @@ handlers := &bootstrap.Handlers{
|
|||||||
| 缓存 | Redis 6.0+ |
|
| 缓存 | Redis 6.0+ |
|
||||||
|
|
||||||
**禁止:**
|
**禁止:**
|
||||||
|
|
||||||
- 直接使用 `database/sql`(必须通过 GORM)
|
- 直接使用 `database/sql`(必须通过 GORM)
|
||||||
- 使用 `net/http` 替代 Fiber
|
- 使用 `net/http` 替代 Fiber
|
||||||
- 使用 `encoding/json` 替代 sonic(除非必要)
|
- 使用 `encoding/json` 替代 sonic(除非必要)
|
||||||
@@ -83,21 +85,25 @@ Handler → Service → Store → Model
|
|||||||
## 核心原则
|
## 核心原则
|
||||||
|
|
||||||
### 错误处理
|
### 错误处理
|
||||||
|
|
||||||
- 所有错误必须在 `pkg/errors/` 中定义
|
- 所有错误必须在 `pkg/errors/` 中定义
|
||||||
- 使用统一错误码系统
|
- 使用统一错误码系统
|
||||||
- Handler 层通过返回 `error` 传递给全局 ErrorHandler
|
- Handler 层通过返回 `error` 传递给全局 ErrorHandler
|
||||||
|
|
||||||
#### 错误报错规范(必须遵守)
|
#### 错误报错规范(必须遵守)
|
||||||
|
|
||||||
- Handler 层禁止直接返回/拼接底层错误信息给客户端(例如 `"参数验证失败: "+err.Error()`、`err.Error()`)
|
- Handler 层禁止直接返回/拼接底层错误信息给客户端(例如 `"参数验证失败: "+err.Error()`、`err.Error()`)
|
||||||
- 参数校验失败:对外统一返回 `errors.New(errors.CodeInvalidParam)`(详细校验错误写日志)
|
- 参数校验失败:对外统一返回 `errors.New(errors.CodeInvalidParam)`(详细校验错误写日志)
|
||||||
- Service 层禁止对外返回 `fmt.Errorf(...)`,必须返回 `errors.New(...)` 或 `errors.Wrap(...)`
|
- Service 层禁止对外返回 `fmt.Errorf(...)`,必须返回 `errors.New(...)` 或 `errors.Wrap(...)`
|
||||||
- 约定用法:`errors.New(code[, msg])`、`errors.Wrap(code, err[, msg])`
|
- 约定用法:`errors.New(code[, msg])`、`errors.Wrap(code, err[, msg])`
|
||||||
|
|
||||||
### 响应格式
|
### 响应格式
|
||||||
|
|
||||||
- 所有 API 响应使用 `pkg/response/` 的统一格式
|
- 所有 API 响应使用 `pkg/response/` 的统一格式
|
||||||
- 格式: `{code, msg, data, timestamp}`
|
- 格式: `{code, msg, data, timestamp}`
|
||||||
|
|
||||||
### 常量管理
|
### 常量管理
|
||||||
|
|
||||||
- 所有常量定义在 `pkg/constants/`
|
- 所有常量定义在 `pkg/constants/`
|
||||||
- Redis key 使用函数生成: `Redis{Module}{Purpose}Key(params...)`
|
- Redis key 使用函数生成: `Redis{Module}{Purpose}Key(params...)`
|
||||||
- 禁止硬编码字符串和 magic numbers
|
- 禁止硬编码字符串和 magic numbers
|
||||||
@@ -177,6 +183,7 @@ func (s *UsageService) ActivateByRealname(ctx context.Context, cardID uint) erro
|
|||||||
#### 未导出符号的注释
|
#### 未导出符号的注释
|
||||||
|
|
||||||
未导出(小写)的函数/方法:
|
未导出(小写)的函数/方法:
|
||||||
|
|
||||||
- **简单逻辑**(< 15 行):可以不加注释
|
- **简单逻辑**(< 15 行):可以不加注释
|
||||||
- **复杂逻辑**(≥ 15 行)或 **非显而易见的算法**:必须加注释
|
- **复杂逻辑**(≥ 15 行)或 **非显而易见的算法**:必须加注释
|
||||||
|
|
||||||
@@ -199,6 +206,7 @@ func (s *Service) buildPermissionTree(permissions []*model.Permission) []*dto.Pe
|
|||||||
| 临时方案/兼容逻辑 | 标注 TODO 或说明背景 |
|
| 临时方案/兼容逻辑 | 标注 TODO 或说明背景 |
|
||||||
|
|
||||||
**✅ 好的内联注释(解释为什么)**:
|
**✅ 好的内联注释(解释为什么)**:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 使用 Redis 分布式锁防止并发重复创建,锁超时 10 秒
|
// 使用 Redis 分布式锁防止并发重复创建,锁超时 10 秒
|
||||||
if !s.acquireLock(ctx, lockKey, 10*time.Second) {
|
if !s.acquireLock(ctx, lockKey, 10*time.Second) {
|
||||||
@@ -212,6 +220,7 @@ if err := s.freezeCommission(ctx, tx, orderID); err != nil {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**❌ 废话注释(禁止)**:
|
**❌ 废话注释(禁止)**:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 获取用户ID ← 禁止:代码本身已经很清楚
|
// 获取用户ID ← 禁止:代码本身已经很清楚
|
||||||
userID := middleware.GetUserIDFromContext(ctx)
|
userID := middleware.GetUserIDFromContext(ctx)
|
||||||
@@ -248,6 +257,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Go 代码风格
|
### Go 代码风格
|
||||||
|
|
||||||
- 使用 `gofmt` 格式化
|
- 使用 `gofmt` 格式化
|
||||||
- 遵循 [Effective Go](https://go.dev/doc/effective_go)
|
- 遵循 [Effective Go](https://go.dev/doc/effective_go)
|
||||||
- 包名: 简短、小写、单数、无下划线
|
- 包名: 简短、小写、单数、无下划线
|
||||||
@@ -256,6 +266,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
## 数据库设计
|
## 数据库设计
|
||||||
|
|
||||||
**核心规则:**
|
**核心规则:**
|
||||||
|
|
||||||
- ❌ 禁止建立外键约束
|
- ❌ 禁止建立外键约束
|
||||||
- ❌ 禁止使用 GORM 关联关系标签(foreignKey、hasMany、belongsTo)
|
- ❌ 禁止使用 GORM 关联关系标签(foreignKey、hasMany、belongsTo)
|
||||||
- ✅ 关联通过存储 ID 字段手动维护
|
- ✅ 关联通过存储 ID 字段手动维护
|
||||||
@@ -264,6 +275,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
## Go 惯用法 vs Java 风格
|
## Go 惯用法 vs Java 风格
|
||||||
|
|
||||||
### ✅ Go 风格(推荐)
|
### ✅ Go 风格(推荐)
|
||||||
|
|
||||||
- 扁平化包结构(最多 2-3 层)
|
- 扁平化包结构(最多 2-3 层)
|
||||||
- 小而专注的接口(1-3 个方法)
|
- 小而专注的接口(1-3 个方法)
|
||||||
- 直接访问导出字段(不用 getter/setter)
|
- 直接访问导出字段(不用 getter/setter)
|
||||||
@@ -271,6 +283,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
- 显式错误返回和检查
|
- 显式错误返回和检查
|
||||||
|
|
||||||
### ❌ Java 风格(禁止)
|
### ❌ Java 风格(禁止)
|
||||||
|
|
||||||
- 过度抽象(不必要的接口、工厂)
|
- 过度抽象(不必要的接口、工厂)
|
||||||
- Getter/Setter 方法
|
- Getter/Setter 方法
|
||||||
- 深层继承层次
|
- 深层继承层次
|
||||||
@@ -282,6 +295,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
**本项目不使用任何形式的自动化测试代码。**
|
**本项目不使用任何形式的自动化测试代码。**
|
||||||
|
|
||||||
**绝对禁止:**
|
**绝对禁止:**
|
||||||
|
|
||||||
- ❌ **禁止编写单元测试** - 无论任何场景
|
- ❌ **禁止编写单元测试** - 无论任何场景
|
||||||
- ❌ **禁止编写集成测试** - 无论任何场景
|
- ❌ **禁止编写集成测试** - 无论任何场景
|
||||||
- ❌ **禁止编写验收测试** - 无论任何场景
|
- ❌ **禁止编写验收测试** - 无论任何场景
|
||||||
@@ -292,15 +306,18 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
- ❌ **禁止在文档中提及测试要求** - 规范、设计文档均不讨论测试
|
- ❌ **禁止在文档中提及测试要求** - 规范、设计文档均不讨论测试
|
||||||
|
|
||||||
**唯一例外:**
|
**唯一例外:**
|
||||||
|
|
||||||
- ✅ **仅当用户明确要求**时才编写测试代码
|
- ✅ **仅当用户明确要求**时才编写测试代码
|
||||||
- ✅ 用户必须主动说明"请写测试"或"需要测试"
|
- ✅ 用户必须主动说明"请写测试"或"需要测试"
|
||||||
|
|
||||||
**原因说明:**
|
**原因说明:**
|
||||||
|
|
||||||
- 业务系统的正确性通过人工验证和生产环境监控保证
|
- 业务系统的正确性通过人工验证和生产环境监控保证
|
||||||
- 测试代码的维护成本高于价值
|
- 测试代码的维护成本高于价值
|
||||||
- 快速迭代优先于测试覆盖率
|
- 快速迭代优先于测试覆盖率
|
||||||
|
|
||||||
**替代方案:**
|
**替代方案:**
|
||||||
|
|
||||||
- 使用 PostgreSQL MCP 工具手动验证数据
|
- 使用 PostgreSQL MCP 工具手动验证数据
|
||||||
- 使用 Postman/curl 手动测试 API
|
- 使用 Postman/curl 手动测试 API
|
||||||
- 依赖生产环境日志和监控发现问题
|
- 依赖生产环境日志和监控发现问题
|
||||||
@@ -349,23 +366,27 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
## Code Review 检查清单
|
## Code Review 检查清单
|
||||||
|
|
||||||
### 错误处理
|
### 错误处理
|
||||||
|
|
||||||
- [ ] Service 层无 `fmt.Errorf` 对外返回
|
- [ ] Service 层无 `fmt.Errorf` 对外返回
|
||||||
- [ ] Handler 层参数校验不泄露细节
|
- [ ] Handler 层参数校验不泄露细节
|
||||||
- [ ] 错误码使用正确(4xx vs 5xx)
|
- [ ] 错误码使用正确(4xx vs 5xx)
|
||||||
- [ ] 错误日志完整(包含上下文)
|
- [ ] 错误日志完整(包含上下文)
|
||||||
|
|
||||||
### 代码质量
|
### 代码质量
|
||||||
|
|
||||||
- [ ] 遵循 Handler → Service → Store → Model 分层
|
- [ ] 遵循 Handler → Service → Store → Model 分层
|
||||||
- [ ] 函数长度 ≤ 100 行(核心逻辑 ≤ 50 行)
|
- [ ] 函数长度 ≤ 100 行(核心逻辑 ≤ 50 行)
|
||||||
- [ ] 常量定义在 `pkg/constants/`
|
- [ ] 常量定义在 `pkg/constants/`
|
||||||
- [ ] 使用 Go 惯用法(非 Java 风格)
|
- [ ] 使用 Go 惯用法(非 Java 风格)
|
||||||
|
|
||||||
### 文档和注释
|
### 文档和注释
|
||||||
|
|
||||||
- [ ] 所有注释使用中文
|
- [ ] 所有注释使用中文
|
||||||
- [ ] 导出函数/类型有文档注释
|
- [ ] 导出函数/类型有文档注释
|
||||||
- [ ] API 路径注释与真实路由一致
|
- [ ] API 路径注释与真实路由一致
|
||||||
|
|
||||||
### 幂等性
|
### 幂等性
|
||||||
|
|
||||||
- [ ] 创建类写操作有 Redis 业务键防重
|
- [ ] 创建类写操作有 Redis 业务键防重
|
||||||
- [ ] 状态变更使用条件更新(`WHERE status = expected`)
|
- [ ] 状态变更使用条件更新(`WHERE status = expected`)
|
||||||
- [ ] 余额/库存变更使用乐观锁(version 字段)
|
- [ ] 余额/库存变更使用乐观锁(version 字段)
|
||||||
@@ -381,6 +402,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
1. **路由层中间件**(粗粒度拦截)
|
1. **路由层中间件**(粗粒度拦截)
|
||||||
- 用于明显的权限限制(如企业账号禁止访问账号管理)
|
- 用于明显的权限限制(如企业账号禁止访问账号管理)
|
||||||
- 示例:
|
- 示例:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
group.Use(func(c *fiber.Ctx) error {
|
group.Use(func(c *fiber.Ctx) error {
|
||||||
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
||||||
@@ -404,6 +426,7 @@ func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
|||||||
- 无需手动调用
|
- 无需手动调用
|
||||||
|
|
||||||
**统一错误返回**:
|
**统一错误返回**:
|
||||||
|
|
||||||
- 越权访问统一返回:`errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")`
|
- 越权访问统一返回:`errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")`
|
||||||
- 不区分"不存在"和"无权限",防止信息泄露
|
- 不区分"不存在"和"无权限",防止信息泄露
|
||||||
|
|
||||||
@@ -522,6 +545,7 @@ func RedisOrderCreateLockKey(carrierType string, carrierID uint) string
|
|||||||
**使用方式**:
|
**使用方式**:
|
||||||
|
|
||||||
1. **Service 层集成审计日志**:
|
1. **Service 层集成审计日志**:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store *Store
|
store *Store
|
||||||
@@ -585,3 +609,18 @@ func RedisOrderCreateLockKey(carrierType string, carrierID uint) string
|
|||||||
> "任务 3.1 在当前实现中可能不需要,是否可以跳过?"
|
> "任务 3.1 在当前实现中可能不需要,是否可以跳过?"
|
||||||
|
|
||||||
**详细规范和 OpenSpec 工作流请查看**: `@/openspec/AGENTS.md`
|
**详细规范和 OpenSpec 工作流请查看**: `@/openspec/AGENTS.md`
|
||||||
|
|
||||||
|
# English Learning Mode
|
||||||
|
|
||||||
|
The user is learning English through practical use. Apply these rules in every conversation:
|
||||||
|
|
||||||
|
1. **Always respond in Chinese** — regardless of whether the user writes in English or Chinese.
|
||||||
|
|
||||||
|
2. **When the user writes in English**, append a one-line correction at the end of your response in this format:
|
||||||
|
→ `[natural version of what they wrote]`
|
||||||
|
No explanation needed — just the corrected phrase.
|
||||||
|
|
||||||
|
3. **When the user mixes Chinese into English** (e.g., "I want to 实现 dark mode"), translate the Chinese word/phrase inline and continue naturally. Do not make a
|
||||||
|
big deal of it.
|
||||||
|
|
||||||
|
4. **Never interrupt the flow** to give grammar lessons. Corrections are silent and brief — the user's focus is on the task, not the language.
|
||||||
|
|||||||
Reference in New Issue
Block a user