- F1 一个资产同时只能有一个有效手机号关联,他号绑定返回 1192(不含号码),同号幂等 - 登录 need_bind_phone 改看资产是否已有有效关联,新增 asset_phone_bound、asset_phone_is_own;H5 绑定按客户/资产两侧状态分别写入 - 客户换号不再迁移资产关联 - M12 换货新资产有有效手机号关联即拒绝,号码只在 data.phone;换货完成清空旧资产客户绑定并失效旧资产关联(exchange_completed),均不迁移 - OpenSpec Change enforce-single-phone-per-asset:11/16,H5 短信与换货端到端验证待测试部署后完成
4.6 KiB
4.6 KiB
Context
承接《第1批-8-9月迭代缺陷修复技术方案》(下称“方案”)第 5、6 节。
AssociationWriter.Establish(internal/service/phone_asset_association/association_write.go)只限制一个手机号最多 10 个资产;部分唯一索引uq_phone_asset_association_valid (phone, asset_type, asset_id)(迁移000223)只防同号同资产重复。BindPhone用同一号码同时写客户手机号与资产关联;ChangePhone经migrateAssociations迁移关联;need_bind_phone只看当前客户号码与资产是否关联。ensureNewAssetBindingAvailableWithTx(internal/service/exchange/service.go:1071)查tb_personal_customer_device,有人扫过码即拒绝;switchCustomerBindingWithTx(:1089)调用customerBinding.Migrate迁移客户绑定,Migrate只有换货一个调用方。- 后台解除接口已存在(
internal/routes/phone_asset_association.go):单条、按资产批量、CSV 导入、关联列表。 - 测试库 2026-10-11:有效关联 0 条;强制绑定开关已在测试环境打开(提交
f56113ba)。
Goals / Non-Goals
Goals:
- 一资产一号,资产已绑定时不再出现资产侧绑定入口。
- 换货能被后台解除放行,换货后旧资产与原客户、原号码脱钩。
Non-Goals:
- 不加数据库唯一约束(已确认用程序控制)。
- 不做历史关联回填,不调整关联写入与强制绑定开关的耦合关系(方案第 5 节第 7 条,另行决定)。
- 不改“新号已被其他客户持有返回 1184”。
- 不新增后台解除接口。
Decisions
- 一资产一号只做“先查后写”,不加资产锁。
Establish写入前查询该资产的有效关联再判断;不加 Redis 锁、不锁资产行、不加应用锁。2026-10-11 确认:两个不同号码在同一时刻为同一资产完成短信验证的概率极低,后果只是该资产多一个号,可在后台解除,不值得为此加锁。手机号侧既有的十项名额串行(LockPhoneScopes)保持不变。 - 不加唯一约束。 部分唯一索引
(asset_type, asset_id) WHERE status = 1能兜底,但存量可能有多号资产,加约束前要先清理;已确认本批用程序控制,存量只读统计后交业务处理。 - 绑定入口按两侧状态拆写。
bindExistingPrimaryPhone不再要求提交号码等于客户手机号;客户侧与资产侧分别判断是否写入。错误码在pkg/errors新增,提示不带号码。 ChangePhone去掉migrateAssociations及其迁移审计。 客户手机号与资产手机号独立,换号不应牵动资产。- 换货错误号码放
data。 统一错误处理会把错误信息写日志(pkg/errors/handler.go),data只随响应返回;用errors.NewWithData承载{"phone": ...},错误码沿用现有冲突类。 - 换货清空旧资产客户绑定。 新增
customer_binding.ClearForExchange(ctx, tx, oldAssetType, oldAssetID):定位口径同原Migrate;只处理status = 1,置 0(沿用pcdOps.UpdateStatus、pciOps.GetByICCID/UpdateStatus),审计角色用AuditResourceRolePersonalCustomerOldAssetBinding。删除Migrate、migrateFromPCD、migrateFromPCI。 - 换货失效旧资产关联复用存储方法。 用
LockValidByAsset+InvalidateByIDs(phone_asset_association_store.go:215、229),不复用Service.unbindAsset:它自开事务并做后台数据范围校验,放不进换货事务。新增失效方式常量exchange_completed(该列无约束,无迁移)。
Risks / Trade-offs
- [历史上绑过号但没有关联记录的资产不会被换货拦截] → 取决于回填决定,本 Change 不处理。
- [新资产已有钱包余额或生效套餐时,迁移会叠加] → 现状如此,不拦截。
- [前端仍按旧
need_bind_phone语义展示] → 测试环境交付时前端接入两个新布尔字段与data.phone。 - [同一资产并发绑定两个号码] → 不加锁,接受极小概率;交付前与日常用只读统计“同一资产多条有效关联”发现,后台解除多余的。
- [原客户手上的旧资产页面在换货后失效] → 属预期,重新扫码即可进入,已在对外行为变化中告知运营与客服。
Migration Plan
无数据库迁移。测试环境交付前只读统计“同一资产多条有效关联”,有则出清单交业务在后台解除。
Open Questions
- 历史关联回填及口径、关联写入是否与强制绑定开关解耦:方案定为“发布时定”,不阻塞本 Change 开发。