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] =?UTF-8?q?update=20=E6=96=B0=E5=A2=9E=E5=88=86=E7=89=87?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=80=BB=E8=BE=91=EF=BC=8C=E5=9C=A8=E4=B8=8D?= =?UTF-8?q?=E6=94=B9=E8=A1=A8=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B=E5=AE=9E?= =?UTF-8?q?=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 *