修正数据
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m55s

This commit is contained in:
2026-04-27 18:10:08 +08:00
parent bb33232b1b
commit fe4c545308
57 changed files with 4477 additions and 226 deletions

View File

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

View File

@@ -0,0 +1,155 @@
## Context
当前项目已具备成熟的异步导入任务能力(设备导入、单卡导入)与对象存储能力,但导出能力尚未形成统一任务系统。面对未来超大数据量,直接同步导出会引发请求超时、内存峰值高、用户等待时间不可控等问题。
本次变更要先建立“通用导出任务骨架”,并以 `device``iot_card` 作为首批场景验证架构可扩展性;导出字段与具体查询细节后续按场景迭代补充。
约束条件:
- 必须遵循 `Handler -> Service -> Store -> Model` 分层。
- 必须使用 Asynq 异步任务机制。
- 禁止外键约束,任务主从关系通过 ID 字段维护。
- 常量、状态枚举、Redis Key 必须定义在 `pkg/constants/`,禁止硬编码。
## Goals / Non-Goals
**Goals:**
- 提供全局导出任务中心,统一任务创建、列表、详情、取消。
- 支持 `xlsx/csv` 两种格式选择。
- 支持异步分片执行,满足大数据量导出性能需求。
- 导出产物上传 OSS并在任务详情直接返回可下载 URL默认 24h
- 设计可扩展场景机制,首批支持 `device``iot_card`
**Non-Goals:**
- 本阶段不定义最终导出字段清单与前端展示细节。
- 本阶段不实现跨场景聚合导出(仅单场景任务)。
- 本阶段不实现复杂运营能力(如任务重跑、任务克隆、批量取消)。
## Decisions
### 决策1统一导出引擎 + 场景策略注册
**选择**:采用“通用导出任务引擎 + 场景策略Strategy+ 执行模板Template Method”模式。
**理由**
- 避免设备导出、单卡导出重复实现任务编排、分片、上传、状态流转。
- 后续新增导出场景仅需注册策略(查询器、表头、行映射),无需改核心引擎。
**备选方案**:每个场景单独实现导出任务链路。
**放弃原因**:重复代码多,后续维护成本高,难以统一性能与取消语义。
---
### 决策2三段式异步任务流水线
**选择**Asynq 任务拆分为 `dispatch -> shard -> finalize` 三段。
**执行语义**
- `dispatch`:按查询规模创建分片子任务并入队。
- `shard`:每个分片独立查询与写文件,上传 OSS回写分片状态。
- `finalize`:聚合分片结果,生成主任务最终结果(`file_key/download_url`)。
**理由**
- 分片并行可线性提升吞吐。
- 分片失败可独立重试,不影响已完成分片。
- finalize 统一收口,便于生成一致的下载结果。
---
### 决策3任务数据模型拆分主任务与分片任务
**选择**:新增两张表(不建外键):
- `tb_export_task`(主任务)
- `tb_export_shard_task`(分片任务)
**关键字段**
- 主任务:`task_no``scene``format``status``progress``cancel_requested``file_key``error_message``started_at``completed_at`
- 分片任务:`task_id``shard_no``status``cursor_start/cursor_end``row_count``file_key``error_message`
**事务设计**
- `dispatch` 中“创建分片记录 + 分片任务入队标记”在事务内完成,避免分片记录与队列状态不一致。
- `shard``finalize` 使用状态条件更新保证幂等(如 `WHERE status = expected`)。
---
### 决策4下载信息仅在详情接口返回且直出可下载 URL
**选择**
- 任务列表仅返回任务元信息,不返回下载 URL。
- 任务详情在 `status=已完成` 时返回可直接下载的 `download_url`,有效期默认 24 小时。
**理由**
- 减少列表接口的预签名生成开销。
- 与用户已确认的交互模式一致。
---
### 决策5取消机制采用“控制面标记 + Worker 协作停止”
**选择**
- 取消接口将主任务标记为“取消请求”(`cancel_requested=true`)。
- `dispatch/shard/finalize` 在关键节点检查取消标记,命中即停止并回写取消状态。
**理由**
- 兼容已运行任务,避免强杀导致的中间态脏数据。
- 实现简单且可控,符合当前 Asynq 使用方式。
**权衡**
- 取消不是“瞬时终止”,而是“尽快停止”;单个分片运行中的小批次会执行到检查点再退出。
---
### 决策6性能基线采用 Keyset 分页 + 流式写出
**选择**
- 分片数据读取使用 Keyset 分页(`id > last_id`),避免深分页性能衰减。
- 文件写出采用流式写出策略,避免全量数据载入内存。
- 进度按批回写(例如每 N 行)降低数据库写放大。
**理由**
- 满足未来超大数据量导出场景。
- 控制数据库与 Worker 资源占用,减少峰值风险。
---
### 决策7依赖注入与模块边界
**选择**
- Handler 仅做参数解析、权限前置与统一响应。
- Service 注入 `ExportTaskStore``ExportShardTaskStore``queue.Client``storage.Service``SceneRegistry`
- Worker 通过 `pkg/queue/handler.go` 统一注册导出相关处理器。
**理由**
- 与现有导入任务模式保持一致,降低接入复杂度。
- 强化模块边界,便于后续场景扩展。
## Risks / Trade-offs
- **[风险] 分片大小配置不合理导致队列拥塞或分片过小**
**缓解**:分片大小与并发度配置化;增加任务监控指标与告警阈值。
- **[风险] xlsx 在极大数据量场景下生成耗时高**
**缓解**:允许用户选择 `csv`;必要时按分片生成多文件并由 finalize 打包归档。
- **[风险] 取消与 finalize 并发竞争造成状态抖动**
**缓解**统一使用状态条件更新finalize 前再次检查 `cancel_requested`
- **[权衡] 详情接口实时生成预签名 URL 会增加一次存储调用**
**接受**:仅详情触发,调用频率低,收益大于成本。
## Migration Plan
1. 新增导出任务常量、状态枚举、任务类型、Redis Key 生成函数。
2. 新增导出主任务与分片任务模型、Store、数据库迁移。
3. 新增全局导出任务 Handler/Service/Route创建、列表、详情、取消
4. 新增导出 Worker 处理器并注册 Asynq 任务类型dispatch/shard/finalize
5. 接入首批场景策略:`device``iot_card`(先完成骨架与最小可运行实现)。
6. 更新接口文档生成器与业务文档,补充手工验证步骤。
回滚策略:
- 若上线后发现异常,可先关闭导出任务入口(路由开关/权限收敛),保留已生成文件不影响现网核心交易链路。
- 数据库迁移按标准 down 脚本回滚新增表与字段。
## Open Questions
- 是否需要在任务列表返回“最近一次下载 URL 过期时间”用于前端提示(当前方案不返回,仅详情返回)?
- 分片默认大小与队列权重的初始值是否按环境差异化配置(开发/生产)?

