diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/constant/FlowConstants.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/constant/FlowConstants.java index 75f0845..390b150 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/constant/FlowConstants.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/constant/FlowConstants.java @@ -1,5 +1,13 @@ package org.jeecg.modules.tasktask.constant; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + public final class FlowConstants { private FlowConstants() { } @@ -13,27 +21,50 @@ public final class FlowConstants { /** 延迟发起 */ public static final Integer IMMEDIATE_AND_RECURRING = 3; } - public static final class IntervalType { + @Getter + public enum IntervalType { + EVERY_DAY(1, 1, ChronoUnit.DAYS, "每天"), + EVERY_WEEK(2, 1, ChronoUnit.WEEKS, "每周"), + EVERY_TWO_WEEKS(3, 2, ChronoUnit.WEEKS, "每两周"), + EVERY_MONTH(4, 1, ChronoUnit.MONTHS, "每月"), + EVERY_QUARTER(5, 3, ChronoUnit.MONTHS, "每季"); + + private final Integer code; + private final int amount; // 步长数量 + private final ChronoUnit unit; // 时间单位 + private final String description; + + IntervalType(Integer code, int amount, ChronoUnit unit, String description) { + this.code = code; + this.amount = amount; + this.unit = unit; + this.description = description; + } + + // 静态缓存:优化查找性能,避免重复遍历 values() + private static final Map LOOKUP = Arrays.stream(values()) + .collect(Collectors.toMap(IntervalType::getCode, it -> it)); + /** - * 每天 + * 根据 code 获取枚举对象 */ - public static final Integer EVERY_DAY = 1; + public static IntervalType of(Integer code) { + IntervalType type = LOOKUP.get(code); + if (type == null) { + throw new IllegalArgumentException("未知的周期类型代码: " + code); + } + return type; + } + /** - * 每周 + * 计算下一次执行时间 + * @param base 基准时间 + * @param step 第几次循环 */ - public static final Integer EVERY_WEEK = 2; - /** - * 每两周 - */ - public static final Integer EVERY_TWO_WEEKS = 3; - /** - * 每月 - */ - public static final Integer EVERY_MONTH = 4; - /** - * 每季 - */ - public static final Integer EVERY_QUARTER = 5; + public LocalDateTime nextTime(LocalDateTime base, int step) { + // 使用 base + (步长 * 循环次数) 避免累加误差 + return base.plus((long) step * amount, unit); + } } public static final class taskCount{ public static final Integer MAX = 24; diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/controller/TaskTaskController.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/controller/TaskTaskController.java index 9b5d29b..8725a5c 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/controller/TaskTaskController.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/controller/TaskTaskController.java @@ -10,6 +10,7 @@ import java.net.URLDecoder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang.StringUtils; import org.apache.shiro.SecurityUtils; import org.jeecg.common.api.vo.Result; import org.jeecg.common.system.query.QueryGenerator; @@ -114,65 +115,18 @@ public class TaskTaskController extends JeecgController addRecurringTask(@RequestBody TaskTask taskTask, - @RequestParam(name = "intervalType", required = false) Integer intervalType, - @RequestParam(name = "startCount", required = false) Integer startCount) throws Exception { - if (taskTask.getTriggerType() == null) { - return Result.error("触发类型不能为空"); - } - Integer triggerType = taskTask.getTriggerType(); + public Result addRecurringTask(@RequestBody TaskTask taskTask, Integer intervalType, Integer startCount) { + // 获取用户信息 LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); - if (sysUser.getUsername() == null || sysUser.getUsername().isEmpty()) { - return Result.error("无法获取当前流程发起人信息"); + String username = sysUser.getUsername(); + + // 这里只做最基础的鉴权拦截 + if (StringUtils.isEmpty(username)) { + return Result.error("登录已失效,请重新登录"); } - if (startCount > FlowConstants.taskCount.MAX) return Result.error("单词生成周期任务不能超过100个"); - if (FlowConstants.Strategy.IMMEDIATE_ONLY.equals(triggerType)) { - if (taskTaskService.singleFlowStart(taskTask, sysUser.getUsername())) { - return Result.ok("单次流程发起成功"); - } - return Result.error("立即发起流程失败"); - } else if (FlowConstants.Strategy.RECURRING_ONLY.equals(triggerType)) { - //向中间表写入n个待发起流程,由定时器扫描发起 - List temTaskList = new ArrayList<>(); - Date baseDate = Optional.ofNullable(taskTask.getStartTime()) - .orElseThrow(() -> new IllegalArgumentException("周期性流程开始时间不能为空!")); - - LocalDateTime currentLdt = baseDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); - - for (int i = 0; i < startCount; i++) { - // 计算下一次执行时间 - if (FlowConstants.IntervalType.EVERY_DAY.equals(intervalType)) { - currentLdt = currentLdt.plusDays(1); - } else if (FlowConstants.IntervalType.EVERY_WEEK.equals(intervalType)) { - currentLdt = currentLdt.plusWeeks(1); - } else if (FlowConstants.IntervalType.EVERY_TWO_WEEKS.equals(intervalType)) { - currentLdt = currentLdt.plusWeeks(2); - } else if (FlowConstants.IntervalType.EVERY_MONTH.equals(intervalType)) { - currentLdt = currentLdt.plusMonths(1); - } else if (FlowConstants.IntervalType.EVERY_QUARTER.equals(intervalType)) { - currentLdt = currentLdt.plusMonths(3); - } else { - throw new IllegalArgumentException("未知的周期类型: " + intervalType); - } - - // 2. 创建新的任务对象并添加到列表 - TaskTask newTask = new TaskTask(); - // 复制基础属性 (建议使用 BeanUtils.copyProperties) - BeanUtils.copyProperties(taskTask, newTask); - - // 设置计算后的触发时间 - Date nextExecutionDate = Date.from(currentLdt.atZone(ZoneId.systemDefault()).toInstant()); - newTask.setStartTime(nextExecutionDate); - - temTaskList.add(newTask); - } - taskTaskService.saveBatch(temTaskList); - } else { - // 这个 else 很重要,处理那些“意料之外”的数值 - return Result.error("非法的触发类型数值: " + triggerType); - } - return Result.OK("添加成功!"); + // 业务逻辑全部交给 Service + return taskTaskService.handleFlowStart(taskTask, username, intervalType, startCount); } /** diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/ITaskTaskService.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/ITaskTaskService.java index 4251f63..01b437f 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/ITaskTaskService.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/ITaskTaskService.java @@ -1,5 +1,6 @@ package org.jeecg.modules.tasktask.service; +import org.jeecg.common.api.vo.Result; import org.jeecg.modules.tasktask.entity.TaskTask; import com.baomidou.mybatisplus.extension.service.IService; @@ -11,4 +12,6 @@ import com.baomidou.mybatisplus.extension.service.IService; */ public interface ITaskTaskService extends IService { boolean singleFlowStart(TaskTask taskTask,String userName) throws Exception; + boolean multiFlowStart(TaskTask taskTask,String userName,Integer intervalType, Integer startCount) throws Exception; + Result handleFlowStart(TaskTask taskTask, String username, Integer intervalType, Integer startCount); } diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java index 5153195..f5bdfd9 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java @@ -6,17 +6,20 @@ import org.apache.shiro.util.Assert; import org.jeecg.common.api.vo.Result; import org.jeecg.modules.extbpm.process.exception.BpmException; import org.jeecg.modules.extbpm.process.service.impl.BpmBaseExtApiImpl; +import org.jeecg.modules.tasktask.constant.FlowConstants; import org.jeecg.modules.tasktask.entity.TaskTask; import org.jeecg.modules.tasktask.mapper.TaskTaskMapper; import org.jeecg.modules.tasktask.service.ITaskTaskService; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; -import java.util.Optional; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; /** * @Description: 事项任务计划表(发起流程) @@ -28,7 +31,6 @@ import java.util.Optional; public class TaskTaskServiceImpl extends ServiceImpl implements ITaskTaskService { @Autowired private BpmBaseExtApiImpl bpmBaseExtApiImpl; - @Transactional(rollbackFor = Exception.class) public boolean singleFlowStart(TaskTask taskTask, String userName) throws Exception { if (StringUtils.isBlank(taskTask.getFlowCode())) { @@ -46,4 +48,71 @@ public class TaskTaskServiceImpl extends ServiceImpl i Result res = bpmBaseExtApiImpl.startMutilProcess(taskTask.getFlowCode(), taskTask.getBusinessId(), taskTask.getFormUrl(), taskTask.getFormUrl(), userName, jsonString); return res.isSuccess(); } + @Transactional(rollbackFor = Exception.class) + public boolean multiFlowStart(TaskTask taskTask,String userName,Integer intervalType, Integer startCount) throws Exception{ + List temTaskList = new ArrayList<>(); + Date baseDate = Optional.ofNullable(taskTask.getStartTime()) + .orElseThrow(() -> new IllegalArgumentException("周期性流程开始时间不能为空!")); + + LocalDateTime currentLdt = baseDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + FlowConstants.IntervalType intervalTypeInstance = FlowConstants.IntervalType.of(intervalType); + for (int i = 0; i < startCount; i++) { + + LocalDateTime execDateTime = intervalTypeInstance.nextTime(currentLdt,i+1); + + // 2. 创建新的任务对象并添加到列表 + TaskTask newTask = new TaskTask(); + // 复制基础属性 (建议使用 BeanUtils.copyProperties) + BeanUtils.copyProperties(taskTask, newTask); + newTask.setId(null); + // 设置计算后的触发时间 + Date nextExecutionDate = Date.from(execDateTime.atZone(ZoneId.systemDefault()).toInstant()); + newTask.setStartTime(nextExecutionDate); + + temTaskList.add(newTask); + } + return this.saveBatch(temTaskList); + } + public Result handleFlowStart(TaskTask taskTask, String username, Integer intervalType, Integer startCount) throws Exception { + // 1. 显式校验:代替 Assert + Result checkResult = validateBusinessParams(taskTask, startCount); + if (!checkResult.isSuccess()) { + return checkResult; // 校验不通过直接返回错误 + } + + Integer triggerType = taskTask.getTriggerType(); + + // 2. 策略分发(路由) + if (FlowConstants.Strategy.IMMEDIATE_ONLY.equals(triggerType)) { + boolean success = singleFlowStart(taskTask, username); + return success ? Result.ok("立即发起流程成功") : Result.error("立即发起流程失败"); + } + + if (FlowConstants.Strategy.RECURRING_ONLY.equals(triggerType)) { + boolean success = multiFlowStart(taskTask, username, intervalType, startCount); + return success ? Result.ok("周期性流程添加成功") : Result.error("周期性流程添加失败"); + } + + // 3. 保底处理 + return Result.error("未知的触发类型: " + triggerType); + } + private Result validateBusinessParams(TaskTask taskTask, Integer startCount) { + if (taskTask.getTriggerType() == null) { + return Result.error("触发类型不能为空"); + } + + // 如果是周期任务,校验次数和开始时间 + if (FlowConstants.Strategy.RECURRING_ONLY.equals(taskTask.getTriggerType())) { + if (startCount == null || startCount <= 0) { + return Result.error("周期任务生成数量必须大于0"); + } + if (startCount > FlowConstants.taskCount.MAX) { + return Result.error("单次生成周期任务不能超过24个"); + } + if (taskTask.getStartTime() == null) { + return Result.error("周期性流程开始时间不能为空"); + } + } + return Result.ok(); + } }