Browse Source

bug修复

master
luoweijian 2 weeks ago
parent
commit
e57bc0e995
  1. 3
      src/main/java/com/project/classicpaper/domain/service/impl/ImportClassicPaperDomainServiceImpl.java
  2. 65
      src/main/java/com/project/classicpaper/domain/service/parser/WordQuestionParser.java
  3. 21
      src/main/java/com/project/question/domain/service/impl/QuestionInventoryDomainServiceImpl.java
  4. 6
      src/main/java/com/project/task/controller/TaskController.java

3
src/main/java/com/project/classicpaper/domain/service/impl/ImportClassicPaperDomainServiceImpl.java

@ -188,7 +188,8 @@ public class ImportClassicPaperDomainServiceImpl implements ImportClassicPaperDo
scoringPoints.add(spDTO); scoringPoints.add(spDTO);
} }
detailDTO.setScoringPoints(scoringPoints); detailDTO.setScoringPoints(scoringPoints);
}else if (shortAnswerNum > 0){ } else if (Objects.equals(parsed.getQuestionType(), QuestionTypeEnum.SHORT_ANSWER.getValue())) {
// 仅当前题为简答题且缺少得分点时才报错,其他题型无得分点属正常
throw new BusinessErrorException("导入简答题没有得分点,请补充"); throw new BusinessErrorException("导入简答题没有得分点,请补充");
} }

65
src/main/java/com/project/classicpaper/domain/service/parser/WordQuestionParser.java

