Browse Source

简答题阅卷

master
luoweijian 3 months ago
parent
commit
1f3be85dcd
  1. 7
      pom.xml
  2. 186
      src/main/java/com/project/classicpaper/domain/service/impl/FallbackClassicPaperQuestionGenerator.java
  3. 22
      src/main/java/com/project/information/application/DraftReviewApplicationService.java
  4. 24
      src/main/java/com/project/information/application/UploadFilesApplicationService.java
  5. 106
      src/main/java/com/project/information/application/impl/DraftReviewApplicationServiceImpl.java
  6. 53
      src/main/java/com/project/information/application/impl/GenerateFromFilesApplicationServiceImpl.java
  7. 95
      src/main/java/com/project/information/application/impl/UploadFilesApplicationServiceImpl.java
  8. 34
      src/main/java/com/project/information/controller/InformationController.java
  9. 19
      src/main/java/com/project/information/domain/dto/UploadedFileDTO.java
  10. 15
      src/main/java/com/project/information/domain/enums/ContentCategoryEnum.java
  11. 13
      src/main/java/com/project/information/domain/param/GenerateFromFilesParam.java
  12. 20
      src/main/java/com/project/information/domain/param/UploadFilesParam.java
  13. 52
      src/main/java/com/project/information/domain/service/DraftReviewDomainService.java
  14. 172
      src/main/java/com/project/information/domain/service/impl/DraftReviewDomainServiceImpl.java
  15. 12
      src/main/java/com/project/interaction/controller/InteractionController.java
  16. 16
      src/main/java/com/project/interaction/domain/service/AiExtractCallbackDomainService.java
  17. 16
      src/main/java/com/project/interaction/domain/service/DataExtractCallbackDomainService.java
  18. 71
      src/main/java/com/project/interaction/domain/service/impl/AiExtractCallbackDomainServiceImpl.java
  19. 106
      src/main/java/com/project/interaction/domain/service/impl/DataExtractCallbackDomainServiceImpl.java
  20. 4
      src/main/java/com/project/question/domain/enums/QuestionSourceTypeEnum.java

7
pom.xml

@ -40,6 +40,13 @@
<lombok.version>1.18.30</lombok.version>
</properties>
<dependencies>
<!-- Apache POI (Word文档解析) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- 钉钉官方 Java SDK (新版) -->
<dependency>
<groupId>com.aliyun</groupId>

186
src/main/java/com/project/classicpaper/domain/service/impl/FallbackClassicPaperQuestionGenerator.java

