From 0c3e626647338373650dd6ecd77b101295acf41e Mon Sep 17 00:00:00 2001 From: L <545073804@qq.com> Date: Thu, 28 Mar 2024 20:24:11 +0800 Subject: [PATCH 1/7] =?UTF-8?q?update=20=E6=96=B0=E5=A2=9E=E5=88=86?= =?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E9=80=BB=E8=BE=91=EF=BC=8C=E5=9C=A8?= =?UTF-8?q?=E4=B8=8D=E6=94=B9=E8=A1=A8=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/oss/constant/OssConstant.java | 4 + .../dromara/common/oss/core/OssClient.java | 215 +++++++++++++++++- .../oss/entity/CreatePartUploadResult.java | 30 +++ .../dromara/common/oss/entity/PartInfo.java | 24 ++ .../common/oss/entity/PartUploadInfo.java | 74 ++++++ .../common/oss/entity/PartUploadResult.java | 35 +++ .../controller/system/SysOssController.java | 18 ++ .../system/domain/bo/SysOssPartUploadBo.java | 62 +++++ .../system/domain/vo/SysOssPartUploadVo.java | 44 ++++ .../system/service/ISysOssService.java | 11 + .../service/impl/SysOssServiceImpl.java | 73 +++++- 11 files changed, 587 insertions(+), 3 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/CreatePartUploadResult.java create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadResult.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java index 9d8db9335..ef9ccefc5 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java @@ -37,4 +37,8 @@ public interface OssConstant { */ String IS_HTTPS = "Y"; + /** + * 分片上传信息 cacheKey + */ + String PART_UPLOAD_INFO_CACHE_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:part_upload_info:"; } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java index eb1656c06..0562d2a52 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java @@ -7,6 +7,9 @@ import org.dromara.common.core.utils.DateUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.oss.constant.OssConstant; +import org.dromara.common.oss.entity.CreatePartUploadResult; +import org.dromara.common.oss.entity.PartInfo; +import org.dromara.common.oss.entity.PartUploadResult; import org.dromara.common.oss.entity.UploadResult; import org.dromara.common.oss.enumd.AccessPolicyType; import org.dromara.common.oss.enumd.PolicyType; @@ -19,8 +22,7 @@ import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Configuration; -import software.amazon.awssdk.services.s3.model.NoSuchBucketException; -import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.awssdk.services.s3.model.*; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.transfer.s3.S3TransferManager; import software.amazon.awssdk.transfer.s3.model.*; @@ -35,6 +37,9 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; /** * S3 存储协议 所有兼容S3协议的云厂商均支持 @@ -233,6 +238,212 @@ public class OssClient { } } + /** + * 创建分片任务 + * + * @param key 在 Amazon S3 中的对象键 + * @return CreatePartUploadResult 创建上传分片任务返回体,包含上传后的文件信息 + * @throws OssException 如果创建失败,抛出自定义异常 + */ + public CreatePartUploadResult createPartUpload(String key) { + try { + // 创建分片上传 + CompletableFuture createPartUploadFuture = client.createMultipartUpload(builder -> builder.bucket(properties.getBucketName()) + .key(key) + .acl(getAccessPolicy().getObjectCannedACL()) + .build()); + // 等待创建分片上传任务完成 + CreateMultipartUploadResponse createPartUploadResult = createPartUploadFuture.join(); + // 获取分片上传任务 uploadId + String uploadId = createPartUploadResult.uploadId(); + return CreatePartUploadResult.builder().uploadId(uploadId).url(getUrl() + StringUtils.SLASH + key).filename(key).build(); + } catch (Exception e) { + throw new OssException("创建分片上传任务失败,请检查配置信息:[" + e.getMessage() + "]"); + } + } + + /** + * 创建分片任务 + * + * @param suffix 对象键的后缀 + * @return CreatePartUploadResult 创建上传分片任务返回体,包含上传后的文件信息 + * @throws OssException 如果创建失败,抛出自定义异常 + */ + public CreatePartUploadResult createPartUploadSuffix(String suffix) { + return createPartUpload(getPath(properties.getPrefix(), suffix)); + } + + /** + * 分片文件上传 + * + * @param data 文件字节数组 + * @param key 在 Amazon S3 中的对象键 + * @param uploadId oss分片上传id + * @param partNumber 分片序号 + * @return PartUploadResult 分片上传结果,包含上传后的分片文件信息 + * @throws OssException 如果上传失败,抛出自定义异常 + */ + public PartUploadResult partUpload(byte[] data, String key, String uploadId, Integer partNumber) { + try { + // 创建异步请求体 + AsyncRequestBody requestBody = AsyncRequestBody.fromBytes(data); + // 使用 s3client 进行分片上传 + CompletableFuture partUploadFuture = client.uploadPart(builder -> builder.bucket(properties.getBucketName()) + .key(key) + .uploadId(uploadId) + .partNumber(partNumber) + .build(), requestBody); + + // 等待文件上传操作完成 + UploadPartResponse partUploadResult = partUploadFuture.join(); + String eTag = partUploadResult.eTag(); + + // 提取上传结果中的 ETag,并构建一个自定义的 PartUploadResult 对象 + return PartUploadResult.builder().uploadId(uploadId).partNumber(partNumber).partSize((long) data.length).eTag(eTag).build(); + } catch (Exception e) { + throw new OssException("上传分片文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + } + + /** + * 分片文件上传 + * + * @param inputStream 要上传的输入流 + * @param key 在 Amazon S3 中的对象键 + * @param uploadId oss分片上传id + * @param partNumber 分片序号 + * @param partSize 分片大小 + * @return PartUploadResult 分片上传结果,包含上传后的分片文件信息 + * @throws OssException 如果上传失败,抛出自定义异常 + */ + public PartUploadResult partUpload(InputStream inputStream, String key, String uploadId, Integer partNumber, Long partSize) { + if (!(inputStream instanceof ByteArrayInputStream)) { + inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); + } + try { + // 创建异步请求体(length如果为空会报错) + BlockingInputStreamAsyncRequestBody requestBody = AsyncRequestBody.forBlockingInputStream(partSize); + // 使用 s3client 进行分片上传 + CompletableFuture partUploadFuture = client.uploadPart(builder -> builder.bucket(properties.getBucketName()) + .key(key) + .uploadId(uploadId) + .partNumber(partNumber) + .build(), requestBody); + + // 将输入流写入请求体 + requestBody.writeInputStream(inputStream); + + // 等待文件上传操作完成 + UploadPartResponse partUploadResult = partUploadFuture.join(); + String eTag = partUploadResult.eTag(); + + // 提取上传结果中的 ETag,并构建一个自定义的 PartUploadResult 对象 + return PartUploadResult.builder().uploadId(uploadId).partNumber(partNumber).partSize(partSize).eTag(eTag).build(); + } catch (Exception e) { + throw new OssException("上传分片文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + } + + /** + * 终止分片上传任务 + * + * @param uploadId 分片上传任务ID + * @param key 文件在 Amazon S3 中的对象键 + * @return boolean 终止分片上传任务结果 + * @throws OssException 如果终止分片上传失败,抛出自定义异常 + */ + public boolean abortPartUpload(String uploadId, String key) { + try { + // 使用 s3client 请求终止分片上传任务 + CompletableFuture abortPartUploadFuture = client.abortMultipartUpload(builder -> builder.bucket(properties.getBucketName()) + .uploadId(uploadId) + .key(key) + .build()); + // 等待终止分片上传任务请求响应 + AbortMultipartUploadResponse abortPartUploadResult = abortPartUploadFuture.join(); + // 没有异常即成功 + return true; + } catch (Exception e) { +// throw new OssException("终止分片上传任务失败,请检查配置信息:[" + e.getMessage() + "]"); + return false; + } + } + + /** + * 列出已经上传的分片 + * + * @param uploadId 分片上传任务ID + * @param key 文件在 Amazon S3 中的对象键 + * @return List 已经上传的分片信息 + */ + public List listParts(String uploadId, String key) { + // 使用 s3client 请求获取已经上传的分片信息 + CompletableFuture listPartsFuture = client.listParts(builder -> builder.bucket(properties.getBucketName()) + .uploadId(uploadId) + .key(key) + .build()); + // 等待获取已经上传的分片信息请求响应 + ListPartsResponse listPartsResult = listPartsFuture.join(); + return listPartsResult.parts(); + } + + /** + * 完成分片上传 - 合并分片 + * + * @param uploadId 分片上传任务ID + * @param key 文件在 Amazon S3 中的对象键 + * @param completedParts 已经上传的分片信息 + * @return UploadResult 包含上传后的文件信息 + */ + public UploadResult completePartUpload(String uploadId, String key, Collection completedParts) { + // 使用 s3client 请求完成分片上传任务 + CompletableFuture completePartUploadFuture = client.completeMultipartUpload(builder -> builder.bucket(properties.getBucketName()) + .uploadId(uploadId) + .key(key) + .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build()) + .build()); + // 等待完成分片上传任务请求响应 + CompleteMultipartUploadResponse completePartUploadResult = completePartUploadFuture.join(); + // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象 + return UploadResult.builder().url(getUrl() + "/" + key).filename(key).eTag(completePartUploadResult.eTag()).build(); + } + + /** + * 完成分片上传 - 合并分片 + * + * @param uploadId 分片上传任务ID + * @param key 文件在 Amazon S3 中的对象键 + * @param partInfoList 已经上传的分片信息 + * @return UploadResult 包含上传后的文件信息 + */ + public UploadResult completePartUpload(String uploadId, String key, List partInfoList) { + List completedParts = partInfoList.stream() + .map(partInfo -> CompletedPart.builder() + .partNumber(partInfo.getPartNumber()) + .eTag(partInfo.getETag()) + .build()) + .toList(); + return completePartUpload(uploadId, key, completedParts); + } + + /** + * 完成分片上传 - 合并分片(根据已上传的分片结果合并) + * + * @param uploadId 分片上传任务ID + * @param key 文件在 Amazon S3 中的对象键 + * @return UploadResult 包含上传后的文件信息 + */ + public UploadResult completePartUpload(String uploadId, String key) { + // 获取已上传的文件分片列表 + List parts = listParts(uploadId, key); + // 构建 CompletedPart 列表 + List completedParts = parts.stream() + .map(part -> CompletedPart.builder().partNumber(part.partNumber()).eTag(part.eTag()).build()) + .toList(); + // 合并分片并返回结果 + return completePartUpload(uploadId, key, completedParts); + } + /** * 下载文件从 Amazon S3 到临时目录 * diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/CreatePartUploadResult.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/CreatePartUploadResult.java new file mode 100644 index 000000000..8ada7c516 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/CreatePartUploadResult.java @@ -0,0 +1,30 @@ +package org.dromara.common.oss.entity; + +import lombok.Builder; +import lombok.Data; + +/** + * 创建上传分片任务返回体 + * + * @author SunnyDeer0911 + */ +@Data +@Builder +public class CreatePartUploadResult { + + /** + * 文件路径 + */ + private String url; + + /** + * 分片上传ID + */ + private String uploadId; + + /** + * 文件名 + */ + private String filename; + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java new file mode 100644 index 000000000..4a6d621c8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java @@ -0,0 +1,24 @@ +package org.dromara.common.oss.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 分片信息对象 + * + * @author SunnyDeer0911 + */ +@AllArgsConstructor +@Data +public class PartInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Integer partNumber; + + private String eTag; +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java new file mode 100644 index 000000000..d0a5b5fb9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java @@ -0,0 +1,74 @@ +package org.dromara.common.oss.entity; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 分片上传信息对象 + * + * @author SunnyDeer0911 + */ +@Data +public class PartUploadInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 文件上传ID(分片上传扩展字段,文件分片上传S3 OSS返回的唯一标识) + */ + private String uploadId; + + /** + * 文件名 + */ + private String fileName; + + /** + * 原名 + */ + private String originalName; + + /** + * 文件后缀名 + */ + private String fileSuffix; + + /** + * URL地址 + */ + private String url; + + /** + * 文件大小 + */ + private Long fileSize; + + /** + * 服务商 + */ + private String service; + + /** + * 分片大小 + */ + private Long partSize; + + /** + * 分片数量 + */ + private Long totalParts; + + /** + * 合并完成 + */ + private boolean needMerge; + + /** + * 已完成上传的分片列表 + */ + List partInfoList; +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadResult.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadResult.java new file mode 100644 index 000000000..f366d2a74 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadResult.java @@ -0,0 +1,35 @@ +package org.dromara.common.oss.entity; + +import lombok.Builder; +import lombok.Data; + +/** + * 分片上传返回体 + * + * @author SunnyDeer0911 + */ +@Data +@Builder +public class PartUploadResult { + + /** + * 分片上传ID + */ + private String uploadId; + + /** + * 分片序号 + */ + private Integer partNumber; + + /*** + * 分片大小 + */ + private Long partSize; + + /** + * 已上传对象的实体标记(用来校验文件) + */ + private String eTag; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java index 73ada3b84..03d8d7b36 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java @@ -11,6 +11,8 @@ import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.system.domain.bo.SysOssBo; +import org.dromara.system.domain.bo.SysOssPartUploadBo; +import org.dromara.system.domain.vo.SysOssPartUploadVo; import org.dromara.system.domain.vo.SysOssUploadVo; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.service.ISysOssService; @@ -105,4 +107,20 @@ public class SysOssController extends BaseController { return toAjax(ossService.deleteWithValidByIds(List.of(ossIds), true)); } + /** + * 上传OSS对象分片文件 + * + * @param file 分片文件 + * @param bo OSS分片上传业务对象 + * @return R 上传结果 + */ + @SaCheckPermission("system:oss:upload") + @Log(title = "OSS对象存储-分片上传", businessType = BusinessType.INSERT) + @PostMapping(value = "/part/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R partUpload(@RequestPart("file") MultipartFile file, @Validated SysOssPartUploadBo bo) { + if (ObjectUtil.isNull(file)) { + return R.fail("上传文件不能为空"); + } + return R.ok(ossService.partUpload(file, bo)); + } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java new file mode 100644 index 000000000..5c9aa930d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java @@ -0,0 +1,62 @@ +package org.dromara.system.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * OSS分片上传业务对象 + * + * @author SunnyDeer0911 + */ +@Data +public class SysOssPartUploadBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 文件上传ID(分片上传扩展字段,文件分片上传S3 OSS返回的唯一标识) + * TODO 上传第一片分片时允许为空,后续需要从第一次分片上传的响应中获取回 + */ + private String uploadId; + + /** + * 文件名 + * TODO 允许为空,如果为空,则从上传的文件中获取 + */ + private String fileName; + + /** + * 文件大小 + * TODO 文件的总大小,上传第一片分片时不允许为空,后续请求用不到,可传可不传 + */ + @NotNull(message = "文件大小不能为空") + private Long fileSize; + + /** + * 分片序号 + * TODO 分片序号不能为空,从1开始 + */ + @NotNull(message = "分片序号不能为空") + private Integer partNumber; + + /** + * 分片大小 + */ + private Long partSize; + + /** + * 分片数量 + */ + private Long totalParts; + + /** + * 完成状态 + * TODO 允许为空,为空默认 + */ + private Boolean needMerge; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java new file mode 100644 index 000000000..6165c1ee9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java @@ -0,0 +1,44 @@ +package org.dromara.system.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.common.oss.entity.PartInfo; +import org.dromara.common.oss.entity.PartUploadInfo; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 分片上传对象信息VO + * + * @author SunnyDeer0911 + */ +@Data +@AutoMapper(target = PartUploadInfo.class) +public class SysOssPartUploadVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 分片上传ID + */ + private String uploadId; + + /** + * URL地址 + */ + private String url; + + /** + * 已完成上传的分片列表 + */ + List partInfoList; + + /** + * 合并完成 + */ + private Boolean mergeCompleted; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java index 057c068ce..2833aff91 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java @@ -3,6 +3,8 @@ package org.dromara.system.service; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.system.domain.bo.SysOssBo; +import org.dromara.system.domain.bo.SysOssPartUploadBo; +import org.dromara.system.domain.vo.SysOssPartUploadVo; import org.dromara.system.domain.vo.SysOssVo; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; @@ -77,4 +79,13 @@ public interface ISysOssService { */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + /** + * 上传文件分片到对象存储服务 + * + * @param file 要上传的文件对象 + * @param bo OSS分片上传业务对象 + * @return 上传成功后的 SysOssPartUploadVo 对象,包含文件分片上传结果信息 + */ + SysOssPartUploadVo partUpload(MultipartFile file, SysOssPartUploadBo bo); + } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java index 1df221c32..53c882ec5 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java @@ -18,12 +18,16 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.oss.constant.OssConstant; import org.dromara.common.oss.core.OssClient; -import org.dromara.common.oss.entity.UploadResult; +import org.dromara.common.oss.entity.*; import org.dromara.common.oss.enumd.AccessPolicyType; import org.dromara.common.oss.factory.OssFactory; +import org.dromara.common.redis.utils.RedisUtils; import org.dromara.system.domain.SysOss; import org.dromara.system.domain.bo.SysOssBo; +import org.dromara.system.domain.bo.SysOssPartUploadBo; +import org.dromara.system.domain.vo.SysOssPartUploadVo; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.mapper.SysOssMapper; import org.dromara.system.service.ISysOssService; @@ -237,6 +241,73 @@ public class SysOssServiceImpl implements ISysOssService, OssService { return baseMapper.deleteBatchIds(ids) > 0; } + @Override + public SysOssPartUploadVo partUpload(MultipartFile file, SysOssPartUploadBo bo) { + OssClient storage; + PartUploadInfo partUploadInfo; + String uploadId = bo.getUploadId(); + // 如果上传ID为空,则说明是创建任务此时 + if (StringUtils.isBlank(uploadId)) { + storage = OssFactory.instance(); + String originalFileName = bo.getFileName(); + // 如果文件名为空,则使用从上传的文件中获取 + if (StringUtils.isBlank(originalFileName)) { + originalFileName = file.getOriginalFilename(); + } + String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length()); + CreatePartUploadResult partUpload = storage.createPartUploadSuffix(suffix); + uploadId = partUpload.getUploadId(); + + partUploadInfo = new PartUploadInfo(); + partUploadInfo.setUploadId(uploadId); + partUploadInfo.setFileName(partUpload.getFilename()); + partUploadInfo.setOriginalName(originalFileName); + partUploadInfo.setFileSuffix(suffix); + partUploadInfo.setUrl(partUpload.getUrl()); + partUploadInfo.setFileSize(bo.getFileSize()); + partUploadInfo.setService(storage.getConfigKey()); + partUploadInfo.setPartSize(bo.getPartSize()); + partUploadInfo.setTotalParts(bo.getTotalParts()); + partUploadInfo.setPartInfoList(new ArrayList<>()); + partUploadInfo.setNeedMerge(bo.getNeedMerge()); + } else { + // uploadId不为空,且文件不为空,则进行文件上传 + partUploadInfo = RedisUtils.getCacheObject(OssConstant.PART_UPLOAD_INFO_CACHE_KEY + uploadId); + partUploadInfo.setNeedMerge(bo.getNeedMerge()); + storage = OssFactory.instance(partUploadInfo.getService()); + } + return partUpload(storage, file, partUploadInfo, bo.getPartNumber()); + } + + private SysOssPartUploadVo partUpload(OssClient storage, MultipartFile file, PartUploadInfo partUploadInfo, Integer partNumber) { + try { + String uploadId = partUploadInfo.getUploadId(); + PartUploadResult partUploadResult = storage.partUpload(file.getInputStream(), partUploadInfo.getFileName(), partUploadInfo.getUploadId(), partNumber, file.getSize()); + // 将完成上传的分片信息放入集合中 + partUploadInfo.getPartInfoList().add(new PartInfo(partUploadResult.getPartNumber(), partUploadResult.getETag())); + // 分片上传信息放入redis + RedisUtils.setCacheObject(OssConstant.PART_UPLOAD_INFO_CACHE_KEY + partUploadInfo.getUploadId(), partUploadInfo); + // 检查是否需要合并 - 已经完成所有上传时操作 + if (partUploadInfo.isNeedMerge()) { + // 如果已经完成最后一片的上传,则进行合并 + UploadResult uploadResult = storage.completePartUpload(uploadId, partUploadInfo.getFileName(), partUploadInfo.getPartInfoList()); + // 不报错即合并成功,删除redis中的分片上传信息 + RedisUtils.deleteObject(OssConstant.PART_UPLOAD_INFO_CACHE_KEY + uploadId); + // 数据落库 + SysOss oss = new SysOss(); + oss.setFileSuffix(partUploadInfo.getFileSuffix()); + oss.setOriginalName(partUploadInfo.getOriginalName()); + oss.setService(storage.getConfigKey()); + oss.setFileName(partUploadInfo.getFileName()); + oss.setUrl(partUploadInfo.getUrl()); + baseMapper.insert(oss); + } + return MapstructUtils.convert(partUploadInfo, SysOssPartUploadVo.class); + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + /** * 桶类型为 private 的URL 修改为临时URL时长为120s * From ba0d03f6fbc9fe9648aa867bbc6de2d0f656741b Mon Sep 17 00:00:00 2001 From: Liang <545073804@qq.com> Date: Fri, 29 Mar 2024 18:55:13 +0800 Subject: [PATCH 2/7] =?UTF-8?q?update=20=E6=9B=B4=E6=96=B0=E5=88=86?= =?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E7=BB=93=E6=9E=9C=E5=87=BA=E5=8F=82?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/dromara/common/oss/entity/PartInfo.java | 2 ++ .../org/dromara/system/service/impl/SysOssServiceImpl.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java index 4a6d621c8..8d89ac7e5 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java @@ -2,6 +2,7 @@ package org.dromara.common.oss.entity; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serial; import java.io.Serializable; @@ -11,6 +12,7 @@ import java.io.Serializable; * * @author SunnyDeer0911 */ +@NoArgsConstructor @AllArgsConstructor @Data public class PartInfo implements Serializable { diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java index 53c882ec5..fbe8a2a93 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java @@ -302,7 +302,9 @@ public class SysOssServiceImpl implements ISysOssService, OssService { oss.setUrl(partUploadInfo.getUrl()); baseMapper.insert(oss); } - return MapstructUtils.convert(partUploadInfo, SysOssPartUploadVo.class); + SysOssPartUploadVo sysOssPartUploadVo = MapstructUtils.convert(partUploadInfo, SysOssPartUploadVo.class); + sysOssPartUploadVo.setMergeCompleted(partUploadInfo.isNeedMerge()); + return sysOssPartUploadVo; } catch (Exception e) { throw new ServiceException(e.getMessage()); } From 0fb0555bc48555859fdf6da2a71bcceeccc96390 Mon Sep 17 00:00:00 2001 From: L <545073804@qq.com> Date: Sun, 31 Mar 2024 01:16:42 +0800 Subject: [PATCH 3/7] =?UTF-8?q?update=20=E6=9B=B4=E6=96=B0=E5=88=86?= =?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/common/oss/entity/PartInfo.java | 6 + .../common/oss/entity/PartUploadInfo.java | 11 +- .../oss/utils/PartUploadCacheHelper.java | 101 +++++++++++ .../system/domain/bo/SysOssPartUploadBo.java | 30 ++-- .../system/domain/vo/SysOssPartUploadVo.java | 3 - .../service/impl/SysOssServiceImpl.java | 164 +++++++++++------- 6 files changed, 228 insertions(+), 87 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java index 8d89ac7e5..75c583439 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartInfo.java @@ -20,7 +20,13 @@ public class PartInfo implements Serializable { @Serial private static final long serialVersionUID = 1L; + /** + * 分片序号 + */ private Integer partNumber; + /** + * 已上传分片的实体标记(用来校验文件) + */ private String eTag; } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java index d0a5b5fb9..d3230a241 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java @@ -1,6 +1,9 @@ package org.dromara.common.oss.entity; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serial; import java.io.Serializable; @@ -8,17 +11,21 @@ import java.util.List; /** * 分片上传信息对象 + * TODO 用于创建分片上传任务时,数据落库之前,存放分片上传信息数据到Redis * * @author SunnyDeer0911 */ +@NoArgsConstructor +@AllArgsConstructor @Data +@Builder public class PartUploadInfo implements Serializable { @Serial private static final long serialVersionUID = 1L; /** - * 文件上传ID(分片上传扩展字段,文件分片上传S3 OSS返回的唯一标识) + * 文件上传ID(创建分片上传任务时,S3 OSS返回的唯一标识) */ private String uploadId; @@ -28,7 +35,7 @@ public class PartUploadInfo implements Serializable { private String fileName; /** - * 原名 + * 原文件名 */ private String originalName; diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java new file mode 100644 index 000000000..d4f3284b1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java @@ -0,0 +1,101 @@ +package org.dromara.common.oss.utils; + +import cn.hutool.core.collection.CollUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.oss.constant.OssConstant; +import org.dromara.common.oss.core.OssClient; +import org.dromara.common.oss.entity.PartUploadInfo; +import org.dromara.common.oss.factory.OssFactory; +import org.dromara.common.redis.utils.RedisUtils; + +import java.util.Collection; + +/** + * 分片上传缓存助手 + * + * @author SunnyDeer0911 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class PartUploadCacheHelper { + + /** + * 获取分片上传信息cacheKey + * @param uploadId 文件分片上传ID + * @return 分片上传信息cacheKey + */ + public static String getPartUploadInfoCacheKey(String uploadId){ + if (StringUtils.isBlank(uploadId)) { + return null; + } + return OssConstant.PART_UPLOAD_INFO_CACHE_KEY + uploadId; + } + + /** + * 从缓存中获取分片上传信息对象 + * @param uploadId 文件分片上传ID + * @return 获取分片上传信息对象 + */ + public static PartUploadInfo getCache(String uploadId){ + String cacheKey = getPartUploadInfoCacheKey(uploadId); + if (cacheKey==null) { + return null; + } + return RedisUtils.getCacheObject(cacheKey); + } + + /** + * 将分片上传信息对象放入缓存 + * @param partUploadInfo 分片上传信息对象 + */ + public static void putCache(PartUploadInfo partUploadInfo){ + String cacheKey = getPartUploadInfoCacheKey(partUploadInfo.getUploadId()); + if (cacheKey==null) { + return; + } + RedisUtils.setCacheObject(cacheKey, partUploadInfo); + } + + /** + * 移除分片上传信息对象缓存 + * @param uploadId 文件分片上传ID + */ + public static void removeCache(String uploadId){ + String cacheKey = getPartUploadInfoCacheKey(uploadId); + if (cacheKey==null) { + return; + } + removeCacheByKey(cacheKey); + } + + /** + * 移除分片上传信息对象缓存 + * @param uploadId 文件分片上传ID + */ + private static void removeCacheByKey(String cacheKey){ + PartUploadInfo cache = RedisUtils.getCacheObject(cacheKey); + if (cache==null) { + return; + } + // 如果未合并,啧调用OssClient终止分片上传任务的方法,让OSS删除已上传的分片 + if (!cache.isNeedMerge() && CollUtil.isNotEmpty(cache.getPartInfoList())) { + OssClient instance = OssFactory.instance(cache.getService()); + // 终止分片上传任务 + instance.abortPartUpload(cache.getUploadId(),cache.getFileName()); + } + RedisUtils.deleteObject(cacheKey); + } + + /** + * 移除所有分片上传信息对象缓存 + */ + public static void removeAllCache(){ + Collection removeKeys = RedisUtils.keys(OssConstant.PART_UPLOAD_INFO_CACHE_KEY + "*"); + if (CollUtil.isEmpty(removeKeys)) { + return; + } + removeKeys.forEach(PartUploadCacheHelper::removeCacheByKey); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java index 5c9aa930d..0291ce95f 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java @@ -1,5 +1,6 @@ package org.dromara.system.domain.bo; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -18,45 +19,34 @@ public class SysOssPartUploadBo implements Serializable { private static final long serialVersionUID = 1L; /** - * 文件上传ID(分片上传扩展字段,文件分片上传S3 OSS返回的唯一标识) - * TODO 上传第一片分片时允许为空,后续需要从第一次分片上传的响应中获取回 + * 文件上传ID(创建分片上传任务时,S3 OSS返回的唯一标识) + * TODO 上传第一片分片时允许为空,后续需要从第一次分片上传的响应中获取回填 */ private String uploadId; /** * 文件名 - * TODO 允许为空,如果为空,则从上传的文件中获取 + * TODO 非必填,如果为空则从上传的文件中获取 */ +// @NotBlank(message = "文件名不能为空") private String fileName; /** - * 文件大小 - * TODO 文件的总大小,上传第一片分片时不允许为空,后续请求用不到,可传可不传 + * 文件大小(文件的总大小) */ @NotNull(message = "文件大小不能为空") private Long fileSize; /** - * 分片序号 - * TODO 分片序号不能为空,从1开始 + * 分片序号*(分片序号从1开始) */ @NotNull(message = "分片序号不能为空") + @Min(value = 1, message = "分片序号不能小于1") private Integer partNumber; /** - * 分片大小 - */ - private Long partSize; - - /** - * 分片数量 - */ - private Long totalParts; - - /** - * 完成状态 - * TODO 允许为空,为空默认 + * 是否需要合并 */ + @NotNull(message = "是否需要合并不能为空") private Boolean needMerge; - } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java index 6165c1ee9..784f0af06 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java @@ -1,9 +1,7 @@ package org.dromara.system.domain.vo; -import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; import org.dromara.common.oss.entity.PartInfo; -import org.dromara.common.oss.entity.PartUploadInfo; import java.io.Serial; import java.io.Serializable; @@ -15,7 +13,6 @@ import java.util.List; * @author SunnyDeer0911 */ @Data -@AutoMapper(target = PartUploadInfo.class) public class SysOssPartUploadVo implements Serializable { @Serial diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java index fbe8a2a93..ec633ffd5 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java @@ -18,12 +18,11 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.oss.constant.OssConstant; import org.dromara.common.oss.core.OssClient; import org.dromara.common.oss.entity.*; import org.dromara.common.oss.enumd.AccessPolicyType; import org.dromara.common.oss.factory.OssFactory; -import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.common.oss.utils.PartUploadCacheHelper; import org.dromara.system.domain.SysOss; import org.dromara.system.domain.bo.SysOssBo; import org.dromara.system.domain.bo.SysOssPartUploadBo; @@ -243,73 +242,114 @@ public class SysOssServiceImpl implements ISysOssService, OssService { @Override public SysOssPartUploadVo partUpload(MultipartFile file, SysOssPartUploadBo bo) { - OssClient storage; - PartUploadInfo partUploadInfo; - String uploadId = bo.getUploadId(); - // 如果上传ID为空,则说明是创建任务此时 - if (StringUtils.isBlank(uploadId)) { - storage = OssFactory.instance(); - String originalFileName = bo.getFileName(); - // 如果文件名为空,则使用从上传的文件中获取 - if (StringUtils.isBlank(originalFileName)) { - originalFileName = file.getOriginalFilename(); - } - String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length()); - CreatePartUploadResult partUpload = storage.createPartUploadSuffix(suffix); - uploadId = partUpload.getUploadId(); - - partUploadInfo = new PartUploadInfo(); - partUploadInfo.setUploadId(uploadId); - partUploadInfo.setFileName(partUpload.getFilename()); - partUploadInfo.setOriginalName(originalFileName); - partUploadInfo.setFileSuffix(suffix); - partUploadInfo.setUrl(partUpload.getUrl()); - partUploadInfo.setFileSize(bo.getFileSize()); - partUploadInfo.setService(storage.getConfigKey()); - partUploadInfo.setPartSize(bo.getPartSize()); - partUploadInfo.setTotalParts(bo.getTotalParts()); - partUploadInfo.setPartInfoList(new ArrayList<>()); - partUploadInfo.setNeedMerge(bo.getNeedMerge()); - } else { - // uploadId不为空,且文件不为空,则进行文件上传 - partUploadInfo = RedisUtils.getCacheObject(OssConstant.PART_UPLOAD_INFO_CACHE_KEY + uploadId); - partUploadInfo.setNeedMerge(bo.getNeedMerge()); - storage = OssFactory.instance(partUploadInfo.getService()); - } - return partUpload(storage, file, partUploadInfo, bo.getPartNumber()); - } - - private SysOssPartUploadVo partUpload(OssClient storage, MultipartFile file, PartUploadInfo partUploadInfo, Integer partNumber) { try { - String uploadId = partUploadInfo.getUploadId(); - PartUploadResult partUploadResult = storage.partUpload(file.getInputStream(), partUploadInfo.getFileName(), partUploadInfo.getUploadId(), partNumber, file.getSize()); - // 将完成上传的分片信息放入集合中 - partUploadInfo.getPartInfoList().add(new PartInfo(partUploadResult.getPartNumber(), partUploadResult.getETag())); - // 分片上传信息放入redis - RedisUtils.setCacheObject(OssConstant.PART_UPLOAD_INFO_CACHE_KEY + partUploadInfo.getUploadId(), partUploadInfo); - // 检查是否需要合并 - 已经完成所有上传时操作 - if (partUploadInfo.isNeedMerge()) { - // 如果已经完成最后一片的上传,则进行合并 - UploadResult uploadResult = storage.completePartUpload(uploadId, partUploadInfo.getFileName(), partUploadInfo.getPartInfoList()); - // 不报错即合并成功,删除redis中的分片上传信息 - RedisUtils.deleteObject(OssConstant.PART_UPLOAD_INFO_CACHE_KEY + uploadId); - // 数据落库 - SysOss oss = new SysOss(); - oss.setFileSuffix(partUploadInfo.getFileSuffix()); - oss.setOriginalName(partUploadInfo.getOriginalName()); - oss.setService(storage.getConfigKey()); - oss.setFileName(partUploadInfo.getFileName()); - oss.setUrl(partUploadInfo.getUrl()); - baseMapper.insert(oss); + String uploadId = bo.getUploadId(); + + // 如果 uploadId 为空,则说明是新建上传分片 + if (StringUtils.isBlank(uploadId)) { + return createPartUpload(file, bo); } - SysOssPartUploadVo sysOssPartUploadVo = MapstructUtils.convert(partUploadInfo, SysOssPartUploadVo.class); - sysOssPartUploadVo.setMergeCompleted(partUploadInfo.isNeedMerge()); - return sysOssPartUploadVo; + + // uploadId不为空,从缓存中获取分片上传信息 + PartUploadInfo partUploadInfo = PartUploadCacheHelper.getCache(uploadId); + if (ObjectUtil.isNull(partUploadInfo)) { + throw new ServiceException("未找到分片上传信息!"); + } + // TODO 是否需要合并分片 + partUploadInfo.setNeedMerge(bo.getNeedMerge()); + OssClient storage = OssFactory.instance(partUploadInfo.getService()); + // 上传分片并返回结果 + return partUpload(storage, partUploadInfo, file, bo.getPartNumber()); } catch (Exception e) { throw new ServiceException(e.getMessage()); } } + /** + * 创建并上传分片 + * + * @param file 需要进行上传的分片文件 + * @param bo OSS分片上传业务对象 + * @return 分片上传对象信息VO + */ + private SysOssPartUploadVo createPartUpload(MultipartFile file, SysOssPartUploadBo bo) throws IOException { + // 文件大小 + Long fileSize = bo.getFileSize(); + // 获取第一片文件的大小作为分片大小 + long partSize = file.getSize(); + // 计算总片数 + long totalParts = fileSize / partSize; + // 当除以分数有余数时每分片总数+1 + if (fileSize % partSize > 0) { + totalParts += 1; + } + // 获取文件名,如果文件名为空,则使用从上传的文件中获取 + String originalFileName = bo.getFileName(); + if (StringUtils.isBlank(originalFileName)) { + originalFileName = file.getOriginalFilename(); + } + String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length()); + OssClient storage = OssFactory.instance(); + CreatePartUploadResult partUpload = storage.createPartUploadSuffix(suffix); + // 构建分片上传信息对象 + PartUploadInfo partUploadInfo = new PartUploadInfo(); + partUploadInfo.setUploadId(partUpload.getUploadId()); + partUploadInfo.setFileName(partUpload.getFilename()); + partUploadInfo.setOriginalName(originalFileName); + partUploadInfo.setFileSuffix(suffix); + partUploadInfo.setUrl(partUpload.getUrl()); + partUploadInfo.setFileSize(fileSize); + partUploadInfo.setService(storage.getConfigKey()); + partUploadInfo.setPartSize(partSize); + partUploadInfo.setTotalParts(totalParts); + partUploadInfo.setPartInfoList(new ArrayList<>()); + // TODO 是否需要合并分片 + partUploadInfo.setNeedMerge(bo.getNeedMerge()); + // 上传分片并返回结果 + return partUpload(storage, partUploadInfo, file, bo.getPartNumber()); + } + + /** + * 上传分片 + * + * @param storage OSS客户端 + * @param partUploadInfo 分片上传信息对象 + * @param file 需要进行上传的分片文件 + * @param partNumber 分片序号 + * @return 分片上传对象信息VO + */ + private SysOssPartUploadVo partUpload(OssClient storage, PartUploadInfo partUploadInfo, MultipartFile file, Integer partNumber) throws IOException { + String uploadId = partUploadInfo.getUploadId(); + // 上传分片 + PartUploadResult partUploadResult = storage.partUpload(file.getInputStream(), partUploadInfo.getFileName(), uploadId, partNumber, file.getSize()); + // 将完成上传的分片信息放入集合中 + partUploadInfo.getPartInfoList().add(new PartInfo(partUploadResult.getPartNumber(), partUploadResult.getETag())); + // 分片上传信息放入缓存 + PartUploadCacheHelper.putCache(partUploadInfo); + // 检查是否需要合并 - 已经完成所有上传时操作 + if (partUploadInfo.isNeedMerge()) { + // 如果已经完成最后一片的上传,则进行合并 + UploadResult uploadResult = storage.completePartUpload(uploadId, partUploadInfo.getFileName(), partUploadInfo.getPartInfoList()); + // 不报错即合并成功,删除缓存中的分片上传信息 + PartUploadCacheHelper.removeCache(uploadId); + // 数据落库 + SysOss oss = new SysOss(); + oss.setFileSuffix(partUploadInfo.getFileSuffix()); + oss.setOriginalName(partUploadInfo.getOriginalName()); + oss.setService(storage.getConfigKey()); + oss.setFileName(partUploadInfo.getFileName()); + oss.setUrl(partUploadInfo.getUrl()); + baseMapper.insert(oss); + } + // 构建分片上传对象信息VO + SysOssPartUploadVo sysOssPartUploadVo = new SysOssPartUploadVo(); + sysOssPartUploadVo.setUploadId(uploadId); + sysOssPartUploadVo.setUrl(partUploadInfo.getUrl()); + sysOssPartUploadVo.setPartInfoList(partUploadInfo.getPartInfoList()); + sysOssPartUploadVo.setMergeCompleted(partUploadInfo.isNeedMerge()); + return sysOssPartUploadVo; + } + /** * 桶类型为 private 的URL 修改为临时URL时长为120s * From 614afb3d7fb71c8571468f9999c19a58e44c6415 Mon Sep 17 00:00:00 2001 From: Liang <545073804@qq.com> Date: Mon, 1 Apr 2024 10:15:04 +0800 Subject: [PATCH 4/7] =?UTF-8?q?update=20=E6=9B=B4=E6=96=B0=E5=85=A5?= =?UTF-8?q?=E5=8F=82/=E5=87=BA=E5=8F=82=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E5=8E=9F=E5=A7=8B=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=97=A0=E6=B3=95=E6=AD=A3=E7=A1=AE=E4=BB=8E?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=9A=84=E5=88=86=E7=89=87=E4=B8=AD=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E8=8E=B7=E5=8F=96=EF=BC=8C=E8=A6=81=E6=B1=82=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E8=BF=9B=E8=A1=8C=E4=BC=A0=E9=80=92=EF=BC=8C?= =?UTF-8?q?=E5=88=86=E7=89=87=E5=90=88=E5=B9=B6=E6=88=90=E5=8A=9F=E6=97=B6?= =?UTF-8?q?=E5=9B=9E=E5=A1=ABossId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/domain/bo/SysOssPartUploadBo.java | 16 +++++++-- .../system/domain/vo/SysOssPartUploadVo.java | 6 ++++ .../service/impl/SysOssServiceImpl.java | 33 +++++++++---------- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java index 0291ce95f..295c82900 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssPartUploadBo.java @@ -1,6 +1,7 @@ package org.dromara.system.domain.bo; import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -26,9 +27,8 @@ public class SysOssPartUploadBo implements Serializable { /** * 文件名 - * TODO 非必填,如果为空则从上传的文件中获取 */ -// @NotBlank(message = "文件名不能为空") + @NotBlank(message = "文件名不能为空") private String fileName; /** @@ -44,6 +44,18 @@ public class SysOssPartUploadBo implements Serializable { @Min(value = 1, message = "分片序号不能小于1") private Integer partNumber; + /** + * 分片大小 + */ +// @NotNull(message = "分片大小不能为空") + private Long partSize; + + /** + * 分片数量 + */ +// @NotNull(message = "分片数量不能为空") + private Long totalParts; + /** * 是否需要合并 */ diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java index 784f0af06..335200d83 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssPartUploadVo.java @@ -38,4 +38,10 @@ public class SysOssPartUploadVo implements Serializable { */ private Boolean mergeCompleted; + /** + * 分片上传ID - 仅在文件成功合并,数据落库时才会生成 + * @see this#mergeCompleted 分片文件合并状态 + */ + private Long ossId; + } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java index ec633ffd5..46563f409 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java @@ -275,19 +275,13 @@ public class SysOssServiceImpl implements ISysOssService, OssService { private SysOssPartUploadVo createPartUpload(MultipartFile file, SysOssPartUploadBo bo) throws IOException { // 文件大小 Long fileSize = bo.getFileSize(); - // 获取第一片文件的大小作为分片大小 - long partSize = file.getSize(); - // 计算总片数 - long totalParts = fileSize / partSize; - // 当除以分数有余数时每分片总数+1 - if (fileSize % partSize > 0) { - totalParts += 1; - } - // 获取文件名,如果文件名为空,则使用从上传的文件中获取 + // 分片大小 + Long partSize = bo.getPartSize(); + // 分片数量 + Long totalParts = bo.getTotalParts(); + // 文件名 String originalFileName = bo.getFileName(); - if (StringUtils.isBlank(originalFileName)) { - originalFileName = file.getOriginalFilename(); - } + // 从文件名中获取文件后缀 String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length()); OssClient storage = OssFactory.instance(); CreatePartUploadResult partUpload = storage.createPartUploadSuffix(suffix); @@ -319,7 +313,14 @@ public class SysOssServiceImpl implements ISysOssService, OssService { * @return 分片上传对象信息VO */ private SysOssPartUploadVo partUpload(OssClient storage, PartUploadInfo partUploadInfo, MultipartFile file, Integer partNumber) throws IOException { + // 文件上传ID String uploadId = partUploadInfo.getUploadId(); + // 构建分片上传对象信息VO + SysOssPartUploadVo sysOssPartUploadVo = new SysOssPartUploadVo(); + sysOssPartUploadVo.setUploadId(uploadId); + sysOssPartUploadVo.setUrl(partUploadInfo.getUrl()); + sysOssPartUploadVo.setPartInfoList(partUploadInfo.getPartInfoList()); + sysOssPartUploadVo.setMergeCompleted(partUploadInfo.isNeedMerge()); // 上传分片 PartUploadResult partUploadResult = storage.partUpload(file.getInputStream(), partUploadInfo.getFileName(), uploadId, partNumber, file.getSize()); // 将完成上传的分片信息放入集合中 @@ -340,13 +341,9 @@ public class SysOssServiceImpl implements ISysOssService, OssService { oss.setFileName(partUploadInfo.getFileName()); oss.setUrl(partUploadInfo.getUrl()); baseMapper.insert(oss); + // 回填对象存储ID + sysOssPartUploadVo.setOssId(oss.getOssId()); } - // 构建分片上传对象信息VO - SysOssPartUploadVo sysOssPartUploadVo = new SysOssPartUploadVo(); - sysOssPartUploadVo.setUploadId(uploadId); - sysOssPartUploadVo.setUrl(partUploadInfo.getUrl()); - sysOssPartUploadVo.setPartInfoList(partUploadInfo.getPartInfoList()); - sysOssPartUploadVo.setMergeCompleted(partUploadInfo.isNeedMerge()); return sysOssPartUploadVo; } From ec03d89919846bfbb9c9f419402b28b5b9b63e31 Mon Sep 17 00:00:00 2001 From: Liang <545073804@qq.com> Date: Mon, 1 Apr 2024 10:20:17 +0800 Subject: [PATCH 5/7] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=20=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E5=8F=82=E6=95=B0=E5=90=8D=E7=A7=B0=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/common/oss/utils/PartUploadCacheHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java index d4f3284b1..af271c116 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java @@ -71,7 +71,7 @@ public class PartUploadCacheHelper { /** * 移除分片上传信息对象缓存 - * @param uploadId 文件分片上传ID + * @param cacheKey 缓存key */ private static void removeCacheByKey(String cacheKey){ PartUploadInfo cache = RedisUtils.getCacheObject(cacheKey); From 67f110565fc72e720f87d71337c668ed74fb5e16 Mon Sep 17 00:00:00 2001 From: Liang <545073804@qq.com> Date: Mon, 1 Apr 2024 10:22:29 +0800 Subject: [PATCH 6/7] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=20=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E5=8F=82=E6=95=B0=E5=90=8D=E7=A7=B0=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/dromara/common/oss/entity/PartUploadInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java index d3230a241..4e4ef31a8 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadInfo.java @@ -70,7 +70,7 @@ public class PartUploadInfo implements Serializable { private Long totalParts; /** - * 合并完成 + * 是否需要合并 */ private boolean needMerge; From 9edb4fb95ed309cd041f0fa86a8510abd6a7097c Mon Sep 17 00:00:00 2001 From: Liang <545073804@qq.com> Date: Mon, 1 Apr 2024 10:25:53 +0800 Subject: [PATCH 7/7] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E9=94=99=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/common/oss/utils/PartUploadCacheHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java index af271c116..4a8a04184 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/PartUploadCacheHelper.java @@ -78,7 +78,7 @@ public class PartUploadCacheHelper { if (cache==null) { return; } - // 如果未合并,啧调用OssClient终止分片上传任务的方法,让OSS删除已上传的分片 + // 如果未合并,则调用OssClient终止分片上传任务的方法,让OSS删除已上传的分片 if (!cache.isNeedMerge() && CollUtil.isNotEmpty(cache.getPartInfoList())) { OssClient instance = OssFactory.instance(cache.getService()); // 终止分片上传任务