@ -26,7 +26,13 @@ public class WordQuestionParser {
private static final Pattern OPTION_PATTERN = Pattern.compile("^[A-Z][..\\u3001::、]\\s*"); private static final Pattern OPTION_PATTERN = Pattern.compile("^[A-Z][..\\u3001::、]\\s*");
private static final Pattern ANSWER_PATTERN = Pattern.compile("^答案[::\\u3001]\\s*(.*)"); private static final Pattern ANSWER_PATTERN = Pattern.compile("^答案[::\\u3001]\\s*(.*)");
private static final Pattern ANALYSIS_PATTERN = Pattern.compile("^解析[::\\u3001]\\s*(.*)"); private static final Pattern ANALYSIS_PATTERN = Pattern.compile("^解析[::\\u3001]\\s*(.*)");
// 难度行:如"难度:中",不属于题干内容,忽略
private static final Pattern DIFFICULTY_PATTERN = Pattern.compile("^难度[::\\u3001]");
private static final Pattern SCORING_POINT_PATTERN = Pattern.compile("^得分点[::\\u3001]\\s*(.*)"); private static final Pattern SCORING_POINT_PATTERN = Pattern.compile("^得分点[::\\u3001]\\s*(.*)");
// 单条得分点行:得分点N. 内容(N 后可为英文点/中文点/顿号)
private static final Pattern SCORING_POINT_ITEM_PATTERN = Pattern.compile("^得分点[0-9]+[..、]");
// 题型标签:[单选题] [多选题] [判断题] [简答题]
private static final Pattern TYPE_TAG_PATTERN = Pattern.compile("\\[[^\\]]*题\\]");
private final MinIoUtils minIoUtils; private final MinIoUtils minIoUtils;
@ -36,6 +42,8 @@ public class WordQuestionParser {
private int imageCounter = 0; private int imageCounter = 0;
/** 是否正在解析得分点段落(得分点触发后,后续段落归入得分点内容) */ /** 是否正在解析得分点段落(得分点触发后,后续段落归入得分点内容) */
private boolean parsingScoringPoints = false; private boolean parsingScoringPoints = false;
/** 是否正在解析简答题参考答案正文(得分点结束后到下一道真题之间的内容) */
private boolean inReferenceAnswer = false;
public WordQuestionParser(MinIoUtils minIoUtils) { public WordQuestionParser(MinIoUtils minIoUtils) {
this.minIoUtils = minIoUtils; this.minIoUtils = minIoUtils;
@ -81,9 +89,15 @@ public class WordQuestionParser {
// 题号匹配 // 题号匹配
if (QUESTION_PATTERN.matcher(text).find()) { if (QUESTION_PATTERN.matcher(text).find()) {
// 如果正在解析得分点,且题干极短,说明是得分点子项(如"1、两块纳米黑板") // 带题型标签(如 40、[简答题])的一定是新题目,即使处于得分点/参考答案状态
// 归入最后一个得分点而非新题目 boolean isRealQuestion = TYPE_TAG_PATTERN.matcher(text).find();
if (parsingScoringPoints && currentQuestion != null // 参考答案正文中的编号行(如"1、LED高清显示系统")不是新题,归入当前简答题参考答案
if (!isRealQuestion && inReferenceAnswer && currentQuestion != null) {
appendReferenceAnswer(currentQuestion, text);
return;
}
// 处于得分点解析状态,且不是真题目,归入得分点子项
if (!isRealQuestion && parsingScoringPoints && currentQuestion != null
&& !currentQuestion.scoringPoints.isEmpty()) { && !currentQuestion.scoringPoints.isEmpty()) {
accumulateScoringPointContent(text, currentQuestion); accumulateScoringPointContent(text, currentQuestion);
return; return;
@ -93,6 +107,7 @@ public class WordQuestionParser {
} }
currentQuestion = new ParsedQuestion(); currentQuestion = new ParsedQuestion();
parsingScoringPoints = false; // 新题目,重置得分点状态 parsingScoringPoints = false; // 新题目,重置得分点状态
inReferenceAnswer = false; // 新题目,重置参考答案状态
// 去掉题号 // 去掉题号
currentQuestion.stem = QUESTION_PATTERN.matcher(text).replaceFirst("").trim(); currentQuestion.stem = QUESTION_PATTERN.matcher(text).replaceFirst("").trim();
currentQuestion.questionType = detectQuestionType(currentQuestion.stem); currentQuestion.questionType = detectQuestionType(currentQuestion.stem);
@ -109,6 +124,12 @@ public class WordQuestionParser {
return; return;
} }
// 正在解析参考答案正文:全部归入 referenceAnswer,不污染题干/选项
if (inReferenceAnswer) {
appendReferenceAnswer(currentQuestion, text);
return;
}
// 答案行 // 答案行
Matcher answerMatcher = ANSWER_PATTERN.matcher(text); Matcher answerMatcher = ANSWER_PATTERN.matcher(text);
if (answerMatcher.matches()) { if (answerMatcher.matches()) {
@ -126,6 +147,11 @@ public class WordQuestionParser {
return; return;
} }
// 难度行(如"难度:中"):元数据,不属于题干,直接忽略
if (DIFFICULTY_PATTERN.matcher(text).find()) {
return;
}
// 得分点行(格式:得分点:得分点1. xxx 得分点2. xxx) // 得分点行(格式:得分点:得分点1. xxx 得分点2. xxx)
Matcher scoringPointMatcher = SCORING_POINT_PATTERN.matcher(text); Matcher scoringPointMatcher = SCORING_POINT_PATTERN.matcher(text);
if (scoringPointMatcher.matches()) { if (scoringPointMatcher.matches()) {
@ -250,27 +276,32 @@ public class WordQuestionParser {
/** /**
* 将后续段落内容归入得分点得分点跨段落时使用 * 将后续段落内容归入得分点得分点跨段落时使用
* 只接受以"得分点N."开头的行遇到不以"得分点"开头的普通正文
* 参考答案则结束得分点收集避免参考答案污染得分点
*/ */
private void accumulateScoringPointContent(String text, ParsedQuestion question) { private void accumulateScoringPointContent(String text, ParsedQuestion question) {
// 检查是否是新得分点标题(如:得分点3. xxx) // 新得分点行(如:得分点3. xxx):交由 parseScoringPoints 处理(它会追加到已有列表)
Matcher m = Pattern.compile("得分点([0-9]+)[..、]\\s*(.*)").matcher(text); if (SCORING_POINT_ITEM_PATTERN.matcher(text).find()) {
if (m.matches()) {
// 新得分点:通过 parseScoringPoints 处理(它会处理保存逻辑)
// 这里把单条"得分点N. 内容"包装后调用
parseScoringPoints(text, question); parseScoringPoints(text, question);
return; return;
} }
// 普通内容:追加到最后一个得分点 // 不是得分点行:视为参考答案正文开始,结束得分点收集,不再污染得分点
if (!question.scoringPoints.isEmpty()) { parsingScoringPoints = false;
ParsedScoringPoint last = question.scoringPoints.get(question.scoringPoints.size() - 1); inReferenceAnswer = true; // 进入参考答案正文,后续编号行不再误判为新题
if (last.getContent() != null && !last.getContent().isEmpty()) { appendReferenceAnswer(question, text);
last.setContent(last.getContent() + " " + text);
} else {
last.setContent(text);
} }
/** 追加参考答案正文(单独存放,不污染题干) */
private void appendReferenceAnswer(ParsedQuestion question, String text) {
if (question == null) {
return;
}
if (question.referenceAnswer == null || question.referenceAnswer.isEmpty()) {
question.referenceAnswer = text;
} else {
question.referenceAnswer += "\n" + text;
} }
// 如果还没有得分点(异常情况),忽略
} }
/** /**
@ -339,6 +370,8 @@ public class WordQuestionParser {
List<ParsedOption> options = new ArrayList<>(); List<ParsedOption> options = new ArrayList<>();
String answer; String answer;
String analysis; String analysis;
/** 简答题参考答案正文(得分点之后、下一道题之前的内容) */
String referenceAnswer;
/** 简答题得分点列表 */ /** 简答题得分点列表 */
List<ParsedScoringPoint> scoringPoints = new ArrayList<>(); List<ParsedScoringPoint> scoringPoints = new ArrayList<>();
} }

21
src/main/java/com/project/question/domain/service/impl/QuestionInventoryDomainServiceImpl.java

@ -83,15 +83,28 @@ public class QuestionInventoryDomainServiceImpl implements QuestionInventoryDoma
List<TaskKnowledgeClusterEntity> clusterList = clusterMapper.selectList( List<TaskKnowledgeClusterEntity> clusterList = clusterMapper.selectList(
new LambdaQueryWrapper<TaskKnowledgeClusterEntity>().eq(TaskKnowledgeClusterEntity::getTaskId, taskId)); new LambdaQueryWrapper<TaskKnowledgeClusterEntity>().eq(TaskKnowledgeClusterEntity::getTaskId, taskId));
// 多选簇专用摊薄系数:u * (多选题数 / 多选簇总数)
// 说明:原逻辑复用了按“全部知识点数”摊薄的 watermark/targetLine(multiplier=总题数/知识点数),
// 在知识点数远大于题数(如 1000+ 知识点)时该系数 << 1,导致每个多选簇目标库存被压到个位数,
// 无法覆盖 u 名考生的多选题实际消耗。多选消耗只应按“多选簇”摊薄,而非全部知识点。
long mcClusterCount = clusterList.stream()
.filter(c -> c.getClusterSize() != null && c.getClusterSize() >= 2)
.count();
double mcClusterMultiplier = mcClusterCount > 0
? task.getMultipleChoiceNum() * 1.0 / mcClusterCount
: 1.0;
int mcWatermark = (int) Math.ceil(getUnpassedCount(task) * mcClusterMultiplier);
int mcTargetLine = (int) Math.ceil(2 * getUnpassedCount(task) * mcClusterMultiplier);
for (TaskKnowledgeClusterEntity cluster : clusterList) { for (TaskKnowledgeClusterEntity cluster : clusterList) {
if (cluster.getClusterSize() < 2) { if (cluster.getClusterSize() < 2) {
continue; continue;
} }
int w = cluster.getClusterSize(); int w = cluster.getClusterSize();
// 簇水位线: 1.0 * U * W // 簇水位线: mcWatermark * W(分子已是多选题数,占比已隐含,不再乘题型占比二次摊薄)
int clusterWatermark = (int) Math.ceil(watermark * w * questionTypeMap.get(QuestionTypeEnum.MULTIPLE_CHOICE.name())); int clusterWatermark = (int) Math.ceil(mcWatermark * w);
// 簇目标线: 2 * U * W // 簇目标线: mcTargetLine * W
int clusterTargetLine = (int) Math.ceil(targetLine * w * questionTypeMap.get(QuestionTypeEnum.MULTIPLE_CHOICE.name())); int clusterTargetLine = (int) Math.ceil(mcTargetLine * w);
checkClusterProduce(cluster.getId(), clusterWatermark, clusterTargetLine); checkClusterProduce(cluster.getId(), clusterWatermark, clusterTargetLine);
} }

6
src/main/java/com/project/task/controller/TaskController.java

@ -50,6 +50,12 @@ public class TaskController {
return questionInventoryDomainService.checkAndReplenishBoolean(taskId); return questionInventoryDomainService.checkAndReplenishBoolean(taskId);
} }
@GetMapping("/doCheckAndReplenish")
public Result<String> doCheckAndReplenish(Long taskId) throws Exception {
questionInventoryDomainService.checkAndReplenish(taskId);
return Result.success("ok");
}
@PostMapping("/batchDelete") @PostMapping("/batchDelete")
@OperationLog(module = "考试任务") @OperationLog(module = "考试任务")

Loading…
Cancel
Save