初始化项目代码
This commit is contained in:
217
src/main/java/top/tqx/demo_1/service/AsyncExamService.java
Normal file
217
src/main/java/top/tqx/demo_1/service/AsyncExamService.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user