From b5246d4e58a0f63872438b4b23951c85bb0dcf99 Mon Sep 17 00:00:00 2001 From: aguanleishen Date: Tue, 28 Jul 2026 08:32:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0APP=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/springblade/auth/AuthApplication.java | 2 + blade-common/pom.xml | 12 + .../org/springblade/common/utils/ObsUtil.java | 277 ++++++++++++++ .../src/main/resources/application.yml | 3 - blade-service-api/blade-file-api/pom.xml | 22 ++ .../file/callback/UnzipCallback.java | 22 ++ .../file/callback/UnzipEventType.java | 22 ++ .../config/FeignFileUploadClientConfig.java | 20 + .../springblade/file/feign/IFileClient.java | 189 +++++++++ .../file/feign/IFileClientFallback.java | 113 ++++++ .../file/feign/IFileUploadClient.java | 49 +++ .../pojo/dto/BusinessAttachmentAddDTO.java | 53 +++ .../dto/BusinessAttachmentBatchAddDTO.java | 73 ++++ .../BusinessAttachmentBatchAddDetailDTO.java | 48 +++ .../pojo/dto/BusinessAttachmentDeleteDTO.java | 54 +++ .../pojo/dto/BusinessAttachmentQueryDTO.java | 62 +++ .../pojo/dto/BusinessUnzipTaskAddDTO.java | 55 +++ .../pojo/dto/BusinessUnzipTaskQueryDTO.java | 55 +++ .../pojo/dto/BusinessUnzipTaskUpdateDTO.java | 42 ++ .../pojo/dto/CertificateAttachmentDTO.java | 44 +++ .../FileCertificateBatchRecognitionDTO.java | 45 +++ .../file/pojo/dto/FileTaskCreateDTO.java | 62 +++ .../file/pojo/dto/FileTaskUpdateDTO.java | 41 ++ .../file/pojo/dto/FileUploadDTO.java | 15 + .../org/springblade/file/pojo/dto/IdsDTO.java | 33 ++ .../file/pojo/entity/Attachment.java | 111 ++++++ .../file/pojo/entity/BusinessAttachment.java | 113 ++++++ .../file/pojo/entity/BusinessUnzipDetail.java | 85 +++++ .../file/pojo/entity/BusinessUnzipTask.java | 89 +++++ .../file/pojo/entity/FileTask.java | 152 ++++++++ .../file/pojo/entity/FileTaskPart.java | 69 ++++ .../file/pojo/enums/FileTaskStatus.java | 39 ++ .../file/pojo/enums/UnzipTask.java | 99 +++++ .../file/pojo/vo/AttachmentDetailVO.java | 73 ++++ .../file/pojo/vo/BusinessAttachmentVO.java | 105 +++++ .../pojo/vo/BusinessUnzipDetailListVO.java | 94 +++++ .../file/pojo/vo/BusinessUnzipTaskListVO.java | 169 ++++++++ .../file/pojo/vo/FileHandleVO.java | 36 ++ .../file/pojo/vo/FileTaskUpdateVO.java | 25 ++ .../springblade/file/pojo/vo/FileTaskVO.java | 133 +++++++ .../springblade/file/pojo/vo/FileUrlVO.java | 33 ++ .../org/springblade/file/pojo/vo/FileVO.java | 35 ++ .../springblade/file/pojo/vo/PartEtagVO.java | 29 ++ .../file/pojo/vo/TempFileUrlVO.java | 33 ++ .../system/pojo/entity/AppVersion.java | 81 ++++ blade-service-api/pom.xml | 1 + blade-service/blade-file/Dockerfile | 19 + blade-service/blade-file/Dockerfile-rt | 19 + blade-service/blade-file/Dockerfile-zh-dev | 19 + blade-service/blade-file/pom.xml | 139 +++++++ .../org/springblade/file/FileApplication.java | 47 +++ .../springblade/file/config/FileConfig.java | 33 ++ .../file/config/ObsProperties.java | 69 ++++ .../BusinessAttachmentController.java | 75 ++++ .../BusinessUnzipTaskController.java | 69 ++++ .../file/controller/FileController.java | 168 ++++++++ .../file/controller/FileTaskController.java | 85 +++++ .../convert/BusinessAttachmentConvert.java | 44 +++ .../file/convert/FileTaskConvert.java | 31 ++ .../file/convert/OCRConverter.java | 17 + .../file/exception/UnzipFailException.java | 28 ++ .../springblade/file/feign/FileClient.java | 157 ++++++++ .../springblade/file/listener/FileEvent.java | 56 +++ .../file/listener/FileListener.java | 173 +++++++++ .../file/listener/FileUploadedEvent.java | 21 + .../file/listener/UnzipCompletedEvent.java | 22 ++ .../file/listener/UnzipFailEvent.java | 22 ++ .../file/listener/UnzipStartEvent.java | 35 ++ .../file/mapper/AttachmentMapper.java | 37 ++ .../file/mapper/AttachmentMapper.xml | 5 + .../file/mapper/BusinessAttachmentMapper.java | 24 ++ .../file/mapper/BusinessAttachmentMapper.xml | 48 +++ .../mapper/BusinessUnzipDetailMapper.java | 16 + .../file/mapper/BusinessUnzipDetailMapper.xml | 22 ++ .../file/mapper/BusinessUnzipTaskMapper.java | 37 ++ .../file/mapper/BusinessUnzipTaskMapper.xml | 95 +++++ .../file/mapper/FileTaskMapper.java | 38 ++ .../file/mapper/FileTaskMapper.xml | 5 + .../file/mapper/FileTaskPartMapper.java | 38 ++ .../file/mapper/FileTaskPartMapper.xml | 5 + .../file/service/IAttachmentService.java | 89 +++++ .../service/IBusinessAttachmentService.java | 51 +++ .../service/IBusinessUnzipDetailService.java | 23 ++ .../service/IBusinessUnzipTaskService.java | 82 ++++ .../file/service/IFileHandleService.java | 20 + .../file/service/IFileService.java | 122 ++++++ .../file/service/IFileTaskPartService.java | 38 ++ .../file/service/IFileTaskService.java | 94 +++++ .../file/service/IOCRConvertService.java | 19 + .../service/impl/AttachmentServiceImpl.java | 255 +++++++++++++ .../impl/BusinessAttachmentServiceImpl.java | 136 +++++++ .../impl/BusinessUnzipDetailServiceImpl.java | 71 ++++ .../impl/BusinessUnzipTaskServiceImpl.java | 229 +++++++++++ .../service/impl/FileHandleServiceImpl.java | 92 +++++ .../service/impl/FileTaskPartServiceImpl.java | 46 +++ .../service/impl/FileTaskServiceImpl.java | 360 ++++++++++++++++++ .../service/impl/OCRConvertServiceImpl.java | 40 ++ .../file/service/impl/ObsFileServiceImpl.java | 293 ++++++++++++++ .../support/DownloadProgressListener.java | 31 ++ .../support/UploadProgressListener.java | 31 ++ .../springblade/file/util/ObjectKeyUtils.java | 98 +++++ .../org/springblade/file/util/UnzipUtil.java | 245 ++++++++++++ .../src/main/resources/bootstrap-dev.yml | 6 + .../src/main/resources/bootstrap-prod.yml | 6 + .../src/main/resources/bootstrap-test.yml | 8 + .../src/main/resources/bootstrap.yml | 41 ++ blade-service/blade-system/pom.xml | 37 ++ .../controller/AppVersionController.java | 115 ++++++ .../system/mapper/AppVersionMapper.java | 36 ++ .../system/service/AppVersionService.java | 54 +++ blade-service/pom.xml | 1 + pom.xml | 25 +- 112 files changed, 7504 insertions(+), 25 deletions(-) create mode 100644 blade-common/src/main/java/org/springblade/common/utils/ObsUtil.java create mode 100644 blade-service-api/blade-file-api/pom.xml create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipCallback.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipEventType.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/config/FeignFileUploadClientConfig.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClient.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClientFallback.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileUploadClient.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentAddDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDetailDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentDeleteDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentQueryDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskAddDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskQueryDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskUpdateDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/CertificateAttachmentDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskUpdateDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileUploadDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/IdsDTO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/Attachment.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessAttachment.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipDetail.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipTask.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTaskPart.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/FileTaskStatus.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/UnzipTask.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/AttachmentDetailVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessAttachmentVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipDetailListVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipTaskListVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileHandleVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskUpdateVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileUrlVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/PartEtagVO.java create mode 100644 blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/TempFileUrlVO.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/AppVersion.java create mode 100644 blade-service/blade-file/Dockerfile create mode 100644 blade-service/blade-file/Dockerfile-rt create mode 100644 blade-service/blade-file/Dockerfile-zh-dev create mode 100644 blade-service/blade-file/pom.xml create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/config/ObsProperties.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessAttachmentController.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessUnzipTaskController.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/convert/BusinessAttachmentConvert.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/convert/FileTaskConvert.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/convert/OCRConverter.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/exception/UnzipFailException.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/feign/FileClient.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/listener/FileEvent.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/listener/FileListener.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/listener/FileUploadedEvent.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipCompletedEvent.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipFailEvent.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipStartEvent.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.xml create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.xml create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.xml create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.xml create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.xml create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IAttachmentService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessAttachmentService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipDetailService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipTaskService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IFileHandleService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IFileService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskPartService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/IOCRConvertService.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessAttachmentServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipDetailServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipTaskServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileHandleServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskPartServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/impl/ObsFileServiceImpl.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/support/DownloadProgressListener.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/service/support/UploadProgressListener.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/util/ObjectKeyUtils.java create mode 100644 blade-service/blade-file/src/main/java/org/springblade/file/util/UnzipUtil.java create mode 100644 blade-service/blade-file/src/main/resources/bootstrap-dev.yml create mode 100644 blade-service/blade-file/src/main/resources/bootstrap-prod.yml create mode 100644 blade-service/blade-file/src/main/resources/bootstrap-test.yml create mode 100644 blade-service/blade-file/src/main/resources/bootstrap.yml create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/controller/AppVersionController.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/mapper/AppVersionMapper.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/AppVersionService.java diff --git a/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java b/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java index 62627f3..c674021 100644 --- a/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java +++ b/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java @@ -29,6 +29,7 @@ package org.springblade.auth; import org.springblade.core.cloud.client.BladeCloudApplication; import org.springblade.core.launch.BladeApplication; import org.springblade.core.launch.constant.AppConstant; +import org.springframework.context.annotation.ComponentScan; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /** @@ -38,6 +39,7 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableR */ @EnableRedisHttpSession @BladeCloudApplication +@ComponentScan({"org.springblade.auth", "org.springblade.**.feign"}) public class AuthApplication { public static void main(String[] args) { diff --git a/blade-common/pom.xml b/blade-common/pom.xml index 993b866..6535ccc 100644 --- a/blade-common/pom.xml +++ b/blade-common/pom.xml @@ -27,6 +27,18 @@ blade-core-auto provided + + + com.huaweicloud + esdk-obs-java-bundle + 3.24.9 + + + commons-io + commons-io + 2.11.0 + + diff --git a/blade-common/src/main/java/org/springblade/common/utils/ObsUtil.java b/blade-common/src/main/java/org/springblade/common/utils/ObsUtil.java new file mode 100644 index 0000000..f992aca --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/utils/ObsUtil.java @@ -0,0 +1,277 @@ +package org.springblade.common.utils; + +import com.alibaba.cloud.commons.lang.StringUtils; +import com.obs.services.ObsClient; +import com.obs.services.exception.ObsException; +import com.obs.services.model.*; +import jakarta.activation.MimetypesFileTypeMap; +import lombok.SneakyThrows; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.*; +import java.net.URL; +import java.util.List; + +/** + * @ClassName: OBSUtil + * @Description: + * @Author: zhaowei + * @Date: 2024/8/22 + */ +@Component +public class ObsUtil { + private final static Logger logger = LoggerFactory.getLogger(ObsUtil.class); + + private static MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); + /** + * 华为云OBS + */ + public static String obsEndpoint; + public static String obsAccessKeyId; + public static String obsAccessKeySecret; + public static String obsBucketName; + public static String obsRootDirectory; + + @Value("${obs.endpoint}") + public void setObsEndpoint(String endpoint) { + ObsUtil.obsEndpoint = endpoint; + } + + @Value("${obs.access-key-id}") + public void setObsAccessKeyId(String accessKeyId) { + ObsUtil.obsAccessKeyId = accessKeyId; + } + + @Value("${obs.access-key-secret}") + public void setObsAccessKeySecret(String accessKeySecret) { + ObsUtil.obsAccessKeySecret = accessKeySecret; + } + + @Value("${obs.bucket-name}") + public void setObsBucketName(String bucketName) { + ObsUtil.obsBucketName = bucketName; + } + + @Value("${obs.root-directory}") + public void setObsRootDirectory(String rootDirectory) { + ObsUtil.obsRootDirectory = rootDirectory; + } + + + // 如果Bucket不存在,则创建它。 + private static void ensureBucket(ObsClient client, String bucketName) throws ObsException { + + if (client.headBucket(bucketName)) { + return; + } + // 创建bucket + client.createBucket(bucketName); + } + + // 把Bucket设置为所有人可读 + private static void setBucketPublicReadable(String bucketName) throws ObsException { + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + if (obsClient.doesObjectExist(obsBucketName, obsRootDirectory)) { + + } else { + obsClient.createBucket(obsBucketName); + } + obsClient.setBucketAcl(obsBucketName, com.obs.services.model.AccessControlList.REST_CANNED_PUBLIC_READ_WRITE); + } + + /** + * 上传一个Object + * + * @param key + * @param content + * @throws FileNotFoundException + */ + public static String putObject(String key, InputStream content) throws IOException { + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + obsClient.putObject(obsBucketName, key, content); + } catch (Exception e) { + logger.error("obs put object error", e); + throw new RuntimeException("obs eror" + e.getMessage()); + } finally { + obsClient.close(); + } + return getExpireUrl(key); + } + + + /** + * 下载文件 + * @param objectKey 上传到OBS起的名 + * @param filename 文件下载到本地保存的路径 + * @throws ObsException + * @throws IOException + */ + public static void downloadFile(String objectKey, String filename) throws ObsException, IOException { + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + obsClient.getObject(obsBucketName, objectKey); + } finally { + obsClient.close(); + } + } + + /** + * 删除一个Bucket和其中的Objects + * + * @param bucketName Bucket名 + * @throws ObsException + */ + private static void deleteBucket(String bucketName) throws ObsException { + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + com.obs.services.model.ObjectListing objectListing = obsClient.listObjects(bucketName); + List listObject = objectListing.getObjects(); + for (ObsObject o : listObject) { + obsClient.deleteObject(bucketName, o.getObjectKey()); + } + obsClient.deleteBucket(bucketName); + } + + /** + * 批量删除Object + * @param objList Object的key(目录路径/文件名) + */ + public static void delelteObjectList(List objList) { + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + for (String key : objList) { + obsClient.deleteObject(obsBucketName, key); + } + } + + + /** + * 删除Object + * @param key 目录路径/文件名 + */ + public static void delelteObject(String key) { + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + obsClient.deleteObject(obsBucketName, key); + } + + /** + * 通过key获取带签名的url + */ + public static String getExpireUrl(String key) { + String fileUrl = ""; + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + // URL有效期,3600秒.5分钟 + // long expireSeconds = 3600L; + // 约等于1年 + long expireSeconds = 31536000L; + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expireSeconds); + request.setBucketName(obsBucketName); + request.setObjectKey(key); + TemporarySignatureResponse response = obsClient.createTemporarySignature(request); + fileUrl = response.getSignedUrl(); + return fileUrl; + } + + /** + * 上传文件至OSS并且返回链接地址 + * @param url 远程路径 + * @param ossDir OSS目录 + * @param fileName 文件名 + * @return 文件OSS链接 + * @modify + */ + public static String uploadFile2Oss(URL url, String ossDir, String fileName) { + InputStream is; + String key = ossDir + fileName; + try { + is = url.openStream(); + return uploadFile2OSS(is, key); + } catch (Exception ex) { + logger.error("上传失败!", ex); + } + return StringUtils.EMPTY; + } + + /** + * 上传到OSS服务器 如果同名文件会覆盖服务器上的 + * + * @param inputStream 文件流 + * @return 返回文件访问路径 + */ + @SneakyThrows + public static String uploadFile2OSS(InputStream inputStream, String key) { + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setCacheControl("no-cache"); + String fileName = FilenameUtils.getName(key); + objectMetadata.setContentType(fileTypeMap.getContentType(fileName)); + objectMetadata.addUserMetadata("filename", fileName); + + ObsClient obsClient = new ObsClient(obsAccessKeyId, + obsAccessKeySecret, obsEndpoint); + try { + obsClient.putObject(obsBucketName, obsRootDirectory + key, inputStream, objectMetadata); + return obsRootDirectory + "/" + key; + } finally { + obsClient.close(); + } + } + + @SneakyThrows + public Boolean checkObjectExist(String key) { + Boolean existsFlag = false; + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + existsFlag = obsClient.doesObjectExist(obsBucketName, key); + } finally { + obsClient.close(); + } + return existsFlag; + } + + + /** + * 下载文件 + * @param objectKey 上传到OBS起的名 + * @param filename 文件下载到本地保存的路径 + * @throws ObsException + * @throws IOException + */ + public static void downloadFileNew(String objectKey, String filename){ + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + ObsObject ossObject = obsClient.getObject(obsBucketName, objectKey); + + OutputStream outputStream = null; + try { + outputStream = new BufferedOutputStream(new FileOutputStream(filename)); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = ossObject.getObjectContent().read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } catch (IOException ex) { + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + } + } + } + } finally { + if (obsClient != null) { + try { + obsClient.close(); + } catch (IOException e) { + } + } + } + } +} diff --git a/blade-gateway/src/main/resources/application.yml b/blade-gateway/src/main/resources/application.yml index bbcb4bd..d0c3371 100644 --- a/blade-gateway/src/main/resources/application.yml +++ b/blade-gateway/src/main/resources/application.yml @@ -14,6 +14,3 @@ knife4j: - blade-admin - blade-gateway - blade-log - -server: - port: 28080 diff --git a/blade-service-api/blade-file-api/pom.xml b/blade-service-api/blade-file-api/pom.xml new file mode 100644 index 0000000..d85da74 --- /dev/null +++ b/blade-service-api/blade-file-api/pom.xml @@ -0,0 +1,22 @@ + + + + org.springblade + blade-service-api + ${revision} + + 4.0.0 + + blade-file-api + ${project.artifactId} + jar + + + + org.springblade + blade-ocr-api + + + \ No newline at end of file diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipCallback.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipCallback.java new file mode 100644 index 0000000..7135272 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipCallback.java @@ -0,0 +1,22 @@ +package org.springblade.file.callback; + +/** + * 解压回调接口 + * @author bfhuange + * @date 2024/11/8 + */ +public interface UnzipCallback { + /** + * 解压任务的业务编码 + * @return + */ + String getBizCode(); + + /** + * 回调接口 + * @param bizId 业务id + * @param event 事件 org.springblade.file.callback.UnzipEventType name + * @return + */ + boolean callback(Long bizId, String event); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipEventType.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipEventType.java new file mode 100644 index 0000000..3df651d --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipEventType.java @@ -0,0 +1,22 @@ +package org.springblade.file.callback; + +/** + * 解压事件类型 + * @author bfhuange + * @date 2024/11/15 + */ +public enum UnzipEventType { + /** + * 开始解压 + */ + START, + /** + * 解压成功 + */ + SUCCESS, + /** + * 解压失败 + */ + FAIL, + ; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/config/FeignFileUploadClientConfig.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/config/FeignFileUploadClientConfig.java new file mode 100644 index 0000000..b23ff27 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/config/FeignFileUploadClientConfig.java @@ -0,0 +1,20 @@ +package org.springblade.file.config; + +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; + +/** + * @author bfhuange + * @since 2025/4/9 + */ +public class FeignFileUploadClientConfig { + + @Bean + public Encoder feignFormEncoder(ObjectFactory messageConverters) { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClient.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClient.java new file mode 100644 index 0000000..31876cc --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClient.java @@ -0,0 +1,189 @@ +package org.springblade.file.feign; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import org.springblade.core.launch.constant.AppConstant; +import org.springblade.core.tool.api.FR; +import org.springblade.file.pojo.dto.*; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.*; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.SpringQueryMap; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@FeignClient( + value = AppConstant.APPLICATION_FILE_NAME, + fallback = IFileClientFallback.class +) +public interface IFileClient { + + String API_PREFIX = "/feign/client/file"; + String ATTACHMENT = API_PREFIX + "/attachment"; + String ATTACHMENT_DETAIL = API_PREFIX + "/attachment-detail"; + String BUSINESS_ATTACHMENT_SAVE_REMOVE = API_PREFIX + "/businessAttachment-save-remove"; + String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE = API_PREFIX + "/businessAttachment-batch-save-remove"; + String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_BY_PARAM = API_PREFIX + "/businessAttachment-batch-save-remove-by-param"; + String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_NO_BIZ_ID = API_PREFIX + "/businessAttachment-batch-save-remove-no-bizId"; + String BUSINESS_ATTACHMENT_REMOVE = API_PREFIX + "/businessAttachment-remove"; + String BUSINESS_ATTACHMENT_LIST = API_PREFIX + "/businessAttachment-list"; + String BUSINESS_UNZIP_TASK_SAVE = API_PREFIX + "/businessUnzipTask-save"; + String BUSINESS_UNZIP_TASK_UPDATE = API_PREFIX + "/businessUnzipTask-update"; + String BUSINESS_UNZIP_TASK_CANCEL = API_PREFIX + "/businessUnzipTask-cancel"; + String BUSINESS_UNZIP_TASK_REMOVE = API_PREFIX + "/businessUnzipTask-remove"; + String BUSINESS_UNZIP_TASK_LIST = API_PREFIX + "/businessUnzipTask-list"; + String BUSINESS_UNZIP_DETAIL_LIST = API_PREFIX + "/businessUnzipDetail-list"; + String RECOGNITION_TRANSPORT_CERTIFICATE = API_PREFIX + "/recognitionTransportCertificate"; + String FILE_URLS = API_PREFIX + "/getFileUrls"; + + /** + * 获取附件,多个附件id用逗号分割 + * + * @param id + * @return List + */ + @GetMapping(ATTACHMENT) + FR> getAttachment(@RequestParam("id") String id); + + /** + * 获取附件详情 + * + * @param id + * @return List + */ + @GetMapping(ATTACHMENT_DETAIL) + FR getAttachmentDetail(@RequestParam("id") Long id); + + /** + * 保存并删除业务附件,remove为true,先用bizId删除,再新增 + * + * @param param + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_SAVE_REMOVE) + FR saveAndRemoveBusinessAttachment(@RequestBody @Validated BusinessAttachmentBatchAddDTO param); + + /** + * 批量保存并删除业务附件,先删除附件,再新增,业务id为空,从params中获取bizId,先删除 + * + * @param params 参数为空不新增 + * @param bizId 业务id + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE) + FR batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @RequestParam(value = "bizId", required = false) Long bizId); + + /** + * 批量保存并删除业务附件,deleteParam为空,从params获取bizId删除,不为空,使用bizIds和bizCodes删除附件,bizCodes为空删除所有bizIds的附件 + * deleteParam 中 delete 为false,不删除 + * + * @param params 参数为空不新增 + * @param deleteParam 删除参数,支持只删除业务编码参数的附件 + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_BY_PARAM) + FR batchSaveAndRemoveByParamBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @Valid @SpringQueryMap BusinessAttachmentDeleteDTO deleteParam); + + /** + * 批量保存并删除业务附件,从参数中提取所有bizId + * + * @param params 参数 + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_NO_BIZ_ID) + FR batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params); + + /** + * 根据业务id删除业务附件 + * + * @param bizId + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_REMOVE) + FR removeBusinessAttachment(@NotNull(message = "业务id不能为空") @RequestParam(value = "bizId", required = false) Long bizId); + + /** + * 查询业务附件列表 + * + * @param param + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_LIST) + FR> getBusinessAttachments(@Validated @RequestBody BusinessAttachmentQueryDTO param); + + /** + * 保存业务解压任务 + * @param param + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_SAVE) + FR saveBusinessUnzipTask(@Validated @RequestBody BusinessUnzipTaskAddDTO param); + + /** + * 更新业务解压任务(重新上传) + * @param param + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_UPDATE) + FR updateBusinessUnzipTask(@Validated @RequestBody BusinessUnzipTaskUpdateDTO param); + + /** + * 取消业务解压任务 + * @param bizId + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_CANCEL) + FR cancelBusinessUnzipTask(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId); + + /** + * 删除业务解压任务 + * @param bizId + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_REMOVE) + FR removeBusinessUnzipTask(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId); + + /** + * 根据业务id列表查询业务解压任务列表 + * @param bizIds + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_LIST) + FR> getBusinessUnzipTasksByBizIds(@Validated @NotEmpty(message = "业务id列表不能为空") @RequestBody List bizIds); + + /** + * 查询解压明细列表 + * @param bizId + * @return + */ + @GetMapping(BUSINESS_UNZIP_DETAIL_LIST) + FR> queryBusinessUnzipDetail(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId); + + /** + * 识别车辆运输凭证 + * + * @param param 同一车牌文件夹下的附件objectKey列表 + * @return + */ + @PostMapping(RECOGNITION_TRANSPORT_CERTIFICATE) + FR recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param); + + /** + * 批量获取附件下载地址 + * @param param + * @return + */ + @PostMapping(FILE_URLS) + FR> getFileUrls(@Validated @RequestBody IdsDTO param); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClientFallback.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClientFallback.java new file mode 100644 index 0000000..ecf0da5 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClientFallback.java @@ -0,0 +1,113 @@ +package org.springblade.file.feign; + +import jakarta.validation.Valid; +import org.springblade.core.tool.api.FR; +import org.springblade.file.pojo.dto.*; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.*; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springframework.cloud.openfeign.SpringQueryMap; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@Component +public class IFileClientFallback implements IFileClient, IFileUploadClient { + + @Override + public FR> getAttachment(String id) { + return FR.fail("获取数据失败"); + } + + @Override + public FR getAttachmentDetail(Long id) { + return FR.fail("获取数据失败"); + } + + @Override + public FR saveAndRemoveBusinessAttachment(BusinessAttachmentBatchAddDTO param) { + return FR.fail("获取数据失败"); + } + + @Override + public FR batchSaveAndRemoveBusinessAttachment(List params, Long bizId) { + return FR.fail("获取数据失败"); + } + + @Override + public FR batchSaveAndRemoveByParamBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params, @Valid @SpringQueryMap BusinessAttachmentDeleteDTO deleteParam) { + return FR.fail("获取数据失败"); + } + + @Override + public FR batchSaveAndRemoveBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params) { + return FR.fail("获取数据失败"); + } + + @Override + public FR removeBusinessAttachment(Long bizId) { + return FR.fail("获取数据失败"); + } + + @Override + public FR> getBusinessAttachments(BusinessAttachmentQueryDTO param) { + return FR.fail("获取数据失败"); + } + + @Override + public FR saveBusinessUnzipTask(BusinessUnzipTaskAddDTO param) { + return FR.fail("获取数据失败"); + } + + @Override + public FR updateBusinessUnzipTask(BusinessUnzipTaskUpdateDTO param) { + return FR.fail("获取数据失败"); + } + + @Override + public FR cancelBusinessUnzipTask(Long bizId) { + return FR.fail("获取数据失败"); + } + + @Override + public FR removeBusinessUnzipTask(Long bizId) { + return FR.fail("获取数据失败"); + } + + @Override + public FR> getBusinessUnzipTasksByBizIds(List bizIds) { + return FR.fail("获取数据失败"); + } + + @Override + public FR> queryBusinessUnzipDetail(Long bizId) { + return FR.fail("获取数据失败"); + } + + @Override + public FR recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param) { + return FR.fail("获取数据失败"); + } + + @Override + public FR> getFileUrls(IdsDTO param) { + return FR.fail("获取数据失败"); + } + + @Override + public FR> ossUpload(MultipartFile[] files) { + return FR.fail("获取数据失败"); + } + + @Override + public FR ossUploadTemp(MultipartFile file, Integer expires) { + return FR.fail("获取数据失败"); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileUploadClient.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileUploadClient.java new file mode 100644 index 0000000..1502b4b --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileUploadClient.java @@ -0,0 +1,49 @@ +package org.springblade.file.feign; + +import jakarta.validation.constraints.NotNull; +import org.springblade.core.launch.constant.AppConstant; +import org.springblade.core.tool.api.FR; +import org.springblade.file.config.FeignFileUploadClientConfig; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.TempFileUrlVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@FeignClient( + value = AppConstant.APPLICATION_FILE_NAME, + fallback = IFileClientFallback.class, + configuration = FeignFileUploadClientConfig.class +) +public interface IFileUploadClient { + + String API_PREFIX = "/feign/client/file"; + String UPLOAD = API_PREFIX + "/ossUpload"; + String UPLOAD_TEMP = API_PREFIX + "/ossUploadTemp"; + + /** + * 文件上传 + * @param files + * @return + */ + @PostMapping(value = UPLOAD, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + FR> ossUpload(@RequestPart("files") MultipartFile[] files); + + /** + * 上传临时文件,不保存到附件表,过期后自动删除 + * @param file + * @param expires 过期时间,单位天,过期后自动删除 + * @return + */ + @PostMapping(value = UPLOAD_TEMP, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + FR ossUploadTemp(@RequestPart("file") MultipartFile file, @NotNull(message = "过期时间不能为空") @RequestParam(name = "expires", required = false) Integer expires); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentAddDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentAddDTO.java new file mode 100644 index 0000000..6870a20 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentAddDTO.java @@ -0,0 +1,53 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Data +@Schema(description = "业务附件新增参数") +@Builder +public class BusinessAttachmentAddDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务id + */ + @NotNull(message = "业务id不能为空") + @Schema(description = "业务id") + private Long bizId; + /** + * 业务code + */ + @NotBlank(message = "业务code不能为空") + @Schema(description = "业务code") + private String bizCode; + /** + * 附件id + */ + @NotNull(message = "附件id不能为空") + @Schema(description = "附件id") + private Long attachmentId; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDTO.java new file mode 100644 index 0000000..6ae7ce0 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDTO.java @@ -0,0 +1,73 @@ +package org.springblade.file.pojo.dto; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@Schema(description = "业务附件批量新增参数") +public class BusinessAttachmentBatchAddDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务id + */ + @NotNull(message = "业务id不能为空") + @Schema(description = "业务id") + private Long bizId; + + /** + * 明细列表 + */ + @Schema(description = "明细列表") + private List<@Valid BusinessAttachmentBatchAddDetailDTO> details; + + /** + * 是否先删除,默认 是 + */ + @Schema(description = "是否先删除,默认 是") + private boolean remove = true; + + public BusinessAttachmentBatchAddDTO(Long bizId) { + this.bizId = bizId; + } + + public BusinessAttachmentBatchAddDTO(Long bizId, String bizCode, Long attachmentId) { + this.bizId = bizId; + this.details = Lists.newArrayList(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentId)); + } + + public BusinessAttachmentBatchAddDTO addDetail(BusinessAttachmentBatchAddDetailDTO detail) { + if (this.details == null) { + this.details = new ArrayList<>(); + } + this.details.add(detail); + return this; + } + + public BusinessAttachmentBatchAddDTO addDetail(String bizCode, Long attachmentId) { + return this.addDetail(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentId)); + } + + public BusinessAttachmentBatchAddDTO addDetail(String bizCode, List attachmentIds) { + return this.addDetail(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentIds)); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDetailDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDetailDTO.java new file mode 100644 index 0000000..ac5d3a7 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDetailDTO.java @@ -0,0 +1,48 @@ +package org.springblade.file.pojo.dto; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@Schema(description = "业务附件批量新增明细参数") +@Builder +public class BusinessAttachmentBatchAddDetailDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务code + */ + @NotBlank(message = "业务编码不能为空") + @Schema(description = "业务code") + private String bizCode; + /** + * 附件id列表 + */ + @NotEmpty(message = "附件id列表不能为空") + @Schema(description = "附件id列表") + private List<@NotNull(message = "附件id不能为空") Long> attachmentIds; + + public BusinessAttachmentBatchAddDetailDTO(String bizCode, Long attachmentId) { + this.bizCode = bizCode; + this.attachmentIds = Lists.newArrayList(attachmentId); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentDeleteDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentDeleteDTO.java new file mode 100644 index 0000000..d4ea148 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentDeleteDTO.java @@ -0,0 +1,54 @@ +package org.springblade.file.pojo.dto; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springblade.core.tool.utils.StringUtil; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业务附件删除参数 + * @author bfhuange + * @since 2024/12/3 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@Schema(description = "业务附件删除参数") +public class BusinessAttachmentDeleteDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务id列表 + */ + @Schema(description = "业务id列表") + private List bizIds; + /** + * 业务code列表 + */ + @Schema(description = "业务code列表") + private List bizCodes; + /** + * 是否删除 + */ + @Schema(description = "是否删除") + private boolean delete = true; + + public BusinessAttachmentDeleteDTO(Long bizId) { + this.bizIds = Lists.newArrayList(bizId); + } + + public BusinessAttachmentDeleteDTO(Long bizId, String bizCode) { + this.bizIds = Lists.newArrayList(bizId); + if (StringUtil.isNotBlank(bizCode)) { + this.bizCodes = Lists.newArrayList(bizCode); + } + } + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentQueryDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentQueryDTO.java new file mode 100644 index 0000000..6bd17d0 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentQueryDTO.java @@ -0,0 +1,62 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "业务附件查询参数") +public class BusinessAttachmentQueryDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务id列表 + */ + @NotNull(message = "业务id列表不能为空") + @Schema(description = "业务id列表") + private List<@NotNull(message = "业务id不能为空") Long> bizIds; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 业务code列表 + */ + @Schema(description = "业务code列表") + private List bizCodes; + + public BusinessAttachmentQueryDTO(Long bizId) { + this.bizIds = List.of(bizId); + } + + public BusinessAttachmentQueryDTO(List bizIds) { + this.bizIds = bizIds; + } + + /** + * 兼容历史参数 + * @param bizId + */ + public void setBizId(Long bizId) { + if (this.bizIds == null) { + this.bizIds = new ArrayList<>(); + } + this.bizIds.add(bizId); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskAddDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskAddDTO.java new file mode 100644 index 0000000..913eafa --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskAddDTO.java @@ -0,0 +1,55 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springblade.file.pojo.enums.UnzipTask; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 业务解压任务新增参数 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务新增参数") +public class BusinessUnzipTaskAddDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务id + */ + @NotNull(message = "业务id不能为空") + @Schema(description = "业务id") + private Long bizId; + /** + * 部门名称ID + */ + @Schema(description = "部门名称ID") + private Long departmentId; + /** + * 业务code + */ + @NotBlank(message = "业务code不能为空") + @Schema(description = "业务code") + private String bizCode = UnzipTask.BizCode.WAYBILL.getCode(); + /** + * 文档编号 + */ + @Schema(description = "文档编号") + private String docCode; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件") + private String relateType = UnzipTask.RelateType.FILE_TASK.getCode(); + /** + * 关联id,文件任务id或附件id + */ + @NotNull(message = "关联id不能为空") + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskQueryDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskQueryDTO.java new file mode 100644 index 0000000..9b66c8c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskQueryDTO.java @@ -0,0 +1,55 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业务解压任务查询参数 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务查询参数") +public class BusinessUnzipTaskQueryDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文档编号(凭证批次号) + */ + @Schema(description = "文档编号(凭证批次号)") + private String docCode; + /** + * 附件名称 + */ + @Schema(description = "附件名称") + private String attachmentName; + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件", hidden = true) + private String relateType; + /** + * 是否只查询当前用户 + */ + @Schema(description = "是否只查询当前用户") + private boolean currentUser; + /** + * 当前登录人id + */ + @Schema(hidden = true) + private Long currentUserId; + /** + * 业务id列表 + */ + @Schema(description = "业务id列表", hidden = true) + private List bizIds; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskUpdateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskUpdateDTO.java new file mode 100644 index 0000000..e838efc --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskUpdateDTO.java @@ -0,0 +1,42 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springblade.file.pojo.enums.UnzipTask; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 业务解压任务更新参数 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务更新参数") +public class BusinessUnzipTaskUpdateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @Schema(description = "主键") + private Long id; + /** + * 业务id + */ + @Schema(description = "业务id") + private Long bizId; + /** + * 关联id,文件任务id或附件id + */ + @NotNull(message = "关联id不能为空") + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件", hidden = true) + private String relateType = UnzipTask.RelateType.FILE_TASK.getCode(); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/CertificateAttachmentDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/CertificateAttachmentDTO.java new file mode 100644 index 0000000..0e0738a --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/CertificateAttachmentDTO.java @@ -0,0 +1,44 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 凭证识别参数,依次调用单张识别 + * @author bfhuange + * @since 2024/12/13 + */ +@Data +@Schema(description = "凭证附件参数") +public class CertificateAttachmentDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 磅单ObjectKey + */ + @Schema(description = "磅单ObjectKey") + private String poundObjectKey; + /** + * 车头ObjectKey + */ + @Schema(description = "车头ObjectKey") + private String carFrontObjectKey; + /** + * 驾驶证ObjectKey + */ + @Schema(description = "驾驶证ObjectKey") + private String driverLicenseObjectKey; + /** + * 行驶证ObjectKey + */ + @Schema(description = "行驶证ObjectKey") + private String vehicleLicenseObjectKey; + /** + * 项目简称 + */ + @Schema(description = "项目简称") + private String projectAbbreviation; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java new file mode 100644 index 0000000..92c6a0c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java @@ -0,0 +1,45 @@ +package org.springblade.file.pojo.dto; + +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 凭证批量识别参数,所有图像链接统一识别,不分类 + * @author bfhuange + * @since 2025/3/13 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class FileCertificateBatchRecognitionDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 所有图像 obs key列表 + */ + @NotEmpty(message = "图像 obs key列表不能为空") + private List objectKeys; + /** + * 项目简称 + */ + private String projectAbbreviation; + /** + * 凭证编码 + */ + private String code; + /** + * 目录名称 + */ + private String dirName; + + public FileCertificateBatchRecognitionDTO(List objectKeys) { + this.objectKeys = objectKeys; + } + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java new file mode 100644 index 0000000..4b3acf0 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java @@ -0,0 +1,62 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件任务创建对象 + * @author bfhuange + * @date 2024/10/22 + */ +@Data +@Schema(description = "文件任务创建对象") +public class FileTaskCreateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Md5 + */ + @NotBlank(message = "Md5不能为空") + @Schema(description = "Md5") + private String md5; + + /** + * 附件名 + */ + @NotBlank(message = "附件名不能为空") + @Schema(description = "附件名") + private String attachmentName; + + /** + * 附件大小(单位:B) + */ + @NotNull(message = "附件大小不能为空") + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @NotNull(message = "分段大小不能为空") + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @NotNull(message = "分段数量不能为空") + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 是否强制创建新任务,默认 false,如果存在相同md5的任务,会创建失败,true则新创建一个任务 + */ + @Schema(description = "是否强制创建新任务,默认 false,如果存在相同md5的任务,会创建失败,true则新创建一个任务") + private boolean force; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskUpdateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskUpdateDTO.java new file mode 100644 index 0000000..9a6d6bf --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskUpdateDTO.java @@ -0,0 +1,41 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springblade.file.pojo.vo.PartEtagVO; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件任务更新对象 + * @author bfhuange + * @date 2024/10/22 + */ +@Data +@Schema(description = "文件任务更新对象") +public class FileTaskUpdateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 文件任务id + */ + @NotNull(message = "文件任务id不能为空") + @Schema(description = "主键id") + private Long id; + /** + * 段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串 + */ + @NotBlank(message = "etag不能为空") + @Schema(description = "etag") + private String etag; + /** + * 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数 + */ + @NotNull(message = "段号不能为空") + @Schema(description = "段号") + private Integer partNumber; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileUploadDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileUploadDTO.java new file mode 100644 index 0000000..a53e06c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileUploadDTO.java @@ -0,0 +1,15 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +@Data +@Schema(description = "分段上传文件对象") +public class FileUploadDTO { + private MultipartFile multipartFile; // 文件对象 + private String md5; // 文件md5值 + private Long chunkTotal; // 分段总数 + private Long chunkSize; // 分段大小 + private Integer index; // 当前分段的位置 +} \ No newline at end of file diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/IdsDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/IdsDTO.java new file mode 100644 index 0000000..7119e61 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/IdsDTO.java @@ -0,0 +1,33 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * id批量参数 + * @author bfhuange + * @date 2024/10/29 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "id批量参数") +public class IdsDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * id列表 + */ + @NotEmpty(message = "id列表不能为空") + @Schema(description = "id列表") + private @Valid List<@NotNull(message = "id不能为空") Long> ids; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/Attachment.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/Attachment.java new file mode 100644 index 0000000..a721e52 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/Attachment.java @@ -0,0 +1,111 @@ +package org.springblade.file.pojo.entity; +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; +import java.io.Serializable; +import java.util.Date; + +/** + * @author zhaowei + * @date 2024/8/16 + */ + +@Data +@TableName("blade_attachment") +@Schema(description = "附件对象") +public class Attachment implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 压缩文件所在压缩包相对路径 + */ + @Schema(description = "压缩文件所在压缩包相对路径") + private String dir; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private long size; + + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT) + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除 [0:未删除,1:删除]") + private Integer isDeleted; + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessAttachment.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessAttachment.java new file mode 100644 index 0000000..a19529f --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessAttachment.java @@ -0,0 +1,113 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务附件信息表 实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Data +@TableName("blade_business_attachment") +@Schema(description = "BusinessAttachment对象") +public class BusinessAttachment implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务id + */ + @Schema(description = "业务id") + private Long bizId; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 附件id + */ + @Schema(description = "附件id") + private Long attachmentId; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + @TableField(value = "`desc`") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; + + /** + * 租户ID + */ + @Schema(description = "租户ID") + @TableField(fill = FieldFill.INSERT) + private String tenantId; + + /** + * 主键id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Integer isDeleted; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipDetail.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipDetail.java new file mode 100644 index 0000000..6d21a9f --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipDetail.java @@ -0,0 +1,85 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 业务解压明细表 + *

+ * + * @author bfhuange + * @since 2024-10-30 + */ +@Data +@TableName("blade_business_unzip_detail") +@Schema(description = "BusinessUnzipDetail对象") +public class BusinessUnzipDetail implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + /** + * 业务解压任务id + */ + @Schema(description = "业务解压任务id") + private Long businessUnzipTaskId; + /** + * 附件id + */ + @Schema(description = "附件id") + private Long attachmentId; + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除 [0:未删除,1:删除]") + @TableField(fill = FieldFill.INSERT) + private Integer isDeleted; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipTask.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipTask.java new file mode 100644 index 0000000..8f9d027 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipTask.java @@ -0,0 +1,89 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 业务解压任务表 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +@Data +@TableName("blade_business_unzip_task") +@Schema(description = "业务解压任务表") +public class BusinessUnzipTask implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "业务id") + private Long bizId; + /** + * 部门名称ID + */ + @Schema(description = "部门名称ID") + private Long departmentId; + + @Schema(description = "业务code") + private String bizCode; + + @Schema(description = "文档编号") + private String docCode; + + @Schema(description = "关联类型,file_task 文件任务,attachment 附件") + private String relateType; + + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; + + @Schema(description = "状态 uploading:正在上传 unzipping:解压中 finished:已完成") + private String status; + + @Schema(description = "租户ID") + @TableField(fill = FieldFill.INSERT) + private String tenantId; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + @Schema(description = "是否已删除", hidden = true) + @TableField(fill = FieldFill.INSERT) + @TableLogic + private Integer isDeleted; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java new file mode 100644 index 0000000..36d0ed2 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java @@ -0,0 +1,152 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author zhaowei + * @date 2024/8/23 + */ + +@Data +@TableName("blade_file_task") +@Schema(description = "文件上传任务对象") +public class FileTask implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 文件类型 + */ + @Schema(description = "文件类型") + private String suffix; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1 + */ + @Schema(description = "当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1") + private Integer currentIndex; + + /** + * 华为云初始化上传任务生成的uploadId + */ + @Schema(description = "华为云初始化上传任务生成的uploadId") + private String uploadId; + + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT) + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除 [0:未删除,1:删除]") + @TableField(fill = FieldFill.INSERT) + private Integer isDeleted; + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTaskPart.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTaskPart.java new file mode 100644 index 0000000..a69e1a5 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTaskPart.java @@ -0,0 +1,69 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author zhaowei + * @date 2024/8/16 + */ + +@Data +@TableName("blade_file_task_part") +@Schema(description = "文件任务分段表") +public class FileTaskPart implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 文件任务Id + */ + @Schema(description = "文件任务Id") + private Long fileTaskId; + + /** + * 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数 + */ + @Schema(description = "段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数") + private Integer partNumber; + + /** + * 段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串 + */ + @Schema(description = "段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串") + private String etag; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/FileTaskStatus.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/FileTaskStatus.java new file mode 100644 index 0000000..b258db8 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/FileTaskStatus.java @@ -0,0 +1,39 @@ +package org.springblade.file.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 文件任务状态 + * @author bfhuange + * @date 2024/10/22 + */ +@AllArgsConstructor +@Getter +public enum FileTaskStatus { + /** + * 正在上传 + */ + UPLOADING("uploading", "正在上传"), + /** + * 上传完成 + */ + COMPLETED("completed", "上传完成"), + /** + * 上传失败 + */ + FAILED("failed", "上传失败"), + /** + * 已暂停 + */ + PAUSED("paused", "已暂停"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/UnzipTask.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/UnzipTask.java new file mode 100644 index 0000000..7ea5d49 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/UnzipTask.java @@ -0,0 +1,99 @@ +package org.springblade.file.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 解压任务枚举类 + * @author bfhuange + * @date 2024/10/27 + */ +public class UnzipTask { + + /** + * 业务编码枚举 + */ + @AllArgsConstructor + @Getter + public enum BizCode { + /** + * 运单 + */ + WAYBILL("waybill", "运单"), + /** + * 基地 + */ + WAREHOUSE("warehouse", "基地"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + } + + /** + * 关联类型枚举 + */ + @AllArgsConstructor + @Getter + public enum RelateType { + /** + * 文件任务 + */ + FILE_TASK("file_task", "文件任务"), + /** + * 附件 + */ + ATTACHMENT("attachment", "附件"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + } + + /** + * 状态枚举 + */ + @AllArgsConstructor + @Getter + public enum Status { + /** + * 正在上传 + */ + UPLOADING("uploading", "正在上传"), + /** + * 上传完成 + */ + UPLOADED("uploaded", "上传完成"), + /** + * 正在解压 + */ + UNZIPPING("unzipping", "正在解压"), + /** + * 解压失败 + */ + UNZIP_FAIL("unzip_fail", "解压失败"), + /** + * 已完成 + */ + FINISHED("finished", "已完成"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/AttachmentDetailVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/AttachmentDetailVO.java new file mode 100644 index 0000000..7059435 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/AttachmentDetailVO.java @@ -0,0 +1,73 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 附件详情对象 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +@Schema(description = "附件详情对象") +public class AttachmentDetailVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键id + */ + @Schema(description = "主键id") + private Long id; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private long size; + + /** + * 创建人 + */ + @Schema(description = "创建人") + private Long createUser; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新人 + */ + @Schema(description = "更新人") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private Date updateTime; + + /** + * 下载链接 + */ + @Schema(description = "下载链接") + private String url; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessAttachmentVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessAttachmentVO.java new file mode 100644 index 0000000..2a76bae --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessAttachmentVO.java @@ -0,0 +1,105 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务附件信息表 视图实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Data +@Schema(description = "业务附件响应参数") +public class BusinessAttachmentVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + private Long id; + /** + * 业务id + */ + @Schema(description = "业务id") + @JsonSerialize(using = ToStringSerializer.class) + private Long bizId; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 业务code名称 + */ + @Schema(description = "业务code名称") + private String bizCodeStr; + /** + * 附件id + */ + @Schema(description = "附件id") + @JsonSerialize(using = ToStringSerializer.class) + private Long attachmentId; + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private long size; + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + @Size(max = 100, message = "附件描述长度不能超过100") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; + /** + * 上传人id + */ + @Schema(description = "上传人id") + @JsonSerialize(using = ToStringSerializer.class) + private Long uploadUserId; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String uploadUserName; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String createUserName; + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_TIME_MINI) + @Schema(description = "创建时间") + private Date createTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipDetailListVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipDetailListVO.java new file mode 100644 index 0000000..779c13e --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipDetailListVO.java @@ -0,0 +1,94 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务解压任务明细对象 + * @author bfhuange + * @date 2024/11/08 + */ +@Data +@Schema(description = "业务解压任务明细对象") +public class BusinessUnzipDetailListVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + private Long id; + /** + * 附件id + */ + @Schema(description = "附件id") + @JsonSerialize(using = ToStringSerializer.class) + private Long attachmentId; + + //==========================附件属性============================= + /** + * 压缩文件所在压缩包相对路径 + */ + @Schema(description = "压缩文件所在压缩包相对路径") + private String dir; + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private long size; + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + @Size(max = 100, message = "附件描述长度不能超过100") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; + /** + * 上传人id + */ + @Schema(description = "上传人id") + @JsonSerialize(using = ToStringSerializer.class) + private Long uploadUserId; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String uploadUserName; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String createUserName; + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_TIME_MINI) + @Schema(description = "创建时间") + private Date createTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipTaskListVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipTaskListVO.java new file mode 100644 index 0000000..4e1de52 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipTaskListVO.java @@ -0,0 +1,169 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务解压任务分页对象 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务分页对象") +public class BusinessUnzipTaskListVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + private Long id; + /** + * 业务id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "业务id") + private Long bizId; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 文档编号 + */ + @Schema(description = "文档编号") + private String docCode; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件") + private String relateType; + /** + * 关联id,文件任务id或附件id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; + + //==========================文件任务属性============================= + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 文件类型 + */ + @Schema(description = "文件类型") + private String suffix; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1 + */ + @Schema(description = "当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1") + private Integer currentIndex; + + /** + * 华为云初始化上传任务生成的uploadId + */ + @Schema(description = "华为云初始化上传任务生成的uploadId") + private String uploadId; + + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + + /** + * 状态名称 + */ + @Schema(description = "状态名称") + private String statusStr; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + private Date updateTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileHandleVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileHandleVO.java new file mode 100644 index 0000000..c2af5f6 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileHandleVO.java @@ -0,0 +1,36 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.File; +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 文件处理结果对象 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +@Schema(description = "文件处理结果对象") +public class FileHandleVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 下载的压缩文件 + */ + @Schema(description = "下载的压缩文件") + private File downloadFile; + /** + * 解压目录 + */ + @Schema(description = "解压目录") + private File unzippedDir; + /** + * 解压后的文件列表 + */ + @Schema(description = "解压后的文件列表") + private List unzippedFiles; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskUpdateVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskUpdateVO.java new file mode 100644 index 0000000..3bb8b6c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskUpdateVO.java @@ -0,0 +1,25 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 文件任务更新结果 + * @author bfhuange + * @date 2024/10/28 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(description = "文件任务更新结果") +public class FileTaskUpdateVO extends FileTaskVO { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文档编号(凭证批次号),如果有则返回,没有为空 + */ + @Schema(description = "文档编号(凭证批次号),如果有则返回,没有为空") + private String docCode; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java new file mode 100644 index 0000000..0407ff8 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java @@ -0,0 +1,133 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 文件任务对象 + * @author bfhuange + * @date 2024/10/22 + */ +@Data +@Schema(description = "文件任务对象") +public class FileTaskVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + private Long id; + + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 文件类型 + */ + @Schema(description = "文件类型") + private String suffix; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1 + */ + @Schema(description = "当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1") + private Integer currentIndex; + + /** + * 华为云初始化上传任务生成的uploadId + */ + @Schema(description = "华为云初始化上传任务生成的uploadId") + private String uploadId; + + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + private Date updateTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileUrlVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileUrlVO.java new file mode 100644 index 0000000..2d19918 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileUrlVO.java @@ -0,0 +1,33 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件下载地址对象 + * @author bfhuange + * @since 2025/4/22 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "文件下载地址对象") +public class FileUrlVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键id + */ + @Schema(description = "主键id") + private Long id; + /** + * 文件下载地址 + */ + @Schema(description ="文件下载地址") + private String url; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileVO.java new file mode 100644 index 0000000..446d5a3 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileVO.java @@ -0,0 +1,35 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件对象 + * @author bfhuange + * @date 2024/11/19 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "文件对象") +public class FileVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/PartEtagVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/PartEtagVO.java new file mode 100644 index 0000000..281ed29 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/PartEtagVO.java @@ -0,0 +1,29 @@ +package org.springblade.file.pojo.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 待合并的段 + * @author bfhuange + * @date 2024/10/21 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class PartEtagVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串 + */ + private String etag; + /** + * 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数 + */ + private Integer partNumber; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/TempFileUrlVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/TempFileUrlVO.java new file mode 100644 index 0000000..6d92f0e --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/TempFileUrlVO.java @@ -0,0 +1,33 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件下载地址对象 + * @author bfhuange + * @since 2025/4/22 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "临时文件下载地址对象") +public class TempFileUrlVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * obs key + */ + @Schema(description = "obs key") + private String objectKey; + /** + * 文件下载地址 + */ + @Schema(description ="文件下载地址") + private String url; +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/AppVersion.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/AppVersion.java new file mode 100644 index 0000000..3387a92 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/AppVersion.java @@ -0,0 +1,81 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.entity; + +import com.baomidou.mybatisplus.annotation.SqlCondition; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.tenant.mp.TenantEntity; + +import java.io.Serial; + + +@Data +@Schema(description = "APP版本管理") +@TableName("t_app_version") +@EqualsAndHashCode(callSuper = true) +public class AppVersion extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @NotEmpty + @Schema(description = "更新类型") + protected String updateType; + + @TableField(condition = SqlCondition.LIKE) + @NotEmpty + @Schema(description = "版本号") + protected String version; + + @TableField(condition = SqlCondition.LIKE) + @NotEmpty + @Schema(description = "子版本号") + protected String subVersion; + + @NotEmpty + @Schema(description = "强制更新(是/否)") + protected String isForceUpdate; + + @NotEmpty + @Schema(description = "安装方式") + protected String installMethod; + + @NotNull + @Schema(description = "安装包(附件Id)") + protected Long installAttachmentId; + + @TableField(condition = SqlCondition.LIKE) + @NotEmpty + @Schema(description = "更新说明") + protected String updateExplanation; + +} diff --git a/blade-service-api/pom.xml b/blade-service-api/pom.xml index 4dbf357..42afb44 100644 --- a/blade-service-api/pom.xml +++ b/blade-service-api/pom.xml @@ -23,6 +23,7 @@ blade-system-api blade-user-api blade-record-api + blade-file-api diff --git a/blade-service/blade-file/Dockerfile b/blade-service/blade-file/Dockerfile new file mode 100644 index 0000000..939092c --- /dev/null +++ b/blade-service/blade-file/Dockerfile @@ -0,0 +1,19 @@ +FROM bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/blade-file.jar /opt/app.jar + +WORKDIR /opt + +EXPOSE 8107 + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx4096m", "-jar", "app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"] diff --git a/blade-service/blade-file/Dockerfile-rt b/blade-service/blade-file/Dockerfile-rt new file mode 100644 index 0000000..e005354 --- /dev/null +++ b/blade-service/blade-file/Dockerfile-rt @@ -0,0 +1,19 @@ +#FROM 10.38.16.129/bladex/alpine-java:openjdk17_cn_slim +#FROM registry.cn-hangzhou.aliyuncs.com/vic_person/alpine-java:openjdk17_cn_slim +FROM 10.38.16.129:80/bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +RUN apk add --no-cache tzdata + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/*.jar /opt/app.jar + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/opt/app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}"] diff --git a/blade-service/blade-file/Dockerfile-zh-dev b/blade-service/blade-file/Dockerfile-zh-dev new file mode 100644 index 0000000..66022e9 --- /dev/null +++ b/blade-service/blade-file/Dockerfile-zh-dev @@ -0,0 +1,19 @@ +FROM bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/blade-file.jar /opt/app.jar + +WORKDIR /opt + +EXPOSE 8107 + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx1024m", "-jar", "app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"] diff --git a/blade-service/blade-file/pom.xml b/blade-service/blade-file/pom.xml new file mode 100644 index 0000000..ce15c44 --- /dev/null +++ b/blade-service/blade-file/pom.xml @@ -0,0 +1,139 @@ + + + + blade-service + org.springblade + ${revision} + + 4.0.0 + + blade-file + ${project.artifactId} + jar + + + 1.27.1 + 1.10 + + + 1.18.30 + 1.5.5.Final + 7.5.5 + + + + + org.springblade + blade-core-boot + + + org.springblade + blade-starter-swagger + + + + org.springblade + blade-file-api + + + + org.springblade + blade-wps-api + + + + org.springblade + blade-ocr-api + + + + + net.lingala.zip4j + zip4j + 1.3.1 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + org.tukaani + xz + ${xz.version} + + + + com.github.junrar + junrar + ${junrar.version} + + + + com.alibaba.csp + sentinel-datasource-nacos + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + + + + + io.fabric8 + docker-maven-plugin + + ${docker.fabric.skip} + + + + org.apache.maven.plugins + maven-antrun-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.plugin.version} + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java b/blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java new file mode 100644 index 0000000..cfd985f --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java @@ -0,0 +1,47 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file; + +import org.springblade.core.cloud.client.BladeCloudApplication; +import org.springblade.core.launch.BladeApplication; +import org.springblade.core.launch.constant.AppConstant; +import org.springframework.context.annotation.ComponentScan; + +/** + * Desk启动器 + * + * @author Chill + */ +@BladeCloudApplication +@ComponentScan({"org.springblade.file", "org.springblade.**.feign"}) +public class FileApplication { + + public static void main(String[] args) { + BladeApplication.run(AppConstant.APPLICATION_FILE_NAME, FileApplication.class, args); + } + +} + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java new file mode 100644 index 0000000..0f462c3 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java @@ -0,0 +1,33 @@ +package org.springblade.file.config; + +import com.obs.services.ObsClient; +import org.springblade.common.utils.ObsUtil; +import org.springblade.file.service.impl.ObsFileServiceImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 文件配置类 + * @author bfhuange + * @date 2024/10/21 + */ +@Import(ObsUtil.class) +@Configuration +public class FileConfig { + + @Bean + public ObsProperties obsProperties() { + return new ObsProperties(); + } + + @Bean + public ObsClient obsClient(ObsProperties properties) { + return new ObsClient(properties.getAccessKeyId(), properties.getAccessKeySecret(), properties.getEndpoint()); + } + + @Bean + public ObsFileServiceImpl fileService(ObsProperties properties, ObsClient obsClient) { + return new ObsFileServiceImpl(properties, obsClient); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/config/ObsProperties.java b/blade-service/blade-file/src/main/java/org/springblade/file/config/ObsProperties.java new file mode 100644 index 0000000..9e42e6e --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/config/ObsProperties.java @@ -0,0 +1,69 @@ +package org.springblade.file.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.File; +import java.time.Duration; + +/** + * obs 配置 + * @author bfhuange + * @date 2024/10/21 + */ +@Data +@ConfigurationProperties(prefix = "obs") +public class ObsProperties { + /** + * OBS endpoint + */ + public String endpoint; + /** + * AK in the access key + */ + public String accessKeyId; + /** + * SK in the access key + */ + public String accessKeySecret; + /** + * 桶名 + */ + public String bucketName; + /** + * 根目录 + */ + public String rootDirectory; + /** + * 过期时间,默认1小时 + */ + public Long expires = Duration.ofHours(1).toSeconds(); + /** + * 本地临时下载文件目录 + */ + public String tmpDownloadDir = "tmp" + File.separator + "download"; + /** + * 本地临时解压文件目录 + */ + public String tmpUnzipDir = "tmp" + File.separator + "unzip"; + /** + * obs上压缩文件上传目录 + */ + public String zipUploadDir = "part"; + /** + * obs上解压后的文件上传目录 + */ + public String unzipUploadDir = "unzip"; + /** + * obs上设置了过期时间自动删除的上传目录 + */ + public String tmpUploadDir = "tmp"; + /** + * 下载进度间隔,默认 10m + */ + public Long downloadProgressInterval = 10 * 1024 * 1024L; + /** + * 上传进度间隔,默认 10m + */ + public Long uploadProgressInterval = 10 * 1024 * 1024L; +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessAttachmentController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessAttachmentController.java new file mode 100644 index 0000000..d698223 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessAttachmentController.java @@ -0,0 +1,75 @@ +package org.springblade.file.controller; + +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springblade.core.tool.api.R; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; +import org.springblade.file.service.IBusinessAttachmentService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 业务附件信息表 控制器 + * + * @author BladeX + * @since 2024-09-04 + */ +@Valid +@RestController +@AllArgsConstructor +@RequestMapping("businessAttachment") +@Tag(name = "业务附件信息表", description = "业务附件信息表接口") +public class BusinessAttachmentController { + + private final IBusinessAttachmentService businessAttachmentService; + + /** + * 业务附件信息表 新增 + */ + @PostMapping("/batchSave") + @ApiOperationSupport(order = 1) + @Operation(summary = "批量新增", description = "批量新增") + public R batchSave(@Validated @RequestBody List params) { + return R.status(businessAttachmentService.saveAndRemove(params, null)); + } + + /** + * 业务附件信息表 新增 + */ + @PostMapping("/save") + @ApiOperationSupport(order = 2) + @Operation(summary = "新增", description = "新增") + public R save(@Validated @RequestBody BusinessAttachmentBatchAddDTO param) { + return R.status(businessAttachmentService.saveAndRemove(param)); + } + + /** + * 业务附件信息表 删除 + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 3) + @Operation(summary = "逻辑删除", description = "传入bizId") + public R remove(@Parameter(description = "bizId", required = true) @RequestParam Long bizId) { + return R.status(businessAttachmentService.removeByBizIds(List.of(bizId))); + } + + /** + * 查询业务附件信息列表 + */ + @PostMapping("/queryList") + @ApiOperationSupport(order = 4) + @Operation(summary = "查询业务附件信息列表", description = "查询业务附件信息列表") + public R> queryList(@Validated @RequestBody BusinessAttachmentQueryDTO param) { + return R.data(businessAttachmentService.queryList(param)); + } + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessUnzipTaskController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessUnzipTaskController.java new file mode 100644 index 0000000..eab22f5 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessUnzipTaskController.java @@ -0,0 +1,69 @@ +package org.springblade.file.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springblade.core.log.annotation.ApiLog; +import org.springblade.core.log.utils.AssertUtils; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.api.R; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO; +import org.springblade.file.pojo.enums.UnzipTask; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; +import org.springblade.file.service.IBusinessUnzipTaskService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @author bfhuange + * @date 2024/10/28 + */ +@RestController +@AllArgsConstructor +@RequestMapping("/businessUnzipTask") +@Tag(name = "业务文件解压任务", description = "业务文件解压任务") +public class BusinessUnzipTaskController { + + private final IBusinessUnzipTaskService unzipTaskService; + + /** + * 文件任务列表 + * @param param + * @param query + */ + @GetMapping("/list") + @Operation(summary = "业务文件解压任务分页查询", description = "业务文件解压任务分页查询") + @PreAuth("hasPermission('waybill:uploadTask:list')") + public R> list(BusinessUnzipTaskQueryDTO param, Query query) { + if (param == null) { + param = new BusinessUnzipTaskQueryDTO(); + } + // 只查文件任务类型 + param.setRelateType(UnzipTask.RelateType.FILE_TASK.getCode()); + if (param.isCurrentUser()) { + // 只查自己的 + param.setCurrentUserId(AuthUtil.getUserId()); + } + IPage pages = unzipTaskService.selectPage(Condition.getPage(query), param); + return R.data(pages); + } + + /** + * 业务文件解压任务更新(重新上传) + * @param param + * @return + */ + @ApiLog("上传任务-重新上传") + @PostMapping("/update") + @Operation(summary = "业务文件解压任务更新(重新上传)", description = "业务文件解压任务更新(重新上传)") + @PreAuth("hasPermission('waybill:uploadTask:update')") + public R update(@Validated @RequestBody BusinessUnzipTaskUpdateDTO param) { + AssertUtils.notNull(param.getId(), "id不能为空"); + return R.status(unzipTaskService.updateReupload(param)); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java new file mode 100644 index 0000000..417f9e8 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java @@ -0,0 +1,168 @@ +package org.springblade.file.controller; + +import com.alibaba.csp.sentinel.annotation.SentinelResource; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.log.annotation.ApiLog; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.listener.FileEvent; +import org.springblade.file.pojo.dto.CertificateAttachmentDTO; +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.IOCRConvertService; +import org.springblade.thirdparty.ocr.pojo.dto.CertificateRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springblade.thirdparty.ocr.service.IOCRService; +import org.springblade.thirdparty.wps.service.IWpsService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Optional; + +/** + * @ClassName: fileUploadController + * @Author: zhaowei + * @Date: 2024/8/22 + */ +@Valid +@NonDS +@RestController +@AllArgsConstructor +@RequestMapping("/file") +@Tag(name = "文件相关管理", description = "文件相关管理") +public class FileController extends BladeController { + + private final IAttachmentService attachmentService; + private final IFileService fileService; + private final IWpsService wpsService; + private final IOCRService ocrService; + private final IOCRConvertService ocrConvertService; + private final ApplicationContext applicationContext; + + @ApiLog("附件管理-批量上传") + @Operation(summary = "OBS批量上传", description = "OBS批量上传,参数名:files") + @PostMapping("/ossUpload") + public R ossUpload(MultipartFile[] files, @RequestParam(value = "fileName", required = false) String fileName) { + return attachmentService.batchOssUpload(files, fileName); + } + + @Operation(summary = "上传", description = "上传,参数名:file") + @PostMapping("/upload") + public R upload(MultipartFile file) { + MultipartFile[] files = {file}; + R> result = attachmentService.batchOssUpload(files, null); + List data = result.getData(); + if(data != null && !data.isEmpty())return R.data(data.get(0)); + return R.fail("上传失败"); + } + + @Operation(summary = "获取附件,多个附件id用逗号分割", description = "获取附件,多个附件id用逗号分割") + @GetMapping("/getAttachment") + public R getAttachment(@RequestParam(value = "id") String id) { + return R.data(attachmentService.getAttachment(id)); + } + + @Operation(summary = "获取文件url", description = "获取文件url,参数:objectKey") + @GetMapping("/getFileUrl") + public R getFileUrl(@RequestParam(value = "objectKey") String objectKey, @RequestParam(value = "attachmentName", required = false) String attachmentName) { + if (StringUtil.isBlank(attachmentName)) { + // 附件名为空,查询附件名 + Attachment attachment = attachmentService.getOne(Wrappers.lambdaQuery() + .eq(Attachment::getObjectKey, objectKey) + .last("limit 1") + ); + attachmentName = Optional.ofNullable(attachment) + .map(Attachment::getAttachmentName) + .orElse(null); + if (attachmentName != null) { + // 附件名中如果有逗号,则替换为下划线 + attachmentName = attachmentName.replaceAll(",", "_"); + } + } + return R.data(fileService.getFileUrl(objectKey, attachmentName, null)); + } + + /** + * 获取wps文件预览url + * @param attachmentId * @param attachmentName + * @return + */ + @Operation(summary = "获取wps文件预览url", description = "获取wps文件预览url") + @GetMapping("/getWpsFilePreviewUrl") + public R getWpsFilePreviewUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId, + @NotBlank(message = "附件名称不能为空") @RequestParam(value = "attachmentName", required = false) String attachmentName) { + return R.data(wpsService.getFilePreviewUrl(attachmentId, attachmentName)); + } + + /** + * 获取wps文件编辑url + * @param attachmentId + * @param attachmentName + * @return + */ + @Operation(summary = "获取wps文件编辑url", description = "获取wps文件编辑url") + @GetMapping("/getWpsFileEditUrl") + public R getWpsFileEditUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId, + @NotBlank(message = "附件名称不能为空") @RequestParam(value = "attachmentName", required = false) String attachmentName) { + return R.data(wpsService.getWpsFileEditUrl(attachmentId, attachmentName)); + } + + @Operation(summary = "批量获取文件url", description = "批量获取文件url,参数:objectKey数组") + @PostMapping("/getFileUrls") + public R getFileUrls(@RequestBody List objectKeys) { + return R.data(fileService.getFileUrls(objectKeys, null)); + } + + @ApiLog("OCR识别-识别身份证") + @Operation(summary = "识别身份证信息支持正反面", description = "参数:objectKey") + @GetMapping("/recognitionIDCard") + public R recognitionIDCard(@NotNull(message = "objectKey不能为空") @RequestParam(value = "objectKey", required = false) String objectKey) { + String fileUrl = fileService.getFileUrl(objectKey, null); + return R.data(ocrService.recognitionIDCard(List.of(fileUrl))); + } + + @SentinelResource("ocr:batchCards") + @ApiLog("OCR识别-识别车辆运输凭证(不知道类型)") + @Operation(summary = "识别车辆运输凭证,不知道类型", description = "参数:objectKeylist") + @PostMapping("/recognitionTransportCertificates") + public R recognitionTransportCertificates(@RequestBody List objectKeys, @RequestParam(value = "projectAbbreviation", required = false) String projectAbbreviation, @RequestParam(value = "code", required = false) String code) { + return R.data(ocrConvertService.recognitionTransportCertificate(new FileCertificateBatchRecognitionDTO(objectKeys, projectAbbreviation, code, null))); + } + + @ApiLog("OCR识别-识别车辆运输凭证(知道类型)") + @Operation(summary = "识别车辆运输凭证,知道类型", description = "参数:objectKeylist") + @PostMapping("/recognitionTransportCertificatesByType") + public R recognitionTransportCertificatesByType(@RequestBody CertificateAttachmentDTO param, @RequestParam(value = "code", required = false) String code) { + CertificateRecognitionDTO recognitionParam = new CertificateRecognitionDTO(); + recognitionParam.setPoundUrl(fileService.getFileUrl(param.getPoundObjectKey(), null)); + recognitionParam.setCarFrontUrl(fileService.getFileUrl(param.getCarFrontObjectKey(), null)); + recognitionParam.setDriverLicenseUrl(fileService.getFileUrl(param.getDriverLicenseObjectKey(), null)); + recognitionParam.setVehicleLicenseUrl(fileService.getFileUrl(param.getVehicleLicenseObjectKey(), null)); + recognitionParam.setName(param.getProjectAbbreviation()); + return R.data(ocrService.recognitionTransportCertificate(recognitionParam, code)); + } + + @Operation(hidden = true) + @PostMapping("/publishEvent") + public R publishEvent(String code, Long id) { + ApplicationEvent event = FileEvent.createEvent(code, id); + if (event == null) { + return R.fail("事件名称错误"); + } + applicationContext.publishEvent(event); + return R.status(true); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java new file mode 100644 index 0000000..071625d --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java @@ -0,0 +1,85 @@ +package org.springblade.file.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.log.annotation.ApiLog; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.dto.FileTaskUpdateDTO; +import org.springblade.file.pojo.dto.IdsDTO; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; +import org.springblade.file.service.IFileTaskService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @ClassName: FileTaskController + * @Author: zhaowei + * @Date: 2024/8/28 + */ +@Valid +@NonDS +@RestController +@AllArgsConstructor +@RequestMapping("/fileTask") +@Tag(name = "文件任务", description = "文件任务") +public class FileTaskController extends BladeController { + + private final IFileTaskService fileTaskService; + + @GetMapping("/queryFileTask") + @Operation(summary = "根据文件的MD5值查询文件任务", description = "文件的MD5值检查分段,参数名:md5") + public R queryFileTask(@RequestParam(value = "md5") String md5) { + FileTaskUpdateVO fileTask = fileTaskService.queryFileTask(null, md5); + return R.data(fileTask); + } + + @ApiLog("上传任务-创建文件任务") + @PostMapping("/createFileTask") + @Operation(summary = "创建文件任务", description = "创建文件任务") + public R createFileTask(@RequestBody FileTaskCreateDTO param) { + Long id = fileTaskService.createFileTask(param); + return R.data(fileTaskService.queryFileTask(id, null)); + } + + @ApiLog("上传任务-更新文件任务进度") + @PostMapping("/updateFileTask") + @Operation(summary = "更新文件任务进度", description = "更新文件任务进度") + public R updateFileTask(@RequestBody FileTaskUpdateDTO param) { + return R.data(fileTaskService.updateFileTask(param)); + } + + @ApiLog("上传任务-修改文件任务状态为失败") + @PostMapping("/updateFailed") + @Operation(summary = "更新文件任务为失败", description = "更新文件任务为失败") + public R updateFailed(@NotNull(message = "文件任务id不能为空") Long id) { + return R.data(fileTaskService.updateFailed(id)); + } + + @ApiLog("上传任务-暂停文件任务") + @PostMapping("/updatePaused") + @Operation(summary = "更新文件任务为暂停", description = "更新文件任务为暂停") + public R updatePaused(@Validated @RequestBody IdsDTO param) { + return R.status(fileTaskService.updatePaused(param.getIds())); + } + + @GetMapping("/getUploadUrl") + @Parameters({ + @Parameter(name = "id", description = "文件任务id", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "partNumber", description = "分段编号", in = ParameterIn.QUERY, schema = @Schema(type = "int")) + }) + @Operation(summary = "根据文件任务id查询上传链接", description = "根据文件任务id查询上传链接") + public R getUploadUrl(Long id, Integer partNumber) { + String uploadUrl = fileTaskService.getUploadUrl(id, partNumber); + return R.data(uploadUrl); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/convert/BusinessAttachmentConvert.java b/blade-service/blade-file/src/main/java/org/springblade/file/convert/BusinessAttachmentConvert.java new file mode 100644 index 0000000..6a81f10 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/convert/BusinessAttachmentConvert.java @@ -0,0 +1,44 @@ +package org.springblade.file.convert; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.AttachmentDetailVO; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author bfhuange + * @date 2024/9/4 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface BusinessAttachmentConvert { + BusinessAttachment dto2entity(BusinessAttachmentAddDTO dto); + + List dtos2entities(List dtos); + + AttachmentDetailVO entity2vo(Attachment entity); + + default List dto2entities(BusinessAttachmentBatchAddDTO dto) { + if (dto == null || dto.getDetails() == null) { + return Collections.emptyList(); + } + AtomicInteger sort = new AtomicInteger(1); + return dto.getDetails().stream() + .flatMap(detail -> detail.getAttachmentIds().stream().map(attachmentId -> { + BusinessAttachment businessAttachment = new BusinessAttachment(); + businessAttachment.setAttachmentId(attachmentId); + businessAttachment.setBizId(dto.getBizId()); + businessAttachment.setBizCode(detail.getBizCode()); + businessAttachment.setSort(sort.getAndIncrement()); + return businessAttachment; + })).toList(); + } + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/convert/FileTaskConvert.java b/blade-service/blade-file/src/main/java/org/springblade/file/convert/FileTaskConvert.java new file mode 100644 index 0000000..f966920 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/convert/FileTaskConvert.java @@ -0,0 +1,31 @@ +package org.springblade.file.convert; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.entity.FileTaskPart; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; +import org.springblade.file.pojo.vo.FileTaskVO; +import org.springblade.file.pojo.vo.PartEtagVO; + +/** + * @author bfhuange + * @date 2024/10/22 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface FileTaskConvert { + + FileTaskVO entity2vo(FileTask entity); + + FileTaskUpdateVO entity2updateVo(FileTask entity); + + FileTask dto2entity(FileTaskCreateDTO dto); + + PartEtagVO entity2vo(FileTaskPart entity); + + BusinessUnzipTask dto2entity(BusinessUnzipTaskAddDTO dto); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/convert/OCRConverter.java b/blade-service/blade-file/src/main/java/org/springblade/file/convert/OCRConverter.java new file mode 100644 index 0000000..bd551bb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/convert/OCRConverter.java @@ -0,0 +1,17 @@ +package org.springblade.file.convert; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.dto.CertificateBatchRecognitionDTO; + +/** + * @author bfhuange + * @since 2025/3/13 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface OCRConverter { + + CertificateBatchRecognitionDTO dto2ocr(FileCertificateBatchRecognitionDTO dto); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/exception/UnzipFailException.java b/blade-service/blade-file/src/main/java/org/springblade/file/exception/UnzipFailException.java new file mode 100644 index 0000000..739c41e --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/exception/UnzipFailException.java @@ -0,0 +1,28 @@ +package org.springblade.file.exception; + +import lombok.Getter; + +import java.io.File; + +/** + * 文件解压失败异常 + * @author bfhuange + * @date 2024/11/11 + */ +@Getter +public class UnzipFailException extends RuntimeException{ + /** + * 解压目录 + */ + private final File unzipDir; + + public UnzipFailException(Throwable cause, File unzipDir) { + super(cause); + this.unzipDir = unzipDir; + } + + public UnzipFailException(String message, File unzipDir) { + super(message); + this.unzipDir = unzipDir; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/feign/FileClient.java b/blade-service/blade-file/src/main/java/org/springblade/file/feign/FileClient.java new file mode 100644 index 0000000..05e7f86 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/feign/FileClient.java @@ -0,0 +1,157 @@ +package org.springblade.file.feign; + +import com.alibaba.csp.sentinel.annotation.SentinelResource; +import io.swagger.v3.oas.annotations.Hidden; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.utils.AssertUtils; +import org.springblade.core.tool.api.FR; +import org.springblade.core.tool.api.R; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.pojo.dto.*; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.*; +import org.springblade.file.service.*; +import org.springblade.file.util.ObjectKeyUtils; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@Slf4j +@Valid +@Hidden +@RestController +@AllArgsConstructor +public class FileClient implements IFileClient, IFileUploadClient { + + private final IAttachmentService attachmentService; + private final IBusinessAttachmentService businessAttachmentService; + private final IBusinessUnzipTaskService businessUnzipTaskService; + private final IOCRConvertService ocrConvertService; + private final IFileService fileService; + private final ObsProperties obsProperties; + + @Override + @GetMapping(ATTACHMENT) + public FR> getAttachment(String id) { + return FR.data(attachmentService.getAttachment(id)); + } + + @Override + public FR getAttachmentDetail(Long id) { + return FR.data(attachmentService.getDetail(id)); + } + + @Override + public FR saveAndRemoveBusinessAttachment(@RequestBody @Validated BusinessAttachmentBatchAddDTO param) { + return FR.status(businessAttachmentService.saveAndRemove(param)); + } + + @Override + public FR batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @RequestParam(value = "bizId", required = false) Long bizId) { + return FR.status(businessAttachmentService.saveAndRemove(params, new BusinessAttachmentDeleteDTO(bizId))); + } + + @Override + public FR batchSaveAndRemoveByParamBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @Valid BusinessAttachmentDeleteDTO deleteParam) { + return FR.status(businessAttachmentService.saveAndRemove(params, deleteParam)); + } + + @Override + public FR batchSaveAndRemoveBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params) { + return FR.status(businessAttachmentService.saveAndRemove(params, null)); + } + + @Override + public FR removeBusinessAttachment(@NotNull(message = "业务id不能为空") @RequestParam(value = "bizId", required = false) Long bizId) { + businessAttachmentService.removeByBizIds(List.of(bizId)); + return FR.data(true); + } + + @Override + public FR> getBusinessAttachments(@Validated @RequestBody BusinessAttachmentQueryDTO param) { + return FR.data(businessAttachmentService.queryList(param)); + } + + @Override + public FR saveBusinessUnzipTask(BusinessUnzipTaskAddDTO param) { + return FR.status(businessUnzipTaskService.save(param)); + } + + @Override + public FR updateBusinessUnzipTask(BusinessUnzipTaskUpdateDTO param) { + AssertUtils.notNull(param.getBizId(), "业务id不能为空"); + return FR.status(businessUnzipTaskService.updateReupload(param)); + } + + @Override + public FR cancelBusinessUnzipTask(@NotNull(message = "业务id不能为空") Long bizId) { + return FR.status(businessUnzipTaskService.cancelBusinessUnzipTask(bizId, true)); + } + + @Override + public FR removeBusinessUnzipTask(Long bizId) { + return FR.status(businessUnzipTaskService.cancelBusinessUnzipTask(bizId, false)); + } + + @Override + public FR> getBusinessUnzipTasksByBizIds(List bizIds) { + return FR.data(businessUnzipTaskService.getBusinessUnzipTasksByBizIds(bizIds)); + } + + @Override + public FR> queryBusinessUnzipDetail(@NotNull(message = "业务id不能为空") Long bizId) { + return FR.data(businessUnzipTaskService.queryBusinessUnzipDetail(bizId)); + } + + @SentinelResource("ocr:batchCards") + @Override + public FR recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param) { + return FR.data(ocrConvertService.recognitionTransportCertificate(param)); + } + + @Override + public FR> getFileUrls(IdsDTO param) { + List list = attachmentService.listByIds(param.getIds()).stream() + .map(attachment -> { + String fileUrl = fileService.getFileUrl(attachment.getObjectKey(), attachment.getAttachmentName(), null); + return new FileUrlVO(attachment.getId(), fileUrl); + }) + .toList(); + return FR.data(list); + } + + @Override + public FR> ossUpload(MultipartFile[] files) { + R> r = attachmentService.batchOssUpload(files, null); + return R.isNotSuccess(r) ? FR.fail(r.getCode(), r.getMsg()) : FR.data(r.getData(), r.getMsg()); + } + + @Override + public FR ossUploadTemp(MultipartFile file, Integer expires) { + if (expires == null) { + // 默认1天过期 + expires = 1; + } + try { + String objectKey = ObjectKeyUtils.generateObjectKey(obsProperties.getTmpUploadDir(), file.getOriginalFilename()); + String fileUrl = fileService.uploadTempFile(objectKey, file.getInputStream(), expires); + return FR.data(new TempFileUrlVO(objectKey, fileUrl)); + } catch (Exception e) { + log.error("上传临时文件异常", e); + return FR.fail("上传临时文件异常"); + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileEvent.java new file mode 100644 index 0000000..78c9a3a --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileEvent.java @@ -0,0 +1,56 @@ +package org.springblade.file.listener; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * 文件事件枚举 + * @author bfhuange + * @since 2024/11/22 + */ +@AllArgsConstructor +@Getter +public enum FileEvent { + /** + * 上传完成事件 + */ + UPLOADED(FileUploadedEvent::new), + /** + * 解压开始事件 + */ + UNZIP_START(UnzipStartEvent::new), + /** + * 解压完成事件 + */ + UNZIP_COMPLETED(UnzipCompletedEvent::new), + /** + * 解压失败事件 + */ + UNZIP_FAILED(UnzipFailEvent::new), + ; + /** + * 事件函数 + */ + private final Function eventFunction; + + /** + * 根据编码创建事件 + * @param code + * @param id + * @return + */ + public static ApplicationEvent createEvent(String code, Long id) { + if (code == null || code.isBlank()) { + return null; + } + return Stream.of(values()) + .filter(event -> event.name().equalsIgnoreCase(code)) + .findFirst() + .map(event -> event.getEventFunction().apply(id)) + .orElse(null); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileListener.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileListener.java new file mode 100644 index 0000000..7ca5a0f --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileListener.java @@ -0,0 +1,173 @@ +package org.springblade.file.listener; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.log.utils.AssertUtils; +import org.springblade.file.callback.UnzipCallback; +import org.springblade.file.callback.UnzipEventType; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.enums.UnzipTask; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IBusinessUnzipDetailService; +import org.springblade.file.service.IBusinessUnzipTaskService; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 文件监听器 + * @author bfhuange + * @date 2024/11/8 + */ +@Slf4j +@Component +public class FileListener { + + private final IBusinessUnzipTaskService taskService; + private final IBusinessUnzipDetailService detailService; + private final Map callbackMap; + + public FileListener(IBusinessUnzipTaskService taskService, IBusinessUnzipDetailService detailService, List callbacks) { + this.taskService = taskService; + this.detailService = detailService; + this.callbackMap = callbacks.stream() + .collect(Collectors.toMap(UnzipCallback::getBizCode, Function.identity(), (a, b) -> { + throw new ServiceException("重复的解压完成回调接口"); + })); + } + + /** + * 文件上传完成监听器 + * @param event + */ + @Async + @TransactionalEventListener(value = FileUploadedEvent.class, phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) + public void fileUploaded(FileUploadedEvent event) { + try { + Long fileTaskId = event.getFileTaskId(); + log.info("文件上传任务完成事件 id:{}", fileTaskId); + BusinessUnzipTask businessUnzipTask = taskService.getOne(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getRelateId, fileTaskId) + .last("limit 1") + ); + if (businessUnzipTask != null) { + // 存在解压任务,更新任务为上传完成 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(businessUnzipTask.getId()); + updateParam.setStatus(UnzipTask.Status.UPLOADED.getCode()); + taskService.updateById(updateParam); + // 解压开始 + this.unzipStart(new UnzipStartEvent(businessUnzipTask)); + } + } catch (Exception e) { + log.error("文件上传任务完成事件 异常", e); + } + } + + /** + * 解压开始 + * @param event + */ + @Async + @TransactionalEventListener(value = UnzipStartEvent.class, phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) + public void unzipStart(UnzipStartEvent event) { + try { + Long unzipTaskId = event.getUnzipTaskId(); + BusinessUnzipTask task = event.getTask(); + log.info("解压开始事件 id:{} 业务解压任务:{}", unzipTaskId, JSON.toJSONString(task)); + if (task == null) { + // 手动触发时,只有id + task = taskService.getById(unzipTaskId); + } + // 回调接口 + callback(task, UnzipEventType.START); + // 解压并保存 + detailService.unzipAndSaveAsync(task.getId(), taskService.getFile(task)); + } catch (Exception e) { + log.error("解压开始事件 异常", e); + } + } + + /** + * 解压完成监听器 + * @param event + */ + @Async + @EventListener(UnzipCompletedEvent.class) + public void unzipCompleted(UnzipCompletedEvent event) { + try { + Long unzipTaskId = event.getUnzipTaskId(); + log.info("解压完成事件 id:{}", unzipTaskId); + // 查询解压任务 + BusinessUnzipTask unzipTask = taskService.getById(unzipTaskId); + AssertUtils.notNull(unzipTask, "解压任务不存在"); + // 更新解压任务状态 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(unzipTaskId); + updateParam.setStatus(UnzipTask.Status.FINISHED.getCode()); + taskService.updateById(updateParam); + // 回调接口 + callback(unzipTask, UnzipEventType.SUCCESS); + } catch (Exception e) { + log.error("解压完成事件 异常", e); + } + } + + /** + * 解压失败监听器 + * @param event + */ + @Async + @EventListener(UnzipFailEvent.class) + public void unzipFail(UnzipFailEvent event) { + try { + Long unzipTaskId = event.getUnzipTaskId(); + log.info("解压失败事件 id:{}", unzipTaskId); + // 查询解压任务 + BusinessUnzipTask unzipTask = taskService.getById(unzipTaskId); + AssertUtils.notNull(unzipTask, "解压任务不存在"); + // 更新解压任务状态 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(unzipTaskId); + updateParam.setStatus(UnzipTask.Status.UNZIP_FAIL.getCode()); + taskService.updateById(updateParam); + // 回调接口 + callback(unzipTask, UnzipEventType.FAIL); + } catch (Exception e) { + log.error("解压失败事件 异常", e); + } + } + + /** + * 回调接口 + * @param unzipTask + * @param event + */ + private void callback(BusinessUnzipTask unzipTask, UnzipEventType event) { + if (unzipTask == null) { + log.warn("解压回调接口 解压任务为空"); + return; + } + UnzipCallback unzipCallback = callbackMap.get(unzipTask.getBizCode()); + if (unzipCallback == null) { + log.error("未配置业务编码:{}对应的解压回调接口", unzipTask.getBizCode()); + return; + } + Boolean result = null; + try { + result = unzipCallback.callback(unzipTask.getBizId(), event.name()); + } catch (Exception e) { + log.error("解压回调接口异常", e); + } + log.info("解压回调接口参数:{} 业务编码:{} 事件:{} 结果:{}", unzipTask.getBizId(), unzipTask.getBizCode(), event.name(), result); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileUploadedEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileUploadedEvent.java new file mode 100644 index 0000000..459193e --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileUploadedEvent.java @@ -0,0 +1,21 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class FileUploadedEvent extends ApplicationEvent { + /** + * 文件任务id + */ + private final Long fileTaskId; + + public FileUploadedEvent(Long fileTaskId) { + super(fileTaskId); + this.fileTaskId = fileTaskId; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipCompletedEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipCompletedEvent.java new file mode 100644 index 0000000..32a60ad --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipCompletedEvent.java @@ -0,0 +1,22 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * 解压完成事件 + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class UnzipCompletedEvent extends ApplicationEvent { + /** + * 解压任务id + */ + private final Long unzipTaskId; + + public UnzipCompletedEvent(Long unzipTaskId) { + super(unzipTaskId); + this.unzipTaskId = unzipTaskId; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipFailEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipFailEvent.java new file mode 100644 index 0000000..771d464 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipFailEvent.java @@ -0,0 +1,22 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * 解压失败事件 + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class UnzipFailEvent extends ApplicationEvent { + /** + * 解压任务id + */ + private final Long unzipTaskId; + + public UnzipFailEvent(Long unzipTaskId) { + super(unzipTaskId); + this.unzipTaskId = unzipTaskId; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipStartEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipStartEvent.java new file mode 100644 index 0000000..a317fc6 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipStartEvent.java @@ -0,0 +1,35 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.vo.FileVO; +import org.springframework.context.ApplicationEvent; + +/** + * 解压开始事件 + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class UnzipStartEvent extends ApplicationEvent { + /** + * 解压任务id + */ + private final Long unzipTaskId; + /** + * 解压任务 + */ + private final BusinessUnzipTask task; + + public UnzipStartEvent(Long unzipTaskId) { + super(unzipTaskId); + this.unzipTaskId = unzipTaskId; + this.task = null; + } + + public UnzipStartEvent(BusinessUnzipTask task) { + super(task); + this.task = task; + this.unzipTaskId = null; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java new file mode 100644 index 0000000..c607e20 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java @@ -0,0 +1,37 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.entity.Attachment; + +/** + * AttachmentMapper 接口 + * + * @author zhaowei + */ +public interface AttachmentMapper extends BaseMapper { +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml new file mode 100644 index 0000000..d5bc4eb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.java new file mode 100644 index 0000000..65b5af0 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.java @@ -0,0 +1,24 @@ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; + +import java.util.List; + +/** + * 业务附件信息表 Mapper 接口 + * + * @author BladeX + * @since 2024-09-04 + */ +public interface BusinessAttachmentMapper extends BaseMapper { + + /** + * 查询业务附件列表 + * @param param + * @return + */ + List queryList(BusinessAttachmentQueryDTO param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.xml new file mode 100644 index 0000000..0d3b4fd --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.java new file mode 100644 index 0000000..8c284af --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.java @@ -0,0 +1,16 @@ +package org.springblade.file.mapper; + +import org.springblade.file.pojo.entity.BusinessUnzipDetail; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 业务解压明细表 Mapper 接口 + *

+ * + * @author bfhuange + * @since 2024-10-30 + */ +public interface BusinessUnzipDetailMapper extends BaseMapper { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.xml new file mode 100644 index 0000000..ff49b0c --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + id, business_unzip_task_id, attachment_id, create_user, create_time, update_user, update_time, is_deleted + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.java new file mode 100644 index 0000000..1b0ddfe --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.java @@ -0,0 +1,37 @@ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.apache.ibatis.annotations.Param; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; + +import java.util.List; + +/** + *

+ * 业务解压任务表 Mapper 接口 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +public interface BusinessUnzipTaskMapper extends BaseMapper { + + /** + * 查询列表 + * @param page + * @param param + * @return + */ + List selectTaskList(IPage page, @Param("param") BusinessUnzipTaskQueryDTO param); + + /** + * 根据业务id查询解压明细 + * @param bizId + * @return + */ + List queryBusinessUnzipDetail(@Param("bizId") Long bizId); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.xml new file mode 100644 index 0000000..3fb6be6 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, biz_id, department_id, biz_code, doc_code, relate_type, relate_id, status, tenant_id, create_user, create_time, update_user, update_time, is_deleted + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.java new file mode 100644 index 0000000..aa70585 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.java @@ -0,0 +1,38 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.entity.FileTask; + +/** + * FileTaskMapper 接口 + * + * @author zhaowei + */ +public interface FileTaskMapper extends BaseMapper { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.xml new file mode 100644 index 0000000..3a983c3 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.java new file mode 100644 index 0000000..fc3e278 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.java @@ -0,0 +1,38 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.entity.FileTaskPart; + +/** + * FileUploadRecordMapper 接口 + * + * @author zhaowei + */ +public interface FileTaskPartMapper extends BaseMapper { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.xml new file mode 100644 index 0000000..924e84b --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IAttachmentService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IAttachmentService.java new file mode 100644 index 0000000..0a54e01 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IAttachmentService.java @@ -0,0 +1,89 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.core.tool.api.R; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.AttachmentDetailVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.util.List; + +/** + * 服务类 + * + * @author zhaowei + */ +public interface IAttachmentService extends IService { + + /** + * 批量上传 + * + * @param files + * @param fileName 文件名,如果 files只有1个,且fileName不为空,设置文件名为 fileName + * @return + */ + R> batchOssUpload(MultipartFile[] files, String fileName); + + /** + * 获取附件,多个附件id用逗号分割 + * @param id + * @return List + */ + List getAttachment(String id); + + /** + * 批量上传解压缩文件并删除文件 + * + * @param files + * @param workDir 工作目录,取文件相对于工作目录的相对路径作为前缀 + * @return + */ + List batchOssUploadUnzipFilesAndDelete(List files, File workDir); + + /** + * 新事务批量保存 + * @param list + */ + void batchSaveWithNewTransaction(List list); + + /** + * 获取附件详情 + * @param id + * @return + */ + AttachmentDetailVO getDetail(Long id); + + /** + * 解压文件并上传 + * @param file + * @return + */ + List unzipAndUpload(FileVO file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessAttachmentService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessAttachmentService.java new file mode 100644 index 0000000..9b46ba8 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessAttachmentService.java @@ -0,0 +1,51 @@ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentDeleteDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; + +import java.util.List; + +/** + * 业务附件信息表 服务类 + * + * @author BladeX + * @since 2024-09-04 + */ +public interface IBusinessAttachmentService extends IService { + /** + * 保存 + * @param param + * @return + */ + boolean saveAndRemove(BusinessAttachmentBatchAddDTO param); + + /** + * 保存并删除 + * + * @param params + * @param deleteParam 业务id不为空,则先删除 + * @return + */ + boolean saveAndRemove(List params, BusinessAttachmentDeleteDTO deleteParam); + + /** + * 根据bizId列表删除 + * + * @param bizIds + * @return + */ + boolean removeByBizIds(List bizIds); + + /** + * 查询业务附件列表 + * + * @param param + * @return + */ + List queryList(BusinessAttachmentQueryDTO param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipDetailService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipDetailService.java new file mode 100644 index 0000000..eda034a --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipDetailService.java @@ -0,0 +1,23 @@ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.entity.BusinessUnzipDetail; +import org.springblade.file.pojo.vo.FileVO; + +/** + *

+ * 业务解压明细表 服务类 + *

+ * + * @author bfhuange + * @since 2024-10-30 + */ +public interface IBusinessUnzipDetailService extends IService { + + /** + * 异步解压并保存解压明细 + * @param unzipTaskId 解压任务id + * @param file obs对象存储的key合md5值,md5有可能为空 + */ + void unzipAndSaveAsync(Long unzipTaskId, FileVO file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipTaskService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipTaskService.java new file mode 100644 index 0000000..56f7531 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipTaskService.java @@ -0,0 +1,82 @@ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; +import org.springblade.file.pojo.vo.FileVO; + +import java.util.List; + +/** + *

+ * 业务解压任务表 服务类 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +public interface IBusinessUnzipTaskService extends IService { + + /** + * 保存业务解压任务 + * @param param + * @return + */ + boolean save(BusinessUnzipTaskAddDTO param); + + /** + * 获取obs对象存储的key + * @param task + * @return + */ + String getObjectKey(BusinessUnzipTask task); + + /** + * 获取文件对象 + * @param task + * @return + */ + FileVO getFile(BusinessUnzipTask task); + + /** + * 业务解压任务分页查询 + * @param page + * @param param + * @return + */ + IPage selectPage(IPage page, BusinessUnzipTaskQueryDTO param); + + /** + * 业务文件解压任务更新(重新上传) + * @param param + * @return + */ + boolean updateReupload(BusinessUnzipTaskUpdateDTO param); + + /** + * 根据业务id删除业务文件解压任务 + * @param bizId + * @param cancelFile 是否取消文件,如果取消,上传完成后取消会报错 + * @return + */ + boolean cancelBusinessUnzipTask(Long bizId, boolean cancelFile); + + /** + * 根据业务id查询解压明细 + * @param bizId + * @return + */ + List queryBusinessUnzipDetail(Long bizId); + + /** + * 根据业务id列表查询解压任务列表 + * @param bizIds + * @return + */ + List getBusinessUnzipTasksByBizIds(List bizIds); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileHandleService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileHandleService.java new file mode 100644 index 0000000..bffbbee --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileHandleService.java @@ -0,0 +1,20 @@ +package org.springblade.file.service; + +import org.springblade.file.pojo.vo.FileHandleVO; +import org.springblade.file.pojo.vo.FileVO; + +/** + * 文件处理 + * @author bfhuange + * @date 2024/10/22 + */ +public interface IFileHandleService { + + /** + * 下载文件并解压 + * + * @param file obs对象存储的key合md5,md5可能为空 + * @return + */ + FileHandleVO downloadAndUnzip(FileVO file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileService.java new file mode 100644 index 0000000..f021fdf --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileService.java @@ -0,0 +1,122 @@ +package org.springblade.file.service; + +import org.springblade.file.pojo.vo.PartEtagVO; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +/** + * 文件接口 + * @author bfhuange + * @date 2024/10/21 + */ +public interface IFileService { + + /** + * 上传文件 + * @param objectKey 文件key + * @param is 输入流 + */ + void uploadFile(String objectKey, InputStream is); + + /** + * 上传临时文件 + * @param objectKey 文件key + * @param is 输入流 + * @param expires 过期时间,单位天,过期后自动删除 + */ + String uploadTempFile(String objectKey, InputStream is, Integer expires); + + /** + * 上传文件 + * @param objectKey 文件key + * @param file 文件 + */ + void uploadFile(String objectKey, File file); + + /** + * 初始化分片上传 + * + * @param objectKey 文件key + * @return 上传任务id + */ + String initiateMultipartUpload(String objectKey); + + /** + * 分段上传 + * @param objectKey 文件key + * @param uploadId 上传任务id + * @param file 文件 + * @param partNumber 分段编号 + * @param offset 偏移位 + * @param partSize 分段大小 + * @return + */ + PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize); + + /** + * 完成分段上传任务 + * @param objectKey 文件key + * @param uploadId 上传任务id + * @param partEtagVOS 要合并的分段 + */ + void completeMultipartUpload(String objectKey, String uploadId, List partEtagVOS); + + /** + * 取消分段上传任务 + * @param objectKey + * @param uploadId + */ + void cancelMultipartUpload(String objectKey, String uploadId); + + /** + * 获取文件链接 + * @param objectKey 文件key + * @param expires 过期时间,单位秒 + * @return + */ + String getFileUrl(String objectKey, Long expires); + + /** + * 获取文件链接 + * @param objectKey 文件key + * @param fileName 文件名 + * @param expires 过期时间,单位秒 + * @return + */ + String getFileUrl(String objectKey, String fileName, Long expires); + + /** + * 获取文件上传链接 + * + * @param objectKey 文件key + * @param uploadId + * @param partNumber + * @param expires 过期时间,单位秒 + * @return + */ + String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires); + + /** + * 获取文件链接批量 + * @param objectKeys + * @param expires 过期时间 + * @return + */ + List getFileUrls(List objectKeys, Long expires); + + /** + * 下载文件 + * @param objectKey + * @param downloadFile 下载路径 + */ + void downloadFile(String objectKey, String downloadFile); + + /** + * 获取文件md5 + * @param file + * @return + */ + String getFileMd5(File file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskPartService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskPartService.java new file mode 100644 index 0000000..844aa44 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskPartService.java @@ -0,0 +1,38 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.entity.FileTaskPart; + +/** + * 服务类 + * + * @author zhaowei + */ +public interface IFileTaskPartService extends IService { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java new file mode 100644 index 0000000..3a4a992 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java @@ -0,0 +1,94 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.dto.FileTaskUpdateDTO; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; + +import java.util.List; + +/** + * 服务类 + * + * @author zhaowei + */ +public interface IFileTaskService extends IService { + + /** + * 根据文件md5值查询最新一条上传任务记录 + * + * @param id + * @param md5 + * @return + */ + FileTaskUpdateVO queryFileTask(Long id, String md5); + + /** + * 创建文件任务 + * @param param + * @return + */ + Long createFileTask(FileTaskCreateDTO param); + + /** + * 更新文件任务进度 + * @param param + * @return + */ + FileTaskUpdateVO updateFileTask(FileTaskUpdateDTO param); + + /** + * 根据文件任务id获取上传链接 + * + * @param id + * @param partNumber + * @return + */ + String getUploadUrl(Long id, Integer partNumber); + + /** + * 取消文件任务 + * @param taskIds + */ + void cancelBatchByIds(List taskIds); + + /** + * 更新文件任务状态为失败 + * @param id + * @return + */ + FileTaskUpdateVO updateFailed(Long id); + + /** + * 根据id列表更新文件任务状态为暂停 + * @param param + * @return + */ + boolean updatePaused(List param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IOCRConvertService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IOCRConvertService.java new file mode 100644 index 0000000..6e6784c --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IOCRConvertService.java @@ -0,0 +1,19 @@ +package org.springblade.file.service; + +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; + +/** + * ocr转换 + * @author bfhuange + * @since 2025/3/13 + */ +public interface IOCRConvertService { + + /** + * 凭证批量识别,不分类 + * @param param + * @return + */ + TransportCertificateVO recognitionTransportCertificate(FileCertificateBatchRecognitionDTO param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java new file mode 100644 index 0000000..67bd5fb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java @@ -0,0 +1,255 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.SpringUtil; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.convert.BusinessAttachmentConvert; +import org.springblade.file.exception.UnzipFailException; +import org.springblade.file.mapper.AttachmentMapper; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.AttachmentDetailVO; +import org.springblade.file.pojo.vo.FileHandleVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IFileHandleService; +import org.springblade.file.service.IFileService; +import org.springblade.file.util.ObjectKeyUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 服务实现类 + * @author zhaowei + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class AttachmentServiceImpl extends ServiceImpl implements IAttachmentService { + + private final IFileService fileService; + private final BusinessAttachmentConvert convert; + private final ObsProperties obsProperties; + private final IFileHandleService fileHandleService; + private IAttachmentService self; + + @Override + public R> batchOssUpload(MultipartFile[] files, String overWriteFileName){ + try { + List attachmentList = new ArrayList<>(); + // 文件数量为1个,且重写的文件名不为空,使用重写的文件名,给uniapp上传使用,uniapp上传的文件名不是原始文件名 + boolean useOverWriteFileName = files.length == 1 && StringUtil.isNotBlank(overWriteFileName); + for(MultipartFile file : files){ + String fileName = useOverWriteFileName ? overWriteFileName : file.getOriginalFilename(); + String objectkey = ObjectKeyUtils.generateObjectKey(fileName); + fileService.uploadFile(objectkey, file.getInputStream()); + // 插入附件数据 + Attachment attachment = new Attachment(); + Long id = IdWorker.getId(); + attachment.setId(id); + attachment.setAttachmentName(fileName); + attachment.setSize(file.getSize()); + attachment.setObjectKey(objectkey); + attachmentList.add(attachment); + } + this.saveBatch(attachmentList); + log.info("OBS上传 successfully"); + return R.data(attachmentList,"OBS上传 successfully"); + } catch (Exception e) { + log.error("OBS上传 failed,Exception", e); + } + return R.fail("OBS上传 failed"); + } + + @Override + public List getAttachment(String id) { + return this.list(new LambdaQueryWrapper() + .in(Attachment::getId, Arrays.stream(id.split(",")).toList()) + ); + } + + @Override + public List batchOssUploadUnzipFilesAndDelete(List files, File workDir) { + if (CollectionUtil.isEmpty(files)) { + return Collections.emptyList(); + } + List attachmentList = new ArrayList<>(); + AtomicInteger index = new AtomicInteger(1); + try { + log.info("上传文件总数:{}", files.size()); + for(File file : files){ + String fileName = file.getName(); + // 压缩文件所在压缩包父目录相对路径 + String dir = this.getDir(workDir, file); + // 获取上传的目录前缀 + String prefixDir = getUploadPrefix(); + String objectkey = prefixDir + ObjectKeyUtils.generateObjectKey(fileName); + fileService.uploadFile(objectkey, file); + int completeFiles = index.getAndIncrement(); + if (completeFiles % 10 == 0) { + log.info("上传文件总进度:{}/{}", completeFiles, files.size()); + } + // 插入附件数据 + Attachment attachment = new Attachment(); + Long id = IdWorker.getId(); + attachment.setId(id); + attachment.setDir(dir); + attachment.setAttachmentName(fileName); + attachment.setSize(file.length()); + attachment.setObjectKey(objectkey); + attachmentList.add(attachment); + } + log.info("上传文件完成:{}", files.size()); + // 新事务批量保存 + this.getSelf().batchSaveWithNewTransaction(attachmentList); + log.info("OBS上传 successfully"); + } catch (Exception e) { + log.error("OBS上传失败", e); + throw new ServiceException("OBS上传失败"); + } + // 返回新增的附件列表 + return attachmentList; + } + + /** + * 获取上传文件目录前缀 + * @return + */ + private String getUploadPrefix() { + // 上传路径前缀 = 配置的解压上传路径 + / + return obsProperties.getUnzipUploadDir() + StringPool.SLASH; + } + + /** + * 获取压缩文件所在压缩包父目录的相对路径,结尾不带/ + * @param workDir 工作目录,解压的根目录 + * @param file 解压后的文件 + * @return + */ + private String getDir(File workDir, File file) { + if (workDir == null) { + return StringPool.EMPTY; + } + Path workDirPath = workDir.toPath(); + Path filePath = file.toPath(); + return workDirPath.relativize(filePath).toString() + // 去掉文件名以及文件所在父目录的后缀/或\ + .replace(File.separator + file.getName(), "") + // 将文件分隔符替换为/,windows下是\,需要处理 + .replace(File.separator, StringPool.SLASH); + } + + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + @Override + public void batchSaveWithNewTransaction(List list) { + this.saveBatch(list); + } + + @Override + public AttachmentDetailVO getDetail(Long id) { + if (id == null) { + return null; + } + Attachment attachment = this.getById(id); + if (attachment == null) { + return null; + } + AttachmentDetailVO detailVO = convert.entity2vo(attachment); + // 查询下载链接 + String fileUrl = fileService.getFileUrl(attachment.getObjectKey(), null); + detailVO.setUrl(fileUrl); + return detailVO; + } + + @Override + public List unzipAndUpload(FileVO file) { + // 1. 下载并解压文件 + try { + FileHandleVO fileHandleVO = fileHandleService.downloadAndUnzip(file); + List unzippedFiles = fileHandleVO.getUnzippedFiles(); + if (CollectionUtil.isEmpty(unzippedFiles)) { + log.warn("下载解压文件为空 objectKey:{}", file.getObjectKey()); + throw new UnzipFailException("解压文件为空", fileHandleVO.getUnzippedDir()); + } + File unzippedDir = fileHandleVO.getUnzippedDir(); + // 2. 上传文件 + long start = System.currentTimeMillis(); + try { + List attachments = getSelf().batchOssUploadUnzipFilesAndDelete(unzippedFiles, unzippedDir); + if (CollectionUtil.isEmpty(attachments)) { + log.warn("上传解压文件为空 objectKey:{}", file.getObjectKey()); + return Collections.emptyList(); + } + // 删除下载的压缩文件 + fileHandleVO.getDownloadFile().delete(); + // 删除解压后的目录 + FileUtils.deleteQuietly(unzippedDir); + return attachments; + } finally { + long end = System.currentTimeMillis(); + log.info("文件:{} 解压后上传完成 耗时:{}", file.getObjectKey(), end - start); + } + } catch (UnzipFailException e) { + // 文件解压失败,删除解压后的文件 + File unzipDir = e.getUnzipDir(); + // 删除解压后的目录 + FileUtils.deleteQuietly(unzipDir); + throw e; + } + } + + /** + * 获取自己 + * @return + */ + private IAttachmentService getSelf() { + if (self == null) { + self = SpringUtil.getBean(IAttachmentService.class); + } + return self; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessAttachmentServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessAttachmentServiceImpl.java new file mode 100644 index 0000000..db199e7 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessAttachmentServiceImpl.java @@ -0,0 +1,136 @@ +package org.springblade.file.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.constant.BladeConstant; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.convert.BusinessAttachmentConvert; +import org.springblade.file.mapper.BusinessAttachmentMapper; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentDeleteDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; +import org.springblade.file.service.IBusinessAttachmentService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 业务附件信息表 服务实现类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class BusinessAttachmentServiceImpl extends ServiceImpl implements IBusinessAttachmentService { + + private final BusinessAttachmentConvert convert; + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean saveAndRemove(BusinessAttachmentBatchAddDTO param) { + if (param == null) { + log.warn("批量保存业务附件,参数为空"); + return false; + } + if (param.isRemove() && param.getBizId() != null) { + // 先删除 + this.removeByBizIds(List.of(param.getBizId())); + } + List addList = convert.dto2entities(param); + if (CollectionUtil.isNotEmpty(addList)) { + return this.saveBatch(addList); + } + return true; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean saveAndRemove(List params, BusinessAttachmentDeleteDTO deleteParam) { + boolean hasBizIds = deleteParam != null && deleteParam.isDelete() && CollectionUtil.isNotEmpty(deleteParam.getBizIds()); + if (hasBizIds) { + // bizIds不为空,先删除 + this.removeByBizIdsAndCodes(deleteParam.getBizIds(), deleteParam.getBizCodes()); + } + if (CollectionUtil.isEmpty(params)) { + log.warn("保存业务附件,参数为空"); + return true; + } + if (!hasBizIds && (deleteParam == null || deleteParam.isDelete())) { + // bizIds为空,从参数提取bizIds,再删除 + // 删除参数为空,默认删除,删除参数中delete为true默认删除,false不删除 + List bizIds = params.stream() + .map(BusinessAttachmentAddDTO::getBizId) + .distinct() + .toList(); + this.removeByBizIds(bizIds); + } + + List businessAttachments = convert.dtos2entities(params); + + // 按照业务id分组排序 + Map> bizIdAttachmentsMap = businessAttachments.stream() + .collect(Collectors.groupingBy(BusinessAttachment::getBizId)); + String tenantId = AuthUtil.getTenantId(); + for (Map.Entry> entry : bizIdAttachmentsMap.entrySet()) { + int i = 1; + for (BusinessAttachment businessAttachment : entry.getValue()) { + businessAttachment.setId(IdWorker.getId()); + // 处理排序 + businessAttachment.setSort(i++); + if (StringUtil.isBlank(tenantId)) { + businessAttachment.setTenantId(BladeConstant.ADMIN_TENANT_ID); + } + } + } + return this.saveBatch(businessAttachments); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean removeByBizIds(List bizIds) { + return this.removeByBizIdsAndCodes(bizIds, null); + } + + @Override + public List queryList(BusinessAttachmentQueryDTO param) { + return baseMapper.queryList(param); + } + + /** + * 批量删除 + * @param bizIds 业务id列表,为空,不删除 + * @param bizCodes 业务编码列表,为空,删除所有 + * @return + */ + private boolean removeByBizIdsAndCodes(List bizIds, List bizCodes) { + if (CollectionUtil.isEmpty(bizIds)) { + log.warn("删除附件,参数为空"); + return false; + } + List ids = this.list(Wrappers.lambdaQuery() + .in(BusinessAttachment::getBizId, bizIds) + // 业务编码列表不为空,添加业务编码查询条件 + .in(CollectionUtil.isNotEmpty(bizCodes), BusinessAttachment::getBizCode, bizCodes) + ).stream() + .map(BusinessAttachment::getId) + .toList(); + if (CollectionUtil.isNotEmpty(ids)) { + return this.removeBatchByIds(ids); + } + return true; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipDetailServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipDetailServiceImpl.java new file mode 100644 index 0000000..ce66261 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipDetailServiceImpl.java @@ -0,0 +1,71 @@ +package org.springblade.file.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.file.exception.UnzipFailException; +import org.springblade.file.listener.UnzipCompletedEvent; +import org.springblade.file.listener.UnzipFailEvent; +import org.springblade.file.mapper.BusinessUnzipDetailMapper; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.entity.BusinessUnzipDetail; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IBusinessUnzipDetailService; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/30 + */ +@AllArgsConstructor +@Slf4j +@Service +public class BusinessUnzipDetailServiceImpl extends ServiceImpl implements IBusinessUnzipDetailService { + + private final IAttachmentService attachmentService; + private final ApplicationContext applicationContext; + + @Async + @Override + public void unzipAndSaveAsync(Long unzipTaskId, FileVO file) { + log.info("解压文件并上传开始 unzipTaskId: {} file: {}", unzipTaskId, JSON.toJSONString(file)); + long start = System.currentTimeMillis(); + try { + // 1. 上传文件并解压 + List attachments = attachmentService.unzipAndUpload(file); + if (CollectionUtil.isEmpty(attachments)) { + log.warn("上传解压文件为空 unzipTaskId:{} objectKey:{}", unzipTaskId, file.getObjectKey()); + return; + } + // 2. 保存压缩文件和解压文件关系 + List addParams = attachments.stream() + .map(attachment -> { + BusinessUnzipDetail addParam = new BusinessUnzipDetail(); + addParam.setBusinessUnzipTaskId(unzipTaskId); + addParam.setAttachmentId(attachment.getId()); + return addParam; + }).toList(); + if (CollectionUtil.isNotEmpty(addParams)) { + this.saveBatch(addParams); + } + // 3. 发布解压完成事件 + applicationContext.publishEvent(new UnzipCompletedEvent(unzipTaskId)); + } catch (UnzipFailException e) { + log.error("解压文件异常", e); + // 发布解压失败事件 + applicationContext.publishEvent(new UnzipFailEvent(unzipTaskId)); + } catch (Exception e) { + log.error("解压文件并上传异常", e); + } finally { + long end = System.currentTimeMillis(); + log.info("解压文件并上传 完成 unzipTaskId: {} objectKey: {} 耗时:{}", unzipTaskId, file.getObjectKey(), end - start); + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipTaskServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipTaskServiceImpl.java new file mode 100644 index 0000000..6bee0d2 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipTaskServiceImpl.java @@ -0,0 +1,229 @@ +package org.springblade.file.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.common.constant.DictTypeEnum; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.log.utils.AssertUtils; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.convert.FileTaskConvert; +import org.springblade.file.listener.UnzipStartEvent; +import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.mapper.BusinessUnzipTaskMapper; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.enums.FileTaskStatus; +import org.springblade.file.pojo.enums.UnzipTask; +import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IBusinessUnzipDetailService; +import org.springblade.file.service.IBusinessUnzipTaskService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springblade.file.service.IFileTaskService; +import org.springblade.system.cache.DictCache; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; + +/** + *

+ * 业务解压任务表 服务实现类 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +@Slf4j +@AllArgsConstructor +@Service +public class BusinessUnzipTaskServiceImpl extends ServiceImpl implements IBusinessUnzipTaskService { + + private final FileTaskConvert convert; + private final IFileTaskService fileTaskService; + private final IBusinessUnzipDetailService detailService; + private final IAttachmentService attachmentService; + private final ApplicationContext applicationContext; + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean save(BusinessUnzipTaskAddDTO param) { + BusinessUnzipTask addParam = convert.dto2entity(param); + String status = getStatus(param.getRelateType(), param.getRelateId()); + addParam.setStatus(status); + boolean result = this.save(addParam); + if (result && UnzipTask.Status.UPLOADED.getCode().equals(status)) { + // 解压任务状态是上传完成,发布解压开始事件 + applicationContext.publishEvent(new UnzipStartEvent(addParam)); + } + return result; + } + + @Override + public String getObjectKey(BusinessUnzipTask task) { + return this.getFile(task).getObjectKey(); + } + + @Override + public FileVO getFile(BusinessUnzipTask task) { + AssertUtils.notNull(task, "文件任务不存在"); + String relateType = task.getRelateType(); + AssertUtils.notBlank(relateType, "文件任务关联类型不能为空"); + Long relateId = task.getRelateId(); + AssertUtils.notNull(relateId, "文件任务关联id不能为空"); + if (UnzipTask.RelateType.FILE_TASK.getCode().equals(relateType)) { + // 文件任务关联类型 + FileTask fileTask = fileTaskService.getById(relateId); + AssertUtils.notNull(fileTask, "文件任务不存在"); + return new FileVO(fileTask.getObjectKey(), fileTask.getMd5()); + } + if (UnzipTask.RelateType.ATTACHMENT.getCode().equals(relateType)) { + // 附件关联类型 + Attachment attachment = attachmentService.getById(relateId); + AssertUtils.notNull(attachment, "附件不存在"); + // 附件暂时没有md5 + return new FileVO(attachment.getObjectKey(), null); + } + throw new ServiceException("文件任务关联类型错误"); + } + + @Override + public IPage selectPage(IPage page, BusinessUnzipTaskQueryDTO param) { + List list = baseMapper.selectTaskList(page, param); + if (CollectionUtil.isNotEmpty(list)) { + // 处理状态枚举值名称 + list.forEach(vo -> { + String status = vo.getStatus(); + if (StringUtil.isNotBlank(status)) { + vo.setStatusStr(DictCache.getValue(DictTypeEnum.FILE_TASK_STATUS.getType(), vo.getStatus())); + } + }); + } + IPage newPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + return newPage.setRecords(list); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean updateReupload(BusinessUnzipTaskUpdateDTO param) { + log.info("修改业务解压任务重新上传参数:{}", JSON.toJSONString(param)); + if (param == null || (param.getId() == null && param.getBizId() == null)) { + throw new ServiceException("参数不能为空"); + } + // 校验业务解压任务 + BusinessUnzipTask unzipTask = param.getId() != null ? this.getById(param.getId()) : this.getOne(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getBizId, param.getBizId()) + .last("limit 1") + ); + AssertUtils.notNull(unzipTask, "业务任务不存在"); + // 上传中或解压失败才可以重新上传 + AssertUtils.isTrue(UnzipTask.Status.UPLOADING.getCode().equals(unzipTask.getStatus()) + || UnzipTask.Status.UNZIP_FAIL.getCode().equals(unzipTask.getStatus()), "只有上传中或解压失败的任务才可以重新上传"); + + if (UnzipTask.Status.UPLOADING.getCode().equals(unzipTask.getStatus())) { + // 上传中的才需要取消上传任务,解压失败的无需取消 + try { + // 取消文件任务 + fileTaskService.cancelBatchByIds(Lists.newArrayList(unzipTask.getRelateId())); + } catch (Exception e) { + log.error("取消上传异常", e); + } + } + + // 更新业务解压任务 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(unzipTask.getId()); + updateParam.setRelateId(param.getRelateId()); + String status = getStatus(param.getRelateType(), param.getRelateId()); + updateParam.setStatus(status); + boolean result = this.updateById(updateParam); + if (result && UnzipTask.Status.UPLOADED.getCode().equals(status)) { + // 重新查询,防止解压开始事件中查询时事务未提交查询不到最新的数据 + unzipTask = this.getById(unzipTask.getId()); + // 解压任务状态是上传完成,发布解压开始事件 + applicationContext.publishEvent(new UnzipStartEvent(unzipTask)); + } + return result; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean cancelBusinessUnzipTask(Long bizId, boolean cancelFile) { + List list = this.list(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getBizId, bizId) + ); + if (CollectionUtil.isEmpty(list)) { + return true; + } + if (cancelFile) { + boolean hasUpload = list.stream() + .anyMatch(task -> !UnzipTask.Status.UPLOADING.getCode().equals(task.getStatus())); + AssertUtils.isFalse(hasUpload, "当前任务已上传完成,不能取消"); + // 取消文件任务 + List taskIds = list.stream() + .filter(task -> UnzipTask.RelateType.FILE_TASK.getCode().equals(task.getRelateType())) + .map(BusinessUnzipTask::getRelateId) + .toList(); + fileTaskService.cancelBatchByIds(taskIds); + } + // 删除业务文件解压任务 + List unzipTaskIds = list.stream() + .map(BusinessUnzipTask::getId) + .toList(); + this.removeBatchByIds(unzipTaskIds); + return true; + } + + @Override + public List queryBusinessUnzipDetail(Long bizId) { + return baseMapper.queryBusinessUnzipDetail(bizId); + } + + @Override + public List getBusinessUnzipTasksByBizIds(List bizIds) { + if (CollectionUtil.isEmpty(bizIds)) { + return Collections.emptyList(); + } + BusinessUnzipTaskQueryDTO param = new BusinessUnzipTaskQueryDTO(); + param.setBizIds(bizIds); + return baseMapper.selectTaskList(null, param); + } + + /** + * 获取解压任务状态 + * @param relateType 关联类型 + * @param relateId 关联id + * @return + */ + private String getStatus(String relateType, Long relateId) { + if (UnzipTask.RelateType.ATTACHMENT.getCode().equals(relateType)) { + // 关联类型是附件,状态直接就是上传完成 + return UnzipTask.Status.UPLOADED.getCode(); + } + if (UnzipTask.RelateType.FILE_TASK.getCode().equals(relateType)) { + // 关联类型是文件任务,查询文件任务状态 + FileTask fileTask = fileTaskService.getById(relateId); + AssertUtils.notNull(fileTask, "文件任务不存在"); + if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) { + // 文件任务上传完成,解压任务设置为上传完成 + return UnzipTask.Status.UPLOADED.getCode(); + } + // 否则解压任务设置为上传中状态 + return UnzipTask.Status.UPLOADING.getCode(); + } + throw new ServiceException("关联类型错误"); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileHandleServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileHandleServiceImpl.java new file mode 100644 index 0000000..f3134ec --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileHandleServiceImpl.java @@ -0,0 +1,92 @@ +package org.springblade.file.service.impl; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.exception.UnzipFailException; +import org.springblade.file.pojo.vo.FileHandleVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IFileHandleService; +import org.springblade.file.service.IFileService; +import org.springblade.file.util.UnzipUtil; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/22 + */ +@Slf4j +@AllArgsConstructor +@Service +public class FileHandleServiceImpl implements IFileHandleService { + + private final IFileService fileService; + private final ObsProperties obsProperties; + + @Override + public FileHandleVO downloadAndUnzip(FileVO file) { + File downloadDir = new File(obsProperties.getTmpDownloadDir()); + if (!downloadDir.exists()) { + downloadDir.mkdirs(); + } + File downloadFile = new File(downloadDir, file.getObjectKey()); + String downloadFilePath = downloadFile.getAbsolutePath(); + File unzipDir = null; + long unzipStart = System.currentTimeMillis(); + try { + // 下载文件 + downloadFile(file, downloadFile, downloadFilePath); + // 2. 解压文件目录 + unzipDir = new File(obsProperties.getTmpUnzipDir(), file.getObjectKey()); + if (!unzipDir.exists()) { + unzipDir.mkdirs(); + } + long start = System.currentTimeMillis(); + List files = UnzipUtil.unzipOrUnrar(downloadFilePath, unzipDir); + long end = System.currentTimeMillis(); + log.info("文件:{} 解压完成 共{}个 耗时:{}", file.getObjectKey(), files.size(), end - start); + FileHandleVO fileHandleVO = new FileHandleVO(); + fileHandleVO.setDownloadFile(downloadFile); + fileHandleVO.setUnzippedDir(unzipDir); + fileHandleVO.setUnzippedFiles(files); + return fileHandleVO; + } catch (Exception e) { + log.error("文件解压失败异常", e); + throw new UnzipFailException(e, unzipDir); + } finally { + long unzipEnd = System.currentTimeMillis(); + log.info("文件:{} 下载并解压完成 耗时:{}", file.getObjectKey(), unzipEnd - unzipStart); + } + } + + /** + * 下载文件 + * + * @param file + * @param downloadFile + * @param downloadFilePath + */ + private void downloadFile(FileVO file, File downloadFile, String downloadFilePath) { + if (downloadFile.exists() && file.getMd5() != null) { + // 下载文件存在且md5不为空,可能是重新解压 + String fileMd5 = fileService.getFileMd5(downloadFile); + if (StringUtil.equals(fileMd5, file.getMd5())) { + log.info("文件存在且md5一致,无需重新下载 文件路径:{}", downloadFile.getAbsolutePath()); + return; + } + } + long downloadStart = System.currentTimeMillis(); + try { + // 1. 下载文件 + fileService.downloadFile(file.getObjectKey(), downloadFilePath); + } finally { + long downloadEnd = System.currentTimeMillis(); + log.info("文件:{} 下载完成 耗时:{}", file.getObjectKey(), downloadEnd - downloadStart); + } + } + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskPartServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskPartServiceImpl.java new file mode 100644 index 0000000..540c170 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskPartServiceImpl.java @@ -0,0 +1,46 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.file.mapper.FileTaskPartMapper; +import org.springblade.file.pojo.entity.FileTaskPart; +import org.springblade.file.service.IFileTaskPartService; +import org.springframework.stereotype.Service; + +/** + * 服务实现类 + * + * @author zhaowei + */ +@Service +@Slf4j +@AllArgsConstructor +public class FileTaskPartServiceImpl extends ServiceImpl implements IFileTaskPartService { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java new file mode 100644 index 0000000..fdc8b4d --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java @@ -0,0 +1,360 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.log.utils.AssertUtils; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.convert.FileTaskConvert; +import org.springblade.file.listener.FileUploadedEvent; +import org.springblade.file.mapper.BusinessUnzipTaskMapper; +import org.springblade.file.mapper.FileTaskMapper; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.dto.FileTaskUpdateDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.entity.FileTaskPart; +import org.springblade.file.pojo.enums.FileTaskStatus; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; +import org.springblade.file.pojo.vo.PartEtagVO; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.IFileTaskPartService; +import org.springblade.file.service.IFileTaskService; +import org.springblade.file.util.ObjectKeyUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 服务实现类 + * + * @author zhaowei + */ +@Service +@Slf4j +@AllArgsConstructor +public class FileTaskServiceImpl extends ServiceImpl implements IFileTaskService { + + private final FileTaskConvert convert; + private final IFileTaskPartService partService; + private final IFileService fileService; + private final BusinessUnzipTaskMapper unzipTaskMapper; + private final ObsProperties obsProperties; + private final ApplicationContext applicationContext; + + @Override + public FileTaskUpdateVO queryFileTask(Long id, String md5) { + if (id == null && StringUtil.isBlank(md5)) { + throw new ServiceException("id和md5不能都为空"); + } + // 根据md5按创建时间倒序取一条 + FileTask fileTask = this.getOne(Wrappers.lambdaQuery() + .eq(id != null, FileTask::getId, id) + .eq(StringUtil.isNotBlank(md5), FileTask::getMd5, md5) + .orderByDesc(FileTask::getCreateTime) + .last("limit 1") + ); + if (fileTask == null) { + return null; + } + return getFileTaskUpdateVO(fileTask); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Long createFileTask(FileTaskCreateDTO param) { + if (!param.isForce()) { + // 不是强制创建新任务,文件任务存在就报错 + FileTask fileTask = this.getOne(Wrappers.lambdaQuery() + .eq(FileTask::getMd5, param.getMd5()) + .last("limit 1") + ); + AssertUtils.isNull(fileTask, "文件任务已存在"); + } + + // 1. 初始化上传任务 + String fileName = param.getAttachmentName(); + String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); + String objectKey = ObjectKeyUtils.generateObjectKey(obsProperties.getZipUploadDir(), fileName); + String uploadId = fileService.initiateMultipartUpload(objectKey); + + // 2. 保存文件任务 + FileTask addParam = convert.dto2entity(param); + addParam.setId(IdWorker.getId()); + addParam.setObjectKey(objectKey); + addParam.setSuffix(fileSuffix); + // 当前位置 + addParam.setCurrentIndex(0); + addParam.setUploadId(uploadId); + // 正在上传 + addParam.setStatus(FileTaskStatus.UPLOADING.getCode()); + this.save(addParam); + + return addParam.getId(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FileTaskUpdateVO updateFileTask(FileTaskUpdateDTO param) { + // 校验文件任务 + FileTask fileTask = this.getById(param.getId()); + AssertUtils.notNull(fileTask, "文件任务不存在"); + AssertUtils.isTrue(!FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus()), "文件任务已完成"); + + // 1. 新增分段 + addPart(param, fileTask); + + // 是否最后一段 + boolean isLast = fileTask.getChunkTotal().equals(param.getPartNumber()); + // 2. 更新文件任务 + boolean update = updateTask(param, isLast); + // 3. 处理上传结束 + handleFinished(isLast, update, fileTask); + return this.getUpdateVO(param.getId()); + } + + /** + * 处理结束 + * @param isLast + * @param update + * @param fileTask + */ + private void handleFinished(boolean isLast, boolean update, FileTask fileTask) { + if (isLast && update) { + // 最后一段并且更新成功,合并文件 + List parts = partService.list(Wrappers.lambdaQuery() + .eq(FileTaskPart::getFileTaskId, fileTask.getId()) + .orderByAsc(FileTaskPart::getPartNumber) + ); + + try { + // 按分段号去重,相同分段号取第一个,如果分段号相同,etag不同,报错 + Map numberTag = parts.stream() + .collect(Collectors.toMap(FileTaskPart::getPartNumber, Function.identity(), (a, b) -> { + if (StringUtil.equals(a.getEtag(), b.getEtag())) { + return a; + } + log.error("etag不一致 id1:{} id2:{} etag1:{} etag2:{}", a.getId(), b.getId(), a.getEtag(), b.getEtag()); + throw new ServiceException("etag不一致"); + })); + // 获取要保留的分段id + List partId = numberTag.values().stream() + .map(FileTaskPart::getId) + .toList(); + // 去重 + parts = parts.stream() + .filter(e -> partId.contains(e.getId())) + .toList(); + List partEtags = parts.stream() + .map(convert::entity2vo) + .toList(); + // 3. 合并文件 + fileService.completeMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId(), partEtags); + // 4. 删除文件任务分段 + List partIds = parts.stream() + .map(FileTaskPart::getId) + .toList(); + partService.removeByIds(partIds); + // 5. 发布上传完成事件 + applicationContext.publishEvent(new FileUploadedEvent(fileTask.getId())); + } catch (Exception e) { + log.error("文件分段上传合并文件异常", e); + log.error("文件任务id:{} objectKey:{}", fileTask.getId(), fileTask.getObjectKey()); + // 更新文件任务状态为失败 + FileTask updateParam = new FileTask(); + updateParam.setId(fileTask.getId()); + updateParam.setStatus(FileTaskStatus.FAILED.getCode()); + this.updateById(updateParam); + } + } + } + + /** + * 新增分段 + * @param param + * @param fileTask + */ + private void addPart(FileTaskUpdateDTO param, FileTask fileTask) { + // 校验分段 + FileTaskPart part = partService.getOne(Wrappers.lambdaQuery() + .eq(FileTaskPart::getFileTaskId, fileTask.getId()) + .eq(FileTaskPart::getPartNumber, param.getPartNumber()) + .last("limit 1") + ); + if (part != null) { + // 分段存在 + if (StringUtil.equals(part.getEtag(), param.getEtag())) { + // 且etag和当前的etag相同,不做任何处理 + return; + } + // etag和当前的etag不同,报错 + throw new ServiceException("当前分段已存在"); + } + + // 新增分段 + FileTaskPart addParam = new FileTaskPart(); + addParam.setId(IdWorker.getId()); + addParam.setFileTaskId(fileTask.getId()); + addParam.setPartNumber(param.getPartNumber()); + addParam.setEtag(param.getEtag()); + partService.save(addParam); + } + + @Override + public String getUploadUrl(Long id, Integer partNumber) { + FileTask fileTask = this.getById(id); + AssertUtils.notNull(fileTask, "文件任务不存在"); + AssertUtils.isTrue(!FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus()), "文件任务已完成"); + return fileService.getUploadPartUrl(fileTask.getObjectKey(), fileTask.getUploadId(), partNumber, null); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void cancelBatchByIds(List taskIds) { + // 查询任务 + List fileTasks = this.listByIds(taskIds); + if (CollectionUtil.isEmpty(fileTasks)) { + return; + } + // 校验状态 + fileTasks.forEach(task -> AssertUtils.isFalse(FileTaskStatus.COMPLETED.getCode().equals(task.getStatus()), "文件任务已上传完成")); + // 调用obs取消上传任务 + try { + for (FileTask fileTask : fileTasks) { + fileService.cancelMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId()); + } + } catch (Exception e) { + log.error("调用obs取消文件上传任务异常", e); + throw new ServiceException("调用obs取消文件上传任务异常"); + } + // 删除上传任务 + this.removeBatchByIds(taskIds); + // 删除文件任务分片 + List list = partService.list(Wrappers.lambdaQuery() + .in(FileTaskPart::getFileTaskId, taskIds) + ); + if (CollectionUtil.isNotEmpty(list)) { + List partIds = list.stream() + .map(FileTaskPart::getId) + .toList(); + partService.removeByIds(partIds); + } + } + + @Override + public FileTaskUpdateVO updateFailed(Long id) { + // 校验文件任务 + FileTask fileTask = this.getById(id); + AssertUtils.notNull(fileTask, "文件任务不存在"); + AssertUtils.isTrue(!FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus()), "文件任务已完成"); + // 更新文件任务状态为失败 + FileTask updateParam = new FileTask(); + updateParam.setId(id); + updateParam.setStatus(FileTaskStatus.FAILED.getCode()); + this.updateById(updateParam); + return this.getUpdateVO(id); + } + + @Override + public boolean updatePaused(List ids) { + if (CollectionUtil.isEmpty(ids)) { + return false; + } + FileTask updateParam = new FileTask(); + // 修改状态为已暂停 + updateParam.setStatus(FileTaskStatus.PAUSED.getCode()); + return this.update(updateParam, Wrappers.lambdaUpdate() + .in(FileTask::getId, ids) + // 只更新正在上传状态的 + .eq(FileTask::getStatus, FileTaskStatus.UPLOADING.getCode()) + ); + } + + /** + * 查询文件任务 + * @param id + * @return + */ + private FileTaskUpdateVO getUpdateVO(Long id) { + FileTask fileTask = this.getById(id); + return getFileTaskUpdateVO(fileTask); + } + + /** + * 查询文件任务 + * @param fileTask + * @return + */ + private FileTaskUpdateVO getFileTaskUpdateVO(FileTask fileTask) { + if (fileTask == null) { + return null; + } + FileTaskUpdateVO updateVO = convert.entity2updateVo(fileTask); + BusinessUnzipTask businessUnzipTask = unzipTaskMapper.selectOne(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getRelateId, fileTask.getId()) + .last("limit 1") + ); + if (businessUnzipTask != null && businessUnzipTask.getDocCode() != null) { + updateVO.setDocCode(businessUnzipTask.getDocCode()); + } + return updateVO; + } + + /** + * 更新任务 + * + * @param param + * @param isLast 是否最后一段 + * @return + */ + private boolean updateTask(FileTaskUpdateDTO param, boolean isLast) { + FileTask updateParam = new FileTask(); + updateParam.setCurrentIndex(param.getPartNumber()); + if (isLast) { + // 最后一段,修改任务状态为完成 + updateParam.setStatus(FileTaskStatus.COMPLETED.getCode()); + } else { + // 非最后一段,修改任务状态为上传中 + updateParam.setStatus(FileTaskStatus.UPLOADING.getCode()); + } + return this.update(updateParam, Wrappers.lambdaUpdate() + .eq(FileTask::getId, param.getId()) + .lt(FileTask::getCurrentIndex, param.getPartNumber()) + ); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java new file mode 100644 index 0000000..22290e5 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java @@ -0,0 +1,40 @@ +package org.springblade.file.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.utils.AssertUtils; +import org.springblade.file.convert.OCRConverter; +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.IOCRConvertService; +import org.springblade.thirdparty.ocr.pojo.dto.CertificateBatchRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springblade.thirdparty.ocr.service.IOCRService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author bfhuange + * @since 2025/3/13 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class OCRConvertServiceImpl implements IOCRConvertService { + + private final IFileService fileService; + private final IOCRService ocrService; + private final OCRConverter converter; + + @Override + public TransportCertificateVO recognitionTransportCertificate(FileCertificateBatchRecognitionDTO param) { + AssertUtils.notNull(param, "参数不能为空"); + AssertUtils.notNull(param.getObjectKeys(), "图像obs key不能为空"); + CertificateBatchRecognitionDTO ocrParam = converter.dto2ocr(param); + // 转换文件链接 + List fileUrls = fileService.getFileUrls(param.getObjectKeys(), null); + ocrParam.setUrls(fileUrls); + return ocrService.recognitionTransportCertificate(ocrParam); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/ObsFileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/ObsFileServiceImpl.java new file mode 100644 index 0000000..82014e2 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/ObsFileServiceImpl.java @@ -0,0 +1,293 @@ +package org.springblade.file.service.impl; + +import cn.hutool.core.net.URLEncodeUtil; +import com.obs.services.ObsClient; +import com.obs.services.exception.ObsException; +import com.obs.services.internal.Constants; +import com.obs.services.internal.utils.ServiceUtils; +import com.obs.services.model.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.pojo.vo.PartEtagVO; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.support.DownloadProgressListener; +import org.springblade.file.service.support.UploadProgressListener; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 华为obs对象存储 + * @author bfhuange + * @date 2024/10/21 + */ +@Slf4j +@RequiredArgsConstructor +public class ObsFileServiceImpl implements IFileService { + private final ObsProperties properties; + private final ObsClient obsClient; + + @Override + public void uploadFile(String objectKey, InputStream is) { + this.uploadFile(objectKey, null, is, null); + } + + @Override + public String uploadTempFile(String objectKey, InputStream is, Integer expires) { + // 上传文件 + this.uploadFile(objectKey, null, is, expires); + // 生成下载链接 + return this.getFileUrl(objectKey, properties.getExpires()); + } + + @Override + public void uploadFile(String objectKey, File file) { + this.uploadFile(objectKey, file, null, null); + } + + @Override + public String initiateMultipartUpload(String objectKey) { + InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(properties.getBucketName(), objectKey); + try { + InitiateMultipartUploadResult initiateResult = obsClient.initiateMultipartUpload(initiateRequest); + return initiateResult.getUploadId(); + } catch (Exception e) { + log.error("初始化分段上传异常", e); + throw new ServiceException("初始化分段上传异常"); + } + } + + @Override + public PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize) { + UploadPartRequest uploadPartRequest = new UploadPartRequest(); + uploadPartRequest.setBucketName(properties.getBucketName()); + uploadPartRequest.setObjectKey(objectKey); + uploadPartRequest.setUploadId(uploadId); + uploadPartRequest.setPartNumber(partNumber); + uploadPartRequest.setFile(file); + if (offset != null) { + uploadPartRequest.setOffset(offset); + } + if (partSize != null) { + uploadPartRequest.setPartSize(partSize); + } + try { + UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest); + return new PartEtagVO(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()); + } catch (Exception e) { + log.error("分段上传异常", e); + throw new ServiceException("分段上传异常"); + } + } + + @Override + public void completeMultipartUpload(String objectKey, String uploadId, List partEtagVOS) { + if (CollectionUtil.isEmpty(partEtagVOS)) { + log.warn("完成分段上传,要合并的分段列表为空"); + return; + } + // 参数转换 + List list = partEtagVOS.stream() + .map(partEtagVO -> new PartEtag(partEtagVO.getEtag(), partEtagVO.getPartNumber())) + .collect(Collectors.toCollection(ArrayList::new)); + CompleteMultipartUploadRequest request = + new CompleteMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId, list); + try { + obsClient.completeMultipartUpload(request); + } catch (Exception e) { + log.error("完成分段上传异常", e); + throw new ServiceException("完成分段上传异常"); + } + } + + @Override + public void cancelMultipartUpload(String objectKey, String uploadId) { + AbortMultipartUploadRequest request = new AbortMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId); + try { + obsClient.abortMultipartUpload(request); + } catch (Exception e) { + log.error("取消分段上传任务异常", e); + throw new ServiceException("取消分段上传任务异常"); + } + } + + @Override + public String getFileUrl(String objectKey, Long expires) { + return this.getFileUrl(objectKey, null, expires); + } + + @Override + public String getFileUrl(String objectKey, String fileName, Long expires) { + if (StringUtil.isBlank(objectKey)) { + return null; + } + // 链接失效时间 + expires = Optional.ofNullable(expires) + // 参数为空取配置 + .orElse(Optional.ofNullable(properties.getExpires()) + // 配置为空,默认1小时 + .orElse(Duration.ofHours(1).getSeconds())); + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); + request.setBucketName(properties.getBucketName()); + request.setObjectKey(objectKey); + request.setRequestDate(new Date()); + if (StringUtil.isNotBlank(fileName)) { + // 文件名不为空,修改响应的文件名 + Map queryParam = new HashMap<>(); + String encodeFileName = encode(fileName); + queryParam.put(Constants.ObsRequestParams.RESPONSE_CONTENT_DISPOSITION, "attachment;filename*=UTF-8''" + encodeFileName); + request.setQueryParams(queryParam); + } + try { + TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request); + return temporarySignature.getSignedUrl(); + } catch (Exception e) { + log.error("获取文件链接异常", e); + throw new ServiceException("获取文件链接异常"); + } + } + + /** + * 编码文件名 + * @param fileName + * @return + */ + private String encode(String fileName) { + int index = fileName.lastIndexOf("."); + if (index < 0) { + return URLEncodeUtil.encode(fileName); + } + // 文件名 + String prefix = fileName.substring(0, index); + // 后缀 + String ext = fileName.substring(index); + return URLEncodeUtil.encode(prefix) + ext; + } + + @Override + public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) { + TemporarySignatureRequest request = new TemporarySignatureRequest(); + request.setBucketName(properties.getBucketName()); + request.setObjectKey(objectKey); + request.setRequestDate(new Date()); + // 链接失效时间 + expires = Optional.ofNullable(expires) + .orElse(properties.getExpires()); + if (expires != null) { + request.setExpires(expires); + } + request.setMethod(HttpMethodEnum.PUT); + request.setHeaders(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)); + Map params = new HashMap<>(); + params.put(Constants.ObsRequestParams.PART_NUMBER, String.valueOf(partNumber)); + params.put(Constants.ObsRequestParams.UPLOAD_ID, uploadId); + request.setQueryParams(params); + try { + TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request); + return temporarySignature.getSignedUrl(); + } catch (Exception e) { + log.error("获取文件链接异常", e); + throw new ServiceException("获取文件链接异常"); + } + } + + @Override + public List getFileUrls(List objectKeys, Long expires) { + if (CollectionUtil.isEmpty(objectKeys)) { + return Collections.emptyList(); + } + return objectKeys.stream() + .map(objectKey -> this.getFileUrl(objectKey, expires)) + .toList(); + } + + @Override + public void downloadFile(String objectKey, String downloadFile) { + DownloadFileRequest request = new DownloadFileRequest(properties.getBucketName(), objectKey); + request.setDownloadFile(downloadFile); + // 开启断点续传模式 + request.setEnableCheckpoint(true); + Long progressInterval = properties.getDownloadProgressInterval(); + // 设置下载进度日志 + request.setProgressListener(new DownloadProgressListener(objectKey, progressInterval)); + request.setProgressInterval(progressInterval); + DownloadFileResult result = null; + try { + result = obsClient.downloadFile(request); + } catch (ObsException e) { + log.error("下载文件异常", e); + log.error("下载文件异常 objectKey:{} responseCode:{} errorCode:{} errorMessage:{} errorRequestId:{} errorHostId:{}", objectKey, e.getResponseCode(), e.getErrorCode(), e.getErrorMessage(), e.getErrorRequestId(), e.getErrorHostId()); + throw new ServiceException("下载文件异常"); + } catch (Exception e) { + log.error("下载文件异常", e); + throw new ServiceException("下载文件异常"); + } finally { + String etag = Optional.ofNullable(result) + .map(DownloadFileResult::getObjectMetadata) + .map(ObjectMetadata::getEtag) + .orElse(null); + log.info("下载文件结束 objectKey:{} etag:{}", objectKey, etag); + } + } + + /** + * 上传文件 + * @param objectKey + * @param file + * @param is + * @param expires 过期时间,单位天,过期后自动删除 + */ + private void uploadFile(String objectKey, File file, InputStream is, Integer expires) { + if (file == null && is == null ) { + throw new ServiceException("文件不存在"); + } + PutObjectRequest request = new PutObjectRequest(); + request.setBucketName(properties.getBucketName()); + request.setObjectKey(objectKey); + if (expires != null) { + request.setExpires(expires); + } + if (is != null) { + request.setInput(is); + } else { + request.setFile(file); + // 文件不为空,设置md5 + String md5 = getFileMd5(file); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentMd5(md5); + request.setMetadata(metadata); + } + Long progressInterval = properties.getUploadProgressInterval(); + // 设置上传进度日志 + request.setProgressListener(new UploadProgressListener(objectKey, progressInterval)); + request.setProgressInterval(progressInterval); + try { + obsClient.putObject(request); + } catch (Exception e) { + log.error("上传文件异常", e); + throw new ServiceException("上传文件异常"); + } + } + + @Override + public String getFileMd5(File file) { + try { + return ServiceUtils.toBase64(ServiceUtils.computeMD5Hash(new FileInputStream(file))); + } catch (NoSuchAlgorithmException | IOException e) { + log.error("获取文件md5异常", e); + return null; + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/support/DownloadProgressListener.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/DownloadProgressListener.java new file mode 100644 index 0000000..7703cc5 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/DownloadProgressListener.java @@ -0,0 +1,31 @@ +package org.springblade.file.service.support; + +import com.obs.services.model.ProgressListener; +import com.obs.services.model.ProgressStatus; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 文件下载进度监听器 + * @author bfhuange + * @date 2024/10/28 + */ +@AllArgsConstructor +@Slf4j +public class DownloadProgressListener implements ProgressListener { + + /** + * obs对象名称 + */ + private final String objectKey; + + /** + * 进度间隔,如果新传输的大小不等于进度间隔,说明是结束了 + */ + private final long progressInterval; + + @Override + public void progressChanged(ProgressStatus status) { + log.info("文件下载进度 文件名:{} 是否结束:{} 总大小:{} 已下载大小:{} 百分比:{} 下载速度:{}", objectKey, progressInterval != status.getNewlyTransferredBytes(), status.getTotalBytes(), status.getTransferredBytes(), status.getTransferPercentage(), String.format("%.2f", status.getAverageSpeed())); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/support/UploadProgressListener.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/UploadProgressListener.java new file mode 100644 index 0000000..f1af602 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/UploadProgressListener.java @@ -0,0 +1,31 @@ +package org.springblade.file.service.support; + +import com.obs.services.model.ProgressListener; +import com.obs.services.model.ProgressStatus; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 文件上传进度监听器 + * @author bfhuange + * @date 2024/10/28 + */ +@AllArgsConstructor +@Slf4j +public class UploadProgressListener implements ProgressListener { + + /** + * obs对象名称 + */ + private final String objectKey; + + /** + * 进度间隔,如果新传输的大小不等于进度间隔,说明是结束了 + */ + private final long progressInterval; + + @Override + public void progressChanged(ProgressStatus status) { + log.info("文件上传进度 文件名:{} 是否结束:{} 已上传大小:{} 上传速度:{}", objectKey, progressInterval != status.getNewlyTransferredBytes(), status.getTransferredBytes(), String.format("%.2f", status.getAverageSpeed())); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/util/ObjectKeyUtils.java b/blade-service/blade-file/src/main/java/org/springblade/file/util/ObjectKeyUtils.java new file mode 100644 index 0000000..e658836 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/util/ObjectKeyUtils.java @@ -0,0 +1,98 @@ +package org.springblade.file.util; + +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.utils.RandomType; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.core.tool.utils.StringUtil; + +/** + * 对象存储key工具类 + * @author bfhuange + * @date 2024/10/29 + */ +public class ObjectKeyUtils { + /** + * object key 格式,前缀目录/ + id + 用户id后4位 + 后缀 + */ + private static final String FORMAT = "%s%s%s%s"; + /** + * 用户id后缀位数 + */ + private static final int ID_SUFFIX_LENGTH = 4; + + private ObjectKeyUtils() {} + + /** + * 生成 object key + * @param dir 文件目录 + * @param fileName 文件名 + * @return + */ + public static String generateObjectKey(String dir, String fileName) { + // 目录字符串 + String dirString = ObjectKeyUtils.getDirString(dir); + // 文件名后缀 + String fileNameSuffix = ObjectKeyUtils.getFileNameSuffix(fileName); + // 用户id后4位 + String userIdSuffix = ObjectKeyUtils.getUserIdSuffix(); + // 格式,目录 + id + 用户id后4位 + 后缀 + return String.format(FORMAT, dirString, IdWorker.getId(), userIdSuffix, fileNameSuffix); + } + + /** + * 生成 object key + * @param fileName 文件名 + * @return + */ + public static String generateObjectKey(String fileName) { + return ObjectKeyUtils.generateObjectKey(StringPool.EMPTY, fileName); + } + + /** + * 获取用户id后缀字符串 + * @return + */ + private static String getUserIdSuffix() { + Long userId = AuthUtil.getUserId(); + if (userId > 0) { + String idString = userId.toString(); + if (idString.length() >= ID_SUFFIX_LENGTH) { + // 截取用户id后几位 + return idString.substring(idString.length() - ID_SUFFIX_LENGTH); + } + } + // 未获取到用户id,取随机数字 + return StringUtil.random(ID_SUFFIX_LENGTH, RandomType.INT); + } + + /** + * 获取目录字符串 + * @param dir + * @return + */ + private static String getDirString(String dir) { + if (StringUtil.isBlank(dir)) { + return StringPool.EMPTY; + } + if (dir.endsWith(StringPool.SLASH)) { + return dir; + } + return dir + StringPool.SLASH; + } + + /** + * 获取文件名后缀 + * @param fileName 文件名 + * @return + */ + private static String getFileNameSuffix(String fileName) { + if (StringUtil.isBlank(fileName)) { + return StringPool.EMPTY; + } + if (!fileName.contains(StringPool.DOT)) { + return StringPool.EMPTY; + } + return fileName.substring(fileName.lastIndexOf(StringPool.DOT)); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/util/UnzipUtil.java b/blade-service/blade-file/src/main/java/org/springblade/file/util/UnzipUtil.java new file mode 100644 index 0000000..af0aceb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/util/UnzipUtil.java @@ -0,0 +1,245 @@ +package org.springblade.file.util; + +import cn.hutool.core.io.CharsetDetector; +import com.github.junrar.Archive; +import com.github.junrar.rarfile.FileHeader; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; +import org.apache.commons.compress.archivers.sevenz.SevenZFile; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.io.IOUtils; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringPool; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipInputStream; + +/** + * 解压工具类 + * @author bfhuange + * @since 2025/2/28 + */ +@Slf4j +public class UnzipUtil { + /** + * rar格式 + */ + public static final String RAR = "rar"; + + /** + * macos下垃圾目录,不处理 + */ + private static final String MACOSX_DIR = "__MACOSX"; + + /** + * 解压文件 + * @param zipFilePath 压缩文件路径 + * @param unzipDir 解压目录 + */ + public static List unzipOrUnrar(String zipFilePath, File unzipDir) { + if (zipFilePath.toLowerCase().endsWith(RAR)) { + // 解压rar + return unrar(zipFilePath, unzipDir); + } else if (zipFilePath.toLowerCase().endsWith(ArchiveStreamFactory.SEVEN_Z)) { + // 解压7z + return un7z(zipFilePath, unzipDir); + } + // 解压其余格式 + return unzip(zipFilePath, unzipDir); + } + + /** + * 解压zip格式 + * @param downloadFile + * @param destDir + */ + public static List un7z(String downloadFile, File destDir) { + List files = new ArrayList<>(); + try( + SevenZFile sevenZFile = new SevenZFile.Builder() + .setFile(new File(downloadFile)) + .get() + ) { + SevenZArchiveEntry entry = sevenZFile.getNextEntry(); + while (entry != null) { + log.debug("压缩文件条目:{}", entry.getName()); + File file = new File(destDir, entry.getName()); + if (!entry.isDirectory()) { + // 不是目录才处理 + files.add(file); + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + try( + OutputStream os = new FileOutputStream(file); + InputStream is = sevenZFile.getInputStream(entry); + ) { + IOUtils.copy(is, os); + } + } + entry = sevenZFile.getNextEntry(); + } + } catch (IOException e) { + log.error("解压文件异常", e); + throw new RuntimeException(e); + } + return files; + } + + /** + * 解压rar格式 + * @param zipFilePath 压缩文件路径 + * @param unzipDir 解压文件目录 + */ + public static List unrar(String zipFilePath, File unzipDir) { + List files = new ArrayList<>(); + try ( + // 加载rar文件 + InputStream inputStream = Files.newInputStream(Paths.get(zipFilePath)); + Archive archive = new Archive(inputStream); + ) { + List fileHeaders = archive.getFileHeaders(); + if (fileHeaders == null) { + throw new ServiceException("压缩文件为空"); + } + int total = fileHeaders.size(); + for (int i = 0; i < fileHeaders.size(); i++) { + FileHeader fileHeader = fileHeaders.get(i); + log.debug("解压进度:{}/{} 文件:{}", i + 1, total, fileHeader.getFileName()); + // 解压文件 + File file = new File(unzipDir, fileHeader.getFileName()); + if (!fileHeader.isDirectory()) { + // 不是目录才处理 + files.add(file); + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + InputStream is = archive.getInputStream(fileHeader); + try (OutputStream os = new FileOutputStream(file)) { + IOUtils.copy(is, os); + } + } + } + } catch (ServiceException e) { + throw e; + } catch (Exception e) { + log.error("解压文件异常", e); + throw new ServiceException("解压文件异常"); + } + return files; + } + + /** + * 解压zip格式 + * @param zipFilePath 压缩文件路径 + * @param unzipDir 解压目录 + */ + public static List unzip(String zipFilePath, File unzipDir) { + List files = new ArrayList<>(); + ArchiveStreamFactory archiveStreamFactory = new ArchiveStreamFactory(); + try( + FileInputStream is = new FileInputStream(zipFilePath); + BufferedInputStream bis = new BufferedInputStream(is); + ArchiveInputStream ais = archiveStreamFactory.createArchiveInputStream(bis); + ) { + ArchiveEntry entry; + while ((entry = ais.getNextEntry()) != null) { + // 获取文件名称 + String entryName = UnzipUtil.getEntryName(entry); + log.debug("压缩文件条目:{}", entryName); + if (entryName.contains(MACOSX_DIR)) { + // macos下垃圾目录,跳过 + log.info("跳过macos下__MACOSX的文件:{}", entryName); + continue; + } + // 压缩文件内文件夹分隔符是\,linux下不会识别为文件夹,会识别成文件名,处理下 + if (entryName.contains(StringPool.BACK_SLASH)) { + entryName = entryName.replace(StringPool.BACK_SLASH, StringPool.SLASH); + } + if (!isDirectory(entryName)) { + File file = new File(unzipDir, entryName); + if (file.getName().startsWith(StringPool.DOT)) { + // .开头的文件,是隐藏文件,跳过 + log.info("跳过.开头的文件:{}", entryName); + continue; + } + // 不是目录才处理 + files.add(file); + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + try(OutputStream os = new FileOutputStream(file)) { + IOUtils.copy(ais, os); + } + } + } + } catch (IOException e) { + log.error("解压文件异常", e); + throw new RuntimeException(e); + } catch (ArchiveException e) { + throw new RuntimeException(e); + } + return files; + } + + /** + * 获取文件名称 + * @param entry + * @return + */ + private static String getEntryName(ArchiveEntry entry) { + if (entry instanceof ZipArchiveEntry zipArchiveEntry) { + byte[] rawName = zipArchiveEntry.getRawName(); + // 检测文件名编码 + Charset charset = CharsetDetector.detect(new ByteArrayInputStream(rawName)); + return new String(rawName, charset); + } + return entry.getName(); + } + + /** + * 判断压缩文件是否utf8编码 + * @param zipFilePath 压缩文件路径 + * @return + */ + public static boolean isUtf8(String zipFilePath) { + try ( + FileInputStream fileInputStream = new FileInputStream(zipFilePath); + ZipInputStream zipInputStream = new ZipInputStream(fileInputStream, StandardCharsets.UTF_8); + ) { + while (zipInputStream.getNextEntry() != null) { + + } + return true; + } catch (Exception ignore) { + // gbk编码的用utf8编码读取会抛异常 + return false; + } + } + + /** + * 判断压缩文件中条目是否文件夹 + * @param entryName + * @return + */ + private static boolean isDirectory(String entryName) { + if (entryName == null) { + return false; + } + return entryName.endsWith(StringPool.SLASH) || entryName.endsWith(StringPool.BACK_SLASH); + } + +} diff --git a/blade-service/blade-file/src/main/resources/bootstrap-dev.yml b/blade-service/blade-file/src/main/resources/bootstrap-dev.yml new file mode 100644 index 0000000..303fc85 --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap-dev.yml @@ -0,0 +1,6 @@ +#spring: +# cloud: +# nacos: +# username: nacos +# password: ${NACOS_PASSWORD:gr30wIs5%Hi7keQj} +# server-addr: ${NACOS_ADDR:10.38.16.127:8848} diff --git a/blade-service/blade-file/src/main/resources/bootstrap-prod.yml b/blade-service/blade-file/src/main/resources/bootstrap-prod.yml new file mode 100644 index 0000000..673f396 --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap-prod.yml @@ -0,0 +1,6 @@ +#spring: +# cloud: +# nacos: +# username: nacos +# password: rWrMrVTWyf%ekjuw +# server-addr: ${NACOS_ADDR:192.168.0.242:8848} diff --git a/blade-service/blade-file/src/main/resources/bootstrap-test.yml b/blade-service/blade-file/src/main/resources/bootstrap-test.yml new file mode 100644 index 0000000..7586f5a --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap-test.yml @@ -0,0 +1,8 @@ +#server: +# port: 38107 +#spring: +# cloud: +# nacos: +# username: nacos +# password: gr30wIs5%Hi7keQj +# server-addr: ${NACOS_ADDR:10.38.16.127:8848} diff --git a/blade-service/blade-file/src/main/resources/bootstrap.yml b/blade-service/blade-file/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..3dcc197 --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap.yml @@ -0,0 +1,41 @@ +server: + port: 8107 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.dev.url} + username: ${blade.datasource.dev.username} + password: ${blade.datasource.dev.password} + +#spring: +# application: +# name: blade-file +# cloud: +# nacos: +# discovery: +# namespace: ${spring.profiles.active} +# # 不注册到nacos +# #registerEnabled: false +# config: +# # 文件后缀名 +# file-extension: yaml +# namespace: ${spring.profiles.active} +# shared-configs: +# - data-id: blade.yaml +# refresh: true +# - data-id: blade-${spring.profiles.active}.yaml +# refresh: true +# - data-id: third-party-api.yaml +# refresh: true +# sentinel: +# datasource: +# flow: +# nacos: +# serverAddr: ${spring.cloud.nacos.server-addr} +# username: ${spring.cloud.nacos.username} +# password: ${spring.cloud.nacos.password} +# dataId: sentinel-${spring.application.name}.json +# groupId: DEFAULT_GROUP +# namespace: ${spring.profiles.active} +# ruleType: flow diff --git a/blade-service/blade-system/pom.xml b/blade-service/blade-system/pom.xml index 87f4b33..4ab8af9 100644 --- a/blade-service/blade-system/pom.xml +++ b/blade-service/blade-system/pom.xml @@ -13,6 +13,13 @@ ${project.artifactId} jar + + + 1.18.30 + 1.5.5.Final + 7.5.5 + + org.springblade @@ -48,9 +55,11 @@ blade-mk-api + org.mapstruct mapstruct + ${org.mapstruct.version} @@ -80,6 +89,34 @@ org.apache.maven.plugins maven-antrun-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.plugin.version} + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/AppVersionController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/AppVersionController.java new file mode 100644 index 0000000..debea12 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/AppVersionController.java @@ -0,0 +1,115 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Query; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.pojo.entity.AppVersion; +import org.springblade.system.service.AppVersionService; +import org.springframework.web.bind.annotation.*; + + +@RestController +@AllArgsConstructor +@RequestMapping("appVersion") +@Tag(name = "APP版本管理", description = "APP版本管理") +public class AppVersionController extends BladeController { + private static final Logger logger = LoggerFactory.getLogger(AppVersionController.class); + + private final AppVersionService appVersionService; + + /** + * 分页 + */ + @GetMapping("/page") + @ApiOperationSupport(order = 1) + @Operation(summary = "分页") + public R> page(AppVersion modelVo, Query query) { + try { + return R.data(appVersionService.page(modelVo,query)); + } catch (Exception e) { + logger.error("appVersion/page:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + + /** + * 详情 + */ + @GetMapping("/detail/{id}") + @ApiOperationSupport(order = 2) + @Operation(summary = "详情") + public R detail(@PathVariable Long id) { + try { + return R.data(appVersionService.detail(id)); + } catch (Exception e) { + logger.error("appVersion/detail:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + + /** + * 发布版本/编辑 + */ + @PostMapping("/publishOrEdit") + @ApiOperationSupport(order = 3) + @Operation(summary = "发布版本/编辑") + public R publishOrEdit(@Valid @RequestBody AppVersion modelVo) { + try { + return R.data(appVersionService.publishOrEdit(modelVo)); + } catch (Exception e) { + logger.error("appVersion/publishOrEdit:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + + /** + * 删除 + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 4) + @Operation(summary = "删除") + public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { + try { + return R.status(appVersionService.removeByIds(Func.toLongList(ids))); + } catch (Exception e) { + logger.error("appVersion/remove:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/AppVersionMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/AppVersionMapper.java new file mode 100644 index 0000000..571b8ce --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/AppVersionMapper.java @@ -0,0 +1,36 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.system.pojo.entity.AppVersion; + + +public interface AppVersionMapper extends BaseMapper { + + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/AppVersionService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/AppVersionService.java new file mode 100644 index 0000000..536528b --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/AppVersionService.java @@ -0,0 +1,54 @@ +package org.springblade.system.service; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import org.springblade.core.log.utils.AssertUtils; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.system.mapper.AppVersionMapper; +import org.springblade.system.pojo.entity.AppVersion; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class AppVersionService extends ServiceImpl { + + /** + * 分页 + */ + public IPage page(AppVersion modelVo, Query query){ + query.setDescs("create_time"); + IPage page = this.page(Condition.getPage(query), Condition.getQueryWrapper(modelVo)); + return page; + } + + /** + * 详情 + */ + public AppVersion detail(Long id){ + AssertUtils.isTrue(ObjectUtil.isNotEmpty(id),"id不能为空"); + AppVersion model = this.getById(id); + return model; + } + + /** + * 发布版本/编辑 + */ + public AppVersion publishOrEdit(AppVersion modelVo) { + //版本号没有重复校验 + AppVersion appVersion=this.getOne(new LambdaQueryWrapper().eq(AppVersion::getUpdateType, modelVo.getUpdateType()) + .eq(AppVersion::getVersion, modelVo.getVersion()) + .eq(AppVersion::getSubVersion, modelVo.getSubVersion()) + ); + if(ObjectUtil.isNotEmpty(appVersion)){ + if(ObjectUtil.isEmpty(modelVo.getId())||(ObjectUtil.isNotEmpty(modelVo.getId())&&!modelVo.getId().equals(appVersion.getId()))){ + throw new RuntimeException("该更新类型的版本号重复"); + } + } + this.saveOrUpdate(modelVo); + return modelVo; + } +} diff --git a/blade-service/pom.xml b/blade-service/pom.xml index 024bffa..409eb6f 100644 --- a/blade-service/pom.xml +++ b/blade-service/pom.xml @@ -19,6 +19,7 @@ blade-desk blade-system + blade-file diff --git a/pom.xml b/pom.xml index 554de4e..492c624 100644 --- a/pom.xml +++ b/pom.xml @@ -25,11 +25,6 @@ blade false 0.42.0 - - - 1.18.30 - 1.5.5.Final - @@ -142,11 +137,10 @@ ${revision} - - org.mapstruct - mapstruct - ${org.mapstruct.version} + org.springblade + blade-file-api + ${revision} @@ -292,19 +286,6 @@ -parameters - - - org.projectlombok - lombok - ${lombok.version} - - - org.mapstruct - mapstruct-processor - ${org.mapstruct.version} - - -