|
|
@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
import com.project.base.domain.exception.BusinessErrorException; |
|
|
import com.project.base.domain.exception.BusinessErrorException; |
|
|
import com.project.classicpaper.domain.entity.ClassicPaperSetEntity; |
|
|
import com.project.classicpaper.domain.entity.ClassicPaperSetEntity; |
|
|
import com.project.classicpaper.domain.service.ClassicPaperSetBaseService; |
|
|
import com.project.classicpaper.domain.service.ClassicPaperSetBaseService; |
|
|
|
|
|
import com.project.task.config.ExamScoreRatioConfig; |
|
|
import com.project.task.domain.dto.TaskDTO; |
|
|
import com.project.task.domain.dto.TaskDTO; |
|
|
import com.project.task.domain.entity.TaskEntity; |
|
|
import com.project.task.domain.entity.TaskEntity; |
|
|
import com.project.task.domain.service.TaskBaseService; |
|
|
import com.project.task.domain.service.TaskBaseService; |
|
|
@ -35,6 +36,9 @@ public class ClassicTaskConfigStrategy implements TaskConfigStrategy { |
|
|
@Autowired |
|
|
@Autowired |
|
|
private ClassicPaperSetBaseService classicPaperSetBaseService; |
|
|
private ClassicPaperSetBaseService classicPaperSetBaseService; |
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
private ExamScoreRatioConfig examScoreRatioConfig; |
|
|
|
|
|
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper(); |
|
|
private final ObjectMapper objectMapper = new ObjectMapper(); |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
@ -97,25 +101,35 @@ public class ClassicTaskConfigStrategy implements TaskConfigStrategy { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 经典模式下题目数量和分数来自套卷,DTO 中可能为 null,赋默认值避免 NPE |
|
|
* 经典模式下题目数量和分数来自套卷,DTO 中可能为 null,从纸套组补全以免写死 0 |
|
|
*/ |
|
|
*/ |
|
|
private void fillDefaults(TaskDTO dto) { |
|
|
private void fillDefaults(TaskDTO dto) { |
|
|
|
|
|
// 1. 从纸套组读取实际题量(如果 DTO 未传)
|
|
|
|
|
|
ClassicPaperSetEntity paperSet = null; |
|
|
|
|
|
if (dto.getClassicPaperSetId() != null) { |
|
|
|
|
|
paperSet = classicPaperSetBaseService.getById(dto.getClassicPaperSetId()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. 补全题量:DTO 没传就从纸套取,纸套也没有就给 0
|
|
|
if (dto.getSingleChoiceNum() == null) { |
|
|
if (dto.getSingleChoiceNum() == null) { |
|
|
dto.setSingleChoiceNum(0); |
|
|
dto.setSingleChoiceNum(paperSet != null && paperSet.getSingleChoiceNum() != null ? paperSet.getSingleChoiceNum() : 0); |
|
|
} |
|
|
} |
|
|
if (dto.getMultipleChoiceNum() == null) { |
|
|
if (dto.getMultipleChoiceNum() == null) { |
|
|
dto.setMultipleChoiceNum(0); |
|
|
dto.setMultipleChoiceNum(paperSet != null && paperSet.getMultipleChoiceNum() != null ? paperSet.getMultipleChoiceNum() : 0); |
|
|
} |
|
|
} |
|
|
if (dto.getTrueFalseNum() == null) { |
|
|
if (dto.getTrueFalseNum() == null) { |
|
|
dto.setTrueFalseNum(0); |
|
|
dto.setTrueFalseNum(paperSet != null && paperSet.getTrueFalseNum() != null ? paperSet.getTrueFalseNum() : 0); |
|
|
} |
|
|
} |
|
|
if (dto.getShortAnswerNum() == null) { |
|
|
if (dto.getShortAnswerNum() == null) { |
|
|
dto.setShortAnswerNum(0); |
|
|
dto.setShortAnswerNum(paperSet != null && paperSet.getShortAnswerNum() != null ? paperSet.getShortAnswerNum() : 0); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 从套题组的分值比例计算每题分值(保留2位小数)
|
|
|
// 3. 从纸套比例算分(传入 paperSet 避免重复查询)
|
|
|
calculateScoresFromRatio(dto); |
|
|
if (paperSet != null) { |
|
|
|
|
|
calculateScoresFromRatio(dto, paperSet); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 4. 补全分数:算分后仍为 null 才给 0
|
|
|
if (dto.getSingleChoiceScore() == null) { |
|
|
if (dto.getSingleChoiceScore() == null) { |
|
|
dto.setSingleChoiceScore(0.0); |
|
|
dto.setSingleChoiceScore(0.0); |
|
|
} |
|
|
} |
|
|
@ -142,39 +156,72 @@ public class ClassicTaskConfigStrategy implements TaskConfigStrategy { |
|
|
/** |
|
|
/** |
|
|
* 从 ClassicPaperSet.scoreRatio 解析比例,计算每题分值 |
|
|
* 从 ClassicPaperSet.scoreRatio 解析比例,计算每题分值 |
|
|
* 公式:每题分值 = 100 × 题型比例 / 总比例 / 该题型数量 |
|
|
* 公式:每题分值 = 100 × 题型比例 / 总比例 / 该题型数量 |
|
|
|
|
|
* 条件判断用 DTO 的题量(已在 fillDefaults 中从纸套补全),确保与任务实际题量一致 |
|
|
*/ |
|
|
*/ |
|
|
private void calculateScoresFromRatio(TaskDTO dto) { |
|
|
private void calculateScoresFromRatio(TaskDTO dto, ClassicPaperSetEntity paperSet) { |
|
|
if (dto.getClassicPaperSetId() == null) { |
|
|
if (StrUtil.isBlank(paperSet.getScoreRatio())) { |
|
|
return; |
|
|
// 纸套没有分值比例时,用配置默认权重计算(同生成式模式逻辑)
|
|
|
} |
|
|
calculateScoresFromDefaultWeights(dto); |
|
|
ClassicPaperSetEntity paperSet = classicPaperSetBaseService.getById(dto.getClassicPaperSetId()); |
|
|
|
|
|
if (paperSet == null || StrUtil.isBlank(paperSet.getScoreRatio())) { |
|
|
|
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
try { |
|
|
try { |
|
|
Map<String, Integer> ratioMap = objectMapper.readValue(paperSet.getScoreRatio(), Map.class); |
|
|
// JacksonTypeHandler 对 String 字段存 JSON 时会产生二次编码(存为 JSON 字符串),需要反解一次
|
|
|
|
|
|
String scoreRatioStr = paperSet.getScoreRatio(); |
|
|
|
|
|
if (scoreRatioStr.startsWith("\"") && scoreRatioStr.endsWith("\"")) { |
|
|
|
|
|
scoreRatioStr = objectMapper.readValue(scoreRatioStr, String.class); |
|
|
|
|
|
} |
|
|
|
|
|
Map<String, Integer> ratioMap = objectMapper.readValue(scoreRatioStr, Map.class); |
|
|
int totalRatio = ratioMap.values().stream().mapToInt(Integer::intValue).sum(); |
|
|
int totalRatio = ratioMap.values().stream().mapToInt(Integer::intValue).sum(); |
|
|
if (totalRatio <= 0) { |
|
|
if (totalRatio <= 0) { |
|
|
|
|
|
calculateScoresFromDefaultWeights(dto); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
if (paperSet.getSingleChoiceNum() != null && paperSet.getSingleChoiceNum() > 0) { |
|
|
if (dto.getSingleChoiceNum() != null && dto.getSingleChoiceNum() > 0) { |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("single", 0) / totalRatio / paperSet.getSingleChoiceNum(); |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("single", 0) / totalRatio / dto.getSingleChoiceNum(); |
|
|
dto.setSingleChoiceScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
dto.setSingleChoiceScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
} |
|
|
} |
|
|
if (paperSet.getMultipleChoiceNum() != null && paperSet.getMultipleChoiceNum() > 0) { |
|
|
if (dto.getMultipleChoiceNum() != null && dto.getMultipleChoiceNum() > 0) { |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("multiple", 0) / totalRatio / paperSet.getMultipleChoiceNum(); |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("multiple", 0) / totalRatio / dto.getMultipleChoiceNum(); |
|
|
dto.setMultipleChoiceScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
dto.setMultipleChoiceScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
} |
|
|
} |
|
|
if (paperSet.getTrueFalseNum() != null && paperSet.getTrueFalseNum() > 0) { |
|
|
if (dto.getTrueFalseNum() != null && dto.getTrueFalseNum() > 0) { |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("trueFalse", 0) / totalRatio / paperSet.getTrueFalseNum(); |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("trueFalse", 0) / totalRatio / dto.getTrueFalseNum(); |
|
|
dto.setTrueFalseScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
dto.setTrueFalseScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
} |
|
|
} |
|
|
if (paperSet.getShortAnswerNum() != null && paperSet.getShortAnswerNum() > 0) { |
|
|
if (dto.getShortAnswerNum() != null && dto.getShortAnswerNum() > 0) { |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("shortAnswer", 0) / totalRatio / paperSet.getShortAnswerNum(); |
|
|
double perScore = 100.0 * ratioMap.getOrDefault("shortAnswer", 0) / totalRatio / dto.getShortAnswerNum(); |
|
|
dto.setShortAnswerScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
dto.setShortAnswerScore(BigDecimal.valueOf(perScore).setScale(2, RoundingMode.HALF_UP).doubleValue()); |
|
|
} |
|
|
} |
|
|
} catch (Exception e) { |
|
|
} catch (Exception e) { |
|
|
log.warn(">>> [经典模式] scoreRatio 解析失败, paperSetId={}", dto.getClassicPaperSetId(), e); |
|
|
log.warn(">>> [经典模式] scoreRatio 解析失败, paperSetId={}", dto.getClassicPaperSetId(), e); |
|
|
|
|
|
calculateScoresFromDefaultWeights(dto); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 用配置默认权重计算每题分值(降级方案,同生成式模式逻辑) |
|
|
|
|
|
* 公式:base = 总分 / 总权重,每题分值 = base × 题型权重 |
|
|
|
|
|
*/ |
|
|
|
|
|
private void calculateScoresFromDefaultWeights(TaskDTO dto) { |
|
|
|
|
|
int shortAnswerNum = dto.getShortAnswerNum() != null ? dto.getShortAnswerNum() : 0; |
|
|
|
|
|
|
|
|
|
|
|
int totalUnit = dto.getSingleChoiceNum() * examScoreRatioConfig.getSingle() |
|
|
|
|
|
+ dto.getMultipleChoiceNum() * examScoreRatioConfig.getMultiple() |
|
|
|
|
|
+ dto.getTrueFalseNum() * examScoreRatioConfig.getTrueFalse() |
|
|
|
|
|
+ shortAnswerNum * examScoreRatioConfig.getShortAnswer(); |
|
|
|
|
|
if (totalUnit <= 0) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
double base = examScoreRatioConfig.getTotalScore() * 1.0 / totalUnit; |
|
|
|
|
|
|
|
|
|
|
|
dto.setSingleChoiceScore(dto.getSingleChoiceNum() > 0 |
|
|
|
|
|
? Math.round(base * examScoreRatioConfig.getSingle() * 100) / 100.0 : 0.0); |
|
|
|
|
|
dto.setMultipleChoiceScore(dto.getMultipleChoiceNum() > 0 |
|
|
|
|
|
? Math.round(base * examScoreRatioConfig.getMultiple() * 100) / 100.0 : 0.0); |
|
|
|
|
|
dto.setTrueFalseScore(dto.getTrueFalseNum() > 0 |
|
|
|
|
|
? Math.round(base * examScoreRatioConfig.getTrueFalse() * 100) / 100.0 : 0.0); |
|
|
|
|
|
dto.setShortAnswerScore(shortAnswerNum > 0 |
|
|
|
|
|
? Math.round(base * examScoreRatioConfig.getShortAnswer() * 100) / 100.0 : 0.0); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|