一、概述
Task 模块是一个通用的异步任务执行框架,采用工厂模式 + 策略模式设计,支持任务的异步执行、状态管理、结果持久化和分布式锁保护。核心能力包括:导入/导出任务处理、任务状态跟踪、失败重试、结果文件上传等。
二、核心架构
2.1 架构分层
┌─────────────────────────────────────────────────────────────────────┐
│ Controller 层 │
│ TaskController TaskImportController TaskExportController │
└───────────────────────────┬─────────────────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌────────────────┐ ┌──────────────┐ ┌───────────────────┐
│ TaskService │ │TaskInvokeService│ │ TaskAsyncService │
│ (CRUD + 查询) │ │ (任务编排) │ │ (异步执行引擎) │
└────────┬───────┘ └──────┬────────┘ └─────────┬─────────┘
│ │ │
▼ │ │
┌────────────────┐ │ │
│ TaskMapper │ │ │
│ (数据库访问) │ │ │
└────────┬───────┘ │ │
│ │ │
▼ │ │
┌────────────────┐ │ │
│ sys_task │ │ │
│ (任务表) │ │ │
└────────────────┘ │ │
▼ │
┌──────────────────┐ │
│ TaskHandlerFactory│◄─────────┘
│ (处理器工厂) │
└──────────┬─────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ImportHandler │ │ExportHandler │ │CustomHandler │
│ (导入处理器) │ │ (导出处理器) │ │ (自定义处理器) │
└───────────────┘ └───────────────┘ └───────────────┘
2.2 核心组件职责
| 组件 | 职责 | 说明 |
|---|---|---|
| TaskController | REST API 入口 | 任务创建、查询、状态查询、重试 |
| TaskImportController | 导入任务专用入口 | 文件上传 + 导入任务创建 |
| TaskExportController | 导出任务专用入口 | 导出结果下载 |
| TaskService | 任务数据管理 | CRUD、分页查询、状态更新、Excel导出 |
| TaskInvokeService | 任务编排调度 | 创建任务记录、获取处理器、触发异步执行 |
| TaskAsyncService | 异步执行引擎 | 分布式锁、任务执行、结果处理、OSS上传 |
| TaskHandlerFactory | 处理器工厂 | Spring启动时自动注册所有 ITaskHandler |
| ITaskHandler | 任务处理器接口 | 定义任务类型和执行方法 |
三、任务实体设计
3.1 Task 实体结构
| 字段 | 类型 | 说明 |
|---|---|---|
id |
Long | 任务主键 |
name |
String | 任务名称 |
type |
String | 任务类型(唯一标识,对应 handler) |
tag |
String | 任务标签(IMPORT/EXPORT/CUSTOM) |
status |
String | 任务状态(PENDING/RUNNING/SUCCESS/FAILED) |
className |
String | 数据类全限定名(导入入参/导出出参) |
fileId |
String | 关联文件ID(导入上传/导出结果) |
param |
String | 任务参数(JSON格式) |
result |
String | 执行结果(JSON格式或成功/失败计数) |
errorMessage |
String | 错误信息 |
errorStack |
String | 异常堆栈 |
startTime |
Instant | 开始时间 |
endTime |
Instant | 结束时间 |
duration |
Long | 执行时长(毫秒) |
3.2 任务状态流转
PENDING (待执行)
│
└─→ RUNNING (执行中)
│
┌─────┴─────┐
▼ ▼
SUCCESS FAILED
(成功) (失败)
四、任务处理器设计
4.1 处理器接口
public interface ITaskHandler {
// 返回任务定义(类型、名称、类名、标签)
TaskHandleDTO getTaskHandleDTO();
// 执行任务
Object execute(TaskExecuteDTO executeDTO);
}
4.2 抽象基类
框架提供两个抽象基类,封装通用逻辑:
| 基类 | 用途 | 自动设置 |
|---|---|---|
AbstractImportHandler |
导入任务 | taskTag = "IMPORT" |
AbstractExportHandler |
导出任务 | taskTag = "EXPORT" |
设计意图:通过模板方法模式,子类只需实现具体的 doImport() / doExport() 方法,无需关心任务注册、标签设置等通用逻辑。
4.3 处理器注册机制
// TaskHandlerFactory 构造函数自动扫描所有 ITaskHandler 实现
@Autowired
public TaskHandlerFactory(List<ITaskHandler> taskHandlers) {
// 检测重复任务类型
// 按 taskType 映射到 Map(大小写不敏感)
}
// 使用时通过类型获取处理器
ITaskHandler handler = taskHandlerFactory.getTaskHandler("dictImport");
五、异步执行引擎
5.1 执行流程
TaskInvokeService.createAndExecute()
│
▼
┌─────────────────────────────┐
│ 1. 创建 Task 记录 │
│ status = PENDING │
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ 2. 获取处理器 │
│ ITaskHandler handler │
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ 3. 触发异步执行 │
│ executeTask() @Async │
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ 4. 抢占分布式锁 │
│ Redis setnx │
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ 5. 更新状态为 RUNNING │
│ 启动锁续期线程 │
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ 6. 执行 handler.execute() │
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ 7. 处理结果(OSS上传) │
│ 更新状态为 SUCCESS/FAILED│
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ 8. 清理资源(关闭线程池、 │
│ 删除锁) │
└─────────────────────────────┘
5.2 分布式锁机制
| 机制 | 实现 | 说明 |
|---|---|---|
| 锁获取 | CacheHelper.setnx(key, value, expire) |
原子操作,保证只有一个节点获得锁 |
| 锁续期 | ScheduledExecutorService.scheduleAtFixedRate() |
每30秒续期一次,防止任务超时释放锁 |
| 锁过期 | 默认300秒 | 宕机场景下自动释放锁 |
| 宕机恢复 | 启动时检查 RUNNING 状态任务的锁是否存在 | 不存在则标记为 FAILED |
六、导入/导出任务特殊处理
6.1 导入任务
// AbstractImportHandler.execute() 模板方法
public Object execute(TaskExecuteDTO executeDTO) {
return doImport(executeDTO); // 子类实现具体导入逻辑
}
导入流程:
- 前端上传 Excel 文件 → 保存到 OSS → 返回 fileId
- 创建导入任务,记录 fileId 和 className
- 异步执行时从 OSS 下载文件 → 解析 Excel → 调用业务导入逻辑
- 导入结果(含校验状态)生成 Excel → 上传 OSS → 更新 task.fileId
6.2 导出任务
// AbstractExportHandler.execute() 模板方法
public Object execute(TaskExecuteDTO executeDTO) {
return doExport(executeDTO.getTaskParam()); // 子类实现具体导出逻辑
}
导出流程:
- 创建导出任务,记录查询参数
- 异步执行时根据参数查询数据 → 生成 Excel → 上传 OSS
- 前端通过任务 ID 下载结果文件
七、关键设计决策
- 工厂模式解耦 — 任务类型与处理器解耦,新增任务类型只需实现接口并注册为 Spring Bean
- 分布式锁保护 — 防止多节点重复执行同一任务,支持水平扩展
- 锁续期机制 — 长任务不会因锁过期被其他节点抢占
- 宕机恢复 — 启动时自动检测并修复宕机导致的异常状态任务
- 模板方法模式 — 导入/导出抽象基类封装通用逻辑,子类只需关注业务实现
- 内存流处理 — Excel 读写使用 ByteArrayOutputStream,不落地磁盘,提升性能