View File

@@ -0,0 +1,40 @@
## Why
当前系统已有设备与单卡导入任务,但缺少统一的异步导出任务能力,随着数据规模持续增长,直接同步导出会带来超时、内存占用和用户体验问题。现在需要先落地可复用的“导出任务骨架”,为后续多个业务场景复用,避免重复建设。
## What Changes
- `feature-001-unified-export-task-center`:新增全局导出任务中心,统一创建、查询、详情、取消导出任务。
- `feature-002-export-format-selectable`:创建导出任务时支持用户选择 `xlsx/csv` 格式。
- `feature-003-scene-plugin-support`:首批支持 `device``iot_card` 两个导出场景,并设计为可扩展插件式接入。
- `feature-004-async-shard-export`:导出采用 Asynq 异步执行,支持分片处理与进度回写,面向大数据量场景。
- `feature-005-export-oss-delivery`:导出产物上传 OSS任务详情直接返回可下载 URL默认有效期 24 小时)。
- `feature-006-export-task-cancel`:支持取消待处理/执行中的导出任务,并在 Worker 侧协作停止。
- 架构遵循 `Handler -> Service -> Store -> Model` 分层,导出能力以通用引擎 + 场景策略方式实现。
## Capabilities
### New Capabilities
- `unified-export-task-system`统一导出任务系统能力覆盖全局任务入口、异步分片导出、OSS 交付、详情直出下载 URL 与任务取消。
### Modified Capabilities
-
## Impact
- 影响模块:
- `internal/routes`:新增导出任务全局路由。
- `internal/handler`:新增导出任务 Handler。
- `internal/service`:新增导出任务编排服务与场景策略注册。
- `internal/store`:新增导出任务与分片任务存储。
- `internal/task``pkg/queue`:新增导出 dispatch/shard/finalize/cancel 相关任务处理。
- `pkg/constants`新增导出任务类型、状态、Redis Key 常量。
- 影响 API
- 新增全局导出任务创建/列表/详情/取消接口(按 `scene` 过滤展示)。
- 影响数据与基础设施:
- 新增导出主任务与分片任务表。
- 复用现有 OSS 能力与 Asynq 队列能力。
- 性能考虑:
- 使用分片、流式写出、批量进度更新、Keyset 分页策略,避免大数据量导出时超时与内存峰值。
- 验证计划:
- 按项目约束采用 PostgreSQL MCP + Postman/curl 的手工验证,覆盖创建任务、分片执行、下载 URL 获取、取消任务等关键流程。

