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

5.7 KiB
Raw Permalink Blame History

name, overview, todos
name overview todos
允许免费额度配置为 0 修复新用户免费额度配置无法设为 0 的问题:配置字段改为 *int64 以区分「未配置」与「显式 0」,未配置回落内置默认,显式 0 生效为 0。
id content status
change-setting-fields 将 UserDefaultQuotaSettingS 四字段改为 *int64 并更新注释 completed
id content status dependencies
fix-default-quota-value 改 defaultQuotaValue 为指针判空判据,允许 0 生效 completed
change-setting-fields
id content status dependencies
sync-constants-comment 同步 constants.go 内置默认常量注释语义 completed
fix-default-quota-value
id content status dependencies
sync-docs 同步 README、config.example.yaml 与接口文档说明 completed
fix-default-quota-value
id content status dependencies
build-verify 执行 go build ./... 与 go vet ./... 验证编译 completed
sync-constants-comment
sync-docs
id content status dependencies
review-config-usage 用 [subagent:code-explorer] 复核配置消费点与文档无旧语义残留 completed
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」