@ -16,8 +16,10 @@ import java.util.*;
import java.util.stream.Collectors;
/**
* 降级实现生成占位题目
* 降级实现基于知识点生成符合结构的题目
* 当算法服务新接口未就绪时使用后续替换为真正的算法调用
*
* 使用方式替换此实现类即可切换到算法服务无需改动调用方
*/
@Component
@Slf4j
@ -26,9 +28,8 @@ public class FallbackClassicPaperQuestionGenerator implements ClassicPaperQuesti
@Autowired
private SaveQuestionDomainService saveQuestionDomainService;
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 static final String[] OPTION_KEYS = {"A", "B", "C", "D"};
private static final String[] TRUE_FALSE_KEYS = {"A", "B"};
@Override
public List<QuestionEntity> generate(List<KnowledgePointEntity> knowledgePoints,
@ -39,71 +40,128 @@ public class FallbackClassicPaperQuestionGenerator implements ClassicPaperQuesti
List<QuestionEntity> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
KnowledgePointEntity kp = knowledgePoints.get(i % knowledgePoints.size());
QuestionEntity saved = generateOne(kp, questionType, i + 1);
result.add(saved);
}
log.info(">>> [经典套题-降级] 生成完成, 共{}题", result.size());
return result;
}
/**
* 生成单道题目并保存
*/
private QuestionEntity generateOne(KnowledgePointEntity kp, QuestionTypeEnum questionType, int seq) throws Exception {
QuestionDTO questionDTO = new QuestionDTO();
questionDTO.setKpIdList(Collections.singletonList(kp.getId()));
questionDTO.setQuestionType(questionType.getValue());
questionDTO.setSourceType(0);
QuestionDTO.QuestionDetailDTO detailDTO = new QuestionDTO.QuestionDetailDTO();
questionDTO.setQuestionDetailDTO(detailDTO);
detailDTO.setType(questionType.getValue());
// 截取知识点内容作为题干素材
String kpContent = kp.getContent();
if (kpContent != null && kpContent.length() > 50) {
kpContent = kpContent.substring(0, 50) + "...";
}
switch (questionType) {
case SINGLE_CHOICE -> buildSingleChoice(detailDTO, kpContent, seq);
case MULTIPLE_CHOICE -> buildMultipleChoice(detailDTO, kpContent, seq);
case TRUE_FALSE -> buildTrueFalse(detailDTO, kpContent, seq);
case SHORT_ANSWER -> buildShortAnswer(detailDTO, kpContent, seq);
default -> throw new IllegalArgumentException("不支持的题型: " + questionType);
}
// 保存题目
Result<QuestionDTO> saveResult = saveQuestionDomainService.save(questionDTO);
QuestionDTO saved = saveResult.getData();
QuestionEntity savedEntity = new QuestionEntity();
savedEntity.setId(saved.getId());
savedEntity.setQuestionType(questionType.getValue());
savedEntity.setKpIdList(saved.getKpIdList());
return savedEntity;
}
QuestionDTO questionDTO = new QuestionDTO();
questionDTO.setKpIdList(Collections.singletonList(kp.getId()));
questionDTO.setQuestionType(questionType.getValue());
questionDTO.setSourceType(0);
QuestionDTO.QuestionDetailDTO detailDTO = new QuestionDTO.QuestionDetailDTO();
questionDTO.setQuestionDetailDTO(detailDTO);
detailDTO.setQuestionContent(String.format("【经典套题】%s - 第%d题(%s)",
kp.getParseName(), i + 1, questionType.getDescription()));
detailDTO.setType(questionType.getValue());
if (QuestionTypeEnum.SINGLE_CHOICE.equals(questionType)) {
int rightAnswerNo = RandomUtil.randomInt(0, 4);
TreeMap<String, String> optionList = new TreeMap<>();
for (int j = 0; j < optionListStr.length; j++) {
optionList.put(optionListStr[j], String.format("选项%s", optionListStr[j]));
if (j == rightAnswerNo) {
detailDTO.setRightAnswer(optionListStr[j]);
detailDTO.setAnalysis(String.format("正确答案是%s", optionListStr[j]));
}
}
detailDTO.setOptions(optionList);
} else if (QuestionTypeEnum.MULTIPLE_CHOICE.equals(questionType)) {
Collections.shuffle(list);
Set<Integer> resultIdx = list.subList(0, 2).stream().collect(Collectors.toSet());
TreeMap<String, String> optionList = new TreeMap<>();
List<String> rightAnswerList = new ArrayList<>();
for (int j = 0; j < optionListStr.length; j++) {
optionList.put(optionListStr[j], String.format("选项%s", optionListStr[j]));
if (resultIdx.contains(j)) {
rightAnswerList.add(optionListStr[j]);
}
}
detailDTO.setRightAnswer(String.join(",", rightAnswerList));
detailDTO.setAnalysis(String.format("正确答案是%s", detailDTO.getRightAnswer()));
detailDTO.setOptions(optionList);
} else if (QuestionTypeEnum.TRUE_FALSE.equals(questionType)) {
int rightAnswerNo = RandomUtil.randomInt(0, 2);
TreeMap<String, String> optionList = new TreeMap<>();
for (int j = 0; j < trueFalseListStr.length; j++) {
optionList.put(trueFalseListStr[j], j == 0 ? "对" : "错");
if (j == rightAnswerNo) {
detailDTO.setRightAnswer(trueFalseListStr[j]);
detailDTO.setAnalysis(String.format("正确答案是%s", trueFalseListStr[j]));
}
}
detailDTO.setOptions(optionList);
} else {
// 简答题
detailDTO.setRightAnswer("参考答案");
detailDTO.setAnalysis("解析");
/**
* 单选题题干 + 4个选项 + 1个正确答案
*/
private void buildSingleChoice(QuestionDTO.QuestionDetailDTO detail, String kpContent, int seq) {
detail.setQuestionContent(String.format("【单选题】关于「%s」,以下说法正确的是?(第%d题)", kpContent, seq));
int rightIdx = RandomUtil.randomInt(0, 4);
TreeMap<String, String> options = new TreeMap<>();
for (int i = 0; i < OPTION_KEYS.length; i++) {
options.put(OPTION_KEYS[i], String.format("选项%s(%s)", OPTION_KEYS[i], i == rightIdx ? "正确" : "干扰"));
}
detail.setOptions(options);
detail.setRightAnswer(OPTION_KEYS[rightIdx]);
detail.setAnalysis(String.format("正确答案是%s,考查知识点:%s", OPTION_KEYS[rightIdx], kpContent));
}
/**
* 多选题题干 + 4个选项 + 2-3个正确答案
*/
private void buildMultipleChoice(QuestionDTO.QuestionDetailDTO detail, String kpContent, int seq) {
detail.setQuestionContent(String.format("【多选题】关于「%s」,以下说法正确的有?(第%d题)", kpContent, seq));
// 随机选 2-3 个正确答案
int rightCount = RandomUtil.randomInt(2, 4); // 2 or 3
List<Integer> indices = new ArrayList<>(Arrays.asList(0, 1, 2, 3));
Collections.shuffle(indices);
Set<Integer> rightIndices = new HashSet<>(indices.subList(0, rightCount));
TreeMap<String, String> options = new TreeMap<>();
List<String> rightAnswers = new ArrayList<>();
for (int i = 0; i < OPTION_KEYS.length; i++) {
boolean isRight = rightIndices.contains(i);
options.put(OPTION_KEYS[i], String.format("选项%s(%s)", OPTION_KEYS[i], isRight ? "正确" : "干扰"));
if (isRight) {
rightAnswers.add(OPTION_KEYS[i]);
}
}
detail.setOptions(options);
detail.setRightAnswer(String.join(",", rightAnswers));
detail.setAnalysis(String.format("正确答案是%s,考查知识点:%s", detail.getRightAnswer(), kpContent));
}
/**
* 判断题题干 + /错选项
*/
private void buildTrueFalse(QuestionDTO.QuestionDetailDTO detail, String kpContent, int seq) {
detail.setQuestionContent(String.format("【判断题】%s,这种说法是否正确?(第%d题)", kpContent, seq));
Result<QuestionDTO> saveResult = saveQuestionDomainService.save(questionDTO);
QuestionDTO saved = saveResult.getData();
int rightIdx = RandomUtil.randomInt(0, 2); // 0=对, 1=错
TreeMap<String, String> options = new TreeMap<>();
options.put("A", "对");
options.put("B", "错");
detail.setOptions(options);
detail.setRightAnswer(TRUE_FALSE_KEYS[rightIdx]);
detail.setAnalysis(String.format("正确答案是%s(%s),考查知识点:%s",
TRUE_FALSE_KEYS[rightIdx], rightIdx == 0 ? "对" : "错", kpContent));
}
QuestionEntity savedEntity = new QuestionEntity();
savedEntity.setId(saved.getId());
savedEntity.setQuestionType(questionType.getValue());
savedEntity.setKpIdList(saved.getKpIdList());
result.add(savedEntity);
/**
* 简答题题干 + 得分点多点得分制
*/
private void buildShortAnswer(QuestionDTO.QuestionDetailDTO detail, String kpContent, int seq) {
detail.setQuestionContent(String.format("【简答题】请简述「%s」的核心要点。(第%d题)", kpContent, seq));
// 生成 3 个得分点
List<QuestionDTO.ScoringPointDTO> scoringPoints = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
QuestionDTO.ScoringPointDTO point = new QuestionDTO.ScoringPointDTO();
point.setIndex(i);
point.setContent(String.format("得分点%d:需提及%s相关概念%d", i, kpContent, i));
scoringPoints.add(point);
}
detail.setScoringPoints(scoringPoints);
log.info(">>> [经典套题-降级] 生成完成, 共{}题", result.size());
return result;
detail.setRightAnswer("参考答案:需结合知识点核心内容作答");
detail.setAnalysis("解析:本题考查" + kpContent + "的理解和表述能力");
}
}

22
src/main/java/com/project/information/application/DraftReviewApplicationService.java

@ -0,0 +1,22 @@
package com.project.information.application;
import com.project.base.domain.result.Result;
import com.project.information.domain.entity.KnowledgePointDraftEntity;
import java.util.List;
/**
* 知识点草稿审核应用服务
*/
public interface DraftReviewApplicationService {
Result<List<KnowledgePointDraftEntity>> listDrafts(Long informationId);
Result<String> updateDraft(Long draftId, String content);
Result<String> deleteDraft(Long draftId);
Result<String> addDraft(Long informationId, String content, Integer knowledgeType);
Result<String> confirm(Long informationId);
}

