Files
junhong_cmp_fiber/openspec/specs/asset-suspend-resume/spec.md
huang b9c3875c08
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-14 18:27:28 +08:00

175 lines
7.2 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.
# asset-suspend-resume Specification
## Purpose
提供统一的资产停复机接口,包括设备级批量停复机和单卡停复机,含保护期感知逻辑。废弃原分散在各模块的旧停复机接口,统一使用 `/api/admin/assets/` 路径。
## Requirements
### Requirement: 设备停机接口
系统 SHALL 提供设备停机接口,批量停用设备下所有已实名卡,并建立停机保护期。
**API 端点**: `POST /api/admin/assets/device/:device_id/stop`
**执行流程**:
1. 验证设备存在(不存在返回 HTTP 404
2. 检查设备是否在保护期(`RedisDeviceProtectKey(deviceID, "stop")``"start"` 存在则返回 HTTP 403
3. 获取该设备所有已实名(`real_name_status = 1`)的绑定卡
4. 遍历调用网关停机接口(未实名卡跳过,永远是停机状态)
5. 更新成功停机的卡的 `network_status = 0``stopped_at = now()``stop_reason = "manual"`
6. 在 Redis 中设置停机保护期:`RedisDeviceProtectKey(deviceID, "stop")`TTL = 1 小时
7. 响应:返回成功,附带失败卡列表(如有)
**保护期说明**:
- 保护期时长1 小时(常量 `DeviceProtectPeriodDuration = 1 * time.Hour`,定义在 `pkg/constants/`
- 停机保护期 key`protect:device:{device_id}:stop`
- 复机保护期 key`protect:device:{device_id}:start`
- 两个 key 互斥:设置 stop 保护期时删除 start 保护期,反之亦然
**批量部分失败策略**:
- 部分卡调网关失败:**仍设置** Redis 保护期(保护期从发起操作时算起)
- 已成功停机的卡**不回滚**
- 失败的卡记录 Error 日志,响应体中携带失败列表
#### Scenario: 成功执行设备停机
- **WHEN** 管理员调用 `POST /api/admin/assets/device/456/stop`,该设备有 3 张已实名卡
- **THEN** 系统批量调网关停机,更新 3 张卡 network_status=0设置 1 小时 stop 保护期,返回成功
#### Scenario: 设备存在保护期
- **WHEN** 管理员在设备已有 stop 保护期时再次调用停机接口
- **THEN** 系统返回 HTTP 403提示"设备处于保护期,不允许操作"
#### Scenario: 设备下无已实名卡
- **WHEN** 管理员对只有未实名卡的设备执行停机
- **THEN** 系统返回成功0 张卡操作),设置 stop 保护期
#### Scenario: 设备不存在
- **WHEN** 管理员调用不存在的设备 ID
- **THEN** 系统返回 HTTP 404
#### Scenario: 部分卡停机失败
- **WHEN** 设备有 3 张卡1 张网关调用失败
- **THEN** 2 张成功停机1 张失败记录日志,**仍设置** stop 保护期,响应中包含失败卡信息
---
### Requirement: 设备复机接口
系统 SHALL 提供设备复机接口,批量恢复设备下所有已实名卡,并建立复机保护期。
**API 端点**: `POST /api/admin/assets/device/:device_id/start`
**执行流程**:
1. 验证设备存在(不存在返回 HTTP 404
2. 检查设备是否在保护期stop 或 start 保护期均存在时返回 HTTP 403
3. 获取该设备所有已实名(`real_name_status = 1`)的绑定卡
4. 遍历调用网关复机接口
5. 更新成功复机的卡的 `network_status = 1``resumed_at = now()`
6. 设置复机保护期:`RedisDeviceProtectKey(deviceID, "start")`TTL = 1 小时
7. 响应:返回成功
#### Scenario: 成功执行设备复机
- **WHEN** 管理员调用 `POST /api/admin/assets/device/456/start`,该设备有 2 张已实名卡
- **THEN** 系统批量复机,更新卡状态,设置 1 小时 start 保护期,返回成功
#### Scenario: 设备在 start 保护期内再次复机
- **WHEN** 设备已有 start 保护期时再次调用复机接口
- **THEN** 系统返回 HTTP 403提示"设备处于保护期,不允许操作"
---
### Requirement: 卡停机接口
系统 SHALL 提供单卡停机接口,含保护期感知逻辑。
**API 端点**: `POST /api/admin/assets/card/:iccid/stop`
**执行流程**:
1. 通过 ICCID 查找卡(不存在返回 HTTP 404
2. 检查卡是否已实名(`real_name_status = 0` 时返回 HTTP 403未实名卡不允许停复机
3. 若卡绑定了设备,检查该设备的保护期:
- 设备有 **stop 保护期**:允许停机(本已是停机方向,无冲突)
- 设备有 **start 保护期**:允许停机(用户可主动停单张卡)
- 设备无保护期:正常执行
4. 调用网关停机接口
5. 更新卡 `network_status = 0``stopped_at = now()``stop_reason = "manual"`
#### Scenario: 独立卡(未绑定设备)停机
- **WHEN** 管理员对一张未绑定设备的已实名卡执行停机
- **THEN** 系统正常调网关停机,更新卡状态
#### Scenario: 绑定设备且设备在 start 保护期内停机
- **WHEN** 管理员对绑定了设备且设备有 start 保护期的卡执行停机
- **THEN** 系统允许执行(用户主动停单张卡不违反 start 保护期),正常停机
#### Scenario: 对未实名卡执行停机
- **WHEN** 管理员对 real_name_status=0未实名的卡执行停机
- **THEN** 系统返回 HTTP 403提示"未实名卡不允许停复机操作"
---
### Requirement: 卡复机接口
系统 SHALL 提供单卡复机接口,含保护期感知逻辑。
**API 端点**: `POST /api/admin/assets/card/:iccid/start`
**执行流程**:
1. 通过 ICCID 查找卡(不存在返回 HTTP 404
2. 检查卡是否已实名(`real_name_status = 0` 时返回 HTTP 403
3. 若卡绑定了设备,检查该设备的保护期:
- 设备有 **stop 保护期****不允许**手动复机,返回 HTTP 403设备处于停机保护期
- 设备有 **start 保护期**:允许复机(本已是复机方向,无冲突)
- 设备无保护期:正常执行
4. 调用网关复机接口
5. 更新卡 `network_status = 1``resumed_at = now()`,清空 `stop_reason`
#### Scenario: 独立卡(未绑定设备)复机
- **WHEN** 管理员对一张未绑定设备的已实名停机卡执行复机
- **THEN** 系统正常调网关复机,更新卡状态
#### Scenario: 设备处于 stop 保护期时尝试复机
- **WHEN** 管理员对绑定了设备且设备有 stop 保护期的卡执行复机
- **THEN** 系统返回 HTTP 403提示"设备处于停机保护期,不允许手动复机"
#### Scenario: 设备在 start 保护期内复机
- **WHEN** 管理员对绑定了设备且设备有 start 保护期的卡执行复机
- **THEN** 系统允许执行(本已是复机方向),正常复机
#### Scenario: 对未实名卡执行复机
- **WHEN** 管理员对 real_name_status=0 的卡执行复机
- **THEN** 系统返回 HTTP 403提示"未实名卡不允许停复机操作"
---
### Requirement: 废弃旧停复机接口
系统 SHALL 删除以下重复的停复机接口,统一使用新的 `/api/admin/assets/` 路径。
**待删除接口**:
- `POST /api/admin/enterprises/:id/cards/:card_id/suspend`
- `POST /api/admin/enterprises/:id/cards/:card_id/resume`
- `POST /h5/devices/:device_id/cards/:card_id/suspend`
- `POST /h5/devices/:device_id/cards/:card_id/resume`
- 旧 Admin 卡停复机接口(`POST /iot-cards/:iccid/suspend|resume`
#### Scenario: 调用已删除的旧接口
- **WHEN** 前端调用 `POST /api/admin/enterprises/:id/cards/:card_id/suspend`
- **THEN** 系统返回 HTTP 404路由已不存在