You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何计算依赖前置任务的任务起止日期(Spring Boot)

Spring Boot 依赖型任务日期自动推算接口实现方案

核心实现逻辑

严格匹配给定业务规则落地:

  • 无依赖根任务(isDependent = false)直接读取数据库存储的起止日期作为基准值
  • 有前置依赖的任务,开始日期直接取前置任务的结束日期,再结合任务自身的工期属性计算自身结束日期
  • 沿依赖链向上递归直到命中根任务,过程中增加缓存、循环依赖校验避免性能问题和栈溢出
  • 全量任务一次性加载到内存,减少递归过程中的数据库IO

分层代码实现

1. 日期推算通用组件

单独抽离递归推算逻辑,和业务逻辑解耦,内置缓存和循环依赖校验:

import java.time.LocalDate;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.springframework.stereotype.Component;

@Component
public class TaskDateCalculator {
    // 任务ID -> [开始日期, 结束日期] 缓存,避免重复递归推算
    private final Map<Long, LocalDate[]> dateCache = new ConcurrentHashMap<>();
    // 单次请求递归链路访问标记,防止循环依赖导致栈溢出
    private final ThreadLocal<Set<Long>> visitPath = ThreadLocal.withInitial(HashSet::new);

    public LocalDate[] calculate(Long taskId, Function<Long, Task> taskGetter) {
        // 命中缓存直接返回
        if (dateCache.containsKey(taskId)) {
            return dateCache.get(taskId);
        }
        Set<Long> currentVisits = visitPath.get();
        // 检测到循环依赖直接抛错
        if (currentVisits.contains(taskId)) {
            throw new RuntimeException("任务依赖存在循环引用,异常任务ID:" + taskId);
        }
        currentVisits.add(taskId);

        Task current = taskGetter.apply(taskId);
        LocalDate start;
        LocalDate end;

        if (Boolean.FALSE.equals(current.getIsDependent())) {
            // 根任务直接取库内存储的日期,做空校验
            if (current.getStartDate() == null || current.getEndDate() == null) {
                throw new RuntimeException("根任务日期配置缺失,异常任务ID:" + taskId);
            }
            start = current.getStartDate();
            end = current.getEndDate();
        } else {
            // 递归查询前置任务日期
            LocalDate[] preDates = calculate(current.getPreTaskId(), taskGetter);
            start = preDates[1];
            // 结束日期根据自身工期推算,可根据业务实际存储的工期字段调整取值逻辑
            end = start.plusDays(current.getDurationDays());
        }

        LocalDate[] result = new LocalDate[]{start, end};
        dateCache.put(taskId, result);
        // 清理链路标记
        currentVisits.remove(taskId);
        if (currentVisits.isEmpty()) {
            visitPath.remove();
        }
        return result;
    }

    // 单请求处理完成后清空缓存,避免跨请求数据干扰
    public void clearCache() {
        dateCache.clear();
    }
}

2. Service层改造

原有查询逻辑基础上,增加日期推算步骤,全量加载任务减少DB查询:

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class TaskServiceImpl implements TaskService {
    @Resource
    private TaskRepository taskRepository;
    @Resource
    private TaskDateCalculator dateCalculator;
    @Resource
    private TaskMapper taskMapper;

    @Override
    public List<TaskDTO> listTasks() {
        try {
            // 一次性查询所有任务,转成ID映射供递归查询
            List<Task> allTasks = taskRepository.findAll();
            Map<Long, Task> taskMap = allTasks.stream()
                    .collect(Collectors.toMap(Task::getId, t -> t));

            List<TaskDTO> result = new ArrayList<>();
            for (Task task : allTasks) {
                LocalDate[] dates = dateCalculator.calculate(task.getId(), taskMap::get);
                TaskDTO dto = taskMapper.toDTO(task);
                dto.setStartDate(dates[0]);
                dto.setEndDate(dates[1]);
                result.add(dto);
            }
            return result;
        } finally {
            dateCalculator.clearCache();
        }
    }
}

3. 其余层适配说明

  • Model层:确保Task实体包含id、isDependent、preTaskId(前置任务ID)、startDate、endDate、durationDays(工期/天)字段,其中依赖型任务的startDate、endDate允许为null
  • Mapper层:保留原有实体转DTO的映射逻辑即可,无需额外修改,推算出的日期会在Service层填充到DTO
  • Repository层:无需修改,保留原有全量查询、单ID查询的基础方法即可
  • Controller层:无需修改,保持原有调用Service返回结果的逻辑即可

边界场景处理

  • 循环依赖:通过单次请求的访问链路标记,遍历到重复ID时直接抛出异常,避免无限递归栈溢出
  • 性能损耗:全量任务预加载+结果缓存,整体推算时间复杂度为O(n),无重复DB查询和重复计算
  • 配置异常:根任务缺失日期、依赖链指向不存在的任务时直接抛出明确异常,方便排查配置问题

内容的提问来源于stack exchange,提问作者Cleiton Freitas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 12:33:13