All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m57s
- 提交写事务内先取事务级 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 通过
15 lines
7.6 KiB
Markdown
15 lines
7.6 KiB
Markdown
## 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` 记录遗留;本次为测试环境数据清理,不改写迁移、不新增审计入口。
|