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 - 可立即开始
