Merge branch 'dev54'

This commit is contained in:
wh
2026-05-07 16:01:02 +08:00
10 changed files with 207 additions and 82 deletions

View File

@@ -4,6 +4,7 @@ import com.labelsys.backend.context.UserContext;
import com.labelsys.backend.dto.common.PageResult;
import com.labelsys.backend.dto.request.AnnotationResultHistoryPageQuery;
import com.labelsys.backend.dto.response.AnnotationResultHistoryResponse;
import com.labelsys.backend.dto.response.FileContentResponse;
import com.labelsys.backend.service.AnnotationResultArchiveService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -38,4 +39,12 @@ public class AnnotationResultArchiveController {
@PathVariable Long id) {
return ResponseEntity.ok(annotationResultArchiveService.getHistory(UserContext.requireUser(), id));
}
@Operation(summary = "加载归档文件内容")
@GetMapping("/{id}/content")
public ResponseEntity<FileContentResponse> loadFileContent(
@Parameter(description = "历史记录ID", example = "901")
@PathVariable Long id) {
return ResponseEntity.ok(annotationResultArchiveService.loadFileContent(UserContext.requireUser(), id));
}
}

View File

@@ -7,14 +7,20 @@ import java.time.LocalDateTime;
@Schema(description = "归档历史响应")
public record AnnotationResultHistoryResponse(
@Schema(description = "历史记录ID", example = "901") Long id,
@Schema(description = "来源结果ID", example = "802") Long sourceResultId,
@Schema(description = "任务ID", example = "701") Long taskId,
@Schema(description = "资源ID", example = "601") Long resourceId,
@Schema(description = "历史记录ID, 不显示", example = "901") Long id,
//@Schema(description = "来源结果ID", example = "802") Long sourceResultId,
@Schema(description = "任务ID", example = "191000000000000301") Long taskId,
@Schema(description = "任务名称", example = "产品说明书标注") String taskName, // 新增
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
@Schema(description = "资源名称", example = "产品A说明书.pdf") String resourceName, // 新增
@Schema(description = "问答内容文件路径", example = "annotation-results/2/qa/802.json") String qaContentFilePath,
@Schema(description = "归档原因", example = "审核通过后归档") String archiveReason,
@Schema(description = "归档操作人ID", example = "5") Long archivedBy,
@Schema(description = "归档时间", example = "2026-05-06T10:30:00") LocalDateTime archivedAt,
@Schema(description = "创建时间", example = "2026-05-06T10:30:00") LocalDateTime createdAt
@Schema(description = "创建时间", example = "2026-05-06T10:30:00") LocalDateTime createdAt,
@Schema(description = "审核人ID自动归档时为null", example = "5") Long reviewerId,
@Schema(description = "审核人姓名自动归档时为auto", example = "张三") String reviewerName,
@Schema(description = "审核意见自动归档时为auto", example = "内容符合要求") String reviewerComment
) {
}

View File

@@ -9,7 +9,9 @@ import java.time.LocalDateTime;
public record AnnotationResultResponse(
@Schema(description = "结果ID", example = "191000000000000401") Long id,
@Schema(description = "任务ID", example = "191000000000000301") Long taskId,
@Schema(description = "任务名称", example = "产品说明书标注") String taskName, // 新增
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
@Schema(description = "资源名称", example = "产品A说明书.pdf") String resourceName, // 新增
@Schema(description = "标注结果状态", example = "MANUAL_REVIEW_PENDING") AnnotationResultStatus runtimeStatus,
@Schema(description = "是否需要人工审核", example = "true") Boolean requiresManualReview,
@Schema(description = "是否已删除", example = "false") Boolean isDeleted,

View File

@@ -0,0 +1,11 @@
package com.labelsys.backend.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "文件内容响应")
public record FileContentResponse(
@Schema(description = "文件路径", example = "annotation-results/2/qa/801.json") String filePath,
@Schema(description = "文件内容") String content,
@Schema(description = "文件大小(字节)", example = "1024") Integer size
) {
}

View File

