From 95a97ab06ccedcf9731f7dfb0e29d95ec2cd8d4f Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Mon, 15 Apr 2024 12:14:47 +0800 Subject: [PATCH 1/4] =?UTF-8?q?update=20oss=E5=A2=9E=E5=8A=A0=E5=88=86?= =?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E5=B7=A5=E5=85=B7=EF=BC=8C=E4=B8=B4?= =?UTF-8?q?=E6=97=B6url=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/common/oss/core/OssClient.java | 140 +++++++++++++++++- .../common/oss/entity/PartUploadResult.java | 25 ++++ .../common/oss/entity/UploadResult.java | 12 ++ 3 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadResult.java 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..d30b21d9e 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,7 @@ 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.PartUploadResult; import org.dromara.common.oss.entity.UploadResult; import org.dromara.common.oss.enumd.AccessPolicyType; import org.dromara.common.oss.enumd.PolicyType; @@ -19,7 +20,9 @@ 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.CompletedPart; import software.amazon.awssdk.services.s3.model.NoSuchBucketException; +import software.amazon.awssdk.services.s3.model.Part; import software.amazon.awssdk.services.s3.model.S3Exception; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.transfer.s3.S3TransferManager; @@ -35,6 +38,8 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; /** * S3 存储协议 所有兼容S3协议的云厂商均支持 @@ -274,10 +279,11 @@ public class OssClient { } /** - * 获取私有URL链接 + * 生成预签名的私有对象获取 URL * - * @param objectKey 对象KEY - * @param second 授权时间 + * @param objectKey 对象键(Object Key) + * @param second 签名持续时间(秒) + * @return 预签名 URL 字符串 */ public String getPrivateUrl(String objectKey, Integer second) { // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL @@ -292,6 +298,122 @@ public class OssClient { return url.toString(); } + /** + * 生成预签名的私有上传 URL + * + * @param key 对象键(Object Key) + * @param md5Digest 内容的 MD5 摘要,如果有的话 + * @param second 签名持续时间(秒) + * @return 上传结果对象 + */ + public UploadResult putPrivateUrl(String key, String md5Digest, Integer second) { + URL url = presigner.presignPutObject( + x -> x.signatureDuration(Duration.ofSeconds(second)) + .putObjectRequest( + y -> y.bucket(properties.getBucketName()) + .key(key) + .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null) + .build() + ).build() + ).url(); + return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).privateUrl(url.toString()).build(); + } + + /** + * 创建分片上传任务 + * + * @param key 对象的键(Object Key) + * @return 包含上传 ID 的 UploadResult 对象 + */ + public UploadResult initiateMultipartUpload(String key) { + String uploadId = client.createMultipartUpload( + x -> x.bucket(properties.getBucketName()) + .key(key) + .build() + ).join().uploadId(); + return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).uploadId(uploadId).build(); + } + + /** + * 生成预签名的分片上传 URL + * + * @param key 对象键(Object Key) + * @param uploadId 分片上传任务的 Upload ID + * @param partNumber 分片编号 + * @param second 签名持续时间(秒) + * @return 预签名 URL 字符串 + */ + public String uploadPartFutures(String key, String uploadId, Integer partNumber, Integer second) { + URL url = presigner.presignUploadPart( + x -> x.signatureDuration(Duration.ofSeconds(second)) + .uploadPartRequest( + y -> y.bucket(properties.getBucketName()) + .key(key) + .uploadId(uploadId) + .partNumber(partNumber) + .build() + ).build() + ).url(); + return url.toString(); + } + + /** + * 获取指定对象分片上传的分片列表 + * + * @param key 对象的键(Object Key) + * @param uploadId 分片上传任务的 Upload ID + * @param maxParts 最大返回的分片数(默认为1000) + * @param partNumberMarker 分片编号的标记,用于分页查询(默认为0,表示从第一个分片开始查询) + * @return 包含分片上传结果信息的 PartUploadResult 对象列表 + */ + public List listParts(String key, String uploadId, Integer maxParts, Integer partNumberMarker) { + List parts = client.listParts( + x -> x.bucket(properties.getBucketName()) + .key(key) + .uploadId(uploadId) + .maxParts(maxParts != null ? maxParts : 1000) + .partNumberMarker(partNumberMarker != null ? partNumberMarker : 0) + .build()).join().parts(); + return parts.stream() + .map(x -> PartUploadResult.builder() + .partNumber(x.partNumber()) + .eTag(x.eTag()) + .build()) + .collect(Collectors.toList()); + } + + /** + * 完成分片上传任务 + * + * @param key 对象的键(Object Key) + * @param uploadId 分片上传任务的 Upload ID + * @param partUploadResults 已完成的分片列表 + * @return 包含上传结果信息的 UploadResult 对象 + */ + public UploadResult completeMultipartUpload(String key, String uploadId, List partUploadResults) { + List completedParts = partUploadResults.stream() + .map(x -> CompletedPart.builder() + .partNumber(x.getPartNumber()) + .eTag(x.getETag()) + .build()) + .collect(Collectors.toList()); + try { + String eTag = client.completeMultipartUpload( + x -> x.bucket(properties.getBucketName()) + .key(key) + .uploadId(uploadId) + .multipartUpload(y -> y.parts(completedParts) + .build()) + .build() + ).join().eTag(); + // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象 + return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build(); + } catch (Exception e) { + // 捕获异常并抛出自定义异常 + throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + } + /** * 上传 byte[] 数据到 Amazon S3,使用指定的后缀构造对象键。 * @@ -329,6 +451,18 @@ public class OssClient { return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null); } + /** + * 并生成私有上传预签名 URL + * + * @param suffix 文件后缀 + * @param md5Digest 内容的 MD5 摘要,如果有的话 + * @param second 预签名 URL 的有效期(秒) + * @return 上传结果对象 + */ + public UploadResult uploadSuffix(String suffix, String md5Digest, Integer second) { + return putPrivateUrl(getPath(properties.getPrefix(), suffix), md5Digest, second); + } + /** * 获取文件输入流 * 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..dee893df2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/PartUploadResult.java @@ -0,0 +1,25 @@ +package org.dromara.common.oss.entity; + +import lombok.Builder; +import lombok.Data; + +/** + * 上传部件返回体 + * + * @author AprilWind + */ +@Data +@Builder +public class PartUploadResult { + + /** + * 新上传部件的部件号 + */ + private Integer partNumber; + + /** + * 从上传部分的内容生成的实体标签 + */ + private String eTag; + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java index 81a18e62a..4c9c32a56 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java @@ -27,4 +27,16 @@ public class UploadResult { */ private String eTag; + /** + * 用于分片上传任务的 Upload ID + * 在初始化分片上传时获取,并在后续的分片上传和完成上传过程中使用 + */ + private String uploadId; + + /** + * 用于私有预签名 URL + * 用于上传文件或分片的预签名 URL,确保文件或分片在上传时具有私有访问权限 + */ + private String privateUrl; + } From bd8994f0debca298b16568ed6be858fa7d45a8e9 Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Mon, 15 Apr 2024 12:37:47 +0800 Subject: [PATCH 2/4] =?UTF-8?q?update=20oss=E5=B7=A5=E5=85=B7=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=8A=A5=E9=94=99=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/common/oss/core/OssClient.java | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) 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 d30b21d9e..ccd09b04d 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 @@ -1,5 +1,6 @@ package org.dromara.common.oss.core; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; import org.dromara.common.core.constant.Constants; @@ -326,12 +327,17 @@ public class OssClient { * @return 包含上传 ID 的 UploadResult 对象 */ public UploadResult initiateMultipartUpload(String key) { - String uploadId = client.createMultipartUpload( - x -> x.bucket(properties.getBucketName()) - .key(key) - .build() - ).join().uploadId(); - return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).uploadId(uploadId).build(); + try { + String uploadId = client.createMultipartUpload( + x -> x.bucket(properties.getBucketName()) + .key(key) + .build() + ).join().uploadId(); + return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).uploadId(uploadId).build(); + } catch (Exception e) { + // 捕获异常并抛出自定义异常 + throw new OssException("创建分片上传任务失败,请检查配置信息:[" + e.getMessage() + "]"); + } } /** @@ -367,19 +373,24 @@ public class OssClient { * @return 包含分片上传结果信息的 PartUploadResult 对象列表 */ public List listParts(String key, String uploadId, Integer maxParts, Integer partNumberMarker) { - List parts = client.listParts( - x -> x.bucket(properties.getBucketName()) - .key(key) - .uploadId(uploadId) - .maxParts(maxParts != null ? maxParts : 1000) - .partNumberMarker(partNumberMarker != null ? partNumberMarker : 0) - .build()).join().parts(); - return parts.stream() - .map(x -> PartUploadResult.builder() - .partNumber(x.partNumber()) - .eTag(x.eTag()) - .build()) - .collect(Collectors.toList()); + try { + List parts = client.listParts( + x -> x.bucket(properties.getBucketName()) + .key(key) + .uploadId(uploadId) + .maxParts(maxParts != null ? maxParts : 1000) + .partNumberMarker(partNumberMarker != null ? partNumberMarker : 0) + .build()).join().parts(); + return parts.stream() + .map(x -> PartUploadResult.builder() + .partNumber(x.partNumber()) + .eTag(x.eTag()) + .build()) + .collect(Collectors.toList()); + } catch (Exception e) { + // 捕获异常并抛出自定义异常 + throw new OssException("获取分片列表失败,请检查配置信息:[" + e.getMessage() + "]"); + } } /** @@ -391,6 +402,9 @@ public class OssClient { * @return 包含上传结果信息的 UploadResult 对象 */ public UploadResult completeMultipartUpload(String key, String uploadId, List partUploadResults) { + if (CollUtil.isEmpty(partUploadResults)) { + throw new OssException("分片列表不能为空"); + } List completedParts = partUploadResults.stream() .map(x -> CompletedPart.builder() .partNumber(x.getPartNumber()) From 27aca5d5b2e9332b9e21a1c28662080ab75f36b3 Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Mon, 15 Apr 2024 14:29:59 +0800 Subject: [PATCH 3/4] =?UTF-8?q?update=20oss=E5=B7=A5=E5=85=B7=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E6=A0=BC=E5=BC=8F=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/common/oss/core/OssClient.java | 22 ++++++++++--------- .../common/oss/entity/PartUploadResult.java | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) 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 ccd09b04d..8d0a1821d 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 @@ -282,7 +282,7 @@ public class OssClient { /** * 生成预签名的私有对象获取 URL * - * @param objectKey 对象键(Object Key) + * @param objectKey 在 Amazon S3 中的对象键 * @param second 签名持续时间(秒) * @return 预签名 URL 字符串 */ @@ -302,7 +302,7 @@ public class OssClient { /** * 生成预签名的私有上传 URL * - * @param key 对象键(Object Key) + * @param key 在 Amazon S3 中的对象键 * @param md5Digest 内容的 MD5 摘要,如果有的话 * @param second 签名持续时间(秒) * @return 上传结果对象 @@ -323,8 +323,8 @@ public class OssClient { /** * 创建分片上传任务 * - * @param key 对象的键(Object Key) - * @return 包含上传 ID 的 UploadResult 对象 + * @param key 在 Amazon S3 中的对象键 + * @return 包含上传后的文件信息 */ public UploadResult initiateMultipartUpload(String key) { try { @@ -343,13 +343,14 @@ public class OssClient { /** * 生成预签名的分片上传 URL * - * @param key 对象键(Object Key) + * @param key 在 Amazon S3 中的对象键 * @param uploadId 分片上传任务的 Upload ID - * @param partNumber 分片编号 + * @param partNumber 分片编号(从1开始递增) + * @param md5Digest 内容的 MD5 摘要,如果有的话 * @param second 签名持续时间(秒) * @return 预签名 URL 字符串 */ - public String uploadPartFutures(String key, String uploadId, Integer partNumber, Integer second) { + public String uploadPartFutures(String key, String uploadId, Integer partNumber, String md5Digest, Integer second) { URL url = presigner.presignUploadPart( x -> x.signatureDuration(Duration.ofSeconds(second)) .uploadPartRequest( @@ -357,6 +358,7 @@ public class OssClient { .key(key) .uploadId(uploadId) .partNumber(partNumber) + .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null) .build() ).build() ).url(); @@ -366,7 +368,7 @@ public class OssClient { /** * 获取指定对象分片上传的分片列表 * - * @param key 对象的键(Object Key) + * @param key 在 Amazon S3 中的对象键 * @param uploadId 分片上传任务的 Upload ID * @param maxParts 最大返回的分片数(默认为1000) * @param partNumberMarker 分片编号的标记,用于分页查询(默认为0,表示从第一个分片开始查询) @@ -396,10 +398,10 @@ public class OssClient { /** * 完成分片上传任务 * - * @param key 对象的键(Object Key) + * @param key 在 Amazon S3 中的对象键 * @param uploadId 分片上传任务的 Upload ID * @param partUploadResults 已完成的分片列表 - * @return 包含上传结果信息的 UploadResult 对象 + * @return 包含上传后的文件信息 */ public UploadResult completeMultipartUpload(String key, String uploadId, List partUploadResults) { if (CollUtil.isEmpty(partUploadResults)) { 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 index dee893df2..a00408925 100644 --- 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 @@ -13,7 +13,7 @@ import lombok.Data; public class PartUploadResult { /** - * 新上传部件的部件号 + * 分片编号(从1开始递增) */ private Integer partNumber; From 0131163368951a6c3a1e87ca257d61cf995a31ed Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Mon, 15 Apr 2024 15:15:17 +0800 Subject: [PATCH 4/4] =?UTF-8?q?update=20oss=E5=B7=A5=E5=85=B7=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=BF=85=E9=A1=BB=E6=98=AF=E5=94=AF=E4=B8=80=E4=B8=94=E6=8C=89?= =?UTF-8?q?=E7=85=A7=E9=80=92=E5=A2=9E=E9=A1=BA=E5=BA=8F=E6=8E=92=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/dromara/common/oss/core/OssClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8d0a1821d..42edf2ae2 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 @@ -400,7 +400,7 @@ public class OssClient { * * @param key 在 Amazon S3 中的对象键 * @param uploadId 分片上传任务的 Upload ID - * @param partUploadResults 已完成的分片列表 + * @param partUploadResults 已完成的分片列表(必须是唯一且按照递增顺序排列,严格检查是否漏传) * @return 包含上传后的文件信息 */ public UploadResult completeMultipartUpload(String key, String uploadId, List partUploadResults) {