Browse Source

简答题统计

master
luogw 2 months ago
parent
commit
dd080bd928
  1. 1
      src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java
  2. 2
      src/main/java/com/project/statistics/domain/entity/ErrorProneStatisticsEntity.java
  3. 99
      src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java
  4. 1
      src/main/java/com/project/statistics/domain/service/impl/SearchErrorProneStatisticsDomainServiceImpl.java

1
src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java

@ -1,7 +1,6 @@
package com.project.statistics.application.impl; package com.project.statistics.application.impl;
import com.project.base.domain.result.Result; 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.dto.ExamRecordDTO;
import com.project.exam.domain.entity.ExamRecordEntity; import com.project.exam.domain.entity.ExamRecordEntity;
import com.project.exam.domain.service.GetDetailExamRecordDomainService; import com.project.exam.domain.service.GetDetailExamRecordDomainService;

2
src/main/java/com/project/statistics/domain/entity/ErrorProneStatisticsEntity.java

@ -21,7 +21,7 @@ import java.util.function.Supplier;
@Data @Data
@Table(name = "evaluator_error_prone_statistics") @Table(name = "evaluator_error_prone_statistics",indexes = {@Index(name = "idx_task_id", columnList = "task_id")})
@Entity @Entity
@TableName(value = "evaluator_error_prone_statistics", autoResultMap = true) @TableName(value = "evaluator_error_prone_statistics", autoResultMap = true)
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

99
src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java

