## 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 1. **一资产一号只做“先查后写”,不加资产锁。** `Establish` 写入前查询该资产的有效关联再判断;不加 Redis 锁、不锁资产行、不加应用锁。2026-10-11 确认:两个不同号码在同一时刻为同一资产完成短信验证的概率极低,后果只是该资产多一个号,可在后台解除,不值得为此加锁。手机号侧既有的十项名额串行(`LockPhoneScopes`)保持不变。 2. **不加唯一约束。** 部分唯一索引 `(asset_type, asset_id) WHERE status = 1` 能兜底,但存量可能有多号资产,加约束前要先清理;已确认本批用程序控制,存量只读统计后交业务处理。 3. **绑定入口按两侧状态拆写。** `bindExistingPrimaryPhone` 不再要求提交号码等于客户手机号;客户侧与资产侧分别判断是否写入。错误码在 `pkg/errors` 新增,提示不带号码。 4. **`ChangePhone` 去掉 `migrateAssociations` 及其迁移审计。** 客户手机号与资产手机号独立,换号不应牵动资产。 5. **换货错误号码放 `data`。** 统一错误处理会把错误信息写日志(`pkg/errors/handler.go`),`data` 只随响应返回;用 `errors.NewWithData` 承载 `{"phone": ...}`,错误码沿用现有冲突类。 6. **换货清空旧资产客户绑定。** 新增 `customer_binding.ClearForExchange(ctx, tx, oldAssetType, oldAssetID)`:定位口径同原 `Migrate`;只处理 `status = 1`,置 0(沿用 `pcdOps.UpdateStatus`、`pciOps.GetByICCID` / `UpdateStatus`),审计角色用 `AuditResourceRolePersonalCustomerOldAssetBinding`。删除 `Migrate`、`migrateFromPCD`、`migrateFromPCI`。 7. **换货失效旧资产关联复用存储方法。** 用 `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 开发。