All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m33s
- 删除 CSV 解析代码,新增 Excel 解析器 (excelize) - 更新 IoT 卡和设备导入任务处理器 - 更新 API 路由文档和前端接入指南 - 归档变更到 openspec/changes/archive/ - 同步 delta specs 到 main specs
297 lines
10 KiB
Markdown
297 lines
10 KiB
Markdown
# iot-card-import-task Specification
|
||
|
||
## Purpose
|
||
TBD - created by archiving change iot-card-standalone-management. Update Purpose after archive.
|
||
## Requirements
|
||
### Requirement: 导入任务实体定义
|
||
|
||
系统 SHALL 定义 IoT 卡导入任务(IotCardImportTask)实体,用于跟踪 IoT 卡批量导入的进度和结果。
|
||
|
||
**实体字段**:
|
||
|
||
**任务信息**:
|
||
- `id`: 任务 ID(主键,BIGINT)
|
||
- `task_no`: 任务编号(VARCHAR(50),唯一,格式: IMP-YYYYMMDD-XXXXXX)
|
||
- `status`: 任务状态(INT,1-待处理 2-处理中 3-已完成 4-失败)
|
||
|
||
**导入参数**:
|
||
- `carrier_id`: 运营商 ID(BIGINT,必填)
|
||
- `carrier_type`: 运营商类型(VARCHAR(10),CMCC/CUCC/CTCC/CBN)
|
||
- `batch_no`: 批次号(VARCHAR(100),可选)
|
||
- `file_name`: 原始文件名(VARCHAR(255),可选)
|
||
|
||
**待导入数据**:
|
||
- `card_list`: 待导入卡列表(JSONB,结构: [{iccid, msisdn}],替代原 iccid_list)
|
||
|
||
**进度统计**:
|
||
- `total_count`: 总数(INT,CSV 文件总行数)
|
||
- `success_count`: 成功数(INT,成功导入的卡数量)
|
||
- `skip_count`: 跳过数(INT,因重复等原因跳过的数量)
|
||
- `fail_count`: 失败数(INT,因格式错误等原因失败的数量)
|
||
|
||
**结果详情**:
|
||
- `skipped_items`: 跳过记录详情(JSONB,结构: [{line, iccid, msisdn, reason}])
|
||
- `failed_items`: 失败记录详情(JSONB,结构: [{line, iccid, msisdn, reason}])
|
||
|
||
**时间和错误**:
|
||
- `started_at`: 开始处理时间(TIMESTAMP,可空)
|
||
- `completed_at`: 完成时间(TIMESTAMP,可空)
|
||
- `error_message`: 任务级错误信息(TEXT,可空,如文件解析失败等)
|
||
|
||
**系统字段**:
|
||
- `shop_id`: 店铺 ID(BIGINT,可空,记录发起导入的店铺)
|
||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||
- `creator`: 创建人 ID(BIGINT)
|
||
- `updater`: 更新人 ID(BIGINT)
|
||
|
||
#### Scenario: 创建导入任务
|
||
|
||
- **GIVEN** 管理员上传包含 ICCID 和 MSISDN 两列的 CSV 文件
|
||
- **WHEN** 系统解析 CSV 并创建导入任务
|
||
- **THEN** 系统创建导入任务记录,`card_list` 包含 [{iccid, msisdn}] 结构,`status` 为 1(待处理)
|
||
|
||
---
|
||
|
||
### Requirement: 导入任务状态流转
|
||
|
||
系统 SHALL 管理导入任务的状态流转,确保状态变更符合业务规则。
|
||
|
||
**状态定义**:
|
||
- **1-待处理**: 任务已创建,等待 Worker 处理
|
||
- **2-处理中**: Worker 正在处理导入
|
||
- **3-已完成**: 导入处理完成(可能有部分失败)
|
||
- **4-失败**: 任务级别错误,导入中断
|
||
|
||
**状态流转规则**:
|
||
- 待处理(1) → 处理中(2): Worker 开始处理
|
||
- 处理中(2) → 已完成(3): 处理完成
|
||
- 处理中(2) → 失败(4): 发生严重错误
|
||
- 待处理(1) → 失败(4): 文件验证失败等
|
||
|
||
#### Scenario: 正常状态流转
|
||
|
||
- **WHEN** 导入任务经历完整生命周期
|
||
- **THEN** 状态依次变更: 待处理(1) → 处理中(2) → 已完成(3)
|
||
|
||
#### Scenario: 异常状态流转
|
||
|
||
- **WHEN** 导入任务处理过程中发生严重错误
|
||
- **THEN** 状态变更: 待处理(1) → 处理中(2) → 失败(4)
|
||
|
||
---
|
||
|
||
### Requirement: 导入任务列表查询
|
||
|
||
系统 SHALL 支持查询导入任务列表,用于管理和监控导入任务。
|
||
|
||
**查询条件**:
|
||
- 任务状态(status): 可选,1-待处理 2-处理中 3-已完成 4-失败
|
||
- 运营商 ID(carrier_id): 可选
|
||
- 批次号(batch_no): 可选,模糊匹配
|
||
- 创建时间范围: 可选
|
||
|
||
**分页**:
|
||
- 默认每页 20 条,最大每页 100 条
|
||
- 默认按创建时间倒序排列
|
||
|
||
**数据权限**:
|
||
- 基于 shop_id 自动应用数据权限过滤
|
||
- 代理只能看到自己店铺及下级店铺发起的导入任务
|
||
|
||
#### Scenario: 查询所有导入任务
|
||
|
||
- **WHEN** 管理员查询导入任务列表
|
||
- **THEN** 系统返回导入任务列表,包含任务编号、状态、运营商、总数、成功数、跳过数、失败数、创建时间
|
||
|
||
#### Scenario: 按状态筛选导入任务
|
||
|
||
- **WHEN** 管理员查询状态为 2(处理中) 的导入任务
|
||
- **THEN** 系统返回所有正在处理的导入任务列表
|
||
|
||
---
|
||
|
||
### Requirement: 导入任务详情查询
|
||
|
||
系统 SHALL 支持查询单个导入任务的详细信息,包括跳过/失败记录详情。
|
||
|
||
**详情信息**:
|
||
- 任务基本信息: 任务编号、状态、运营商、批次号、文件名
|
||
- 进度统计: 总数、成功数、跳过数、失败数
|
||
- 时间信息: 创建时间、开始时间、完成时间
|
||
- 跳过记录详情: 行号、ICCID、原因
|
||
- 失败记录详情: 行号、ICCID、原因
|
||
- 错误信息: 任务级错误(如有)
|
||
|
||
#### Scenario: 查询导入任务详情
|
||
|
||
- **WHEN** 管理员查询导入任务(ID 为 1)的详情
|
||
- **THEN** 系统返回任务完整信息,包括跳过和失败记录的详细列表
|
||
|
||
#### Scenario: 查询导入任务的跳过记录
|
||
|
||
- **WHEN** 管理员查询导入任务(ID 为 1)的跳过记录
|
||
- **THEN** 系统返回跳过记录列表,每条包含: 行号(line)、ICCID、原因(如"ICCID 已存在")
|
||
|
||
#### Scenario: 查询导入任务的失败记录
|
||
|
||
- **WHEN** 管理员查询导入任务(ID 为 1)的失败记录
|
||
- **THEN** 系统返回失败记录列表,每条包含: 行号(line)、ICCID、原因(如"电信 ICCID 必须为 19 位")
|
||
|
||
---
|
||
|
||
### Requirement: 导入任务数据校验
|
||
|
||
系统 SHALL 对导入任务数据进行校验,确保数据完整性和一致性。
|
||
|
||
**校验规则**:
|
||
- 任务编号(task_no): 必填,系统自动生成,格式 IMP-YYYYMMDD-XXXXXX,唯一
|
||
- 任务状态(status): 必填,枚举值 1(待处理) | 2(处理中) | 3(已完成) | 4(失败)
|
||
- 运营商 ID(carrier_id): 必填,必须是有效的运营商 ID
|
||
- 总数(total_count): 必填,≥ 0
|
||
- 成功数(success_count): 必填,≥ 0,≤ total_count
|
||
- 跳过数(skip_count): 必填,≥ 0,≤ total_count
|
||
- 失败数(fail_count): 必填,≥ 0,≤ total_count
|
||
- 数量一致性: success_count + skip_count + fail_count ≤ total_count
|
||
|
||
#### Scenario: 创建任务时运营商 ID 无效
|
||
|
||
- **WHEN** 创建导入任务时 carrier_id 不存在
|
||
- **THEN** 系统拒绝创建,返回错误信息"运营商 ID 无效"
|
||
|
||
#### Scenario: 更新任务时数量不一致
|
||
|
||
- **WHEN** 更新导入任务时 success_count + skip_count + fail_count > total_count
|
||
- **THEN** 系统拒绝更新,返回错误信息"统计数量不一致"
|
||
|
||
### Requirement: Excel 文件格式规范
|
||
|
||
系统 SHALL 要求 Excel 文件必须包含 ICCID 和 MSISDN 两列。
|
||
|
||
**文件格式要求**:
|
||
- **文件格式**: 仅支持 `.xlsx` (Excel 2007+)
|
||
- **Sheet**: 读取第一个sheet,或优先读取名为"导入数据"的sheet
|
||
- **表头行**: 第1行(可选,但建议包含)
|
||
- **表头识别关键字**:
|
||
- ICCID列: iccid/ICCID/卡号/号码
|
||
- MSISDN列: msisdn/MSISDN/接入号/手机号/电话/号码
|
||
- **列数要求**: 至少2列(ICCID和MSISDN)
|
||
- **列格式**: 应设置为文本格式(避免长数字被转为科学记数法)
|
||
|
||
**解析规则**:
|
||
- 自动检测表头(第1行包含识别关键字则跳过)
|
||
- 自动去除单元格首尾空格
|
||
- 跳过空行
|
||
- ICCID 为空的行记录为失败
|
||
- MSISDN 为空的行记录为失败
|
||
|
||
**示例Excel内容**:
|
||
```
|
||
| ICCID | MSISDN |
|
||
|----------------------|-------------|
|
||
| 89860012345678901234 | 13800000001 |
|
||
| 89860012345678901235 | 13800000002 |
|
||
```
|
||
|
||
#### Scenario: 解析标准双列 Excel 文件
|
||
|
||
- **GIVEN** Excel 文件内容为:
|
||
```
|
||
| ICCID | MSISDN |
|
||
| 89860012345678901234 | 13800000001 |
|
||
| 89860012345678901235 | 13800000002 |
|
||
```
|
||
- **WHEN** 系统解析该 Excel 文件
|
||
- **THEN** 解析结果包含 2 条有效记录,每条包含 ICCID 和 MSISDN
|
||
|
||
#### Scenario: 支持中文表头
|
||
|
||
- **GIVEN** Excel 文件内容为:
|
||
```
|
||
| 卡号 | 接入号 |
|
||
| 89860012345678901234 | 13800000001 |
|
||
```
|
||
- **WHEN** 系统解析该 Excel 文件
|
||
- **THEN** 系统正确识别列,解析结果包含 1 条有效记录
|
||
|
||
#### Scenario: 拒绝非Excel格式文件
|
||
|
||
- **GIVEN** 上传文件扩展名为 .csv
|
||
- **WHEN** 系统尝试解析该文件
|
||
- **THEN** 系统返回错误 "不支持的文件格式 .csv,请上传Excel文件(.xlsx)"
|
||
|
||
#### Scenario: Excel文件无工作表
|
||
|
||
- **GIVEN** Excel 文件不包含任何工作表
|
||
- **WHEN** 系统尝试解析该 Excel 文件
|
||
- **THEN** 系统返回错误 "Excel文件无工作表"
|
||
|
||
#### Scenario: MSISDN 为空的行记录失败
|
||
|
||
- **GIVEN** Excel 文件内容为:
|
||
```
|
||
| ICCID | MSISDN |
|
||
| 89860012345678901234 | 13800000001 |
|
||
| 89860012345678901235 | |
|
||
```
|
||
- **WHEN** 系统解析该 Excel 文件
|
||
- **THEN** 第一条记录解析成功,第二条记录标记为失败,原因为 "MSISDN 不能为空"
|
||
|
||
#### Scenario: ICCID 为空的行记录失败
|
||
|
||
- **GIVEN** Excel 文件内容为:
|
||
```
|
||
| ICCID | MSISDN |
|
||
| 89860012345678901234 | 13800000001 |
|
||
| | 13800000002 |
|
||
```
|
||
- **WHEN** 系统解析该 Excel 文件
|
||
- **THEN** 第一条记录解析成功,第二条记录标记为失败,原因为 "ICCID 不能为空"
|
||
|
||
#### Scenario: 长数字无损解析
|
||
|
||
- **GIVEN** Excel 文件中ICCID列设置为文本格式,包含20位数字 "89860012345678901234"
|
||
- **WHEN** 系统解析该 Excel 文件
|
||
- **THEN** ICCID 完整保留为 "89860012345678901234",无精度损失,无科学记数法
|
||
|
||
---
|
||
|
||
### Requirement: 导入时填充 MSISDN 字段
|
||
|
||
系统 SHALL 在创建 IoT 卡记录时填充 MSISDN 字段。
|
||
|
||
**处理规则**:
|
||
- 从 `card_list` 中获取 ICCID 和 MSISDN
|
||
- 创建 `IotCard` 记录时同时设置 `iccid` 和 `msisdn` 字段
|
||
|
||
#### Scenario: 创建卡记录时填充 MSISDN
|
||
|
||
- **GIVEN** 导入任务包含卡数据 [{iccid: "898600...", msisdn: "13800000001"}]
|
||
- **WHEN** Worker 处理导入任务创建卡记录
|
||
- **THEN** 创建的 `IotCard` 记录 `iccid` 为 "898600...",`msisdn` 为 "13800000001"
|
||
|
||
---
|
||
|
||
### Requirement: 导入物联网卡时记录运营商信息
|
||
|
||
系统 SHALL 在导入物联网卡时,将运营商的 carrier_type 和 carrier_name 作为冗余字段存储到 IotCard 记录中。这些字段在导入时从 Carrier 表查询并写入,后续不再依赖 Carrier 表。
|
||
|
||
#### Scenario: 导入时填充冗余字段
|
||
- **WHEN** 系统处理物联网卡导入任务
|
||
- **THEN** 系统根据 carrier_id 查询 Carrier 表,将 carrier_type 和 carrier_name 写入每条 IotCard 记录
|
||
|
||
#### Scenario: Carrier 不存在
|
||
- **WHEN** 导入任务指定的 carrier_id 对应的 Carrier 不存在或已删除
|
||
- **THEN** 系统拒绝导入,返回错误"运营商不存在"
|
||
|
||
---
|
||
|
||
### Requirement: 导入任务记录运营商名称
|
||
|
||
系统 SHALL 在创建导入任务时,将 carrier_name 作为冗余字段存储到 IotCardImportTask 记录中(已有 carrier_type)。
|
||
|
||
#### Scenario: 创建导入任务时填充 carrier_name
|
||
- **WHEN** 管理员创建物联网卡导入任务
|
||
- **THEN** 系统根据 carrier_id 查询 Carrier 表,将 carrier_name 写入导入任务记录
|
||
|