@ -1,7 +1,9 @@
package com.project.statistics.domain.service.impl; package com.project.statistics.domain.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.project.exam.domain.entity.ExamRecordEntity; import com.project.exam.domain.entity.ExamRecordEntity;
import com.project.question.domain.entity.QuestionEntity; import com.project.question.domain.entity.QuestionEntity;
import com.project.question.domain.entity.TaskKnowledgePointEntity; 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.entity.ErrorProneStatisticsEntity;
import com.project.statistics.domain.service.ErrorProneStatisticsBaseService; import com.project.statistics.domain.service.ErrorProneStatisticsBaseService;
import com.project.statistics.domain.service.SaveErrorProneStatisticsDomainService; 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.ExamModeEnum;
import com.project.task.domain.enums.QuestionTypeEnum; import com.project.task.domain.enums.QuestionTypeEnum;
import com.project.task.domain.service.TaskBaseService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
@ -23,6 +27,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@ -37,14 +42,15 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
@Autowired @Autowired
private TaskKnowledgePointBaseService taskKnowledgePointBaseService; private TaskKnowledgePointBaseService taskKnowledgePointBaseService;
@Autowired
private TaskBaseService taskBaseService;
@Autowired @Autowired
private StringRedisTemplate redisTemplate; private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "lock:error_prone:"; private static final String LOCK_PREFIX = "lock:error_prone:";
// 锁自动过期时间5秒,防止死锁(拿到锁后程序崩溃没释放,5s自动失效) // 锁自动过期时间5秒,防止死锁(拿到锁后程序崩溃没释放,5s自动失效)
private static final int LOCK_EXPIRE_SECONDS = 5; private static final int LOCK_EXPIRE_SECONDS = 5;
// 自旋最大重试次数:最多抢锁10次
private static final int LOCK_RETRY_TIMES = 10;
// 每次抢锁失败后,休眠50ms再重试 // 每次抢锁失败后,休眠50ms再重试
private static final long LOCK_RETRY_INTERVAL_MS = 50; private static final long LOCK_RETRY_INTERVAL_MS = 50;
@ -55,11 +61,18 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
return; return;
} }
TaskEntity task = taskBaseService.getById(taskId);
if (task == null){
throw new IllegalArgumentException("考试任务不存在,taskId: " + taskId);
}
try { try {
Double shortAnswerScore = task.getShortAnswerScore();
if (ExamModeEnum.GENERATIVE.getValue().equals(examMode)) { if (ExamModeEnum.GENERATIVE.getValue().equals(examMode)) {
collectGenerativeStatistics(taskId, snapshots); collectGenerativeStatistics(taskId, shortAnswerScore, snapshots);
} else if (ExamModeEnum.CLASSIC.getValue().equals(examMode)) { } else if (ExamModeEnum.CLASSIC.getValue().equals(examMode)) {
collectClassicStatistics(taskId, snapshots); collectClassicStatistics(taskId, shortAnswerScore, snapshots);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[易错统计] 收集失败, taskId={}", taskId, e); log.error("[易错统计] 收集失败, taskId={}", taskId, e);
@ -69,12 +82,8 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
/** /**
* 生成式套题以知识点为单位统计 * 生成式套题以知识点为单位统计
*/ */
private void collectGenerativeStatistics(Long taskId, List<ExamRecordEntity.QuestionSnapshot> snapshots) { private void collectGenerativeStatistics(Long taskId, Double shortAnswerScore, List<ExamRecordEntity.QuestionSnapshot> snapshots) {
for (ExamRecordEntity.QuestionSnapshot snapshot : snapshots) { for (ExamRecordEntity.QuestionSnapshot snapshot : snapshots) {
// 跳过简答题
if (isShortAnswer(snapshot.getType())) {
continue;
}
Long questionId = snapshot.getQuestionId(); Long questionId = snapshot.getQuestionId();
if (questionId == null) { if (questionId == null) {
continue; continue;
@ -123,7 +132,7 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
} }
entity.setQuestionIdList(questionIdList); entity.setQuestionIdList(questionIdList);
entity.setAppearCount(entity.getAppearCount() + 1); entity.setAppearCount(entity.getAppearCount() + 1);
if (Boolean.FALSE.equals(snapshot.getIsRight())) { if (isErrorQuestion(snapshot, shortAnswerScore)) {
entity.setErrorCount(entity.getErrorCount() + 1); entity.setErrorCount(entity.getErrorCount() + 1);
} }
@ -138,18 +147,15 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
/** /**
* 经典套题以题目为单位统计并统计选项选择情况 * 经典套题以题目为单位统计并统计选项选择情况
*/ */
private void collectClassicStatistics(Long taskId, List<ExamRecordEntity.QuestionSnapshot> snapshots) { private void collectClassicStatistics(Long taskId, Double shortAnswerScore, List<ExamRecordEntity.QuestionSnapshot> snapshots) {
for (ExamRecordEntity.QuestionSnapshot snapshot : snapshots) { for (ExamRecordEntity.QuestionSnapshot snapshot : snapshots) {
// 跳过简答题
if (isShortAnswer(snapshot.getType())) {
continue;
}
String content = snapshot.getQuestionContent(); String content = snapshot.getQuestionContent();
if (StrUtil.isBlank(content)) { if (StrUtil.isBlank(content)) {
continue; continue;
} }
QuestionEntity question = questionBaseService.getById(snapshot.getQuestionId());
String lockKey = LOCK_PREFIX + "classic:" + taskId + ":" + snapshot.getQuestionId(); String lockKey = LOCK_PREFIX + "classic:" + taskId + ":" + snapshot.getQuestionId();
boolean locked = tryLock(lockKey); boolean locked = tryLock(lockKey);
if (!locked) { if (!locked) {
@ -173,15 +179,22 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
entity.setQuestionType(snapshot.getType()); entity.setQuestionType(snapshot.getType());
entity.setErrorCount(0); entity.setErrorCount(0);
entity.setAppearCount(0); entity.setAppearCount(0);
entity.setOptionDetails(buildOptionDetail(snapshot)); //构建题目详细信息
List<ErrorProneStatisticsEntity.OptionDetail> optionDetails;
if (isShortAnswer(snapshot.getType())) {
optionDetails = buildOptionDetail(question.getQuestionDetail());
} else {
optionDetails = buildOptionDetail(snapshot);
}
entity.setOptionDetails(optionDetails);
} }
entity.setAppearCount(entity.getAppearCount() + 1); entity.setAppearCount(entity.getAppearCount() + 1);
if (Boolean.FALSE.equals(snapshot.getIsRight())) { if (isErrorQuestion(snapshot, shortAnswerScore)) {
entity.setErrorCount(entity.getErrorCount() + 1); entity.setErrorCount(entity.getErrorCount() + 1);
} }
updateOptionDetail(entity, snapshot.getUserAnswer()); updateOptionDetail(entity, snapshot);
errorProneStatisticsBaseService.saveOrUpdate(entity); errorProneStatisticsBaseService.saveOrUpdate(entity);
} finally { } finally {
@ -194,13 +207,11 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
* 尝试获取 Redis 分布式锁带自旋重试 * 尝试获取 Redis 分布式锁带自旋重试
*/ */
private boolean tryLock(String lockKey) { private boolean tryLock(String lockKey) {
int retry = 0; while (true) {
while (retry < LOCK_RETRY_TIMES) {
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS); Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) { if (Boolean.TRUE.equals(locked)) {
return true; return true;
} }
retry++;
try { try {
Thread.sleep(LOCK_RETRY_INTERVAL_MS); Thread.sleep(LOCK_RETRY_INTERVAL_MS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -208,7 +219,6 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
return false; return false;
} }
} }
return false;
} }
/** /**
@ -219,7 +229,27 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
} }
/** /**
* 构建选项详情 * 构建简答题得分点详情
*/
private List<ErrorProneStatisticsEntity.OptionDetail> buildOptionDetail(QuestionEntity.QuestionDetail questionDetail) {
List<ErrorProneStatisticsEntity.OptionDetail> list = new ArrayList<>();
List<QuestionEntity.ScoringPoint> 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<ErrorProneStatisticsEntity.OptionDetail> buildOptionDetail(ExamRecordEntity.QuestionSnapshot snapshot) { private List<ErrorProneStatisticsEntity.OptionDetail> buildOptionDetail(ExamRecordEntity.QuestionSnapshot snapshot) {
List<ErrorProneStatisticsEntity.OptionDetail> list = new ArrayList<>(); List<ErrorProneStatisticsEntity.OptionDetail> list = new ArrayList<>();
@ -244,8 +274,14 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
/** /**
* 更新选项被选择次数 * 更新选项被选择次数
*/ */
private void updateOptionDetail(ErrorProneStatisticsEntity entity, String userAnswer) { private void updateOptionDetail(ErrorProneStatisticsEntity entity, ExamRecordEntity.QuestionSnapshot snapshot) {
String normalizedUserAnswer = normalizeAnswer(userAnswer); String normalizedUserAnswer;
if(isShortAnswer(snapshot.getType())){
List<Integer> hitPoints = snapshot.getHitPoints();
normalizedUserAnswer = CollectionUtil.isEmpty(hitPoints) ? "" : hitPoints.stream().map(String::valueOf).collect(Collectors.joining(","));
}else{
normalizedUserAnswer = normalizeAnswer(snapshot.getUserAnswer());
}
if (StrUtil.isBlank(normalizedUserAnswer)) { if (StrUtil.isBlank(normalizedUserAnswer)) {
return; return;
} }
@ -278,4 +314,15 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
private boolean isShortAnswer(Integer type) { private boolean isShortAnswer(Integer type) {
return Objects.equals(type, QuestionTypeEnum.SHORT_ANSWER.getValue()); 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());
}
} }

1
src/main/java/com/project/statistics/domain/service/impl/SearchErrorProneStatisticsDomainServiceImpl.java

@ -21,6 +21,7 @@ public class SearchErrorProneStatisticsDomainServiceImpl implements SearchErrorP
public Result<List<ErrorProneStatisticsDTO>> searchTopByTaskId(Long taskId) { public Result<List<ErrorProneStatisticsDTO>> searchTopByTaskId(Long taskId) {
List<ErrorProneStatisticsEntity> entityList = errorProneStatisticsBaseService.lambdaQuery() List<ErrorProneStatisticsEntity> entityList = errorProneStatisticsBaseService.lambdaQuery()
.eq(ErrorProneStatisticsEntity::getTaskId, taskId) .eq(ErrorProneStatisticsEntity::getTaskId, taskId)
.gt(ErrorProneStatisticsEntity::getErrorCount, 0)
.orderByDesc(ErrorProneStatisticsEntity::getErrorCount) .orderByDesc(ErrorProneStatisticsEntity::getErrorCount)
.last("LIMIT 10") .last("LIMIT 10")
.list(); .list();

Loading…
Cancel
Save