View File

@@ -0,0 +1,129 @@
## ADDED Requirements
### Requirement: 全局导出任务中心
系统 SHALL 提供统一的全局导出任务中心,覆盖任务创建、任务列表、任务详情、任务取消四类接口,并使用统一响应格式 `{code, msg, data, timestamp}`
#### Scenario: 创建导出任务成功
- **WHEN** 已认证用户调用创建导出任务接口并提交合法参数
- **THEN** 系统返回 `task_id``task_no`、初始任务状态和创建成功提示
#### Scenario: 查询全局任务列表并按场景过滤
- **WHEN** 用户调用任务列表接口并传入 `scene=device`
- **THEN** 系统仅返回 `device` 场景任务,且结果按创建时间倒序分页返回
#### Scenario: 查询任务详情
- **WHEN** 用户调用任务详情接口并传入存在的任务 ID
- **THEN** 系统返回任务状态、进度、格式、场景、错误信息、产物信息等完整详情
---
### Requirement: 导出任务创建参数与格式选择
导出任务创建接口 SHALL 支持用户选择导出格式,且 `format` MUST 仅允许 `xlsx``csv``scene` MUST 支持首批 `device``iot_card`
#### Scenario: 选择 xlsx 格式创建任务
- **WHEN** 用户创建任务时传入 `format=xlsx`
- **THEN** 系统创建任务成功并记录任务格式为 `xlsx`
#### Scenario: 选择 csv 格式创建任务
- **WHEN** 用户创建任务时传入 `format=csv`
- **THEN** 系统创建任务成功并记录任务格式为 `csv`
#### Scenario: 非法格式被拒绝
- **WHEN** 用户创建任务时传入 `format=pdf`
- **THEN** 系统返回参数错误,不创建任务
#### Scenario: 非法场景被拒绝
- **WHEN** 用户创建任务时传入未注册的 `scene`
- **THEN** 系统返回参数错误,不创建任务
---
### Requirement: 导出任务异步分片执行
系统 SHALL 使用 Asynq 异步任务执行导出流程,并采用 `dispatch -> shard -> finalize` 三段式处理;大数据量导出 MUST 支持分片并行执行。
#### Scenario: 创建任务后异步执行
- **WHEN** 导出任务创建成功
- **THEN** 系统将主任务入队,任务状态从“待处理”推进到“处理中”
#### Scenario: 分片任务并行处理
- **WHEN** dispatch 阶段根据查询规模生成多个分片
- **THEN** 系统并行执行多个 shard 任务并按分片回写进度
#### Scenario: 分片失败触发重试
- **WHEN** 某个 shard 任务因临时错误失败
- **THEN** 系统按 Asynq 重试策略重试该分片,且不影响其他分片状态
#### Scenario: finalize 收敛任务结果
- **WHEN** 所有分片任务完成(成功或失败)
- **THEN** 系统执行 finalize 汇总并更新主任务最终状态
---
### Requirement: 导出结果 OSS 交付与详情下载链接
系统 SHALL 将导出产物上传至 OSS并在任务详情接口返回可直接下载的 `download_url`;该下载链接默认有效期 MUST 为 24 小时。
#### Scenario: 已完成任务详情返回下载链接
- **WHEN** 任务状态为“已完成”且产物上传成功
- **THEN** 任务详情接口返回 `file_key` 与可直接下载的 `download_url`
#### Scenario: 未完成任务不返回下载链接
- **WHEN** 任务状态为“待处理”或“处理中”
- **THEN** 任务详情接口不返回可用的 `download_url`
#### Scenario: 下载链接过期后可重新获取
- **WHEN** 用户在 URL 过期后再次调用任务详情接口
- **THEN** 系统重新生成新的 24 小时有效下载链接
---
### Requirement: 导出任务取消能力
系统 SHALL 支持取消任务,取消操作 MUST 支持待处理与处理中任务;已完成/已失败/已取消任务 MUST 不可重复取消。
#### Scenario: 取消待处理任务
- **WHEN** 用户取消处于待处理状态的任务
- **THEN** 系统将任务状态更新为“已取消”,且不再调度执行
#### Scenario: 取消处理中任务
- **WHEN** 用户取消处于处理中状态的任务
- **THEN** 系统记录取消请求并在 Worker 检查点协作停止,最终状态为“已取消”
#### Scenario: 取消已完成任务被拒绝
- **WHEN** 用户取消处于已完成状态的任务
- **THEN** 系统返回业务错误,提示该状态不可取消
---
### Requirement: 导出任务数据权限与可见性
系统 SHALL 对导出任务应用数据权限控制:用户仅可查看有权限的任务与导出结果;跨角色/跨租户越权访问 MUST 被拒绝。
#### Scenario: 用户查看自己创建的任务
- **WHEN** 用户查询其本人创建的任务
- **THEN** 系统返回任务数据
#### Scenario: 用户查看无权限任务
- **WHEN** 用户查询不属于其数据权限范围的任务
- **THEN** 系统返回无权限错误,不泄露任务存在性细节
#### Scenario: 场景导出遵循原业务数据权限
- **WHEN** 用户创建 `scene=iot_card``scene=device` 导出任务
- **THEN** 系统按对应场景既有数据权限规则导出可见数据,不导出越权数据
---
### Requirement: 导出任务性能基线
系统 MUST 面向大数据量导出实现稳定性能,查询与写出流程 SHALL 采用可扩展方案(如 Keyset 分页、流式写出、批量进度回写)。
#### Scenario: 超大数据量导出不发生单次全量加载
- **WHEN** 导出数据规模超过单批次内存可承受范围
- **THEN** 系统按分批/分片方式处理,不进行单次全量加载
#### Scenario: 进度可观测
- **WHEN** 任务正在执行
- **THEN** 列表与详情可查看当前进度与处理状态,便于用户感知导出进展

