diff --git a/pom.xml b/pom.xml
index 8eab379..4a58dc3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,13 @@
1.18.30
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
+
+
com.aliyun
diff --git a/src/main/java/com/project/classicpaper/domain/service/impl/FallbackClassicPaperQuestionGenerator.java b/src/main/java/com/project/classicpaper/domain/service/impl/FallbackClassicPaperQuestionGenerator.java
index b0519c0..903ab60 100644
--- a/src/main/java/com/project/classicpaper/domain/service/impl/FallbackClassicPaperQuestionGenerator.java
+++ b/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 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 generate(List knowledgePoints,
@@ -39,71 +40,128 @@ public class FallbackClassicPaperQuestionGenerator implements ClassicPaperQuesti
List 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 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 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 resultIdx = list.subList(0, 2).stream().collect(Collectors.toSet());
- TreeMap optionList = new TreeMap<>();
- List 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 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 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 indices = new ArrayList<>(Arrays.asList(0, 1, 2, 3));
+ Collections.shuffle(indices);
+ Set rightIndices = new HashSet<>(indices.subList(0, rightCount));
+
+ TreeMap options = new TreeMap<>();
+ List 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 saveResult = saveQuestionDomainService.save(questionDTO);
- QuestionDTO saved = saveResult.getData();
+ int rightIdx = RandomUtil.randomInt(0, 2); // 0=对, 1=错
+ TreeMap 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 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 + "的理解和表述能力");
}
}
diff --git a/src/main/java/com/project/information/application/DraftReviewApplicationService.java b/src/main/java/com/project/information/application/DraftReviewApplicationService.java
new file mode 100644
index 0000000..411850b
--- /dev/null
+++ b/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> listDrafts(Long informationId);
+
+ Result updateDraft(Long draftId, String content);
+
+ Result deleteDraft(Long draftId);
+
+ Result addDraft(Long informationId, String content, Integer knowledgeType);
+
+ Result confirm(Long informationId);
+}
diff --git a/src/main/java/com/project/information/application/UploadFilesApplicationService.java b/src/main/java/com/project/information/application/UploadFilesApplicationService.java
new file mode 100644
index 0000000..5480e78
--- /dev/null
+++ b/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> uploadFiles(UploadFilesParam param) throws Exception;
+}
diff --git a/src/main/java/com/project/information/application/impl/DraftReviewApplicationServiceImpl.java b/src/main/java/com/project/information/application/impl/DraftReviewApplicationServiceImpl.java
new file mode 100644
index 0000000..61af3e8
--- /dev/null
+++ b/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> listDrafts(Long informationId) {
+ if (informationId == null) {
+ throw new BusinessErrorException("资料ID不能为空");
+ }
+ List drafts = knowledgePointDraftBaseService.list(
+ new LambdaQueryWrapper()
+ .eq(KnowledgePointDraftEntity::getInformationId, informationId)
+ .orderByAsc(KnowledgePointDraftEntity::getId));
+ return Result.success(drafts);
+ }
+
+ @Override
+ public Result 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 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 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 confirm(Long informationId) {
+ return confirmKnowledgePointDraftDomainService.confirm(informationId);
+ }
+}
diff --git a/src/main/java/com/project/information/application/impl/GenerateFromFilesApplicationServiceImpl.java b/src/main/java/com/project/information/application/impl/GenerateFromFilesApplicationServiceImpl.java
index 4bf8259..7e59668 100644
--- a/src/main/java/com/project/information/application/impl/GenerateFromFilesApplicationServiceImpl.java
+++ b/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. 创建虚拟 InformationEntity(materialType=原始资料, 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 categories = param.getContentCategories();
+ // 2. 创建子文件记录并关联虚拟资料
List 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("上传成功,正在解析中");
}
}
diff --git a/src/main/java/com/project/information/application/impl/UploadFilesApplicationServiceImpl.java b/src/main/java/com/project/information/application/impl/UploadFilesApplicationServiceImpl.java
new file mode 100644
index 0000000..b7245f7
--- /dev/null
+++ b/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> uploadFiles(UploadFilesParam param) throws Exception {
+ MultipartFile[] files = param.getFiles();
+ List 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 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);
+ }
+}
diff --git a/src/main/java/com/project/information/controller/InformationController.java b/src/main/java/com/project/information/controller/InformationController.java
index f971af0..15e4465 100644
--- a/src/main/java/com/project/information/controller/InformationController.java
+++ b/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 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> uploadFiles(UploadFilesParam param) throws Exception {
+ return uploadFilesApplicationService.uploadFiles(param);
+ }
/**
- * 文件生成知识点(原始资料上传)
- * 创建虚拟资料 + 上传子文件 + 异步发送数据服务解析
+ * Step2:提交表单(创建虚拟资料 + 关联子文件 + 触发解析)
*/
@PostMapping("/generateFromFiles")
public Result generateFromFiles(GenerateFromFilesParam param) throws Exception {
@@ -76,7 +90,7 @@ public class InformationController {
*/
@GetMapping("/draftList")
public Result> draftList(Long informationId) {
- return draftReviewDomainService.listDrafts(informationId);
+ return draftReviewApplicationService.listDrafts(informationId);
}
/**
@@ -84,7 +98,7 @@ public class InformationController {
*/
@PostMapping("/draft/update")
public Result 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 deleteDraft(Long draftId) {
- return draftReviewDomainService.deleteDraft(draftId);
+ return draftReviewApplicationService.deleteDraft(draftId);
}
/**
@@ -100,7 +114,7 @@ public class InformationController {
*/
@PostMapping("/draft/add")
public Result 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 confirmDraft(Long informationId) {
- return draftReviewDomainService.confirm(informationId);
+ return draftReviewApplicationService.confirm(informationId);
}
}
diff --git a/src/main/java/com/project/information/domain/dto/UploadedFileDTO.java b/src/main/java/com/project/information/domain/dto/UploadedFileDTO.java
new file mode 100644
index 0000000..210098a
--- /dev/null
+++ b/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;
+}
diff --git a/src/main/java/com/project/information/domain/enums/ContentCategoryEnum.java b/src/main/java/com/project/information/domain/enums/ContentCategoryEnum.java
index 5709663..ccebd0a 100644
--- a/src/main/java/com/project/information/domain/enums/ContentCategoryEnum.java
+++ b/src/main/java/com/project/information/domain/enums/ContentCategoryEnum.java
@@ -14,4 +14,19 @@ public enum ContentCategoryEnum implements HasValueEnum {
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;
+ }
}
diff --git a/src/main/java/com/project/information/domain/param/GenerateFromFilesParam.java b/src/main/java/com/project/information/domain/param/GenerateFromFilesParam.java
index 6a94e4f..da684b2 100644
--- a/src/main/java/com/project/information/domain/param/GenerateFromFilesParam.java
+++ b/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 contentCategories;
+ /** Step1 上传的文件信息列表 */
+ private List files;
}
diff --git a/src/main/java/com/project/information/domain/param/UploadFilesParam.java b/src/main/java/com/project/information/domain/param/UploadFilesParam.java
new file mode 100644
index 0000000..608f7ff
--- /dev/null
+++ b/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 contentCategories;
+}
diff --git a/src/main/java/com/project/information/domain/service/DraftReviewDomainService.java b/src/main/java/com/project/information/domain/service/DraftReviewDomainService.java
deleted file mode 100644
index b7fbd2f..0000000
--- a/src/main/java/com/project/information/domain/service/DraftReviewDomainService.java
+++ /dev/null
@@ -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> listDrafts(Long informationId);
-
- /**
- * 修改单条草稿(仅修改 content,aiContent 保持不变)
- * @param draftId 草稿ID
- * @param content 用户修改后的内容
- */
- Result updateDraft(Long draftId, String content);
-
- /**
- * 删除单条草稿
- * @param draftId 草稿ID
- */
- Result deleteDraft(Long draftId);
-
- /**
- * 新增草稿
- * @param informationId 虚拟资料ID
- * @param content 知识点内容
- * @param knowledgeType 类型(0-精准,1-模糊)
- */
- Result 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 confirm(Long informationId);
-}
diff --git a/src/main/java/com/project/information/domain/service/impl/DraftReviewDomainServiceImpl.java b/src/main/java/com/project/information/domain/service/impl/DraftReviewDomainServiceImpl.java
deleted file mode 100644
index 61696a6..0000000
--- a/src/main/java/com/project/information/domain/service/impl/DraftReviewDomainServiceImpl.java
+++ /dev/null
@@ -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> listDrafts(Long informationId) {
- if (informationId == null) {
- throw new BusinessErrorException("资料ID不能为空");
- }
- List drafts = knowledgePointDraftBaseService.list(
- new LambdaQueryWrapper()
- .eq(KnowledgePointDraftEntity::getInformationId, informationId)
- .orderByAsc(KnowledgePointDraftEntity::getId));
- return Result.success(drafts);
- }
-
- @Override
- public Result 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 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 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 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 drafts = knowledgePointDraftBaseService.list(
- new LambdaQueryWrapper()
- .eq(KnowledgePointDraftEntity::getInformationId, informationId)
- .eq(KnowledgePointDraftEntity::getAuditStatus, AuditStatusEnum.PENDING.getValue()));
- if (drafts.isEmpty()) {
- throw new BusinessErrorException("没有待审核的草稿");
- }
-
- // 3. 将草稿 content 复制到正式知识点表
- List 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 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 kpList = knowledgePoints.stream()
- .map(entity -> entity.toDTO(KnowledgePointDTO::new))
- .toList();
- algorithmApplicationService.postToClusteringByInformationId(informationId, kpList);
-
- log.info(">>> [草稿审核] 确认固化完成, informationId={}, 草稿数={}", informationId, drafts.size());
- return Result.success("确认成功");
- }
-}
diff --git a/src/main/java/com/project/interaction/controller/InteractionController.java b/src/main/java/com/project/interaction/controller/InteractionController.java
index e3fc716..97bfdc8 100644
--- a/src/main/java/com/project/interaction/controller/InteractionController.java
+++ b/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 dataExtractCallback(DataExtractCallbackDTO dto) {
- dataExtractCallbackDomainService.handleCallback(dto);
+ dataExtractCallbackApplicationService.handleCallback(dto);
return Result.success("回调处理成功");
}
@@ -109,7 +109,7 @@ public class InteractionController {
*/
@PostMapping("/aiExtractCallback")
public Result aiExtractCallback(AiExtractCallbackDTO dto) {
- aiExtractCallbackDomainService.handleCallback(dto);
+ aiExtractCallbackApplicationService.handleCallback(dto);
return Result.success("回调处理成功");
}
}
diff --git a/src/main/java/com/project/interaction/domain/service/AiExtractCallbackDomainService.java b/src/main/java/com/project/interaction/domain/service/AiExtractCallbackDomainService.java
deleted file mode 100644
index 834b7cf..0000000
--- a/src/main/java/com/project/interaction/domain/service/AiExtractCallbackDomainService.java
+++ /dev/null
@@ -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);
-}
diff --git a/src/main/java/com/project/interaction/domain/service/DataExtractCallbackDomainService.java b/src/main/java/com/project/interaction/domain/service/DataExtractCallbackDomainService.java
deleted file mode 100644
index df4b60d..0000000
--- a/src/main/java/com/project/interaction/domain/service/DataExtractCallbackDomainService.java
+++ /dev/null
@@ -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);
-}
diff --git a/src/main/java/com/project/interaction/domain/service/impl/AiExtractCallbackDomainServiceImpl.java b/src/main/java/com/project/interaction/domain/service/impl/AiExtractCallbackDomainServiceImpl.java
deleted file mode 100644
index cd5699f..0000000
--- a/src/main/java/com/project/interaction/domain/service/impl/AiExtractCallbackDomainServiceImpl.java
+++ /dev/null
@@ -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 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 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());
- }
-}
diff --git a/src/main/java/com/project/interaction/domain/service/impl/DataExtractCallbackDomainServiceImpl.java b/src/main/java/com/project/interaction/domain/service/impl/DataExtractCallbackDomainServiceImpl.java
deleted file mode 100644
index f6d4be1..0000000
--- a/src/main/java/com/project/interaction/domain/service/impl/DataExtractCallbackDomainServiceImpl.java
+++ /dev/null
@@ -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 allFiles = informationFileBaseService.list(
- new LambdaQueryWrapper()
- .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 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);
- }
-}
diff --git a/src/main/java/com/project/question/domain/enums/QuestionSourceTypeEnum.java b/src/main/java/com/project/question/domain/enums/QuestionSourceTypeEnum.java
index d07a9c7..9dac2f5 100644
--- a/src/main/java/com/project/question/domain/enums/QuestionSourceTypeEnum.java
+++ b/src/main/java/com/project/question/domain/enums/QuestionSourceTypeEnum.java
@@ -9,7 +9,9 @@ import lombok.RequiredArgsConstructor;
public enum QuestionSourceTypeEnum implements HasValueEnum {
Single_Concept(0 , "单一知识点") ,
- Multi_Concept(1 , "多知识点/复合");
+ Multi_Concept(1 , "多知识点/复合"),
+ // V1.1 新增:历史题库导入(无知识点关联)
+ Historical_Import(2 , "历史导入");
private final Integer value;
private final String desc;
}