2026-04-27 10:27:57 +08:00
|
|
|
|
package com.labelsys.backend.service;
|
|
|
|
|
|
|
2026-05-07 00:23:27 +08:00
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
2026-04-27 16:25:39 +08:00
|
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
2026-05-06 19:07:45 +08:00
|
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
2026-04-27 10:27:57 +08:00
|
|
|
|
import com.labelsys.backend.common.ResultCode;
|
|
|
|
|
|
import com.labelsys.backend.common.exception.BusinessException;
|
|
|
|
|
|
import com.labelsys.backend.context.LoginUser;
|
|
|
|
|
|
import com.labelsys.backend.dto.common.PageResult;
|
|
|
|
|
|
import com.labelsys.backend.dto.request.AnnotationResultPageQuery;
|
2026-05-06 19:07:45 +08:00
|
|
|
|
import com.labelsys.backend.dto.request.MergeReviewResultRequest;
|
2026-04-27 10:27:57 +08:00
|
|
|
|
import com.labelsys.backend.dto.response.AnnotationResultCompareResponse;
|
|
|
|
|
|
import com.labelsys.backend.dto.response.AnnotationResultResponse;
|
|
|
|
|
|
import com.labelsys.backend.entity.AnnotationResult;
|
2026-05-06 19:07:45 +08:00
|
|
|
|
import com.labelsys.backend.entity.AnnotationResultHistory;
|
2026-04-27 10:27:57 +08:00
|
|
|
|
import com.labelsys.backend.entity.SourceResource;
|
2026-04-29 14:02:01 +08:00
|
|
|
|
import com.labelsys.backend.enums.AnnotationResultStatus;
|
2026-05-07 00:23:27 +08:00
|
|
|
|
import com.labelsys.backend.enums.UserRole;
|
2026-05-06 19:07:45 +08:00
|
|
|
|
import com.labelsys.backend.mapper.AnnotationResultHistoryMapper;
|
2026-04-27 10:27:57 +08:00
|
|
|
|
import com.labelsys.backend.mapper.AnnotationResultMapper;
|
|
|
|
|
|
import com.labelsys.backend.mapper.SourceResourceMapper;
|
2026-05-06 19:07:45 +08:00
|
|
|
|
import com.labelsys.backend.util.IdGenerator;
|
2026-04-27 10:27:57 +08:00
|
|
|
|
import lombok.RequiredArgsConstructor;
|
2026-04-27 16:25:39 +08:00
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
2026-05-06 19:07:45 +08:00
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
|
import java.util.List;
|
2026-04-27 10:27:57 +08:00
|
|
|
|
|
2026-04-27 16:25:39 +08:00
|
|
|
|
@Slf4j
|
2026-04-27 10:27:57 +08:00
|
|
|
|
@Service
|
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
|
public class AnnotationResultService {
|
|
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
private final AnnotationResultMapper annotationResultMapper;
|
|
|
|
|
|
private final AnnotationResultHistoryMapper annotationResultHistoryMapper;
|
|
|
|
|
|
private final SourceResourceMapper sourceResourceMapper;
|
|
|
|
|
|
private final DataPermissionService dataPermissionService;
|
|
|
|
|
|
private final ObjectStorageService objectStorageService;
|
|
|
|
|
|
private final ObjectMapper objectMapper;
|
2026-04-27 10:27:57 +08:00
|
|
|
|
|
|
|
|
|
|
public PageResult<AnnotationResultResponse> pageResults(LoginUser currentUser, AnnotationResultPageQuery query) {
|
2026-04-27 16:25:39 +08:00
|
|
|
|
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
|
|
|
|
|
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
2026-04-28 12:15:10 +08:00
|
|
|
|
|
2026-05-07 00:23:27 +08:00
|
|
|
|
var wrapper = new LambdaQueryWrapper<AnnotationResult>()
|
2026-05-07 16:00:17 +08:00
|
|
|
|
.eq(AnnotationResult::getIsDeleted, Boolean.FALSE)
|
2026-04-29 14:02:01 +08:00
|
|
|
|
.eq(AnnotationResult::getCompanyId, currentUser.companyId())
|
2026-04-28 12:15:10 +08:00
|
|
|
|
.eq(query.taskId() != null, AnnotationResult::getTaskId, query.taskId())
|
|
|
|
|
|
.eq(query.resourceId() != null, AnnotationResult::getResourceId, query.resourceId())
|
|
|
|
|
|
.eq(query.requiresManualReview() != null, AnnotationResult::getRequiresManualReview,
|
2026-05-06 19:07:45 +08:00
|
|
|
|
query.requiresManualReview())
|
|
|
|
|
|
.orderByDesc(AnnotationResult::getCreatedAt);
|
2026-04-28 12:15:10 +08:00
|
|
|
|
|
2026-04-27 16:25:39 +08:00
|
|
|
|
if (shouldFilterByUserId) {
|
|
|
|
|
|
wrapper.eq(AnnotationResult::getCreatorId, currentUser.userId());
|
|
|
|
|
|
} else if (!allowedRoles.isEmpty()) {
|
|
|
|
|
|
wrapper.in(AnnotationResult::getCreatorRole, allowedRoles);
|
|
|
|
|
|
}
|
2026-04-28 12:15:10 +08:00
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
var page = new Page<AnnotationResult>(query.pageNo(), query.pageSize());
|
|
|
|
|
|
var resultPage = annotationResultMapper.selectPage(page, wrapper);
|
2026-04-27 16:25:39 +08:00
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
var records = resultPage.getRecords().stream()
|
|
|
|
|
|
.map(this::toResponse)
|
2026-04-29 14:02:01 +08:00
|
|
|
|
.filter(response -> query.runtimeStatus() == null
|
|
|
|
|
|
|| query.runtimeStatus().equals(response.runtimeStatus()))
|
|
|
|
|
|
.toList();
|
2026-04-27 16:25:39 +08:00
|
|
|
|
|
2026-04-29 14:02:01 +08:00
|
|
|
|
return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(),
|
|
|
|
|
|
(int) resultPage.getSize());
|
2026-04-27 10:27:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public AnnotationResultResponse getResult(LoginUser currentUser, Long resultId) {
|
2026-04-27 16:25:39 +08:00
|
|
|
|
AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId());
|
|
|
|
|
|
if (result == null) {
|
2026-04-28 12:15:10 +08:00
|
|
|
|
log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", resultId,
|
2026-04-29 14:02:01 +08:00
|
|
|
|
currentUser.companyId(), currentUser.userId());
|
2026-04-27 10:27:57 +08:00
|
|
|
|
throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在");
|
|
|
|
|
|
}
|
2026-05-07 00:23:27 +08:00
|
|
|
|
//assertResultPermission(currentUser, result);
|
2026-04-27 10:27:57 +08:00
|
|
|
|
return toResponse(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
public AnnotationResultCompareResponse compareResult(LoginUser currentUser, Long resultId) {
|
|
|
|
|
|
AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId());
|
|
|
|
|
|
if (result == null) {
|
|
|
|
|
|
log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", resultId,
|
|
|
|
|
|
currentUser.companyId(), currentUser.userId());
|
|
|
|
|
|
throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在");
|
|
|
|
|
|
}
|
2026-05-07 00:23:27 +08:00
|
|
|
|
//assertResultPermission(currentUser, result);
|
2026-05-06 19:07:45 +08:00
|
|
|
|
|
|
|
|
|
|
QaContent qaContent = loadQaContent(result);
|
2026-05-07 16:00:17 +08:00
|
|
|
|
DiffContent diffContent = Boolean.TRUE.equals(result.getRequiresManualReview()) ?
|
2026-05-06 19:07:45 +08:00
|
|
|
|
loadDiffSummary(result) : null;
|
|
|
|
|
|
|
|
|
|
|
|
SourceResource resource = sourceResourceMapper.selectById(result.getResourceId());
|
|
|
|
|
|
|
|
|
|
|
|
// 转换 QA 记录
|
|
|
|
|
|
List<AnnotationResultCompareResponse.QaRecord> qaRecords = qaContent.records().stream()
|
|
|
|
|
|
.map(qa -> new AnnotationResultCompareResponse.QaRecord(
|
|
|
|
|
|
qa.id(),
|
|
|
|
|
|
qa.question(),
|
|
|
|
|
|
qa.answer(),
|
|
|
|
|
|
qa.requiresReview()
|
|
|
|
|
|
)).toList();
|
|
|
|
|
|
|
|
|
|
|
|
// 转换差异记录
|
|
|
|
|
|
List<AnnotationResultCompareResponse.DiffRecord> diffRecords = diffContent != null ?
|
|
|
|
|
|
diffContent.records().stream()
|
|
|
|
|
|
.map(diff -> new AnnotationResultCompareResponse.DiffRecord(
|
|
|
|
|
|
diff.qaId(),
|
|
|
|
|
|
diff.question(),
|
|
|
|
|
|
diff.extractAnswer(),
|
|
|
|
|
|
diff.verifyAnswer(),
|
|
|
|
|
|
diff.diffReason(),
|
|
|
|
|
|
diff.mergedAnswer()
|
|
|
|
|
|
)).toList() : List.of();
|
|
|
|
|
|
|
|
|
|
|
|
return new AnnotationResultCompareResponse(
|
|
|
|
|
|
result.getId(),
|
|
|
|
|
|
result.getTaskId(),
|
|
|
|
|
|
result.getResourceId(),
|
|
|
|
|
|
qaRecords,
|
|
|
|
|
|
diffRecords,
|
|
|
|
|
|
resource == null ? null : resource.getFilePath()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public void mergeReviewResult(LoginUser currentUser, Long resultId, MergeReviewResultRequest request) {
|
|
|
|
|
|
AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId());
|
|
|
|
|
|
if (result == null) {
|
|
|
|
|
|
throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在");
|
|
|
|
|
|
}
|
2026-05-07 00:23:27 +08:00
|
|
|
|
//assertResultPermission(currentUser, result);
|
2026-05-06 19:07:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 读取当前 qa.json
|
|
|
|
|
|
QaContent qaContent = loadQaContent(result);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新 qa.json 的 answer 字段
|
|
|
|
|
|
List<QaContent.QaRecord> updatedQaRecords = qaContent.records().stream()
|
|
|
|
|
|
.map(record -> {
|
|
|
|
|
|
String mergedAnswer = request.mergedAnswers().get(record.id());
|
|
|
|
|
|
if (mergedAnswer != null) {
|
|
|
|
|
|
return new QaContent.QaRecord(
|
|
|
|
|
|
record.id(),
|
|
|
|
|
|
record.question(),
|
|
|
|
|
|
mergedAnswer,
|
|
|
|
|
|
false
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return record;
|
|
|
|
|
|
})
|
|
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
|
|
|
|
QaContent updatedQaContent = new QaContent(
|
|
|
|
|
|
qaContent.taskId(),
|
|
|
|
|
|
qaContent.resourceId(),
|
|
|
|
|
|
updatedQaRecords,
|
|
|
|
|
|
new QaContent.Metadata(
|
|
|
|
|
|
qaContent.metadata().createdAt(),
|
|
|
|
|
|
LocalDateTime.now().toString()
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
saveQaContent(result, updatedQaContent);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新数据库记录
|
2026-05-07 16:00:17 +08:00
|
|
|
|
result.setIsDeleted(Boolean.TRUE);
|
2026-05-06 19:07:45 +08:00
|
|
|
|
result.setReviewerId(currentUser.userId());
|
|
|
|
|
|
result.setReviewComment(request.reviewComment());
|
|
|
|
|
|
result.setReviewedAt(LocalDateTime.now());
|
|
|
|
|
|
result.setRequiresManualReview(false);
|
|
|
|
|
|
annotationResultMapper.updateById(result);
|
|
|
|
|
|
|
2026-05-07 16:00:17 +08:00
|
|
|
|
// 归档到历史表(人工审核后归档)
|
|
|
|
|
|
archiveToHistory(result, currentUser, "审核通过后归档", false);
|
2026-05-06 19:07:45 +08:00
|
|
|
|
|
|
|
|
|
|
log.info("merged review result, companyId={}, userId={}, resultId={}",
|
|
|
|
|
|
currentUser.companyId(), currentUser.userId(), resultId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 14:02:01 +08:00
|
|
|
|
private AnnotationResultResponse toResponse(AnnotationResult result) {
|
2026-05-06 19:07:45 +08:00
|
|
|
|
return new AnnotationResultResponse(
|
|
|
|
|
|
result.getId(),
|
|
|
|
|
|
result.getTaskId(),
|
2026-05-07 16:00:17 +08:00
|
|
|
|
result.getTaskName(), // 新增
|
2026-05-06 19:07:45 +08:00
|
|
|
|
result.getResourceId(),
|
2026-05-07 16:00:17 +08:00
|
|
|
|
result.getResourceName(), // 新增
|
2026-05-06 19:07:45 +08:00
|
|
|
|
deriveStatus(result),
|
|
|
|
|
|
result.getRequiresManualReview(),
|
|
|
|
|
|
result.getIsDeleted(),
|
|
|
|
|
|
result.getQaContentFilePath(),
|
|
|
|
|
|
result.getDiffSummaryFilePath(),
|
|
|
|
|
|
result.getReviewComment(),
|
|
|
|
|
|
result.getReviewedAt(),
|
|
|
|
|
|
result.getCreatedAt()
|
|
|
|
|
|
);
|
2026-04-29 14:02:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private AnnotationResultStatus deriveStatus(AnnotationResult result) {
|
|
|
|
|
|
if (Boolean.TRUE.equals(result.getIsDeleted())) {
|
|
|
|
|
|
return AnnotationResultStatus.ARCHIVED;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (Boolean.TRUE.equals(result.getRequiresManualReview())) {
|
|
|
|
|
|
return AnnotationResultStatus.MANUAL_REVIEW_PENDING;
|
|
|
|
|
|
}
|
|
|
|
|
|
return AnnotationResultStatus.AUTO_ARCHIVE_PENDING;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
private QaContent loadQaContent(AnnotationResult result) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
String filePath = result.getQaContentFilePath();
|
|
|
|
|
|
String bucketName = extractBucketName(filePath);
|
|
|
|
|
|
String objectKey = extractObjectKey(filePath);
|
|
|
|
|
|
byte[] content = objectStorageService.download(bucketName, objectKey);
|
|
|
|
|
|
String jsonContent = new String(content, StandardCharsets.UTF_8);
|
|
|
|
|
|
return objectMapper.readValue(jsonContent, new TypeReference<QaContent>() {
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("Failed to load qa content, resultId={}, filePath={}", result.getId(),
|
|
|
|
|
|
result.getQaContentFilePath(), e);
|
|
|
|
|
|
throw new BusinessException(ResultCode.ERROR, "加载问答内容失败");
|
2026-04-27 10:27:57 +08:00
|
|
|
|
}
|
2026-05-06 19:07:45 +08:00
|
|
|
|
}
|
2026-04-29 14:02:01 +08:00
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
private DiffContent loadDiffSummary(AnnotationResult result) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
String filePath = result.getDiffSummaryFilePath();
|
|
|
|
|
|
String bucketName = extractBucketName(filePath);
|
|
|
|
|
|
String objectKey = extractObjectKey(filePath);
|
|
|
|
|
|
byte[] content = objectStorageService.download(bucketName, objectKey);
|
|
|
|
|
|
String jsonContent = new String(content, StandardCharsets.UTF_8);
|
|
|
|
|
|
return objectMapper.readValue(jsonContent, new TypeReference<DiffContent>() {
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("Failed to load diff summary, resultId={}, filePath={}", result.getId(),
|
|
|
|
|
|
result.getDiffSummaryFilePath(), e);
|
|
|
|
|
|
throw new BusinessException(ResultCode.ERROR, "加载差异摘要失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-29 14:02:01 +08:00
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
private void saveQaContent(AnnotationResult result, QaContent qaContent) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
String jsonContent = objectMapper.writeValueAsString(qaContent);
|
|
|
|
|
|
String filePath = result.getQaContentFilePath();
|
|
|
|
|
|
String bucketName = extractBucketName(filePath);
|
|
|
|
|
|
String objectKey = extractObjectKey(filePath);
|
|
|
|
|
|
objectStorageService.upload(bucketName, objectKey, jsonContent.getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
|
"application/json");
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("Failed to save qa content, resultId={}", result.getId(), e);
|
|
|
|
|
|
throw new BusinessException(ResultCode.ERROR, "保存问答内容失败");
|
|
|
|
|
|
}
|
2026-04-27 10:27:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
private void assertResultPermission(LoginUser currentUser, AnnotationResult result) {
|
|
|
|
|
|
if (!dataPermissionService.canAccessCreator(currentUser, result.getCreatorId(),
|
|
|
|
|
|
UserRole.valueOf(result.getCreatorRole()))) {
|
|
|
|
|
|
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问该标注结果");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 16:00:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 归档到历史表
|
|
|
|
|
|
* @param result 标注结果
|
|
|
|
|
|
* @param currentUser 当前用户
|
|
|
|
|
|
* @param archiveReason 归档原因
|
|
|
|
|
|
* @param isAutoArchive 是否自动归档(true=自动归档,false=人工审核后归档)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void archiveToHistory(AnnotationResult result, LoginUser currentUser, String archiveReason, boolean isAutoArchive) {
|
2026-05-06 19:07:45 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 读取 qa.json 内容用于归档
|
|
|
|
|
|
QaContent qaContent = loadQaContent(result);
|
|
|
|
|
|
|
|
|
|
|
|
// 构建归档记录
|
2026-05-07 16:00:17 +08:00
|
|
|
|
AnnotationResultHistory.AnnotationResultHistoryBuilder historyBuilder = AnnotationResultHistory.builder()
|
2026-05-06 19:07:45 +08:00
|
|
|
|
.id(IdGenerator.nextId())
|
|
|
|
|
|
.companyId(result.getCompanyId())
|
2026-05-07 16:00:17 +08:00
|
|
|
|
.creatorId(result.getCreatorId())
|
|
|
|
|
|
.creatorRole(result.getCreatorRole())
|
2026-05-06 19:07:45 +08:00
|
|
|
|
.sourceResultId(result.getId())
|
|
|
|
|
|
.taskId(result.getTaskId())
|
2026-05-07 16:00:17 +08:00
|
|
|
|
.taskName(result.getTaskName())
|
2026-05-06 19:07:45 +08:00
|
|
|
|
.resourceId(result.getResourceId())
|
2026-05-07 16:00:17 +08:00
|
|
|
|
.resourceName(result.getResourceName())
|
2026-05-06 19:07:45 +08:00
|
|
|
|
//.qaContentJson(objectMapper.writeValueAsString(qaContent))
|
|
|
|
|
|
.qaContentFilePath(result.getQaContentFilePath())
|
|
|
|
|
|
.archiveReason(archiveReason)
|
|
|
|
|
|
.archivedBy(currentUser.userId())
|
|
|
|
|
|
.archivedAt(LocalDateTime.now())
|
2026-05-07 16:00:17 +08:00
|
|
|
|
.createdAt(LocalDateTime.now());
|
|
|
|
|
|
|
|
|
|
|
|
// 根据归档类型设置审核人信息
|
|
|
|
|
|
if (isAutoArchive) {
|
|
|
|
|
|
// 自动归档:reviewer_id为NULL,name和comment为"auto"
|
|
|
|
|
|
historyBuilder
|
|
|
|
|
|
.reviewerId(null)
|
|
|
|
|
|
.reviewerName("auto")
|
|
|
|
|
|
.reviewerComment("auto");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 人工审核后归档:使用审核人信息
|
|
|
|
|
|
historyBuilder
|
|
|
|
|
|
.reviewerId(result.getReviewerId())
|
|
|
|
|
|
.reviewerName(currentUser.realName())
|
|
|
|
|
|
.reviewerComment(result.getReviewComment());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
annotationResultHistoryMapper.insert(historyBuilder.build());
|
|
|
|
|
|
|
|
|
|
|
|
log.info("archived result to history, resultId={}, historyId={}, isAutoArchive={}",
|
|
|
|
|
|
result.getId(), historyBuilder.build().getId(), isAutoArchive);
|
2026-05-06 19:07:45 +08:00
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("Failed to archive result to history, resultId={}", result.getId(), e);
|
|
|
|
|
|
throw new BusinessException(ResultCode.ERROR, "归档失败");
|
2026-04-29 14:02:01 +08:00
|
|
|
|
}
|
2026-04-27 10:27:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 14:02:01 +08:00
|
|
|
|
private String extractBucketName(String filePath) {
|
2026-05-06 19:07:45 +08:00
|
|
|
|
// 从文件路径中提取 bucket 名称
|
|
|
|
|
|
// 例如:annotation-results/2/qa/801.json -> annotation-results
|
|
|
|
|
|
int firstSlash = filePath.indexOf('/');
|
|
|
|
|
|
return firstSlash > 0 ? filePath.substring(0, firstSlash) : filePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String extractObjectKey(String filePath) {
|
|
|
|
|
|
// 从文件路径中提取 object key
|
|
|
|
|
|
// 例如:annotation-results/2/qa/801.json -> 2/qa/801.json
|
|
|
|
|
|
int firstSlash = filePath.indexOf('/');
|
|
|
|
|
|
return firstSlash > 0 ? filePath.substring(firstSlash + 1) : "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 内部类:qa.json 结构
|
|
|
|
|
|
private record QaContent(
|
|
|
|
|
|
Long taskId,
|
|
|
|
|
|
Long resourceId,
|
|
|
|
|
|
List<QaRecord> records,
|
|
|
|
|
|
Metadata metadata
|
|
|
|
|
|
) {
|
|
|
|
|
|
private record QaRecord(String id, String question, String answer, Boolean requiresReview) {
|
2026-04-27 10:27:57 +08:00
|
|
|
|
}
|
2026-05-06 19:07:45 +08:00
|
|
|
|
|
|
|
|
|
|
private record Metadata(String createdAt, String updatedAt) {
|
2026-04-29 14:02:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 19:07:45 +08:00
|
|
|
|
// 内部类:diff.json 结构
|
|
|
|
|
|
private record DiffContent(
|
|
|
|
|
|
Long taskId,
|
|
|
|
|
|
Long resourceId,
|
|
|
|
|
|
List<DiffRecord> records,
|
|
|
|
|
|
Metadata metadata
|
|
|
|
|
|
) {
|
|
|
|
|
|
private record DiffRecord(String qaId, String question, String extractAnswer,
|
|
|
|
|
|
String verifyAnswer, String diffReason, String mergedAnswer) {
|
2026-04-29 14:02:01 +08:00
|
|
|
|
}
|
2026-05-06 19:07:45 +08:00
|
|
|
|
|
|
|
|
|
|
private record Metadata(String createdAt) {
|
2026-04-27 10:27:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-27 16:25:39 +08:00
|
|
|
|
}
|