24
src/main/java/com/project/information/application/UploadFilesApplicationService.java

@ -0,0 +1,24 @@
package com.project.information.application;
import com.project.base.domain.result.Result;
import com.project.information.domain.dto.UploadedFileDTO;
import com.project.information.domain.param.UploadFilesParam;
import java.util.List;
/**
* 原始资料文件上传应用服务
* Step1只上传文件到 MinIO不建表记录
*/
public interface UploadFilesApplicationService {
/**
* 上传原始资料文件
* 校验最多5个文件单个<100MB
* 只上传到 MinIO返回文件信息列表不建表
*
* @param param 上传参数
* @return 文件信息列表
*/
Result<List<UploadedFileDTO>> uploadFiles(UploadFilesParam param) throws Exception;
}

106
src/main/java/com/project/information/application/impl/DraftReviewApplicationServiceImpl.java

@ -0,0 +1,106 @@
package com.project.information.application.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.project.base.domain.exception.BusinessErrorException;
import com.project.base.domain.result.Result;
import com.project.information.application.DraftReviewApplicationService;
import com.project.information.domain.entity.InformationEntity;
import com.project.information.domain.entity.KnowledgePointDraftEntity;
import com.project.information.domain.enums.AuditStatusEnum;
import com.project.information.domain.enums.DraftStatusEnum;
import com.project.information.domain.service.ConfirmKnowledgePointDraftDomainService;
import com.project.information.domain.service.InformationBaseService;
import com.project.information.domain.service.KnowledgePointDraftBaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 知识点草稿审核应用服务实现
* 简单 CRUD 直接调 BaseService确认固化调 DomainService
*/
@Service
public class DraftReviewApplicationServiceImpl implements DraftReviewApplicationService {
@Autowired
private KnowledgePointDraftBaseService knowledgePointDraftBaseService;
@Autowired
private InformationBaseService informationBaseService;
@Autowired
private ConfirmKnowledgePointDraftDomainService confirmKnowledgePointDraftDomainService;
@Override
public Result<List<KnowledgePointDraftEntity>> listDrafts(Long informationId) {
if (informationId == null) {
throw new BusinessErrorException("资料ID不能为空");
}
List<KnowledgePointDraftEntity> drafts = knowledgePointDraftBaseService.list(
new LambdaQueryWrapper<KnowledgePointDraftEntity>()
.eq(KnowledgePointDraftEntity::getInformationId, informationId)
.orderByAsc(KnowledgePointDraftEntity::getId));
return Result.success(drafts);
}
@Override
public Result<String> updateDraft(Long draftId, String content) {
if (draftId == null) {
throw new BusinessErrorException("草稿ID不能为空");
}
KnowledgePointDraftEntity draft = knowledgePointDraftBaseService.getById(draftId);
if (draft == null) {
throw new BusinessErrorException("草稿不存在");
}
if (!AuditStatusEnum.PENDING.getValue().equals(draft.getAuditStatus())) {
throw new BusinessErrorException("已固化的草稿不可修改");
}
draft.setContent(content);
knowledgePointDraftBaseService.updateById(draft);
return Result.success("修改成功");
}
@Override
public Result<String> deleteDraft(Long draftId) {
if (draftId == null) {
throw new BusinessErrorException("草稿ID不能为空");
}
KnowledgePointDraftEntity draft = knowledgePointDraftBaseService.getById(draftId);
if (draft == null) {
throw new BusinessErrorException("草稿不存在");
}
if (!AuditStatusEnum.PENDING.getValue().equals(draft.getAuditStatus())) {
throw new BusinessErrorException("已固化的草稿不可删除");
}
knowledgePointDraftBaseService.removeById(draftId);
return Result.success("删除成功");
}
@Override
public Result<String> addDraft(Long informationId, String content, Integer knowledgeType) {
if (informationId == null) {
throw new BusinessErrorException("资料ID不能为空");
}
InformationEntity information = informationBaseService.getById(informationId);
if (information == null) {
throw new BusinessErrorException("资料不存在");
}
if (!DraftStatusEnum.PENDING.getValue().equals(information.getDraftStatus())) {
throw new BusinessErrorException("该资料不在待审核状态,无法新增草稿");
}
KnowledgePointDraftEntity draft = new KnowledgePointDraftEntity();
draft.setInformationId(informationId);
draft.setContent(content);
draft.setAiContent(content);
draft.setKnowledgeType(knowledgeType);
draft.setAuditStatus(AuditStatusEnum.PENDING.getValue());
knowledgePointDraftBaseService.save(draft);
return Result.success("新增成功");
}
@Override
public Result<String> confirm(Long informationId) {
return confirmKnowledgePointDraftDomainService.confirm(informationId);
}
}

53
src/main/java/com/project/information/application/impl/GenerateFromFilesApplicationServiceImpl.java

