From f640a144240cba7b9e72b0272cd8bfb0ce1fefdd Mon Sep 17 00:00:00 2001 From: luogw <3132758203@qq.com> Date: Wed, 4 Mar 2026 15:48:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=EF=BC=8C?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=BC=82=E5=B8=B8=E8=80=83=E8=AF=95=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exam/domain/job/examHandleJob.java | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/main/java/com/project/exam/domain/job/examHandleJob.java diff --git a/src/main/java/com/project/exam/domain/job/examHandleJob.java b/src/main/java/com/project/exam/domain/job/examHandleJob.java new file mode 100644 index 0000000..000a90d --- /dev/null +++ b/src/main/java/com/project/exam/domain/job/examHandleJob.java @@ -0,0 +1,133 @@ +package com.project.exam.domain.job; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.project.exam.domain.entity.ExamRecordEntity; +import com.project.exam.mapper.ExamRecordMapper; +import com.project.task.domain.entity.TaskEntity; +import com.project.task.domain.entity.TaskUserEntity; +import com.project.task.mapper.TaskMapper; +import com.project.task.mapper.TaskUserMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +/** + * 考试异常处理定时任务 + * 处理考试任务截止或考试时间结束时未提交的考试记录 + */ +@Service +@Slf4j +public class examHandleJob { + + @Autowired + private ExamRecordMapper examRecordMapper; + + @Autowired + private TaskMapper taskMapper; + + @Autowired + private TaskUserMapper taskUserMapper; + + /** + * 考试超时缓冲时间(秒),只处理超过这个时间的超时考试 + */ + private static final long EXAM_TIMEOUT_BUFFER_SECONDS = 10; + + /** + * 每小时执行一次,处理考试时间结束但未提交的考试记录 + */ + @Scheduled(cron = "0 0 * * * ?") + public void handleUnsubmittedExams() { + log.info(">>> [考试异常处理任务] 启动执行..."); + + try { + Date now = new Date(); + + // 查询所有未提交的考试记录 + LambdaQueryWrapper examWrapper = new LambdaQueryWrapper<>(); + examWrapper.isNull(ExamRecordEntity::getSubmitTime); + + List unsubmittedRecords = examRecordMapper.selectList(examWrapper); + + if (unsubmittedRecords.isEmpty()) { + log.info(">>> [考试异常处理任务] 没有未提交的考试记录"); + return; + } + + log.info(">>> [考试异常处理任务] 发现 {} 条未提交的考试记录,开始检查考试时间", unsubmittedRecords.size()); + + // 处理每条未提交的考试记录 + for (ExamRecordEntity examRecord : unsubmittedRecords) { + handleUnsubmittedExamRecord(examRecord, now); + } + + log.info(">>> [考试异常处理任务] 执行完毕"); + } catch (Exception e) { + log.error(">>> [考试异常处理任务] 执行异常", e); + } + } + + /** + * 处理单条未提交的考试记录 + * 如果考试时间已结束超过缓冲时间,则设置为未通过,提交时间为考试结束时间 + */ + private void handleUnsubmittedExamRecord(ExamRecordEntity examRecord, Date now) { + try { + // 获取关联的 TaskUserEntity,从中获取考试开始时间 + TaskUserEntity taskUser = taskUserMapper.selectById(examRecord.getTaskUserId()); + + if (taskUser == null) { + log.warn(">>> [考试异常处理任务] 找不到 TaskUserEntity,id: {}", examRecord.getTaskUserId()); + return; + } + + // 获取考试任务的时长配置 + TaskEntity task = taskMapper.selectById(taskUser.getTaskId()); + + if (task == null) { + log.warn(">>> [考试异常处理任务] 找不到 TaskEntity,id: {}", taskUser.getTaskId()); + return; + } + + // 计算考试结束时间 = 开始时间 + 考试时长(分钟) + Date examEndTime = new Date(examRecord.getStartTime().getTime() + (long) task.getDuration() * 60 * 1000); + + // 计算考试结束时间 + 缓冲时间 + Date examTimeoutThreshold = new Date(examEndTime.getTime() + EXAM_TIMEOUT_BUFFER_SECONDS * 1000); + + // 判断考试是否已经超时(超过缓冲时间) + if (examTimeoutThreshold.after(now)) { + log.debug(">>> [考试异常处理任务] 考试记录 {} 的考试时间未超过缓冲时间,跳过。考试结束时间: {},缓冲阈值: {},当前时间: {}", + examRecord.getId(), examEndTime, examTimeoutThreshold, now); + return; + } + + log.info(">>> [考试异常处理任务] 考试记录 {} 已超时 {} 秒以上,将其设置为未通过。考试开始: {},时长: {} 分钟,结束: {},当前: {}", + examRecord.getId(), EXAM_TIMEOUT_BUFFER_SECONDS, examRecord.getStartTime(), task.getDuration(), examEndTime, now); + + // 更新未提交的考试记录 - 使用条件更新防止并发问题 + // 只更新提交时间仍为null的记录,防止用户刚提交时的竞态条件 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ExamRecordEntity::getId, examRecord.getId()) + .isNull(ExamRecordEntity::getSubmitTime) // 再次检查提交时间是否为null + .set(ExamRecordEntity::getPass, false) // 设置为未通过 + .set(ExamRecordEntity::getSubmitTime, examEndTime); // 提交时间设置为考试结束时间 + + int updateCount = examRecordMapper.update(null, updateWrapper); + + if (updateCount > 0) { + log.info(">>> [考试异常处理任务] 考试记录 {} 更新成功", examRecord.getId()); + } else { + log.warn(">>> [考试异常处理任务] 考试记录 {} 更新失败,可能已被其他操作修改(如用户已提交)", examRecord.getId()); + } + + } catch (Exception e) { + log.error(">>> [考试异常处理任务] 处理考试记录 {} 异常", examRecord.getId(), e); + } + } +}