feat(iot-card-import): 为导入任务接口添加平台用户权限控制
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m10s

- 在 Import/List/GetByID 接口添加用户类型校验
- 仅超级管理员和平台用户可访问
- 同步更新 OpenAPI 路由描述
- 补充集成测试覆盖权限拒绝场景
This commit is contained in:
2026-02-02 10:25:03 +08:00
parent d81bd242a4
commit a30b3036bb
9 changed files with 318 additions and 17 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-02

View File

@@ -0,0 +1,54 @@
## Context
当前后台接口中,设备导入任务相关接口在 Handler 层做了“仅平台用户/超级管理员可访问”的用户类型校验(基于 `middleware.GetUserTypeFromContext` + `constants.UserTypeSuperAdmin/UserTypePlatform`)。
IoT 卡导入任务相关接口(提交导入、任务列表、任务详情)未做同等校验,导致权限边界与设备导入不一致。
本变更目标是将 IoT 卡导入任务相关接口的访问权限收敛为与设备导入一致:仅超级管理员与平台用户可访问。
## Goals / Non-Goals
**Goals:**
- 对以下 3 个接口增加用户类型校验:仅允许超级管理员与平台用户访问。
- 返回行为与现有模式一致:非允许用户类型直接返回 403`errors.CodeForbidden`),错误消息使用中文。
- OpenAPI 路由描述与实际权限一致。
- 补齐/新增集成测试,覆盖非平台用户访问应被拒绝。
**Non-Goals:**
- 不调整导入任务的数据模型与存储逻辑。
- 不引入新的 RBAC 权限点或权限表配置(保持“用户类型硬校验”的现有风格)。
- 不改动其他 IoT 卡管理接口的权限策略。
## Decisions
1) **在 Handler 层做用户类型校验(与设备导入对齐)**
- 方案:在 `internal/handler/admin/iot_card_import.go``Import` / `List` / `GetByID` 入口处增加与 `internal/handler/admin/device_import.go` 同风格的校验。
- 理由:
- 与现有“设备导入”实现一致,减少认知负担。
- 校验发生在参数解析与业务调用前,能最早拒绝无权限请求。
- 备选方案:
- 路由层中间件:更集中,但需要在路由注册处引入新中间件组合,且当前设备导入并未采用该方式。
- Service 层校验:更“业务化”,但会改变当前导入模块的职责边界(设备导入限制目前在 Handler 层)。
2) **错误码与错误消息遵循现有约定**
- 方案:使用 `errors.New(errors.CodeForbidden, "仅平台用户可...")` 风格,保持与设备导入一致。
- 理由:该模块已有同类错误消息,避免引入新的错误码或文案风格。
3) **同步更新路由描述OpenAPI**
- 方案:在 `internal/routes/iot_card.go` 对相关接口补充 `Description: "仅平台用户可操作。"`(与设备导入相同语义)。
- 理由:避免文档与实际行为不一致,减少前后端联调成本。
## Risks / Trade-offs
- **[风险] 规格与实现不一致** → **缓解**:在本变更的 specsdelta spec中明确将权限收敛为平台用户/超管,并在实现阶段对齐。
- **[风险] 测试缺口导致回归** → **缓解**:新增集成测试覆盖非平台用户访问 403并尽量复用现有测试基建集成测试 env
## Migration Plan
- 该变更为权限收敛,无数据迁移。
- 发布后影响:非平台用户/超管将无法调用 IoT 卡导入与导入任务查询接口。
- 回滚策略:如业务需要恢复原可见性,可回滚本变更提交(或通过后续变更重新放开)。
## Open Questions
- 是否需要将错误消息文案统一为同一句(如“仅平台用户可操作”),还是分别保留更具体的文案(导入/列表/详情)?(实现阶段可按现有 device_import 文案对齐)

View File

@@ -0,0 +1,37 @@
# 限制 IoT 卡导入任务接口仅平台用户可访问
Feature ID: feature-iot-card-import-task-platform-only
## Why
目前设备导入任务列表/详情已限制为仅平台用户/超级管理员可访问,但 IoT 卡导入任务列表/详情/提交导入未做同等限制,存在权限边界不一致与潜在越权风险。
需要将 IoT 卡导入任务相关接口的访问权限收敛为与设备导入一致,避免非平台账号通过后台接口获取或操作导入任务。
## What Changes
- **权限收敛**IoT 卡导入相关接口仅允许超级管理员与平台用户访问;其他用户类型访问返回 403。
- **接口范围**
- `POST /api/admin/iot-cards/import`
- `GET /api/admin/iot-cards/import-tasks`
- `GET /api/admin/iot-cards/import-tasks/:id`
- **文档一致性**:补充路由描述,确保 OpenAPI 文档与实际权限一致。
- **测试补齐**:新增/补充集成测试覆盖非平台用户访问上述接口应被拒绝。
## Capabilities
### New Capabilities
<!-- 无新增能力,本次为权限规则收敛 -->
### Modified Capabilities
- `iot-card-import-task`: 将“导入任务列表/详情/提交导入”的访问权限从“按 shop_id 数据权限过滤”调整为“仅平台用户/超级管理员可访问”。
## Impact
- 影响 API`/api/admin/iot-cards/import``/api/admin/iot-cards/import-tasks``/api/admin/iot-cards/import-tasks/:id`
- 预期涉及代码:
- Handler`internal/handler/admin/iot_card_import.go`
- Routes/OpenAPI`internal/routes/iot_card.go`
- 集成测试:`tests/integration/iot_card_test.go`(或新增对应测试文件)

