疯狂的狮子Li
2025-01-20 3c8d864b5f68af5167199e0d5c9ff6c0c5852638
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
@@ -9,7 +9,6 @@
import org.dromara.common.oss.constant.OssConstant;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enumd.AccessPolicyType;
import org.dromara.common.oss.enumd.PolicyType;
import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.properties.OssProperties;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
@@ -22,8 +21,6 @@
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.*;
@@ -35,6 +32,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.function.Consumer;
/**
 * S3 存储协议 所有兼容S3协议的云厂商均支持
@@ -115,50 +113,11 @@
                .serviceConfiguration(config)
                .build();
            // 创建存储桶
            createBucket();
        } catch (Exception e) {
            if (e instanceof OssException) {
                throw e;
            }
            throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
        }
    }
    /**
     * 同步创建存储桶
     * 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
     *
     * @throws OssException 当创建存储桶时发生异常时抛出
     */
    public void createBucket() {
        String bucketName = properties.getBucketName();
        try {
            // 尝试获取存储桶的信息
            client.headBucket(
                    x -> x.bucket(bucketName)
                        .build())
                .join();
        } catch (Exception ex) {
            if (ex.getCause() instanceof NoSuchBucketException) {
                try {
                    // 存储桶不存在,尝试创建存储桶
                    client.createBucket(
                            x -> x.bucket(bucketName))
                        .join();
                    // 设置存储桶的访问策略(Bucket Policy)
                    client.putBucketPolicy(
                            x -> x.bucket(bucketName)
                                .policy(getPolicy(bucketName, getAccessPolicy().getPolicyType())))
                        .join();
                } catch (S3Exception e) {
                    // 存储桶创建或策略设置失败
                    throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
                }
            } else {
                throw new OssException("判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]");
            }
        }
    }
@@ -281,10 +240,11 @@
     *
     * @param key 文件在 Amazon S3 中的对象键
     * @param out 输出流
     * @param consumer 自定义处理逻辑
     * @return 输出流中写入的字节数(长度)
     * @throws OssException 如果下载失败,抛出自定义异常
     */
    public long download(String key, OutputStream out) {
    public void download(String key, OutputStream out, Consumer<Long> consumer) {
        try {
            // 构建下载请求
            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
@@ -300,7 +260,10 @@
            Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
            // 输出到流中
            try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
                return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
                if (consumer != null) {
                    consumer.accept(responseStream.response().contentLength());
                }
                responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
            }
        } catch (Exception e) {
            throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
@@ -326,13 +289,13 @@
    /**
     * 获取私有URL链接
     *
     * @param objectKey 对象KEY
     * @param second    授权时间
     * @param objectKey   对象KEY
     * @param expiredTime 链接授权到期时间
     */
    public String getPrivateUrl(String objectKey, Integer second) {
    public String getPrivateUrl(String objectKey, Duration expiredTime) {
        // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
        URL url = presigner.presignGetObject(
                x -> x.signatureDuration(Duration.ofSeconds(second))
                x -> x.signatureDuration(expiredTime)
                    .getObjectRequest(
                        y -> y.bucket(properties.getBucketName())
                            .key(objectKey)
@@ -527,79 +490,6 @@
     */
    public AccessPolicyType getAccessPolicy() {
        return AccessPolicyType.getByType(properties.getAccessPolicy());
    }
    /**
     * 生成 AWS S3 存储桶访问策略
     *
     * @param bucketName 存储桶
     * @param policyType 桶策略类型
     * @return 符合 AWS S3 存储桶访问策略格式的字符串
     */
    private static String getPolicy(String bucketName, PolicyType policyType) {
        String policy = switch (policyType) {
            case WRITE -> """
                {
                  "Version": "2012-10-17",
                  "Statement": []
                }
                """;
            case READ_WRITE -> """
                {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": [
                        "s3:GetBucketLocation",
                        "s3:ListBucket",
                        "s3:ListBucketMultipartUploads"
                      ],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": [
                        "s3:AbortMultipartUpload",
                        "s3:DeleteObject",
                        "s3:GetObject",
                        "s3:ListMultipartUploadParts",
                        "s3:PutObject"
                      ],
                      "Resource": "arn:aws:s3:::bucketName/*"
                    }
                  ]
                }
                """;
            case READ -> """
                {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": ["s3:GetBucketLocation"],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Deny",
                      "Principal": "*",
                      "Action": ["s3:ListBucket"],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": "s3:GetObject",
                      "Resource": "arn:aws:s3:::bucketName/*"
                    }
                  ]
                }
                """;
        };
        return policy.replaceAll("bucketName", bucketName);
    }
}