|
|
@@ -0,0 +1,99 @@
|
|
|
+package com.train.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.train.entity.TrainAuditLog;
|
|
|
+import com.train.entity.TrainClass;
|
|
|
+import com.train.entity.TrainUser;
|
|
|
+import com.train.mapper.TrainAuditLogMapper;
|
|
|
+import com.train.mapper.TrainClassMapper;
|
|
|
+import com.train.mapper.TrainUserMapper;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 课前提醒定时任务(FR-PRE-02):扫描已设置 startAt 的 active 班次,
|
|
|
+ * 对已进班的学员在 T-7 / T-3 / T-1 触发提醒,落 audit_log(幂等)。
|
|
|
+ * test-mode:仅打日志;生产可在此处调用 SubscribeMessageService.sendToUser 推送订阅消息。
|
|
|
+ *
|
|
|
+ * 触发点判定:班次 startAt 为基准,相对天数落入窗口且当天未触发过则落账一次。
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class PreClassReminderTask {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private TrainClassMapper trainClassMapper;
|
|
|
+ @Resource
|
|
|
+ private TrainUserMapper trainUserMapper;
|
|
|
+ @Resource
|
|
|
+ private TrainAuditLogMapper trainAuditLogMapper;
|
|
|
+
|
|
|
+ private static final long DAY_MILLIS = 24L * 60 * 60 * 1000;
|
|
|
+
|
|
|
+ /** 每日 10:00 执行(错开 FollowUpTask 的 09:30) */
|
|
|
+ @Scheduled(cron = "0 0 10 * * ?")
|
|
|
+ public void runPreClassReminder() {
|
|
|
+ List<TrainClass> classes = trainClassMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<TrainClass>()
|
|
|
+ .eq(TrainClass::getStatus, "active")
|
|
|
+ .isNotNull(TrainClass::getStartAt));
|
|
|
+ Date now = new Date();
|
|
|
+ int totalReminders = 0;
|
|
|
+ for (TrainClass cls : classes) {
|
|
|
+ if (cls.getStartAt() == null || cls.getStartAt().getTime() <= now.getTime()) {
|
|
|
+ continue; // 已开课或未设时间
|
|
|
+ }
|
|
|
+ long daysAhead = (cls.getStartAt().getTime() - now.getTime()) / DAY_MILLIS;
|
|
|
+ String milestone = null;
|
|
|
+ String detail = null;
|
|
|
+ if (daysAhead >= 6 && daysAhead <= 8) {
|
|
|
+ milestone = "preclass_t7";
|
|
|
+ detail = "T-7 课前提醒:距离「" + cls.getName() + "」还有 7 天,请预装 WorkBuddy 并准备三本账数据";
|
|
|
+ } else if (daysAhead >= 2 && daysAhead <= 4) {
|
|
|
+ milestone = "preclass_t3";
|
|
|
+ detail = "T-3 课前提醒:距离「" + cls.getName() + "」还有 3 天,请完成课前 3 件事(装机/数据/最乱一本账)";
|
|
|
+ } else if (daysAhead >= 0 && daysAhead <= 1) {
|
|
|
+ milestone = "preclass_t1";
|
|
|
+ detail = "T-1 课前提醒:明天「" + cls.getName() + "」开课," + (cls.getPlace() == null ? "" : "地点:" + cls.getPlace()) + ",记得准时到场";
|
|
|
+ }
|
|
|
+ if (milestone == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ List<TrainUser> users = trainUserMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<TrainUser>().eq(TrainUser::getClassId, cls.getId()));
|
|
|
+ for (TrainUser u : users) {
|
|
|
+ if (ensureLog(u.getId(), milestone, detail)) {
|
|
|
+ totalReminders++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (totalReminders > 0) {
|
|
|
+ log.info("PreClassReminderTask 本轮下发 {} 条课前提醒", totalReminders);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 幂等:同 action + targetId 同日已存在则跳过;返回 true 表示本次落账 */
|
|
|
+ private boolean ensureLog(Long uid, String action, String detail) {
|
|
|
+ Long exists = trainAuditLogMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<TrainAuditLog>()
|
|
|
+ .eq(TrainAuditLog::getAction, action)
|
|
|
+ .eq(TrainAuditLog::getTargetId, uid));
|
|
|
+ if (exists != null && exists > 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ TrainAuditLog entry = new TrainAuditLog();
|
|
|
+ entry.setAction(action);
|
|
|
+ entry.setActorUid(null);
|
|
|
+ entry.setTargetType("user");
|
|
|
+ entry.setTargetId(uid);
|
|
|
+ entry.setDetail(detail);
|
|
|
+ entry.setTs(new Date());
|
|
|
+ trainAuditLogMapper.insert(entry);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+}
|