Files
junhong_cmp_fiber/openspec/specs/iot-card/spec.md
huang ce0783f96e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m30s
feat: 实现设备管理和设备导入功能,修复测试问题
主要变更:
- 实现设备管理模块(创建、查询、列表、更新状态、删除)
- 实现设备批量导入功能(CSV 解析、ICCID 绑定、异步任务处理)
- 添加设备-SIM 卡绑定约束(部分唯一索引防止并发问题)
- 修复 fee_rate 数据库字段类型(numeric -> bigint)
- 修复测试数据隔离问题(基于增量断言)
- 修复集成测试中间件顺序问题
- 清理无用测试文件(PersonalCustomer、Email 相关)
- 归档 enterprise-card-authorization 变更
2026-01-26 18:05:12 +08:00

23 KiB
Raw Blame History

IoT Card Management

Purpose

Manage IoT cards (SIM cards) for the IoT management system, including inventory management, distribution, activation, status tracking, and Gateway integration.

This capability supports:

  • IoT card entity definition and lifecycle management
  • Platform self-operation and agent distribution models
  • Integration with Gateway project for real-time status synchronization
  • Batch import and multi-dimensional querying
  • Support for normal cards (require real-name verification) and industry cards (no real-name required)

Requirements

Requirement: IoT 卡实体定义

系统 SHALL 定义 IoT 卡(IotCard)实体,包含 IoT 卡(物联网卡/流量卡/SIM卡)的商品属性、状态属性、所有权信息和 Gateway 集成字段。

核心概念: IoT 卡 = 物联网卡 = SIM 卡 = 网卡 = 流量卡(同一个东西,不同叫法)。系统使用 ICCID 作为 IoT 卡的唯一标识。

卡业务类型:

  • 普通卡(normal): 需要实名认证才能激活使用,遵循运营商实名制要求
  • 行业卡(industry): 不需要实名认证,可以直接激活使用,适用于企业/行业客户批量采购场景

实体字段:

商品属性:

  • id: IoT 卡 ID(主键,BIGINT)
  • iccid: ICCID(VARCHAR(50),唯一,国际移动用户识别码,IoT卡的唯一标识)
  • card_type: 卡类型(VARCHAR(50),如 "4G"、"5G"、"NB-IoT")
  • card_category: 卡业务类型(VARCHAR(20),枚举值:"normal"-普通卡 | "industry"-行业卡,默认 "normal")
  • carrier_id: 运营商 ID(BIGINT,关联 carriers 表,如中国移动、中国联通、中国电信)
  • imsi: IMSI(VARCHAR(50),可选,国际移动用户识别码)
  • msisdn: 手机号码(VARCHAR(20),可选)
  • batch_no: 批次号(VARCHAR(100),用于批量导入追溯)
  • supplier: 供应商名称(VARCHAR(255),可选)
  • cost_price: 成本价(DECIMAL(10,2),平台进货价)
  • distribute_price: 分销价(DECIMAL(10,2),分销给代理的价格,仅当 owner_type 为 agent 时有值)

所有权和状态:

  • status: IoT 卡状态(INT,1-在库 2-已分销 3-已激活 4-已停用)
  • owner_type: 所有者类型(VARCHAR(20),"platform"-平台自营 | "agent"-代理商 | "user"-用户 | "device"-设备)
  • owner_id: 所有者 ID(BIGINT,platform 时为 0,agent/user/device 时为对应的 ID)
  • activated_at: 激活时间(TIMESTAMP,可空)

Gateway 集成字段(从 Gateway 项目同步):

  • activation_status: 激活状态(INT,0-未激活 1-已激活)
  • real_name_status: 实名状态(INT,0-未实名 1-已实名)
  • network_status: 网络状态(INT,0-停机 1-开机)
  • data_usage_mb: 累计流量使用(BIGINT,MB 为单位,默认 0)
  • last_sync_time: 最后一次与 Gateway 同步时间(TIMESTAMP,可空)

