Files
junhong_cmp_fiber/openspec/specs/iot-card-import-task/spec.md
huang afc6d7fc76
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 50s
docs: 归档 excel-import-refactor,同步更新主 specs
- 将变更目录移至 archive/2026-04-09-excel-import-refactor
- device-import spec:更新为固定列位置读取(IMEI 调整至第5列),新增 MaxSimSlots 范围校验说明
- iot-card-import-task spec:更新为固定列位置读取,新增"导入批次支持卡业务类型"需求

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 17:37:44 +08:00

13 KiB
Raw Blame History

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 仅允许超级管理员与平台用户创建 IoT 卡导入任务。

Scenario: 平台用户创建导入任务

  • WHEN 平台用户请求创建导入任务
  • THEN 系统创建导入任务并返回任务信息

Scenario: 非平台用户创建导入任务被拒绝

  • WHEN 非平台用户(代理账号/企业账号等)请求创建导入任务
  • THEN 系统返回 403Forbidden并返回统一错误码 CodeForbidden

Requirement: 导入任务列表查询

系统 SHALL 支持查询导入任务列表,用于管理和监控导入任务。

查询条件:

  • 任务状态(status): 可选,1-待处理 2-处理中 3-已完成 4-失败
  • 运营商 ID(carrier_id): 可选
  • 批次号(batch_no): 可选,模糊匹配
  • 创建时间范围: 可选

分页:

  • 默认每页 20 条,最大每页 100 条
  • 默认按创建时间倒序排列

权限:

  • 仅超级管理员/平台用户可查询导入任务列表

Scenario: 查询所有导入任务

  • WHEN 平台管理员查询导入任务列表
  • THEN 系统返回导入任务列表,包含任务编号、状态、运营商、总数、成功数、跳过数、失败数、创建时间

Scenario: 按状态筛选导入任务

  • WHEN 平台管理员查询状态为 2(处理中) 的导入任务
  • THEN 系统返回所有正在处理的导入任务列表

Scenario: 非平台用户查询导入任务列表被拒绝

  • WHEN 非平台用户(代理账号/企业账号等)查询导入任务列表
  • THEN 系统返回 403Forbidden并返回统一错误码 CodeForbidden

Requirement: 导入任务详情查询

系统 SHALL 支持查询单个导入任务的详细信息,包括跳过/失败记录详情。

详情信息:

  • 任务基本信息: 任务编号、状态、运营商、批次号、文件名
  • 进度统计: 总数、成功数、跳过数、失败数
  • 时间信息: 创建时间、开始时间、完成时间
  • 跳过记录详情: 行号、ICCID、原因
  • 失败记录详情: 行号、ICCID、原因
  • 错误信息: 任务级错误(如有)

权限:

  • 仅超级管理员/平台用户可查询导入任务详情

Scenario: 查询导入任务详情

  • WHEN 平台管理员查询导入任务(ID 为 1)的详情
  • THEN 系统返回任务完整信息,包括跳过和失败记录的详细列表

Scenario: 非平台用户查询导入任务详情被拒绝

  • WHEN 非平台用户(代理账号/企业账号等)查询导入任务详情
  • THEN 系统返回 403Forbidden并返回统一错误码 CodeForbidden

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行固定为表头永远跳过内容不限中文、英文均可
  • 列位置(固定,不可变):
    • 第1列索引0: ICCID
    • 第2列索引1: MSISDN
    • 第3列索引2: 虚拟号(必填)
  • 列格式: 应设置为文本格式(避免长数字被转为科学记数法)

解析规则:

  • 按列索引取值,不识别列名,第一行永远跳过
  • 自动去除单元格首尾空格
  • 若 ICCID、MSISDN、virtual_no 三列皆为空,视为空行跳过,不计入 total不计入失败
  • ICCID 为空(但其他列非空)的行记录为失败,失败原因为"ICCID 不能为空"
  • MSISDN 为空(但其他列非空)的行记录为失败,失败原因为"MSISDN 不能为空"
  • virtual_no第3列为空的行记录为失败失败原因为"虚拟号(virtual_no)不能为空"

