# 需求02：H5 流程顺序配置化

> 状态：原需求独立稿；最终口径以标准评审稿为准。

---

## 背景

H5 用户进入后：绑定手机号 → 充值 → 实名（当前顺序写死）

需求：允许**按资产个体**配置充值和实名的顺序，支持后台单条或批量改。

---

## 流程图

### 图一：H5 登录后资产视角判断与策略读取

```mermaid
flowchart TD
    A[用户扫码 / 输入虚拟号] --> B[解析 identifier]
    B --> C{资产类型}

    C -->|card| D{该卡是否绑定设备?}
    D -->|否 独立卡| E[卡视角\n读 IotCard.realname_policy]
    D -->|是| F[设备视角\n读 Device.realname_policy\n卡自身策略忽略]

    C -->|device| F

    E --> G{realname_policy}
    F --> G

    G -->|none| H[无需实名\n直接进充值页]
    G -->|before_order| I[先进实名页\n实名完成后才能充值]
    G -->|after_order| J[先进充值页\n充值完成后提示实名]
```

### 图二：H5 充值/购买前的策略拦截逻辑

```mermaid
flowchart TD
    A[用户发起充值/购买] --> B[读取 resolved.Asset.RealnamePolicy]
    B --> C{策略是 before_order?}
    C -->|否| D[放行，正常创建订单]
    C -->|是| E{当前资产 RealNameStatus == 1?}
    E -->|已实名| D
    E -->|未实名| F[返回 CodeNeedRealname\nH5 跳转实名页]
```

### 图三：GetEffectiveRealnamePolicy 取值逻辑

```mermaid
flowchart TD
    A[GetEffectiveRealnamePolicy\ncard, device] --> B{device != nil?}
    B -->|是| C[返回 device.RealnamePolicy]
    B -->|否| D{card != nil?}
    D -->|是| E[返回 card.RealnamePolicy]
    D -->|否| F[返回 none]
```

---

## 资产类型与策略归属

系统有两类资产：**独立卡** 和 **设备**。

| 资产类型 | realname_policy 归属 | H5 视角 |
|---------|---------------------|---------|
| 独立卡（无设备绑定） | 卡自身的 `realname_policy` | 卡视角 |
| 设备 | 设备自身的 `realname_policy` | 设备视角 |
| 设备下的卡 | **设备**的 `realname_policy`（卡自身策略无效） | 设备视角 |

**关键规则**：设备下的卡无法以卡视角独立登录 H5，登录后自动进入设备视角，因此实名流程策略由设备决定，卡自身的 `realname_policy` 字段对 H5 流程无影响（但字段保留，仅作记录）。

该逻辑已在 `internal/service/asset/service.go:GetEffectiveRealnamePolicy()` 实现：设备不为 nil 时取设备策略，否则取卡策略。

---

## 当前字段状态

两个模型都已有 `realname_policy` 字段，无需迁移：

```go
// internal/model/iot_card.go
RealnamePolicy string `gorm:"column:realname_policy;type:varchar(20);default:'after_order';not null;
    comment:实名认证策略(none=无需实名,before_order=先实名后充值/购买,after_order=先充值/购买后实名)"`

// internal/model/device.go
RealnamePolicy string `gorm:"column:realname_policy;type:varchar(20);default:'after_order';not null;
    comment:实名认证策略(none=无需实名,before_order=先实名后充值/购买,after_order=先充值/购买后实名)"`
