# 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 1. 实现设备基础管理(列表、详情、删除) 2. 实现设备导入(CSV 批量导入,自动绑定卡) 3. 实现设备卡绑定管理(绑定、解绑、查询) 4. 实现设备分配/回收(自动同步绑定的卡) 5. 复用现有资产分配记录能力 ### Non-Goals 1. ❌ 设备操作(远程重启、改密码、重置) 2. ❌ 设备套餐购买和流量共享 3. ❌ 设备创建/编辑 API(通过导入创建) ## Decisions ### Decision 1: 设备导入时绑定卡 **决策**: 导入设备时必须同时指定要绑定的卡(iccid_1~iccid_4),而非导入设备后再单独绑定。 **原因**: - 业务流程:平台在外部系统报单后发货,设备和卡是一起出库的 - 减少操作步骤:一次导入完成设备创建和卡绑定 - 数据一致性:避免"空设备"状态 **CSV 格式**: ```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。 **原因**: - 业务需求:设备和卡作为整体分配,不能分开 - 数据一致性:设备和卡的归属必须一致 - 简化操作:代理商无需感知卡的存在 **实现**: ```go // 分配设备时 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 ```sql 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 1. **设备导入失败后是否支持重试?** - 当前设计:不支持,用户需修正 CSV 重新导入 - 可后续添加:断点续传、失败重试功能 2. **设备和卡 shop_id 不一致时如何修复?** - 需要管理员工具或 SQL 脚本修复 - 建议后续添加数据一致性检查接口