From e1c1628e297f66a7244a018c4b580ee8e71636f9 Mon Sep 17 00:00:00 2001 From: wh Date: Sat, 9 May 2026 17:57:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=84=E6=BA=90=E8=BD=AF=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=88=A4=E6=96=AD=EF=BC=8Cbbxo=E6=9F=A5=E8=AF=A2=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E4=B8=B4=E6=97=B6=E7=AD=BE=E5=90=8D=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/ImageBboxResponse.java | 1 + .../backend/entity/AnnotationResult.java | 1 + .../backend/entity/AnnotationTask.java | 2 + .../backend/entity/ImageBboxAnnotation.java | 12 ++ .../backend/entity/SourceResource.java | 4 + .../labelsys/backend/enums/IndustryType.java | 3 +- .../service/AnnotationResultService.java | 23 ++-- .../backend/service/ObjectStorageService.java | 14 ++- .../service/RustfsObjectStorageService.java | 38 +++++- .../service/SourceResourceService.java | 118 ++++++++++++++++-- .../mapper/ImageBboxAnnotationMapper.xml | 12 +- .../resources/mapper/SourceResourceMapper.xml | 7 +- src/main/resources/sql/schema.sql | 8 +- 13 files changed, 212 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/labelsys/backend/dto/response/ImageBboxResponse.java b/src/main/java/com/labelsys/backend/dto/response/ImageBboxResponse.java index 5da61d1..7edfb74 100644 --- a/src/main/java/com/labelsys/backend/dto/response/ImageBboxResponse.java +++ b/src/main/java/com/labelsys/backend/dto/response/ImageBboxResponse.java @@ -9,6 +9,7 @@ import java.util.List; public record ImageBboxResponse( @Schema(description = "bbox标识ID", example = "191000000000000101") Long id, @Schema(description = "资源ID", example = "191000000000000102") Long resourceId, + @Schema(description = "资源文件路径", example = "/data/images/car.jpg") String filepath, @Schema(description = "BBOX坐标列表") List bboxes, @Schema(description = "备注", example = "车辆检测标注") String remark, @Schema(description = "创建人名称", example = "张审核") String creatorName, diff --git a/src/main/java/com/labelsys/backend/entity/AnnotationResult.java b/src/main/java/com/labelsys/backend/entity/AnnotationResult.java index 74e43b8..c25c8a4 100644 --- a/src/main/java/com/labelsys/backend/entity/AnnotationResult.java +++ b/src/main/java/com/labelsys/backend/entity/AnnotationResult.java @@ -49,6 +49,7 @@ public class AnnotationResult { @TableField("requires_manual_review") private Boolean requiresManualReview; + @TableLogic(value = "false", delval = "true") @TableField("is_deleted") private Boolean isDeleted; diff --git a/src/main/java/com/labelsys/backend/entity/AnnotationTask.java b/src/main/java/com/labelsys/backend/entity/AnnotationTask.java index 38026ec..cb343f7 100644 --- a/src/main/java/com/labelsys/backend/entity/AnnotationTask.java +++ b/src/main/java/com/labelsys/backend/entity/AnnotationTask.java @@ -2,6 +2,7 @@ package com.labelsys.backend.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import com.labelsys.backend.enums.IndustryType; import com.labelsys.backend.enums.TaskType; @@ -28,6 +29,7 @@ public class AnnotationTask { private IndustryType industryType; private TaskType taskType; private String taskStatus; + @TableLogic(value = "false", delval = "true") private Boolean isDeleted; private LocalDateTime startedAt; private LocalDateTime finishedAt; diff --git a/src/main/java/com/labelsys/backend/entity/ImageBboxAnnotation.java b/src/main/java/com/labelsys/backend/entity/ImageBboxAnnotation.java index 9dc0d05..b5bd6b6 100644 --- a/src/main/java/com/labelsys/backend/entity/ImageBboxAnnotation.java +++ b/src/main/java/com/labelsys/backend/entity/ImageBboxAnnotation.java @@ -2,6 +2,7 @@ package com.labelsys.backend.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import com.labelsys.backend.enums.UserRole; import java.time.LocalDateTime; @@ -65,4 +66,15 @@ public class ImageBboxAnnotation { * 更新时间 */ private LocalDateTime updatedAt; + + /** + * 软删除标记 + */ + @TableLogic(value = "false", delval = "true") + private Boolean deleted; + + /** + * 软删除时间 + */ + private LocalDateTime deletedAt; } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/entity/SourceResource.java b/src/main/java/com/labelsys/backend/entity/SourceResource.java index 3ff3b9e..774b449 100644 --- a/src/main/java/com/labelsys/backend/entity/SourceResource.java +++ b/src/main/java/com/labelsys/backend/entity/SourceResource.java @@ -2,6 +2,7 @@ package com.labelsys.backend.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import com.labelsys.backend.enums.UserRole; import java.time.LocalDateTime; @@ -31,4 +32,7 @@ public class SourceResource { private String remark; private LocalDateTime createdAt; private LocalDateTime updatedAt; + @TableLogic(value = "false", delval = "true") + private Boolean deleted; + private LocalDateTime deletedAt; } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/enums/IndustryType.java b/src/main/java/com/labelsys/backend/enums/IndustryType.java index 8f047c1..cffe909 100644 --- a/src/main/java/com/labelsys/backend/enums/IndustryType.java +++ b/src/main/java/com/labelsys/backend/enums/IndustryType.java @@ -2,8 +2,9 @@ package com.labelsys.backend.enums; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "行业类型,枚举值:TRANSPORT:交通、ELECTRICITY:电力、FINANCE:金融、MEDICAL:医疗、EDUCATION:教育") +@Schema(description = "行业类型,枚举值:TRANSPORT:交通、ELECTRICITY:电力、FINANCE:金融、MEDICAL:医疗、EDUCATION:教育、GENERAL:通用") public enum IndustryType { + GENERAL, TRANSPORT, ELECTRICITY, FINANCE, diff --git a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java index 5408086..e8a6eb5 100644 --- a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java +++ b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java @@ -26,6 +26,7 @@ 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; @@ -109,7 +110,7 @@ public class AnnotationResultService { } private AnnotationResultDetailResponse toDetailResponse(AnnotationResult result, - QaContent qaContent, DiffContent diffContent) { + QaContent qaContent, DiffContent diffContent) { // 转换 QA 内容(仅保留 records) AnnotationResultDetailResponse.QaContentDto qaContentDto = new AnnotationResultDetailResponse.QaContentDto( @@ -299,6 +300,10 @@ public class AnnotationResultService { private QaContent loadQaContent(AnnotationResult result) { try { String filePath = result.getQaContentFilePath(); + if (!StringUtils.hasText(filePath)) { + log.warn("Qa content file path is empty, resultId={}", result.getId()); + return new QaContent(null, null, List.of(), null); + } String bucketName = extractBucketName(filePath); String objectKey = extractObjectKey(filePath); byte[] content = objectStorageService.download(bucketName, objectKey); @@ -306,15 +311,19 @@ public class AnnotationResultService { return objectMapper.readValue(jsonContent, new TypeReference() { }); } catch (Exception e) { - log.error("Failed to load qa content, resultId={}, filePath={}", result.getId(), - result.getQaContentFilePath(), e); - throw new BusinessException(ResultCode.ERROR, "加载问答内容失败"); + log.warn("Failed to load qa content, returning empty content. resultId={}, filePath={}, error={}", + result.getId(), result.getQaContentFilePath(), e.getMessage()); + return new QaContent(null, null, List.of(), null); } } private DiffContent loadDiffSummary(AnnotationResult result) { try { String filePath = result.getDiffSummaryFilePath(); + if (!StringUtils.hasText(filePath)) { + log.warn("Diff summary file path is empty, resultId={}", result.getId()); + return new DiffContent(null, null, List.of(), null); + } String bucketName = extractBucketName(filePath); String objectKey = extractObjectKey(filePath); byte[] content = objectStorageService.download(bucketName, objectKey); @@ -322,9 +331,9 @@ public class AnnotationResultService { return objectMapper.readValue(jsonContent, new TypeReference() { }); } catch (Exception e) { - log.error("Failed to load diff summary, resultId={}, filePath={}", result.getId(), - result.getDiffSummaryFilePath(), e); - throw new BusinessException(ResultCode.ERROR, "加载差异摘要失败"); + log.warn("Failed to load diff summary, returning empty content. resultId={}, filePath={}, error={}", + result.getId(), result.getDiffSummaryFilePath(), e.getMessage()); + return new DiffContent(null, null, List.of(), null); } } diff --git a/src/main/java/com/labelsys/backend/service/ObjectStorageService.java b/src/main/java/com/labelsys/backend/service/ObjectStorageService.java index b40556b..d2ceef5 100644 --- a/src/main/java/com/labelsys/backend/service/ObjectStorageService.java +++ b/src/main/java/com/labelsys/backend/service/ObjectStorageService.java @@ -1,5 +1,7 @@ package com.labelsys.backend.service; +import java.time.Duration; + public interface ObjectStorageService { String upload(String bucketName, String objectKey, byte[] content, String contentType); @@ -7,4 +9,14 @@ public interface ObjectStorageService { void delete(String bucketName, String objectKey); byte[] download(String bucketName, String objectKey); -} + + /** + * 生成预签名URL(用于私有bucket的临时访问) + * + * @param bucketName bucket名称 + * @param objectKey 对象键 + * @param duration URL有效期 + * @return 预签名URL + */ + String generatePresignedUrl(String bucketName, String objectKey, Duration duration); +} \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java b/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java index 5b11725..30f5675 100644 --- a/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java +++ b/src/main/java/com/labelsys/backend/service/RustfsObjectStorageService.java @@ -2,20 +2,30 @@ package com.labelsys.backend.service; import com.labelsys.backend.common.ResultCode; import com.labelsys.backend.common.exception.BusinessException; +import com.labelsys.backend.config.ObjectStorageProperties; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.regions.Region; 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; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; +import java.net.URI; +import java.time.Duration; +import java.time.Instant; @Service @RequiredArgsConstructor public class RustfsObjectStorageService implements ObjectStorageService { private final S3Client s3Client; + private final ObjectStorageProperties objectStorageProperties; @Override public String upload(String bucketName, String objectKey, byte[] content, String contentType) { @@ -57,4 +67,30 @@ public class RustfsObjectStorageService implements ObjectStorageService { throw new BusinessException(ResultCode.ERROR, "对象存储下载失败"); } } -} + + @Override + public String generatePresignedUrl(String bucketName, String objectKey, Duration duration) { + try (S3Presigner presigner = S3Presigner.builder() + .endpointOverride(URI.create(objectStorageProperties.getEndpoint())) + .region(Region.of(objectStorageProperties.getRegion())) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create( + objectStorageProperties.getAccessKey(), + objectStorageProperties.getSecretKey()))) + .build()) { + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucketName) + .key(objectKey) + .build(); + + GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() + .signatureDuration(duration) + .getObjectRequest(getObjectRequest) + .build(); + + return presigner.presignGetObject(presignRequest).url().toString(); + } catch (Exception ex) { + throw new BusinessException(ResultCode.ERROR, "生成预签名URL失败"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/SourceResourceService.java b/src/main/java/com/labelsys/backend/service/SourceResourceService.java index fe24e0a..7b0c726 100644 --- a/src/main/java/com/labelsys/backend/service/SourceResourceService.java +++ b/src/main/java/com/labelsys/backend/service/SourceResourceService.java @@ -16,11 +16,17 @@ import com.labelsys.backend.dto.request.SourceUploadRequest; import com.labelsys.backend.dto.response.ImageBboxResponse; import com.labelsys.backend.dto.response.SourceResourceResponse; import com.labelsys.backend.dto.response.SourceUploadResponse; +import com.labelsys.backend.entity.AnnotationResult; +import com.labelsys.backend.entity.AnnotationResultHistory; +import com.labelsys.backend.entity.AnnotationTask; +import com.labelsys.backend.entity.AnnotationTaskResource; 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.mapper.AnnotationResultHistoryMapper; +import com.labelsys.backend.mapper.AnnotationResultMapper; +import com.labelsys.backend.mapper.AnnotationTaskMapper; import com.labelsys.backend.mapper.AnnotationTaskResourceMapper; import com.labelsys.backend.mapper.ImageBboxAnnotationMapper; import com.labelsys.backend.mapper.SourceResourceMapper; @@ -35,6 +41,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.time.Duration; import java.time.LocalDateTime; import java.util.List; @@ -43,14 +50,17 @@ import java.util.List; @RequiredArgsConstructor public class SourceResourceService { - private final SourceResourceMapper sourceResourceMapper; - private final AnnotationTaskResourceMapper annotationTaskResourceMapper; - private final SysUserMapper sysUserMapper; - private final DataPermissionService dataPermissionService; - private final ObjectStorageService objectStorageService; - private final ObjectStorageProperties objectStorageProperties; - private final ImageBboxAnnotationMapper imageBboxAnnotationMapper; - private final ObjectMapper objectMapper; + private final SourceResourceMapper sourceResourceMapper; + private final AnnotationResultMapper annotationResultMapper; + private final AnnotationResultHistoryMapper annotationResultHistoryMapper; + private final AnnotationTaskResourceMapper annotationTaskResourceMapper; + private final SysUserMapper sysUserMapper; + private final DataPermissionService dataPermissionService; + private final ObjectStorageService objectStorageService; + private final ObjectStorageProperties objectStorageProperties; + private final ImageBboxAnnotationMapper imageBboxAnnotationMapper; + private final ObjectMapper objectMapper; + private final AnnotationTaskMapper annotationTaskMapper; @Transactional public SourceUploadResponse upload(LoginUser currentUser, SourceUploadRequest request) { @@ -91,7 +101,8 @@ public class SourceResourceService { request.getResourceName() : file.getOriginalFilename()) .resourceType(request.getResourceType()).bucketName(objectStorageProperties.getSourceBucket()) - .filePath(objectKey).fileSize(file.getSize()).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); @@ -191,9 +202,22 @@ public class SourceResourceService { resource.getCreatorRole())) { throw new BusinessException(ResultCode.FORBIDDEN, "无权删除资源"); } + + // 检查外键关联记录 + checkForeignKeyAssociations(resourceId); + + // 删除关联资源 + deleteAssociatedRecords(resourceId); + + // 执行软删除 + resource.setDeleted(true); + resource.setDeletedAt(LocalDateTime.now()); + sourceResourceMapper.updateById(resource); + + // 删除对象存储中的文件 objectStorageService.delete(resource.getBucketName(), resource.getFilePath()); - sourceResourceMapper.deleteById(resourceId); - log.info("deleted source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(), + + log.info("soft deleted source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(), currentUser.userId(), resourceId); } catch (BusinessException e) { throw e; @@ -204,6 +228,24 @@ public class SourceResourceService { } } + /** + * 删除资源的关联记录 + * + * @param resourceId 资源ID + */ + private void deleteAssociatedRecords(Long resourceId) { + // 删除任务资源关联记录 + annotationTaskResourceMapper.delete(new LambdaQueryWrapper() + .eq(AnnotationTaskResource::getResourceId, resourceId)); + + // 删除标注结果记录(包括已删除的) + annotationResultMapper.delete(new LambdaQueryWrapper() + .eq(AnnotationResult::getResourceId, resourceId)); + + // 删除BBOX标注记录 + imageBboxAnnotationMapper.deleteByResourceId(resourceId); + } + /** * 下载资源(支持 TEXT、IMAGE、VIDEO) * @@ -240,8 +282,14 @@ public class SourceResourceService { } ImageBboxAnnotation annotation = imageBboxAnnotationMapper.selectByResourceId(resourceId); + // 生成预签名URL(有效期1小时),用于私有bucket的临时访问 + String imageUrl = objectStorageService.generatePresignedUrl( + resource.getBucketName(), + resource.getFilePath(), + Duration.ofHours(1)); if (annotation == null) { - return new ImageBboxResponse(null, resourceId, List.of(), null, null, null, null); + return new ImageBboxResponse(null, resourceId, imageUrl, List.of(), null, null, null, + null); } List bboxes = parseBboxJson(annotation.getBboxJson()); @@ -250,6 +298,7 @@ public class SourceResourceService { return new ImageBboxResponse( annotation.getId(), annotation.getResourceId(), + imageUrl, bboxes, annotation.getRemark(), creator == null ? null : creator.getRealName(), @@ -432,4 +481,47 @@ public class SourceResourceService { default -> "application/octet-stream"; }; } + + /** + * 检查资源是否有外键关联记录 + * 如果存在关联记录,抛出业务异常 + * + * @param resourceId 资源ID + */ + private void checkForeignKeyAssociations(Long resourceId) { + // 1. 检查资源关联的任务是否被软删除(未软删除的任务关联则不能删除) + List taskIds = annotationTaskResourceMapper.selectList( + new LambdaQueryWrapper() + .eq(AnnotationTaskResource::getResourceId, resourceId)) + .stream() + .map(AnnotationTaskResource::getTaskId) + .toList(); + + if (!taskIds.isEmpty()) { + Long activeTaskCount = annotationTaskMapper.selectCount( + new LambdaQueryWrapper() + .in(AnnotationTask::getId, taskIds) + .notIn(AnnotationTask::getTaskStatus, "COMPLETED", "FAILED")); + if (activeTaskCount > 0) { + throw new BusinessException(ResultCode.FORBIDDEN, "资源已被标注任务引用,无法删除"); + } + } + + // 2. 检查资源是否存在标注历史记录(只要存在就不能删除) + Long historyCount = annotationResultHistoryMapper.selectCount( + new LambdaQueryWrapper() + .eq(AnnotationResultHistory::getResourceId, resourceId)); + if (historyCount > 0) { + throw new BusinessException(ResultCode.FORBIDDEN, "资源存在标注历史记录,无法删除"); + } + + // 3. 检查资源是否有未被软删除的标注结果 + Long activeResultCount = annotationResultMapper.selectCount( + new LambdaQueryWrapper() + .eq(AnnotationResult::getResourceId, resourceId) + .eq(AnnotationResult::getIsDeleted, false)); + if (activeResultCount > 0) { + throw new BusinessException(ResultCode.FORBIDDEN, "资源已有标注结果,无法删除"); + } + } } \ No newline at end of file diff --git a/src/main/resources/mapper/ImageBboxAnnotationMapper.xml b/src/main/resources/mapper/ImageBboxAnnotationMapper.xml index 0ebf565..5841177 100644 --- a/src/main/resources/mapper/ImageBboxAnnotationMapper.xml +++ b/src/main/resources/mapper/ImageBboxAnnotationMapper.xml @@ -11,14 +11,16 @@ + + - - DELETE FROM image_bbox_annotation WHERE resource_id = #{resourceId} - + + UPDATE image_bbox_annotation SET deleted = true, deleted_at = CURRENT_TIMESTAMP WHERE resource_id = #{resourceId} + \ No newline at end of file diff --git a/src/main/resources/mapper/SourceResourceMapper.xml b/src/main/resources/mapper/SourceResourceMapper.xml index ceba1ac..77805a8 100644 --- a/src/main/resources/mapper/SourceResourceMapper.xml +++ b/src/main/resources/mapper/SourceResourceMapper.xml @@ -15,19 +15,22 @@ + + id, company_id, creator_id, creator_role, resource_name, resource_type, bucket_name, file_path, file_size, storage_provider, remark, - created_at, updated_at + created_at, updated_at, deleted, deleted_at + AND resource_name = #{resourceName} AND deleted = false LIMIT 1 \ No newline at end of file diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index 03fa631..e99dafa 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -138,6 +138,8 @@ CREATE TABLE IF NOT EXISTS source_resource remark VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN DEFAULT FALSE, + deleted_at TIMESTAMP, CONSTRAINT fk_source_resource_company FOREIGN KEY (company_id) REFERENCES sys_company (id), CONSTRAINT fk_source_resource_creator FOREIGN KEY (creator_id) REFERENCES sys_user (id) ); @@ -169,6 +171,8 @@ CREATE TABLE IF NOT EXISTS image_bbox_annotation creator_role VARCHAR(32) NOT NULL DEFAULT 'EMPLOYEE', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN DEFAULT FALSE, + deleted_at TIMESTAMP, CONSTRAINT fk_image_bbox_annotation_company FOREIGN KEY (company_id) REFERENCES sys_company (id), CONSTRAINT fk_image_bbox_annotation_resource FOREIGN KEY (resource_id) REFERENCES source_resource (id), CONSTRAINT fk_image_bbox_annotation_creator FOREIGN KEY (creator_id) REFERENCES sys_user (id) @@ -184,6 +188,8 @@ COMMENT ON COLUMN image_bbox_annotation.creator_id IS '创建人用户ID。'; COMMENT ON COLUMN image_bbox_annotation.creator_role IS '创建人数据权限角色,默认 EMPLOYEE。'; COMMENT ON COLUMN image_bbox_annotation.created_at IS '创建时间。'; COMMENT ON COLUMN image_bbox_annotation.updated_at IS '更新时间。'; +COMMENT ON COLUMN image_bbox_annotation.deleted IS '软删除标记,默认 FALSE。'; +COMMENT ON COLUMN image_bbox_annotation.deleted_at IS '软删除时间。'; -- 修改 annotation_task 表,删除模型和提示词相关字段 CREATE TABLE IF NOT EXISTS annotation_task @@ -193,7 +199,7 @@ CREATE TABLE IF NOT EXISTS annotation_task creator_id BIGINT NOT NULL, creator_role VARCHAR(32) NOT NULL DEFAULT 'EMPLOYEE', task_name VARCHAR(255) NOT NULL, - industry_type VARCHAR(32) NOT NULL DEFAULT 'transport', + industry_type VARCHAR(32) NOT NULL DEFAULT 'GENERAL', task_type VARCHAR(32) NOT NULL DEFAULT 'EXTRACT_QA', task_status VARCHAR(32) NOT NULL DEFAULT 'PENDING', is_deleted BOOLEAN NOT NULL DEFAULT FALSE,