```

值含义：
- `none` — 无需实名
- `before_order` — 先实名后充值
- `after_order` — 先充值后实名（**当前默认**）

---

## 后端实现

### 1. 单条修改接口（已有，无需新建）

```
PATCH /api/admin/assets/:identifier/realname-mode
```

该接口已在 `internal/handler/admin/asset.go:UpdateRealnamePolicy()` 实现，通过 identifier 自动解析资产类型，卡和设备都走这里，**不需要再建卡专属或设备专属路由**。

请求体（已有 DTO）：
```go
type UpdateAssetRealnamePolicyRequest struct {
    RealnamePolicy string `json:"realname_policy" validate:"required,oneof=none before_order after_order"
        description:"实名策略 (none:无需实名, before_order:先实名后充值, after_order:先充值后实名)"`
}
```

### 2. 新增批量修改接口（需新建）

卡和设备分开批量接口，因为两者在后台是不同的列表页。

#### 2a. 批量修改卡实名策略

```
POST /api/admin/iot-cards/batch-update-realname-policy
```

请求体：
```go
type BatchUpdateIotCardRealnamePolicy struct {
    IotCardIDs     []uint `json:"iot_card_ids" validate:"required,min=1,max=500,dive,gt=0" description:"卡ID列表（最多500条）"`
    RealnamePolicy string `json:"realname_policy" validate:"required,oneof=none before_order after_order"
        description:"实名策略 (none:无需实名, before_order:先实名后充值, after_order:先充值后实名)"`
}
```

Service：在一个事务中校验最多 500 条 ID 均存在且均在当前账号数据范围内，再执行 `UPDATE tb_iot_card SET realname_policy = ? WHERE id IN (...)`。任一记录不合法则整批回滚，并写一条包含目标策略和 ID 数量的批量审计日志。

#### 2b. 批量修改设备实名策略

```
POST /api/admin/devices/batch-update-realname-policy
```

请求体：
```go
type BatchUpdateDeviceRealnamePolicy struct {
    DeviceIDs      []uint `json:"device_ids" validate:"required,min=1,max=500,dive,gt=0" description:"设备ID列表（最多500条）"`
    RealnamePolicy string `json:"realname_policy" validate:"required,oneof=none before_order after_order"
        description:"实名策略 (none:无需实名, before_order:先实名后充值, after_order:先充值后实名)"`
}
```

Service：与卡批量接口相同，单次最多 500 条、事务内全成全败；任一设备不存在或越权则不更新任何记录。

### 3. 全局默认值（兜底）

新建卡/设备时默认 `after_order`，通过 GORM default 标签保证，不需要读 `tb_system_config`。

---

## 前端对接

### 卡列表页

"操作"列或批量操作下拉增加"设置实名策略"：

- **单条**：弹框选策略 → `PATCH /api/admin/assets/{iccid}/realname-mode`
- **批量**：勾选多条 → 批量操作 → "设置实名策略" → `POST /api/admin/iot-cards/batch-update-realname-policy`

> 注意：设备下的卡即使在卡列表中修改了策略，对 H5 流程也无效（H5 取设备策略）。建议在卡列表展示"所属设备"列，提示运营该卡已属于某设备，实名策略需到设备处修改。

### 设备列表页

"操作"列或批量操作下拉增加"设置实名策略"：

- **单条**：弹框选策略 → `PATCH /api/admin/assets/{sn}/realname-mode`
- **批量**：勾选多条 → 批量操作 → "设置实名策略" → `POST /api/admin/devices/batch-update-realname-policy`

### 字段展示

卡列表/详情、设备列表/详情均展示"实名策略"字段：

| realname_policy | 展示文案 |
|----------------|---------|
| `none` | 无需实名 |
| `before_order` | 先实名后充值 |
| `after_order` | 先充值后实名 |

### H5 侧（C端）

H5 读取资产初始化接口返回的 `realname_policy` 字段，决定先跳充值页还是先跳实名页。

- 独立卡登录 → 读卡的 `realname_policy`
- 设备/设备下的卡登录 → 读设备的 `realname_policy`

该逻辑由 `GetEffectiveRealnamePolicy()` 统一处理，H5 无需区分资产类型，直接用接口返回值即可。

---

## 实施范围汇总

| 项目 | 状态 | 说明 |
|------|------|------|
| `IotCard.realname_policy` 字段 | ✅ 已有 | 无需迁移 |
| `Device.realname_policy` 字段 | ✅ 已有 | 无需迁移 |
| 单条修改接口 | ✅ 已有 | `PATCH /api/admin/assets/:identifier/realname-mode` |
| `GetEffectiveRealnamePolicy()` | ✅ 已有 | 设备视角取设备策略 |
| H5 充值前校验 | ✅ 已有 | `client_wallet.go` 已正确读取 |
| 批量修改卡接口 | ❌ 待建 | `POST /api/admin/iot-cards/batch-update-realname-policy` |
| 批量修改设备接口 | ❌ 待建 | `POST /api/admin/devices/batch-update-realname-policy` |
| 后台卡列表操作入口 | ❌ 待建（前端） | 单条+批量 |
| 后台设备列表操作入口 | ❌ 待建（前端） | 单条+批量 |