View File

@@ -0,0 +1,76 @@
# iot-card-import-task Specification (Delta)
本变更用于收敛 IoT 卡导入任务相关接口的访问权限:仅超级管理员/平台用户可访问。
## ADDED Requirements
### Requirement: 导入任务创建权限控制
系统 SHALL 仅允许超级管理员与平台用户创建 IoT 卡导入任务。
#### Scenario: 平台用户创建导入任务
- **WHEN** 平台用户请求创建导入任务
- **THEN** 系统创建导入任务并返回任务信息
#### Scenario: 非平台用户创建导入任务被拒绝
- **WHEN** 非平台用户(代理账号/企业账号等)请求创建导入任务
- **THEN** 系统返回 403Forbidden并返回统一错误码 `CodeForbidden`
## MODIFIED Requirements
### 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`

View File

@@ -0,0 +1,18 @@
## 1. 权限校验收敛IoT 卡导入任务)
- [x] 1.1 在 `internal/handler/admin/iot_card_import.go``Import`/`List`/`GetByID` 增加用户类型校验:仅 `UserTypeSuperAdmin`/`UserTypePlatform` 允许访问,其余返回 `CodeForbidden`
- [x] 1.2 校验错误消息文案与设备导入保持同风格(中文、明确动作),并确认不会泄露底层错误细节
## 2. 路由描述与文档一致性
- [x] 2.1 在 `internal/routes/iot_card.go``POST /iot-cards/import``GET /iot-cards/import-tasks``GET /iot-cards/import-tasks/:id` 补充 `Description: "仅平台用户可操作。"`
## 3. 测试补齐
- [x] 3.1 在集成测试中新增用例:非平台用户(至少覆盖代理账号)访问上述 3 个接口应返回 403Forbidden
- [x] 3.2 运行并通过相关测试:`go test -v ./tests/integration/...`(如存在既有失败,需明确区分是否由本变更引入)
## 4. 本地验证
- [x] 4.1 对修改过的 Go 文件执行 `lsp_diagnostics` 确保无新增错误/告警
- [x] 4.2 运行 `go test ./...` 做一次全量回归验证(如耗时可接受)

View File

@@ -81,6 +81,20 @@ TBD - created by archiving change iot-card-standalone-management. Update Purpose
---
### Requirement: 导入任务创建权限控制
系统 SHALL 仅允许超级管理员与平台用户创建 IoT 卡导入任务。
#### Scenario: 平台用户创建导入任务
- **WHEN** 平台用户请求创建导入任务
- **THEN** 系统创建导入任务并返回任务信息
#### Scenario: 非平台用户创建导入任务被拒绝
- **WHEN** 非平台用户(代理账号/企业账号等)请求创建导入任务
- **THEN** 系统返回 403Forbidden并返回统一错误码 `CodeForbidden`
---
### Requirement: 导入任务列表查询
系统 SHALL 支持查询导入任务列表,用于管理和监控导入任务。
@@ -95,20 +109,24 @@ TBD - created by archiving change iot-card-standalone-management. Update Purpose
- 默认每页 20 条,最大每页 100 条
- 默认按创建时间倒序排列
**数据权限**:
- 基于 shop_id 自动应用数据权限过滤
- 代理只能看到自己店铺及下级店铺发起的导入任务
**权限**:
- 仅超级管理员/平台用户可查询导入任务列表
#### Scenario: 查询所有导入任务
- **WHEN** 管理员查询导入任务列表
- **WHEN** 平台管理员查询导入任务列表
- **THEN** 系统返回导入任务列表,包含任务编号、状态、运营商、总数、成功数、跳过数、失败数、创建时间
#### Scenario: 按状态筛选导入任务
- **WHEN** 管理员查询状态为 2(处理中) 的导入任务
- **WHEN** 平台管理员查询状态为 2(处理中) 的导入任务
- **THEN** 系统返回所有正在处理的导入任务列表
#### Scenario: 非平台用户查询导入任务列表被拒绝
- **WHEN** 非平台用户(代理账号/企业账号等)查询导入任务列表
- **THEN** 系统返回 403Forbidden并返回统一错误码 `CodeForbidden`
---
### Requirement: 导入任务详情查询
@@ -123,11 +141,19 @@ TBD - created by archiving change iot-card-standalone-management. Update Purpose
- 失败记录详情: 行号、ICCID、原因
- 错误信息: 任务级错误(如有)
**权限**:
- 仅超级管理员/平台用户可查询导入任务详情
#### Scenario: 查询导入任务详情
- **WHEN** 管理员查询导入任务(ID 为 1)的详情
- **WHEN** 平台管理员查询导入任务(ID 为 1)的详情
- **THEN** 系统返回任务完整信息,包括跳过和失败记录的详细列表
#### Scenario: 非平台用户查询导入任务详情被拒绝
- **WHEN** 非平台用户(代理账号/企业账号等)查询导入任务详情
- **THEN** 系统返回 403Forbidden并返回统一错误码 `CodeForbidden`
#### Scenario: 查询导入任务的跳过记录
- **WHEN** 管理员查询导入任务(ID 为 1)的跳过记录