Files
zt-closet-backend/.codebuddy/plans/允许免费额度配置为_0_dc363311.md
T
2026-09-11 01:41:52 +08:00

92 lines
5.7 KiB
Markdown
Raw 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.
---
name: 允许免费额度配置为 0
overview: 修复新用户免费额度配置无法设为 0 的问题:配置字段改为 *int64 以区分「未配置」与「显式 0」,未配置回落内置默认,显式 0 生效为 0。
todos:
- id: change-setting-fields
content: 将 UserDefaultQuotaSettingS 四字段改为 *int64 并更新注释
status: completed
- id: fix-default-quota-value
content: 改 defaultQuotaValue 为指针判空判据,允许 0 生效
status: completed
dependencies:
- change-setting-fields
- id: sync-constants-comment
content: 同步 constants.go 内置默认常量注释语义
status: completed
dependencies:
- fix-default-quota-value
- id: sync-docs
content: 同步 README、config.example.yaml 与接口文档说明
status: completed
dependencies:
- fix-default-quota-value
- id: build-verify
content: 执行 go build ./... 与 go vet ./... 验证编译
status: completed
dependencies:
- sync-constants-comment
- sync-docs
- id: review-config-usage
content: 用 [subagent:code-explorer] 复核配置消费点与文档无旧语义残留
status: completed
dependencies:
- build-verify
---
## 产品概述
修复新用户免费额度初始化的配置语义缺陷:原有实现把「配置为 0」误判为「未配置」而回落内置默认,导致无法将某类免费额度配成 0(例如要求新用户 AI 推荐额度为 0、必须购买后才可用)。
## 核心功能
- **区分「未配置」与「显式 0」**:`UserDefaultQuota` 四个配置字段改为指针类型,未配置(nil)回落内置默认 20/3/10/5,显式配置 0 则生效为 0
- **保持兜底能力**:整段配置缺失、单个键缺失、配置为负数三种情况仍回落内置默认,不破坏既有部署
- **文档同步**:README 配置项说明、config.example.yaml 注释、接口文档额度说明统一为「允许配 0,未配置回落默认」
- **行为不变**:四类额度的初始化流程、流水记录、唯一索引幂等逻辑完全不动
## 技术栈
- Go 1.27,模块 `git.lzcwy.cn/zhengchiliu/zt-closet-backend`
- 配置:`pkg/setting`(viper 只读 `configs/config.yaml`,`ReadSection` 走 `UnmarshalKey`/mapstructure)
- 消费点唯一:`internal/service/quota.go` 的 `defaultQuotaValue`(grep 已确认无其它引用)
## 实现方案
采用**指针字段区分三态**:把 `UserDefaultQuotaSettingS` 的 `Cos/AI/OutfitShare/ClosetShare` 由 `int64` 改为 `*int64`。viper 解码时键存在则分配指针(含 0),键不存在则保持 nil,从而在类型层面区分「未配置」与「配置为 0」。
取值判据(`defaultQuotaValue`):
- `cfg == nil`(整段缺失)→ 内置默认
- `cfg.X == nil`(该键缺失)→ 内置默认
- `*cfg.X >= 0` → 用配置值(**0 生效,本次修复目标**)
- `*cfg.X < 0` → 内置默认(防御:负数会让 `total` 为负,破坏额度校验)
关键决策与取舍:
1. **用 `*int64` 而非把判据改成 `>= 0`**:后者能让 0 生效,但「键缺失」解码后同样是 0,会静默把兜底默认值变成 0——对现有未显式配置的环境是破坏性变更。指针方案零歧义,代价仅是多一层判空。
2. **用指针而非 `viper.IsSet` 逐键查询**:配置结构仍由结构体承载,`ReadSection` 用法与其它配置段一致,不引入第二套读取方式,符合现有架构。
3. **负数回落而非报错**:保持 best-effort 风格(与额度初始化失败仅告警不阻断一致),不引入启动期配置校验的新失败路径。
4. **保留内置常量值 20/3/10/5 不变**:只改判据与注释,不动 `ensureDefaultQuota` 的创建+流水事务逻辑。
性能与可靠性:该路径仅在首次登录注册时执行 4 次,指针判空为 O(1) 无额外开销;风险点仅为指针解引用,通过判空分支覆盖,并有 `go build`/`go vet` 兜底。
## 实现要点(防回归)
- 改字段类型后必须全量编译,`global.UserDefaultQuotaSetting` 的其它消费点若有解引用会直接编译报错(当前仅 quota.go 一处)。
- 判空顺序必须为 `cfg != nil && cfg.X != nil && *cfg.X >= 0`,避免 nil 解引用 panic。
- 注释需同步:结构体注释「值 <=0 视为未设置」、`constants.go:42`「配置缺省或 <=0 时回落」、`defaultQuotaValue` 函数注释,三处都要改成新语义,否则后续维护者会按旧语义改回去。
- 不要扩大范围:`Upload.MaxSizeMB`、`MattingModel.MaxRetry` 等其它 `<=0` 兜底语义不同(0 无业务意义),本次不动,仅在结论中提示。
- 不改动 `configs/config.yaml`(用户本地文件,已被 .gitignore 忽略)。
## 目录结构
```
pkg/setting/section.go # [MODIFY] UserDefaultQuotaSettingS 四字段 int64 → *int64;
# 注释改为「nil=未配置回落默认;非 nil 生效,允许 0」
internal/service/quota.go # [MODIFY] defaultQuotaValue 判据改为指针判空 + >=0 生效;
# 函数注释同步(未配置/负数回落,0 生效)
internal/service/constants.go # [MODIFY] 第 42 行注释「配置缺省或 <=0 时回落」→「未配置(nil)或负数时回落」
README.md # [MODIFY] 第 195 行配置表说明改为「未配置回落内置默认 20/3/10/5;可显式配 0」
configs/config.example.yaml # [MODIFY] UserDefaultQuota 段注释补充「可为 0 表示不赠送;留空则回落内置默认」
接口文档.md # [MODIFY] 第 488/1432/1446 行及 5.2 节额度表格补「可由 UserDefaultQuota 配置,允许为 0」
```