规范: 新增枚举与状态字段规范并修复代理充值状态描述错误
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

- 新增 docs/enum-status-standards.md:完整的枚举/状态规范,涵盖 int vs string 选择、起始值约定、description 格式统一、constants 唯一真相来源、Response DTO 必须提供 status_name
- 更新 dto-standards SKILL.md:加入枚举规范要点,AI 写 DTO 时自动触发
- 更新 AGENTS.md:常量管理章节和 Code Review 检查清单各加入枚举检查项
- 修复 agent_recharge_dto.go:description 从错误的 3 个状态改为正确的 5 个状态(原 3:已取消 实为 3:已完成)
- 修复 agent_recharge service:toResponse 补充 StatusName 中文字段,防止前端映射出错
This commit is contained in:
2026-04-11 11:25:58 +08:00
parent c807b99a91
commit e9ff14df0e
5 changed files with 449 additions and 58 deletions

View File

@@ -14,6 +14,8 @@ description: DTO 数据传输对象规范。创建或修改 DTO 文件、请求/
- 创建 `XXXRequest``XXXResponse``XXXReq``XXXResp` 结构体
- 添加或修改 API 接口的输入输出参数
---
## 必须项MUST
### 1. Description 标签规范
@@ -36,59 +38,150 @@ type CreateUserRequest struct {
}
```
### 2. 枚举字段必须列出所有可能值(中文)
### 2. 枚举字段int vs string 选择
**所有枚举类型字段必须在 `description` 中列出所有可能值和对应的中文含义**
**必须按以下规则选择类型,禁止混用:**
| 场景 | 类型 | 示例 |
|------|------|------|
| 状态类(生命周期阶段) | `int` | 待支付→已完成→已关闭 |
| 布尔状态(启用/禁用) | `int` | `0=禁用, 1=启用` |
| 类型/方式类(种类) | `string` | `"wechat"`, `"single_card"` |
| 平台/标识符类 | `string` | `"web"`, `"h5"`, `"all"` |
```go
// 用户类型
UserType int `json:"user_type" description:"用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)"`
// ✅ 状态 → int
Status int `json:"status"`
PaymentStatus int `json:"payment_status"`
// 角色类型
RoleType int `json:"role_type" description:"角色类型 (1:平台角色, 2:客户角色)"`
// 权限类型
PermType int `json:"perm_type" description:"权限类型 (1:菜单, 2:按钮)"`
// 状态字段
Status int `json:"status" description:"状态 (0:禁用, 1:启用)"`
// 适用端口
Platform string `json:"platform" description:"适用端口 (all:全部, web:Web后台, h5:H5端)"`
// ✅ 类型/方式 → string
PaymentMethod string `json:"payment_method" validate:"required,oneof=wechat offline"`
OrderType string `json:"order_type" validate:"required,oneof=single_card device"`
```
**禁止使用英文枚举值**
### 3. Int 状态值约定
#### 3.1 通用禁用/启用
**必须用全局常量,禁止自定义(尤其禁止 1=启用 2=禁用 这种反向写法)**
```go
UserType int `json:"user_type" description:"用户类型 (1:SuperAdmin, 2:Platform)"` // 错误!
// pkg/constants/constants.go 已定义,直接使用
StatusDisabled = 0 // 禁用
StatusEnabled = 1 // 启用
```
### 3. 验证标签与 OpenAPI 标签一致
✅ 正确:`description:"状态 (0:禁用, 1:启用)"`
❌ 禁止:`description:"状态 (1:启用, 2:禁用)"`
**所有验证约束必须同时在 `validate` 和 OpenAPI 标签中声明**
#### 3.2 生命周期状态
**1** 开始递增0 不使用(避免与 Go 零值混淆):
```go
const (
RechargeStatusPending = 1 // 待支付
RechargeStatusPaid = 2 // 已支付
RechargeStatusCompleted = 3 // 已完成
RechargeStatusClosed = 4 // 已关闭
)
```
### 4. 枚举列表必须从 constants 原文抄写
**DTO description 的枚举列表必须与 `pkg/constants/` 定义完全一致,不可凭记忆填写。**
操作步骤:
1. 先查/定义 `pkg/constants/` 中的枚举常量
2. 将常量注释**原文抄写**到 description
```go
// constants.go 中:
RechargeStatusPending = 1 // 待支付
RechargeStatusPaid = 2 // 已支付
RechargeStatusCompleted = 3 // 已完成
RechargeStatusClosed = 4 // 已关闭
RechargeStatusRefunded = 5 // 已退款
// DTO description 从上面抄:
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
```
❌ 禁止description 与 constants 不一致,是历史 bug 的根因):
```go
// constants 说 3=已完成description 却写 3:已取消
Status int `json:"status" description:"状态 (1:待支付, 2:已完成, 3:已取消)"`
```
### 5. description 格式标准
**统一格式**`字段含义 (值1:中文含义1, 值2:中文含义2)`
- 值与含义之间用**冒号** `:`(禁止用等号 `=`
- 多个值之间用**逗号加空格** `, `
- 含义必须是**中文**
```go
// ✅ 统一格式
Status int `description:"状态 (1:待支付, 2:已支付, 3:已完成)"`
Platform string `description:"适用端口 (all:全部, web:Web后台, h5:H5端)"`
// ❌ 格式混乱
Status int `description:"状态 (0=禁用, 1=启用)"` // 用等号
Status int `description:"0=禁用 1=启用"` // 无括号无逗号
```
### 6. Response DTO 的状态字段必须同时返回 int 和 text
**所有 Response DTO 中的 int 状态字段,必须同时提供对应的 `_name` 文字字段。**
原因:防止前端维护映射表出错(历史上已有因此产生 bug 的案例)。
```go
// ✅ Response DTO 标准写法
type XxxResponse struct {
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
StatusName string `json:"status_name" description:"状态名称(中文)"`
}
// toResponse 函数中赋值
func rechargeStatusName(status int) string {
switch status {
case constants.RechargeStatusPending:
return "待支付"
case constants.RechargeStatusCompleted:
return "已完成"
// ...
default:
return "未知"
}
}
```
字段命名约定:`status``status_name``payment_status``payment_status_name`
**例外**Request DTO查询过滤、创建请求不需要 `_name` 字段。
### 7. 验证标签与 OpenAPI 标签一致
```go
Username string `json:"username" validate:"required,min=3,max=50" required:"true" minLength:"3" maxLength:"50" description:"用户名"`
```
**标签对照表**
| validate 标签 | OpenAPI 标签 |
|--------------|--------------|
| `required` | `required:"true"` |
| `min=N,max=M`(数值) | `minimum:"N" maximum:"M"` |
| `min=N,max=M`(字符串) | `minLength:"N" maxLength:"M"` |
| `oneof=A B C` | description 中说明枚举值 |
| validate 标签 | OpenAPI 标签 | 说明 |
|--------------|--------------|------|
| `required` | `required:"true"` | 必填字段 |
| `min=N,max=M` | `minimum:"N" maximum:"M"` | 数值范围 |
| `min=N,max=M` (字符串) | `minLength:"N" maxLength:"M"` | 字符串长度 |
| `len=N` | `minLength:"N" maxLength:"N"` | 固定长度 |
| `oneof=A B C` | `description` 中说明 | 枚举值 |
### 4. 请求参数类型标签
**Query 参数和 Path 参数必须添加对应标签**
### 8. 请求参数类型标签
```go
// Query 参数
type ListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
UserType *int `json:"user_type" query:"user_type" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
Status *int `json:"status" query:"status" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭)"`
}
// Path 参数
@@ -97,43 +190,50 @@ type IDReq struct {
}
```
### 5. 响应 DTO 完整性
**所有响应 DTO 的字段都必须有完整的 `description` 标签**
### 9. 响应 DTO 完整性
```go
type AccountResponse struct {
ID uint `json:"id" description:"账号ID"`
Username string `json:"username" description:"用户名"`
UserType int `json:"user_type" description:"用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)"`
Status int `json:"status" description:"状态 (0:禁用, 1:启用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
ID uint `json:"id" description:"账号ID"`
Username string `json:"username" description:"用户名"`
UserType int `json:"user_type" description:"用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)"`
Status int `json:"status" description:"状态 (0:禁用, 1:启用)"`
StatusName string `json:"status_name" description:"状态名称(中文)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
```
---
## AI 助手必须执行的检查
**在创建或修改任何 DTO 文件后,必须执行以下检查:**
1.检查所有字段是否`description` 标签
2.检查枚举字段是否列出了所有可能值(中文
3.检查状态字段是否说明了 0 和 1 的含义
4.检查 validate 标签与 OpenAPI 标签是否一致
5.检查是否禁止使用行内注释替代 description
6.检查枚举值是否使用中文而非英文
7.重新生成 OpenAPI 文档验证:`go run cmd/gendocs/main.go`
1. ✅ 所有字段有 `description` 标签(无行内注释)
2.枚举类型选择正确(状态用 int类型/方式用 string
3.禁用/启用使用 `0=禁用, 1=启用`(禁止 1=启用 2=禁用)
4.description 枚举列表已从 `pkg/constants/` 原文抄写,无遗漏
5.description 格式统一(冒号 `:`,括号,逗号)
6.Response DTO 有 `_name` 伴生字段
7.validate 标签与 OpenAPI 标签一致
8. ✅ 重新生成 OpenAPI 文档验证:`go run cmd/gendocs/main.go`
**详细检查清单**: 参见 `docs/code-review-checklist.md`
**完整枚举规范**: 参见 [`docs/enum-status-standards.md`](../../docs/enum-status-standards.md)
---
## 常见枚举字段标准值
```go
// 用户类型
// 用户类型(从 constants.UserType* 抄)
description:"用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)"
// 角色类型
description:"角色类型 (1:平台角色, 2:客户角色)"
// 通用启用/禁用(从 constants.StatusEnabled/Disabled 抄)
description:"状态 (0:禁用, 1:启用)"
// 充值状态(从 constants.RechargeStatus* 抄)
description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"
// 权限类型
description:"权限类型 (1:菜单, 2:按钮)"
@@ -141,9 +241,6 @@ description:"权限类型 (1:菜单, 2:按钮)"
// 适用端口
description:"适用端口 (all:全部, web:Web后台, h5:H5端)"
// 状态
description:"状态 (0:禁用, 1:启用)"
// 店铺层级
description:"店铺层级 (1-7级)"
```

View File

@@ -113,6 +113,24 @@ Handler → Service → Store → Model
- 禁止硬编码字符串和 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)
### 注释规范
- **所有注释使用中文**,导出符号必须有文档注释(包、函数、类型、接口、常量)
@@ -220,6 +238,13 @@ Handler → Service → Store → Model
- [ ] 常量定义在 `pkg/constants/`
- [ ] 使用 Go 惯用法(非 Java 风格)
### 枚举与状态
- [ ] 状态类字段用 `int`,类型/方式类字段用 `string`
- [ ] 禁用/启用使用 `0=禁用, 1=启用`(禁止 `1=启用, 2=禁用`
- [ ] DTO description 枚举列表已从 `pkg/constants/` 原文抄写,无遗漏、无错误
- [ ] Response DTO 的 int 状态字段有对应的 `_name` 文字字段
### 文档和注释
- [ ] 所有注释使用中文

View File

@@ -0,0 +1,250 @@
# 枚举与状态字段规范
**背景**:系统历史上存在 int/string 枚举混用、description 与常量定义脱节、响应字段不统一等问题,导致前端映射错误、接口文档失真。本规范统一所有枚举相关写法。
---
## 1. 类型选择int vs string
### 选择规则
| 场景 | 类型 | 理由 |
|------|------|------|
| **状态类**:表示生命周期阶段(待支付→已完成→已关闭) | `int` | 便于范围查询、数值比较、DB 索引优化 |
| **布尔状态**:启用/禁用、激活/未激活 | `int` | 与全局常量统一,`0=禁用, 1=启用` |
| **类型/方式类**:支付方式、订单类型、运营商、平台 | `string` | 语义更清晰,无需记忆数字含义 |
| **标识符类**平台类型web/h5/all、角色来源 | `string` | 约定字符串值,自文档化 |
### 判断依据
```
这个字段表示"现在处于哪个阶段" → int
这个字段表示"它属于哪种类别/使用什么方式" → string
```
### ✅ 正确示例
```go
// 状态类 → int
Status int `json:"status"` // 充值状态1=待支付, 2=已支付...
PaymentStatus int `json:"payment_status"` // 订单支付状态
// 类型/方式类 → string
PaymentMethod string `json:"payment_method"` // "wechat" | "offline"
OrderType string `json:"order_type"` // "single_card" | "device"
Platform string `json:"platform"` // "web" | "h5" | "all"
```
### ❌ 错误示例
```go
// ❌ 状态用 string无法范围查询数字对比失效
Status string `json:"status"` // "pending" | "completed"
// ❌ 支付方式用 int前端必须维护不直观的数字映射
PaymentMethod int `json:"payment_method"` // 1=微信, 2=线下
```
---
## 2. Int 状态值约定
### 2.1 通用禁用/启用
**必须使用全局常量,禁止自定义**
```go
// pkg/constants/constants.go
StatusDisabled = 0 // 禁用
StatusEnabled = 1 // 启用
```
✅ 正确:
```go
Status int `gorm:"comment:状态(0-禁用 1-启用);default:1"`
```
❌ 禁止(与全局常量语义相反):
```go
// ❌ 不可以用 1=启用 2=禁用 这种方式
Status int `gorm:"comment:状态(1-启用 2-禁用)"`
```
### 2.2 生命周期状态
**1** 开始递增0 保留,避免与"未赋值/零值"混淆):
```go
// 充值状态(标准示例)
const (
RechargeStatusPending = 1 // 待支付
RechargeStatusPaid = 2 // 已支付
RechargeStatusCompleted = 3 // 已完成
RechargeStatusClosed = 4 // 已关闭
RechargeStatusRefunded = 5 // 已退款
)
```
### 2.3 特殊值
- 跳跃值(如 `99`)仅用于明确需要人工干预的异常状态,必须加注释说明原因
- 禁止在同一枚举中混用 0 起始和 1 起始
---
## 3. String 枚举值约定
- 全小写字母 + 下划线 `snake_case`(禁止驼峰、禁止大写)
- 必须在 `pkg/constants/` 定义常量,禁止硬编码字符串
- DTO 中必须加 `validate:"oneof=..."` 约束
```go
// pkg/constants/constants.go
const (
PaymentMethodWechat = "wechat" // 微信支付
PaymentMethodOffline = "offline" // 线下转账
)
// DTO 中
PaymentMethod string `json:"payment_method" validate:"required,oneof=wechat offline" description:"支付方式 (wechat:微信支付, offline:线下转账)"`
```
❌ 禁止:
```go
// ❌ 硬编码字符串
if record.PaymentMethod == "wechat" { ... }
// ❌ 大写或驼峰
const PaymentMethodWechat = "Wechat"
const PaymentMethodWechat = "WECHAT"
```
---
## 4. Constants 是唯一真相来源
**DTO 的 description 枚举列表必须与 `pkg/constants/` 定义完全一致。**
### 操作规程
1. 先在 `pkg/constants/` 定义(或查找已有)枚举常量及注释
2. 将常量的注释**原文抄写**到 DTO description
```go
// 步骤1constants.go 中定义
const (
RechargeStatusPending = 1 // 待支付
RechargeStatusPaid = 2 // 已支付
RechargeStatusCompleted = 3 // 已完成
RechargeStatusClosed = 4 // 已关闭
RechargeStatusRefunded = 5 // 已退款
)
// 步骤2DTO description 从上面抄写,格式统一
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
```
### 禁止行为
```go
// ❌ description 中的枚举值与 constants 不一致(本次 bug 根因)
// constants: RechargeStatusCompleted=3已完成
// dto 写的: 3:已取消 ← 完全错误!
Status int `json:"status" description:"状态 (1:待支付, 2:已完成, 3:已取消)"`
```
---
## 5. description 格式标准
**统一格式**`字段含义 (值1:中文含义1, 值2:中文含义2, ...)`
规则:
- 必须用**中文圆括号外**、**英文括号内**
- 值和含义之间用**冒号** `:`,不用等号 `=`
- 多个值之间用**逗号加空格** `, `
- 含义必须是**中文**,不可用英文
```go
// ✅ 统一格式
Status int `description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
Platform string `description:"适用端口 (all:全部, web:Web后台, h5:H5端)"`
UserType int `description:"用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)"`
// ❌ 格式混乱
Status int `description:"状态 (0=禁用, 1=启用)"` // 用等号
Status int `description:"0=禁用 1=启用"` // 无括号无逗号
Status int `description:"状态0待生效 1生效中 2已用完"` // 格式不统一
```
---
## 6. Response DTO 的状态字段
**所有 Response DTO 中的 int 状态字段,必须同时提供对应的 `xxx_name` 文字字段。**
这是防止前端映射错误的关键措施。
```go
// ✅ Response DTO 标准写法
type AgentRechargeResponse struct {
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
StatusName string `json:"status_name" description:"状态名称(中文)"`
// ...
}
// Service 层 toResponse 函数中赋值
func statusName(status int) string {
switch status {
case constants.RechargeStatusPending:
return "待支付"
case constants.RechargeStatusPaid:
return "已支付"
case constants.RechargeStatusCompleted:
return "已完成"
case constants.RechargeStatusClosed:
return "已关闭"
case constants.RechargeStatusRefunded:
return "已退款"
default:
return "未知"
}
}
```
**字段命名约定**
- `status``status_name`
- `payment_status``payment_status_name`
- `commission_status``commission_status_name`
**例外**Request DTO查询过滤、创建请求不需要 `_name` 字段。
---
## 7. Code Review 检查项
```
□ int/string 类型选择是否符合规则?
□ 禁/启用是否用了 0=禁用, 1=启用?(禁止 1=启用, 2=禁用)
□ string 枚举常量是否在 pkg/constants/ 定义?
□ dto description 是否从 constants 注释原文抄写?
□ description 格式是否统一(冒号、逗号、括号)?
□ Response DTO 是否同时有 status int 和 status_name string
□ 是否有遗漏的枚举值没有在 description 中列出?
```
---
## 8. 现存已知不一致(技术债,待修复)
| 文件 | 问题 | 正确值 |
|------|------|--------|
| `internal/model/shop_package_allocation.go` | `1-启用 2-禁用` | 应改为 `0-禁用 1-启用` |
| `internal/model/system.go` | `1-启用 2-禁用` | 需确认是否改 DB |
| `internal/model/dto/agent_recharge_dto.go` | description `2:已完成, 3:已取消` | 应为 `2:已支付, 3:已完成, 4:已关闭, 5:已退款` |
| `internal/model/dto/asset_wallet_dto.go` | 只有 `status_text``status int` | 需补充 `status int` |
> ⚠️ 已有数据的状态起始值(如 `1-启用 2-禁用`)改变前必须做 DB 数据迁移,改动前先评估影响。

View File

@@ -30,7 +30,8 @@ type AgentRechargeResponse struct {
PaymentChannel string `json:"payment_channel" description:"实际支付通道 (wechat_direct:微信直连, fuyou:富友, offline:线下转账)"`
PaymentConfigID *uint `json:"payment_config_id" description:"关联支付配置ID线下充值为null"`
PaymentTransactionID string `json:"payment_transaction_id" description:"第三方支付流水号"`
Status int `json:"status" description:"状态 (1:待支付, 2:已完成, 3:已取消)"`
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
StatusName string `json:"status_name" description:"状态名称(中文)"`
PaidAt *string `json:"paid_at" description:"支付时间"`
CompletedAt *string `json:"completed_at" description:"完成时间"`
CreatedAt string `json:"created_at" description:"创建时间"`
@@ -42,7 +43,7 @@ type AgentRechargeListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码默认1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页条数默认20最大100"`
ShopID *uint `json:"shop_id" query:"shop_id" description:"按店铺ID过滤"`
Status *int `json:"status" query:"status" description:"按状态过滤 (1:待支付, 2:已完成, 3:已取消)"`
Status *int `json:"status" query:"status" description:"按状态过滤 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
StartDate string `json:"start_date" query:"start_date" description:"创建时间起始日期(YYYY-MM-DD)"`
EndDate string `json:"end_date" query:"end_date" description:"创建时间截止日期(YYYY-MM-DD)"`
}

View File

@@ -469,6 +469,23 @@ func (s *Service) generateRechargeNo() string {
return fmt.Sprintf("%s%s%06d", constants.AgentRechargeOrderPrefix, timestamp, randomNum)
}
func agentRechargeStatusName(status int) string {
switch status {
case constants.RechargeStatusPending:
return "待支付"
case constants.RechargeStatusPaid:
return "已支付"
case constants.RechargeStatusCompleted:
return "已完成"
case constants.RechargeStatusClosed:
return "已关闭"
case constants.RechargeStatusRefunded:
return "已退款"
default:
return "未知"
}
}
// toResponse 将模型转换为响应 DTO
func toResponse(record *model.AgentRechargeRecord, shopName string) *dto.AgentRechargeResponse {
resp := &dto.AgentRechargeResponse{
@@ -480,6 +497,7 @@ func toResponse(record *model.AgentRechargeRecord, shopName string) *dto.AgentRe
Amount: record.Amount,
PaymentMethod: record.PaymentMethod,
Status: record.Status,
StatusName: agentRechargeStatusName(record.Status),
CreatedAt: record.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: record.UpdatedAt.Format("2006-01-02 15:04:05"),
}