From dd080bd928f1e708126685ac2d696a4a413f7e1d Mon Sep 17 00:00:00 2001 From: luogw <3132758203@qq.com> Date: Wed, 1 Jul 2026 19:04:48 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E7=AD=94=E9=A2=98=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StatisticsApplicationServiceImpl.java | 1 - .../entity/ErrorProneStatisticsEntity.java | 2 +- ...ErrorProneStatisticsDomainServiceImpl.java | 99 ++++++++++++++----- ...ErrorProneStatisticsDomainServiceImpl.java | 1 + 4 files changed, 75 insertions(+), 28 deletions(-) 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 e9f8fd4..2c1da94 100644 --- a/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java +++ b/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java @@ -1,7 +1,6 @@ package com.project.statistics.application.impl; import com.project.base.domain.result.Result; -import com.project.exam.application.ExamRecordApplicationService; import com.project.exam.domain.dto.ExamRecordDTO; import com.project.exam.domain.entity.ExamRecordEntity; import com.project.exam.domain.service.GetDetailExamRecordDomainService; diff --git a/src/main/java/com/project/statistics/domain/entity/ErrorProneStatisticsEntity.java b/src/main/java/com/project/statistics/domain/entity/ErrorProneStatisticsEntity.java index 3609576..9df886a 100644 --- a/src/main/java/com/project/statistics/domain/entity/ErrorProneStatisticsEntity.java +++ b/src/main/java/com/project/statistics/domain/entity/ErrorProneStatisticsEntity.java @@ -21,7 +21,7 @@ import java.util.function.Supplier; @Data -@Table(name = "evaluator_error_prone_statistics") +@Table(name = "evaluator_error_prone_statistics",indexes = {@Index(name = "idx_task_id", columnList = "task_id")}) @Entity @TableName(value = "evaluator_error_prone_statistics", autoResultMap = true) @EqualsAndHashCode(callSuper = true) 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 53afc2e..2fc833e 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 @@ -1,7 +1,9 @@ package com.project.statistics.domain.service.impl; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import com.project.exam.domain.entity.ExamRecordEntity; import com.project.question.domain.entity.QuestionEntity; import com.project.question.domain.entity.TaskKnowledgePointEntity; @@ -10,8 +12,10 @@ 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.entity.TaskEntity; import com.project.task.domain.enums.ExamModeEnum; import com.project.task.domain.enums.QuestionTypeEnum; +import com.project.task.domain.service.TaskBaseService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; @@ -23,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @Service @Slf4j @@ -37,14 +42,15 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP @Autowired private TaskKnowledgePointBaseService taskKnowledgePointBaseService; + @Autowired + private TaskBaseService taskBaseService; + @Autowired private StringRedisTemplate redisTemplate; private static final String LOCK_PREFIX = "lock:error_prone:"; // 锁自动过期时间5秒,防止死锁(拿到锁后程序崩溃没释放,5s自动失效) private static final int LOCK_EXPIRE_SECONDS = 5; - // 自旋最大重试次数:最多抢锁10次 - private static final int LOCK_RETRY_TIMES = 10; // 每次抢锁失败后,休眠50ms再重试 private static final long LOCK_RETRY_INTERVAL_MS = 50; @@ -55,11 +61,18 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP return; } + TaskEntity task = taskBaseService.getById(taskId); + if (task == null){ + throw new IllegalArgumentException("考试任务不存在,taskId: " + taskId); + } + try { + Double shortAnswerScore = task.getShortAnswerScore(); + if (ExamModeEnum.GENERATIVE.getValue().equals(examMode)) { - collectGenerativeStatistics(taskId, snapshots); + collectGenerativeStatistics(taskId, shortAnswerScore, snapshots); } else if (ExamModeEnum.CLASSIC.getValue().equals(examMode)) { - collectClassicStatistics(taskId, snapshots); + collectClassicStatistics(taskId, shortAnswerScore, snapshots); } } catch (Exception e) { log.error("[易错统计] 收集失败, taskId={}", taskId, e); @@ -69,12 +82,8 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP /** * 生成式套题:以知识点为单位统计 */ - private void collectGenerativeStatistics(Long taskId, List snapshots) { + private void collectGenerativeStatistics(Long taskId, Double shortAnswerScore, List snapshots) { for (ExamRecordEntity.QuestionSnapshot snapshot : snapshots) { - // 跳过简答题 - if (isShortAnswer(snapshot.getType())) { - continue; - } Long questionId = snapshot.getQuestionId(); if (questionId == null) { continue; @@ -123,7 +132,7 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP } entity.setQuestionIdList(questionIdList); entity.setAppearCount(entity.getAppearCount() + 1); - if (Boolean.FALSE.equals(snapshot.getIsRight())) { + if (isErrorQuestion(snapshot, shortAnswerScore)) { entity.setErrorCount(entity.getErrorCount() + 1); } @@ -138,18 +147,15 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP /** * 经典套题:以题目为单位统计,并统计选项选择情况 */ - private void collectClassicStatistics(Long taskId, List snapshots) { + private void collectClassicStatistics(Long taskId, Double shortAnswerScore, List snapshots) { for (ExamRecordEntity.QuestionSnapshot snapshot : snapshots) { - // 跳过简答题 - if (isShortAnswer(snapshot.getType())) { - continue; - } - String content = snapshot.getQuestionContent(); if (StrUtil.isBlank(content)) { continue; } + QuestionEntity question = questionBaseService.getById(snapshot.getQuestionId()); + String lockKey = LOCK_PREFIX + "classic:" + taskId + ":" + snapshot.getQuestionId(); boolean locked = tryLock(lockKey); if (!locked) { @@ -173,15 +179,22 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP entity.setQuestionType(snapshot.getType()); entity.setErrorCount(0); entity.setAppearCount(0); - entity.setOptionDetails(buildOptionDetail(snapshot)); + //构建题目详细信息 + List optionDetails; + if (isShortAnswer(snapshot.getType())) { + optionDetails = buildOptionDetail(question.getQuestionDetail()); + } else { + optionDetails = buildOptionDetail(snapshot); + } + entity.setOptionDetails(optionDetails); } entity.setAppearCount(entity.getAppearCount() + 1); - if (Boolean.FALSE.equals(snapshot.getIsRight())) { + if (isErrorQuestion(snapshot, shortAnswerScore)) { entity.setErrorCount(entity.getErrorCount() + 1); } - updateOptionDetail(entity, snapshot.getUserAnswer()); + updateOptionDetail(entity, snapshot); errorProneStatisticsBaseService.saveOrUpdate(entity); } finally { @@ -194,13 +207,11 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP * 尝试获取 Redis 分布式锁(带自旋重试) */ private boolean tryLock(String lockKey) { - int retry = 0; - while (retry < LOCK_RETRY_TIMES) { + while (true) { Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { return true; } - retry++; try { Thread.sleep(LOCK_RETRY_INTERVAL_MS); } catch (InterruptedException e) { @@ -208,7 +219,6 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP return false; } } - return false; } /** @@ -219,7 +229,27 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP } /** - * 构建选项详情 + * 构建简答题得分点详情 + */ + private List buildOptionDetail(QuestionEntity.QuestionDetail questionDetail) { + List list = new ArrayList<>(); + List scoringPoints = questionDetail.getScoringPoints(); + if (CollectionUtil.isEmpty(scoringPoints)) { + return list; + } + + for (QuestionEntity.ScoringPoint scoringPoint : scoringPoints) { + ErrorProneStatisticsEntity.OptionDetail od = new ErrorProneStatisticsEntity.OptionDetail(); + od.setOption(scoringPoint.getIndex().toString()); + od.setOptionContent(scoringPoint.getContent()); + od.setChoosenCount(0); + list.add(od); + } + return list; + } + + /** + * 构建客观题选项详情 */ private List buildOptionDetail(ExamRecordEntity.QuestionSnapshot snapshot) { List list = new ArrayList<>(); @@ -244,8 +274,14 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP /** * 更新选项被选择次数 */ - private void updateOptionDetail(ErrorProneStatisticsEntity entity, String userAnswer) { - String normalizedUserAnswer = normalizeAnswer(userAnswer); + private void updateOptionDetail(ErrorProneStatisticsEntity entity, ExamRecordEntity.QuestionSnapshot snapshot) { + String normalizedUserAnswer; + if(isShortAnswer(snapshot.getType())){ + List hitPoints = snapshot.getHitPoints(); + normalizedUserAnswer = CollectionUtil.isEmpty(hitPoints) ? "" : hitPoints.stream().map(String::valueOf).collect(Collectors.joining(",")); + }else{ + normalizedUserAnswer = normalizeAnswer(snapshot.getUserAnswer()); + } if (StrUtil.isBlank(normalizedUserAnswer)) { return; } @@ -278,4 +314,15 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP private boolean isShortAnswer(Integer type) { return Objects.equals(type, QuestionTypeEnum.SHORT_ANSWER.getValue()); } + + /** + * 判断是否为错题:客观题按 isRight,简答题按 aiScore 是否得满 + */ + private boolean isErrorQuestion(ExamRecordEntity.QuestionSnapshot snapshot, Double shortAnswerScore) { + if (isShortAnswer(snapshot.getType())) { + Double aiScore = snapshot.getAiScore(); + return aiScore == null || aiScore < shortAnswerScore; + } + return Boolean.FALSE.equals(snapshot.getIsRight()); + } } diff --git a/src/main/java/com/project/statistics/domain/service/impl/SearchErrorProneStatisticsDomainServiceImpl.java b/src/main/java/com/project/statistics/domain/service/impl/SearchErrorProneStatisticsDomainServiceImpl.java index 072d3c4..9e5fd8e 100644 --- a/src/main/java/com/project/statistics/domain/service/impl/SearchErrorProneStatisticsDomainServiceImpl.java +++ b/src/main/java/com/project/statistics/domain/service/impl/SearchErrorProneStatisticsDomainServiceImpl.java @@ -21,6 +21,7 @@ public class SearchErrorProneStatisticsDomainServiceImpl implements SearchErrorP public Result> searchTopByTaskId(Long taskId) { List entityList = errorProneStatisticsBaseService.lambdaQuery() .eq(ErrorProneStatisticsEntity::getTaskId, taskId) + .gt(ErrorProneStatisticsEntity::getErrorCount, 0) .orderByDesc(ErrorProneStatisticsEntity::getErrorCount) .last("LIMIT 10") .list();