轮询控制字段:

  • enable_polling: 是否参与轮询(BOOLEAN,默认 true,用于控制是否对该卡进行定时轮询)
  • last_data_check_at: 最后一次卡流量检查时间(TIMESTAMP,可空,记录上次轮询卡流量的时间)
  • last_real_name_check_at: 最后一次实名检查时间(TIMESTAMP,可空,记录上次轮询实名状态的时间)

系统字段:

  • created_at: 创建时间(TIMESTAMP,自动填充)
  • updated_at: 更新时间(TIMESTAMP,自动填充)

Scenario: 创建平台自营 IoT 卡

  • WHEN 平台批量导入 IoT 卡数据,ICCID 为 "89860123456789012345"
  • THEN 系统创建 IoT 卡记录,owner_type 为 "platform",owner_id 为 0,状态为 1(在库),activation_status 为 0(未激活)

Scenario: 平台分销 IoT 卡给代理

  • WHEN 平台将在库 IoT 卡分销给代理商(用户 ID 为 123),设置分销价为 50.00 元
  • THEN 系统将 IoT 卡状态从 1(在库) 变更为 2(已分销),owner_type 变更为 "agent",owner_id 设置为 123,distribute_price 设置为 50.00

Scenario: IoT 卡绑定到设备

  • WHEN 用户将 IoT 卡(ICCID 为 "8986...")绑定到设备(ID 为 1001)
  • THEN 系统在 device_sim_bindings 表创建绑定记录,IoT 卡的 owner_type 变更为 "device",owner_id 变更为 1001

Scenario: IoT 卡直接销售给用户

  • WHEN 平台或代理将 IoT 卡直接销售给用户(用户 ID 为 2001)
  • THEN 系统创建套餐订单记录,IoT 卡的 owner_type 变更为 "user",owner_id 变更为 2001

Scenario: 行业卡无需实名认证

  • WHEN 创建卡业务类型为 "industry"(行业卡)的 IoT 卡
  • THEN 系统允许该卡在 real_name_status 为 0(未实名)的情况下激活使用,不强制要求实名认证

Scenario: 普通卡需要实名认证

  • WHEN 创建卡业务类型为 "normal"(普通卡)的 IoT 卡
  • THEN 系统要求该卡必须先完成实名认证(real_name_status 为 1)才能激活使用

Requirement: IoT 卡状态流转

系统 SHALL 管理 IoT 卡的状态流转,确保状态变更符合业务规则。

状态定义:

  • 1-在库: IoT 卡在平台库存中,未分销
  • 2-已分销: IoT 卡已分销给代理商,代理可销售
  • 3-已激活: IoT 卡已被终端用户激活使用
  • 4-已停用: IoT 卡已停用,不可使用

状态流转规则:

  • 在库(1) → 已分销(2): 平台分销给代理
  • 在库(1) → 已激活(3): 平台自营直接销售给用户并激活
  • 已分销(2) → 已激活(3): 代理销售给用户并激活
  • 已激活(3) → 已停用(4): 用户或平台主动停用
  • 已停用(4) → 已激活(3): 用户或平台主动复机(仅在符合业务规则时)

Scenario: 代理销售 IoT 卡给用户

  • WHEN 代理商销售已分销 IoT 卡给终端用户并激活
  • THEN 系统将 IoT 卡状态从 2(已分销) 变更为 3(已激活),activated_at 记录激活时间,activation_status 从 Gateway 同步后变更为 1

Scenario: 平台自营销售 IoT 卡

  • WHEN 平台直接销售在库 IoT 卡给终端用户并激活
  • THEN 系统将 IoT 卡状态从 1(在库) 变更为 3(已激活),owner_type 保持 "platform",activated_at 记录激活时间

Scenario: 停用已激活 IoT 卡

  • WHEN 用户或平台停用已激活 IoT 卡
  • THEN 系统将 IoT 卡状态从 3(已激活) 变更为 4(已停用),通过 Gateway API 执行停机操作

