|
|
@@ -13,8 +13,14 @@ import java.util.Date;
|
|
|
import java.util.List;
|
|
|
|
|
|
/**
|
|
|
- * TASK-004: 重复任务定时生成器
|
|
|
- * 每天凌晨自动生成每日/每周重复任务
|
|
|
+ * TASK-SYS-248: 重复任务定时生成器(频率分派版)
|
|
|
+ * 每小时执行一次,按 frequency 分派生成任务实例:
|
|
|
+ * - hourly: 每小时生成(无实例则生成)
|
|
|
+ * - daily: 每天生成(凌晨5点窗口内)
|
|
|
+ * - every_other_day: 隔天生成(按模板创建日奇偶)
|
|
|
+ * - weekly: 每周生成(按模板 deadline 的星期几)
|
|
|
+ * - monthly: 每月生成(按模板 deadline 的日)
|
|
|
+ * 兼容旧版 repeat_type (none/daily/weekly):repeat_type=daily 等价 frequency=daily,weekly 等价 weekly
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@Component
|
|
|
@@ -24,12 +30,12 @@ public class RepeatTaskGenerator {
|
|
|
private TaskMapper taskMapper;
|
|
|
|
|
|
/**
|
|
|
- * 每天凌晨5点执行,生成当天的重复任务
|
|
|
+ * 每小时第5分执行,按频率分派生成重复任务
|
|
|
*/
|
|
|
- @Scheduled(cron = "0 0 5 * * ?")
|
|
|
+ @Scheduled(cron = "0 5 * * * ?")
|
|
|
public void generateDailyTasks() {
|
|
|
- log.info("开始生成重复任务...");
|
|
|
-
|
|
|
+ log.info("开始按频率生成重复任务...");
|
|
|
+
|
|
|
try {
|
|
|
Date now = new Date();
|
|
|
Calendar todayCal = Calendar.getInstance();
|
|
|
@@ -38,91 +44,130 @@ public class RepeatTaskGenerator {
|
|
|
todayCal.set(Calendar.SECOND, 0);
|
|
|
todayCal.set(Calendar.MILLISECOND, 0);
|
|
|
Date today = todayCal.getTime();
|
|
|
-
|
|
|
- // 1. 处理每日重复任务
|
|
|
+
|
|
|
+ int dayOfWeek = todayCal.get(Calendar.DAY_OF_WEEK);
|
|
|
+ int weekday = dayOfWeek == 1 ? 7 : dayOfWeek - 1; // 周一=1...周日=7
|
|
|
+
|
|
|
+ // 1. 每日重复任务(frequency=daily,或旧版 repeat_type=daily)
|
|
|
List<Task> dailyTasks = taskMapper.selectList(
|
|
|
new LambdaQueryWrapper<Task>()
|
|
|
- .eq(Task::getRepeatType, "daily")
|
|
|
- .eq(Task::getStatus, "pending")
|
|
|
+ .eq(Task::getIsTemplate, 1)
|
|
|
+ .and(w -> w.eq(Task::getFrequency, "daily")
|
|
|
+ .or().eq(Task::getRepeatType, "daily"))
|
|
|
);
|
|
|
-
|
|
|
for (Task template : dailyTasks) {
|
|
|
- // 检查今天是否已存在该模板的任务
|
|
|
- if (!hasTaskForDate(template, today)) {
|
|
|
- createTaskFromTemplate(template, today);
|
|
|
+ if (!hasTaskForDate(template, today, 86400000L)) {
|
|
|
+ createTaskFromTemplate(template, today, false);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
log.info("每日重复任务生成完成,处理{}个模板", dailyTasks.size());
|
|
|
-
|
|
|
- // 2. 处理每周重复任务(今天是周几?)
|
|
|
- int dayOfWeek = todayCal.get(Calendar.DAY_OF_WEEK);
|
|
|
- // Calendar.SUNDAY = 1, MONDAY = 2, ... SATURDAY = 7
|
|
|
- // 转换为周一=1 ... 周日=7
|
|
|
- int weekday = dayOfWeek == 1 ? 7 : dayOfWeek - 1;
|
|
|
-
|
|
|
- // 获取每周重复的任务模板(存储在category或用其他方式)
|
|
|
- // 这里假设原始模板任务的截止时间就是它应该在的星期几
|
|
|
+
|
|
|
+ // 2. 隔天重复任务(frequency=every_other_day)
|
|
|
+ List<Task> everyOtherDayTasks = taskMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Task>()
|
|
|
+ .eq(Task::getIsTemplate, 1)
|
|
|
+ .eq(Task::getFrequency, "every_other_day")
|
|
|
+ );
|
|
|
+ for (Task template : everyOtherDayTasks) {
|
|
|
+ if (isEveryOtherDay(template, today)) {
|
|
|
+ if (!hasTaskForDate(template, today, 86400000L)) {
|
|
|
+ createTaskFromTemplate(template, today, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("隔天重复任务生成完成,处理{}个模板", everyOtherDayTasks.size());
|
|
|
+
|
|
|
+ // 3. 每周重复任务(frequency=weekly,或旧版 repeat_type=weekly)
|
|
|
List<Task> weeklyTasks = taskMapper.selectList(
|
|
|
new LambdaQueryWrapper<Task>()
|
|
|
- .eq(Task::getRepeatType, "weekly")
|
|
|
- .eq(Task::getStatus, "pending")
|
|
|
+ .eq(Task::getIsTemplate, 1)
|
|
|
+ .and(w -> w.eq(Task::getFrequency, "weekly")
|
|
|
+ .or().eq(Task::getRepeatType, "weekly"))
|
|
|
);
|
|
|
-
|
|
|
for (Task template : weeklyTasks) {
|
|
|
- // 检查模板的截止时间是周几
|
|
|
Calendar templateCal = Calendar.getInstance();
|
|
|
templateCal.setTime(template.getDeadline());
|
|
|
int templateDayOfWeek = templateCal.get(Calendar.DAY_OF_WEEK);
|
|
|
int templateWeekday = templateDayOfWeek == 1 ? 7 : templateDayOfWeek - 1;
|
|
|
-
|
|
|
- // 如果今天是该任务的执行日
|
|
|
if (weekday == templateWeekday) {
|
|
|
- if (!hasTaskForDate(template, today)) {
|
|
|
- createTaskFromTemplate(template, today);
|
|
|
+ if (!hasTaskForDate(template, today, 86400000L)) {
|
|
|
+ createTaskFromTemplate(template, today, false);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
log.info("每周重复任务生成完成,处理{}个模板", weeklyTasks.size());
|
|
|
-
|
|
|
+
|
|
|
+ // 4. 每月重复任务(frequency=monthly)
|
|
|
+ List<Task> monthlyTasks = taskMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Task>()
|
|
|
+ .eq(Task::getIsTemplate, 1)
|
|
|
+ .eq(Task::getFrequency, "monthly")
|
|
|
+ );
|
|
|
+ for (Task template : monthlyTasks) {
|
|
|
+ Calendar templateCal = Calendar.getInstance();
|
|
|
+ templateCal.setTime(template.getDeadline());
|
|
|
+ int templateDay = templateCal.get(Calendar.DAY_OF_MONTH);
|
|
|
+ int todayDay = todayCal.get(Calendar.DAY_OF_MONTH);
|
|
|
+ if (templateDay == todayDay) {
|
|
|
+ if (!hasTaskForDate(template, today, 86400000L)) {
|
|
|
+ createTaskFromTemplate(template, today, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("每月重复任务生成完成,处理{}个模板", monthlyTasks.size());
|
|
|
+
|
|
|
+ // 5. 每小时重复任务(frequency=hourly)——最近1小时无实例则生成
|
|
|
+ List<Task> hourlyTasks = taskMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Task>()
|
|
|
+ .eq(Task::getIsTemplate, 1)
|
|
|
+ .eq(Task::getFrequency, "hourly")
|
|
|
+ );
|
|
|
+ for (Task template : hourlyTasks) {
|
|
|
+ if (!hasTaskForDate(template, now, 3600000L)) {
|
|
|
+ createTaskFromTemplate(template, now, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("每小时重复任务生成完成,处理{}个模板", hourlyTasks.size());
|
|
|
+
|
|
|
} catch (Exception e) {
|
|
|
log.error("生成重复任务失败", e);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
- * 检查指定日期是否已存在该模板的任务实例
|
|
|
+ * 隔天判定:模板创建时间到今天的间隔天数为偶数 → 今天生成
|
|
|
*/
|
|
|
- private boolean hasTaskForDate(Task template, Date date) {
|
|
|
- Calendar cal = Calendar.getInstance();
|
|
|
- cal.setTime(date);
|
|
|
- cal.set(Calendar.HOUR_OF_DAY, 23);
|
|
|
- cal.set(Calendar.MINUTE, 59);
|
|
|
- cal.set(Calendar.SECOND, 59);
|
|
|
- Date endOfDay = cal.getTime();
|
|
|
-
|
|
|
- cal.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
- cal.set(Calendar.MINUTE, 0);
|
|
|
- cal.set(Calendar.SECOND, 0);
|
|
|
- Date startOfDay = cal.getTime();
|
|
|
-
|
|
|
+ private boolean isEveryOtherDay(Task template, Date today) {
|
|
|
+ Date created = template.getCreatedAt() != null ? template.getCreatedAt() : template.getDeadline();
|
|
|
+ if (created == null) return true;
|
|
|
+ long diffDays = (today.getTime() - created.getTime()) / 86400000L;
|
|
|
+ return diffDays % 2 == 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查指定时间窗口内是否已存在该模板的任务实例
|
|
|
+ * @param windowMs 去重窗口(毫秒):86400000=当天,3600000=最近1小时
|
|
|
+ */
|
|
|
+ private boolean hasTaskForDate(Task template, Date date, long windowMs) {
|
|
|
+ Date windowEnd = new Date(date.getTime() + windowMs);
|
|
|
+
|
|
|
Long count = taskMapper.selectCount(
|
|
|
new LambdaQueryWrapper<Task>()
|
|
|
.eq(Task::getTitle, template.getTitle())
|
|
|
.eq(Task::getChildId, template.getChildId())
|
|
|
.eq(Task::getFamilyId, template.getFamilyId())
|
|
|
- .ge(Task::getDeadline, startOfDay)
|
|
|
- .le(Task::getDeadline, endOfDay)
|
|
|
+ .ge(Task::getCreatedAt, date)
|
|
|
+ .lt(Task::getCreatedAt, windowEnd)
|
|
|
);
|
|
|
-
|
|
|
+
|
|
|
return count != null && count > 0;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 从模板创建新任务实例
|
|
|
+* @param useNow 是否使用当前时间作为截止时间(hourly 任务使用)
|
|
|
*/
|
|
|
-private void createTaskFromTemplate(Task template, Date targetDate) {
|
|
|
+private void createTaskFromTemplate(Task template, Date targetDate, boolean useNow) {
|
|
|
Task newTask = new Task();
|
|
|
newTask.setFamilyId(template.getFamilyId());
|
|
|
newTask.setCreatorId(template.getCreatorId());
|
|
|
@@ -141,23 +186,48 @@ newTask.setTemplateId(template.getId()); // 指向模板
|
|
|
newTask.setIsTemplate(0); // 这是实例,不是模板
|
|
|
newTask.setMemberOnly(template.getMemberOnly()); // B21: 透传会员标记
|
|
|
|
|
|
-// 设置新的截止时间(保持原有时分,更新日期)
|
|
|
-Calendar newDeadline = Calendar.getInstance();
|
|
|
-newDeadline.setTime(targetDate);
|
|
|
+// TASK-SYS-248: 透传行为调度中心字段(action/填写/两阶段/最短时长)
|
|
|
+newTask.setPrerequisiteTaskId(template.getPrerequisiteTaskId());
|
|
|
+newTask.setActionType(template.getActionType());
|
|
|
+newTask.setActionConfig(template.getActionConfig());
|
|
|
+newTask.setRequireInput(template.getRequireInput());
|
|
|
+newTask.setStartRequired(template.getStartRequired());
|
|
|
+newTask.setStartCount(0);
|
|
|
+newTask.setMinDurationSeconds(template.getMinDurationSeconds());
|
|
|
|
|
|
-Calendar templateTime = Calendar.getInstance();
|
|
|
-templateTime.setTime(template.getDeadline());
|
|
|
-
|
|
|
-newDeadline.set(Calendar.HOUR_OF_DAY, templateTime.get(Calendar.HOUR_OF_DAY));
|
|
|
-newDeadline.set(Calendar.MINUTE, templateTime.get(Calendar.MINUTE));
|
|
|
+// 设置新的截止时间
|
|
|
+Calendar newDeadline = Calendar.getInstance();
|
|
|
+if (useNow) {
|
|
|
+ // hourly 任务:截止 = 当前时间 + 模板原时长(默认1小时)
|
|
|
+ int dur = template.getDuration() != null && template.getDuration() > 0 ? template.getDuration() : 60;
|
|
|
+ newDeadline.setTime(new Date(targetDate.getTime() + dur * 60000L));
|
|
|
+} else {
|
|
|
+ newDeadline.setTime(targetDate);
|
|
|
+ Calendar templateTime = Calendar.getInstance();
|
|
|
+ templateTime.setTime(template.getDeadline());
|
|
|
+ newDeadline.set(Calendar.HOUR_OF_DAY, templateTime.get(Calendar.HOUR_OF_DAY));
|
|
|
+ newDeadline.set(Calendar.MINUTE, templateTime.get(Calendar.MINUTE));
|
|
|
+}
|
|
|
|
|
|
newTask.setDeadline(newDeadline.getTime());
|
|
|
newTask.setRepeatType("none"); // 生成的实例不重复
|
|
|
-newTask.setStatus("pending");
|
|
|
+newTask.setFrequency("none"); // 实例不重复生成
|
|
|
newTask.setCreatedAt(new Date());
|
|
|
newTask.setUpdatedAt(new Date());
|
|
|
|
|
|
+// TASK-SYS-248: 有前置任务且前置未完成 → 实例初始状态 locked
|
|
|
+if (newTask.getPrerequisiteTaskId() != null) {
|
|
|
+ Task prereq = taskMapper.selectById(newTask.getPrerequisiteTaskId());
|
|
|
+ if (prereq == null || !"completed".equals(prereq.getStatus())) {
|
|
|
+ newTask.setStatus("locked");
|
|
|
+ } else {
|
|
|
+ newTask.setStatus("pending");
|
|
|
+ }
|
|
|
+} else {
|
|
|
+ newTask.setStatus("pending");
|
|
|
+}
|
|
|
+
|
|
|
taskMapper.insert(newTask);
|
|
|
log.info("已生成重复任务: {} (截止: {})", newTask.getTitle(), newTask.getDeadline());
|
|
|
}
|
|
|
-}
|
|
|
+}
|