[062][调度模块]任务全生命周期管理 —— 从启动到追踪的完整闭环
本文章代码: gitee , gitcode , github
1. 任务启动与取消的核心协调器:ScheduleService
ScheduleService 作为应用层门面,协调 TaskRepository(持久化)和 ScheduleTaskManager(调度引擎)。
@Service
@RequiredArgsConstructor
public class ScheduleService {
private final ScheduleTaskManager scheduleTaskManager;
private final TaskRepository<?> taskRepository;
public TaskExecutionDetails startTask(String taskCode) {
Optional<? extends Task> taskOpt = taskRepository.findByTaskCode(taskCode);
if (taskOpt.isPresent()) {
Task task = taskOpt.get();
scheduleTaskManager.createTask(task); // 向调度器注册任务
return buildDetails(task); // 返回聚合详情
}
return null;
}
public TaskExecutionDetails cancelTask(String taskCode) {
Optional<? extends Task> taskOpt = taskRepository.findByTaskCode(taskCode);
if (taskOpt.isPresent()) {
Task task = taskOpt.get();
scheduleTaskManager.cancelTask(task.getTaskCode()); // 取消调度
return buildDetails(task);
}
return null;
}
}
关键设计:
- 返回最新详情:启动/取消后立即返回
TaskExecutionDetails,方便 UI 即时反馈。 - 容错处理:若
taskCode不存在,返回null(建议优化为抛出TaskNotFoundException或返回Optional)。
2. 运行时详情聚合:buildDetails 的巧妙实现
private TaskExecutionDetails buildDetails(Task task) {
// 1. 初始化 Builder,填充静态配置
TaskExecutionDetails.TaskExecutionDetailsBuilder builder =
TaskExecutionDetails.builder()
.taskCode(task.getTaskCode())
.classSimpleName(task.getClassSimpleName())
.cron(task.getCron())
.enabled(task.isEnabled())
.description(task.getDescription())
.metadata(MapUtils.unmodifiableMap(task.getMetadata()))
.initialDelay(task.getInitialDelay())
.maxExecutionCount(task.getMaxExecutionCount())
.maxFailureCount(task.getMaxFailureCount())
.dueDate(task.getDueDate());
// 2. 覆盖内存中运行任务的实时配置(若任务正在运行)
Task runningTask = scheduleTaskManager.getTask(task.getTaskCode());
if (runningTask != null) {
// ✅ 修正:必须复用 builder 实例,而非重新调用 builder()
builder.taskCode(runningTask.getTaskCode())
.classSimpleName(runningTask.getClassSimpleName())
.cron(runningTask.getCron())
.enabled(runningTask.isEnabled())
.description(runningTask.getDescription())
.metadata(MapUtils.unmodifiableMap(runningTask.getMetadata()))
.initialDelay(runningTask.getInitialDelay())
.maxExecutionCount(runningTask.getMaxExecutionCount())
.maxFailureCount(runningTask.getMaxFailureCount())
.dueDate(runningTask.getDueDate());
}
// 3. 填充最近一次执行状态(如运行中、成功、失败)
TaskRuntimeData lastRuntimeData =
scheduleTaskManager.getLastTaskRuntimeData(task.getTaskCode());
if (lastRuntimeData != null) {
builder.taskStatus(lastRuntimeData.taskStatus())
.lotNo(lastRuntimeData.lotNo())
.totalCount(lastRuntimeData.totalCount())
.totalFailureCount(lastRuntimeData.totalFailureCount())
.startTime(lastRuntimeData.startTime())
.endTime(lastRuntimeData.endTime())
.message(lastRuntimeData.message())
.throwable(lastRuntimeData.throwable());
}
return builder.build();
}
技术亮点:
- 三级数据聚合:① 数据库持久化配置(基础)→ ② 内存运行实例(动态覆盖)→ ③ 最近执行状态(运行快照)。
- 链式调用复用:通过复用
builder变量,避免生成多个临时对象。
3. 运行态保护:修改/删除前的安全校验
在 JobEndpoint 中,更新或删除任务前必须检查是否正在运行:
@PutMapping("/{id}")
public ResponseEntity<JobVO> updateJob(@PathVariable Long id, @RequestBody JobUpdateDTO dto) {
JobEntity job = jobService.findById(id);
checkJobIsRunning(job.getTaskCode()); // 抛出异常若运行中
BeanUtils.copyProperties(dto, job);
return ResponseEntity.ok(JobVO.of(jobService.save(job)));
}
private void checkJobIsRunning(String taskCode) {
if (scheduleTaskManager.isTaskRunning(taskCode)) {
throw new ScheduleException("任务运行中,不能操作;请先取消任务");
}
}
并发考量:此处存在 TOCTOU(Time-of-Check-Time-of-Use)问题,建议在 ScheduleTaskManager 层使用 ConcurrentHashMap 的原子操作或分布式锁(如 Redisson)。
4. 分页查询任务:规格模式的应用
@Data
public class JobQuery {
private String taskCode;
private String classSimpleName;
private String description;
public Specification<JobEntity> buildSpecification() {
List<Specification<JobEntity>> specList = new ArrayList<>();
specList.add(JobSpecification.taskCodeEqual(taskCode));
specList.add(JobSpecification.classSimpleNameEqual(classSimpleName));
specList.add(JobSpecification.descriptionLike(description));
return specList.stream()
.filter(Objects::nonNull)
.reduce(Specification::and)
.orElse((root, query, cb) -> cb.conjunction()); // 无条件时返回恒真条件
}
}
技术亮点:
- 流式组合:通过
reduce(Specification::and)链式拼接所有非空条件。 - 默认全查询:若查询条件为空,返回
cb.conjunction()(即WHERE 1=1),避免null异常。