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;
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;

2
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)

99
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<ExamRecordEntity.QuestionSnapshot> snapshots) {
private void collectGenerativeStatistics(Long taskId, Double shortAnswerScore, List<ExamRecordEntity.QuestionSnapshot> 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<ExamRecordEntity.QuestionSnapshot> snapshots) {
private void collectClassicStatistics(Long taskId, Double shortAnswerScore, List<ExamRecordEntity.QuestionSnapshot> 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<ErrorProneStatisticsEntity.OptionDetail> 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<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) {
List<ErrorProneStatisticsEntity.OptionDetail> 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<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)) {
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());
}
}

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) {
List<ErrorProneStatisticsEntity> entityList = errorProneStatisticsBaseService.lambdaQuery()
.eq(ErrorProneStatisticsEntity::getTaskId, taskId)
.gt(ErrorProneStatisticsEntity::getErrorCount, 0)
.orderByDesc(ErrorProneStatisticsEntity::getErrorCount)
.last("LIMIT 10")
.list();

Loading…
Cancel
Save