Requirement: IoT 卡平台自营和代理分销

系统 SHALL 支持 IoT 卡的平台自营销售和代理分销两种模式,通过 owner_typeowner_id 区分所有者。

平台自营:

  • owner_type 为 "platform"
  • owner_id 为 0
  • 平台直接销售给终端用户
  • 销售价格由平台自主定价

代理分销:

  • owner_type 为 "agent"
  • owner_id 为代理用户 ID
  • 代理商可以销售给终端用户或下级代理
  • 分销价格由平台设置(distribute_price),代理商可在分销价基础上加价(但不能超过 2 倍)

Scenario: 查询平台自营 IoT 卡库存

  • WHEN 查询平台自营 IoT 卡库存
  • THEN 系统返回 owner_type 为 "platform" 且 status 为 1(在库) 的 IoT 卡列表

Scenario: 查询代理分销 IoT 卡库存

  • WHEN 代理商(用户 ID 为 123)查询自己的 IoT 卡库存
  • THEN 系统返回 owner_type 为 "agent" 且 owner_id 为 123 且 status 为 2(已分销) 的 IoT 卡列表

Scenario: 代理加价销售 IoT 卡套餐

  • WHEN 代理商为已分销 IoT 卡设置套餐售价
  • THEN 系统校验套餐售价不超过分销价的 2 倍,校验通过后允许销售

Requirement: IoT 卡批量导入

系统 SHALL 支持通过 CSV 文件批量导入 IoT 卡 ICCID,支持大批量数据(几万条),异步处理并跟踪导入进度。

导入方式:

  • 上传 CSV 文件,每行一个 ICCID
  • 在界面选择运营商、批次号等公共参数
  • 不支持一次导入多种运营商的卡

导入参数:

  • CSV 文件(必填): 仅包含 ICCID 列
  • 运营商 ID(必填): 在界面选择
  • 批次号(可选): 在界面填写

校验规则:

  • ICCID 格式校验: 字母数字混合,长度根据运营商(电信19位,其他20位)
  • ICCID 唯一性校验: 重复 ICCID 跳过,不中断导入

处理规则:

  • 异步处理: 创建导入任务后立即返回任务 ID
  • 分批处理: 每批 1000 条
  • 重复处理: 跳过已存在的 ICCID,记录跳过原因
  • 格式错误: 记录失败原因,继续处理其他行

导入结果:

  • 总数(total_count)
  • 成功数(success_count)
  • 跳过数(skip_count): 因重复等原因跳过
  • 失败数(fail_count): 因格式错误等原因失败
  • 跳过详情: 包含行号、ICCID、原因
  • 失败详情: 包含行号、ICCID、原因

Scenario: 发起 IoT 卡批量导入

  • WHEN 管理员上传包含 10000 个 ICCID 的 CSV 文件,选择运营商为电信,批次号为 "BATCH-2025-001"
  • THEN 系统创建导入任务,返回任务 ID,后台异步处理导入

Scenario: 导入时跳过重复 ICCID

  • WHEN CSV 文件中的 ICCID "8986001234567890123" 已存在于系统中
  • THEN 系统跳过该 ICCID,记录跳过原因为"ICCID 已存在",继续处理其他 ICCID

Scenario: 导入时记录格式错误

  • WHEN CSV 文件第 100 行的 ICCID "12345" 长度不符合电信卡要求(19位)
  • THEN 系统记录失败,原因为"电信 ICCID 必须为 19 位",行号为 100,继续处理其他 ICCID

Scenario: 查询导入任务进度

  • WHEN 管理员查询导入任务(ID 为 1)的进度
  • THEN 系统返回任务状态、总数、成功数、跳过数、失败数、开始时间、完成时间

Scenario: 查询导入任务失败详情

  • WHEN 管理员查询导入任务(ID 为 1)的失败详情
  • THEN 系统返回失败记录列表,每条包含行号、ICCID、失败原因

