diff --git a/src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java b/src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java index 7d43d86..e993007 100644 --- a/src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java +++ b/src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java @@ -9,10 +9,12 @@ import com.project.question.domain.entity.QuestionEntity; import com.project.information.domain.entity.KnowledgePointEntity; import com.project.information.domain.service.KnowledgePointBaseService; import com.project.question.domain.service.QuestionBaseService; +import com.project.task.domain.dto.TaskDTO; import com.project.task.domain.entity.TaskEntity; import com.project.task.domain.enums.QuestionTypeEnum; import com.project.task.domain.enums.TaskUserStatusEnum; import com.project.exam.domain.enums.ExamRecordScoringStatusEnum; +import com.project.statistics.application.StatisticsApplicationService; import com.project.task.mapper.TaskMapper; import com.project.task.mapper.TaskUserMapper; import com.project.task.domain.service.TaskBaseService; @@ -56,6 +58,9 @@ public class ShortAnswerScoringJob { @Autowired private TaskBaseService taskBaseService; + @Autowired + private StatisticsApplicationService statisticsApplicationService; + /** 每批最多处理多少道简答题 */ @Value("${scoring.batch-size:5}") private int batchSize; @@ -126,6 +131,9 @@ public class ShortAnswerScoringJob { recalcTotalScore(record); examRecordMapper.updateById(record); log.info(">>> [简答题评分任务] recordId={} 快照已更新, 总分={}", record.getId(), record.getScore()); + + // 简答题评分完成后,单独统计易错数据 + collectShortAnswerErrorProneStatistics(record, snapshots); } } @@ -207,7 +215,7 @@ public class ShortAnswerScoringJob { hitPoints.add(i); } if (ps.getReason() != null && !ps.getReason().isEmpty()) { - if (comment.length() > 0) { + if (!comment.isEmpty()) { comment.append("; "); } comment.append(ps.getReason()); @@ -233,6 +241,30 @@ public class ShortAnswerScoringJob { } } + /** + * 统计简答题 + */ + private void collectShortAnswerErrorProneStatistics(ExamRecordEntity record, List snapshots) { + try { + Long taskUserId = record.getTaskUserId(); + if (taskUserId == null) { + return; + } + com.project.task.domain.entity.TaskUserEntity tu = taskUserMapper.selectById(taskUserId); + if (tu == null || tu.getTaskId() == null) { + return; + } + TaskEntity task = taskMapper.selectById(tu.getTaskId()); + if (task == null) { + return; + } + + statisticsApplicationService.collectShortAnswerErrorProneStatistics(task.toDTO(TaskDTO::new), snapshots); + } catch (Exception e) { + log.error(">>> [简答题评分任务] 简答题易错统计失败, recordId={}", record.getId(), e); + } + } + private void recalcTotalScore(ExamRecordEntity record) { List snapshots = record.getAnswerSnapshot(); if (snapshots == null || snapshots.isEmpty()) { diff --git a/src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java b/src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java index 0cdc50c..1c7c01b 100644 --- a/src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java +++ b/src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java @@ -142,7 +142,7 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService { // 异步收集易错统计 statisticsApplicationService.collectErrorProneStatistics( - task.getId(), task.getExamMode(), record.getAnswerSnapshot()); + task.toDTO(TaskDTO::new), record.getAnswerSnapshot()); // 构建dto ExamRecordDTO dto = record.toDTO(ExamRecordDTO::new); diff --git a/src/main/java/com/project/statistics/application/StatisticsApplicationService.java b/src/main/java/com/project/statistics/application/StatisticsApplicationService.java index d0e2f35..a473d6d 100644 --- a/src/main/java/com/project/statistics/application/StatisticsApplicationService.java +++ b/src/main/java/com/project/statistics/application/StatisticsApplicationService.java @@ -4,6 +4,7 @@ import com.project.base.domain.result.Result; import com.project.exam.domain.entity.ExamRecordEntity; import com.project.statistics.domain.dto.ErrorProneStatisticsDTO; import com.project.statistics.domain.dto.ExamTaskStatisticsDTO; +import com.project.task.domain.dto.TaskDTO; import java.util.List; @@ -11,7 +12,9 @@ public interface StatisticsApplicationService { Result examTaskStatistics(Long taskId); - void collectErrorProneStatistics(Long id, Integer examMode, List answerSnapshot); + void collectErrorProneStatistics(TaskDTO taskDTO, List answerSnapshot); + + void collectShortAnswerErrorProneStatistics(TaskDTO taskDTO, List answerSnapshot); Result> searchErrorProneTop(Long taskId); diff --git a/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java b/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java index 2c1da94..b9d8f65 100644 --- a/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java +++ b/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java @@ -1,5 +1,6 @@ package com.project.statistics.application.impl; +import cn.hutool.cron.task.Task; import com.project.base.domain.result.Result; import com.project.exam.domain.dto.ExamRecordDTO; import com.project.exam.domain.entity.ExamRecordEntity; @@ -10,6 +11,8 @@ import com.project.statistics.domain.dto.ExamTaskStatisticsDTO; import com.project.statistics.domain.service.SaveErrorProneStatisticsDomainService; import com.project.statistics.domain.service.ExamTaskStatisticsDomainService; import com.project.statistics.domain.service.SearchErrorProneStatisticsDomainService; +import com.project.task.domain.dto.TaskDTO; +import com.project.task.domain.service.TaskBaseService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -26,6 +29,8 @@ public class StatisticsApplicationServiceImpl implements StatisticsApplicationSe private SearchErrorProneStatisticsDomainService searchErrorProneStatisticsDomainService; @Autowired private GetDetailExamRecordDomainService getDetailExamRecordDomainService; + @Autowired + private TaskBaseService taskBaseService; /** * 统计考试任务数据 @@ -39,8 +44,13 @@ public class StatisticsApplicationServiceImpl implements StatisticsApplicationSe * 统计高频易错数据 */ @Override - public void collectErrorProneStatistics(Long id, Integer examMode, List answerSnapshot) { - saveErrorProneStatisticsDomainService.collectErrorProneStatistics(id, examMode, answerSnapshot); + public void collectErrorProneStatistics(TaskDTO taskDTO, List answerSnapshot) { + saveErrorProneStatisticsDomainService.collectErrorProneStatistics(taskDTO, answerSnapshot); + } + + @Override + public void collectShortAnswerErrorProneStatistics(TaskDTO taskDTO, List answerSnapshot) { + saveErrorProneStatisticsDomainService.collectShortAnswerErrorProneStatistics(taskDTO, answerSnapshot); } @Override @@ -52,6 +62,10 @@ public class StatisticsApplicationServiceImpl implements StatisticsApplicationSe public void singleExamStatistics(Long examRecordId) throws Exception { ExamRecordDTO detail = getDetailExamRecordDomainService.getDetail(examRecordId).getData(); ExamRecordEntity entity = detail.toEntity(ExamRecordEntity::new); - saveErrorProneStatisticsDomainService.collectErrorProneStatistics(detail.getTaskId(), 1, entity.getAnswerSnapshot()); + TaskDTO taskDTO = taskBaseService.getById(detail.getTaskId()).toDTO(TaskDTO::new); + //统计客观题 + saveErrorProneStatisticsDomainService.collectErrorProneStatistics(taskDTO, entity.getAnswerSnapshot()); + //统计简答题 + saveErrorProneStatisticsDomainService.collectShortAnswerErrorProneStatistics(taskDTO, entity.getAnswerSnapshot()); } } diff --git a/src/main/java/com/project/statistics/domain/service/SaveErrorProneStatisticsDomainService.java b/src/main/java/com/project/statistics/domain/service/SaveErrorProneStatisticsDomainService.java index a74155b..b9eef2e 100644 --- a/src/main/java/com/project/statistics/domain/service/SaveErrorProneStatisticsDomainService.java +++ b/src/main/java/com/project/statistics/domain/service/SaveErrorProneStatisticsDomainService.java @@ -1,17 +1,25 @@ package com.project.statistics.domain.service; import com.project.exam.domain.entity.ExamRecordEntity; +import com.project.task.domain.dto.TaskDTO; import java.util.List; public interface SaveErrorProneStatisticsDomainService { /** - * 异步收集易错统计 + * 异步收集易错统计(仅客观题,提交时调用) * - * @param taskId 考试任务ID - * @param examMode 考试模式 + * @param taskDTO 考试任务 * @param snapshots 答题快照 */ - void collectErrorProneStatistics(Long taskId, Integer examMode, List snapshots); + void collectErrorProneStatistics(TaskDTO taskDTO, List snapshots); + + /** + * 异步收集简答题易错统计(简答题评分完成后调用) + * + * @param taskDTO 考试任务 + * @param snapshots 已评分的简答题快照 + */ + void collectShortAnswerErrorProneStatistics(TaskDTO taskDTO, List snapshots); } diff --git a/src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java b/src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java index 36ce9e5..f96fe03 100644 --- a/src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java +++ b/src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java @@ -2,6 +2,7 @@ package com.project.statistics.domain.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.project.exam.domain.entity.ExamRecordEntity; import com.project.question.domain.entity.QuestionEntity; @@ -11,6 +12,7 @@ import com.project.question.domain.service.TaskKnowledgePointBaseService; import com.project.statistics.domain.entity.ErrorProneStatisticsEntity; import com.project.statistics.domain.service.ErrorProneStatisticsBaseService; import com.project.statistics.domain.service.SaveErrorProneStatisticsDomainService; +import com.project.task.domain.dto.TaskDTO; import com.project.task.domain.entity.TaskEntity; import com.project.task.domain.enums.ExamModeEnum; import com.project.task.domain.enums.QuestionTypeEnum; @@ -26,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.stream.Collectors; @Service @@ -41,9 +44,6 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP @Autowired private TaskKnowledgePointBaseService taskKnowledgePointBaseService; - @Autowired - private TaskBaseService taskBaseService; - @Autowired private StringRedisTemplate redisTemplate; @@ -53,28 +53,44 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP // 每次抢锁失败后,休眠50ms再重试 private static final long LOCK_RETRY_INTERVAL_MS = 50; + /** + * 收集客观题易错统计(试卷提交时调用,此时简答题尚未评分) + */ @Override @Async - public void collectErrorProneStatistics(Long taskId, Integer examMode, List snapshots) { - if (taskId == null || CollUtil.isEmpty(snapshots)) { + public void collectErrorProneStatistics(TaskDTO taskDTO, List snapshots) { + collectByFilter(taskDTO, snapshots, this::isNotShortAnswer); + } + + /** + * 收集简答题易错统计(简答题评分完成后调用) + */ + @Override + @Async + public void collectShortAnswerErrorProneStatistics(TaskDTO taskDTO, List snapshots) { + collectByFilter(taskDTO, snapshots, this::isShortAnswer); + } + + private void collectByFilter(TaskDTO taskDTO, List snapshots, Predicate filter) { + if (ObjectUtil.isNull(taskDTO) || CollUtil.isEmpty(snapshots)) { return; } - TaskEntity task = taskBaseService.getById(taskId); - if (Objects.isNull(task)){ - throw new IllegalArgumentException("考试任务不存在,taskId: " + taskId); + List filtered = snapshots.stream().filter(filter).collect(Collectors.toList()); + if (CollUtil.isEmpty(filtered)) { + return; } try { - Double shortAnswerScore = task.getShortAnswerScore(); + Double shortAnswerScore = taskDTO.getShortAnswerScore(); - if (ExamModeEnum.GENERATIVE.getValue().equals(examMode)) { - collectGenerativeStatistics(taskId, shortAnswerScore, snapshots); - } else if (ExamModeEnum.CLASSIC.getValue().equals(examMode)) { - collectClassicStatistics(taskId, shortAnswerScore, snapshots); + if (ExamModeEnum.GENERATIVE.getValue().equals(taskDTO.getExamMode())) { + collectGenerativeStatistics(taskDTO.getId(), shortAnswerScore, filtered); + } else if (ExamModeEnum.CLASSIC.getValue().equals(taskDTO.getExamMode())) { + collectClassicStatistics(taskDTO.getId(), shortAnswerScore, filtered); } } catch (Exception e) { - log.error("[易错统计] 收集失败, taskId={}", taskId, e); + log.error("[易错统计] 收集失败, taskId={}", taskDTO.getId(), e); } } @@ -180,7 +196,7 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP entity.setAppearCount(0); //构建题目详细信息 List optionDetails; - if (isShortAnswer(snapshot.getType())) { + if (isShortAnswer(snapshot)) { optionDetails = buildOptionDetail(question.getQuestionDetail()); } else { optionDetails = buildOptionDetail(snapshot); @@ -275,7 +291,7 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP */ private void updateOptionDetail(ErrorProneStatisticsEntity entity, ExamRecordEntity.QuestionSnapshot snapshot) { String normalizedUserAnswer; - if(isShortAnswer(snapshot.getType())){ + if (isShortAnswer(snapshot)) { List hitPoints = snapshot.getHitPoints(); normalizedUserAnswer = CollectionUtil.isEmpty(hitPoints) ? "" : hitPoints.stream().map(String::valueOf).collect(Collectors.joining(",")); }else{ @@ -310,15 +326,22 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP /** * 是否为简答题 */ - private boolean isShortAnswer(Integer type) { - return Objects.equals(type, QuestionTypeEnum.SHORT_ANSWER.getValue()); + private boolean isShortAnswer(ExamRecordEntity.QuestionSnapshot snapshot) { + return Objects.equals(snapshot.getType(), QuestionTypeEnum.SHORT_ANSWER.getValue()); + } + + /** + * 是否为非简答题 + */ + private boolean isNotShortAnswer(ExamRecordEntity.QuestionSnapshot snapshot) { + return !isShortAnswer(snapshot); } /** * 判断是否为错题:客观题按 isRight,简答题按 aiScore 是否得满 */ private boolean isErrorQuestion(ExamRecordEntity.QuestionSnapshot snapshot, Double shortAnswerScore) { - if (isShortAnswer(snapshot.getType())) { + if (isShortAnswer(snapshot)) { Double aiScore = snapshot.getAiScore(); return aiScore == null || aiScore < shortAnswerScore; }