资源软删除判断,bbxo查询返回临时签名链接
This commit is contained in:
@@ -9,6 +9,7 @@ import java.util.List;
|
|||||||
public record ImageBboxResponse(
|
public record ImageBboxResponse(
|
||||||
@Schema(description = "bbox标识ID", example = "191000000000000101") Long id,
|
@Schema(description = "bbox标识ID", example = "191000000000000101") Long id,
|
||||||
@Schema(description = "资源ID", example = "191000000000000102") Long resourceId,
|
@Schema(description = "资源ID", example = "191000000000000102") Long resourceId,
|
||||||
|
@Schema(description = "资源文件路径", example = "/data/images/car.jpg") String filepath,
|
||||||
@Schema(description = "BBOX坐标列表") List<BboxCoordinateResponse> bboxes,
|
@Schema(description = "BBOX坐标列表") List<BboxCoordinateResponse> bboxes,
|
||||||
@Schema(description = "备注", example = "车辆检测标注") String remark,
|
@Schema(description = "备注", example = "车辆检测标注") String remark,
|
||||||
@Schema(description = "创建人名称", example = "张审核") String creatorName,
|
@Schema(description = "创建人名称", example = "张审核") String creatorName,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public class AnnotationResult {
|
|||||||
@TableField("requires_manual_review")
|
@TableField("requires_manual_review")
|
||||||
private Boolean requiresManualReview;
|
private Boolean requiresManualReview;
|
||||||
|
|
||||||
|
@TableLogic(value = "false", delval = "true")
|
||||||
@TableField("is_deleted")
|
@TableField("is_deleted")
|
||||||
private Boolean isDeleted;
|
private Boolean isDeleted;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.labelsys.backend.entity;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.labelsys.backend.enums.IndustryType;
|
import com.labelsys.backend.enums.IndustryType;
|
||||||
import com.labelsys.backend.enums.TaskType;
|
import com.labelsys.backend.enums.TaskType;
|
||||||
@@ -28,6 +29,7 @@ public class AnnotationTask {
|
|||||||
private IndustryType industryType;
|
private IndustryType industryType;
|
||||||
private TaskType taskType;
|
private TaskType taskType;
|
||||||
private String taskStatus;
|
private String taskStatus;
|
||||||
|
@TableLogic(value = "false", delval = "true")
|
||||||
private Boolean isDeleted;
|
private Boolean isDeleted;
|
||||||
private LocalDateTime startedAt;
|
private LocalDateTime startedAt;
|
||||||
private LocalDateTime finishedAt;
|
private LocalDateTime finishedAt;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.labelsys.backend.entity;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.labelsys.backend.enums.UserRole;
|
import com.labelsys.backend.enums.UserRole;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -65,4 +66,15 @@ public class ImageBboxAnnotation {
|
|||||||
* 更新时间
|
* 更新时间
|
||||||
*/
|
*/
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软删除标记
|
||||||
|
*/
|
||||||
|
@TableLogic(value = "false", delval = "true")
|
||||||
|
private Boolean deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软删除时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime deletedAt;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.labelsys.backend.entity;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.labelsys.backend.enums.UserRole;
|
import com.labelsys.backend.enums.UserRole;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -31,4 +32,7 @@ public class SourceResource {
|
|||||||
private String remark;
|
private String remark;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
@TableLogic(value = "false", delval = "true")
|
||||||
|
private Boolean deleted;
|
||||||
|
private LocalDateTime deletedAt;
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,9 @@ package com.labelsys.backend.enums;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
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 {
|
public enum IndustryType {
|
||||||
|
GENERAL,
|
||||||
TRANSPORT,
|
TRANSPORT,
|
||||||
ELECTRICITY,
|
ELECTRICITY,
|
||||||
FINANCE,
|
FINANCE,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -109,7 +110,7 @@ public class AnnotationResultService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AnnotationResultDetailResponse toDetailResponse(AnnotationResult result,
|
private AnnotationResultDetailResponse toDetailResponse(AnnotationResult result,
|
||||||
QaContent qaContent, DiffContent diffContent) {
|
QaContent qaContent, DiffContent diffContent) {
|
||||||
|
|
||||||
// 转换 QA 内容(仅保留 records)
|
// 转换 QA 内容(仅保留 records)
|
||||||
AnnotationResultDetailResponse.QaContentDto qaContentDto = new AnnotationResultDetailResponse.QaContentDto(
|
AnnotationResultDetailResponse.QaContentDto qaContentDto = new AnnotationResultDetailResponse.QaContentDto(
|
||||||
@@ -299,6 +300,10 @@ public class AnnotationResultService {
|
|||||||
private QaContent loadQaContent(AnnotationResult result) {
|
private QaContent loadQaContent(AnnotationResult result) {
|
||||||
try {
|
try {
|
||||||
String filePath = result.getQaContentFilePath();
|
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 bucketName = extractBucketName(filePath);
|
||||||
String objectKey = extractObjectKey(filePath);
|
String objectKey = extractObjectKey(filePath);
|
||||||
byte[] content = objectStorageService.download(bucketName, objectKey);
|
byte[] content = objectStorageService.download(bucketName, objectKey);
|
||||||
@@ -306,15 +311,19 @@ public class AnnotationResultService {
|
|||||||
return objectMapper.readValue(jsonContent, new TypeReference<QaContent>() {
|
return objectMapper.readValue(jsonContent, new TypeReference<QaContent>() {
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to load qa content, resultId={}, filePath={}", result.getId(),
|
log.warn("Failed to load qa content, returning empty content. resultId={}, filePath={}, error={}",
|
||||||
result.getQaContentFilePath(), e);
|
result.getId(), result.getQaContentFilePath(), e.getMessage());
|
||||||
throw new BusinessException(ResultCode.ERROR, "加载问答内容失败");
|
return new QaContent(null, null, List.of(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DiffContent loadDiffSummary(AnnotationResult result) {
|
private DiffContent loadDiffSummary(AnnotationResult result) {
|
||||||
try {
|
try {
|
||||||
String filePath = result.getDiffSummaryFilePath();
|
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 bucketName = extractBucketName(filePath);
|
||||||
String objectKey = extractObjectKey(filePath);
|
String objectKey = extractObjectKey(filePath);
|
||||||
byte[] content = objectStorageService.download(bucketName, objectKey);
|
byte[] content = objectStorageService.download(bucketName, objectKey);
|
||||||
@@ -322,9 +331,9 @@ public class AnnotationResultService {
|
|||||||
return objectMapper.readValue(jsonContent, new TypeReference<DiffContent>() {
|
return objectMapper.readValue(jsonContent, new TypeReference<DiffContent>() {
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to load diff summary, resultId={}, filePath={}", result.getId(),
|
log.warn("Failed to load diff summary, returning empty content. resultId={}, filePath={}, error={}",
|
||||||
result.getDiffSummaryFilePath(), e);
|
result.getId(), result.getDiffSummaryFilePath(), e.getMessage());
|
||||||
throw new BusinessException(ResultCode.ERROR, "加载差异摘要失败");
|
return new DiffContent(null, null, List.of(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.labelsys.backend.service;
|
package com.labelsys.backend.service;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
public interface ObjectStorageService {
|
public interface ObjectStorageService {
|
||||||
|
|
||||||
String upload(String bucketName, String objectKey, byte[] content, String contentType);
|
String upload(String bucketName, String objectKey, byte[] content, String contentType);
|
||||||
@@ -7,4 +9,14 @@ public interface ObjectStorageService {
|
|||||||
void delete(String bucketName, String objectKey);
|
void delete(String bucketName, String objectKey);
|
||||||
|
|
||||||
byte[] download(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);
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,30 @@ package com.labelsys.backend.service;
|
|||||||
|
|
||||||
import com.labelsys.backend.common.ResultCode;
|
import com.labelsys.backend.common.ResultCode;
|
||||||
import com.labelsys.backend.common.exception.BusinessException;
|
import com.labelsys.backend.common.exception.BusinessException;
|
||||||
|
import com.labelsys.backend.config.ObjectStorageProperties;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
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.RequestBody;
|
||||||
import software.amazon.awssdk.core.sync.ResponseTransformer;
|
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.S3Client;
|
||||||
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
|
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
|
||||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
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
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RustfsObjectStorageService implements ObjectStorageService {
|
public class RustfsObjectStorageService implements ObjectStorageService {
|
||||||
|
|
||||||
private final S3Client s3Client;
|
private final S3Client s3Client;
|
||||||
|
private final ObjectStorageProperties objectStorageProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String upload(String bucketName, String objectKey, byte[] content, String contentType) {
|
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, "对象存储下载失败");
|
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失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,11 +16,17 @@ import com.labelsys.backend.dto.request.SourceUploadRequest;
|
|||||||
import com.labelsys.backend.dto.response.ImageBboxResponse;
|
import com.labelsys.backend.dto.response.ImageBboxResponse;
|
||||||
import com.labelsys.backend.dto.response.SourceResourceResponse;
|
import com.labelsys.backend.dto.response.SourceResourceResponse;
|
||||||
import com.labelsys.backend.dto.response.SourceUploadResponse;
|
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.ImageBboxAnnotation;
|
||||||
import com.labelsys.backend.entity.SourceResource;
|
import com.labelsys.backend.entity.SourceResource;
|
||||||
import com.labelsys.backend.entity.SysUser;
|
import com.labelsys.backend.entity.SysUser;
|
||||||
import com.labelsys.backend.enums.ResourceType;
|
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.AnnotationTaskResourceMapper;
|
||||||
import com.labelsys.backend.mapper.ImageBboxAnnotationMapper;
|
import com.labelsys.backend.mapper.ImageBboxAnnotationMapper;
|
||||||
import com.labelsys.backend.mapper.SourceResourceMapper;
|
import com.labelsys.backend.mapper.SourceResourceMapper;
|
||||||
@@ -35,6 +41,7 @@ import org.springframework.util.StringUtils;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -43,14 +50,17 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SourceResourceService {
|
public class SourceResourceService {
|
||||||
|
|
||||||
private final SourceResourceMapper sourceResourceMapper;
|
private final SourceResourceMapper sourceResourceMapper;
|
||||||
private final AnnotationTaskResourceMapper annotationTaskResourceMapper;
|
private final AnnotationResultMapper annotationResultMapper;
|
||||||
private final SysUserMapper sysUserMapper;
|
private final AnnotationResultHistoryMapper annotationResultHistoryMapper;
|
||||||
private final DataPermissionService dataPermissionService;
|
private final AnnotationTaskResourceMapper annotationTaskResourceMapper;
|
||||||
private final ObjectStorageService objectStorageService;
|
private final SysUserMapper sysUserMapper;
|
||||||
private final ObjectStorageProperties objectStorageProperties;
|
private final DataPermissionService dataPermissionService;
|
||||||
private final ImageBboxAnnotationMapper imageBboxAnnotationMapper;
|
private final ObjectStorageService objectStorageService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectStorageProperties objectStorageProperties;
|
||||||
|
private final ImageBboxAnnotationMapper imageBboxAnnotationMapper;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final AnnotationTaskMapper annotationTaskMapper;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public SourceUploadResponse upload(LoginUser currentUser, SourceUploadRequest request) {
|
public SourceUploadResponse upload(LoginUser currentUser, SourceUploadRequest request) {
|
||||||
@@ -91,7 +101,8 @@ public class SourceResourceService {
|
|||||||
request.getResourceName() :
|
request.getResourceName() :
|
||||||
file.getOriginalFilename())
|
file.getOriginalFilename())
|
||||||
.resourceType(request.getResourceType()).bucketName(objectStorageProperties.getSourceBucket())
|
.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);
|
sourceResourceMapper.insert(resource);
|
||||||
log.info("uploaded source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(),
|
log.info("uploaded source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(),
|
||||||
currentUser.userId(), resourceId);
|
currentUser.userId(), resourceId);
|
||||||
@@ -191,9 +202,22 @@ public class SourceResourceService {
|
|||||||
resource.getCreatorRole())) {
|
resource.getCreatorRole())) {
|
||||||
throw new BusinessException(ResultCode.FORBIDDEN, "无权删除资源");
|
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());
|
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);
|
currentUser.userId(), resourceId);
|
||||||
} catch (BusinessException e) {
|
} catch (BusinessException e) {
|
||||||
throw e;
|
throw e;
|
||||||
@@ -204,6 +228,24 @@ public class SourceResourceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除资源的关联记录
|
||||||
|
*
|
||||||
|
* @param resourceId 资源ID
|
||||||
|
*/
|
||||||
|
private void deleteAssociatedRecords(Long resourceId) {
|
||||||
|
// 删除任务资源关联记录
|
||||||
|
annotationTaskResourceMapper.delete(new LambdaQueryWrapper<AnnotationTaskResource>()
|
||||||
|
.eq(AnnotationTaskResource::getResourceId, resourceId));
|
||||||
|
|
||||||
|
// 删除标注结果记录(包括已删除的)
|
||||||
|
annotationResultMapper.delete(new LambdaQueryWrapper<AnnotationResult>()
|
||||||
|
.eq(AnnotationResult::getResourceId, resourceId));
|
||||||
|
|
||||||
|
// 删除BBOX标注记录
|
||||||
|
imageBboxAnnotationMapper.deleteByResourceId(resourceId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载资源(支持 TEXT、IMAGE、VIDEO)
|
* 下载资源(支持 TEXT、IMAGE、VIDEO)
|
||||||
*
|
*
|
||||||
@@ -240,8 +282,14 @@ public class SourceResourceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageBboxAnnotation annotation = imageBboxAnnotationMapper.selectByResourceId(resourceId);
|
ImageBboxAnnotation annotation = imageBboxAnnotationMapper.selectByResourceId(resourceId);
|
||||||
|
// 生成预签名URL(有效期1小时),用于私有bucket的临时访问
|
||||||
|
String imageUrl = objectStorageService.generatePresignedUrl(
|
||||||
|
resource.getBucketName(),
|
||||||
|
resource.getFilePath(),
|
||||||
|
Duration.ofHours(1));
|
||||||
if (annotation == null) {
|
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<ImageBboxResponse.BboxCoordinateResponse> bboxes = parseBboxJson(annotation.getBboxJson());
|
List<ImageBboxResponse.BboxCoordinateResponse> bboxes = parseBboxJson(annotation.getBboxJson());
|
||||||
@@ -250,6 +298,7 @@ public class SourceResourceService {
|
|||||||
return new ImageBboxResponse(
|
return new ImageBboxResponse(
|
||||||
annotation.getId(),
|
annotation.getId(),
|
||||||
annotation.getResourceId(),
|
annotation.getResourceId(),
|
||||||
|
imageUrl,
|
||||||
bboxes,
|
bboxes,
|
||||||
annotation.getRemark(),
|
annotation.getRemark(),
|
||||||
creator == null ? null : creator.getRealName(),
|
creator == null ? null : creator.getRealName(),
|
||||||
@@ -432,4 +481,47 @@ public class SourceResourceService {
|
|||||||
default -> "application/octet-stream";
|
default -> "application/octet-stream";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查资源是否有外键关联记录
|
||||||
|
* 如果存在关联记录,抛出业务异常
|
||||||
|
*
|
||||||
|
* @param resourceId 资源ID
|
||||||
|
*/
|
||||||
|
private void checkForeignKeyAssociations(Long resourceId) {
|
||||||
|
// 1. 检查资源关联的任务是否被软删除(未软删除的任务关联则不能删除)
|
||||||
|
List<Long> taskIds = annotationTaskResourceMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<AnnotationTaskResource>()
|
||||||
|
.eq(AnnotationTaskResource::getResourceId, resourceId))
|
||||||
|
.stream()
|
||||||
|
.map(AnnotationTaskResource::getTaskId)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (!taskIds.isEmpty()) {
|
||||||
|
Long activeTaskCount = annotationTaskMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<AnnotationTask>()
|
||||||
|
.in(AnnotationTask::getId, taskIds)
|
||||||
|
.notIn(AnnotationTask::getTaskStatus, "COMPLETED", "FAILED"));
|
||||||
|
if (activeTaskCount > 0) {
|
||||||
|
throw new BusinessException(ResultCode.FORBIDDEN, "资源已被标注任务引用,无法删除");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查资源是否存在标注历史记录(只要存在就不能删除)
|
||||||
|
Long historyCount = annotationResultHistoryMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<AnnotationResultHistory>()
|
||||||
|
.eq(AnnotationResultHistory::getResourceId, resourceId));
|
||||||
|
if (historyCount > 0) {
|
||||||
|
throw new BusinessException(ResultCode.FORBIDDEN, "资源存在标注历史记录,无法删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查资源是否有未被软删除的标注结果
|
||||||
|
Long activeResultCount = annotationResultMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<AnnotationResult>()
|
||||||
|
.eq(AnnotationResult::getResourceId, resourceId)
|
||||||
|
.eq(AnnotationResult::getIsDeleted, false));
|
||||||
|
if (activeResultCount > 0) {
|
||||||
|
throw new BusinessException(ResultCode.FORBIDDEN, "资源已有标注结果,无法删除");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,14 +11,16 @@
|
|||||||
<result column="creator_role" property="creatorRole"/>
|
<result column="creator_role" property="creatorRole"/>
|
||||||
<result column="created_at" property="createdAt"/>
|
<result column="created_at" property="createdAt"/>
|
||||||
<result column="updated_at" property="updatedAt"/>
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
<result column="deleted" property="deleted"/>
|
||||||
|
<result column="deleted_at" property="deletedAt"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectByResourceId" resultMap="ImageBboxAnnotationResultMap">
|
<select id="selectByResourceId" resultMap="ImageBboxAnnotationResultMap">
|
||||||
SELECT id, company_id, resource_id, bbox_json, remark, creator_id, creator_role, created_at, updated_at
|
SELECT id, company_id, resource_id, bbox_json, remark, creator_id, creator_role, created_at, updated_at, deleted, deleted_at
|
||||||
FROM image_bbox_annotation WHERE resource_id = #{resourceId}
|
FROM image_bbox_annotation WHERE resource_id = #{resourceId} AND deleted = false
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<delete id="deleteByResourceId">
|
<update id="deleteByResourceId">
|
||||||
DELETE FROM image_bbox_annotation WHERE resource_id = #{resourceId}
|
UPDATE image_bbox_annotation SET deleted = true, deleted_at = CURRENT_TIMESTAMP WHERE resource_id = #{resourceId}
|
||||||
</delete>
|
</update>
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -15,19 +15,22 @@
|
|||||||
<result column="remark" property="remark" />
|
<result column="remark" property="remark" />
|
||||||
<result column="created_at" property="createdAt" />
|
<result column="created_at" property="createdAt" />
|
||||||
<result column="updated_at" property="updatedAt" />
|
<result column="updated_at" property="updatedAt" />
|
||||||
|
<result column="deleted" property="deleted" />
|
||||||
|
<result column="deleted_at" property="deletedAt" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="SourceResourceColumns"> id, company_id, creator_id, creator_role, resource_name,
|
<sql id="SourceResourceColumns"> id, company_id, creator_id, creator_role, resource_name,
|
||||||
resource_type, bucket_name, file_path, file_size, storage_provider, remark,
|
resource_type, bucket_name, file_path, file_size, storage_provider, remark,
|
||||||
created_at, updated_at </sql>
|
created_at, updated_at, deleted, deleted_at </sql>
|
||||||
|
|
||||||
<select id="selectByCompanyIdAndIds" resultMap="SourceResourceResultMap"> select <include
|
<select id="selectByCompanyIdAndIds" resultMap="SourceResourceResultMap"> select <include
|
||||||
refid="SourceResourceColumns" /> from source_resource where company_id = #{companyId}
|
refid="SourceResourceColumns" /> from source_resource where company_id = #{companyId}
|
||||||
and id in <foreach collection="resourceIds" item="resourceId" open="(" separator=","
|
and id in <foreach collection="resourceIds" item="resourceId" open="(" separator=","
|
||||||
close=")"> #{resourceId} </foreach>
|
close=")"> #{resourceId} </foreach>
|
||||||
|
and deleted = false
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectByCompanyIdAndResourceName" resultMap="SourceResourceResultMap"> SELECT <include
|
<select id="selectByCompanyIdAndResourceName" resultMap="SourceResourceResultMap"> SELECT <include
|
||||||
refid="SourceResourceColumns" /> FROM source_resource WHERE company_id = #{companyId}
|
refid="SourceResourceColumns" /> FROM source_resource WHERE company_id = #{companyId}
|
||||||
AND resource_name = #{resourceName} LIMIT 1 </select>
|
AND resource_name = #{resourceName} AND deleted = false LIMIT 1 </select>
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -138,6 +138,8 @@ CREATE TABLE IF NOT EXISTS source_resource
|
|||||||
remark VARCHAR(255),
|
remark VARCHAR(255),
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_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_company FOREIGN KEY (company_id) REFERENCES sys_company (id),
|
||||||
CONSTRAINT fk_source_resource_creator FOREIGN KEY (creator_id) REFERENCES sys_user (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',
|
creator_role VARCHAR(32) NOT NULL DEFAULT 'EMPLOYEE',
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_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_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_resource FOREIGN KEY (resource_id) REFERENCES source_resource (id),
|
||||||
CONSTRAINT fk_image_bbox_annotation_creator FOREIGN KEY (creator_id) REFERENCES sys_user (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.creator_role IS '创建人数据权限角色,默认 EMPLOYEE。';
|
||||||
COMMENT ON COLUMN image_bbox_annotation.created_at IS '创建时间。';
|
COMMENT ON COLUMN image_bbox_annotation.created_at IS '创建时间。';
|
||||||
COMMENT ON COLUMN image_bbox_annotation.updated_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 表,删除模型和提示词相关字段
|
-- 修改 annotation_task 表,删除模型和提示词相关字段
|
||||||
CREATE TABLE IF NOT EXISTS 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_id BIGINT NOT NULL,
|
||||||
creator_role VARCHAR(32) NOT NULL DEFAULT 'EMPLOYEE',
|
creator_role VARCHAR(32) NOT NULL DEFAULT 'EMPLOYEE',
|
||||||
task_name VARCHAR(255) NOT NULL,
|
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_type VARCHAR(32) NOT NULL DEFAULT 'EXTRACT_QA',
|
||||||
task_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
task_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
|||||||
Reference in New Issue
Block a user