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] =?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; + }