@ -1,10 +1,10 @@
package com.project.information.application.impl;
import cn.hutool.core.io.FileUtil;
import com.project.base.config.CustomIdGenerator;
import com.project.base.domain.exception.BusinessErrorException;
import com.project.base.domain.result.Result;
import com.project.information.application.GenerateFromFilesApplicationService;
import com.project.information.domain.dto.UploadedFileDTO;
import com.project.information.domain.entity.InformationEntity;
import com.project.information.domain.entity.InformationFileEntity;
import com.project.information.domain.enums.DraftStatusEnum;
@ -14,22 +14,17 @@ import com.project.information.domain.param.GenerateFromFilesParam;
import com.project.information.domain.service.InformationBaseService;
import com.project.information.domain.service.InformationFileBaseService;
import com.project.information.domain.service.PostToDataExtractDomainService;
import com.project.information.utils.MinIoUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 原始资料上传应用服务实现
* 1. 创建虚拟 InformationEntitymaterialType=原始资料, draftStatus=PENDING
* 2. 上传子文件到 MinIO创建 InformationFileEntity
* 3. 异步发送每个子文件到数据服务解析
* 原始资料表单提交应用服务实现
* Step2创建虚拟资料 + 子文件记录 + 触发数据服务解析
*/
@Service
@Slf4j
@ -44,9 +39,6 @@ public class GenerateFromFilesApplicationServiceImpl implements GenerateFromFile
@Autowired
private PostToDataExtractDomainService postToDataExtractDomainService;
@Autowired
private MinIoUtils minIoUtils;
@Autowired
private CustomIdGenerator customIdGenerator;
@ -57,11 +49,8 @@ public class GenerateFromFilesApplicationServiceImpl implements GenerateFromFile
if (param.getSubLineId() == null) {
throw new BusinessErrorException("子产品线不能为空");
}
if (param.getFiles() == null || param.getFiles().length == 0) {
throw new BusinessErrorException("上传文件不能为空");
}
if (param.getContentCategories() == null || param.getContentCategories().size() != param.getFiles().length) {
throw new BusinessErrorException("每个文件需要对应一个内容分类");
if (param.getFiles() == null || param.getFiles().isEmpty()) {
throw new BusinessErrorException("请先上传文件");
}
// 1. 创建虚拟 InformationEntity(materialType=原始资料, draftStatus=PENDING)
@ -77,37 +66,19 @@ public class GenerateFromFilesApplicationServiceImpl implements GenerateFromFile
Long informationId = virtualInfo.getId();
log.info(">>> [原始资料] 创建虚拟资料, informationId={}, fileName={}", informationId, param.getFileName());
// 2. 上传子文件到 MinIO,创建 InformationFileEntity
MultipartFile[] files = param.getFiles();
List<Integer> categories = param.getContentCategories();
// 2. 创建子文件记录并关联虚拟资料
List<InformationFileEntity> fileEntities = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i];
String originalFilename = file.getOriginalFilename();
String suffix = FileUtil.getSuffix(originalFilename);
// 生成 MinIO 存储路径:{subLineId}/{yyyyMMdd}/{uuid}.{ext}
String filePath = String.format("%s/%tF/%s.%s",
param.getSubLineId(), new Date(),
customIdGenerator.nextId(null), suffix);
// 上传到 MinIO
minIoUtils.uploadFile(file.getInputStream(), filePath);
// 创建子文件记录
for (UploadedFileDTO file : param.getFiles()) {
InformationFileEntity fileEntity = new InformationFileEntity();
fileEntity.setInformationId(informationId);
fileEntity.setFileName(originalFilename);
fileEntity.setFilePath(filePath);
fileEntity.setFileSuffix(suffix);
fileEntity.setContentCategory(categories.get(i));
fileEntity.setFileName(file.getFileName());
fileEntity.setFilePath(file.getFilePath());
fileEntity.setFileSuffix(file.getFileSuffix());
fileEntity.setContentCategory(file.getContentCategory());
fileEntity.setParseStatus(FileParseStatusEnum.NotStarted.getValue());
fileEntity.setId(customIdGenerator.nextId(fileEntity));
fileEntities.add(fileEntity);
}
// 批量保存子文件
informationFileBaseService.saveBatch(fileEntities);
// 3. 异步发送每个子文件到数据服务解析
@ -115,7 +86,7 @@ public class GenerateFromFilesApplicationServiceImpl implements GenerateFromFile
postToDataExtractDomainService.postToDataExtract(fileEntity, informationId);
}
log.info(">>> [原始资料] 上传完成, informationId={}, 子文件数={}", informationId, fileEntities.size());
log.info(">>> [原始资料] 提交完成, informationId={}, 子文件数={}", informationId, fileEntities.size());
return Result.success("上传成功,正在解析中");
}
}

95
src/main/java/com/project/information/application/impl/UploadFilesApplicationServiceImpl.java

@ -0,0 +1,95 @@
package com.project.information.application.impl;
import cn.hutool.core.io.FileUtil;
import com.project.base.config.CustomIdGenerator;
import com.project.base.domain.exception.BusinessErrorException;
import com.project.base.domain.result.Result;
import com.project.information.application.UploadFilesApplicationService;
import com.project.information.domain.dto.UploadedFileDTO;
import com.project.information.domain.enums.ContentCategoryEnum;
import com.project.information.domain.param.UploadFilesParam;
import com.project.information.utils.MinIoUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 原始资料文件上传应用服务实现
* Step1只上传文件到 MinIO不建表记录
*/
@Service
@Slf4j
public class UploadFilesApplicationServiceImpl implements UploadFilesApplicationService {
private static final int MAX_FILE_COUNT = 5;
private static final long MAX_FILE_SIZE = 100 * 1024 * 1024L; // 100MB
@Autowired
private MinIoUtils minIoUtils;
@Autowired
private CustomIdGenerator customIdGenerator;
@Override
public Result<List<UploadedFileDTO>> uploadFiles(UploadFilesParam param) throws Exception {
MultipartFile[] files = param.getFiles();
List<Integer> categories = param.getContentCategories();
// 参数校验
if (files == null || files.length == 0) {
throw new BusinessErrorException("上传文件不能为空");
}
if (files.length > MAX_FILE_COUNT) {
throw new BusinessErrorException("最多上传" + MAX_FILE_COUNT + "个文件");
}
if (categories == null || categories.size() != files.length) {
throw new BusinessErrorException("每个文件需要对应一个内容分类");
}
// 校验内容分类是否合法
for (int i = 0; i < categories.size(); i++) {
if (!ContentCategoryEnum.isValid(categories.get(i))) {
throw new BusinessErrorException("第" + (i + 1) + "个文件的内容分类不合法");
}
}
List<UploadedFileDTO> result = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i];
// 单文件大小校验
if (file.getSize() > MAX_FILE_SIZE) {
throw new BusinessErrorException("文件「" + file.getOriginalFilename() + "」超过100MB限制");
}
String originalFilename = file.getOriginalFilename();
String suffix = FileUtil.getSuffix(originalFilename);
// MinIO 路径规范:classicpaper/{yyyyMMdd}/{uuid}.{ext}
String filePath = String.format("classicpaper/%s/%s.%s",
new java.text.SimpleDateFormat("yyyyMMdd").format(new Date()),
customIdGenerator.nextId(null),
suffix);
// 上传到 MinIO
minIoUtils.uploadFile(file.getInputStream(), filePath);
// 构建返回结果(不建表)
UploadedFileDTO dto = new UploadedFileDTO();
dto.setFileName(originalFilename);
dto.setFilePath(filePath);
dto.setFileSuffix(suffix);
dto.setContentCategory(categories.get(i));
result.add(dto);
log.info(">>> [文件上传] 上传成功, fileName={}, filePath={}", originalFilename, filePath);
}
return Result.success(result);
}
}

34
src/main/java/com/project/information/controller/InformationController.java

