From f1208474f771a1c233d7425c8ed13fbaa0d521ac Mon Sep 17 00:00:00 2001
From: baoshiwei <baoshiwei@shlanbao.cn>
Date: 星期三, 12 三月 2025 09:35:13 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/5.X' into 5.X

---
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java |  211 ++++++++++++++++++----------------------------------
 1 files changed, 74 insertions(+), 137 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 5533297..e3b20dd 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
@@ -9,32 +9,30 @@
 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;
 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
-import software.amazon.awssdk.core.async.AsyncRequestBody;
+import software.amazon.awssdk.core.ResponseInputStream;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
 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.crt.S3CrtHttpConfiguration;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
 import software.amazon.awssdk.services.s3.presigner.S3Presigner;
 import software.amazon.awssdk.transfer.s3.S3TransferManager;
 import software.amazon.awssdk.transfer.s3.model.*;
 import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
 
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
 import java.net.URI;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Duration;
+import java.util.function.Consumer;
 
 /**
  * S3 瀛樺偍鍗忚 鎵�鏈夊吋瀹筍3鍗忚鐨勪簯鍘傚晢鍧囨敮鎸�
@@ -83,10 +81,10 @@
             StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                 AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
 
-            //MinIO 浣跨敤 HTTPS 闄愬埗浣跨敤鍩熷悕璁块棶锛岀珯鐐瑰~鍩熷悕銆傞渶瑕佸惎鐢ㄨ矾寰勬牱寮忚闂�
+            // MinIO 浣跨敤 HTTPS 闄愬埗浣跨敤鍩熷悕璁块棶锛岀珯鐐瑰~鍩熷悕銆傞渶瑕佸惎鐢ㄨ矾寰勬牱寮忚闂�
             boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
 
-            //鍒涘缓AWS鍩轰簬 CRT 鐨� S3 瀹㈡埛绔�
+            // 鍒涘缓AWS鍩轰簬 CRT 鐨� S3 瀹㈡埛绔�
             this.client = S3AsyncClient.crtBuilder()
                 .credentialsProvider(credentialsProvider)
                 .endpointOverride(URI.create(getEndpoint()))
@@ -95,6 +93,9 @@
                 .minimumPartSizeInBytes(10 * 1025 * 1024L)
                 .checksumValidationEnabled(false)
                 .forcePathStyle(isStyle)
+                .httpConfiguration(S3CrtHttpConfiguration.builder()
+                    .connectionTimeout(Duration.ofSeconds(60)) // 璁剧疆杩炴帴瓒呮椂
+                    .build())
                 .build();
 
             //AWS鍩轰簬 CRT 鐨� S3 AsyncClient 瀹炰緥鐢ㄤ綔 S3 浼犺緭绠$悊鍣ㄧ殑搴曞眰瀹㈡埛绔�
@@ -112,8 +113,6 @@
                 .serviceConfiguration(config)
                 .build();
 
-            // 鍒涘缓瀛樺偍妗�
-            createBucket();
         } catch (Exception e) {
             if (e instanceof OssException) {
                 throw e;
@@ -123,52 +122,16 @@
     }
 
     /**
-     * 鍚屾鍒涘缓瀛樺偍妗�
-     * 濡傛灉瀛樺偍妗朵笉瀛樺湪锛屼細杩涜鍒涘缓锛涘鏋滃瓨鍌ㄦ《瀛樺湪锛屼笉鎵ц浠讳綍鎿嶄綔
-     *
-     * @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();
-
-                    // 璁剧疆瀛樺偍妗剁殑璁块棶绛栫暐锛圔ucket 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() + "]");
-            }
-        }
-    }
-
-    /**
      * 涓婁紶鏂囦欢鍒� Amazon S3锛屽苟杩斿洖涓婁紶缁撴灉
      *
-     * @param filePath  鏈湴鏂囦欢璺緞
-     * @param key       鍦� Amazon S3 涓殑瀵硅薄閿�
-     * @param md5Digest 鏈湴鏂囦欢鐨� MD5 鍝堝笇鍊硷紙鍙�夛級
+     * @param filePath    鏈湴鏂囦欢璺緞
+     * @param key         鍦� Amazon S3 涓殑瀵硅薄閿�
+     * @param md5Digest   鏈湴鏂囦欢鐨� MD5 鍝堝笇鍊硷紙鍙�夛級
+     * @param contentType 鏂囦欢鍐呭绫诲瀷
      * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
      * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
      */