Requirement: IoT 卡查询和筛选

系统 SHALL 支持多维度查询和筛选 IoT 卡,包括状态、所有者、批次号、卡类型等。

查询条件:

  • ICCID(精确匹配或模糊匹配)
  • IoT 卡状态(单选或多选)
  • 所有者类型(platform | agent | user | device)
  • 所有者 ID(仅当所有者类型为 agent/user/device 时有效)
  • 批次号(精确匹配)
  • 卡类型(单选或多选)
  • 运营商 ID(单选或多选,从 carriers 表选择)
  • 激活状态(0-未激活 | 1-已激活)
  • 实名状态(0-未实名 | 1-已实名)
  • 网络状态(0-停机 | 1-开机)
  • 是否参与轮询(true | false)
  • 激活时间范围(开始时间 - 结束时间)
  • 创建时间范围(开始时间 - 结束时间)

分页:

  • 默认每页 20 条,最大每页 100 条
  • 返回总记录数和总页数

Scenario: 查询特定批次的在库 IoT 卡

  • WHEN 平台查询批次号为 "BATCH-2025-001" 且状态为 1(在库) 的 IoT 卡
  • THEN 系统返回符合条件的 IoT 卡列表,包含 ICCID、类型、运营商、成本价等信息

Scenario: 代理查询自己的已分销 IoT 卡

  • WHEN 代理商(用户 ID 为 123)查询自己的已分销 IoT 卡
  • THEN 系统返回 owner_type 为 "agent" 且 owner_id 为 123 且 status 为 2(已分销) 的 IoT 卡列表

Scenario: 分页查询 IoT 卡

  • WHEN 平台查询在库 IoT 卡,指定每页 50 条,查询第 2 页
  • THEN 系统返回第 51-100 条 IoT 卡记录,以及总记录数和总页数

Requirement: Gateway 集成

系统 SHALL 预留 IoT 卡状态相关字段,用于后续与 Gateway 项目集成。

集成字段:

  • activation_status: 激活状态(从 Gateway 同步)
  • real_name_status: 实名状态(从 Gateway 同步)
  • network_status: 网络状态(从 Gateway 同步)
  • data_usage_mb: 累计流量使用(从 Gateway 同步)
  • last_sync_time: 最后同步时间

集成说明:

  • 本阶段只设计数据模型字段,不实现 Gateway HTTP 客户端代码
  • 后续 Service 层将调用 Gateway API 获取 IoT 卡状态并更新这些字段
  • Gateway 使用 AES 加密 + MD5 签名的统一传输协议(参考 design.md)

Gateway API 功能:

  • 查询 IoT 卡状态(激活状态、实名状态、网络状态)
  • 查询流量详情(累计流量使用、剩余流量)
  • 停复机操作(停机、复机)
  • 实名认证操作

Scenario: 预留 Gateway 集成字段

  • WHEN 创建 IoT 卡记录
  • THEN 系统初始化 Gateway 相关字段为默认值:activation_status 为 0,real_name_status 为 0,network_status 为 0,data_usage_mb 为 0,last_sync_time 为空

Scenario: 从 Gateway 同步 IoT 卡状态

  • WHEN Service 层调用 Gateway API 查询 IoT 卡状态
  • THEN 系统更新 IoT 卡的 activation_statusreal_name_statusnetwork_statusdata_usage_mblast_sync_time 字段

Requirement: IoT 卡数据校验

系统 SHALL 对 IoT 卡数据进行校验,确保数据完整性和一致性。

