Browse Source

联调算法生成题目接口

master
luogw 5 days ago
parent
commit
5eebbe678b
  1. 4
      src/main/java/com/project/exam/domain/job/examHandleJob.java
  2. 72
      src/main/java/com/project/interaction/domain/dto/GenerateQuestionRequestDTO.java
  3. 36
      src/main/java/com/project/interaction/domain/dto/GenerateQuestionResponseDTO.java
  4. 144
      src/main/java/com/project/interaction/domain/service/impl/PostToGenerateQuestionDomainServiceImpl.java
  5. 4
      src/main/java/com/project/milvus/application/impl/MilvusApplicationServiceImpl.java
  6. 11
      src/main/java/com/project/milvus/domain/dto/TitleVector.java
  7. 17
      src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java
  8. 4
      src/main/java/com/project/milvus/domain/service/impl/MilvusDemoServiceImpl.java
  9. 4
      src/main/resources/application-dev.yml
  10. 2
      src/main/resources/application.yml

4
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(">>> [考试异常处理任务] 启动执行...");

72
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<String> 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<String> sourceText;
/**
* 知识点ID集合
*/
@JsonProperty("source_id")
private List<Long> sourceId;
}
}

36
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;
/**
* 任务状态pendingprocessingcompletedfailed
*/
@JsonProperty("status")
private String status;
/**
* 返回消息
*/
@JsonProperty("message")
private String message;
}

144
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<Integer> list = Arrays.asList(0,1,2,3);
private final List<Integer> list = Arrays.asList(0, 1, 2, 3);
/**
* 生成题目
* 1. 先尝试调用算法服务生成真实题目
* 2. 如果算法服务失败使用熔断降级方案生成默认题目
*/
@Override
public void postToGenerateQuestion(List<TaskKnowledgePointDTO> 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<TaskKnowledgePointDTO> dtoList,
QuestionTypeEnum questionType,
int num) {
// 获取知识点内容
List<Long> kpIds = dtoList.stream().map(TaskKnowledgePointDTO::getId).collect(Collectors.toList());
List<String> 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<TaskKnowledgePointDTO> 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);
}
}
}

4
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());

11
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<Long> atomIds;
public List<Long> taskKpIds;
//题目ID
//题目向量
public List<Float> titleVector;
//题目唯一标识
@ -27,8 +28,8 @@ public class TitleVector {
//题目内容
public QuestionDTO.QuestionDetailDTO QuestionDetailDTO;
public String getAtomIdsHash() {
List<Long> sorted = new ArrayList<>(atomIds);
public String getTaskKpIdsHash() {
List<Long> 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<Long> getAtomIdsList() {
List<Long> sorted = new ArrayList<>(atomIds);
public List<Long> getTaskKpIdsList() {
List<Long> sorted = new ArrayList<>(taskKpIds);
Collections.sort(sorted);
return sorted;
}

17
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<Long> pointIdsList = title.getAtomIdsList();
List<Long> pointIdsList = title.getTaskKpIdsList();
List<Float> 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<Long> taskKpIdsList = title.getTaskKpIdsList();
List<TaskKnowledgePointEntity> taskKnowledgePointEntities = taskKnowledgePointBaseService.listByIds(taskKpIdsList);
if (CollectionUtils.isEmpty(taskKnowledgePointEntities)) {
throw new IllegalArgumentException("知识点不存在"); // 空值防护
}
List<Long> kpIdsList = taskKnowledgePointEntities.stream().map(TaskKnowledgePointEntity::getAtomId).collect(Collectors.toList());
//查询知识点并提取非空的 parseName
List<KnowledgePointEntity> knowledgePointEntities = knowledgePointBaseService.listByIds(title.getAtomIdsList());
List<KnowledgePointEntity> 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);

4
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<List<SearchResp.SearchResult>> 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())

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

@ -71,4 +71,6 @@ ding:
corpId: ding13d71da66ad91ff0f5bf40eda33b7ba0
algo:
clusterUrl: /api/algorithm/v1/cluster
baseUrl: /
baseUrl: /
generateQuestionUrl: /v1/generate/questions_from_cluster
apiUrl: http://172.16.25.174:8000

2
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"
Loading…
Cancel
Save