[063][调度模块]事件驱动的日志记录与高效查询体系

简介: 本文介绍事件驱动的日志记录与高效查询体系:通过异步+独立事务消费状态变更事件,确保日志不丢失、不影响调度性能;采用JPA Specification实现类型安全、可组合的多条件动态查询;VO层冗余关键关联字段,减少前端请求;集成Spring Data分页,简洁高效。代码开源。

[063][调度模块]事件驱动的日志记录与高效查询体系

本文章代码: gitee , gitcode , github

1. 事件驱动架构:解耦调度与日志

任务状态变更时,ScheduleTaskManager 发布 ChangeStatusEventJobLogEventConsumer 异步消费(需配置 @Async)。

@Slf4j
@Component
@RequiredArgsConstructor
public class JobLogEventConsumer implements ChangeStatusEventConsumer {
   
    private final JobLogRepository jobLogRepository;
    private final JobRepository jobRepository;

    @Override
    @Async  // ✅ 异步执行,不阻塞主调度线程
    @Transactional(propagation = Propagation.REQUIRES_NEW)  // ✅ 独立事务,不受业务回滚影响
    public void consumer(ChangeStatusEvent event) {
   
        TaskRuntimeData runtimeData = event.taskRuntimeData();
        jobRepository.findByTaskCode(runtimeData.taskCode())
            .ifPresentOrElse(
                task -> {
   
                    JobLogEntity log = new JobLogEntity();
                    log.setJob(task);
                    log.setTaskStatus(runtimeData.taskStatus());
                    log.setLotNo(runtimeData.lotNo());
                    log.setTotalCount(runtimeData.totalCount());
                    log.setTotalFailureCount(runtimeData.totalFailureCount());
                    log.setStartTime(runtimeData.startTime());
                    log.setEndTime(runtimeData.endTime());

                    // 异常信息智能截取
                    Throwable throwable = runtimeData.throwable();
                    if (throwable != null) {
   
                        log.setHasError(YesNoEnum.Y);
                        log.setMessage(StringUtils.substring(
                            ExceptionUtils.getSelfStackTrace(throwable), 0, 500));
                    } else {
   
                        log.setHasError(YesNoEnum.N);
                        log.setMessage(runtimeData.message());
                    }

                    jobLogRepository.save(log);
                },
                () -> log.warn("任务不存在,无法记录日志,taskCode={}", runtimeData.taskCode())
            );
    }
}

技术亮点:

  • 异步+独立事务:日志写入不影响调度性能,且即使主业务事务回滚,日志依然留存。
  • 异常堆栈裁剪:500 字符限制防止数据截断异常,同时保留关键堆栈信息。
  • 空值友好:使用 ifPresentOrElse 优雅处理任务不存在的情况。

2. 日志查询的规格模式组合

JobLogQuery 支持多条件动态查询,包括关联表(JobEntity)字段。

public class JobLogSpecification {
   
    // 关联查询示例:通过 leftJoin 连接 JobEntity
    public static Specification<JobLogEntity> taskCodeEqual(String taskCode) {
   
        return (root, query, cb) -> {
   
            Join<JobLogEntity, JobEntity> jobJoin = leftJoin(root, "job");
            return JobSpecification.taskCodeEqual(jobJoin, taskCode, cb);
        };
    }

    // 时间范围查询
    public static Predicate createdAtBetween(Path<JobLogEntity> path, 
                                             Instant start, Instant end, 
                                             CriteriaBuilder cb) {
   
        Predicate predicate = cb.conjunction();
        if (start != null) {
   
            predicate = cb.and(predicate, cb.greaterThanOrEqualTo(path.get("createdAt"), start));
        }
        if (end != null) {
   
            predicate = cb.and(predicate, cb.lessThanOrEqualTo(path.get("createdAt"), end));
        }
        return predicate;
    }
}

技术亮点:

  • 类型安全:使用 JPA 元模型(Metamodel)或字符串常量,避免硬编码。
  • 可组合性:每个条件都是独立的 Specification,可灵活组合复用。

3. VO 组装与关联数据补充

JobLogVO 在转换时额外填充任务标识字段,避免前端额外请求。

@Data
public class JobLogVO extends BaseVO {
   
    // 日志自身字段
    private TaskStatusEnum taskStatus;
    private String lotNo;
    private Instant createdAt;
    // ... 其他日志字段

    // 关联任务冗余字段(非数据库字段,用于展示)
    private String taskCode;
    private String classSimpleName;

    public static JobLogVO of(JobLogEntity entity) {
   
        JobLogVO vo = new JobLogVO();
        BeanUtils.copyProperties(entity, vo);
        JobEntity job = entity.getJob();
        if (job != null) {
   
            vo.setTaskCode(job.getTaskCode());
            vo.setClassSimpleName(job.getClassSimpleName());
        }
        return vo;
    }
}

设计权衡

  • 优点:一次查询返回完整信息,减少网络往返。
  • 注意:若 JobEntity 中关联数据较大(如 metadata 含大量 JSON),不建议全部复制,只取必要字段。

4. 日志分页查询接口

@RestController
@RequestMapping("/api/job-log")
@RequiredArgsConstructor
public class JobLogEndpoint {
   
    private final JobLogService jobLogService;

    @GetMapping("page")
    public ResponseEntity<PagedModel<JobLogVO>> findPage(JobLogQuery query, Pageable pageable) {
   
        Page<JobLogEntity> page = jobLogService.find(query, pageable);
        return ResponseEntity.ok(new PagedModel<>(page.map(JobLogVO::of)));
    }
}

技术亮点

  • Spring Data 集成Pageable 自动解析分页参数,PagedModel 封装分页元数据。
  • VO 映射page.map(JobLogVO::of) 流式转换,代码简洁。
目录
相关文章
人工智能 缓存 前端开发
10496 50
人工智能 JavaScript 开发工具
4112 13
开发工具 Swift git
1603 2
人工智能 Java BI
1016 1
人工智能 JavaScript 测试技术
1458 2
缓存 JavaScript Shell
1862 3
人工智能 JavaScript 测试技术
684 4
Shell API 调度
1014 3