@@ -33,6 +33,13 @@ public class AnnotationResult {
@TableField("resource_id")
private Long resourceId;
// 添加字段
@TableField("task_name")
private String taskName;
@TableField("resource_name")
private String resourceName;
@TableField("qa_content_file_path")
private String qaContentFilePath;

View File

@@ -3,13 +3,13 @@ package com.labelsys.backend.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.labelsys.backend.enums.UserRole;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@@ -17,18 +17,25 @@ import lombok.NoArgsConstructor;
@TableName("annotation_result_history")
public class AnnotationResultHistory {
@TableId(type = IdType.INPUT)
private Long id;
private Long companyId;
private Long creatorId;
private String creatorRole;
private Long sourceResultId;
private Long taskId;
private Long resourceId;
private Long id;
private Long companyId;
private Long creatorId;
private String creatorRole;
private Long sourceResultId;
private Long taskId;
private Long resourceId;
private String taskName;
private String resourceName;
//private String qaContentJson;
private String qaContentStorageMode;
private String qaContentFilePath;
private String archiveReason;
private Long archivedBy;
// private String qaContentStorageMode;
private String qaContentFilePath;
private String archiveReason;
private Long archivedBy;
private LocalDateTime archivedAt;
private LocalDateTime createdAt;
}
// 新增审核人相关字段
private Long reviewerId;
private String reviewerName;
private String reviewerComment;
}

View File

