diff --git a/src/main/java/com/project/classicpaper/application/ClassicPaperSetApplicationService.java b/src/main/java/com/project/classicpaper/application/ClassicPaperSetApplicationService.java index 2a69d56..152d84d 100644 --- a/src/main/java/com/project/classicpaper/application/ClassicPaperSetApplicationService.java +++ b/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 org.springframework.web.multipart.MultipartFile; +import java.util.List; + public interface ClassicPaperSetApplicationService { /** @@ -28,7 +30,7 @@ public interface ClassicPaperSetApplicationService { /** * 废弃套题组(连带所有版本) */ - Result delete(Long setId) throws Exception; + Result delete(List ids) throws Exception; /** * 更新分值比例,同时计算每道题的分值 diff --git a/src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java b/src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java index f950ea2..68ebf71 100644 --- a/src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java +++ b/src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java @@ -227,21 +227,10 @@ public class ClassicPaperSetApplicationServiceImpl implements ClassicPaperSetApp @Override @Transactional(rollbackFor = Exception.class) - public Result delete(Long setId) throws Exception { - ClassicPaperSetEntity paperSet = classicPaperSetBaseService.getById(setId); - 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(); + public Result delete(List ids) throws Exception { + classicPaperSetBaseService.removeBatchByIds(ids); - return Result.success("废弃成功"); + return Result.success("删除成功"); } /** diff --git a/src/main/java/com/project/classicpaper/controller/ClassicPaperSetController.java b/src/main/java/com/project/classicpaper/controller/ClassicPaperSetController.java index a23b5ea..2e6c6e9 100644 --- a/src/main/java/com/project/classicpaper/controller/ClassicPaperSetController.java +++ b/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.multipart.MultipartFile; +import java.util.List; + @RestController @Slf4j @RequestMapping("/api/admin/classicPaperSet") @@ -53,8 +55,8 @@ public class ClassicPaperSetController { @PostMapping("/delete") @OperationLog(module = "经典套题") - public Result delete(Long setId) throws Exception { - return classicPaperSetApplicationService.delete(setId); + public Result delete(List ids) throws Exception { + return classicPaperSetApplicationService.delete(ids); } /** diff --git a/src/main/java/com/project/classicpaper/domain/entity/ClassicPaperSetEntity.java b/src/main/java/com/project/classicpaper/domain/entity/ClassicPaperSetEntity.java index 47cab46..8829643 100644 --- a/src/main/java/com/project/classicpaper/domain/entity/ClassicPaperSetEntity.java +++ b/src/main/java/com/project/classicpaper/domain/entity/ClassicPaperSetEntity.java @@ -86,7 +86,8 @@ public class ClassicPaperSetEntity extends BaseEntity { @Column(name = "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导入时记录)'") @TableField("source_file_name") diff --git a/src/main/java/com/project/classicpaper/domain/service/impl/SavePaperSetDomainServiceImpl.java b/src/main/java/com/project/classicpaper/domain/service/impl/SavePaperSetDomainServiceImpl.java index d417f1f..4d01a71 100644 --- a/src/main/java/com/project/classicpaper/domain/service/impl/SavePaperSetDomainServiceImpl.java +++ b/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.ClassicPaperQuestionEntity; 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.ClassicPaperQuestionBaseService; import com.project.classicpaper.domain.service.ClassicPaperSetBaseService; diff --git a/src/main/java/com/project/classicpaper/domain/service/parser/WordQuestionParser.java b/src/main/java/com/project/classicpaper/domain/service/parser/WordQuestionParser.java index c3a3246..0f93789 100644 --- a/src/main/java/com/project/classicpaper/domain/service/parser/WordQuestionParser.java +++ b/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 cn.hutool.core.date.DateUtil; import cn.hutool.core.util.IdUtil; +import com.project.task.domain.enums.QuestionTypeEnum; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.poi.xwpf.usermodel.*; @@ -217,16 +218,16 @@ public class WordQuestionParser { */ private int detectQuestionType(String stem) { if (stem.contains("[判断题]")) { - return 2; // TRUE_FALSE + return QuestionTypeEnum.TRUE_FALSE.getValue(); // TRUE_FALSE } if (stem.contains("[简答题]")) { - return 4; // SHORT_ANSWER + return QuestionTypeEnum.SHORT_ANSWER.getValue(); // SHORT_ANSWER } if (stem.contains("[多选题]")) { - return 1; // MULTIPLE_CHOICE + return QuestionTypeEnum.MULTIPLE_CHOICE.getValue(); // MULTIPLE_CHOICE } if (stem.contains("[单选题]")) { - return 0; // SINGLE_CHOICE + return QuestionTypeEnum.SINGLE_CHOICE.getValue(); // SINGLE_CHOICE } return 0; // 默认单选 } diff --git a/src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java b/src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java index b30cd07..2d7b148 100644 --- a/src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java +++ b/src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java @@ -79,6 +79,13 @@ public class ExamRecordEntity extends BaseEntity { @Column(name = "submit_time") @TableField("submit_time") private Date submitTime; + + /** 简答题评分状态(0=全部已评分或无需评分,1=有待评分简答题) */ + @Column(name = "scoring_status") + @TableField("scoring_status") + @Comment("简答题评分状态:0-已评分/无需评分,1-待评分") + private Integer scoringStatus; + /** * 考试快照(包含:题干、当时乱序后的选项内容、用户答案、正确答案、解析、是否正确) */ diff --git a/src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java b/src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java index 927694c..5c43d91 100644 --- a/src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java +++ b/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.enums.QuestionTypeEnum; 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.TaskUserMapper; import com.project.task.domain.service.TaskBaseService; @@ -87,20 +88,30 @@ public class ShortAnswerScoringJob { int processed = 0; for (ExamRecordEntity record : records) { - if (processed >= batchSize) break; + if (processed >= batchSize) { + break; + } List snapshots = record.getAnswerSnapshot(); - if (snapshots == null || snapshots.isEmpty()) continue; + if (snapshots == null || snapshots.isEmpty()) { + continue; + } boolean updated = false; for (int i = 0; i < snapshots.size(); i++) { - if (processed >= batchSize) break; + if (processed >= batchSize) { + break; + } ExamRecordEntity.QuestionSnapshot snapshot = snapshots.get(i); // 只处理简答题且待评分状态 - if (!QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) continue; - if (!Objects.equals(0, snapshot.getScoringStatus())) continue; + if (!QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) { + continue; + } + if (!Objects.equals(ExamRecordScoringStatusEnum.PENDING.getValue(), snapshot.getScoringStatus())) { + continue; + } log.info(">>> [简答题评分任务] 开始评分, recordId={}, questionId={}", record.getId(), snapshot.getQuestionId()); @@ -197,7 +208,9 @@ public class ShortAnswerScoringJob { hitPoints.add(i); } if (ps.getReason() != null && !ps.getReason().isEmpty()) { - if (comment.length() > 0) comment.append("; "); + if (comment.length() > 0) { + comment.append("; "); + } comment.append(ps.getReason()); } } @@ -205,25 +218,27 @@ public class ShortAnswerScoringJob { snapshot.setAiScore(aiScore); snapshot.setAiComment(comment.toString()); snapshot.setHitPoints(hitPoints); - snapshot.setScoringStatus(1); + snapshot.setScoringStatus(ExamRecordScoringStatusEnum.COMPLETED.getValue()); log.info(">>> [简答题评分任务] 评分完成, questionId={}, shortAnswerScore={}, totalPoints={}, hitPoints={}, aiScore={}", snapshot.getQuestionId(), shortAnswerScore, totalPoints, hitPoints, aiScore); } else { log.warn(">>> [简答题评分任务] 评分失败, questionId={}, message={}", snapshot.getQuestionId(), response.getMessage()); - snapshot.setScoringStatus(2); + snapshot.setScoringStatus(ExamRecordScoringStatusEnum.FAILED.getValue()); } } catch (Exception e) { log.error(">>> [简答题评分任务] 评分异常, questionId={}", snapshot.getQuestionId(), e); - snapshot.setScoringStatus(2); + snapshot.setScoringStatus(ExamRecordScoringStatusEnum.FAILED.getValue()); } } private void recalcTotalScore(ExamRecordEntity record) { List snapshots = record.getAnswerSnapshot(); - if (snapshots == null || snapshots.isEmpty()) return; + if (snapshots == null || snapshots.isEmpty()) { + return; + } double total = 0.0; boolean allShortDone = true; @@ -231,7 +246,7 @@ public class ShortAnswerScoringJob { for (ExamRecordEntity.QuestionSnapshot s : snapshots) { boolean isShort = QuestionTypeEnum.SHORT_ANSWER.getValue().equals(s.getType()); if (isShort) { - if (!Objects.equals(1, s.getScoringStatus())) { + if (!Objects.equals(ExamRecordScoringStatusEnum.COMPLETED.getValue(), s.getScoringStatus())) { allShortDone = false; break; } @@ -245,6 +260,7 @@ public class ShortAnswerScoringJob { if (allShortDone) { record.setScore(total); + record.setScoringStatus(ExamRecordScoringStatusEnum.COMPLETED.getValue()); // 查询任务及格线 double passScore = 60.0; TaskEntity task = null; diff --git a/src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java b/src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java index 82386b3..0cdc50c 100644 --- a/src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java +++ b/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.enums.QuestionTypeEnum; 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.mapper.TaskMapper; import com.project.task.mapper.TaskUserMapper; @@ -74,6 +75,8 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService { ScoringContext scoringContext = new ScoringContext(); scoringContext.setTask(task); + // 客观题即时出分,简答题异步评分 + boolean hasShortAnswer = false; for (ExamRecordEntity.QuestionSnapshot snapshot : snapshotList) { double questionWeight = getQuestionWeight(snapshot.getType(), task); // 设置题目分值到快照(DTO转换用) @@ -85,13 +88,13 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService { tempDto.setRightAnswer(snapshot.getRightAnswer()); tempDto.setScore(questionWeight); - // 客观题即时出分,简答题异步评分 if (QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) { + hasShortAnswer = true; // 简答题:标记待评分,跳过同步调用 snapshot.setIsRight(false); snapshot.setAiScore(0.0); snapshot.setScore(tempDto.getScore()); - snapshot.setScoringStatus(0); + snapshot.setScoringStatus(ExamRecordScoringStatusEnum.PENDING.getValue()); } else { // 客观题:策略判分(比对答案) ScoringStrategy strategy = scoringStrategyFactory.getStrategy(snapshot.getType()); @@ -119,6 +122,10 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService { record.setScore(finalScore); record.setPass(isPassed); record.setSubmitTime(new Date()); + // 有简答题 → 待评分;全客观题 → 已评分 + record.setScoringStatus(hasShortAnswer + ? ExamRecordScoringStatusEnum.PENDING.getValue() + : ExamRecordScoringStatusEnum.COMPLETED.getValue()); record.setAnswerSnapshot(snapshotList); examRecordMapper.updateById(record); @@ -141,8 +148,10 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService { ExamRecordDTO dto = record.toDTO(ExamRecordDTO::new); // 后面需要用到这个taskDTO dto.setTaskDTO(task.toDTO(TaskDTO::new)); - // 发送通知 - notifyExamRecordDomainService.notifyExamRecord(dto); + // 只有全部评分完成(全客观题或简答题已评完)才发送通知 + if (ExamRecordScoringStatusEnum.COMPLETED.getValue().equals(record.getScoringStatus())) { + notifyExamRecordDomainService.notifyExamRecord(dto); + } return buildExamRecordDomainService.buildDTO(dto); } diff --git a/src/main/java/com/project/exam/domain/service/strategy/ClassicPaperStrategy.java b/src/main/java/com/project/exam/domain/service/strategy/ClassicPaperStrategy.java index eb06fa1..63b482f 100644 --- a/src/main/java/com/project/exam/domain/service/strategy/ClassicPaperStrategy.java +++ b/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.exam.domain.dto.ExamRecordDTO; import com.project.exam.domain.entity.ExamRecordEntity; +import com.project.exam.domain.service.BuildExamRecordDomainService; import com.project.exam.domain.service.ExamRecordBaseService; 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.TaskPaperSnapshotEntity; import com.project.task.domain.entity.TaskUserEntity; @@ -44,6 +46,8 @@ public class ClassicPaperStrategy implements PaperAssemblyStrategy { @Autowired private StringRedisTemplate stringRedisTemplate; + @Autowired + private BuildExamRecordDomainService buildExamRecordDomainService; @Override @Transactional(rollbackFor = Exception.class) @@ -67,6 +71,10 @@ public class ClassicPaperStrategy implements PaperAssemblyStrategy { throw new BusinessErrorException("请勿短时间内重复参加考试"); } + + // 获取任务配置 + TaskDTO taskDTO = taskBaseService.getById(taskId).toDTO(TaskDTO::new); + // 3. 查询任务配置 TaskEntity task = taskBaseService.getById(taskId); @@ -123,12 +131,13 @@ public class ClassicPaperStrategy implements PaperAssemblyStrategy { recordEntity.setTaskUserId(taskUser.getId()); recordEntity.setTaskName(task.getName()); recordEntity.setStartTime(new Date()); - examRecordBaseService.save(recordEntity); - examRecordDTO.setId(recordEntity.getId()); + examRecordDTO = examRecordBaseService.saveDTO(examRecordDTO, ExamRecordEntity::new, ExamRecordDTO::new); + examRecordDTO.setRemindTimePoint(taskDTO.getRemindTimePoint()); log.info(">>> [经典组卷] 组卷完成, taskId={}, userId={}, selectedSetIndex={}, 题目数={}", taskId, userId, selectedSetIndex, snapshotDTOList.size()); - return examRecordDTO; + return buildExamRecordDomainService.buildDTO(examRecordDTO); + } }