校验规则:

  • ICCID(iccid):必填,长度 19-20 字符,唯一
  • 卡类型(card_type):必填,长度 1-50 字符
  • 卡业务类型(card_category):必填,枚举值 "normal"(普通卡) | "industry"(行业卡),默认 "normal"
  • 运营商 ID(carrier_id):必填,≥ 1,必须是有效的运营商 ID
  • 成本价(cost_price):必填,≥ 0,最多 2 位小数
  • 分销价(distribute_price):可选,≥ 0,最多 2 位小数,≥ 成本价
  • 所有者类型(owner_type):必填,枚举值 "platform" | "agent" | "user" | "device"
  • 所有者 ID(owner_id):必填,≥ 0,当 owner_type 为 "platform" 时必须为 0
  • 激活状态(activation_status):必填,枚举值 0(未激活) | 1(已激活)
  • 实名状态(real_name_status):必填,枚举值 0(未实名) | 1(已实名),当 card_category 为 "industry"(行业卡)时可以保持 0
  • 网络状态(network_status):必填,枚举值 0(停机) | 1(开机)
  • 轮询开关(enable_polling):必填,布尔值 true | false

Scenario: 创建 IoT 卡时 ICCID 格式错误

  • WHEN 平台创建 IoT 卡,ICCID 长度为 15(小于 19)
  • THEN 系统拒绝创建,返回错误信息"ICCID 长度必须为 19-20 字符"

Scenario: 创建 IoT 卡时 ICCID 重复

  • WHEN 平台创建 IoT 卡,ICCID 为已存在的 "89860123456789012345"
  • THEN 系统拒绝创建,返回错误信息"ICCID 已存在"

Scenario: 创建 IoT 卡时成本价为负数

  • WHEN 平台创建 IoT 卡,成本价为 -10.00
  • THEN 系统拒绝创建,返回错误信息"成本价必须 ≥ 0"

Scenario: 创建 IoT 卡时分销价低于成本价

  • WHEN 平台创建 IoT 卡,成本价为 50.00,分销价为 40.00
  • THEN 系统拒绝创建,返回错误信息"分销价不能低于成本价"

Requirement: 单卡列表查询

系统 SHALL 提供单卡列表查询功能,用于管理未绑定设备的 IoT 卡资产。

单卡定义: 单卡是指未绑定到任何设备的 IoT 卡,即在 device_sim_bindings 表中不存在 bind_status = 1(已绑定) 的记录。

查询条件:

  • 套餐 ID(package_id): 可选,筛选已购买指定套餐的卡
  • 是否分销(is_distributed): 可选,true-已分销 false-未分销
  • 卡号状态(status): 可选,1-在库 2-已分销 3-已激活 4-已停用
  • 运营商(carrier_id): 可选,运营商 ID
  • 分销商 ID(shop_id): 可选,店铺 ID
  • 网卡号段(iccid_range): 可选,格式 "起始ICCID-结束ICCID"
  • ICCID: 可选,模糊匹配
  • 卡接入号(msisdn): 可选,模糊匹配
  • 是否换卡(is_replaced): 可选,true-有换卡记录 false-无换卡记录

分页:

  • 默认每页 20 条,最大每页 100 条
  • 返回总记录数和总页数

数据权限:

  • 基于 shop_id 自动应用数据权限过滤
  • 代理只能看到自己店铺及下级店铺的卡

Scenario: 查询未绑定设备的单卡列表

  • WHEN 管理员查询单卡列表
  • THEN 系统返回所有未绑定设备的 IoT 卡(在 device_sim_bindings 中无 bind_status=1 记录的卡)

Scenario: 按运营商筛选单卡

  • WHEN 管理员查询运营商 ID 为 1(电信)的单卡
  • THEN 系统返回 carrier_id = 1 且未绑定设备的 IoT 卡列表

Scenario: 按网卡号段筛选单卡

  • WHEN 管理员查询 ICCID 号段为 "8986001000000000000-8986001999999999999" 的单卡
  • THEN 系统返回 ICCID 在该号段范围内且未绑定设备的 IoT 卡列表

Scenario: 按是否换卡筛选单卡

  • WHEN 管理员查询有换卡记录的单卡(is_replaced=true)
  • THEN 系统返回在 card_replacement_records 表中有记录的 IoT 卡列表

Requirement: 单卡分配功能

