Status: done # CustomerBinding 模块骨架 + 有虚拟号路径替换(纯重构) ## What to build 建立 `internal/service/customer_binding` 包,对外暴露 `Bind()` 和 `OwnsAsset()` 两个接口,将现有有虚拟号路径的逻辑迁入。同时将代码库中 6 处归属验证调用点统一替换为新接口。 **本切片是纯重构,不改变任何现有行为。** ### Bind(ctx, tx, customerID, assetType, assetID) 封装创建客户与资产绑定记录的逻辑,当前只实现有虚拟号路径: - IoT 卡有虚拟号 → 写 `tb_personal_customer_device`(与现在 `bindAsset` 行为一致) - 设备 → 写 `tb_personal_customer_device`(设备必然有虚拟号) - 首次绑定时触发 `markAssetAsSold()`(firstEverBind 逻辑保持不变) ### OwnsAsset(ctx, customerID, assetType, assetID) bool 封装归属验证逻辑,当前只实现有虚拟号路径: - 查 `tb_personal_customer_device WHERE virtual_no = ? AND status = 1` - 内部统一强制 `status = 1` 过滤(修复现有部分实现缺少此过滤的安全缺口) ### 替换 6 处调用点 以下调用点替换为 `CustomerBinding` 的方法: | 调用点 | 替换目标 | |--------|---------| | `client_auth/service.go: bindAsset()` | `CustomerBinding.Bind()` | | `handler/app/client_realname.go:92` | `CustomerBinding.OwnsAsset()` | | `handler/app/client_device.go:82` | `CustomerBinding.OwnsAsset()` | | `handler/app/client_wallet.go: isCustomerOwnAsset()` | `CustomerBinding.OwnsAsset()` | | `handler/app/client_asset.go: isCustomerOwnAsset()` | `CustomerBinding.OwnsAsset()` | | `service/client_order/service.go: checkAssetOwnership()` | `CustomerBinding.OwnsAsset()` | exchange service 的 `customerOwnsAsset()` 在切片 3 中处理。 ## Acceptance criteria - [ ] `internal/service/customer_binding` 包存在,对外暴露 `Bind` 和 `OwnsAsset` - [ ] 有虚拟号的 IoT 卡登录后,`tb_personal_customer_device` 正常写入绑定记录(与现在一致) - [ ] 设备登录后,`tb_personal_customer_device` 正常写入绑定记录(与现在一致) - [ ] 被禁用(status=0)的绑定记录不能通过 `OwnsAsset` 验证(修复安全缺口) - [ ] 6 处调用点全部替换完毕,原有私有方法(resolveAssetBindingKey、isCustomerOwnAsset 等)可删除 - [ ] 有虚拟号卡的实名认证、购买套餐、设备操作全链路与现在行为一致 ## Blocked by None - 可立即开始