- 新增 GET /api/admin/devices/by-imei/:imei 接口,支持通过设备号查询设备详情 - 新增 GET /api/admin/iot-cards/by-iccid/:iccid 接口,支持通过ICCID查询单卡详情 - 添加对应的 Service 层方法和 Handler - 更新 OpenAPI 文档 - 添加集成测试并修复测试环境配置(使用环境变量) - 归档已完成的 OpenSpec 变更记录 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
10 KiB
device Specification
Purpose
TBD - created by archiving change add-device-management. Update Purpose after archive.
Requirements
Requirement: 设备列表查询
系统 SHALL 提供设备列表查询功能,支持多维度筛选和分页。
查询条件:
device_no(可选): 设备号,支持模糊匹配device_name(可选): 设备名称,支持模糊匹配status(可选): 设备状态,枚举值 1-在库 | 2-已分销 | 3-已激活 | 4-已停用shop_id(可选): 店铺 ID,NULL 表示平台库存batch_no(可选): 批次号,精确匹配device_type(可选): 设备类型manufacturer(可选): 制造商,支持模糊匹配created_at_start(可选): 创建时间起始created_at_end(可选): 创建时间结束
分页:
- 默认每页 20 条,最大每页 100 条
- 返回总记录数和总页数
数据权限:
- 平台用户可查看所有设备
- 代理用户只能查看自己店铺及下级店铺的设备
API 端点: GET /api/admin/devices
响应字段:
id: 设备 IDdevice_no: 设备号device_name: 设备名称device_model: 设备型号device_type: 设备类型max_sim_slots: 最大插槽数manufacturer: 制造商batch_no: 批次号shop_id: 店铺 IDshop_name: 店铺名称status: 状态status_name: 状态名称bound_card_count: 已绑定卡数量activated_at: 激活时间created_at: 创建时间updated_at: 更新时间
Scenario: 平台查询所有设备
- WHEN 平台管理员查询设备列表,不带任何筛选条件
- THEN 系统返回所有设备,按创建时间倒序排列
Scenario: 按设备号模糊查询
- WHEN 管理员输入 device_no = "GPS"
- THEN 系统返回设备号包含 "GPS" 的所有设备
Scenario: 按状态筛选设备
- WHEN 管理员查询状态为 1(在库)的设备
- THEN 系统只返回在库状态的设备
Scenario: 代理查询自己店铺的设备
- WHEN 代理用户(店铺 ID=10)查询设备列表
- THEN 系统只返回 shop_id 为 10 及其下级店铺的设备
Scenario: 查询平台库存设备
- WHEN 平台管理员查询 shop_id 为空的设备
- THEN 系统返回所有平台库存设备(shop_id = NULL)
Requirement: 设备详情查询
系统 SHALL 提供设备详情查询功能,返回设备的基本信息。
API 端点: GET /api/admin/devices/:id
响应字段:
- 包含设备的所有基本字段
shop_name: 店铺名称(如果有)
数据权限:
- 平台用户可查看所有设备
- 代理用户只能查看自己店铺及下级店铺的设备
Scenario: 查询设备详情成功
- WHEN 管理员查询设备详情(ID=1)
- THEN 系统返回该设备的完整基本信息
Scenario: 查询不存在的设备
- WHEN 管理员查询不存在的设备(ID=999)
- THEN 系统返回 404 错误,提示"设备不存在"
Scenario: 代理查询无权限的设备
- WHEN 代理用户(店铺 ID=10)查询其他店铺的设备(shop_id=20,非下级)
- THEN 系统返回 404 错误,提示"设备不存在"
Requirement: 删除设备
系统 SHALL 提供删除设备功能,仅平台用户可操作,执行软删除。
API 端点: DELETE /api/admin/devices/:id
业务规则:
- 仅平台用户可删除设备
- 删除设备时自动解绑该设备上的所有卡
- 执行软删除(设置 deleted_at)
权限: 仅平台用户
Scenario: 平台删除设备成功
- WHEN 平台管理员删除设备(ID=1)
- THEN 系统软删除该设备,并解绑设备上的所有卡
Scenario: 代理尝试删除设备
- WHEN 代理用户尝试删除设备
- THEN 系统返回 403 错误,提示"无权限执行此操作"
Scenario: 删除不存在的设备
- WHEN 平台管理员删除不存在的设备(ID=999)
- THEN 系统返回 404 错误,提示"设备不存在"
Requirement: 获取设备绑定的卡列表
系统 SHALL 提供查询设备绑定的 IoT 卡列表功能。
API 端点: GET /api/admin/devices/:id/cards
响应字段:
bindings: 绑定列表,每个元素包含:id: 绑定记录 IDslot_position: 插槽位置(1-4)iot_card_id: IoT 卡 IDiccid: ICCIDmsisdn: 接入号carrier_name: 运营商名称status: 卡状态bind_time: 绑定时间
Scenario: 查询设备绑定的卡
- WHEN 管理员查询设备(ID=1)绑定的卡
- THEN 系统返回该设备所有已绑定的卡信息,按插槽位置排序
Scenario: 查询无绑定卡的设备
- WHEN 管理员查询没有绑定卡的设备
- THEN 系统返回空的绑定列表
Requirement: 绑定卡到设备
系统 SHALL 提供将 IoT 卡绑定到设备指定插槽的功能,仅平台用户可操作。
API 端点: POST /api/admin/devices/:id/cards
请求参数:
iot_card_id: IoT 卡 ID(必填)slot_position: 插槽位置 1-4(必填)
业务规则:
- 仅平台用户可操作
- 插槽位置不能超过设备的 max_sim_slots
- 该插槽必须为空(无已绑定的卡)
- 该卡不能已绑定到其他设备
- 绑定操作不改变卡的 shop_id
权限: 仅平台用户
Scenario: 绑定卡到设备成功
- WHEN 平台管理员将 IoT 卡(ID=101)绑定到设备(ID=1)的插槽 2
- THEN 系统创建绑定记录,返回绑定成功信息
Scenario: 绑定到已占用的插槽
- WHEN 平台管理员尝试绑定卡到已有卡的插槽
- THEN 系统返回错误,提示"该插槽已有绑定的卡"
Scenario: 绑定已被绑定的卡
- WHEN 平台管理员尝试绑定已绑定到其他设备的卡
- THEN 系统返回错误,提示"该卡已绑定到其他设备"
Scenario: 插槽位置超出范围
- WHEN 平台管理员尝试绑定卡到插槽 5(设备 max_sim_slots=4)
- THEN 系统返回错误,提示"插槽位置超出设备最大插槽数"
Scenario: 代理尝试绑定卡
- WHEN 代理用户尝试绑定卡到设备
- THEN 系统返回 403 错误,提示"无权限执行此操作"
Requirement: 解绑设备上的卡
系统 SHALL 提供解绑设备上指定卡的功能,仅平台用户可操作。
API 端点: DELETE /api/admin/devices/:id/cards/:cardId
业务规则:
- 仅平台用户可操作
- 更新绑定记录的 bind_status 为 2(已解绑),记录 unbind_time
- 解绑操作不改变卡的 shop_id
权限: 仅平台用户
Scenario: 解绑卡成功
- WHEN 平台管理员解绑设备(ID=1)上的卡(ID=101)
- THEN 系统更新绑定记录状态为已解绑,返回成功信息
Scenario: 解绑不存在的绑定关系
- WHEN 平台管理员尝试解绑不存在的绑定关系
- THEN 系统返回错误,提示"该卡未绑定到此设备"
Scenario: 代理尝试解绑卡
- WHEN 代理用户尝试解绑设备上的卡
- THEN 系统返回 403 错误,提示"无权限执行此操作"
Requirement: 批量分配设备
系统 SHALL 提供批量分配设备给下级店铺的功能,分配时自动同步绑定卡的归属。
API 端点: POST /api/admin/devices/allocate
请求参数:
target_shop_id: 目标店铺 ID(必填)device_ids: 设备 ID 列表(必填,最多 100 个)remark: 备注(可选)
业务规则:
- 只能分配给直属下级店铺,不可跨级
- 平台只能分配 shop_id=NULL 的设备
- 代理只能分配自己店铺的设备
- 分配后:
- 设备的 shop_id 变更为目标店铺 ID
- 设备绑定的所有卡的 shop_id 也变更为目标店铺 ID
- 设备状态变为「已分销」(2)
- 创建资产分配记录(asset_type='device')
响应:
success_count: 成功数量fail_count: 失败数量failed_items: 失败详情列表
Scenario: 平台分配设备给一级代理
- WHEN 平台管理员将 5 台设备分配给一级代理店铺(ID=10)
- THEN 系统更新这 5 台设备及其绑定卡的 shop_id 为 10,创建分配记录,返回成功数量
Scenario: 代理分配设备给下级
- WHEN 代理(店铺 ID=10)将 3 台设备分配给直属下级店铺(ID=101)
- THEN 系统更新这 3 台设备及其绑定卡的 shop_id 为 101,创建分配记录
Scenario: 分配给非直属下级
- WHEN 代理(店铺 ID=10)尝试分配设备给非直属下级店铺(ID=1011,是 101 的下级)
- THEN 系统返回错误,提示"只能分配给直属下级店铺"
Scenario: 分配不属于自己的设备
- WHEN 代理(店铺 ID=10)尝试分配其他店铺的设备
- THEN 系统跳过这些设备,只分配属于自己的设备
Requirement: 批量回收设备
系统 SHALL 提供批量回收已分配设备的功能,回收时自动同步绑定卡的归属。
API 端点: POST /api/admin/devices/recall
请求参数:
device_ids: 设备 ID 列表(必填,最多 100 个)remark: 备注(可选)
业务规则:
- 只能回收直属下级店铺的设备,不可跨级
- 平台回收后:设备和绑定卡的 shop_id 变为 NULL
- 代理回收后:设备和绑定卡的 shop_id 变为执行回收的店铺 ID
- 创建资产回收记录(asset_type='device')
响应:
success_count: 成功数量fail_count: 失败数量failed_items: 失败详情列表
Scenario: 平台回收一级代理的设备
- WHEN 平台管理员回收一级代理店铺(ID=10)的 3 台设备
- THEN 系统更新这 3 台设备及其绑定卡的 shop_id 为 NULL,创建回收记录
Scenario: 代理回收下级的设备
- WHEN 代理(店铺 ID=10)回收下级店铺(ID=101)的 2 台设备
- THEN 系统更新这 2 台设备及其绑定卡的 shop_id 为 10,创建回收记录
Scenario: 回收非直属下级的设备
- WHEN 代理(店铺 ID=10)尝试回收非直属下级的设备
- THEN 系统返回错误,提示"只能回收直属下级店铺的设备"