@@ -9,6 +9,7 @@ import com.labelsys.backend.context.LoginUser;
import com.labelsys.backend.dto.common.PageResult;
import com.labelsys.backend.dto.request.AnnotationResultHistoryPageQuery;
import com.labelsys.backend.dto.response.AnnotationResultHistoryResponse;
import com.labelsys.backend.dto.response.FileContentResponse;
import com.labelsys.backend.dto.response.MergeReviewResultResponse;
import com.labelsys.backend.entity.AnnotationResult;
import com.labelsys.backend.entity.AnnotationResultHistory;
@@ -91,14 +92,19 @@ public class AnnotationResultArchiveService {
private AnnotationResultHistoryResponse toResponse(AnnotationResultHistory history) {
return new AnnotationResultHistoryResponse(
history.getId(),
history.getSourceResultId(),
// history.getSourceResultId(),
history.getTaskId(),
history.getTaskName(), // 新增
history.getResourceId(),
history.getResourceName(), // 新增
history.getQaContentFilePath(),
history.getArchiveReason(),
history.getArchivedBy(),
history.getArchivedAt(),
history.getCreatedAt()
history.getCreatedAt(),
history.getReviewerId(),
history.getReviewerName(),
history.getReviewerComment()
);
}
@@ -135,7 +141,7 @@ public class AnnotationResultArchiveService {
LocalDateTime archivedAt = LocalDateTime.now();
// 从对象存储读取 qa.json 内容
String qaContentJson = loadQaContentJson(result);
// String qaContentJson = loadQaContentJson(result);
AnnotationResultHistory history = AnnotationResultHistory.builder()
.id(IdGenerator.nextId())
@@ -144,12 +150,17 @@ public class AnnotationResultArchiveService {
.creatorRole(result.getCreatorRole())
.sourceResultId(result.getId())
.taskId(result.getTaskId())
.taskName(result.getTaskName())
.resourceId(result.getResourceId())
.resourceName(result.getResourceName())
//.qaContentJson(qaContentJson) // 使用从对象存储读取的内容
.qaContentFilePath(result.getQaContentFilePath())
.archiveReason(archiveReason)
.archivedBy(reviewerId)
.archivedAt(archivedAt)
.reviewerId(null)
.reviewerName("auto")
.reviewerComment("auto")
.build();
annotationResultHistoryMapper.insert(history);
@@ -208,4 +219,35 @@ public class AnnotationResultArchiveService {
int firstSlash = filePath.indexOf('/');
return firstSlash > 0 ? filePath.substring(firstSlash + 1) : "";
}
/**
* 加载归档记录的文件内容
* @param currentUser 当前用户
* @param historyId 历史记录ID
* @return 文件内容响应
*/
public FileContentResponse loadFileContent(LoginUser currentUser, Long historyId) {
AnnotationResultHistory history = annotationResultHistoryMapper.selectById(historyId);
if (history == null || !history.getCompanyId().equals(currentUser.companyId())) {
throw new BusinessException(ResultCode.NOT_FOUND, "历史记录不存在");
}
//assertHistoryPermission(currentUser, history);
String filePath = history.getQaContentFilePath();
if (filePath == null || filePath.isEmpty()) {
throw new BusinessException(ResultCode.ERROR, "文件路径为空");
}
try {
String bucketName = extractBucketName(filePath);
String objectKey = extractObjectKey(filePath);
byte[] content = objectStorageService.download(bucketName, objectKey);
String contentStr = new String(content, StandardCharsets.UTF_8);
return new FileContentResponse(filePath, contentStr, content.length);
} catch (Exception e) {
log.error("Failed to load file content, historyId={}, filePath={}", historyId, filePath, e);
throw new BusinessException(ResultCode.ERROR, "加载文件内容失败");
}
}
}

View File

@@ -25,7 +25,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
@@ -48,6 +47,7 @@ public class AnnotationResultService {
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
var wrapper = new LambdaQueryWrapper<AnnotationResult>()
.eq(AnnotationResult::getIsDeleted, Boolean.FALSE)
.eq(AnnotationResult::getCompanyId, currentUser.companyId())
.eq(query.taskId() != null, AnnotationResult::getTaskId, query.taskId())
.eq(query.resourceId() != null, AnnotationResult::getResourceId, query.resourceId())
@@ -95,7 +95,7 @@ public class AnnotationResultService {
//assertResultPermission(currentUser, result);
QaContent qaContent = loadQaContent(result);
DiffContent diffContent = StringUtils.hasText(result.getDiffSummaryFilePath()) ?
DiffContent diffContent = Boolean.TRUE.equals(result.getRequiresManualReview()) ?
loadDiffSummary(result) : null;
SourceResource resource = sourceResourceMapper.selectById(result.getResourceId());
@@ -170,14 +170,15 @@ public class AnnotationResultService {
saveQaContent(result, updatedQaContent);
// 更新数据库记录
result.setIsDeleted(Boolean.TRUE);
result.setReviewerId(currentUser.userId());
result.setReviewComment(request.reviewComment());
result.setReviewedAt(LocalDateTime.now());
result.setRequiresManualReview(false);
annotationResultMapper.updateById(result);
// 归档到历史表
archiveToHistory(result, currentUser, "审核通过后归档");
// 归档到历史表(人工审核后归档)
archiveToHistory(result, currentUser, "审核通过后归档", false);
log.info("merged review result, companyId={}, userId={}, resultId={}",
currentUser.companyId(), currentUser.userId(), resultId);
@@ -187,7 +188,9 @@ public class AnnotationResultService {
return new AnnotationResultResponse(
result.getId(),
result.getTaskId(),
result.getTaskName(), // 新增
result.getResourceId(),
result.getResourceName(), // 新增
deriveStatus(result),
result.getRequiresManualReview(),
result.getIsDeleted(),
@@ -262,31 +265,55 @@ public class AnnotationResultService {
}
}
private void archiveToHistory(AnnotationResult result, LoginUser currentUser, String archiveReason) {
/**
* 归档到历史表
* @param result 标注结果
* @param currentUser 当前用户
* @param archiveReason 归档原因
* @param isAutoArchive 是否自动归档true=自动归档false=人工审核后归档)
*/
private void archiveToHistory(AnnotationResult result, LoginUser currentUser, String archiveReason, boolean isAutoArchive) {
try {
// 读取 qa.json 内容用于归档
QaContent qaContent = loadQaContent(result);
// 构建归档记录
AnnotationResultHistory history = AnnotationResultHistory.builder()
AnnotationResultHistory.AnnotationResultHistoryBuilder historyBuilder = AnnotationResultHistory.builder()
.id(IdGenerator.nextId())
.companyId(result.getCompanyId())
.creatorId(currentUser.userId())
.creatorRole(currentUser.role().name())
.creatorId(result.getCreatorId())
.creatorRole(result.getCreatorRole())
.sourceResultId(result.getId())
.taskId(result.getTaskId())
.taskName(result.getTaskName())
.resourceId(result.getResourceId())
.resourceName(result.getResourceName())
//.qaContentJson(objectMapper.writeValueAsString(qaContent))
.qaContentFilePath(result.getQaContentFilePath())
.archiveReason(archiveReason)
.archivedBy(currentUser.userId())
.archivedAt(LocalDateTime.now())
.createdAt(LocalDateTime.now())
.build();
.createdAt(LocalDateTime.now());
annotationResultHistoryMapper.insert(history);
// 根据归档类型设置审核人信息
if (isAutoArchive) {
// 自动归档reviewer_id为NULLname和comment为"auto"
historyBuilder
.reviewerId(null)
.reviewerName("auto")
.reviewerComment("auto");
} else {
// 人工审核后归档:使用审核人信息
historyBuilder
.reviewerId(result.getReviewerId())
.reviewerName(currentUser.realName())
.reviewerComment(result.getReviewComment());
}
log.info("archived result to history, resultId={}, historyId={}", result.getId(), history.getId());
annotationResultHistoryMapper.insert(historyBuilder.build());
log.info("archived result to history, resultId={}, historyId={}, isAutoArchive={}",
result.getId(), historyBuilder.build().getId(), isAutoArchive);
} catch (Exception e) {
log.error("Failed to archive result to history, resultId={}", result.getId(), e);
throw new BusinessException(ResultCode.ERROR, "归档失败");