系统 SHALL 支持将单卡(未绑定设备的 IoT 卡)分配给直属下级店铺,实现资产所有权的层级流转。

分配规则:

  • 只能分配给直属下级店铺,不可跨级分配
  • 平台shop_id=NULL只能分配状态为在库(status=1)的卡
  • 代理店铺可以分配状态为已分销(status=2)的卡(继续往下分销)
  • 分配后状态变更:在库(1)→已分销(2),已分销(2)保持不变
  • 分配后 shop_id 变更为目标店铺 ID
  • 分配不涉及费用,纯资产所有权转移
  • 分配后上级仍能看到和管理(通过数据权限机制)

选卡方式(三选一):

  • ICCID 列表:指定具体的 ICCID 列表
  • 号段范围:指定起始 ICCID 和结束 ICCID
  • 筛选条件:按运营商、批次号、状态等条件批量选择

API 端点: POST /api/admin/iot-cards/standalone/allocate

请求参数:

  • to_shop_id(必填): 目标店铺 ID
  • selection_type(必填): 选卡方式,枚举值 "list" | "range" | "filter"
  • iccidsselection_type=list 时必填): ICCID 列表
  • iccid_startselection_type=range 时必填): 起始 ICCID
  • iccid_endselection_type=range 时必填): 结束 ICCID
  • carrier_idselection_type=filter 时可选): 运营商 ID
  • batch_noselection_type=filter 时可选): 批次号
  • statusselection_type=filter 时可选): 卡状态
  • remark(可选): 备注

响应:

  • total_count: 待分配总数
  • success_count: 成功数
  • fail_count: 失败数
  • failed_items: 失败项列表(包含 ICCID 和失败原因)

Scenario: 平台通过 ICCID 列表分配单卡给一级代理

  • WHEN 平台管理员选择 3 张在库单卡ICCID 列表分配给一级代理店铺ID=10
  • THEN 系统将这 3 张卡的 shop_id 更新为 10status 从 1 变为 2创建分配记录返回成功数 3

Scenario: 平台通过号段范围批量分配单卡

  • WHEN 平台管理员指定 ICCID 范围 "8986001000000000000" 至 "8986001000000000099"分配给一级代理店铺ID=10
  • THEN 系统查询该范围内的所有在库单卡,批量更新 shop_id 和 status创建分配记录

Scenario: 代理通过筛选条件分配单卡给下级

  • WHEN 一级代理(店铺 ID=10按条件筛选运营商=电信,批次号=BATCH-001自己的已分销卡分配给二级代理店铺ID=20
  • THEN 系统查询符合条件的卡,校验店铺 20 是店铺 10 的直属下级,批量更新 shop_id 为 20status 保持 2

Scenario: 拒绝跨级分配

  • WHEN 平台尝试将卡直接分配给二级代理店铺(非直属下级)
  • THEN 系统拒绝分配,返回错误"只能分配给直属下级店铺"

Scenario: 拒绝平台分配已分销的卡

  • WHEN 平台尝试分配状态为已分销(status=2)的卡
  • THEN 系统拒绝分配,返回错误"在库状态的卡才能分配,请先回收"

Scenario: 拒绝分配已绑定设备的卡

  • WHEN 用户尝试分配已绑定设备的卡(在 device_sim_bindings 中 bind_status=1
  • THEN 系统拒绝分配,返回错误"已绑定设备的卡不能单独分配"

Requirement: 单卡回收功能

系统 SHALL 支持上级回收已分配给直属下级的单卡,将卡的所有权收回。

回收规则:

  • 只有上级可以回收,代理不能主动退回给上级
  • 只能回收直属下级的卡,不可跨级回收
  • 平台回收shop_id 变为 NULLstatus 变为 1在库
  • 店铺回收shop_id 变为执行回收的店铺 IDstatus 保持 2已分销
  • 只能回收单卡(未绑定设备的卡)

选卡方式(与分配相同,三选一):

  • ICCID 列表
  • 号段范围
  • 筛选条件

API 端点: POST /api/admin/iot-cards/standalone/recall

请求参数:

  • from_shop_id(必填): 来源店铺 ID被回收方
  • selection_type(必填): 选卡方式,枚举值 "list" | "range" | "filter"
  • iccidsselection_type=list 时必填): ICCID 列表
  • iccid_startselection_type=range 时必填): 起始 ICCID
  • iccid_endselection_type=range 时必填): 结束 ICCID
  • carrier_idselection_type=filter 时可选): 运营商 ID
  • batch_noselection_type=filter 时可选): 批次号
  • remark(可选): 备注

