All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m39s
172 lines
7.9 KiB
Markdown
172 lines
7.9 KiB
Markdown
# Capability: 资产实名策略
|
||
|
||
## ADDED Requirements
|
||
|
||
### Requirement: 资产实名策略字段定义
|
||
|
||
系统 SHALL 在 `IotCard` 和 `Device` 模型上各新增 `realname_policy` 字段(VARCHAR(20),NOT NULL,DEFAULT 'none'),用于控制该资产的实名认证要求。
|
||
|
||
**枚举值**:
|
||
- `none`:无需实名,充值/购买/实名链接均不受限
|
||
- `before_order`:先实名后充值/购买,充值/购买前若未实名则拦截并返回 `CodeNeedRealname`
|
||
- `after_order`:先充值/购买后实名,充值/购买放行;实名链接在无有效充值/订单记录前拦截
|
||
|
||
**常量定义**(`pkg/constants/iot.go` 或 `pkg/constants/realname.go`):
|
||
```go
|
||
const (
|
||
RealnmePolicyNone = "none" // 无需实名
|
||
RealnmePolicyBeforeOrder = "before_order" // 先实名后充值/购买
|
||
RealnmePolicyAfterOrder = "after_order" // 先充值/购买后实名
|
||
)
|
||
```
|
||
|
||
#### Scenario: 默认值为 none
|
||
- **WHEN** 新建 IotCard 或 Device 时未传入 realname_policy
|
||
- **THEN** 系统自动填充 `realname_policy = "none"`
|
||
|
||
#### Scenario: 存量数据迁移后行为不变
|
||
- **WHEN** 数据库迁移执行后,存量 IotCard 和 Device 的 realname_policy 均为 "none"
|
||
- **THEN** 充值、购买、实名链接行为与迁移前完全相同(均放行)
|
||
|
||
---
|
||
|
||
### Requirement: 生效策略优先级(设备卡 vs 单卡)
|
||
|
||
系统 SHALL 按以下规则确定一张卡的生效实名策略:
|
||
|
||
- **单卡**(该卡未绑定任何设备):使用 `IotCard.realname_policy`
|
||
- **设备卡**(该卡已绑定设备):使用 `Device.realname_policy`,忽略 `IotCard.realname_policy`
|
||
|
||
此规则 SHALL 封装为 Service 层公共方法 `GetEffectiveRealnamePolicy`,所有需要策略判断的场景均调用该方法,不得在多处重复实现。
|
||
|
||
#### Scenario: 设备卡使用设备策略
|
||
- **WHEN** IotCard.realname_policy="none" 且该卡绑定了 Device.realname_policy="before_order"
|
||
- **THEN** 生效策略为 "before_order"(设备策略覆盖卡策略)
|
||
|
||
#### Scenario: 单卡使用卡策略
|
||
- **WHEN** IotCard.realname_policy="before_order" 且该卡未绑定任何设备
|
||
- **THEN** 生效策略为 "before_order"
|
||
|
||
---
|
||
|
||
### Requirement: C 端充值接口实名策略拦截
|
||
|
||
系统 SHALL 在 C 端充值预检接口(C3 `GET /api/c/v1/wallet/recharge-check`)和充值下单接口(C4 `POST /api/c/v1/wallet/recharge`)中,按生效实名策略执行以下逻辑:
|
||
|
||
- `before_order` + `real_name_status=0`(未实名):MUST 拦截,返回 `CodeNeedRealname`(错误码 1187)
|
||
- `before_order` + `real_name_status=1`(已实名):放行
|
||
- `after_order`:放行(不检查实名状态)
|
||
- `none`:放行
|
||
|
||
#### Scenario: before_order 模式未实名时充值被拦截
|
||
- **WHEN** 资产 realname_policy="before_order" 且 real_name_status=0,用户调用充值下单接口
|
||
- **THEN** 系统返回错误码 1187(CodeNeedRealname)
|
||
|
||
#### Scenario: before_order 模式已实名时充值放行
|
||
- **WHEN** 资产 realname_policy="before_order" 且 real_name_status=1,用户调用充值下单接口
|
||
- **THEN** 系统正常创建充值订单
|
||
|
||
#### Scenario: after_order 模式充值放行
|
||
- **WHEN** 资产 realname_policy="after_order" 且 real_name_status=0,用户调用充值下单接口
|
||
- **THEN** 系统正常创建充值订单,不检查实名状态
|
||
|
||
---
|
||
|
||
### Requirement: C 端购买套餐/订单接口实名策略拦截
|
||
|
||
系统 SHALL 在 C 端创建订单接口(D1 `POST /api/c/v1/orders/create`)和支付接口(D4 `POST /api/c/v1/orders/:id/pay`)中,按生效实名策略执行与充值相同的拦截规则。D1 中原 `REALNAME-03` 注释代码 SHALL 被删除,由新策略逻辑替代。
|
||
|
||
#### Scenario: before_order 模式未实名时购买套餐被拦截
|
||
- **WHEN** 资产 realname_policy="before_order" 且 real_name_status=0,用户调用创建订单接口
|
||
- **THEN** 系统返回错误码 1187(CodeNeedRealname),订单不创建
|
||
|
||
#### Scenario: none 模式购买套餐放行
|
||
- **WHEN** 资产 realname_policy="none",用户调用创建订单接口
|
||
- **THEN** 系统正常创建订单,不检查实名状态
|
||
|
||
---
|
||
|
||
### Requirement: C 端实名链接接口策略拦截
|
||
|
||
系统 SHALL 在 C 端实名链接接口(E1 `GET /api/c/v1/realname/link`)中,按生效实名策略执行以下逻辑:
|
||
|
||
- `none`:放行,正常返回实名链接(用户可自愿实名)
|
||
- `before_order`:放行,正常返回实名链接(引导用户先实名再充值)
|
||
- `after_order`:检查该资产当前 generation 内是否存在有效充值(`status=2`)或已支付订单(`payment_status=2`)
|
||
- 有记录 → 放行
|
||
- 无记录 → 拦截,返回错误码(新错误码 `CodeRealnameNotAvailable`),消息为"请先完成充值或购买套餐后再进行实名认证"
|
||
|
||
#### Scenario: after_order 模式有充值记录时实名链接放行
|
||
- **WHEN** 资产 realname_policy="after_order" 且当前 generation 内存在 status=2 的充值记录
|
||
- **THEN** 系统正常返回实名链接
|
||
|
||
#### Scenario: after_order 模式无充值/订单记录时实名链接被拦截
|
||
- **WHEN** 资产 realname_policy="after_order" 且当前 generation 内无任何有效充值或已支付订单
|
||
- **THEN** 系统返回错误,消息为"请先完成充值或购买套餐后再进行实名认证"
|
||
|
||
#### Scenario: before_order 模式正常返回实名链接
|
||
- **WHEN** 资产 realname_policy="before_order"
|
||
- **THEN** 系统正常返回实名链接(引导用户完成实名)
|
||
|
||
---
|
||
|
||
### Requirement: 后台管理更新资产实名策略接口
|
||
|
||
系统 SHALL 提供 `PATCH /api/admin/assets/:identifier/realname-mode`,仅限后台管理端认证用户访问。接口通过 `assetService.Resolve()` 将标识符(ICCID/虚拟号)解析为具体资产,按 asset_type 分别更新 `IotCard.realname_policy` 或 `Device.realname_policy`。
|
||
|
||
**请求体**:
|
||
```json
|
||
{ "realname_policy": "none | before_order | after_order" }
|
||
```
|
||
|
||
**响应体**:
|
||
```json
|
||
{ "asset_type": "card | device", "asset_id": 123, "realname_policy": "before_order" }
|
||
```
|
||
|
||
#### Scenario: 通过 ICCID 更新单卡策略
|
||
- **WHEN** 管理员传入 identifier=ICCID,realname_policy="before_order"
|
||
- **THEN** 系统更新对应 IotCard 的 realname_policy 为 "before_order",返回 asset_type="card"
|
||
|
||
#### Scenario: 通过设备号更新设备策略
|
||
- **WHEN** 管理员传入 identifier=设备虚拟号,realname_policy="after_order"
|
||
- **THEN** 系统更新对应 Device 的 realname_policy 为 "after_order",返回 asset_type="device"
|
||
|
||
#### Scenario: 传入无效枚举值被拒绝
|
||
- **WHEN** 管理员传入 realname_policy="invalid_value"
|
||
- **THEN** 系统返回参数校验错误,提示实名策略值无效
|
||
|
||
---
|
||
|
||
### Requirement: 所有查询接口返回 realname_policy 字段
|
||
|
||
以下所有 DTO SHALL 新增 `realname_policy` 字段(string)及对应的 description 标签:
|
||
|
||
**后台管理端 DTO**:
|
||
- `StandaloneIotCardResponse`(IoT 卡详情/列表)
|
||
- `DeviceResponse`(设备详情/列表)
|
||
- `AssetResolveResponse`(统一资产解析)
|
||
- `AssetRealtimeStatusResponse`(资产实时状态)
|
||
- `DeviceCardBindingResponse`(设备绑卡记录)
|
||
- `BoundCardInfo`(asset_dto.go 中的通用子结构)
|
||
- `ImportTaskResponse`(iot 卡导入任务)
|
||
- `DeviceImportTaskResponse`(设备导入任务)
|
||
|
||
**C 端 DTO**:
|
||
- `AssetInfoResponse`(B1 资产信息)
|
||
- `BoundCardInfo`(client_asset_dto.go 中的 C 端子结构)
|
||
- `DeviceCardItem`(F1 设备卡列表项)
|
||
|
||
**description 标签统一格式**:
|
||
```go
|
||
RealnamePolicy string `json:"realname_policy" description:"实名认证策略 (none:无需实名, before_order:先实名后充值/购买, after_order:先充值/购买后实名)"`
|
||
```
|
||
|
||
#### Scenario: 资产详情接口返回 realname_policy
|
||
- **WHEN** 后台管理员查询 IoT 卡详情或资产解析
|
||
- **THEN** 响应中包含 realname_policy 字段
|
||
|
||
#### Scenario: C 端资产信息接口返回 realname_policy
|
||
- **WHEN** C 端用户调用 GET /api/c/v1/asset/info
|
||
- **THEN** 响应中包含 realname_policy 字段(前端可据此展示提示)
|