Browse Source

简答题评分联调

master
luoweijian 2 months ago
parent
commit
838eba7471
  1. 18
      src/main/java/com/project/classicpaper/domain/service/impl/ClassicPaperQuestionCallbackServiceImpl.java
  2. 35
      src/main/java/com/project/classicpaper/domain/service/impl/RegenerateQuestionDomainServiceImpl.java
  3. 4
      src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java
  4. 294
      src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java
  5. 17
      src/main/java/com/project/exam/domain/service/impl/SubmitPaperDomainServiceImpl.java
  6. 41
      src/main/java/com/project/exam/domain/service/strategy/SubjectiveScoringStrategy.java
  7. 10
      src/main/java/com/project/information/application/impl/KnowledgePointApplicationServiceImpl.java
  8. 38
      src/main/java/com/project/interaction/domain/dto/AiScoringResponseDTO.java
  9. 15
      src/main/java/com/project/interaction/domain/service/impl/PostToAiScoringDomainServiceImpl.java
  10. 4
      src/main/java/com/project/task/domain/dto/TaskDTO.java
  11. 28
      src/main/java/com/project/task/domain/service/impl/SearchTaskDomainServiceImpl.java
  12. 7
      src/main/resources/application-dev.yml
  13. 6
      src/main/resources/application-test.yml

18
src/main/java/com/project/classicpaper/domain/service/impl/ClassicPaperQuestionCallbackServiceImpl.java

