Files
break 6333f4ad13
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m57s
fix(代理分销注册): 校验手机号/用户名/店铺编号唯一性并支持驳回后重注册
- 提交写事务内先取事务级 advisory lock,再校验既有未删除账号/店铺与其它待审批申请:
  手机号 1014、用户名 1013、店铺编号 1031、待审批占用 1007,冲突不落库且不消费短信验证码
- 已驳回(含通过后撤销)与已通过的终态记录不阻塞重新注册,形成新记录与新审批实例
- 并发同关键字段提交串行裁决,同一关键字段至多一条待审批记录
- 审批通过建店建号前复检关键字段,冲突返回可定位错误并整体回滚,不再以裸数据库错误收场
- 归档 Change fix-agent-distribution-registration-duplicate-guard 并同步主 Spec

验证:junhong_cmp_test + Redis DB 6 受控脚手架 37 项通过 / 0 项失败(含 6 路并发仅 1 条落库、
审批冲突回滚与无冲突建店回归),清理后 fixture 残留 0;gofmt/go build/go vet 全绿;
openspec validate --all 35 项通过、doctor healthy、context-health 通过
2026-09-17 19:02:14 +08:00

15 lines
7.6 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.
## 1. 关键字段唯一性门禁
- [x] 1.1 `internal/application/distributionwithdrawal/registration.go` 新增三个内部函数并写明中文注释:`lockRegistrationKeyScopes`(按 key 字符串升序对 `phone`/`username`/`shop_code``pg_advisory_xact_lock(hashtext(:key))`key 前缀 `agent-distribution-registration:`,注释说明行锁无法覆盖首次并发提交、升序避免死锁环);`ensureRegistrationKeysAvailable`(用 GORM 默认软删除范围查 `tb_account.phone``tb_account.username``tb_shop.shop_code`,分别返回 `CodePhoneExists`「手机号已被使用」、`CodeUsernameExists`「用户名已存在」、`CodeShopCodeExists`「店铺编号已存在」,查询失败返回 `CodeDatabaseError``ensureNoPendingRegistration`(查 `status=0``phone`/`username`/`shop_code` 任一命中的最早记录,命中后按字段返回 `CodeConflict` 与区分字段的中文提示)。验证:三个函数位于 `registration.go:152-235``lockRegistrationKeyScopes` 使用 `slices.Sort(keys)` 排序后逐个 `Exec("SELECT pg_advisory_xact_lock(hashtext(?))", key)``ensureRegistrationKeysAvailable` 依次以 `registrationKeyTaken` 校验三个字段并返回三个既有错误码。
- [x] 1.2 `Register` 的写事务内按「取串行化点 → 校验既有账号/店铺 → 校验待审批记录 → 创建注册记录 → 创建审批实例 → 回写实例 ID」顺序接入 1.1 的三个函数,使校验与插入同事务;失败整体回滚,事务外既有的验证码消费不执行。同步更新 `Register` 与相关函数的文档注释(冲突失败原因、已驳回/已通过记录不阻塞重新注册、软删除账号/店铺不占用关键字段)。验证:`registration.go:107-120` 事务体开头依次调用三个函数;受控脚手架中三类冲突场景均断言「不落库 + 验证码仍存在」,驳回后重注册场景断言验证码被消费。
- [x] 1.3 `internal/application/distributionwithdrawal/registration_approval.go``applyApproved``loadEnabledParentShop` 之后、创建店铺与账号之前调用 `ensureRegistrationKeysAvailable`:关键字段已被既有账号或店铺占用时返回 1.1 的可定位错误并整体回滚,注册记录保持待审批,不产生半套店铺/账号/钱包;注释写明该复检只把裸数据库错误换成稳定错误码,审批路径不取 advisory lock 的理由。验证:`registration_approval.go:107-113`;受控脚手架断言冲突审批返回 `1014`、注册记录仍为 `status=0`、无店铺与账号落库。
- [x] 1.4 `internal/routes/agent_distribution_public.go``Description` 增补失败原因(关键字段与既有账号/店铺或其他待审批申请重复时返回各自提示,且不消费验证码);运行 `go run cmd/gendocs/main.go` 重新生成 `docs/admin-openapi.yaml`,确认该路由描述与工作区文件一致且未改写其它路由。验证:`go run cmd/gendocs/main.go` 输出「成功在以下位置生成 OpenAPI 文档」,生成文件 `/api/c/v1/agent-distribution-registrations``description` 已包含新增失败原因(该文件被 `.gitignore` 忽略,为生成产物)。
- [x] 1.5 运行 `gofmt -w` 覆盖全部变更 Go 文件,并运行 `go build ./cmd/api ./cmd/worker``go vet ./internal/... ./pkg/...`,确认无错误输出。验证:`gofmt -l` 对四个变更 Go 文件输出为空;`go build ./cmd/api ./cmd/worker` 退出码 0`go vet ./internal/... ./pkg/...` 退出码 0唯余 `go` 自身对 `/Users/break/go/pkg/mod/cache/download` 无写权限的 `stat cache` 提示,与本次变更无关)。
## 2. 验证
- [x] 2.1 按 ENG-TEST-001 在 `junhong_cmp_test` PostgreSQL + Redis DB 6 以一次性受控脚手架(驱动真实 `RegistrationService` 与真实 `DistributionApprovalHandler`,事后删除且不在仓库留下测试入口)验证提交侧门禁:手机号与既有未删除账号冲突、用户名与既有账号冲突、店铺编号与既有店铺冲突分别返回 `1014`/`1013`/`1031` 与对应提示;软删除账号/店铺占用的关键字段可重新注册;手机号/用户名/店铺编号与既有 `status=0` 记录冲突分别返回 `1007` 与区分字段的中文提示;上一次申请为 `status=2`(驳回)时同一手机号、用户名、店铺编号可成功提交为新记录与新审批实例;所有拒绝场景下注册记录与审批实例均未落库,且失败后同一验证码可直接重试成功;并发发起 N 路同关键字段提交时仅 1 条待审批记录落库、其余返回 `1007`。fixture 只创建与删除本 Change 自己的记录(手机号/用户名/店铺编号带可识别前缀),不重置整库。验证:`go run ./cmd/tmpverify_dupguard``.env.local` 显式 `DB_*` = `junhong_cmp_test`、Redis DB 6 显式覆盖)输出 **37 项通过 / 0 项失败**,覆盖上述全部断言;并发 6 路同手机号/用户名/店铺编号提交仅 1 条成功、待审批记录恰 1 条,失败方错误码为 `1007`(串行点后看到待审批记录)与 `1183`(验证码已被胜者消费);三类冲突场景均验证验证码未被消费,驳回后重注册成功且验证码被消费。脚手架驱动的审批渠道为受控桩(真实 `approval.Port.Prepare` 需要调用企业微信AGENTS.md 禁止对真实外部审批系统做自动验证),审计 Writer 用受控桩以避免在共享测试环境留下审计残留。
- [x] 2.2 以同一脚手架验证审批侧:关键字段已被占用的待审批记录在受控 `TerminalDecisionEvent`(通过)下整体回滚,注册记录仍为 `status=0`,错误码为 1.1 的可定位错误,且 `tb_shop`/`tb_account`/`tb_agent_wallet` 无半套实体;对无冲突待审批记录验证通过路径仍正常建店、建号、建钱包与层级(回归)。验证:冲突审批返回 `1014`,注册记录仍为待审批,按店铺编号与用户名查询均为 0 行;无冲突审批成功建店(`parent_id` 为上级、`level` 为上级 +1、生成新的非空分销码、建代理主账号归属新店铺、`is_primary`、手机号一致)、建两个钱包,注册记录标记已通过。脚手架清理后按前缀复核残留:注册记录 0 / 店铺 0 / 账号 0 / 钱包 0经 dbhub 只读查询 `tb_shop``tb_account``tb_agent_distribution_registration``tb_agent_wallet` 确认),脚手架文件已删除。
- [x] 2.3 运行 `openspec validate fix-agent-distribution-registration-duplicate-guard --strict``openspec doctor --json``./scripts/context-health.sh`,确认 Change 校验通过、仓库健康;自动化测试按项目决策为 N/A不新增测试入口。验证`openspec validate ... --strict` 输出 `Change 'fix-agent-distribution-registration-duplicate-guard' is valid``openspec doctor --json``"healthy": true``status` 为空;`./scripts/context-health.sh` 输出「Context 健康检查通过」。
- [x] 2.4 清理修复前产生的重复待审批记录(维护者指令):测试环境 `tb_agent_distribution_registration``id` 88~93 六条 `status=0` 记录的关键字段均已与既有店铺或账号冲突(`shop_code` 全部被既有店铺占用),在修复后的门禁下不可能通过审批,已人工标记为 `status=2` 并写入可区分来源的中文驳回原因88~91 为渠道提交失败、92~93 为企业微信已通过但关键字段被占用无法建店);注册记录与审批实例的既有渠道事实不改写,`id=94` 已由企业微信回调按既有链路驳回。验证dbhub 只读查询确认七条记录状态均为 `status=2`,无 `status=0` 记录遗留;本次为测试环境数据清理,不改写迁移、不新增审计入口。