-    public UploadResult upload(Path filePath, String key, String md5Digest) {
+    public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) {
         try {
             // 鏋勫缓涓婁紶璇锋眰瀵硅薄
             FileUpload fileUpload = transferManager.uploadFile(
@@ -176,6 +139,10 @@
                         y -> y.bucket(properties.getBucketName())
                             .key(key)
                             .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
+                            .contentType(contentType)
+                            // 鐢ㄤ簬璁剧疆瀵硅薄鐨勮闂帶鍒跺垪琛紙ACL锛夈�備笉鍚屼簯鍘傚晢瀵笰CL鐨勬敮鎸佸拰瀹炵幇鏂瑰紡鏈夋墍涓嶅悓锛�
+                            // 鍥犳鏍规嵁鍏蜂綋鐨勪簯鏈嶅姟鎻愪緵鍟嗭紝浣犲彲鑳介渶瑕佽繘琛屼笉鍚岀殑閰嶇疆锛堣嚜琛屽紑鍚紝闃块噷浜戞湁acl鏉冮檺閰嶇疆锛岃吘璁簯娌℃湁acl鏉冮檺閰嶇疆锛�
+                            //.acl(getAccessPolicy().getObjectCannedACL())
                             .build())
                     .addTransferListener(LoggingTransferListener.create())
                     .source(filePath).build());
@@ -201,17 +168,21 @@
      * @param inputStream 瑕佷笂浼犵殑杈撳叆娴�
      * @param key         鍦� Amazon S3 涓殑瀵硅薄閿�
      * @param length      杈撳叆娴佺殑闀垮害
+     * @param contentType 鏂囦欢鍐呭绫诲瀷
      * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
      * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
      */
-    public UploadResult upload(InputStream inputStream, String key, Long length) {
+    public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) {
         // 濡傛灉杈撳叆娴佷笉鏄� ByteArrayInputStream锛屽垯灏嗗叾璇诲彇涓哄瓧鑺傛暟缁勫啀鍒涘缓 ByteArrayInputStream
         if (!(inputStream instanceof ByteArrayInputStream)) {
             inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
         }
         try {
             // 鍒涘缓寮傛璇锋眰浣擄紙length濡傛灉涓虹┖浼氭姤閿欙級
-            BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length);
+            BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
+                .contentLength(length)
+                .subscribeTimeout(Duration.ofSeconds(30))
+                .build();
 
             // 浣跨敤 transferManager 杩涜涓婁紶
             Upload upload = transferManager.upload(
@@ -219,6 +190,10 @@
                     .putObjectRequest(
                         y -> y.bucket(properties.getBucketName())
                             .key(key)
+                            .contentType(contentType)
+                            // 鐢ㄤ簬璁剧疆瀵硅薄鐨勮闂帶鍒跺垪琛紙ACL锛夈�備笉鍚屼簯鍘傚晢瀵笰CL鐨勬敮鎸佸拰瀹炵幇鏂瑰紡鏈夋墍涓嶅悓锛�
+                            // 鍥犳鏍规嵁鍏蜂綋鐨勪簯鏈嶅姟鎻愪緵鍟嗭紝浣犲彲鑳介渶瑕佽繘琛屼笉鍚岀殑閰嶇疆锛堣嚜琛屽紑鍚紝闃块噷浜戞湁acl鏉冮檺閰嶇疆锛岃吘璁簯娌℃湁acl鏉冮檺閰嶇疆锛�
+                            //.acl(getAccessPolicy().getObjectCannedACL())
                             .build())
                     .build());
 
@@ -261,6 +236,41 @@
     }
 
     /**
+     * 涓嬭浇鏂囦欢浠� Amazon S3 鍒� 杈撳嚭娴�
+     *
+     * @param key 鏂囦欢鍦� Amazon S3 涓殑瀵硅薄閿�
+     * @param out 杈撳嚭娴�
+     * @param consumer 鑷畾涔夊鐞嗛�昏緫
+     * @return 杈撳嚭娴佷腑鍐欏叆鐨勫瓧鑺傛暟锛堥暱搴︼級
+     * @throws OssException 濡傛灉涓嬭浇澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+     */
+    public void download(String key, OutputStream out, Consumer<Long> consumer) {
+        try {
+            // 鏋勫缓涓嬭浇璇锋眰
+            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
+                // 鏂囦欢瀵硅薄
+                .getObjectRequest(y -> y.bucket(properties.getBucketName())
+                    .key(key)
+                    .build())
+                .addTransferListener(LoggingTransferListener.create())
+                // 浣跨敤璁㈤槄杞崲鍣�
+                .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
+                .build();
+            // 浣跨敤 S3TransferManager 涓嬭浇鏂囦欢
+            Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
+            // 杈撳嚭鍒版祦涓�
+            try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
+                if (consumer != null) {
+                    consumer.accept(responseStream.response().contentLength());
+                }
+                responseStream.transferTo(out); // 闃诲璋冪敤绾跨▼ blocks the calling thread
+            }
+        } catch (Exception e) {
+            throw new OssException("鏂囦欢涓嬭浇澶辫触锛岄敊璇俊鎭�:[" + e.getMessage() + "]");
+        }
+    }
+
+    /**
      * 鍒犻櫎浜戝瓨鍌ㄦ湇鍔′腑鎸囧畾璺緞涓嬫枃浠�
      *
      * @param path 鎸囧畾璺緞
@@ -279,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)
@@ -303,8 +313,8 @@
      * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
      * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
      */
-    public UploadResult uploadSuffix(byte[] data, String suffix) {
-        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length));
+    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
+        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), contentType);
     }
 
     /**
@@ -316,8 +326,8 @@
      * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
      * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
      */
-    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
-        return upload(inputStream, getPath(properties.getPrefix(), suffix), length);
+    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType) {
+        return upload(inputStream, getPath(properties.getPrefix(), suffix), length, contentType);
     }
 
     /**
@@ -329,7 +339,7 @@
      * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
      */
     public UploadResult uploadSuffix(File file, String suffix) {
-        return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null);
+        return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null, FileUtils.getMimeType(suffix));
     }
 
     /**
@@ -480,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);
     }
 
 }

--
Gitblit v1.9.3