@ -6,9 +6,11 @@ import com.project.base.domain.result.Result;
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.entity.RegenerateQuestionTaskEntity;
import com.project.classicpaper.domain.enums.ClassicPaperStatusEnum;
import com.project.classicpaper.domain.service.*;
import com.project.interaction.domain.dto.QuestionCallBackDTO;
import com.project.classicpaper.domain.service.RegenerateQuestionTaskBaseService;
import com.project.question.domain.dto.QuestionDTO;
import com.project.question.domain.entity.QuestionEntity;
import com.project.question.domain.service.SaveQuestionDomainService;
@ -40,6 +42,9 @@ public class ClassicPaperQuestionCallbackServiceImpl implements ClassicPaperQues
@Autowired
private ClassicPaperSetBaseService classicPaperSetBaseService;
@Autowired
private RegenerateQuestionTaskBaseService regenerateQuestionTaskBaseService;
@Override
@Transactional(rollbackFor = Exception.class)
public void handleCallback(QuestionCallBackDTO callback) {
@ -113,6 +118,19 @@ public class ClassicPaperQuestionCallbackServiceImpl implements ClassicPaperQues
log.info(">>> [经典套题-回调] 处理完成, paperQuestionId={}, promotedQuestionId={}",
paperQuestionId, promoted.getId());
// 6. 更新重新生题任务状态(如果有进行中的)
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<RegenerateQuestionTaskEntity> taskWrapper =
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
taskWrapper.eq(RegenerateQuestionTaskEntity::getPaperQuestionId, paperQuestionId)
.eq(RegenerateQuestionTaskEntity::getStatus, 0);
RegenerateQuestionTaskEntity regenTask = regenerateQuestionTaskBaseService.getOne(taskWrapper);
if (regenTask != null) {
regenTask.setStatus(1);
regenTask.setNewQuestionId(promoted.getId());
regenerateQuestionTaskBaseService.updateById(regenTask);
log.info(">>> [经典套题-回调] regenerateTaskId={} 状态更新为成功", regenTask.getId());
}
// 检查当前 paper 的所有题目是否都已生成完成
boolean paperAllDone = classicPaperQuestionBaseService.count(
new LambdaQueryWrapper<ClassicPaperQuestionEntity>()

35
src/main/java/com/project/classicpaper/domain/service/impl/RegenerateQuestionDomainServiceImpl.java

@ -238,35 +238,24 @@ public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDo
List<Long> kpIds = kpRels.stream()
.map(QuestionKpRelEntity::getKpId).collect(Collectors.toList());
List<KnowledgePointEntity> knowledgePoints = knowledgePointBaseService.listByIds(kpIds);
// 3. Mock 延迟:模拟算法服务返回耗时
mockSleep();
// 4. 调用生成器生成新题
QuestionTypeEnum questionType = QuestionTypeEnum.findByValue(originalQuestion.getQuestionType());
List<QuestionEntity> newQuestions = classicPaperQuestionGenerator.generate(
knowledgePoints, questionType, 1);
if (newQuestions.isEmpty()) {
throw new BusinessErrorException("生成新题失败");
if (knowledgePoints.isEmpty()) {
throw new BusinessErrorException("关联知识点数据不存在");
}
QuestionEntity newQuestion = newQuestions.get(0);
// 5. 更新关联关系
paperQuestion.setQuestionId(newQuestion.getId());
// 3. 清空 questionId,让回调走提拔分支
paperQuestion.setQuestionId(null);
classicPaperQuestionBaseService.updateById(paperQuestion);
// 6. 更新任务状态为成功
RegenerateQuestionTaskEntity taskEntity = regenerateQuestionTaskBaseService.getOne(
new LambdaQueryWrapper<RegenerateQuestionTaskEntity>()
.eq(RegenerateQuestionTaskEntity::getTaskId, taskId));
taskEntity.setStatus(1);
taskEntity.setNewQuestionId(newQuestion.getId());
regenerateQuestionTaskBaseService.updateById(taskEntity);
// 4. 异步提交生题任务(回调会提拔新题 + 更新 task 状态)
QuestionTypeEnum questionType = QuestionTypeEnum.findByValue(originalQuestion.getQuestionType());
Long clusterId = knowledgePoints.get(0).getClusterId();
classicPaperQuestionGenerator.generateAsync(
paperQuestionId, knowledgePoints, questionType, 1, clusterId);
log.info(">>> [经典套题-重新生题-异步] 任务成功, taskId={}, 原题{} -> 新题{}", taskId, questionId, newQuestion.getId());
log.info(">>> [经典套题-重新生题-异步] 已提交算法, taskId={}, paperQuestionId={}, 题型={}",
taskId, paperQuestionId, questionType);
} catch (Exception e) {
log.error(">>> [经典套题-重新生题-异步] 任务失败, taskId={}", taskId, e);
// 更新任务状态为失败
log.error(">>> [经典套题-重新生题-异步] 提交失败, taskId={}", taskId, e);
RegenerateQuestionTaskEntity taskEntity = regenerateQuestionTaskBaseService.getOne(
new LambdaQueryWrapper<RegenerateQuestionTaskEntity>()
.eq(RegenerateQuestionTaskEntity::getTaskId, taskId));

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

@ -112,6 +112,10 @@ public class ExamRecordEntity extends BaseEntity {
private String aiComment;
// V1.1 新增:命中得分点列表(简答题统计用)
private List<Integer> hitPoints;
// 该题分值(从 task 题型分值映射)
private Double score;
// V1.2 新增:简答题评分状态(0=待评分,1=已完成,2=失败)
private Integer scoringStatus;
}
@Override

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

@ -0,0 +1,294 @@
package com.project.exam.domain.job;
import com.project.exam.domain.entity.ExamRecordEntity;
import com.project.exam.mapper.ExamRecordMapper;
import com.project.interaction.domain.dto.AiScoringRequestDTO;
import com.project.interaction.domain.dto.AiScoringResponseDTO;
import com.project.interaction.domain.service.PostToAiScoringDomainService;
import com.project.question.domain.entity.QuestionEntity;
import com.project.information.domain.entity.KnowledgePointEntity;
import com.project.information.domain.service.KnowledgePointBaseService;
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.task.mapper.TaskMapper;
import com.project.task.mapper.TaskUserMapper;
import com.project.task.domain.service.TaskBaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 简答题异步评分定时任务
* 扫描已提交但简答题尚未评分的考试记录逐题调用算法评分
*/
@Service
@Slf4j
public class ShortAnswerScoringJob {
@Autowired
private ExamRecordMapper examRecordMapper;
@Autowired
private PostToAiScoringDomainService postToAiScoringDomainService;
@Autowired
private QuestionBaseService questionBaseService;
@Autowired
private KnowledgePointBaseService knowledgePointBaseService;
@Autowired
private TaskMapper taskMapper;
@Autowired
private TaskUserMapper taskUserMapper;
@Autowired
private TaskBaseService taskBaseService;
/** 每批最多处理多少道简答题 */
@Value("${scoring.batch-size:5}")
private int batchSize;
@Override
public String toString() {
return "ShortAnswerScoringJob{" +
"batchSize=" + batchSize +
'}';
}
/**
* 每60秒执行一次扫描待评分的简答题
*/
@Scheduled(fixedRateString = "${scoring.interval-ms:60000}")
public void processPendingScoring() {
log.debug(">>> [简答题评分任务] 开始扫描待评分简答题, batchSize={}", batchSize);
try {
// 查询已提交的考试记录
List<ExamRecordEntity> records = examRecordMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ExamRecordEntity>()
.isNotNull(ExamRecordEntity::getSubmitTime)
.orderByAsc(ExamRecordEntity::getSubmitTime));
if (records.isEmpty()) {
return;
}
int processed = 0;
for (ExamRecordEntity record : records) {
if (processed >= batchSize) break;
List<ExamRecordEntity.QuestionSnapshot> snapshots = record.getAnswerSnapshot();
if (snapshots == null || snapshots.isEmpty()) continue;
boolean updated = false;
for (int i = 0; i < snapshots.size(); i++) {
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;
log.info(">>> [简答题评分任务] 开始评分, recordId={}, questionId={}",
record.getId(), snapshot.getQuestionId());
// 调用算法评分
scoreOne(snapshot, record);
processed++;
updated = true;
}
if (updated) {
recalcTotalScore(record);
examRecordMapper.updateById(record);
log.info(">>> [简答题评分任务] recordId={} 快照已更新, 总分={}", record.getId(), record.getScore());
}
}
if (processed > 0) {
log.info(">>> [简答题评分任务] 本轮完成 {} 道简答题评分", processed);
}
} catch (Exception e) {
log.error(">>> [简答题评分任务] 执行异常", e);
}
}
private void scoreOne(ExamRecordEntity.QuestionSnapshot snapshot, ExamRecordEntity record) {
try {
// 查询任务配置(简答题分值、及格线)
double shortAnswerScore = 0.0;
try {
Long taskUserId = record.getTaskUserId();
if (taskUserId != null) {
com.project.task.domain.entity.TaskUserEntity tu = taskUserMapper.selectById(taskUserId);
if (tu != null && tu.getTaskId() != null) {
TaskEntity task = taskMapper.selectById(tu.getTaskId());
if (task != null) {
shortAnswerScore = task.getShortAnswerScore() != null ? task.getShortAnswerScore() : 0.0;
}
}
}
} catch (Exception e) {
log.warn(">>> [简答题评分任务] 查询任务配置失败, 使用0", e);
}
// 从数据库查询题目(得分点 + 产品线名称)
QuestionEntity question = questionBaseService.getById(snapshot.getQuestionId());
String products = "";
if (question != null && question.getKpIdList() != null && !question.getKpIdList().isEmpty()) {
try {
KnowledgePointEntity kp = knowledgePointBaseService.getById(question.getKpIdList().get(0));
if (kp != null && kp.getParseName() != null) {
products = kp.getParseName();
}
} catch (Exception e) {
log.warn(">>> [简答题评分任务] 查询 products 失败", e);
}
}
// 构建请求
AiScoringRequestDTO request = new AiScoringRequestDTO();
request.setProducts(products);
request.setQuestion(snapshot.getQuestionContent());
request.setUserAnswer(snapshot.getUserAnswer());
List<String> options = new ArrayList<>();
if (question != null && question.getQuestionDetail() != null
&& question.getQuestionDetail().getScoringPoints() != null) {
for (QuestionEntity.ScoringPoint sp : question.getQuestionDetail().getScoringPoints()) {
options.add(sp.getContent());
}
}
int totalPoints = options.size();
request.setOptions(options);
// 调用算法
AiScoringResponseDTO response = postToAiScoringDomainService.requestAiScoring(request);
if (response.getSuccess() != null && response.getSuccess()
&& response.getCode() != null && response.getCode() == 0
&& response.getData() != null) {
// 计算各得分点得分和命中点
double pointsValue = totalPoints > 0 ? shortAnswerScore / totalPoints : 0.0;
double aiScore = 0.0;
List<Integer> hitPoints = new ArrayList<>();
StringBuilder comment = new StringBuilder();
for (int i = 0; i < response.getData().size(); i++) {
AiScoringResponseDTO.PointScore ps = response.getData().get(i);
int algoScore = ps.getScore() != null ? ps.getScore() : 0;
double pointScore = pointsValue * algoScore;
aiScore += pointScore;
if (algoScore > 0) {
hitPoints.add(i);
}
if (ps.getReason() != null && !ps.getReason().isEmpty()) {
if (comment.length() > 0) comment.append("; ");
comment.append(ps.getReason());
}
}
snapshot.setAiScore(aiScore);
snapshot.setAiComment(comment.toString());
snapshot.setHitPoints(hitPoints);
snapshot.setScoringStatus(1);
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);
}
} catch (Exception e) {
log.error(">>> [简答题评分任务] 评分异常, questionId={}", snapshot.getQuestionId(), e);
snapshot.setScoringStatus(2);
}
}
private void recalcTotalScore(ExamRecordEntity record) {
List<ExamRecordEntity.QuestionSnapshot> snapshots = record.getAnswerSnapshot();
if (snapshots == null || snapshots.isEmpty()) return;
double total = 0.0;
boolean allShortDone = true;
for (ExamRecordEntity.QuestionSnapshot s : snapshots) {
boolean isShort = QuestionTypeEnum.SHORT_ANSWER.getValue().equals(s.getType());
if (isShort) {
if (!Objects.equals(1, s.getScoringStatus())) {
allShortDone = false;
break;
}
total += s.getAiScore() != null ? s.getAiScore() : 0.0;
} else {
if (s.getIsRight() != null && s.getIsRight()) {
total += s.getScore() != null ? s.getScore() : 0.0;
}
}
}
if (allShortDone) {
record.setScore(total);
// 查询任务及格线
double passScore = 60.0;
TaskEntity task = null;
com.project.task.domain.entity.TaskUserEntity tu = null;
try {
Long taskUserId = record.getTaskUserId();
if (taskUserId != null) {
tu = taskUserMapper.selectById(taskUserId);
if (tu != null && tu.getTaskId() != null) {
task = taskMapper.selectById(tu.getTaskId());
if (task != null && task.getPassScore() != null) {
passScore = task.getPassScore();
}
}
}
} catch (Exception e) {
log.warn(">>> [简答题评分任务] 查询及格线失败,使用默认60分", e);
}
boolean oldPass = record.getPass() != null && record.getPass();
boolean newPass = total >= passScore;
record.setPass(newPass);
// 如果及格状态变了,更新相关冗余字段
if (oldPass != newPass && task != null && tu != null) {
try {
if (newPass) {
// 不及格→及格:增加 pass_num,更新用户状态
taskBaseService.update()
.setSql("pass_num = pass_num + 1")
.eq("id", task.getId())
.update();
if (!TaskUserStatusEnum.Pass.getValue().equals(tu.getStatus())) {
tu.setStatus(TaskUserStatusEnum.Pass.getValue());
tu.setLastRecordId(record.getId());
taskUserMapper.updateById(tu);
}
}
// 及格→不及格:理论上不会发生(简答题只加分不减分),暂不处理
} catch (Exception e) {
log.error(">>> [简答题评分任务] 更新及格状态失败, recordId={}", record.getId(), e);
}
}
}
}
}

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

@ -75,6 +75,7 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService {
scoringContext.setTask(task);
for (ExamRecordEntity.QuestionSnapshot snapshot : snapshotList) {
double questionWeight = getQuestionWeight(snapshot.getType(), task);
// 设置题目分值到快照(DTO转换用)
ExamRecordDTO.QuestionSnapshotDTO tempDto = new ExamRecordDTO.QuestionSnapshotDTO();
tempDto.setQuestionId(snapshot.getQuestionId());
@ -82,18 +83,28 @@ public class SubmitPaperDomainServiceImpl implements SubmitPaperDomainService {
tempDto.setQuestionContent(snapshot.getQuestionContent());
tempDto.setUserAnswer(snapshot.getUserAnswer());
tempDto.setRightAnswer(snapshot.getRightAnswer());
tempDto.setScore(getQuestionWeight(snapshot.getType(), task));
// 根据题型获取对应策略并判分
tempDto.setScore(questionWeight);
// 客观题即时出分,简答题异步评分
if (QuestionTypeEnum.SHORT_ANSWER.getValue().equals(snapshot.getType())) {
// 简答题:标记待评分,跳过同步调用
snapshot.setIsRight(false);
snapshot.setAiScore(0.0);
snapshot.setScore(tempDto.getScore());
snapshot.setScoringStatus(0);
} else {
// 客观题:策略判分(比对答案)
ScoringStrategy strategy = scoringStrategyFactory.getStrategy(snapshot.getType());
strategy.score(tempDto, scoringContext, task.getId());
// 回写判分结果到快照
snapshot.setIsRight(tempDto.getIsRight());
snapshot.setScore(questionWeight);
snapshot.setAiScore(tempDto.getAiScore());
snapshot.setAiComment(tempDto.getAiComment());
snapshot.setHitPoints(tempDto.getHitPoints());
}
}
// 汇总总分:客观题 + 简答题
finalScore = scoringContext.getTotalScore();

41
src/main/java/com/project/exam/domain/service/strategy/SubjectiveScoringStrategy.java

@ -53,7 +53,7 @@ public class SubjectiveScoringStrategy implements ScoringStrategy {
List<String> options = new ArrayList<>();
if (question != null && question.getQuestionDetail() != null
&& question.getQuestionDetail().getScoringPoints() != null) {
for (QuestionEntity.QuestionDetail.ScoringPoint sp : question.getQuestionDetail().getScoringPoints()) {
for (QuestionEntity.ScoringPoint sp : question.getQuestionDetail().getScoringPoints()) {
options.add(sp.getContent());
}
}
@ -71,17 +71,36 @@ public class SubjectiveScoringStrategy implements ScoringStrategy {
// 调用AI阅卷
AiScoringResponseDTO response = postToAiScoringDomainService.requestAiScoring(request);
if (response.getStatus() == 1 && response.getHitPoints() != null) {
// 计算得分:命中数 / 总数 × 题目分值
int hitCount = response.getHitPoints().size();
int totalCount = response.getTotalPoints();
if (response.getSuccess() != null && response.getSuccess()
&& response.getCode() != null && response.getCode() == 0
&& response.getData() != null) {
// 计算得分
int hitCount = 0;
int totalCount = response.getData().size();
StringBuilder comment = new StringBuilder();
List<Integer> hitPoints = new ArrayList<>();
for (int i = 0; i < response.getData().size(); i++) {
AiScoringResponseDTO.PointScore ps = response.getData().get(i);
Integer algoScore = ps.getScore();
if (algoScore != null && algoScore > 0) {
hitCount++;
hitPoints.add(i);
}
if (ps.getReason() != null && !ps.getReason().isEmpty()) {
if (comment.length() > 0) {
comment.append("; ");
}
comment.append(ps.getReason());
}
}
double questionScore = snapshot.getScore() != null ? snapshot.getScore() : 0.0;
double aiScore = totalCount > 0 ? questionScore * hitCount / totalCount : 0.0;
// 更新快照
snapshot.setAiScore(aiScore);
snapshot.setAiComment(response.getComment());
snapshot.setHitPoints(response.getHitPoints());
snapshot.setAiComment(comment.toString());
snapshot.setHitPoints(hitPoints);
snapshot.setUserScore(aiScore);
// 累加简答题得分
@ -89,14 +108,14 @@ public class SubjectiveScoringStrategy implements ScoringStrategy {
// 更新日志
logEntity.setAiScore(aiScore);
logEntity.setHitPoints(response.getHitPoints());
logEntity.setHitPoints(hitPoints);
logEntity.setTotalPoints(totalCount);
logEntity.setAiComment(response.getComment());
logEntity.setAiComment(comment.toString());
logEntity.setStatus(1); // 成功
} else {
log.error(">>> [AI阅卷] 评分失败, questionId={}, error={}", questionId, response.getErrorMsg());
log.error(">>> [AI阅卷] 评分失败, questionId={}, message={}", questionId, response.getMessage());
logEntity.setStatus(2); // 失败
logEntity.setErrorMsg(response.getErrorMsg());
logEntity.setErrorMsg(response.getMessage());
}
} catch (Exception e) {
log.error(">>> [AI阅卷] 异常, questionId={}", questionId, e);

10
src/main/java/com/project/information/application/impl/KnowledgePointApplicationServiceImpl.java

@ -218,7 +218,15 @@ public class KnowledgePointApplicationServiceImpl implements KnowledgePointAppli
KnowledgePointEntity entity = new KnowledgePointEntity();
entity.setContent((String) item.get("knowledge"));
entity.setParseName(fileName);
// parseName 取资料名称(InformationEntity.name),而非原始文件名
String infoName = fileName;
try {
InformationEntity info = informationBaseService.getById(id);
if (info != null && info.getName() != null && !info.getName().isEmpty()) {
infoName = info.getName();
}
} catch (Exception ignored) {}
entity.setParseName(infoName);
entity.setKnowledgeType(hasYellow ? 0 : 1);
entity.setExamFocusList(focusList);
entity.setInformationId(id);

38
src/main/java/com/project/interaction/domain/dto/AiScoringResponseDTO.java

@ -1,5 +1,6 @@
package com.project.interaction.domain.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@ -7,22 +8,39 @@ import java.util.List;
/**
* AI阅卷响应 DTO
* 算法服务返回的评分结果
* 接口/v1/score/short_answer
* {
* "code": 0,
* "success": true,
* "message": "string",
* "data": [{"score_point": "string", "score": 0, "reason": "string"}]
* }
*/
@Data
public class AiScoringResponseDTO {
/** 命中的得分点index列表 */
private List<Integer> hitPoints;
/** 状态码(0=成功) */
private Integer code;
/** 总得分点数 */
private Integer totalPoints;
/** 是否成功 */
private Boolean success;
/** AI评语 */
private String comment;
/** 消息 */
private String message;
/** 处理状态:1-成功,2-失败 */
private Integer status;
/** 各得分点评分结果 */
private List<PointScore> data;
/** 错误信息(失败时有值) */
private String errorMsg;
@Data
public static class PointScore {
/** 得分点内容 */
@JsonProperty("score_point")
private String scorePoint;
/** 该得分点得分 */
private Integer score;
/** 评分理由 */
private String reason;
}
}

15
src/main/java/com/project/interaction/domain/service/impl/PostToAiScoringDomainServiceImpl.java

@ -33,8 +33,7 @@ public class PostToAiScoringDomainServiceImpl implements PostToAiScoringDomainSe
@Override
public AiScoringResponseDTO requestAiScoring(AiScoringRequestDTO request) {
try {
log.info(">>> [AI阅卷] 正在请求AI阅卷, questionId={}, examRecordId={}",
request.getQuestionId(), request.getExamRecordId());
log.info(">>> [AI阅卷] 正在请求AI阅卷, question={}", request.getQuestion());
String responseBody = algorithmWebClient.post()
.uri(scoringUrl)
@ -44,19 +43,19 @@ public class PostToAiScoringDomainServiceImpl implements PostToAiScoringDomainSe
.timeout(Duration.ofSeconds(60))
.block();
log.info(">>> [AI阅卷] 算法服务返回, questionId={}, response={}",
request.getQuestionId(), responseBody);
log.info(">>> [AI阅卷] 算法服务返回, question={}, response={}",
request.getQuestion(), responseBody);
// 解析响应
AiScoringResponseDTO response = objectMapper.readValue(responseBody, AiScoringResponseDTO.class);
response.setStatus(1); // 成功
return response;
} catch (Exception e) {
log.error(">>> [AI阅卷] 算法服务调用异常, questionId={}", request.getQuestionId(), e);
log.error(">>> [AI阅卷] 算法服务调用异常, question={}", request.getQuestion(), e);
AiScoringResponseDTO errorResponse = new AiScoringResponseDTO();
errorResponse.setStatus(2); // 失败
errorResponse.setErrorMsg(e.getMessage());
errorResponse.setCode(1);
errorResponse.setSuccess(false);
errorResponse.setMessage(e.getMessage());
return errorResponse;
}
}

4
src/main/java/com/project/task/domain/dto/TaskDTO.java

@ -100,6 +100,10 @@ public class TaskDTO extends BaseDTO {
*/
private Integer notParticipatedNum;
private String classificationTag;
private String examModeTag;
// =============== V1.1 新增字段 结束 ============
}

28
src/main/java/com/project/task/domain/service/impl/SearchTaskDomainServiceImpl.java

@ -2,6 +2,7 @@ package com.project.task.domain.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -9,6 +10,10 @@ import com.project.base.domain.exception.MissingParameterException;
import com.project.base.domain.result.PageResult;
import com.project.base.domain.result.Result;
import com.project.base.domain.utils.PageConverter;
import com.project.classicpaper.domain.entity.ClassicCategoryEntity;
import com.project.classicpaper.domain.entity.ClassicPaperSetEntity;
import com.project.classicpaper.domain.service.ClassicCategoryBaseService;
import com.project.classicpaper.domain.service.ClassicPaperSetBaseService;
import com.project.ding.domain.dto.UserDTO;
import com.project.information.application.ProductLineApplicationService;
import com.project.information.domain.entity.ProductLineEntity;
@ -16,11 +21,13 @@ import com.project.information.domain.service.ProductLineBaseService;
import com.project.task.domain.dto.TaskDTO;
import com.project.task.domain.entity.TaskEntity;
import com.project.task.domain.entity.TaskUserEntity;
import com.project.task.domain.enums.ExamModeEnum;
import com.project.task.domain.enums.TaskStatusEnum;
import com.project.task.domain.param.TaskParam;
import com.project.task.domain.service.SearchTaskDomainService;
import com.project.task.domain.service.TaskUserBaseService;
import com.project.task.mapper.TaskMapper;
import io.vavr.control.Try;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -43,6 +50,12 @@ public class SearchTaskDomainServiceImpl implements SearchTaskDomainService {
@Autowired
private ProductLineBaseService productLineBaseService;
@Autowired
private ClassicPaperSetBaseService classicPaperSetBaseService;
@Autowired
private ClassicCategoryBaseService classicCategoryBaseService;
@Override
public Result<PageResult<TaskDTO>> search(TaskParam param) throws Exception {
LambdaQueryWrapper<TaskEntity> queryWrapper = new LambdaQueryWrapper<>();
@ -136,7 +149,22 @@ public class SearchTaskDomainServiceImpl implements SearchTaskDomainService {
String subLineName = productLineApplicationService.searchLineNameById(dto.getSubLineId());
dto.setSubLineName(subLineName);
// 题目类型
dto.setExamModeTag(Try.of(() -> EnumUtil.getBy(ExamModeEnum::getValue, dto.getExamMode()))
.getOrElse(ExamModeEnum.GENERATIVE)
.getDesc());
if (ExamModeEnum.GENERATIVE.getValue().equals(dto.getExamMode())) {
// 生成式
ProductLineEntity byId = productLineBaseService.getById(dto.getSubLineId());
dto.setClassificationTag(String.format("%s - %s" , byId.getParentName() , byId.getName()));
} else {
// 经典
ClassicPaperSetEntity byId = classicPaperSetBaseService.getById(dto.getClassicPaperSetId());
ClassicCategoryEntity classicCategoryBaseServiceById = classicCategoryBaseService.getById(byId.getClassicCategoryId());
ClassicCategoryEntity classicCategoryBaseServiceByParentId = classicCategoryBaseService.getById(classicCategoryBaseServiceById.getParentId());
dto.setClassificationTag(String.format("%s - %s" , classicCategoryBaseServiceByParentId.getName() , byId.getName()));
}
dto.setParticipantUserDTOList(participantUserDTOList);
dto.setParticipantUserIdList(participantUserDTOList.stream().map(UserDTO::getId).toList());
dto.setTotalQuestionNum(dto.getSingleChoiceNum() + dto.getMultipleChoiceNum() + dto.getTrueFalseNum());

7
src/main/resources/application-dev.yml

@ -94,6 +94,13 @@ question:
queue:
# 重试间隔(秒)
retry-interval: 60
# 简答题异步评分
scoring:
# 每批最多处理多少道简答题
batch-size: 5
# 执行间隔(毫秒),每60秒扫描一次
interval-ms: 60000
scheduled-task:
owner: test

6
src/main/resources/application-test.yml

@ -109,3 +109,9 @@ classicpaper:
# 是否启用真实算法(true=使用算法服务)
algorithm:
enabled: true
# 简答题异步评分
scoring:
# 每批最多处理多少道简答题
batch-size: 5
# 执行间隔(毫秒),每60秒扫描一次
interval-ms: 60000
Loading…
Cancel
Save