From 74674990d8859d9bf417204c90d2a042d6237227 Mon Sep 17 00:00:00 2001 From: wh Date: Wed, 29 Apr 2026 14:02:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=87=E6=B3=A8=E7=BB=93=E6=9E=9C=E6=AF=94?= =?UTF-8?q?=E8=BE=83=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AnnotationResultController.java | 4 +- .../response/AnnotationResultResponse.java | 4 +- .../backend/enums/AnnotationResultStatus.java | 10 ++ .../labelsys/backend/enums/ConfigMode.java | 5 +- .../labelsys/backend/enums/ConfigType.java | 2 + .../labelsys/backend/enums/IndustryType.java | 11 +- .../backend/enums/QaContentStorageMode.java | 3 +- .../labelsys/backend/enums/ResourceType.java | 3 + .../backend/enums/RuntimeResultStatus.java | 7 -- .../labelsys/backend/enums/SourceStatus.java | 3 + .../labelsys/backend/enums/TaskStatus.java | 3 + .../com/labelsys/backend/enums/TaskType.java | 5 +- .../service/AnnotationResultService.java | 104 ++++++++++++++---- .../backend/service/ObjectStorageService.java | 2 + .../service/RustfsObjectStorageService.java | 15 +++ 15 files changed, 129 insertions(+), 52 deletions(-) create mode 100644 src/main/java/com/labelsys/backend/enums/AnnotationResultStatus.java delete mode 100644 src/main/java/com/labelsys/backend/enums/RuntimeResultStatus.java diff --git a/src/main/java/com/labelsys/backend/controller/AnnotationResultController.java b/src/main/java/com/labelsys/backend/controller/AnnotationResultController.java index 412b1dd..09ef432 100644 --- a/src/main/java/com/labelsys/backend/controller/AnnotationResultController.java +++ b/src/main/java/com/labelsys/backend/controller/AnnotationResultController.java @@ -50,7 +50,7 @@ public class AnnotationResultController { } @Operation(summary = "查询标注结果比对信息") - @RequirePosition(UserPosition.REVIEWER) + //@RequirePosition(UserPosition.REVIEWER) @GetMapping("/{id}/compare") public Result compare( @Parameter(description = "结果ID", example = "191000000000000401") @@ -60,7 +60,7 @@ public class AnnotationResultController { } @Operation(summary = "提交合并审核结果") - @RequirePosition(UserPosition.REVIEWER) + //@RequirePosition(UserPosition.REVIEWER) @PostMapping("/{id}/merge-review") public Result mergeReview( @Parameter(description = "结果ID", example = "191000000000000401") diff --git a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java index 34d0135..0b30c68 100644 --- a/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java +++ b/src/main/java/com/labelsys/backend/dto/response/AnnotationResultResponse.java @@ -3,12 +3,14 @@ package com.labelsys.backend.dto.response; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; +import com.labelsys.backend.enums.AnnotationResultStatus; + @Schema(description = "标注结果响应") public record AnnotationResultResponse( @Schema(description = "结果ID", example = "191000000000000401") Long id, @Schema(description = "任务ID", example = "191000000000000301") Long taskId, @Schema(description = "资源ID", example = "191000000000000101") Long resourceId, - @Schema(description = "运行态状态", example = "MANUAL_REVIEW_PENDING") String runtimeStatus, + @Schema(description = "标注结果状态", example = "MANUAL_REVIEW_PENDING") AnnotationResultStatus runtimeStatus, @Schema(description = "是否需要人工审核", example = "true") Boolean requiresManualReview, @Schema(description = "是否已删除", example = "false") Boolean isDeleted, @Schema(description = "问答存储模式", example = "INLINE") String qaContentStorageMode, diff --git a/src/main/java/com/labelsys/backend/enums/AnnotationResultStatus.java b/src/main/java/com/labelsys/backend/enums/AnnotationResultStatus.java new file mode 100644 index 0000000..94302cf --- /dev/null +++ b/src/main/java/com/labelsys/backend/enums/AnnotationResultStatus.java @@ -0,0 +1,10 @@ +package com.labelsys.backend.enums; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "标注结果状态枚举值:MANUAL_REVIEW_PENDING待人工审核、MANUAL_REVIEW_PENDING待自动归档、ARCHIVED已归档") +public enum AnnotationResultStatus { + MANUAL_REVIEW_PENDING, + AUTO_ARCHIVE_PENDING, + ARCHIVED +} \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/enums/ConfigMode.java b/src/main/java/com/labelsys/backend/enums/ConfigMode.java index 373acd7..b69a957 100644 --- a/src/main/java/com/labelsys/backend/enums/ConfigMode.java +++ b/src/main/java/com/labelsys/backend/enums/ConfigMode.java @@ -2,11 +2,8 @@ package com.labelsys.backend.enums; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "模型配置模式,枚举值:SELECT:选择已有模型配置、MANUAL:手动配置新模型") +@Schema(description = "模型配置模式,枚举值:SELECT:选择已有模型配置、MANUAL:手动录入新模型") public enum ConfigMode { - @Schema(description = "从已有配置中选择") SELECT, - - @Schema(description = "手动录入配置") MANUAL } diff --git a/src/main/java/com/labelsys/backend/enums/ConfigType.java b/src/main/java/com/labelsys/backend/enums/ConfigType.java index 6a52f31..808ef75 100644 --- a/src/main/java/com/labelsys/backend/enums/ConfigType.java +++ b/src/main/java/com/labelsys/backend/enums/ConfigType.java @@ -1,7 +1,9 @@ package com.labelsys.backend.enums; import java.util.Arrays; +import io.swagger.v3.oas.annotations.media.Schema; +@Schema(description = "系统配置类型,枚举值:model:大模型配置、prompt:提示词配置, system:其他配置项") public enum ConfigType { MODEL, PROMPT, diff --git a/src/main/java/com/labelsys/backend/enums/IndustryType.java b/src/main/java/com/labelsys/backend/enums/IndustryType.java index e243737..8f047c1 100644 --- a/src/main/java/com/labelsys/backend/enums/IndustryType.java +++ b/src/main/java/com/labelsys/backend/enums/IndustryType.java @@ -2,20 +2,11 @@ package com.labelsys.backend.enums; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "行业类型") +@Schema(description = "行业类型,枚举值:TRANSPORT:交通、ELECTRICITY:电力、FINANCE:金融、MEDICAL:医疗、EDUCATION:教育") public enum IndustryType { - @Schema(description = "交通运输") TRANSPORT, - - @Schema(description = "电力") ELECTRICITY, - - @Schema(description = "金融") FINANCE, - - @Schema(description = "医疗") MEDICAL, - - @Schema(description = "教育") EDUCATION } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/enums/QaContentStorageMode.java b/src/main/java/com/labelsys/backend/enums/QaContentStorageMode.java index 20e6c11..d06b557 100644 --- a/src/main/java/com/labelsys/backend/enums/QaContentStorageMode.java +++ b/src/main/java/com/labelsys/backend/enums/QaContentStorageMode.java @@ -1,11 +1,12 @@ package com.labelsys.backend.enums; import java.util.Arrays; +import io.swagger.v3.oas.annotations.media.Schema; +@Schema(description = "问答对存储形式,枚举值:INLINE:记录中保存、EXTERNAL:外部存储器rustfs") public enum QaContentStorageMode { INLINE, EXTERNAL; - public static boolean isValid(String value) { return Arrays.stream(values()).anyMatch(mode -> mode.name().equals(value)); } diff --git a/src/main/java/com/labelsys/backend/enums/ResourceType.java b/src/main/java/com/labelsys/backend/enums/ResourceType.java index f3e4883..de89065 100644 --- a/src/main/java/com/labelsys/backend/enums/ResourceType.java +++ b/src/main/java/com/labelsys/backend/enums/ResourceType.java @@ -2,6 +2,9 @@ package com.labelsys.backend.enums; import java.util.Arrays; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "资源类型,枚举值:TEXT:文本、IMAGE:图片、VIDEO:视频") public enum ResourceType { TEXT, IMAGE, diff --git a/src/main/java/com/labelsys/backend/enums/RuntimeResultStatus.java b/src/main/java/com/labelsys/backend/enums/RuntimeResultStatus.java deleted file mode 100644 index 8f3878d..0000000 --- a/src/main/java/com/labelsys/backend/enums/RuntimeResultStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.labelsys.backend.enums; - -public enum RuntimeResultStatus { - MANUAL_REVIEW_PENDING, - AUTO_ARCHIVE_PENDING, - ARCHIVED -} diff --git a/src/main/java/com/labelsys/backend/enums/SourceStatus.java b/src/main/java/com/labelsys/backend/enums/SourceStatus.java index 9dfede0..4be5ad0 100644 --- a/src/main/java/com/labelsys/backend/enums/SourceStatus.java +++ b/src/main/java/com/labelsys/backend/enums/SourceStatus.java @@ -2,6 +2,9 @@ package com.labelsys.backend.enums; import java.util.Arrays; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "资源状态,暂不使用,枚举值:UPLOADED:已上传、PROCESSING:处理中、READY:已完成、ARCHIVED:已归档") public enum SourceStatus { UPLOADED, PROCESSING, diff --git a/src/main/java/com/labelsys/backend/enums/TaskStatus.java b/src/main/java/com/labelsys/backend/enums/TaskStatus.java index 1ab8ab9..f7495e1 100644 --- a/src/main/java/com/labelsys/backend/enums/TaskStatus.java +++ b/src/main/java/com/labelsys/backend/enums/TaskStatus.java @@ -2,6 +2,9 @@ package com.labelsys.backend.enums; import java.util.Arrays; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "任务状态枚举值:PENDING:待执行、RUNNING:处理中、COMPLETED:已完成、FAILED:失败") public enum TaskStatus { PENDING, RUNNING, diff --git a/src/main/java/com/labelsys/backend/enums/TaskType.java b/src/main/java/com/labelsys/backend/enums/TaskType.java index 69edbfc..92edad4 100644 --- a/src/main/java/com/labelsys/backend/enums/TaskType.java +++ b/src/main/java/com/labelsys/backend/enums/TaskType.java @@ -2,11 +2,8 @@ package com.labelsys.backend.enums; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "标注任务类型") +@Schema(description = "标注任务类型枚举值:EXTRACT_QA:抽取任务、FINE_TUNE:微调任务") public enum TaskType { - @Schema(description = "抽取问答对") EXTRACT_QA, - - @Schema(description = "大模型微调") FINE_TUNE } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java index ec72bfb..a1171c4 100644 --- a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java +++ b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java @@ -1,5 +1,6 @@ package com.labelsys.backend.service; +import java.nio.charset.StandardCharsets; import java.util.List; import org.springframework.stereotype.Service; @@ -15,7 +16,8 @@ import com.labelsys.backend.dto.response.AnnotationResultCompareResponse; import com.labelsys.backend.dto.response.AnnotationResultResponse; import com.labelsys.backend.entity.AnnotationResult; import com.labelsys.backend.entity.SourceResource; -import com.labelsys.backend.enums.RuntimeResultStatus; +import com.labelsys.backend.enums.AnnotationResultStatus; +import com.labelsys.backend.enums.QaContentStorageMode; import com.labelsys.backend.mapper.AnnotationResultMapper; import com.labelsys.backend.mapper.SourceResourceMapper; @@ -30,17 +32,18 @@ public class AnnotationResultService { private final AnnotationResultMapper annotationResultMapper; private final SourceResourceMapper sourceResourceMapper; private final DataPermissionService dataPermissionService; + private final ObjectStorageService objectStorageService; public PageResult pageResults(LoginUser currentUser, AnnotationResultPageQuery query) { List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); - LambdaQueryWrapper wrapper = - new LambdaQueryWrapper().eq(AnnotationResult::getCompanyId, currentUser.companyId()) + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AnnotationResult::getCompanyId, currentUser.companyId()) .eq(query.taskId() != null, AnnotationResult::getTaskId, query.taskId()) .eq(query.resourceId() != null, AnnotationResult::getResourceId, query.resourceId()) .eq(query.requiresManualReview() != null, AnnotationResult::getRequiresManualReview, - query.requiresManualReview()); + query.requiresManualReview()); if (shouldFilterByUserId) { wrapper.eq(AnnotationResult::getCreatorId, currentUser.userId()); @@ -54,49 +57,104 @@ public class AnnotationResultService { Page resultPage = annotationResultMapper.selectPage(page, wrapper); List records = resultPage.getRecords().stream().map(this::toResponse) - .filter(response -> query.runtimeStatus() == null || query.runtimeStatus().equals(response.runtimeStatus())) - .toList(); + .filter(response -> query.runtimeStatus() == null + || query.runtimeStatus().equals(response.runtimeStatus())) + .toList(); - return new PageResult<>(records, resultPage.getTotal(), (int)resultPage.getCurrent(), - (int)resultPage.getSize()); + return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), + (int) resultPage.getSize()); } public AnnotationResultResponse getResult(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()); + currentUser.companyId(), currentUser.userId()); throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); } return toResponse(result); } + private AnnotationResultResponse toResponse(AnnotationResult result) { + return new AnnotationResultResponse(result.getId(), result.getTaskId(), result.getResourceId(), + deriveStatus(result), result.getRequiresManualReview(), result.getIsDeleted(), + result.getQaContentStorageMode(), result.getReviewComment(), result.getReviewedAt(), + result.getCreatedAt()); + } + + 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; + } + 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()); + currentUser.companyId(), currentUser.userId()); throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); } + + String qaContentJson = resolveQaContent(result); + SourceResource resource = sourceResourceMapper.selectById(result.getResourceId()); - return new AnnotationResultCompareResponse(result.getId(), result.getTaskId(), result.getResourceId(), - result.getQaContentJson(), result.getDiffSummary(), result.getQaContentStorageMode(), - result.getQaContentFilePath(), resource == null ? null : resource.getFilePath()); + return new AnnotationResultCompareResponse( + result.getId(), + result.getTaskId(), + result.getResourceId(), + qaContentJson, + result.getDiffSummary(), + result.getQaContentStorageMode(), + result.getQaContentFilePath(), + resource == null ? null : resource.getFilePath()); } - private AnnotationResultResponse toResponse(AnnotationResult result) { - return new AnnotationResultResponse(result.getId(), result.getTaskId(), result.getResourceId(), - deriveStatus(result), result.getRequiresManualReview(), result.getIsDeleted(), - result.getQaContentStorageMode(), result.getReviewComment(), result.getReviewedAt(), result.getCreatedAt()); + private String resolveQaContent(AnnotationResult result) { + if (QaContentStorageMode.EXTERNAL.name().equals(result.getQaContentStorageMode())) { + if (result.getQaContentFilePath() == null || result.getQaContentFilePath().isBlank()) { + log.warn("External storage mode but file path is empty, resultId={}", result.getId()); + return "{}"; + } + try { + String filePath = result.getQaContentFilePath(); + String bucketName = extractBucketName(filePath); + String objectKey = extractObjectKey(filePath); + byte[] content = objectStorageService.download(bucketName, objectKey); + return new String(content, StandardCharsets.UTF_8); + } catch (Exception e) { + log.error("Failed to download external qa content, resultId={}, filePath={}", + result.getId(), result.getQaContentFilePath(), e); + throw new BusinessException(ResultCode.ERROR, "下载问答内容失败"); + } + } else { + return result.getQaContentJson() != null ? result.getQaContentJson() : "{}"; + } } - private String deriveStatus(AnnotationResult result) { - if (Boolean.TRUE.equals(result.getIsDeleted())) { - return RuntimeResultStatus.ARCHIVED.name(); + private String extractBucketName(String filePath) { + if (filePath.startsWith("/")) { + filePath = filePath.substring(1); } - if (Boolean.TRUE.equals(result.getRequiresManualReview())) { - return RuntimeResultStatus.MANUAL_REVIEW_PENDING.name(); + int firstSlash = filePath.indexOf("/"); + if (firstSlash > 0) { + return filePath.substring(0, firstSlash); } - return RuntimeResultStatus.AUTO_ARCHIVE_PENDING.name(); + throw new BusinessException(ResultCode.BAD_REQUEST, "无效的文件路径格式"); + } + + private String extractObjectKey(String filePath) { + if (filePath.startsWith("/")) { + filePath = filePath.substring(1); + } + int firstSlash = filePath.indexOf("/"); + if (firstSlash > 0 && firstSlash < filePath.length() - 1) { + return filePath.substring(firstSlash + 1); + } + throw new BusinessException(ResultCode.BAD_REQUEST, "无效的文件路径格式"); } } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/ObjectStorageService.java b/src/main/java/com/labelsys/backend/service/ObjectStorageService.java index b043112..b40556b 100644 --- a/src/main/java/com/labelsys/backend/service/ObjectStorageService.java +++ b/src/main/java/com/labelsys/backend/service/ObjectStorageService.java @@ -5,4 +5,6 @@ public interface ObjectStorageService { String upload(String bucketName, String objectKey, byte[] content, String contentType); void delete(String bucketName, String objectKey); + + byte[] download(String bucketName, String objectKey); } diff --git a/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java b/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java index 0dd9365..5b11725 100644 --- a/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java +++ b/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java @@ -5,8 +5,10 @@ import com.labelsys.backend.common.exception.BusinessException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.core.sync.ResponseTransformer; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; @Service @@ -42,4 +44,17 @@ public class RustfsObjectStorageService implements ObjectStorageService { throw new BusinessException(ResultCode.ERROR, "对象存储删除失败"); } } + + @Override + public byte[] download(String bucketName, String objectKey) { + try { + GetObjectRequest request = GetObjectRequest.builder() + .bucket(bucketName) + .key(objectKey) + .build(); + return s3Client.getObject(request, ResponseTransformer.toBytes()).asByteArray(); + } catch (Exception ex) { + throw new BusinessException(ResultCode.ERROR, "对象存储下载失败"); + } + } }