View File

@@ -0,0 +1,54 @@
## 0. 验证准备(手工验证基线)
- [x] 0.1 梳理导出任务手工验证清单(创建/列表/详情下载/取消/分片执行),明确使用 PostgreSQL MCP + Postman/curl 验证(不新增自动化测试)
- [x] 0.2 准备验证数据样本:`device``iot_card` 两个场景各至少一组小数据与一组大数据
- [x] 0.3 定义验收记录模板请求参数、响应、数据库快照、OSS 文件 Key、日志关键字段
## 1. 导出任务数据模型与迁移
- [x] 1.1 新增导出任务相关常量任务类型、状态枚举、格式枚举、场景枚举、Redis Key 生成函数(全部放 `pkg/constants/`
- [x] 1.2 设计并实现 `tb_export_task``tb_export_shard_task` 的 Model/DTO无外键仅 ID 关联)
- [x] 1.3 编写数据库迁移文件up/down创建导出主任务与分片任务表及必要索引
- [x] 1.4 实现导出任务 Store创建、分页查询、详情查询、状态流转、进度更新、取消标记、条件更新幂等
- [x] 1.5 使用 PostgreSQL MCP 验证迁移结果与索引生效,记录表结构与关键字段
## 2. 全局导出任务管理接口
- [x] 2.1 新增全局导出任务路由与 Handler创建、列表、详情、取消统一响应格式 `{code,msg,data,timestamp}`
- [x] 2.2 新增导出任务 Service 编排:参数校验、场景校验、格式校验、任务号生成、任务入队
- [x] 2.3 实现列表按 `scene/status/time` 过滤和分页(默认 20最大 100
- [x] 2.4 实现权限与可见性控制:仅返回当前用户有权限的任务,越权访问统一拒绝
- [x] 2.5 更新文档生成器注册(`cmd/api/docs.go``cmd/gendocs/main.go`)并更新路由文档描述
- [ ] 2.6 用 Postman/curl 手工验证接口契约(正常与异常参数、越权场景、分页过滤)
## 3. 异步导出执行引擎dispatch/shard/finalize
- [x] 3.1 新增 Asynq 任务载荷结构与任务类型注册:`export:dispatch``export:shard``export:finalize`
- [x] 3.2 实现 dispatch 处理器:读取任务配置、生成分片记录、分片任务入队、状态推进
- [x] 3.3 实现 shard 处理器:按分片查询数据、流式写出文件、上传 OSS、回写分片结果
- [x] 3.4 实现 finalize 处理器:汇总分片结果、更新主任务最终状态、写入产物信息
- [x] 3.5 增加幂等保护与重试安全:状态条件更新、重复消费防重、失败重试不重复产出
- [ ] 3.6 手工验证异步链路:创建任务后观察状态从待处理到完成/失败的完整流转
## 4. 场景策略注册与首批场景接入
- [x] 4.1 实现场景策略注册中心Scene Registry约定统一接口查询器、表头定义、行映射器
- [x] 4.2 接入 `device` 导出策略骨架(先实现最小可运行数据输出)
- [x] 4.3 接入 `iot_card` 导出策略骨架(先实现最小可运行数据输出)
- [x] 4.4 实现场景级数据权限收敛:导出结果必须遵循对应场景既有权限规则
- [ ] 4.5 手工验证两个场景导出任务均可独立创建、执行并产出文件
## 5. 详情下载链接与任务取消能力
- [x] 5.1 在任务详情接口中接入 `download_url` 生成逻辑(仅 `status=已完成` 返回)
- [x] 5.2 下载链接有效期固定为 24 小时,过期后重新访问详情可刷新新链接
- [x] 5.3 实现取消接口:支持待处理/处理中任务,已完成/已失败/已取消任务拒绝取消
- [x] 5.4 在 dispatch/shard/finalize 增加取消检查点,命中后协作停止并更新为已取消
- [ ] 5.5 手工验证取消场景:待处理取消、处理中取消、重复取消、取消后不再继续执行
## 6. 文档与收尾验收
- [x] 6.1 补充导出任务功能文档(`docs/{feature-id}/`),说明接口、状态机、分片策略、取消语义、下载规则
- [x] 6.2 更新 README 与 API 使用示例(全局任务入口 + `scene` 过滤展示约定)
- [ ] 6.3 进行全链路手工验收:`device``iot_card` 各完成一次 `xlsx``csv` 导出
- [ ] 6.4 输出验收记录接口响应样例、数据库状态快照、OSS 文件 key、下载 URL 有效期验证