根据界面需求优化

This commit is contained in:
wh
2026-05-08 22:38:32 +08:00
parent d679340f3c
commit 4065d993e2
13 changed files with 128 additions and 59 deletions

View File

@@ -6,6 +6,7 @@ import com.labelsys.backend.dto.common.PageResult;
import com.labelsys.backend.dto.request.AnnotationResultPageQuery;
import com.labelsys.backend.dto.request.MergeReviewResultRequest;
import com.labelsys.backend.dto.response.AnnotationResultCompareResponse;
import com.labelsys.backend.dto.response.AnnotationResultDetailResponse;
import com.labelsys.backend.dto.response.AnnotationResultResponse;
import com.labelsys.backend.enums.UserPosition;
import com.labelsys.backend.service.AnnotationResultService;
@@ -39,7 +40,7 @@ public class AnnotationResultController {
@Operation(summary = "查询标注结果详情")
@GetMapping("/{id}")
public ResponseEntity<AnnotationResultResponse> getResult(
public ResponseEntity<AnnotationResultDetailResponse> getResult(
@Parameter(description = "结果ID", example = "191000000000000401")
@PathVariable Long id) {
return ResponseEntity.ok(annotationResultService.getResult(UserContext.requireUser(), id));

View File

@@ -6,7 +6,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
public record SourceResourcePageQuery(
@Schema(description = "关键字", example = "运输") String keyword,
@Schema(description = "资源类型", example = "TEXT") String resourceType,
@Schema(description = "资源状态", example = "READY") String sourceStatus,
@Schema(description = "页码可选与pageSize同时提供时启用分页", example = "1") Integer pageNo,
@Schema(description = "每页数量可选与pageNo同时提供时启用分页", example = "10") Integer pageSize
) {

View File

@@ -0,0 +1,63 @@
package com.labelsys.backend.dto.response;
import com.labelsys.backend.enums.AnnotationResultStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "标注结果详情响应(包含文件内容)")
public record AnnotationResultDetailResponse(
@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,
// 文件路径
@Schema(description = "问答内容文件路径", example = "annotation-results/2/qa/801.json") String qaContentFilePath,
@Schema(description = "差异摘要文件路径", example = "annotation-results/2/diff/801.json") String diffSummaryFilePath,
// 文件内容
@Schema(description = "问答内容") QaContentDto qaContent,
@Schema(description = "差异摘要(需要审核时有值)") DiffContentDto diffSummary,
@Schema(description = "审核备注", example = "需统一时间字段口径。") String reviewComment,
@Schema(description = "审核时间", example = "2026-04-27T11:00:00") LocalDateTime reviewedAt,
@Schema(description = "创建时间", example = "2026-04-27T10:40:00") LocalDateTime createdAt
) {
@Schema(description = "问答内容结构")
public record QaContentDto(
@Schema(description = "问答记录列表") List<QaRecordDto> records
) {
}
@Schema(description = "问答记录")
public record QaRecordDto(
@Schema(description = "记录ID", example = "q1") String id,
@Schema(description = "问题", example = "产品重量是多少?") String question,
@Schema(description = "答案", example = "5kg") String answer,
@Schema(description = "是否需要审核", example = "false") Boolean requiresReview
) {
}
@Schema(description = "差异摘要结构")
public record DiffContentDto(
@Schema(description = "差异记录列表") List<DiffRecordDto> records
) {
}
@Schema(description = "差异记录")
public record DiffRecordDto(
@Schema(description = "关联问答ID", example = "q2") String qaId,
@Schema(description = "问题", example = "保修期多久?") String question,
@Schema(description = "抽取答案", example = "2年") String extractAnswer,
@Schema(description = "验证答案", example = "3年") String verifyAnswer,
@Schema(description = "差异原因", example = "抽取与验证结果不一致") String diffReason,
@Schema(description = "合并后答案") String mergedAnswer
) {
}
}

View File

@@ -11,7 +11,6 @@ public record SourceResourceResponse(
@Schema(description = "桶名称", example = "annotation-source") String bucketName,
@Schema(description = "文件路径", example = "company/191000000000000001/text/191000000000000101.txt") String filePath,
@Schema(description = "文件大小", example = "20480") Long fileSize,
@Schema(description = "资源状态", example = "READY") String sourceStatus,
@Schema(description = "存储提供方", example = "rustfs") String storageProvider,
@Schema(description = "是否有BBOX标注,不显示", example = "false") Boolean hasBbox,
@Schema(description = "备注", example = "第一批导入样本") String remark,

View File

@@ -11,7 +11,6 @@ public record SourceUploadResponse(
@Schema(description = "桶名称", example = "annotation-source") String bucketName,
@Schema(description = "文件路径", example = "company/191000000000000001/text/191000000000000101.txt") String filePath,
@Schema(description = "文件大小", example = "20480") Long fileSize,
@Schema(description = "资源状态", example = "READY") String sourceStatus,
@Schema(description = "创建时间", example = "2026-04-27T10:00:00") LocalDateTime createdAt
) {
}
}

View File

@@ -26,7 +26,6 @@ public class SourceResource {
private String bucketName;
private String filePath;
private Long fileSize;
private String sourceStatus;
private String storageProvider;
private Boolean hasBbox;
private String remark;

View File

@@ -1,17 +0,0 @@
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,
READY,
ARCHIVED;
public static boolean isValid(String value) {
return Arrays.stream(values()).anyMatch(status -> status.name().equals(value));
}
}

View File

@@ -11,6 +11,7 @@ import com.labelsys.backend.dto.common.PageResult;
import com.labelsys.backend.dto.request.AnnotationResultPageQuery;
import com.labelsys.backend.dto.request.MergeReviewResultRequest;
import com.labelsys.backend.dto.response.AnnotationResultCompareResponse;
import com.labelsys.backend.dto.response.AnnotationResultDetailResponse;
import com.labelsys.backend.dto.response.AnnotationResultResponse;
import com.labelsys.backend.entity.AnnotationResult;
import com.labelsys.backend.entity.AnnotationResultHistory;
@@ -80,7 +81,7 @@ public class AnnotationResultService {
}
}
public AnnotationResultResponse getResult(LoginUser currentUser, Long resultId) {
public AnnotationResultDetailResponse getResult(LoginUser currentUser, Long resultId) {
try {
AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId,
currentUser.companyId());
@@ -91,7 +92,13 @@ public class AnnotationResultService {
throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在");
}
//assertResultPermission(currentUser, result);
return toResponse(result);
// 加载文件内容
QaContent qaContent = loadQaContent(result);
DiffContent diffContent = Boolean.TRUE.equals(result.getRequiresManualReview()) ?
loadDiffSummary(result) : null;
return toDetailResponse(result, qaContent, diffContent);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
@@ -101,6 +108,48 @@ public class AnnotationResultService {
}
}
private AnnotationResultDetailResponse toDetailResponse(AnnotationResult result,
QaContent qaContent, DiffContent diffContent) {
// 转换 QA 内容(仅保留 records
AnnotationResultDetailResponse.QaContentDto qaContentDto = new AnnotationResultDetailResponse.QaContentDto(
qaContent.records().stream()
.map(r -> new AnnotationResultDetailResponse.QaRecordDto(
r.id(), r.question(), r.answer(), r.requiresReview()))
.toList()
);
// 转换差异内容(仅保留 records
AnnotationResultDetailResponse.DiffContentDto diffContentDto = null;
if (diffContent != null) {
diffContentDto = new AnnotationResultDetailResponse.DiffContentDto(
diffContent.records().stream()
.map(r -> new AnnotationResultDetailResponse.DiffRecordDto(
r.qaId(), r.question(), r.extractAnswer(),
r.verifyAnswer(), r.diffReason(), r.mergedAnswer()))
.toList()
);
}
return new AnnotationResultDetailResponse(
result.getId(),
result.getTaskId(),
result.getTaskName(),
result.getResourceId(),
result.getResourceName(),
deriveStatus(result),
result.getRequiresManualReview(),
result.getIsDeleted(),
result.getQaContentFilePath(),
result.getDiffSummaryFilePath(),
qaContentDto,
diffContentDto,
result.getReviewComment(),
result.getReviewedAt(),
result.getCreatedAt()
);
}
public AnnotationResultCompareResponse compareResult(LoginUser currentUser, Long resultId) {
try {
AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId,

View File

@@ -14,7 +14,6 @@ import com.labelsys.backend.entity.AnnotationTask;
import com.labelsys.backend.entity.AnnotationTaskResource;
import com.labelsys.backend.entity.SourceResource;
import com.labelsys.backend.enums.IndustryType;
import com.labelsys.backend.enums.SourceStatus;
import com.labelsys.backend.enums.TaskStatus;
import com.labelsys.backend.enums.TaskType;
import com.labelsys.backend.mapper.AnnotationTaskMapper;
@@ -219,9 +218,6 @@ public class AnnotationTaskService {
resource.getCreatorRole())) {
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问资源");
}
if (!SourceStatus.READY.name().equals(resource.getSourceStatus())) {
throw new BusinessException(ResultCode.BAD_REQUEST, "仅允许选择已就绪资源");
}
}
resources.sort(Comparator.comparing(SourceResource::getId));
return resources;

View File

@@ -20,7 +20,7 @@ import com.labelsys.backend.entity.ImageBboxAnnotation;
import com.labelsys.backend.entity.SourceResource;
import com.labelsys.backend.entity.SysUser;
import com.labelsys.backend.enums.ResourceType;
import com.labelsys.backend.enums.SourceStatus;
import com.labelsys.backend.mapper.AnnotationTaskResourceMapper;
import com.labelsys.backend.mapper.ImageBboxAnnotationMapper;
import com.labelsys.backend.mapper.SourceResourceMapper;
@@ -91,14 +91,12 @@ public class SourceResourceService {
request.getResourceName() :
file.getOriginalFilename())
.resourceType(request.getResourceType()).bucketName(objectStorageProperties.getSourceBucket())
.filePath(objectKey).fileSize(file.getSize()).sourceStatus(SourceStatus.READY.name())
.storageProvider("rustfs").remark(request.getRemark()).build();
.filePath(objectKey).fileSize(file.getSize()).storageProvider("rustfs").remark(request.getRemark()).build();
sourceResourceMapper.insert(resource);
log.info("uploaded source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(),
currentUser.userId(), resourceId);
return new SourceUploadResponse(resource.getId(), resource.getResourceName(), resource.getResourceType(),
resource.getBucketName(), resource.getFilePath(), resource.getFileSize(),
resource.getSourceStatus(),
resource.getCreatedAt());
} catch (BusinessException e) {
throw e;
@@ -118,8 +116,6 @@ public class SourceResourceService {
new LambdaQueryWrapper<SourceResource>().eq(SourceResource::getCompanyId, currentUser.companyId())
.eq(StringUtils.hasText(query.resourceType()), SourceResource::getResourceType,
query.resourceType())
.eq(StringUtils.hasText(query.sourceStatus()), SourceResource::getSourceStatus,
query.sourceStatus())
.like(StringUtils.hasText(query.keyword()), SourceResource::getResourceName,
query.keyword());
@@ -195,14 +191,6 @@ public class SourceResourceService {
resource.getCreatorRole())) {
throw new BusinessException(ResultCode.FORBIDDEN, "无权删除资源");
}
int bindCount = annotationTaskResourceMapper.countByResourceId(resourceId);
if (bindCount > 0) {
resource.setSourceStatus(SourceStatus.ARCHIVED.name());
sourceResourceMapper.updateById(resource);
log.info("archived referenced source resource, companyId={}, userId={}, resourceId={}",
currentUser.companyId(), currentUser.userId(), resourceId);
return;
}
objectStorageService.delete(resource.getBucketName(), resource.getFilePath());
sourceResourceMapper.deleteById(resourceId);
log.info("deleted source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(),
@@ -231,9 +219,6 @@ public class SourceResourceService {
resourceId, currentUser.companyId(), currentUser.userId());
throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在");
}
if (!"READY".equals(resource.getSourceStatus())) {
throw new BusinessException(ResultCode.BAD_REQUEST, "资源未就绪");
}
return objectStorageService.download(resource.getBucketName(), resource.getFilePath());
} catch (BusinessException e) {
throw e;
@@ -376,7 +361,7 @@ public class SourceResourceService {
private SourceResourceResponse toResponse(SourceResource resource) {
SysUser creator = sysUserMapper.selectById(resource.getCreatorId());
return new SourceResourceResponse(resource.getId(), resource.getResourceName(), resource.getResourceType(),
resource.getBucketName(), resource.getFilePath(), resource.getFileSize(), resource.getSourceStatus(),
resource.getBucketName(), resource.getFilePath(), resource.getFileSize(),
resource.getStorageProvider(), resource.getHasBbox(), resource.getRemark(),
creator == null ? null : creator.getRealName(), resource.getCreatedAt(), resource.getUpdatedAt());
}

View File

@@ -11,7 +11,6 @@
<result column="bucket_name" property="bucketName" />
<result column="file_path" property="filePath" />
<result column="file_size" property="fileSize" />
<result column="source_status" property="sourceStatus" />
<result column="storage_provider" property="storageProvider" />
<result column="remark" property="remark" />
<result column="created_at" property="createdAt" />
@@ -19,7 +18,7 @@
</resultMap>
<sql id="SourceResourceColumns"> id, company_id, creator_id, creator_role, resource_name,
resource_type, bucket_name, file_path, file_size, source_status, storage_provider, remark,
resource_type, bucket_name, file_path, file_size, storage_provider, remark,
created_at, updated_at </sql>
<select id="selectByCompanyIdAndIds" resultMap="SourceResourceResultMap"> select <include

View File

@@ -57,12 +57,12 @@ ON CONFLICT DO NOTHING;
INSERT INTO source_resource (
id, company_id, creator_id, creator_role, resource_name, resource_type,
bucket_name, file_path, file_size, source_status, storage_provider, has_bbox, remark
bucket_name, file_path, file_size, storage_provider, has_bbox, remark
) VALUES
(601, 2, 3, 'EMPLOYEE', '设备巡检规范.txt', 'TEXT',
'source-data', 'text/202604/601.txt', 20480, 'READY', 'rustfs', NULL, '文本资源示例'),
'source-data', 'text/202604/601.txt', 20480, 'rustfs', NULL, '文本资源示例'),
(602, 2, 3, 'EMPLOYEE', '控制柜照片.jpg', 'IMAGE',
'source-data', 'image/202604/602.jpg', 532480, 'READY', 'rustfs', TRUE, '图片资源示例')
'source-data', 'image/202604/602.jpg', 532480, 'rustfs', TRUE, '图片资源示例')
ON CONFLICT DO NOTHING;
INSERT INTO annotation_task (
@@ -89,12 +89,12 @@ INSERT INTO annotation_result (
requires_manual_review, is_deleted, reviewer_id, review_comment, reviewed_at
) VALUES
(801, 2, 3, 'EMPLOYEE', 701, '多资源问答抽取任务', 601, '设备巡检规范.txt',
'annotation-results/qa/801.json',
'annotation-results/diff/801.json',
'annotation-artifacts/qa/qa1.json',
'annotation-artifacts/diff/diff1.json',
TRUE, FALSE, NULL, NULL, NULL),
(802, 2, 3, 'EMPLOYEE', 702, '图片问答抽取任务', 602, '控制柜照片.jpg',
'annotation-results/qa/802.json',
NULL,
'annotation-artifacts/qa/qa2.json',
'annotation-artifacts/diff/diff2.json',
FALSE, FALSE, 5, '结果可通过。', CURRENT_TIMESTAMP)
ON CONFLICT DO NOTHING;

View File

@@ -133,7 +133,6 @@ CREATE TABLE IF NOT EXISTS source_resource
bucket_name VARCHAR(128) NOT NULL,
file_path VARCHAR(512) NOT NULL,
file_size BIGINT NOT NULL DEFAULT 0,
source_status VARCHAR(32) NOT NULL DEFAULT 'UPLOADED',
storage_provider VARCHAR(64) NOT NULL DEFAULT 'rustfs',
has_bbox BOOLEAN,
remark VARCHAR(255),
@@ -153,7 +152,6 @@ COMMENT ON COLUMN source_resource.resource_type IS '资源类型,默认 TEXT
COMMENT ON COLUMN source_resource.bucket_name IS '对象存储桶名称。';
COMMENT ON COLUMN source_resource.file_path IS '文件存储路径,表示对象在存储系统中的实际路径。';
COMMENT ON COLUMN source_resource.file_size IS '文件大小,单位字节,默认 0。';
COMMENT ON COLUMN source_resource.source_status IS '资源状态,默认 UPLOADED可选 PROCESSING、READY、ARCHIVED。';
COMMENT ON COLUMN source_resource.storage_provider IS '存储提供方,默认 rustfs。';
COMMENT ON COLUMN source_resource.has_bbox IS '是否有BBOX标注。NULL表示非图片资源或未标注TRUE表示已标注BBOXFALSE表示已删除BBOX标注。';
COMMENT ON COLUMN source_resource.remark IS '备注说明。';
@@ -418,7 +416,6 @@ CREATE INDEX IF NOT EXISTS idx_sys_user_position ON sys_user (company_id, positi
CREATE INDEX IF NOT EXISTS idx_sys_menu_company_sort ON sys_menu (company_id, sort_order);
CREATE INDEX IF NOT EXISTS idx_sys_config_company_type ON sys_config (company_id, config_type);
CREATE INDEX IF NOT EXISTS idx_source_resource_company_type ON source_resource (company_id, resource_type);
CREATE INDEX IF NOT EXISTS idx_source_resource_company_status ON source_resource (company_id, source_status);
CREATE INDEX IF NOT EXISTS idx_source_resource_creator ON source_resource (company_id, creator_id);
CREATE INDEX IF NOT EXISTS idx_source_resource_has_bbox ON source_resource (company_id, has_bbox);
CREATE INDEX IF NOT EXISTS idx_source_resource_has_bbox ON source_resource (company_id, has_bbox);