All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
175 lines
7.2 KiB
Markdown
175 lines
7.2 KiB
Markdown
# 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(路由已不存在)
|