提取功能改为异步实现,添加ai辅助提取状态

This commit is contained in:
wh
2026-04-17 01:20:27 +08:00
parent ccbcfd2c74
commit bf0b00ed08
18 changed files with 594 additions and 386 deletions

View File

@@ -1,33 +1,30 @@
package com.label.service;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.label.common.ai.AiServiceClient;
import com.label.common.exception.BusinessException;
import com.label.common.auth.TokenPrincipal;
import com.label.common.statemachine.StateValidator;
import com.label.common.statemachine.TaskStatus;
import com.label.entity.AnnotationResult;
import com.label.entity.TrainingDataset;
import com.label.event.ExtractionApprovedEvent;
import com.label.mapper.AnnotationResultMapper;
import com.label.mapper.TrainingDatasetMapper;
import com.label.entity.SourceData;
import com.label.mapper.SourceDataMapper;
import com.label.entity.AnnotationTask;
import com.label.mapper.AnnotationTaskMapper;
import com.label.service.TaskClaimService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.label.common.auth.TokenPrincipal;
import com.label.common.exception.BusinessException;
import com.label.common.statemachine.StateValidator;
import com.label.common.statemachine.TaskStatus;
import com.label.entity.AnnotationResult;
import com.label.entity.AnnotationTask;
import com.label.entity.SourceData;
import com.label.event.ExtractionApprovedEvent;
import com.label.mapper.AnnotationResultMapper;
import com.label.mapper.AnnotationTaskMapper;
import com.label.mapper.SourceDataMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 提取阶段标注服务AI 预标注、更新结果、提交、审批、驳回。
@@ -43,12 +40,13 @@ public class ExtractionService {
private final AnnotationTaskMapper taskMapper;
private final AnnotationResultMapper resultMapper;
private final TrainingDatasetMapper datasetMapper;
// private final TrainingDatasetMapper datasetMapper;
private final SourceDataMapper sourceDataMapper;
private final TaskClaimService taskClaimService;
private final AiServiceClient aiServiceClient;
// private final AiServiceClient aiServiceClient;
private final ApplicationEventPublisher eventPublisher;
private final ObjectMapper objectMapper;
private final AiAnnotationAsyncService aiAnnotationAsyncService; // 注入异步服务
@Value("${rustfs.bucket:label-source-data}")
private String bucket;
@@ -67,32 +65,30 @@ public class ExtractionService {
throw new BusinessException("NOT_FOUND", "关联资料不存在", HttpStatus.NOT_FOUND);
}
// 调用 AI 服务(在事务外,避免长时间持有 DB 连接)
AiServiceClient.ExtractionRequest req = AiServiceClient.ExtractionRequest.builder()
.sourceId(source.getId())
.filePath(source.getFilePath())
.bucket(bucket)
.build();
AiServiceClient.ExtractionResponse aiResponse;
try {
if ("IMAGE".equals(source.getDataType())) {
aiResponse = aiServiceClient.extractImage(req);
} else {
aiResponse = aiServiceClient.extractText(req);
}
} catch (Exception e) {
log.warn("AI 预标注调用失败(任务 {}{}", taskId, e.getMessage());
// AI 失败不阻塞流程,写入空结果
aiResponse = new AiServiceClient.ExtractionResponse();
aiResponse.setItems(Collections.emptyList());
if (source.getFilePath() == null || source.getFilePath().isEmpty()) {
throw new BusinessException("INVALID_SOURCE", "源文件路径不能为空", HttpStatus.BAD_REQUEST);
}
// 将 AI 结果写入 annotation_resultUPSERT 语义)
writeOrUpdateResult(taskId, principal.getCompanyId(), aiResponse.getItems());
}
if (source.getDataType() == null || source.getDataType().isEmpty()) {
throw new BusinessException("INVALID_SOURCE", "数据类型不能为空", HttpStatus.BAD_REQUEST);
}
// ------------------------------------------------------------------ 更新结果 --
String dataType = source.getDataType().toUpperCase();
if (!"IMAGE".equals(dataType) && !"TEXT".equals(dataType)) {
log.warn("不支持的数据类型: {}, 任务ID: {}", dataType, taskId);
throw new BusinessException("UNSUPPORTED_TYPE",
"不支持的数据类型: " + dataType, HttpStatus.BAD_REQUEST);
}
// 更新任务状态为 PROCESSING
taskMapper.update(null, new LambdaUpdateWrapper<AnnotationTask>()
.eq(AnnotationTask::getId, taskId)
.set(AnnotationTask::getAiStatus, "PROCESSING"));
// 触发异步任务
aiAnnotationAsyncService.processAnnotation(taskId, principal.getCompanyId(), source);
// executeAiAnnotationAsync(taskId, principal.getCompanyId(), source);
}
/**
* 人工更新标注结果整体覆盖PUT 语义)。
@@ -237,8 +233,7 @@ public class ExtractionService {
"sourceType", source != null ? source.getDataType() : "",
"sourceFilePath", source != null && source.getFilePath() != null ? source.getFilePath() : "",
"isFinal", task.getIsFinal() != null && task.getIsFinal(),
"resultJson", result != null ? result.getResultJson() : "[]"
);
"resultJson", result != null ? result.getResultJson() : "[]");
}
// ------------------------------------------------------------------ 私有工具 --
@@ -253,20 +248,4 @@ public class ExtractionService {
}
return task;
}
private void writeOrUpdateResult(Long taskId, Long companyId, java.util.List<?> items) {
try {
String json = objectMapper.writeValueAsString(Map.of("items", items != null ? items : Collections.emptyList()));
int updated = resultMapper.updateResultJson(taskId, json, companyId);
if (updated == 0) {
AnnotationResult result = new AnnotationResult();
result.setTaskId(taskId);
result.setCompanyId(companyId);
result.setResultJson(json);
resultMapper.insert(result);
}
} catch (Exception e) {
log.error("写入 AI 预标注结果失败: taskId={}", taskId, e);
}
}
}