From 5eebbe678b1e859c9e74572af7e0b8430111c6e0 Mon Sep 17 00:00:00 2001 From: luogw <3132758203@qq.com> Date: Thu, 5 Mar 2026 17:32:25 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=94=E8=B0=83=E7=AE=97=E6=B3=95=E7=94=9F?= =?UTF-8?q?=E6=88=90=E9=A2=98=E7=9B=AE=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exam/domain/job/examHandleJob.java | 4 +- .../dto/GenerateQuestionRequestDTO.java | 72 +++++++++ .../dto/GenerateQuestionResponseDTO.java | 36 +++++ ...stToGenerateQuestionDomainServiceImpl.java | 144 +++++++++++++++--- .../impl/MilvusApplicationServiceImpl.java | 4 +- .../milvus/domain/dto/TitleVector.java | 11 +- .../impl/CheckMilvusDomainServiceImpl.java | 17 ++- .../service/impl/MilvusDemoServiceImpl.java | 4 +- src/main/resources/application-dev.yml | 4 +- src/main/resources/application.yml | 2 + 10 files changed, 263 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/project/interaction/domain/dto/GenerateQuestionRequestDTO.java create mode 100644 src/main/java/com/project/interaction/domain/dto/GenerateQuestionResponseDTO.java diff --git a/src/main/java/com/project/exam/domain/job/examHandleJob.java b/src/main/java/com/project/exam/domain/job/examHandleJob.java index 000a90d..03d7384 100644 --- a/src/main/java/com/project/exam/domain/job/examHandleJob.java +++ b/src/main/java/com/project/exam/domain/job/examHandleJob.java @@ -39,9 +39,9 @@ public class examHandleJob { private static final long EXAM_TIMEOUT_BUFFER_SECONDS = 10; /** - * 每小时执行一次,处理考试时间结束但未提交的考试记录 + * 每天凌晨执行一次,处理考试时间结束但未提交的考试记录 */ - @Scheduled(cron = "0 0 * * * ?") + @Scheduled(cron = "0 0 0 * * ?") public void handleUnsubmittedExams() { log.info(">>> [考试异常处理任务] 启动执行..."); diff --git a/src/main/java/com/project/interaction/domain/dto/GenerateQuestionRequestDTO.java b/src/main/java/com/project/interaction/domain/dto/GenerateQuestionRequestDTO.java new file mode 100644 index 0000000..54999ab --- /dev/null +++ b/src/main/java/com/project/interaction/domain/dto/GenerateQuestionRequestDTO.java @@ -0,0 +1,72 @@ +package com.project.interaction.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 算法服务 - 生成题目请求 DTO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class GenerateQuestionRequestDTO { + + /** + * 聚类信息 + */ + @JsonProperty("cluster") + private ClusterInfo cluster; + + /** + * 生成题目数量 + */ + @JsonProperty("num_questions") + private Integer numQuestions; + + /** + * 题目类型列表 + */ + @JsonProperty("question_types") + private List questionTypes; + + /** + * 聚类信息内部类 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ClusterInfo { + + /** + * 知识点簇ID + */ + @JsonProperty("cluster_id") + private Long clusterId; + + /** + * 考试任务ID + */ + @JsonProperty("task_id") + private Long taskId; + + /** + * 知识点内容数组 + */ + @JsonProperty("source_text") + private List sourceText; + + /** + * 知识点ID集合 + */ + @JsonProperty("source_id") + private List sourceId; + } +} + diff --git a/src/main/java/com/project/interaction/domain/dto/GenerateQuestionResponseDTO.java b/src/main/java/com/project/interaction/domain/dto/GenerateQuestionResponseDTO.java new file mode 100644 index 0000000..54c1ad3 --- /dev/null +++ b/src/main/java/com/project/interaction/domain/dto/GenerateQuestionResponseDTO.java @@ -0,0 +1,36 @@ +package com.project.interaction.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 算法服务 - 生成题目响应 DTO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class GenerateQuestionResponseDTO { + + /** + * 任务ID(算法服务返回的,用于后续查询结果) + */ + @JsonProperty("task_id") + private String taskId; + + /** + * 任务状态:pending、processing、completed、failed + */ + @JsonProperty("status") + private String status; + + /** + * 返回消息 + */ + @JsonProperty("message") + private String message; +} + diff --git a/src/main/java/com/project/interaction/domain/service/impl/PostToGenerateQuestionDomainServiceImpl.java b/src/main/java/com/project/interaction/domain/service/impl/PostToGenerateQuestionDomainServiceImpl.java index b487905..52f641b 100644 --- a/src/main/java/com/project/interaction/domain/service/impl/PostToGenerateQuestionDomainServiceImpl.java +++ b/src/main/java/com/project/interaction/domain/service/impl/PostToGenerateQuestionDomainServiceImpl.java @@ -1,52 +1,117 @@ package com.project.interaction.domain.service.impl; import cn.hutool.core.util.RandomUtil; +import com.project.information.domain.service.KnowledgePointBaseService; +import com.project.interaction.domain.dto.GenerateQuestionRequestDTO; +import com.project.interaction.domain.dto.GenerateQuestionResponseDTO; import com.project.interaction.domain.service.PostToGenerateQuestionDomainService; import com.project.question.domain.dto.QuestionDTO; import com.project.question.domain.dto.TaskKnowledgePointDTO; -import com.project.question.domain.service.QuestionBaseService; -import com.project.question.domain.service.QuestionKpRelBaseService; import com.project.question.domain.service.SaveQuestionDomainService; import com.project.task.domain.enums.QuestionTypeEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import java.time.Duration; import java.util.*; import java.util.stream.Collectors; @Service +@Slf4j public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQuestionDomainService { + @Resource(name = "algorithmWebClient") + private WebClient algorithmWebClient; - @Autowired - private QuestionBaseService questionBaseService; - + @Value("${algo.generateQuestionUrl:/v1/generate/questions_from_cluster}") + private String generateQuestionUrl; - @Autowired - private QuestionKpRelBaseService questionKpRelBaseService; + @Value("${algo.apiUrl:http://172.16.25.174:8000}") + private String apiUrl; @Autowired private SaveQuestionDomainService saveQuestionDomainService; - private final String[] optionListStr = {"A" , "B" , "C" , "D"}; - - private final String[] trueFalseListStr = {"A" , "B"}; + private final String[] optionListStr = {"A", "B", "C", "D"}; + private final String[] trueFalseListStr = {"A", "B"}; - private final List list = Arrays.asList(0,1,2,3); - + private final List list = Arrays.asList(0, 1, 2, 3); + /** + * 生成题目 + * 1. 先尝试调用算法服务生成真实题目 + * 2. 如果算法服务失败,使用熔断降级方案生成默认题目 + */ @Override public void postToGenerateQuestion(List dtoList, QuestionTypeEnum questionType, int num) throws Exception { -// System.out.println(String.format("<=======生成了%d道%s, 结合了知识点【%s】属于%s=======>" , -// num , -// questionType.getDescription() , -// String.join("," , dtoList.stream().map(dto -> dto.getId().toString()).toList()) , -// dtoList.size() > 1 ? QuestionSourceTypeEnum.Multi_Concept.getDesc() : QuestionSourceTypeEnum.Single_Concept.getDesc())); + Long taskId = Optional.of(dtoList.stream().findFirst()) + .get().orElse(new TaskKnowledgePointDTO()).getTaskId(); + Long clusterId = Optional.of(dtoList.stream().findFirst()) + .get().orElse(new TaskKnowledgePointDTO()).getClusterId(); + + log.info(">>> [题目生成] 开始生成题目, TaskId: {}, ClusterId: {}, 题型: {}, 数量: {}", + taskId, clusterId, questionType.getDescription(), num); + + try { + //尝试调用算法服务生成题目 + GenerateQuestionResponseDTO response = callAlgorithmService(dtoList, questionType, num); + + if (response != null && StringUtils.isNotBlank(response.getMessage()) && response.getMessage().contains("success")) { + log.info(">>> [题目生成] 算法服务接受任务成功, 任务: {}, 状态: {}, 信息:{}", + response.getTaskId(), response.getStatus(),response.getMessage()); + return; + } + + } catch (Exception e) { + log.warn(">>> [题目生成] 调用算法服务失败,启动熔断降级方案, TaskId: {}, 错误: {}", + taskId, e.getMessage()); + } - // todo 算法交互,请求生成问题 - // todo 先写死,写死问题和选项 + //熔断降级:算法服务失败时使用硬编码生成默认题目 + log.info(">>> [题目生成] 启用熔断降级,生成默认题目"); + generateFallbackQuestions(dtoList, questionType, num); + } + + /** + * 调用算法服务生成题目 + */ + private GenerateQuestionResponseDTO callAlgorithmService(List dtoList, + QuestionTypeEnum questionType, + int num) { + // 获取知识点内容 + List kpIds = dtoList.stream().map(TaskKnowledgePointDTO::getId).collect(Collectors.toList()); + List sourceTexts = dtoList.stream().map(TaskKnowledgePointDTO::getContent).collect(Collectors.toList()); + + Long taskId = dtoList.get(0).getTaskId(); + Long clusterId = dtoList.get(0).getClusterId(); + + // 构建请求 + GenerateQuestionRequestDTO requestDTO = GenerateQuestionRequestDTO.builder() + .cluster(GenerateQuestionRequestDTO.ClusterInfo.builder() + .clusterId(clusterId) + .taskId(taskId) + .sourceText(sourceTexts) + .sourceId(kpIds) + .build()) + .numQuestions(num) + .questionTypes(Collections.singletonList(questionType.name().toLowerCase())) + .build(); + + // 调用算法服务 + return generateQuestions(requestDTO); + } + /** + * 熔断降级:生成硬编码的默认题目当算法服务失败时使用 + */ + private void generateFallbackQuestions(List dtoList, + QuestionTypeEnum questionType, int num) throws Exception { for (int i = 0; i < num; i++) { QuestionDTO questionDTO = new QuestionDTO(); questionDTO.setKpIdList(dtoList.stream().map(TaskKnowledgePointDTO::getId).collect(Collectors.toList())); @@ -99,7 +164,46 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu } saveQuestionDomainService.save(questionDTO); } + } - + private GenerateQuestionResponseDTO generateQuestions(GenerateQuestionRequestDTO requestDTO) { + try { + log.info(">>> [题目生成] 向算法服务发送请求, TaskId: {}, ClusterId: {}, 生成数量: {}", + requestDTO.getCluster().getTaskId(), + requestDTO.getCluster().getClusterId(), + requestDTO.getNumQuestions()); + + // 发送请求到算法服务 + GenerateQuestionResponseDTO response = algorithmWebClient.post() + .uri(apiUrl+generateQuestionUrl) + .bodyValue(requestDTO) + .retrieve() + .onStatus( + status -> !status.is2xxSuccessful(), + resp -> resp.bodyToMono(String.class) + .flatMap(body -> reactor.core.publisher.Mono.error( + new RuntimeException("算法服务返回错误: " + body) + )) + ) + .bodyToMono(GenerateQuestionResponseDTO.class) + .timeout(Duration.ofSeconds(30)) // 30秒超时 + .retry(2) // 失败重试2次 + .block(); // 同步等待结果 + + log.info(">>> [题目生成] 算法服务成功接收任务, TaskId: {}, AlgoTaskId: {}, 状态: {}", + requestDTO.getCluster().getTaskId(), + response != null ? response.getTaskId() : "null", + response != null ? response.getStatus() : "null"); + + return response; + + } catch (Exception e) { + log.error(">>> [题目生成] 调用算法服务异常, TaskId: {}, 错误: {}", + requestDTO.getCluster().getTaskId(), + e.getMessage(), e); + throw new RuntimeException("调用算法服务生成题目失败: " + e.getMessage(), e); + } } } + + diff --git a/src/main/java/com/project/milvus/application/impl/MilvusApplicationServiceImpl.java b/src/main/java/com/project/milvus/application/impl/MilvusApplicationServiceImpl.java index 0255523..62fe8a9 100644 --- a/src/main/java/com/project/milvus/application/impl/MilvusApplicationServiceImpl.java +++ b/src/main/java/com/project/milvus/application/impl/MilvusApplicationServiceImpl.java @@ -45,7 +45,7 @@ public class MilvusApplicationServiceImpl implements MilvusApplicationService { // 1. 基础参数校验(在锁外,快速失败) checkMilvusDomainService.checkBasic(title); - String lockKey = buildLockKey(title.getAtomIdsList()); + String lockKey = buildLockKey(title.getTaskKpIdsList()); RLock lock = redissonClient.getLock(lockKey); boolean locked = false; @@ -103,7 +103,7 @@ public class MilvusApplicationServiceImpl implements MilvusApplicationService { private QuestionDTO buildQuestionDTO(TitleVector title) { QuestionDTO questionDTO = new QuestionDTO(); questionDTO.setUseStatus(false); - questionDTO.setKpIdList(title.getAtomIdsList()); + questionDTO.setKpIdList(title.getTaskKpIdsList()); questionDTO.setUniqueHash(title.getUniqueHash()); questionDTO.setClusterId(title.getClusterId()); questionDTO.setQuestionType(title.QuestionDetailDTO.getType()); diff --git a/src/main/java/com/project/milvus/domain/dto/TitleVector.java b/src/main/java/com/project/milvus/domain/dto/TitleVector.java index a45303e..9fe2a1a 100644 --- a/src/main/java/com/project/milvus/domain/dto/TitleVector.java +++ b/src/main/java/com/project/milvus/domain/dto/TitleVector.java @@ -15,7 +15,8 @@ import java.util.stream.Collectors; public class TitleVector { public Long id; //知识点ID - public List atomIds; + public List taskKpIds; + //题目ID //题目向量 public List titleVector; //题目唯一标识 @@ -27,8 +28,8 @@ public class TitleVector { //题目内容 public QuestionDTO.QuestionDetailDTO QuestionDetailDTO; - public String getAtomIdsHash() { - List sorted = new ArrayList<>(atomIds); + public String getTaskKpIdsHash() { + List sorted = new ArrayList<>(taskKpIds); Collections.sort(sorted); String canonical = sorted.stream() .map(String::valueOf) @@ -37,8 +38,8 @@ public class TitleVector { return DigestUtils.md5Hex(canonical); } - public List getAtomIdsList() { - List sorted = new ArrayList<>(atomIds); + public List getTaskKpIdsList() { + List sorted = new ArrayList<>(taskKpIds); Collections.sort(sorted); return sorted; } diff --git a/src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java b/src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java index e29e737..ff24ddf 100644 --- a/src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java +++ b/src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java @@ -10,7 +10,9 @@ import com.project.milvus.domain.dto.TitleVector; import com.project.milvus.domain.service.CheckMilvusDomainService; import com.project.question.domain.dto.QuestionDTO; import com.project.question.domain.entity.QuestionEntity; +import com.project.question.domain.entity.TaskKnowledgePointEntity; import com.project.question.domain.service.QuestionBaseService; +import com.project.question.domain.service.TaskKnowledgePointBaseService; import com.project.task.domain.enums.QuestionTypeEnum; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.collections4.CollectionUtils; @@ -30,6 +32,8 @@ public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService { private KnowledgePointBaseService knowledgePointBaseService; @Autowired private QuestionBaseService questionBaseService; + @Autowired + private TaskKnowledgePointBaseService taskKnowledgePointBaseService; @Override @@ -38,7 +42,7 @@ public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService { throw new MissingParameterException("请求参数缺失或格式错误"); } - List pointIdsList = title.getAtomIdsList(); + List pointIdsList = title.getTaskKpIdsList(); List titleVectorList = title.getTitleVectorList(); if (CollectionUtil.isEmpty(pointIdsList) || CollectionUtil.isEmpty(titleVectorList)) { @@ -111,8 +115,15 @@ public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService { title.setUniqueHash(questionHash); } public String buildQuestionHash(QuestionDTO.QuestionDetailDTO detailDTO, TitleVector title){ + List taskKpIdsList = title.getTaskKpIdsList(); + List taskKnowledgePointEntities = taskKnowledgePointBaseService.listByIds(taskKpIdsList); + if (CollectionUtils.isEmpty(taskKnowledgePointEntities)) { + throw new IllegalArgumentException("知识点不存在"); // 空值防护 + } + List kpIdsList = taskKnowledgePointEntities.stream().map(TaskKnowledgePointEntity::getAtomId).collect(Collectors.toList()); + //查询知识点并提取非空的 parseName - List knowledgePointEntities = knowledgePointBaseService.listByIds(title.getAtomIdsList()); + List knowledgePointEntities = knowledgePointBaseService.listByIds(kpIdsList); if (CollectionUtils.isEmpty(knowledgePointEntities)) { throw new IllegalArgumentException("知识点不存在"); // 空值防护 } @@ -134,7 +145,7 @@ public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService { //生成最终哈希(拆分拼接逻辑,提升可读性;增加分隔符避免字符串拼接歧义) String rawContent = String.format("%s|%s|%s", StringUtils.isNotBlank(detailDTO.getQuestionContent()) ? detailDTO.getQuestionContent() : "", - StringUtils.isNotBlank(title.getAtomIdsHash()) ? title.getAtomIdsHash() : "", + StringUtils.isNotBlank(title.getTaskKpIdsHash()) ? title.getTaskKpIdsHash() : "", DigestUtils.md5Hex(canonicalKnowledgePoints) ); return DigestUtils.md5Hex(rawContent); diff --git a/src/main/java/com/project/milvus/domain/service/impl/MilvusDemoServiceImpl.java b/src/main/java/com/project/milvus/domain/service/impl/MilvusDemoServiceImpl.java index 16012a0..698c6ec 100644 --- a/src/main/java/com/project/milvus/domain/service/impl/MilvusDemoServiceImpl.java +++ b/src/main/java/com/project/milvus/domain/service/impl/MilvusDemoServiceImpl.java @@ -83,7 +83,7 @@ public class MilvusDemoServiceImpl implements MilvusDemoService { JsonObject vector = new JsonObject(); vector.addProperty("id", title.getId()); - vector.addProperty("point_ids", title.getAtomIdsHash()); + vector.addProperty("point_ids", title.getTaskKpIdsHash()); vector.add("title_vector", title.getTitleVectorJson()); InsertReq insertReq = InsertReq.builder() @@ -113,7 +113,7 @@ public class MilvusDemoServiceImpl implements MilvusDemoService { public List> query(TitleVector title){ QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.findByValue(title.getQuestionDetailDTO().getType()); - String expr = String.format("point_ids == '%s'", title.getAtomIdsHash()); + String expr = String.format("point_ids == '%s'", title.getTaskKpIdsHash()); SearchResp searchReq = client.search(SearchReq.builder() .collectionName(COLLECTION_NAME+questionTypeEnum.getType()) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index aeecc8e..c4a95c2 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -71,4 +71,6 @@ ding: corpId: ding13d71da66ad91ff0f5bf40eda33b7ba0 algo: clusterUrl: /api/algorithm/v1/cluster - baseUrl: / \ No newline at end of file + baseUrl: / + generateQuestionUrl: /v1/generate/questions_from_cluster + apiUrl: http://172.16.25.174:8000 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 572fd28..39a4be5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -75,5 +75,7 @@ ding: algo: clusterUrl: /semantic-cluster baseUrl: http://172.16.204.50:8002 + generateQuestionUrl: /v1/generate/questions_from_cluster + apiUrl: http://172.16.25.174:8000 jwt: secret: "my-very-fixed-and-secure-secret-key-1234567890" \ No newline at end of file