Browse Source

简答题评分联调

master
luoweijian 2 months ago
parent
commit
534ce6727a
  1. 4
      src/main/java/com/project/classicpaper/application/ClassicPaperSetApplicationService.java
  2. 17
      src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java
  3. 6
      src/main/java/com/project/classicpaper/controller/ClassicPaperSetController.java
  4. 3
      src/main/java/com/project/classicpaper/domain/entity/ClassicPaperSetEntity.java
  5. 1
      src/main/java/com/project/classicpaper/domain/service/impl/SavePaperSetDomainServiceImpl.java
  6. 9
      src/main/java/com/project/classicpaper/domain/service/parser/WordQuestionParser.java
  7. 7
      src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java
  8. 38
      src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java
  9. 15
      src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java
  10. 15
      src/main/java/com/project/exam/domain/service/strategy/ClassicPaperStrategy.java

4
src/main/java/com/project/classicpaper/application/ClassicPaperSetApplicationService.java

@ -6,6 +6,8 @@ import com.project.classicpaper.domain.dto.ClassicPaperSetDTO;
import com.project.classicpaper.domain.param.ClassicPaperSetParam; import com.project.classicpaper.domain.param.ClassicPaperSetParam;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface ClassicPaperSetApplicationService { public interface ClassicPaperSetApplicationService {
/** /**
@ -28,7 +30,7 @@ public interface ClassicPaperSetApplicationService {
/** /**
* 废弃套题组连带所有版本 * 废弃套题组连带所有版本
*/ */
Result<String> delete(Long setId) throws Exception; Result<String> delete(List<Long> ids) throws Exception;
/** /**
* 更新分值比例同时计算每道题的分值 * 更新分值比例同时计算每道题的分值

17
src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java

@ -227,21 +227,10 @@ public class ClassicPaperSetApplicationServiceImpl implements ClassicPaperSetApp
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Result<String> delete(Long setId) throws Exception { public Result<String> delete(List<Long> ids) throws Exception {
ClassicPaperSetEntity paperSet = classicPaperSetBaseService.getById(setId); classicPaperSetBaseService.removeBatchByIds(ids);
if (paperSet == null) {
throw new BusinessErrorException("套题组不存在");
}
paperSet.setStatus(ClassicPaperStatusEnum.DISCARDED.getValue());
classicPaperSetBaseService.updateById(paperSet);
classicPaperBaseService.lambdaUpdate()
.eq(ClassicPaperEntity::getSetId, setId)
.set(ClassicPaperEntity::getStatus, ClassicPaperStatusEnum.DISCARDED.getValue())
.update();
return Result.success("废弃成功"); return Result.success("删除成功");
} }
/** /**

6
src/main/java/com/project/classicpaper/controller/ClassicPaperSetController.java

@ -11,6 +11,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController @RestController
@Slf4j @Slf4j
@RequestMapping("/api/admin/classicPaperSet") @RequestMapping("/api/admin/classicPaperSet")
@ -53,8 +55,8 @@ public class ClassicPaperSetController {
@PostMapping("/delete") @PostMapping("/delete")
@OperationLog(module = "经典套题") @OperationLog(module = "经典套题")
public Result<String> delete(Long setId) throws Exception { public Result<String> delete(List<Long> ids) throws Exception {
return classicPaperSetApplicationService.delete(setId); return classicPaperSetApplicationService.delete(ids);
} }
/** /**

3
src/main/java/com/project/classicpaper/domain/entity/ClassicPaperSetEntity.java

@ -86,7 +86,8 @@ public class ClassicPaperSetEntity extends BaseEntity {
@Column(name = "status") @Column(name = "status")
@TableField("status") @TableField("status")
@Comment("状态:0-生成中,1-草稿,2-已确认,3-已废弃") private Integer status; @Comment("状态:0-生成中,1-草稿,2-已确认,3-已废弃")
private Integer status;
@Column(name = "source_file_name", columnDefinition = "varchar(500) comment '导入原始文件MinIO路径(Word导入时记录)'") @Column(name = "source_file_name", columnDefinition = "varchar(500) comment '导入原始文件MinIO路径(Word导入时记录)'")
@TableField("source_file_name") @TableField("source_file_name")

1
src/main/java/com/project/classicpaper/domain/service/impl/SavePaperSetDomainServiceImpl.java

@ -10,6 +10,7 @@ import com.project.classicpaper.domain.dto.ClassicPaperSetDTO;
import com.project.classicpaper.domain.entity.ClassicPaperEntity; import com.project.classicpaper.domain.entity.ClassicPaperEntity;
import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity; import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity;
import com.project.classicpaper.domain.entity.ClassicPaperSetEntity; import com.project.classicpaper.domain.entity.ClassicPaperSetEntity;
import com.project.classicpaper.domain.enums.ClassicPaperStatusEnum;
import com.project.classicpaper.domain.service.ClassicPaperBaseService; import com.project.classicpaper.domain.service.ClassicPaperBaseService;
import com.project.classicpaper.domain.service.ClassicPaperQuestionBaseService; import com.project.classicpaper.domain.service.ClassicPaperQuestionBaseService;
import com.project.classicpaper.domain.service.ClassicPaperSetBaseService; import com.project.classicpaper.domain.service.ClassicPaperSetBaseService;

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

@ -3,6 +3,7 @@ package com.project.classicpaper.domain.service.parser;
import com.project.information.utils.MinIoUtils; import com.project.information.utils.MinIoUtils;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import com.project.task.domain.enums.QuestionTypeEnum;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.*; import org.apache.poi.xwpf.usermodel.*;
@ -217,16 +218,16 @@ public class WordQuestionParser {
*/ */
private int detectQuestionType(String stem) { private int detectQuestionType(String stem) {
if (stem.contains("[判断题]")) { if (stem.contains("[判断题]")) {
return 2; // TRUE_FALSE return QuestionTypeEnum.TRUE_FALSE.getValue(); // TRUE_FALSE
} }
if (stem.contains("[简答题]")) { if (stem.contains("[简答题]")) {
return 4; // SHORT_ANSWER return QuestionTypeEnum.SHORT_ANSWER.getValue(); // SHORT_ANSWER
} }
if (stem.contains("[多选题]")) { if (stem.contains("[多选题]")) {
return 1; // MULTIPLE_CHOICE return QuestionTypeEnum.MULTIPLE_CHOICE.getValue(); // MULTIPLE_CHOICE
} }
if (stem.contains("[单选题]")) { if (stem.contains("[单选题]")) {
return 0; // SINGLE_CHOICE return QuestionTypeEnum.SINGLE_CHOICE.getValue(); // SINGLE_CHOICE
} }
return 0; // 默认单选 return 0; // 默认单选
} }

7
src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java

@ -79,6 +79,13 @@ public class ExamRecordEntity extends BaseEntity {
@Column(name = "submit_time") @Column(name = "submit_time")
@TableField("submit_time") @TableField("submit_time")
private Date submitTime; private Date submitTime;
/** 简答题评分状态(0=全部已评分或无需评分,1=有待评分简答题) */
@Column(name = "scoring_status")
@TableField("scoring_status")
@Comment("简答题评分状态:0-已评分/无需评分,1-待评分")
private Integer scoringStatus;
/** /**
* 考试快照包含题干当时乱序后的选项内容用户答案正确答案解析是否正确 * 考试快照包含题干当时乱序后的选项内容用户答案正确答案解析是否正确
*/ */

38
src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java

@ -12,6 +12,7 @@ import com.project.question.domain.service.QuestionBaseService;
import com.project.task.domain.entity.TaskEntity; import com.project.task.domain.entity.TaskEntity;
import com.project.task.domain.enums.QuestionTypeEnum; import com.project.task.domain.enums.QuestionTypeEnum;
import com.project.task.domain.enums.TaskUserStatusEnum; import com.project.task.domain.enums.TaskUserStatusEnum;
import com.project.exam.domain.enums.ExamRecordScoringStatusEnum;
import com.project.task.mapper.TaskMapper; import com.project.task.mapper.TaskMapper;
import com.project.task.mapper.TaskUserMapper; import com.project.task.mapper.TaskUserMapper;
import com.project.task.domain.service.TaskBaseService; import com.project.task.domain.service.TaskBaseService;
@ -87,20 +88,30 @@ public class ShortAnswerScoringJob {
int processed = 0; int processed = 0;
for (ExamRecordEntity record : records) { for (ExamRecordEntity record : records) {
if (processed >= batchSize) break; if (processed >= batchSize) {
break;
}
List<ExamRecordEntity.QuestionSnapshot> snapshots = record.getAnswerSnapshot(); List<ExamRecordEntity.QuestionSnapshot> snapshots = record.getAnswerSnapshot();
if (snapshots == null || snapshots.isEmpty()) continue; if (snapshots == null || snapshots.isEmpty()) {
continue;
}
boolean updated = false; boolean updated = false;
for (int i = 0; i < snapshots.size(); i++) { for (int i = 0; i < snapshots.size(); i++) {
if (processed >= batchSize) break; if (processed >= batchSize) {
break;
}
ExamRecordEntity.QuestionSnapshot snapshot = snapshots.get(i); ExamRecordEntity.QuestionSnapshot snapshot = snapshots.get(i);
// 只处理简答题且待评分状态 // 只处理简答题且待评分状态
if (!QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) continue; if (!QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) {
if (!Objects.equals(0, snapshot.getScoringStatus())) continue; continue;
}
if (!Objects.equals(ExamRecordScoringStatusEnum.PENDING.getValue(), snapshot.getScoringStatus())) {
continue;
}
log.info(">>> [简答题评分任务] 开始评分, recordId={}, questionId={}", log.info(">>> [简答题评分任务] 开始评分, recordId={}, questionId={}",
record.getId(), snapshot.getQuestionId()); record.getId(), snapshot.getQuestionId());
@ -197,7 +208,9 @@ public class ShortAnswerScoringJob {
hitPoints.add(i); hitPoints.add(i);
} }
if (ps.getReason() != null && !ps.getReason().isEmpty()) { if (ps.getReason() != null && !ps.getReason().isEmpty()) {
if (comment.length() > 0) comment.append("; "); if (comment.length() > 0) {
comment.append("; ");
}
comment.append(ps.getReason()); comment.append(ps.getReason());
} }
} }
@ -205,25 +218,27 @@ public class ShortAnswerScoringJob {
snapshot.setAiScore(aiScore); snapshot.setAiScore(aiScore);
snapshot.setAiComment(comment.toString()); snapshot.setAiComment(comment.toString());
snapshot.setHitPoints(hitPoints); snapshot.setHitPoints(hitPoints);
snapshot.setScoringStatus(1); snapshot.setScoringStatus(ExamRecordScoringStatusEnum.COMPLETED.getValue());
log.info(">>> [简答题评分任务] 评分完成, questionId={}, shortAnswerScore={}, totalPoints={}, hitPoints={}, aiScore={}", log.info(">>> [简答题评分任务] 评分完成, questionId={}, shortAnswerScore={}, totalPoints={}, hitPoints={}, aiScore={}",
snapshot.getQuestionId(), shortAnswerScore, totalPoints, hitPoints, aiScore); snapshot.getQuestionId(), shortAnswerScore, totalPoints, hitPoints, aiScore);
} else { } else {
log.warn(">>> [简答题评分任务] 评分失败, questionId={}, message={}", log.warn(">>> [简答题评分任务] 评分失败, questionId={}, message={}",
snapshot.getQuestionId(), response.getMessage()); snapshot.getQuestionId(), response.getMessage());
snapshot.setScoringStatus(2); snapshot.setScoringStatus(ExamRecordScoringStatusEnum.FAILED.getValue());
} }
} catch (Exception e) { } catch (Exception e) {
log.error(">>> [简答题评分任务] 评分异常, questionId={}", snapshot.getQuestionId(), e); log.error(">>> [简答题评分任务] 评分异常, questionId={}", snapshot.getQuestionId(), e);
snapshot.setScoringStatus(2); snapshot.setScoringStatus(ExamRecordScoringStatusEnum.FAILED.getValue());
} }
} }
private void recalcTotalScore(ExamRecordEntity record) { private void recalcTotalScore(ExamRecordEntity record) {
List<ExamRecordEntity.QuestionSnapshot> snapshots = record.getAnswerSnapshot(); List<ExamRecordEntity.QuestionSnapshot> snapshots = record.getAnswerSnapshot();
if (snapshots == null || snapshots.isEmpty()) return; if (snapshots == null || snapshots.isEmpty()) {
return;
}
double total = 0.0; double total = 0.0;
boolean allShortDone = true; boolean allShortDone = true;
@ -231,7 +246,7 @@ public class ShortAnswerScoringJob {
for (ExamRecordEntity.QuestionSnapshot s : snapshots) { for (ExamRecordEntity.QuestionSnapshot s : snapshots) {
boolean isShort = QuestionTypeEnum.SHORT_ANSWER.getValue().equals(s.getType()); boolean isShort = QuestionTypeEnum.SHORT_ANSWER.getValue().equals(s.getType());
if (isShort) { if (isShort) {
if (!Objects.equals(1, s.getScoringStatus())) { if (!Objects.equals(ExamRecordScoringStatusEnum.COMPLETED.getValue(), s.getScoringStatus())) {
allShortDone = false; allShortDone = false;
break; break;
} }
@ -245,6 +260,7 @@ public class ShortAnswerScoringJob {
if (allShortDone) { if (allShortDone) {
record.setScore(total); record.setScore(total);
record.setScoringStatus(ExamRecordScoringStatusEnum.COMPLETED.getValue());
// 查询任务及格线 // 查询任务及格线
double passScore = 60.0; double passScore = 60.0;
TaskEntity task = null; TaskEntity task = null;

15
src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java

@ -17,6 +17,7 @@ import com.project.task.domain.entity.TaskEntity;
import com.project.task.domain.entity.TaskUserEntity; import com.project.task.domain.entity.TaskUserEntity;
import com.project.task.domain.enums.QuestionTypeEnum; import com.project.task.domain.enums.QuestionTypeEnum;
import com.project.task.domain.enums.TaskUserStatusEnum; import com.project.task.domain.enums.TaskUserStatusEnum;
import com.project.exam.domain.enums.ExamRecordScoringStatusEnum;
import com.project.task.domain.service.TaskBaseService; import com.project.task.domain.service.TaskBaseService;
import com.project.task.mapper.TaskMapper; import com.project.task.mapper.TaskMapper;
import com.project.task.mapper.TaskUserMapper; import com.project.task.mapper.TaskUserMapper;
@ -74,6 +75,8 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService {
ScoringContext scoringContext = new ScoringContext(); ScoringContext scoringContext = new ScoringContext();
scoringContext.setTask(task); scoringContext.setTask(task);
// 客观题即时出分,简答题异步评分
boolean hasShortAnswer = false;
for (ExamRecordEntity.QuestionSnapshot snapshot : snapshotList) { for (ExamRecordEntity.QuestionSnapshot snapshot : snapshotList) {
double questionWeight = getQuestionWeight(snapshot.getType(), task); double questionWeight = getQuestionWeight(snapshot.getType(), task);
// 设置题目分值到快照(DTO转换用) // 设置题目分值到快照(DTO转换用)
@ -85,13 +88,13 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService {
tempDto.setRightAnswer(snapshot.getRightAnswer()); tempDto.setRightAnswer(snapshot.getRightAnswer());
tempDto.setScore(questionWeight); tempDto.setScore(questionWeight);
// 客观题即时出分,简答题异步评分
if (QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) { if (QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) {
hasShortAnswer = true;
// 简答题:标记待评分,跳过同步调用 // 简答题:标记待评分,跳过同步调用
snapshot.setIsRight(false); snapshot.setIsRight(false);
snapshot.setAiScore(0.0); snapshot.setAiScore(0.0);
snapshot.setScore(tempDto.getScore()); snapshot.setScore(tempDto.getScore());
snapshot.setScoringStatus(0); snapshot.setScoringStatus(ExamRecordScoringStatusEnum.PENDING.getValue());
} else { } else {
// 客观题:策略判分(比对答案) // 客观题:策略判分(比对答案)
ScoringStrategy strategy = scoringStrategyFactory.getStrategy(snapshot.getType()); ScoringStrategy strategy = scoringStrategyFactory.getStrategy(snapshot.getType());
@ -119,6 +122,10 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService {
record.setScore(finalScore); record.setScore(finalScore);
record.setPass(isPassed); record.setPass(isPassed);
record.setSubmitTime(new Date()); record.setSubmitTime(new Date());
// 有简答题 → 待评分;全客观题 → 已评分
record.setScoringStatus(hasShortAnswer
? ExamRecordScoringStatusEnum.PENDING.getValue()
: ExamRecordScoringStatusEnum.COMPLETED.getValue());
record.setAnswerSnapshot(snapshotList); record.setAnswerSnapshot(snapshotList);
examRecordMapper.updateById(record); examRecordMapper.updateById(record);
@ -141,8 +148,10 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService {
ExamRecordDTO dto = record.toDTO(ExamRecordDTO::new); ExamRecordDTO dto = record.toDTO(ExamRecordDTO::new);
// 后面需要用到这个taskDTO // 后面需要用到这个taskDTO
dto.setTaskDTO(task.toDTO(TaskDTO::new)); dto.setTaskDTO(task.toDTO(TaskDTO::new));
// 发送通知 // 只有全部评分完成(全客观题或简答题已评完)才发送通知
if (ExamRecordScoringStatusEnum.COMPLETED.getValue().equals(record.getScoringStatus())) {
notifyExamRecordDomainService.notifyExamRecord(dto); notifyExamRecordDomainService.notifyExamRecord(dto);
}
return buildExamRecordDomainService.buildDTO(dto); return buildExamRecordDomainService.buildDTO(dto);
} }

15
src/main/java/com/project/exam/domain/service/strategy/ClassicPaperStrategy.java

@ -4,8 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.project.base.domain.exception.BusinessErrorException; import com.project.base.domain.exception.BusinessErrorException;
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.BuildExamRecordDomainService;
import com.project.exam.domain.service.ExamRecordBaseService; import com.project.exam.domain.service.ExamRecordBaseService;
import com.project.question.domain.entity.QuestionEntity; import com.project.question.domain.entity.QuestionEntity;
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.entity.TaskPaperSnapshotEntity; import com.project.task.domain.entity.TaskPaperSnapshotEntity;
import com.project.task.domain.entity.TaskUserEntity; import com.project.task.domain.entity.TaskUserEntity;
@ -44,6 +46,8 @@ public class ClassicPaperStrategy implements PaperAssemblyStrategy {
@Autowired @Autowired
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@Autowired
private BuildExamRecordDomainService buildExamRecordDomainService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -67,6 +71,10 @@ public class ClassicPaperStrategy implements PaperAssemblyStrategy {
throw new BusinessErrorException("请勿短时间内重复参加考试"); throw new BusinessErrorException("请勿短时间内重复参加考试");
} }
// 获取任务配置
TaskDTO taskDTO = taskBaseService.getById(taskId).toDTO(TaskDTO::new);
// 3. 查询任务配置 // 3. 查询任务配置
TaskEntity task = taskBaseService.getById(taskId); TaskEntity task = taskBaseService.getById(taskId);
@ -123,12 +131,13 @@ public class ClassicPaperStrategy implements PaperAssemblyStrategy {
recordEntity.setTaskUserId(taskUser.getId()); recordEntity.setTaskUserId(taskUser.getId());
recordEntity.setTaskName(task.getName()); recordEntity.setTaskName(task.getName());
recordEntity.setStartTime(new Date()); recordEntity.setStartTime(new Date());
examRecordBaseService.save(recordEntity); examRecordDTO = examRecordBaseService.saveDTO(examRecordDTO, ExamRecordEntity::new, ExamRecordDTO::new);
examRecordDTO.setId(recordEntity.getId()); examRecordDTO.setRemindTimePoint(taskDTO.getRemindTimePoint());
log.info(">>> [经典组卷] 组卷完成, taskId={}, userId={}, selectedSetIndex={}, 题目数={}", log.info(">>> [经典组卷] 组卷完成, taskId={}, userId={}, selectedSetIndex={}, 题目数={}",
taskId, userId, selectedSetIndex, snapshotDTOList.size()); taskId, userId, selectedSetIndex, snapshotDTOList.size());
return examRecordDTO; return buildExamRecordDomainService.buildDTO(examRecordDTO);
} }
} }

Loading…
Cancel
Save