初始化项目代码

This commit is contained in:
2026-06-03 12:43:48 +08:00
commit 5b12356567
303 changed files with 40729 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
package top.tqx.demo_1.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import top.tqx.demo_1.dto.ExamGenerateRequest;
import top.tqx.demo_1.dto.ExamGenerateResponse;
import top.tqx.demo_1.entity.File;
import top.tqx.demo_1.enums.FileProcessStepStatus;
import top.tqx.demo_1.mapper.FileMapper;
import java.time.LocalDateTime;
@Slf4j
@Service
public class AsyncExamService {
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private ExamService examService;
@Autowired
private FileMapper fileMapper;
public static final String EXAM_STATUS_UNGENERATED = "UNGENERATED";
public static final String EXAM_STATUS_GENERATING = "GENERATING";
public static final String EXAM_STATUS_GENERATED = "GENERATED";
public static final String EXAM_STATUS_FAILED = "FAILED";
@Async("examTaskExecutor")
public void generateQuestionsAsync(ExamGenerateRequest request) {
log.info("========== 异步任务开始执行 ==========");
log.info("requestId={}, filePath={}, fileId={}, 当前线程: {}",
request.getRequestId(), request.getFilePath(), request.getFileId(), Thread.currentThread().getName());
String filePath = request.getFilePath();
Long fileId = null;
String errorMessage = null;
try {
log.info("步骤1: 查找文件ID");
// 优先使用 request 中的 fileId(来自 collection_file)
if (request.getFileId() != null) {
log.info("优先使用 request 中的 fileId: {}", request.getFileId());
fileId = request.getFileId();
}
// 如果没有 request 中的 fileId,尝试通过路径查询
if (fileId == null && filePath != null && !filePath.isEmpty()) {
fileId = findFileIdByPath(filePath, null);
log.info("通过路径查询得到 fileId: {}", fileId);
}
log.info("最终确定的 fileId: {}", fileId);
// 步骤2: 更新状态为 GENERATING(只要有 fileId 就更新)
if (fileId != null) {
log.info("步骤2: 更新状态为 GENERATING");
updateFileExamStatus(fileId, EXAM_STATUS_GENERATING, "正在生成题目,请稍后...");
}
log.info("步骤3: 调用 generateQuestions");
ExamGenerateResponse response = examService.generateQuestions(request);
log.info("步骤3完成: total={}", response.getTotal());
// 步骤4: 更新状态为完成
if (fileId != null) {
log.info("步骤4: 更新状态为 GENERATED");
if (response.getTotal() == null || response.getTotal() == 0) {
log.warn("RAG服务返回0道题目,记录警告信息");
updateFileExamStatus(fileId, EXAM_STATUS_GENERATED, "警告:未生成任何题目。可能原因:文档内容为空、文档内容无法提取题目、或文档格式不支持。source_chunks_used=" + response.getSourceChunksUsed());
} else {
String processMessage = "生成成功,共" + response.getTotal() + "道题";
log.info("response.getAiAnalysis() = {}", response.getAiAnalysis());
log.info("response.getAiAnalysis() == null? {}", response.getAiAnalysis() == null);
if (response.getAiAnalysis() != null) {
try {
String aiAnalysisJson = objectMapper.writeValueAsString(response.getAiAnalysis());
log.info("序列化成功的 aiAnalysisJson = {}", aiAnalysisJson);
processMessage += "\n\n【AI分析结果】\n" + aiAnalysisJson;
} catch (Exception e) {
log.warn("序列化 aiAnalysis 失败: {}", e.getMessage(), e);
String aiAnalysisString = response.getAiAnalysis().toString();
log.info("使用 toString(): {}", aiAnalysisString);
processMessage += "\n\n【AI分析结果】" + aiAnalysisString;
}
} else {
log.warn("response.getAiAnalysis() 为 null,不追加 AI 分析结果");
}
log.info("最终 processMessage = {}", processMessage);
updateFileExamStatus(fileId, EXAM_STATUS_GENERATED, processMessage);
}
}
log.info("========== 异步任务成功完成 ==========");
} catch (Exception e) {
String errorType = e.getMessage() != null && (e.getMessage().contains("500") || e.getMessage().contains("INTERNAL SERVER ERROR"))
? "[AI端错误]" : "[本地错误]";
errorMessage = String.format("生成失败%s: %s", errorType, e.getMessage());
log.error("========== 异步任务执行失败 ==========", e);
if (fileId != null) {
updateFileExamStatus(fileId, EXAM_STATUS_FAILED, errorMessage);
}
// 不抛出RuntimeException,避免产生"Unexpected exception occurred invoking async method"日志
log.info("异步任务已优雅结束,文件状态已更新为FAILED");
}
}
private Long findFileIdByPath(String filePath, Long requestFileId) {
try {
log.info("开始查询 file 表: filePath={}, requestFileId={}", filePath, requestFileId);
// 优先通过 ID 查询(如果提供了 requestFileId)
if (requestFileId != null) {
log.info("尝试通过 ID 查询: fileId={}", requestFileId);
File fileById = fileMapper.selectById(requestFileId);
if (fileById != null) {
log.info("通过 ID 查询成功: id={}, fileName={}, filePath={}",
fileById.getId(), fileById.getFileName(), fileById.getFilePath());
return fileById.getId();
} else {
log.warn("通过 ID 查询失败,尝试通过路径查询");
}
}
// 其次通过路径查询
log.info("尝试通过路径查询: filePath={}", filePath);
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<File> wrapper =
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
wrapper.eq(File::getFilePath, filePath);
File file = fileMapper.selectOne(wrapper);
if (file != null) {
log.info("通过路径查询成功: id={}, fileName={}, filePath={}",
file.getId(), file.getFileName(), file.getFilePath());
return file.getId();
} else {
log.warn("通过路径查询失败: filePath={}", filePath);
// 尝试模糊查询,看看是否有相似的路径
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<File> likeWrapper =
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
likeWrapper.like(File::getFilePath, "3.docx");
java.util.List<File> similarFiles = fileMapper.selectList(likeWrapper);
if (similarFiles != null && !similarFiles.isEmpty()) {
log.info("找到 {} 条相似的文件记录:", similarFiles.size());
for (File f : similarFiles) {
log.info(" - id={}, fileName={}, filePath={}",
f.getId(), f.getFileName(), f.getFilePath());
}
}
return null;
}
} catch (Exception e) {
log.error("查询文件失败: filePath={}, requestFileId={}, error={}",
filePath, requestFileId, e.getMessage(), e);
return null;
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void updateFileExamStatus(Long fileId, String status, String message) {
if (fileId == null) {
log.warn("更新文件状态失败: fileId为null");
return;
}
try {
File file = fileMapper.selectById(fileId);
if (file != null) {
log.info("更新文件状态: fileId={}, 从 {} 变为 {}", fileId, file.getExamStatus(), status);
file.setExamStatus(status);
if (message != null) {
file.setProcessMessage(message);
}
// 更新流程状态
if (EXAM_STATUS_GENERATING.equals(status)) {
file.setProcessStepStatus(FileProcessStepStatus.EXAM_GENERATING.getCode());
file.setProcessStepMessage(FileProcessStepStatus.EXAM_GENERATING.getDescription());
} else if (EXAM_STATUS_GENERATED.equals(status)) {
// 如果向量化已完成且题目生成完成,则标记为全部完成
if (FileProcessStepStatus.VECTORIZED.getCode().equals(file.getProcessStepStatus())) {
file.setProcessStepStatus(FileProcessStepStatus.COMPLETED.getCode());
file.setProcessStepMessage(FileProcessStepStatus.COMPLETED.getDescription());
} else {
file.setProcessStepStatus(FileProcessStepStatus.EXAM_GENERATED.getCode());
file.setProcessStepMessage(FileProcessStepStatus.EXAM_GENERATED.getDescription());
}
} else if (EXAM_STATUS_FAILED.equals(status)) {
file.setProcessStepStatus(FileProcessStepStatus.EXAM_GENERATE_FAILED.getCode());
String stepMsg = FileProcessStepStatus.EXAM_GENERATE_FAILED.getDescription() + ": " + (message != null ? message : "");
file.setProcessStepMessage(stepMsg.length() > 500 ? stepMsg.substring(0, 500) : stepMsg);
}
file.setUpdateTime(LocalDateTime.now());
int result = fileMapper.updateById(file);
if (result > 0) {
log.info("文件状态更新成功: fileId={}", fileId);
} else {
log.warn("文件状态更新失败: fileId={}, status={}, 未影响任何行", fileId, status);
}
} else {
log.warn("更新文件状态失败: fileId={} 对应的文件不存在", fileId);
}
} catch (Exception e) {
log.error("更新文件题目状态失败: fileId={}, status={}, error={}", fileId, status, e.getMessage(), e);
throw new RuntimeException("更新文件状态失败", e);
}
}
}