Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
- 新增 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>
7.1 KiB
7.1 KiB
Design: 设备管理功能
Context
背景
系统已有完整的单卡(IoT Card)管理功能,包括单卡列表、分配、回收、导入。现需要在单卡之上增加设备维度的管理能力。
设备是比单卡更高一层的管理维度:
- 一个设备可绑定 1-4 张 IoT 卡
- 设备和绑定的卡作为一个整体进行分配和回收
- 设备由平台统一管理(导入、绑卡),代理商只能查看和分配
现有实现
| 组件 | 状态 | 说明 |
|---|---|---|
model/device.go |
✅ 已有 | Device Model |
model/package.go |
✅ 已有 | DeviceSimBinding Model |
tb_device |
✅ 已有 | 设备表 |
tb_device_sim_binding |
✅ 已有 | 设备卡绑定表 |
| Store/Service/Handler | ❌ 需新增 | 设备业务逻辑 |
约束
- 遵循现有分层架构:Handler → Service → Store → Model
- 复用现有的资产分配记录(asset-allocation-record)能力
- 参考现有 ICCID 导入实现异步任务
- 权限控制:导入、绑卡、删除仅平台用户
Goals / Non-Goals
Goals
- 实现设备基础管理(列表、详情、删除)
- 实现设备导入(CSV 批量导入,自动绑定卡)
- 实现设备卡绑定管理(绑定、解绑、查询)
- 实现设备分配/回收(自动同步绑定的卡)
- 复用现有资产分配记录能力
Non-Goals
- ❌ 设备操作(远程重启、改密码、重置)
- ❌ 设备套餐购买和流量共享
- ❌ 设备创建/编辑 API(通过导入创建)
Decisions
Decision 1: 设备导入时绑定卡
决策: 导入设备时必须同时指定要绑定的卡(iccid_1~iccid_4),而非导入设备后再单独绑定。
原因:
- 业务流程:平台在外部系统报单后发货,设备和卡是一起出库的
- 减少操作步骤:一次导入完成设备创建和卡绑定
- 数据一致性:避免"空设备"状态
CSV 格式:
device_no,device_name,device_model,device_type,max_sim_slots,manufacturer,iccid_1,iccid_2,iccid_3,iccid_4
备注: 绑定/解绑 API 仅用于导入后的调整(换卡、补卡)。
Decision 2: 设备分配时自动同步卡的 shop_id
决策: 分配/回收设备时,自动同步修改绑定卡的 shop_id。
原因:
- 业务需求:设备和卡作为整体分配,不能分开
- 数据一致性:设备和卡的归属必须一致
- 简化操作:代理商无需感知卡的存在
实现:
// 分配设备时
func (s *Service) AllocateDevices(ctx, req) {
// 1. 更新设备 shop_id
// 2. 查询设备绑定的所有卡
// 3. 批量更新卡的 shop_id
// 4. 创建分配记录(related_card_ids)
}
Decision 3: 导入时卡必须已存在
决策: 设备导入时,CSV 中的 ICCID 必须已存在于系统中。
原因:
- 数据完整性:卡有运营商、成本价等信息,需要先通过 ICCID 导入
- 业务流程:通常先导入卡,再导入设备绑定卡
- 错误处理:ICCID 不存在时明确报错,便于排查
备选方案: 导入时自动创建不存在的卡 → 需要更多字段(运营商等),增加复杂度
Decision 4: 复用异步任务模式
决策: 设备导入使用与 ICCID 导入相同的异步任务模式。
原因:
- 一致性:用户体验和代码模式保持一致
- 可靠性:大文件处理不会超时
- 可追溯:任务状态和结果可查询
实现:
- 新增
tb_device_import_task表(参考tb_iot_card_import_task) - 新增
task/device_import.go异步处理器 - 复用
pkg/queue和pkg/storage能力
Decision 5: 权限控制策略
决策: 设备导入、绑卡、删除仅限平台用户;列表查询、分配回收所有人可用。
| 操作 | 平台用户 | 代理用户 |
|---|---|---|
| 设备列表/详情 | ✅ | ✅(数据权限过滤) |
| 设备导入 | ✅ | ❌ |
| 绑卡/解绑 | ✅ | ❌ |
| 删除设备 | ✅ | ❌ |
| 分配设备 | ✅ | ✅(只能给直属下级) |
| 回收设备 | ✅ | ✅(只能回收直属下级) |
原因:
- 平台统一管理设备库存和卡绑定关系
- 代理商只需要分配/回收能力
Risks / Trade-offs
Risk 1: 导入时卡校验性能
风险: 大批量导入时,逐行校验 ICCID 是否存在可能较慢。
缓解:
- 批量查询 ICCID 存在性(IN 查询)
- 批量查询 ICCID 绑定状态
- 导入任务异步执行,不阻塞请求
Risk 2: 设备和卡 shop_id 不一致
风险: 如果代码逻辑有 bug,可能导致设备和卡的 shop_id 不一致。
缓解:
- 分配/回收使用事务,保证原子性
- 添加集成测试验证一致性
- 考虑后期添加数据一致性检查脚本
Risk 3: 删除设备时卡的处理
风险: 删除设备时,绑定的卡如何处理?
决策: 删除设备时自动解绑所有卡,卡的 shop_id 保持不变。
原因: 卡是有价值的资产,不应随设备删除而丢失。
Data Model
新增表: tb_device_import_task
CREATE TABLE tb_device_import_task (
id BIGSERIAL PRIMARY KEY,
task_no VARCHAR(50) NOT NULL UNIQUE,
status INT NOT NULL DEFAULT 1, -- 1-待处理 2-处理中 3-已完成 4-失败
batch_no VARCHAR(100),
file_key VARCHAR(500),
file_name VARCHAR(255),
total_count INT DEFAULT 0,
success_count INT DEFAULT 0,
skip_count INT DEFAULT 0,
fail_count INT DEFAULT 0,
skipped_items JSONB,
failed_items JSONB,
error_message TEXT,
started_at TIMESTAMP,
completed_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMP,
creator BIGINT,
updater BIGINT
);
CREATE INDEX idx_device_import_task_status ON tb_device_import_task(status);
CREATE INDEX idx_device_import_task_batch_no ON tb_device_import_task(batch_no);
现有表(无需修改)
tb_device: 设备表tb_device_sim_binding: 设备卡绑定表tb_asset_allocation_record: 资产分配记录表(已支持 device 类型)
API Design
设备管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/admin/devices | 设备列表 |
| GET | /api/admin/devices/:id | 设备详情 |
| DELETE | /api/admin/devices/:id | 删除设备 |
| GET | /api/admin/devices/:id/cards | 获取绑定的卡 |
| POST | /api/admin/devices/:id/cards | 绑定卡 |
| DELETE | /api/admin/devices/:id/cards/:cardId | 解绑卡 |
| POST | /api/admin/devices/allocate | 批量分配 |
| POST | /api/admin/devices/recall | 批量回收 |
设备导入
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/admin/devices/import | 提交导入任务 |
| GET | /api/admin/devices/import/tasks | 导入任务列表 |
| GET | /api/admin/devices/import/tasks/:id | 导入任务详情 |
Open Questions
-
设备导入失败后是否支持重试?
- 当前设计:不支持,用户需修正 CSV 重新导入
- 可后续添加:断点续传、失败重试功能
-
设备和卡 shop_id 不一致时如何修复?
- 需要管理员工具或 SQL 脚本修复
- 建议后续添加数据一致性检查接口