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; }