响应:

  • total_count: 待回收总数
  • success_count: 成功数
  • fail_count: 失败数
  • failed_items: 失败项列表

Scenario: 平台回收一级代理的单卡

  • WHEN 平台管理员选择一级代理店铺ID=10的 5 张单卡进行回收
  • THEN 系统将这 5 张卡的 shop_id 更新为 NULLstatus 从 2 变为 1创建回收记录

Scenario: 一级代理回收二级代理的单卡

  • WHEN 一级代理(店铺 ID=10选择二级代理店铺ID=20的 3 张单卡进行回收
  • THEN 系统将这 3 张卡的 shop_id 更新为 10status 保持 2创建回收记录

Scenario: 拒绝回收非直属下级的卡

  • WHEN 一级代理(店铺 ID=10尝试回收非直属下级店铺ID=30归属于店铺 ID=20的卡
  • THEN 系统拒绝回收,返回错误"只能回收直属下级店铺的卡"

Scenario: 拒绝代理主动退回

  • WHEN 二级代理(店铺 ID=20尝试将卡退回给上级店铺ID=10
  • THEN 系统拒绝操作,返回错误"不能主动退回卡给上级,请联系上级进行回收"

Scenario: 拒绝回收已绑定设备的卡

  • WHEN 用户尝试回收已绑定设备的卡
  • THEN 系统拒绝回收,返回错误"已绑定设备的卡不能单独回收"

Requirement: 企业用户 IoT 卡查询权限控制

系统 SHALL 支持基于用户类型和授权关系的 IoT 卡查询权限控制。

查询权限规则

  • 超级管理员/平台用户:可以查询所有 IoT 卡
  • 代理用户:可以查询自己店铺和下级店铺的 IoT 卡
  • 企业用户
    • 可以查询分配给自己企业的卡owner_type="enterprise" 且 owner_id=自己的企业ID
    • 可以查询授权给自己企业的卡(通过 enterprise_card_authorization 表关联)
  • 个人客户:只能查询自己拥有的卡

数据过滤

  • 企业用户查询时自动过滤敏感商业信息cost_price、distribute_price、supplier
  • 其他用户类型可以看到完整信息

Scenario: 企业用户查询自己拥有的卡

  • WHEN 企业用户查询 IoT 卡列表,且存在 owner_type="enterprise" 且 owner_id=该企业ID 的卡
  • THEN 系统返回这些卡的信息,但隐藏 cost_price、distribute_price、supplier 字段

Scenario: 企业用户查询被授权的卡

  • WHEN 企业用户查询 IoT 卡列表,且存在通过 enterprise_card_authorization 授权给该企业的卡
  • THEN 系统返回这些授权卡的信息,但隐藏商业敏感字段,同时包含授权人和授权时间信息

Scenario: 企业用户无法查询未授权的卡

  • WHEN 企业用户尝试查询既不属于自己也未被授权的卡
  • THEN 系统在查询结果中不包含这些卡,如同它们不存在

Scenario: 代理用户正常查询

  • WHEN 代理用户查询 IoT 卡
  • THEN 系统返回该代理店铺及其下级店铺的所有卡,包含完整信息

Scenario: 授权被回收后企业无法查询

  • WHEN 卡的授权被回收后revoked_at 不为空),企业用户查询该卡
  • THEN 系统不返回该卡信息,企业无法再看到该卡