virtual_no 导入规则:

  • virtual_no 为必填,为空则该行记录为失败
  • virtual_no 全局唯一(跨卡和设备),重复则失败,原因为"虚拟号已被占用: <值>"

Scenario: 正常导入ICCID 和 VirtualNo 均有值)

  • WHEN Excel 中某行第1列 ICCID="898600XXXXX"第3列 virtual_no="CARD-001"第2列 msisdn="13800000001"
  • THEN 导入成功卡记录写入数据库VirtualNo 和 ICCID 同步注册到 tb_asset_identifier

Scenario: 中文表头正常导入

  • GIVEN Excel 文件第1行表头为 ICCID | 接入号 | 虚拟号(任意中文或英文内容)
  • WHEN 系统解析该 Excel 文件
  • THEN 系统跳过第1行从第2行开始按列位置解析数据

Scenario: VirtualNo 为空的行被拒绝

  • WHEN Excel 中某行第3列为空
  • THEN 该行计入失败,失败原因为"虚拟号(virtual_no)不能为空"
  • THEN 其他合法行继续导入,不因此行中断

Scenario: VirtualNo 重复被拒绝

  • WHEN Excel 中某行第3列的值与已有卡/设备的 VirtualNo 重复(跨表)
  • THEN 该行计入失败,原因为"虚拟号已被占用: <值>"

Scenario: 三列皆空时跳过不计入 total

  • WHEN Excel 中某行 ICCID、MSISDN、virtual_no 三列均为空
  • THEN 该行视为空行跳过,不计入 total不计入失败

Scenario: 导入任务完成后的结果报告

  • WHEN 导入任务处理完毕
  • THEN 结果包含:success_countfail_countskip_count、失败明细列表(含行号、原因)
  • THEN VirtualNo 为空的失败行在明细中明确体现行号和原因

Scenario: 拒绝非Excel格式文件

  • GIVEN 上传文件扩展名为 .csv
  • WHEN 系统尝试解析该文件
  • THEN 系统返回错误"不支持的文件格式 .csv请上传Excel文件(.xlsx)"

Scenario: MSISDN 为空的行记录失败

  • WHEN Excel 中某行第2列为空
  • THEN 该行标记为失败,原因为"MSISDN 不能为空"

Scenario: 长数字无损解析

  • GIVEN Excel 文件中第1列设置为文本格式包含 20 位数字 "89860012345678901234"
  • WHEN 系统解析该 Excel 文件
  • THEN ICCID 完整保留为 "89860012345678901234",无精度损失,无科学记数法

Requirement: 导入时填充 MSISDN 字段

系统 SHALL 在创建 IoT 卡记录时填充 MSISDN 字段。

处理规则:

  • card_list 中获取 ICCID 和 MSISDN
  • 创建 IotCard 记录时同时设置 iccidmsisdn 字段

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 写入导入任务记录

Requirement: 导入批次支持卡业务类型

系统 SHALL 在 IoT 卡导入请求中支持 card_category 参数,以批次为单位指定导入卡的业务类型,默认为普通卡。

请求字段:

  • card_category: 卡业务类型(枚举:normal / industry,可选,默认 normal

任务字段:

  • IotCardImportTask 新增 card_category 字段VARCHAR(20),默认 normal

导入行为:

  • 导入任务中所有卡统一使用 card_category 的值,不支持同一批次混用

Scenario: 不传 card_category 时默认为普通卡

  • WHEN 导入请求未包含 card_category 字段
  • THEN 导入的所有卡 card_categorynormal

Scenario: 指定行业卡导入

  • WHEN 导入请求中 card_category = industry
  • THEN 该批次导入的所有卡 card_category 均为 industry

Scenario: 传入无效 card_category

  • WHEN 导入请求中 card_category = unknown(非枚举值)
  • THEN 系统返回参数校验错误,提示卡业务类型无效