@ -3,9 +3,12 @@ package com.project.information.controller;
import com.project.base.domain.result.PageResult;
import com.project.base.domain.result.Result;
import com.project.information.application.DraftReviewApplicationService;
import com.project.information.application.GenerateFromFilesApplicationService;
import com.project.information.application.InformationApplicationService;
import com.project.information.application.ProductLineApplicationService;
import com.project.information.application.UploadFilesApplicationService;
import com.project.information.domain.dto.UploadedFileDTO;
import com.project.information.domain.dto.InformationDTO;
import com.project.information.domain.dto.ProductLineDTO;
import com.project.information.domain.entity.KnowledgePointDraftEntity;
@ -13,8 +16,8 @@ import com.project.information.domain.param.CheckDuplicatesParam;
import com.project.information.domain.param.FileCheckItem;
import com.project.information.domain.param.GenerateFromFilesParam;
import com.project.information.domain.param.InformationParam;
import com.project.information.domain.param.UploadFilesParam;
import com.project.information.domain.param.ProductLineParam;
import com.project.information.domain.service.DraftReviewDomainService;
import com.project.operation.annotation.OperationLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -32,11 +35,14 @@ public class InformationController {
// =============== V1.1 新增:草稿审核 ============
@Autowired
private DraftReviewDomainService draftReviewDomainService;
private DraftReviewApplicationService draftReviewApplicationService;
@Autowired
private GenerateFromFilesApplicationService generateFromFilesApplicationService;
@Autowired
private UploadFilesApplicationService uploadFilesApplicationService;
@PostMapping("/checkDuplicates")
public Result<String> checkDuplicates(CheckDuplicatesParam param) throws Exception {
return informationApplicationService.checkDuplicates(param.getSubLineId(), param.getFileList());
@ -58,11 +64,19 @@ public class InformationController {
return informationApplicationService.batchDelete(ids);
}
// =============== V1.1 新增:原始资料上传 ============
// =============== V1.1 新增:原始资料上传(两步) ============
/**
* Step1上传原始资料文件1-5个单个<100MB
* 只上传到 MinIO返回文件信息列表不建表
*/
@PostMapping("/uploadFiles")
public Result<List<UploadedFileDTO>> uploadFiles(UploadFilesParam param) throws Exception {
return uploadFilesApplicationService.uploadFiles(param);
}
/**
* 文件生成知识点原始资料上传
* 创建虚拟资料 + 上传子文件 + 异步发送数据服务解析
* Step2提交表单创建虚拟资料 + 关联子文件 + 触发解析
*/
@PostMapping("/generateFromFiles")
public Result<String> generateFromFiles(GenerateFromFilesParam param) throws Exception {
@ -76,7 +90,7 @@ public class InformationController {
*/
@GetMapping("/draftList")
public Result<List<KnowledgePointDraftEntity>> draftList(Long informationId) {
return draftReviewDomainService.listDrafts(informationId);
return draftReviewApplicationService.listDrafts(informationId);
}
/**
@ -84,7 +98,7 @@ public class InformationController {
*/
@PostMapping("/draft/update")
public Result<String> updateDraft(Long draftId, String content) {
return draftReviewDomainService.updateDraft(draftId, content);
return draftReviewApplicationService.updateDraft(draftId, content);
}
/**
@ -92,7 +106,7 @@ public class InformationController {
*/
@PostMapping("/draft/delete")
public Result<String> deleteDraft(Long draftId) {
return draftReviewDomainService.deleteDraft(draftId);
return draftReviewApplicationService.deleteDraft(draftId);
}
/**
@ -100,7 +114,7 @@ public class InformationController {
*/
@PostMapping("/draft/add")
public Result<String> addDraft(Long informationId, String content, Integer knowledgeType) {
return draftReviewDomainService.addDraft(informationId, content, knowledgeType);
return draftReviewApplicationService.addDraft(informationId, content, knowledgeType);
}
/**
@ -108,6 +122,6 @@ public class InformationController {
*/
@PostMapping("/draft/confirm")
public Result<String> confirmDraft(Long informationId) {
return draftReviewDomainService.confirm(informationId);
return draftReviewApplicationService.confirm(informationId);
}
}

19
src/main/java/com/project/information/domain/dto/UploadedFileDTO.java

@ -0,0 +1,19 @@
package com.project.information.domain.dto;
import lombok.Data;
/**
* 上传文件结果 DTO
* Step1 返回Step2 提交时回传
*/
@Data
public class UploadedFileDTO {
/** 原始文件名 */
private String fileName;
/** MinIO 存储路径 */
private String filePath;
/** 文件后缀 */
private String fileSuffix;
/** 内容分类 */
private Integer contentCategory;
}

15
src/main/java/com/project/information/domain/enums/ContentCategoryEnum.java

@ -14,4 +14,19 @@ public enum ContentCategoryEnum implements HasValueEnum<Integer> {
private final Integer value;
private final String desc;
/**
* 校验值是否合法
*/
public static boolean isValid(Integer value) {
if (value == null) {
return false;
}
for (ContentCategoryEnum e : values()) {
if (e.getValue().equals(value)) {
return true;
}
}
return false;
}
}

13
src/main/java/com/project/information/domain/param/GenerateFromFilesParam.java

@ -1,13 +1,13 @@
package com.project.information.domain.param;
import com.project.information.domain.dto.UploadedFileDTO;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 原始资料上传请求参数
* 对应前端文件生成知识点弹窗的表单数据
* 原始资料表单提交请求参数
* Step2创建虚拟资料 + 子文件记录 + 触发解析
*/
@Data
public class GenerateFromFilesParam {
@ -18,9 +18,6 @@ public class GenerateFromFilesParam {
/** 虚拟资料文件名(用户填写的占位名称) */
private String fileName;
/** 上传的文件列表 */
private MultipartFile[] files;
/** 每个文件对应的内容分类(与 files 数组一一对应):0-产品类,1-知识类,2-规范类,3-流程类 */
private List<Integer> contentCategories;
/** Step1 上传的文件信息列表 */
private List<UploadedFileDTO> files;
}

20
src/main/java/com/project/information/domain/param/UploadFilesParam.java

@ -0,0 +1,20 @@
package com.project.information.domain.param;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 原始资料文件上传请求参数
* Step1只上传文件到 MinIO不建表记录
*/
@Data
public class UploadFilesParam {
/** 上传的文件列表(1-5个,单个<100MB) */
private MultipartFile[] files;
/** 每个文件对应的内容分类(与 files 数组一一对应):0-产品类,1-知识类,2-规范类,3-流程类 */
private List<Integer> contentCategories;
}

52
src/main/java/com/project/information/domain/service/DraftReviewDomainService.java

@ -1,52 +0,0 @@
package com.project.information.domain.service;
import com.project.base.domain.result.Result;
import com.project.information.domain.entity.KnowledgePointDraftEntity;
import java.util.List;
/**
* 知识点草稿审核域服务
* 处理草稿的增删改查和确认固化
*/
public interface DraftReviewDomainService {
/**
* 查询草稿列表
* @param informationId 虚拟资料ID
* @return 草稿列表
*/
Result<List<KnowledgePointDraftEntity>> listDrafts(Long informationId);
/**
* 修改单条草稿仅修改 contentaiContent 保持不变
* @param draftId 草稿ID
* @param content 用户修改后的内容
*/
Result<String> updateDraft(Long draftId, String content);
/**
* 删除单条草稿
* @param draftId 草稿ID
*/
Result<String> deleteDraft(Long draftId);
/**
* 新增草稿
* @param informationId 虚拟资料ID
* @param content 知识点内容
* @param knowledgeType 类型0-精准1-模糊
*/
Result<String> addDraft(Long informationId, String content, Integer knowledgeType);
/**
* 确认提交固化不可逆
* 1. 幂等校验draftStatus 已是 APPROVED 则直接返回
* 2. 将草稿的 content 复制到 evaluator_knowledge_point 正式表
* 3. 更新草稿 auditStatus 为已通过
* 4. 更新虚拟资料 draftStatus = APPROVED(2)
* 5. 触发聚类
* @param informationId 虚拟资料ID
*/
Result<String> confirm(Long informationId);
}

172
src/main/java/com/project/information/domain/service/impl/DraftReviewDomainServiceImpl.java

@ -1,172 +0,0 @@
package com.project.information.domain.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.project.base.domain.exception.BusinessErrorException;
import com.project.base.domain.result.Result;
import com.project.information.domain.dto.KnowledgePointDTO;
import com.project.information.domain.entity.InformationEntity;
import com.project.information.domain.entity.KnowledgePointDraftEntity;
import com.project.information.domain.entity.KnowledgePointEntity;
import com.project.information.domain.enums.AuditStatusEnum;
import com.project.information.domain.enums.DraftStatusEnum;
import com.project.information.domain.service.*;
import com.project.interaction.application.AlgorithmApplicationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* 知识点草稿审核域服务实现
*/
@Service
@Slf4j
public class DraftReviewDomainServiceImpl implements DraftReviewDomainService {
@Autowired
private KnowledgePointDraftBaseService knowledgePointDraftBaseService;
@Autowired
private InformationBaseService informationBaseService;
@Autowired
private KnowledgePointBaseService knowledgePointBaseService;
@Autowired
private AlgorithmApplicationService algorithmApplicationService;
@Override
public Result<List<KnowledgePointDraftEntity>> listDrafts(Long informationId) {
if (informationId == null) {
throw new BusinessErrorException("资料ID不能为空");
}
List<KnowledgePointDraftEntity> drafts = knowledgePointDraftBaseService.list(
new LambdaQueryWrapper<KnowledgePointDraftEntity>()
.eq(KnowledgePointDraftEntity::getInformationId, informationId)
.orderByAsc(KnowledgePointDraftEntity::getId));
return Result.success(drafts);
}
@Override
public Result<String> updateDraft(Long draftId, String content) {
if (draftId == null) {
throw new BusinessErrorException("草稿ID不能为空");
}
KnowledgePointDraftEntity draft = knowledgePointDraftBaseService.getById(draftId);
if (draft == null) {
throw new BusinessErrorException("草稿不存在");
}
// 仅待审核状态的草稿可修改
if (!AuditStatusEnum.PENDING.getValue().equals(draft.getAuditStatus())) {
throw new BusinessErrorException("已固化的草稿不可修改");
}
draft.setContent(content);
knowledgePointDraftBaseService.updateById(draft);
return Result.success("修改成功");
}
@Override
public Result<String> deleteDraft(Long draftId) {
if (draftId == null) {
throw new BusinessErrorException("草稿ID不能为空");
}
KnowledgePointDraftEntity draft = knowledgePointDraftBaseService.getById(draftId);
if (draft == null) {
throw new BusinessErrorException("草稿不存在");
}
if (!AuditStatusEnum.PENDING.getValue().equals(draft.getAuditStatus())) {
throw new BusinessErrorException("已固化的草稿不可删除");
}
knowledgePointDraftBaseService.removeById(draftId);
return Result.success("删除成功");
}
@Override
public Result<String> addDraft(Long informationId, String content, Integer knowledgeType) {
if (informationId == null) {
throw new BusinessErrorException("资料ID不能为空");
}
// 校验资料存在且为待审核状态
InformationEntity information = informationBaseService.getById(informationId);
if (information == null) {
throw new BusinessErrorException("资料不存在");
}
if (!DraftStatusEnum.PENDING.getValue().equals(information.getDraftStatus())) {
throw new BusinessErrorException("该资料不在待审核状态,无法新增草稿");
}
KnowledgePointDraftEntity draft = new KnowledgePointDraftEntity();
draft.setInformationId(informationId);
draft.setContent(content);
draft.setAiContent(content); // 手动新增的草稿,aiContent 与 content 一致
draft.setKnowledgeType(knowledgeType);
draft.setAuditStatus(AuditStatusEnum.PENDING.getValue());
knowledgePointDraftBaseService.save(draft);
return Result.success("新增成功");
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> confirm(Long informationId) {
if (informationId == null) {
throw new BusinessErrorException("资料ID不能为空");
}
// 1. 查询资料,幂等校验
InformationEntity information = informationBaseService.getById(informationId);
if (information == null) {
throw new BusinessErrorException("资料不存在");
}
if (DraftStatusEnum.APPROVED.getValue().equals(information.getDraftStatus())) {
return Result.success("已固化,不可重复操作");
}
if (!DraftStatusEnum.PENDING.getValue().equals(information.getDraftStatus())) {
throw new BusinessErrorException("该资料不在待审核状态");
}
// 2. 查询所有待审核草稿
List<KnowledgePointDraftEntity> drafts = knowledgePointDraftBaseService.list(
new LambdaQueryWrapper<KnowledgePointDraftEntity>()
.eq(KnowledgePointDraftEntity::getInformationId, informationId)
.eq(KnowledgePointDraftEntity::getAuditStatus, AuditStatusEnum.PENDING.getValue()));
if (drafts.isEmpty()) {
throw new BusinessErrorException("没有待审核的草稿");
}
// 3. 将草稿 content 复制到正式知识点表
List<KnowledgePointEntity> knowledgePoints = new ArrayList<>();
for (KnowledgePointDraftEntity draft : drafts) {
KnowledgePointEntity kp = new KnowledgePointEntity();
kp.setContent(draft.getContent());
kp.setKnowledgeType(draft.getKnowledgeType());
kp.setInformationId(informationId);
kp.setParseName(draft.getParseName());
knowledgePoints.add(kp);
}
knowledgePointBaseService.saveBatch(knowledgePoints);
// 4. 更新草稿 auditStatus 为已通过
List<Long> draftIds = drafts.stream().map(KnowledgePointDraftEntity::getId).toList();
knowledgePointDraftBaseService.lambdaUpdate()
.in(KnowledgePointDraftEntity::getId, draftIds)
.set(KnowledgePointDraftEntity::getAuditStatus, AuditStatusEnum.APPROVED.getValue())
.update();
// 5. 更新虚拟资料 draftStatus = APPROVED(2)
informationBaseService.lambdaUpdate()
.eq(InformationEntity::getId, informationId)
.set(InformationEntity::getDraftStatus, DraftStatusEnum.APPROVED.getValue())
.update();
// 6. 触发聚类
List<KnowledgePointDTO> kpList = knowledgePoints.stream()
.map(entity -> entity.toDTO(KnowledgePointDTO::new))
.toList();
algorithmApplicationService.postToClusteringByInformationId(informationId, kpList);
log.info(">>> [草稿审核] 确认固化完成, informationId={}, 草稿数={}", informationId, drafts.size());
return Result.success("确认成功");
}
}

12
src/main/java/com/project/interaction/controller/InteractionController.java

@ -4,13 +4,13 @@ package com.project.interaction.controller;
import com.project.base.domain.exception.MissingParameterException;
import com.project.base.domain.result.Result;
import com.project.interaction.application.AiExtractCallbackApplicationService;
import com.project.interaction.application.AlgorithmApplicationService;
import com.project.interaction.application.DataExtractCallbackApplicationService;
import com.project.interaction.domain.dto.AiExtractCallbackDTO;
import com.project.interaction.domain.dto.ClusterCallbackDTO;
import com.project.interaction.domain.dto.DataExtractCallbackDTO;
import com.project.interaction.domain.dto.QuestionCallBackDTO;
import com.project.interaction.domain.service.AiExtractCallbackDomainService;
import com.project.interaction.domain.service.DataExtractCallbackDomainService;
import com.project.interaction.domain.service.GenerateQuestionQueueService;
import com.project.question.domain.enums.QuestionSourceTypeEnum;
import com.project.question.domain.service.GenerateQuestionDomainService;
@ -36,9 +36,9 @@ public class InteractionController {
// =============== V1.1 新增:数据服务/算法服务回调 ============
@Autowired
private DataExtractCallbackDomainService dataExtractCallbackDomainService;
private DataExtractCallbackApplicationService dataExtractCallbackApplicationService;
@Autowired
private AiExtractCallbackDomainService aiExtractCallbackDomainService;
private AiExtractCallbackApplicationService aiExtractCallbackApplicationService;
// @PostMapping("/saveCluster")
@ -99,7 +99,7 @@ public class InteractionController {
*/
@PostMapping("/dataExtractCallback")
public Result<String> dataExtractCallback(DataExtractCallbackDTO dto) {
dataExtractCallbackDomainService.handleCallback(dto);
dataExtractCallbackApplicationService.handleCallback(dto);
return Result.success("回调处理成功");
}
@ -109,7 +109,7 @@ public class InteractionController {
*/
@PostMapping("/aiExtractCallback")
public Result<String> aiExtractCallback(AiExtractCallbackDTO dto) {
aiExtractCallbackDomainService.handleCallback(dto);
aiExtractCallbackApplicationService.handleCallback(dto);
return Result.success("回调处理成功");
}
}

16
src/main/java/com/project/interaction/domain/service/AiExtractCallbackDomainService.java

@ -1,16 +0,0 @@
package com.project.interaction.domain.service;
import com.project.interaction.domain.dto.AiExtractCallbackDTO;
/**
* 算法服务知识点提取回调处理
* 接收算法服务返回的草稿知识点写入草稿表
*/
public interface AiExtractCallbackDomainService {
/**
* 处理算法服务知识点提取回调
* @param callback 回调参数
*/
void handleCallback(AiExtractCallbackDTO callback);
}

16
src/main/java/com/project/interaction/domain/service/DataExtractCallbackDomainService.java

@ -1,16 +0,0 @@
package com.project.interaction.domain.service;
import com.project.interaction.domain.dto.DataExtractCallbackDTO;
/**
* 数据服务解析回调处理
* 每个子文件解析完成后回调检查批次是否全部完成全部完成则触发算法服务
*/
public interface DataExtractCallbackDomainService {
/**
* 处理数据服务解析回调
* @param callback 回调参数
*/
void handleCallback(DataExtractCallbackDTO callback);
}

71
src/main/java/com/project/interaction/domain/service/impl/AiExtractCallbackDomainServiceImpl.java

@ -1,71 +0,0 @@
package com.project.interaction.domain.service.impl;
import com.project.information.domain.entity.InformationEntity;
import com.project.information.domain.entity.KnowledgePointDraftEntity;
import com.project.information.domain.enums.AuditStatusEnum;
import com.project.information.domain.enums.DraftStatusEnum;
import com.project.information.domain.service.InformationBaseService;
import com.project.information.domain.service.KnowledgePointDraftBaseService;
import com.project.interaction.domain.dto.AiExtractCallbackDTO;
import com.project.interaction.domain.service.AiExtractCallbackDomainService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* 算法服务知识点提取回调处理实现
* 1. 将算法返回的草稿知识点写入 evaluator_knowledge_point_draft
* 2. 更新虚拟资料 draftStatus = PENDING(1)
*/
@Service
@Slf4j
public class AiExtractCallbackDomainServiceImpl implements AiExtractCallbackDomainService {
@Autowired
private KnowledgePointDraftBaseService knowledgePointDraftBaseService;
@Autowired
private InformationBaseService informationBaseService;
@Override
@Transactional(rollbackFor = Exception.class)
public void handleCallback(AiExtractCallbackDTO callback) {
Long informationId = callback.getInformationId();
List<AiExtractCallbackDTO.DraftItem> drafts = callback.getDrafts();
log.info(">>> [算法服务回调] 收到知识点提取回调, informationId={}, 草稿数={}",
informationId, drafts != null ? drafts.size() : 0);
if (drafts == null || drafts.isEmpty()) {
log.warn(">>> [算法服务回调] 算法返回的草稿为空, informationId={}", informationId);
return;
}
// 1. 保存草稿(aiContent = content 的副本)
List<KnowledgePointDraftEntity> draftEntities = new ArrayList<>();
for (AiExtractCallbackDTO.DraftItem item : drafts) {
KnowledgePointDraftEntity draft = new KnowledgePointDraftEntity();
draft.setInformationId(informationId);
draft.setContent(item.getContent());
draft.setAiContent(item.getContent());
draft.setKnowledgeType(item.getKnowledgeType());
draft.setParseName(item.getParseName());
draft.setAuditStatus(AuditStatusEnum.PENDING.getValue());
draftEntities.add(draft);
}
knowledgePointDraftBaseService.saveBatch(draftEntities);
// 2. 更新虚拟资料 draftStatus = PENDING(1)
// 注意:原始资料创建时已是 PENDING,此处为兜底保障
informationBaseService.lambdaUpdate()
.eq(InformationEntity::getId, informationId)
.set(InformationEntity::getDraftStatus, DraftStatusEnum.PENDING.getValue())
.update();
log.info(">>> [算法服务回调] 草稿保存完成, informationId={}, 草稿数={}", informationId, draftEntities.size());
}
}

106
src/main/java/com/project/interaction/domain/service/impl/DataExtractCallbackDomainServiceImpl.java

@ -1,106 +0,0 @@
package com.project.interaction.domain.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.project.information.domain.entity.InformationEntity;
import com.project.information.domain.entity.InformationFileEntity;
import com.project.information.domain.enums.FileParseStatusEnum;
import com.project.information.domain.service.InformationBaseService;
import com.project.information.domain.service.InformationFileBaseService;
import com.project.interaction.domain.dto.AiExtractRequestDTO;
import com.project.interaction.domain.dto.DataExtractCallbackDTO;
import com.project.interaction.domain.service.DataExtractCallbackDomainService;
import com.project.interaction.domain.service.PostToAiExtractDomainService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 数据服务解析回调处理实现
* 1. 更新子文件解析状态和解析文本
* 2. 检查该虚拟资料下所有子文件是否全部完成
* 3. 全部完成 调用算法服务知识点提取
*/
@Service
@Slf4j
public class DataExtractCallbackDomainServiceImpl implements DataExtractCallbackDomainService {
@Autowired
private InformationFileBaseService informationFileBaseService;
@Autowired
private InformationBaseService informationBaseService;
@Autowired
private PostToAiExtractDomainService postToAiExtractDomainService;
@Override
public void handleCallback(DataExtractCallbackDTO callback) {
Long fileId = callback.getFileId();
Long informationId = callback.getInformationId();
log.info(">>> [数据服务回调] 收到子文件解析回调, fileId={}, informationId={}, status={}",
fileId, informationId, callback.getParseStatus());
// 1. 更新子文件解析状态
InformationFileEntity file = informationFileBaseService.getById(fileId);
if (file == null) {
log.error(">>> [数据服务回调] 子文件不存在, fileId={}", fileId);
return;
}
file.setParseStatus(callback.getParseStatus());
if (FileParseStatusEnum.Success.getValue().equals(callback.getParseStatus())) {
file.setParsedText(callback.getParsedText());
}
informationFileBaseService.updateById(file);
// 2. 如果解析失败,记录日志(不阻断其他文件)
if (!FileParseStatusEnum.Success.getValue().equals(callback.getParseStatus())) {
log.warn(">>> [数据服务回调] 子文件解析失败, fileId={}, error={}", fileId, callback.getErrorMsg());
return;
}
// 3. 检查该虚拟资料下所有子文件是否全部完成
List<InformationFileEntity> allFiles = informationFileBaseService.list(
new LambdaQueryWrapper<InformationFileEntity>()
.eq(InformationFileEntity::getInformationId, informationId));
boolean allDone = allFiles.stream()
.allMatch(f -> FileParseStatusEnum.Success.getValue().equals(f.getParseStatus())
|| FileParseStatusEnum.Failed.getValue().equals(f.getParseStatus()));
if (!allDone) {
log.info(">>> [数据服务回调] 虚拟资料 {} 下还有子文件未完成解析,等待中", informationId);
return;
}
// 4. 全部完成 → 调用算法服务知识点提取
log.info(">>> [数据服务回调] 虚拟资料 {} 下所有子文件解析完成,开始调用算法服务", informationId);
// 查询虚拟资料获取内容分类信息(从子文件中获取)
List<AiExtractRequestDTO.FileContent> fileContents = new ArrayList<>();
for (InformationFileEntity f : allFiles) {
// 只传解析成功的文件
if (FileParseStatusEnum.Success.getValue().equals(f.getParseStatus()) && f.getParsedText() != null) {
AiExtractRequestDTO.FileContent fc = new AiExtractRequestDTO.FileContent();
fc.setFileName(f.getFileName());
fc.setContentCategory(String.valueOf(f.getContentCategory()));
fc.setParsedText(f.getParsedText());
fileContents.add(fc);
}
}
if (fileContents.isEmpty()) {
log.warn(">>> [数据服务回调] 虚拟资料 {} 下没有成功解析的子文件", informationId);
return;
}
AiExtractRequestDTO request = new AiExtractRequestDTO();
request.setInformationId(informationId);
request.setFiles(fileContents);
postToAiExtractDomainService.postToAiExtract(request);
}
}

4
src/main/java/com/project/question/domain/enums/QuestionSourceTypeEnum.java

@ -9,7 +9,9 @@ import lombok.RequiredArgsConstructor;
public enum QuestionSourceTypeEnum implements HasValueEnum<Integer> {
Single_Concept(0 , "单一知识点") ,
Multi_Concept(1 , "多知识点/复合");
Multi_Concept(1 , "多知识点/复合"),
// V1.1 新增:历史题库导入(无知识点关联)
Historical_Import(2 , "历史导入");
private final Integer value;
private final String desc;
}

Loading…
Cancel
Save