Browse Source

bug修复

master
luogw 7 days ago
parent
commit
823ab56315
  1. 53
      src/main/java/com/project/milvus/application/impl/MilvusApplicationServiceImpl.java
  2. 7
      src/main/java/com/project/milvus/domain/dto/TitleVector.java
  3. 10
      src/main/java/com/project/milvus/domain/service/CheckMilvusDomainService.java
  4. 102
      src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java
  5. 4
      src/main/java/com/project/milvus/domain/service/impl/MilvusDemoServiceImpl.java
  6. 31
      src/main/java/com/project/task/domain/service/impl/SaveOrUpdateTaskDomainServiceImpl.java

53
src/main/java/com/project/milvus/application/impl/MilvusApplicationServiceImpl.java

@ -8,14 +8,18 @@ import com.project.milvus.application.MilvusApplicationService;
import com.project.milvus.domain.dto.TitleVector;
import com.project.milvus.domain.service.CheckMilvusDomainService;
import com.project.milvus.domain.service.MilvusDemoService;
import com.project.question.domain.dto.QuestionDTO;
import com.project.question.domain.entity.QuestionEntity;
import com.project.question.domain.service.QuestionBaseService;
import io.milvus.v2.service.vector.response.SearchResp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
@ -29,9 +33,9 @@ public class MilvusApplicationServiceImpl implements MilvusApplicationService {
@Autowired
private CheckMilvusDomainService checkMilvusDomainService;
@Autowired
private CustomIdGenerator customIdGenerator;
@Autowired
private RedissonClient redissonClient;
@Autowired
private QuestionBaseService questionBaseService;
//相似度阈值
@ -39,22 +43,25 @@ public class MilvusApplicationServiceImpl implements MilvusApplicationService {
private static final String LOCK_KEY = "lock:title:pointIds:";
@Override
@Transactional(rollbackFor = Exception.class)
public void insertTitle(TitleVector title) {
//参数校验
checkMilvusDomainService.check(title);
// 1. 基础参数校验(在锁外,快速失败)
checkMilvusDomainService.checkBasic(title);
String lockKey = buildLockKey(title.getPointIdsList());
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
//重试 3 次
int retry = 3;
try {
while (retry > 0 && !locked) {
// 等待 2s,拿到锁后10s自动释放
// 等待 2s,拿到锁后 10s 自动释放
locked = lock.tryLock(2, 10, TimeUnit.SECONDS);
if (!locked) {
//休眠等待重试
Thread.sleep(100 + new Random().nextInt(100));
retry--;
}
}
@ -62,18 +69,25 @@ public class MilvusApplicationServiceImpl implements MilvusApplicationService {
throw new RuntimeException("当前知识点正在处理,请稍后再试");
}
//唯一性校验
checkMilvusDomainService.checkUnique(title);
//比较相似度
List<List<SearchResp.SearchResult>> query = milvusDemoService.query(title);
if (CollectionUtil.isNotEmpty(query) && CollectionUtil.isNotEmpty(query.get(0))) {
SearchResp.SearchResult searchResult = query.get(0).get(0);
Float score = searchResult.getScore();
if(score.compareTo(SIMILARITY_THRESHOLD) == 1){
if(score.compareTo(SIMILARITY_THRESHOLD) > 0){
throw new MissingParameterException("题目相似度"+ score +",超过阈值");
}
}
//新增数据
title.setId(customIdGenerator.nextId(title));
//保存题目到数据库
QuestionEntity questionEntity = buildQuestionEntity(title);
questionBaseService.save(questionEntity);
//保存向量数据到 Milvus
title.setId(questionEntity.getId());
milvusDemoService.insertRecord(title);
}catch (InterruptedException e) {
@ -87,7 +101,26 @@ public class MilvusApplicationServiceImpl implements MilvusApplicationService {
}
/**
* 构建锁的key
* 构建题目实体
*/
private QuestionEntity buildQuestionEntity(TitleVector title) {
QuestionEntity questionEntity = new QuestionEntity();
questionEntity.setSourceType(0); // 0-单 KP 出题
questionEntity.setUseStatus(false); // 未使用
questionEntity.setKpIdList(title.getPointIdsList());
questionEntity.setUniqueHash(title.getUniqueHash());
questionEntity.setQuestionType(title.QuestionDetailDTO.getType());
// 构建题目详情
QuestionEntity.QuestionDetail detail = new QuestionEntity.QuestionDetail();
BeanUtils.copyProperties(title.QuestionDetailDTO, detail);
questionEntity.setQuestionDetail(detail);
return questionEntity;
}
/**
* 构建锁的 key
*/
private String buildLockKey(List<Long> poinIds){
String collect = poinIds.stream().map(String::valueOf).collect(Collectors.joining(","));

7
src/main/java/com/project/milvus/domain/dto/TitleVector.java

@ -2,6 +2,7 @@ package com.project.milvus.domain.dto;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.project.question.domain.dto.QuestionDTO;
import lombok.Data;
import org.apache.commons.codec.digest.DigestUtils;
@ -17,8 +18,10 @@ public class TitleVector {
public List<Long> pointIds;
//题目向量
public List<Float> titleVector;
//题目类型
public String type;
//题目唯一标识
private String uniqueHash;
//题目内容
public QuestionDTO.QuestionDetailDTO QuestionDetailDTO;
public String getPointIdsHash() {
List<Long> sorted = new ArrayList<>(pointIds);

10
src/main/java/com/project/milvus/domain/service/CheckMilvusDomainService.java

@ -3,5 +3,13 @@ package com.project.milvus.domain.service;
import com.project.milvus.domain.dto.TitleVector;
public interface CheckMilvusDomainService {
void check(TitleVector title);
/**
* 基础参数校验
*/
void checkBasic(TitleVector title);
/**
* 唯一性校验
*/
void checkUnique(TitleVector title);
}

102
src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java

@ -2,21 +2,38 @@ package com.project.milvus.domain.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.EnumUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.project.base.domain.exception.MissingParameterException;
import com.project.information.domain.entity.KnowledgePointEntity;
import com.project.information.domain.service.KnowledgePointBaseService;
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.service.QuestionBaseService;
import com.project.task.domain.enums.QuestionTypeEnum;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
* 校验扩展
*/
@Service
public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService {
@Autowired
private KnowledgePointBaseService knowledgePointBaseService;
@Autowired
private QuestionBaseService questionBaseService;
@Override
public void check(TitleVector title) {
public void checkBasic(TitleVector title) {
if (title == null) {
throw new MissingParameterException("请求参数缺失或格式错误");
}
@ -32,8 +49,85 @@ public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService {
throw new MissingParameterException("向量格式错误");
}
if(!EnumUtil.contains(QuestionTypeEnum.class,title.getType())){
throw new MissingParameterException("题目类型不存在");
// 校验 QuestionDetailDTO 及其内部参数
QuestionDTO.QuestionDetailDTO detailDTO = title.QuestionDetailDTO;
if (detailDTO == null) {
throw new MissingParameterException("题目详情不能为空");
}
if (StringUtils.isBlank(detailDTO.getQuestionContent())) {
throw new MissingParameterException("题干不能为空");
}
if (detailDTO.getType() == null) {
throw new MissingParameterException("题目类型不能为空");
}
// 校验题目类型值是否有效(1=单选题,2=多选题,3=判断题)
if (QuestionTypeEnum.findByValue(detailDTO.getType()) == null) {
throw new MissingParameterException("题目类型值无效");
}
if (CollectionUtil.isEmpty(detailDTO.getOptions())) {
throw new MissingParameterException("选项不能为空");
}
// 校验 options 的每个键值对都不为空
for (Map.Entry<String, String> entry : detailDTO.getOptions().entrySet()) {
if (StringUtils.isBlank(entry.getKey())) {
throw new MissingParameterException("选项键不能为空");
}
if (StringUtils.isBlank(entry.getValue())) {
throw new MissingParameterException("选项值不能为空");
}
}
if (StringUtils.isBlank(detailDTO.getRightAnswer())) {
throw new MissingParameterException("正确答案不能为空");
}
if (StringUtils.isBlank(detailDTO.getAnalysis())) {
throw new MissingParameterException("题目解析不能为空");
}
}
@Override
public void checkUnique(TitleVector title) {
// 题目的 HASH 校验
String questionHash = buildQuestionHash(title.QuestionDetailDTO, title);
QuestionEntity questionEntity = questionBaseService.getOne(new QueryWrapper<QuestionEntity>().eq("unique_hash", questionHash));
if (questionEntity != null) {
throw new IllegalArgumentException("题目已存在"); // 空值防护
}
title.setUniqueHash(questionHash);
}
public String buildQuestionHash(QuestionDTO.QuestionDetailDTO detailDTO, TitleVector title){
//查询知识点并提取非空的 parseName
List<KnowledgePointEntity> knowledgePointEntities = knowledgePointBaseService.listByIds(title.getPointIdsList());
if (CollectionUtils.isEmpty(knowledgePointEntities)) {
throw new IllegalArgumentException("知识点不存在"); // 空值防护
}
//提取非空 parseName 并去重,避免重复拼接
Set<String> parseNameSet = knowledgePointEntities.stream()
.filter(entity -> StringUtils.isNotBlank(entity.getParseName())) // 更严谨的非空判断
.map(KnowledgePointEntity::getParseName)
.collect(Collectors.toSet());
//知识点名称排序(保证拼接顺序固定,避免"a,b"和"b,a"生成不同 hash)
List<String> sortedParseNames = new ArrayList<>(parseNameSet);
Collections.sort(sortedParseNames); // 修正原代码"排序 Set"的语法错误
//规范化拼接知识点名称(固定分隔符,避免歧义)
String canonicalKnowledgePoints = sortedParseNames.stream()
.collect(Collectors.joining(","));
//生成最终哈希(拆分拼接逻辑,提升可读性;增加分隔符避免字符串拼接歧义)
String rawContent = String.format("%s|%s|%s",
StringUtils.isNotBlank(detailDTO.getQuestionContent()) ? detailDTO.getQuestionContent() : "",
StringUtils.isNotBlank(title.getPointIdsHash()) ? title.getPointIdsHash() : "",
DigestUtils.md5Hex(canonicalKnowledgePoints)
);
return DigestUtils.md5Hex(rawContent);
}
}

4
src/main/java/com/project/milvus/domain/service/impl/MilvusDemoServiceImpl.java

@ -79,7 +79,7 @@ public class MilvusDemoServiceImpl implements MilvusDemoService {
* 往collection中插入一条数据
*/
public void insertRecord(TitleVector title) {
QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.valueOf(title.getType());
QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.findByValue(title.getQuestionDetailDTO().getType());
JsonObject vector = new JsonObject();
vector.addProperty("id", title.getId());
@ -111,7 +111,7 @@ public class MilvusDemoServiceImpl implements MilvusDemoService {
*/
@Override
public List<List<SearchResp.SearchResult>> query(TitleVector title){
QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.valueOf(title.getType());
QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.findByValue(title.getQuestionDetailDTO().getType());
String expr = String.format("point_ids == '%s'", title.getPointIdsHash());

31
src/main/java/com/project/task/domain/service/impl/SaveOrUpdateTaskDomainServiceImpl.java

@ -10,7 +10,9 @@ import com.project.base.domain.exception.BusinessErrorException;
import com.project.base.domain.result.Result;
import com.project.ding.domain.entity.UserEntity;
import com.project.ding.domain.service.UserBaseService;
import com.project.information.domain.dto.KnowledgePointStatisticsDTO;
import com.project.information.domain.entity.ProductLineEntity;
import com.project.information.domain.service.GetStatisticsKnowledgePointDomainService;
import com.project.information.domain.service.ProductLineBaseService;
import com.project.task.config.ExamScoreRatioConfig;
import com.project.task.domain.dto.TaskDTO;
@ -51,6 +53,10 @@ public class SaveOrUpdateTaskDomainServiceImpl implements SaveOrUpdateTaskDomain
@Autowired
private InitTaskDomainService initTaskDomainService;
@Autowired
private GetStatisticsKnowledgePointDomainService getStatisticsKnowledgePointDomainService;
@Override
@Transactional(rollbackFor = Exception.class)
@ -156,11 +162,6 @@ public class SaveOrUpdateTaskDomainServiceImpl implements SaveOrUpdateTaskDomain
if (Objects.isNull(dto.getTrueFalseNum())) {
throw new BusinessErrorException("判断题数量不能为空");
}
if (!Validator.isBetween(dto.getSingleChoiceNum() +
dto.getMultipleChoiceNum() +
dto.getTrueFalseNum() , 1, 100)) {
throw new BusinessErrorException("题目数量设置错误");
}
if (CollUtil.isEmpty(dto.getParticipantUserIdList())) {
throw new BusinessErrorException("参与用户不能为空");
}
@ -173,6 +174,26 @@ public class SaveOrUpdateTaskDomainServiceImpl implements SaveOrUpdateTaskDomain
if (Objects.isNull(dto.getVagueGraspNum())) {
throw new BusinessErrorException("模糊掌握知识点数量不能为空");
}
//校验知识点数是否有变化
Result<KnowledgePointStatisticsDTO> statistics = getStatisticsKnowledgePointDomainService.getStatistics(dto.getSubLineId());
if (!Objects.equals(statistics.getData().getAccurateGraspNum() , dto.getAccurateGraspNum()) ||
!Objects.equals(statistics.getData().getVagueGraspNum() , dto.getVagueGraspNum())) {
throw new BusinessErrorException("知识点数有变化,请重新选择关联产品线");
}
// 题目总数
int totalQuestions = dto.getSingleChoiceNum() + dto.getMultipleChoiceNum() + dto.getTrueFalseNum();
// 获取知识点总数用于校验
int totalKnowledgePoints = dto.getAccurateGraspNum() + dto.getVagueGraspNum();
if (totalQuestions > totalKnowledgePoints) {
throw new BusinessErrorException("题目总数(" + totalQuestions + ")不能超过知识点总数(" + totalKnowledgePoints + ")");
}
if (totalQuestions > 100) {
throw new BusinessErrorException("题目总数不能超过 100 道,当前为:" + totalQuestions);
}
if (totalQuestions < 1) {
throw new BusinessErrorException("题目总数不能少于 1 道");
}
}

Loading…
Cancel
Save