Files
junhong_cmp_fiber/openspec/specs/asset-realname-policy/spec.md
huang 4aab0bcbf2
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m39s
提案以及归档
2026-04-18 09:10:29 +08:00

172 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Capability: 资产实名策略
## ADDED Requirements
### Requirement: 资产实名策略字段定义
系统 SHALL 在 `IotCard``Device` 模型上各新增 `realname_policy` 字段VARCHAR(20)NOT NULLDEFAULT '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** 系统返回错误码 1187CodeNeedRealname
#### 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** 系统返回错误码 1187CodeNeedRealname订单不创建
#### 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=ICCIDrealname_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 字段(前端可据此展示提示)