Files
huang 477a9fc98d
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat: 添加设备IMEI和单卡ICCID查询接口
- 新增 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>
2026-01-27 09:59:54 +08:00

225 lines
7.1 KiB
Markdown
Raw Permalink 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.
# 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 脚本修复
- 建议后续添加数据一致性检查接口