From 348bd00fa30fbc7eb80db1c0f20a017198498ee6 Mon Sep 17 00:00:00 2001 From: 疯狂的狮子Li <15040126243@163.com> Date: 星期日, 14 一月 2024 21:20:00 +0800 Subject: [PATCH] [重大更新] 升级 awsS3 到2.X版本 支持异步与自动分片上传下载 --- ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java | 629 +++++++++++++++++++++++++++++++++++++++------------- ruoyi-common/ruoyi-common-oss/pom.xml | 40 +++ ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java | 2 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java | 6 pom.xml | 22 + ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java | 2 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java | 18 + 7 files changed, 545 insertions(+), 174 deletions(-) diff --git a/pom.xml b/pom.xml index 883331b..931097c 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,8 @@ <ip2region.version>2.7.0</ip2region.version> <!-- OSS 閰嶇疆 --> - <aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version> + <aws.sdk.version>2.23.0</aws.sdk.version> + <aws.crt.version>0.29.6</aws.crt.version> <!-- SMS 閰嶇疆 --> <sms4j.version>2.2.0</sms4j.version> <!-- 闄愬埗妗嗘灦涓殑fastjson鐗堟湰 --> @@ -235,10 +236,23 @@ <version>${okhttp.version}</version> </dependency> + <!-- AWS SDK for Java 2.x --> <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-java-sdk-s3</artifactId> - <version>${aws-java-sdk-s3.version}</version> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <version>${aws.sdk.version}</version> + </dependency> + <!-- 浣跨敤AWS鍩轰簬 CRT 鐨� S3 瀹㈡埛绔� --> + <dependency> + <groupId>software.amazon.awssdk.crt</groupId> + <artifactId>aws-crt</artifactId> + <version>${aws.crt.version}</version> + </dependency> + <!-- 鍩轰簬 AWS CRT 鐨� S3 瀹㈡埛绔殑鎬ц兘澧炲己鐨� S3 浼犺緭绠$悊鍣� --> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3-transfer-manager</artifactId> + <version>${aws.sdk.version}</version> </dependency> <!--鐭俊sms4j--> <dependency> diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java index 5e4db50..dd6ebb1 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java @@ -22,6 +22,8 @@ public static final String SEPARATOR = ","; + public static final String SLASH = "/"; + /** * 鑾峰彇鍙傛暟涓嶄负绌哄�� * diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml index 8e7afff..18d004f 100644 --- a/ruoyi-common/ruoyi-common-oss/pom.xml +++ b/ruoyi-common/ruoyi-common-oss/pom.xml @@ -26,10 +26,46 @@ <artifactId>ruoyi-common-redis</artifactId> </dependency> + <!-- AWS SDK for Java 2.x --> <dependency> - <groupId>com.amazonaws</groupId> - <artifactId>aws-java-sdk-s3</artifactId> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + <exclusions> + <!-- 灏嗗熀浜� Netty 鐨� HTTP 瀹㈡埛绔粠绫昏矾寰勪腑绉婚櫎 --> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>netty-nio-client</artifactId> + </exclusion> + <!-- 灏嗗熀浜� CRT 鐨� HTTP 瀹㈡埛绔粠绫昏矾寰勪腑绉婚櫎 --> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>aws-crt-client</artifactId> + </exclusion> + <!-- 灏嗗熀浜� Apache 鐨� HTTP 瀹㈡埛绔粠绫昏矾寰勪腑绉婚櫎 --> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>apache-client</artifactId> + </exclusion> + <!-- 灏嗛厤缃熀浜� URL 杩炴帴鐨� HTTP 瀹㈡埛绔粠绫昏矾寰勪腑绉婚櫎 --> + <exclusion> + <groupId>software.amazon.awssdk</groupId> + <artifactId>url-connection-client</artifactId> + </exclusion> + </exclusions> </dependency> + + <!-- 浣跨敤AWS鍩轰簬 CRT 鐨� S3 瀹㈡埛绔� --> + <dependency> + <groupId>software.amazon.awssdk.crt</groupId> + <artifactId>aws-crt</artifactId> + </dependency> + + <!-- 鍩轰簬 AWS CRT 鐨� S3 瀹㈡埛绔殑鎬ц兘澧炲己鐨� S3 浼犺緭绠$悊鍣� --> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3-transfer-manager</artifactId> + </dependency> + </dependencies> </project> 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 53e05c9..6c402dc 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 @@ -2,73 +2,115 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; -import com.amazonaws.ClientConfiguration; -import com.amazonaws.HttpMethod; -import com.amazonaws.Protocol; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.*; +import org.dromara.common.core.constant.Constants; 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.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.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.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.net.URI; import java.net.URL; -import java.util.Date; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; /** * S3 瀛樺偍鍗忚 鎵�鏈夊吋瀹筍3鍗忚鐨勪簯鍘傚晢鍧囨敮鎸� * 闃块噷浜� 鑵捐浜� 涓冪墰浜� minio * - * @author Lion Li + * @author AprilWind */ public class OssClient { + /** + * 鏈嶅姟鍟� + */ private final String configKey; + /** + * 閰嶇疆灞炴�� + */ private final OssProperties properties; - private final AmazonS3 client; + /** + * Amazon S3 寮傛瀹㈡埛绔� + */ + private final S3AsyncClient client; + /** + * 鐢ㄤ簬绠$悊 S3 鏁版嵁浼犺緭鐨勯珮绾у伐鍏� + */ + private final S3TransferManager transferManager; + + /** + * AWS S3 棰勭鍚� URL 鐨勭敓鎴愬櫒 + */ + private final S3Presigner presigner; + + /** + * 鏋勯�犳柟娉� + * + * @param configKey 閰嶇疆閿� + * @param ossProperties Oss閰嶇疆灞炴�� + */ public OssClient(String configKey, OssProperties ossProperties) { this.configKey = configKey; this.properties = ossProperties; try { - AwsClientBuilder.EndpointConfiguration endpointConfig = - new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion()); + // 鍒涘缓 AWS 璁よ瘉淇℃伅 + StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())); - AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey()); - AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); - ClientConfiguration clientConfig = new ClientConfiguration(); - if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) { - clientConfig.setProtocol(Protocol.HTTPS); - } else { - clientConfig.setProtocol(Protocol.HTTP); - } - AmazonS3ClientBuilder build = AmazonS3Client.builder() - .withEndpointConfiguration(endpointConfig) - .withClientConfiguration(clientConfig) - .withCredentials(credentialsProvider) - .disableChunkedEncoding(); - if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) { + //鍒涘缓AWS鍩轰簬 CRT 鐨� S3 瀹㈡埛绔� + this.client = S3AsyncClient.crtBuilder() + .credentialsProvider(credentialsProvider) + .endpointOverride(URI.create(getEndpoint())) + .region(of()) + .targetThroughputInGbps(20.0) + .minimumPartSizeInBytes(10 * 1025 * 1024L) + .checksumValidationEnabled(false) + .build(); + + //AWS鍩轰簬 CRT 鐨� S3 AsyncClient 瀹炰緥鐢ㄤ綔 S3 浼犺緭绠$悊鍣ㄧ殑搴曞眰瀹㈡埛绔� + this.transferManager = S3TransferManager.builder().s3Client(this.client).build(); + + // 妫�鏌ユ槸鍚﹁繛鎺ュ埌 MinIO锛孧inIO 浣跨敤 HTTPS 闄愬埗浣跨敤鍩熷悕璁块棶锛岄渶瑕佸惎鐢ㄨ矾寰勬牱寮忚闂� + S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false) // minio 浣跨敤https闄愬埗浣跨敤鍩熷悕璁块棶 闇�瑕佹閰嶇疆 绔欑偣濉煙鍚� - build.enablePathStyleAccess(); - } - this.client = build.build(); + .pathStyleAccessEnabled(!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)).build(); + // 鍒涘缓 棰勭鍚� URL 鐨勭敓鎴愬櫒 瀹炰緥锛岀敤浜庣敓鎴� S3 棰勭鍚� URL + this.presigner = S3Presigner.builder() + .region(of()) + .credentialsProvider(credentialsProvider) + .endpointOverride(URI.create(getDomain())) + .serviceConfiguration(config) + .build(); + + // 鍒涘缓瀛樺偍妗� createBucket(); } catch (Exception e) { if (e instanceof OssException) { @@ -78,126 +120,161 @@ } } + /** + * 鍚屾鍒涘缓瀛樺偍妗� + * 濡傛灉瀛樺偍妗朵笉瀛樺湪锛屼細杩涜鍒涘缓锛涘鏋滃瓨鍌ㄦ《瀛樺湪锛屼笉鎵ц浠讳綍鎿嶄綔 + * + * @throws OssException 褰撳垱寤哄瓨鍌ㄦ《鏃跺彂鐢熷紓甯告椂鎶涘嚭 + */ public void createBucket() { + String bucketName = properties.getBucketName(); try { - String bucketName = properties.getBucketName(); - if (client.doesBucketExistV2(bucketName)) { - return; + // 灏濊瘯鑾峰彇瀛樺偍妗剁殑淇℃伅 + 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() + "]"); } - CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName); - AccessPolicyType accessPolicy = getAccessPolicy(); - createBucketRequest.setCannedAcl(accessPolicy.getAcl()); - client.createBucket(createBucketRequest); - client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType())); - } catch (Exception e) { - throw new OssException("鍒涘缓Bucket澶辫触, 璇锋牳瀵归厤缃俊鎭�:[" + e.getMessage() + "]"); } } - public UploadResult upload(byte[] data, String path, String contentType) { - return upload(new ByteArrayInputStream(data), path, contentType); + /** + * 涓婁紶鏂囦欢鍒� Amazon S3锛屽苟杩斿洖涓婁紶缁撴灉 + * + * @param filePath 鏈湴鏂囦欢璺緞 + * @param key 鍦� Amazon S3 涓殑瀵硅薄閿� + * @param md5Digest 鏈湴鏂囦欢鐨� MD5 鍝堝笇鍊硷紙鍙�夛級 + * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅 + * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父 + */ + public UploadResult upload(Path filePath, String key, String md5Digest) { + try { + // 鏋勫缓涓婁紶璇锋眰瀵硅薄 + FileUpload fileUpload = transferManager.uploadFile( + x -> x.putObjectRequest( + y -> y.bucket(properties.getBucketName()) + .key(key) + .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null) + .build()) + .addTransferListener(LoggingTransferListener.create()) + .source(filePath).build()); + + // 绛夊緟涓婁紶瀹屾垚骞惰幏鍙栦笂浼犵粨鏋� + CompletedFileUpload uploadResult = fileUpload.completionFuture().join(); + String eTag = uploadResult.response().eTag(); + + // 鎻愬彇涓婁紶缁撴灉涓殑 ETag锛屽苟鏋勫缓涓�涓嚜瀹氫箟鐨� UploadResult 瀵硅薄 + return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build(); + } catch (Exception e) { + // 鎹曡幏寮傚父骞舵姏鍑鸿嚜瀹氫箟寮傚父 + throw new OssException("涓婁紶鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]"); + } finally { + // 鏃犺涓婁紶鏄惁鎴愬姛锛屾渶缁堥兘浼氬垹闄や复鏃舵枃浠� + FileUtils.del(filePath); + } } - public UploadResult upload(InputStream inputStream, String path, String contentType) { + /** + * 涓婁紶 InputStream 鍒� Amazon S3 + * + * @param inputStream 瑕佷笂浼犵殑杈撳叆娴� + * @param key 鍦� Amazon S3 涓殑瀵硅薄閿� + * @param length 杈撳叆娴佺殑闀垮害 + * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅 + * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父 + */ + public UploadResult upload(InputStream inputStream, String key, Long length) { + // 濡傛灉杈撳叆娴佷笉鏄� ByteArrayInputStream锛屽垯灏嗗叾璇诲彇涓哄瓧鑺傛暟缁勫啀鍒涘缓 ByteArrayInputStream if (!(inputStream instanceof ByteArrayInputStream)) { inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); } try { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentType(contentType); - metadata.setContentLength(inputStream.available()); - PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata); - // 璁剧疆涓婁紶瀵硅薄鐨� Acl 涓哄叕鍏辫 - putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); - client.putObject(putObjectRequest); + // 鍒涘缓寮傛璇锋眰浣擄紙length濡傛灉涓虹┖浼氭姤閿欙級 + BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length); + + // 浣跨敤 transferManager 杩涜涓婁紶 + Upload upload = transferManager.upload( + x -> x.requestBody(body) + .putObjectRequest( + y -> y.bucket(properties.getBucketName()) + .key(key) + .build()) + .build()); + + // 灏嗚緭鍏ユ祦鍐欏叆璇锋眰浣� + body.writeInputStream(inputStream); + + // 绛夊緟鏂囦欢涓婁紶鎿嶄綔瀹屾垚 + CompletedUpload uploadResult = upload.completionFuture().join(); + String eTag = uploadResult.response().eTag(); + + // 鎻愬彇涓婁紶缁撴灉涓殑 ETag锛屽苟鏋勫缓涓�涓嚜瀹氫箟鐨� UploadResult 瀵硅薄 + return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build(); } catch (Exception e) { throw new OssException("涓婁紶鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]"); } - return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); - } - - public UploadResult upload(File file, String path) { - try { - PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file); - // 璁剧疆涓婁紶瀵硅薄鐨� Acl 涓哄叕鍏辫 - putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); - client.putObject(putObjectRequest); - } catch (Exception e) { - throw new OssException("涓婁紶鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]"); - } - return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); - } - - public void delete(String path) { - path = path.replace(getUrl() + "/", ""); - try { - client.deleteObject(properties.getBucketName(), path); - } catch (Exception e) { - throw new OssException("鍒犻櫎鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]"); - } - } - - public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { - return upload(data, getPath(properties.getPrefix(), suffix), contentType); - } - - public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) { - return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType); - } - - public UploadResult uploadSuffix(File file, String suffix) { - return upload(file, getPath(properties.getPrefix(), suffix)); } /** - * 鑾峰彇鏂囦欢鍏冩暟鎹� + * 涓嬭浇鏂囦欢浠� Amazon S3 鍒颁复鏃剁洰褰� * - * @param path 瀹屾暣鏂囦欢璺緞 + * @param path 鏂囦欢鍦� Amazon S3 涓殑瀵硅薄閿� + * @return 涓嬭浇鍚庣殑鏂囦欢鍦ㄦ湰鍦扮殑涓存椂璺緞 + * @throws OssException 濡傛灉涓嬭浇澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父 */ - public ObjectMetadata getObjectMetadata(String path) { - path = path.replace(getUrl() + "/", ""); - S3Object object = client.getObject(properties.getBucketName(), path); - return object.getObjectMetadata(); + public Path fileDownload(String path) { + // 浠庤矾寰勪腑绉婚櫎 URL 鍓嶇紑 + String url = removeBaseUrl(path); + + // 鏋勫缓涓存椂鏂囦欢璺緞 鏂囦欢鍚嶅繀椤绘槸鍞竴涓嶅瓨鍦ㄧ殑锛岃矾寰勫繀椤绘槸瀛樺湪鐨� + Path tempFilePath = Paths.get(extractFileName(url)); + // 浣跨敤 S3TransferManager 涓嬭浇鏂囦欢 + FileDownload downloadFile = transferManager.downloadFile( + x -> x.getObjectRequest( + y -> y.bucket(properties.getBucketName()) + .key(url) + .build()) + .addTransferListener(LoggingTransferListener.create()) + .destination(tempFilePath) + .build()); + // 绛夊緟鏂囦欢涓嬭浇鎿嶄綔瀹屾垚 + downloadFile.completionFuture().join(); + return tempFilePath; } - public InputStream getObjectContent(String path) { - path = path.replace(getUrl() + "/", ""); - S3Object object = client.getObject(properties.getBucketName(), path); - return object.getObjectContent(); - } - - public String getUrl() { - String domain = properties.getDomain(); - String endpoint = properties.getEndpoint(); - String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://"; - // 浜戞湇鍔″晢鐩存帴杩斿洖 - if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { - if (StringUtils.isNotBlank(domain)) { - return header + domain; - } - return header + properties.getBucketName() + "." + endpoint; + /** + * 鍒犻櫎浜戝瓨鍌ㄦ湇鍔′腑鎸囧畾璺緞涓嬫枃浠� + * + * @param path 鎸囧畾璺緞 + */ + public void delete(String path) { + try { + client.deleteObject( + x -> x.bucket(properties.getBucketName()) + .key(removeBaseUrl(path)) + .build()); + } catch (Exception e) { + throw new OssException("鍒犻櫎鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]"); } - // minio 鍗曠嫭澶勭悊 - if (StringUtils.isNotBlank(domain)) { - return header + domain + "/" + properties.getBucketName(); - } - return header + endpoint + "/" + properties.getBucketName(); - } - - public String getPath(String prefix, String suffix) { - // 鐢熸垚uuid - String uuid = IdUtil.fastSimpleUUID(); - // 鏂囦欢璺緞 - String path = DateUtils.datePath() + "/" + uuid; - if (StringUtils.isNotBlank(prefix)) { - path = prefix + "/" + path; - } - return path + suffix; - } - - - public String getConfigKey() { - return configKey; } /** @@ -207,12 +284,197 @@ * @param second 鎺堟潈鏃堕棿 */ public String getPrivateUrl(String objectKey, Integer second) { - GeneratePresignedUrlRequest generatePresignedUrlRequest = - new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) - .withMethod(HttpMethod.GET) - .withExpiration(new Date(System.currentTimeMillis() + 1000L * second)); - URL url = client.generatePresignedUrl(generatePresignedUrlRequest); + // 浣跨敤 AWS S3 棰勭鍚� URL 鐨勭敓鎴愬櫒 鑾峰彇瀵硅薄鐨勯绛惧悕 URL + URL url = presigner.presignGetObject( + x -> x.signatureDuration(Duration.ofSeconds(second)) + .getObjectRequest( + y -> y.bucket(properties.getBucketName()) + .key(objectKey) + .build()) + .build()) + .url(); return url.toString(); + } + + /** + * 涓婁紶 byte[] 鏁版嵁鍒� Amazon S3锛屼娇鐢ㄦ寚瀹氱殑鍚庣紑鏋勯�犲璞¢敭銆� + * + * @param data 瑕佷笂浼犵殑 byte[] 鏁版嵁 + * @param suffix 瀵硅薄閿殑鍚庣紑 + * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅 + * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父 + */ + public UploadResult uploadSuffix(byte[] data, String suffix) { + return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length)); + } + + /** + * 涓婁紶 InputStream 鍒� Amazon S3锛屼娇鐢ㄦ寚瀹氱殑鍚庣紑鏋勯�犲璞¢敭銆� + * + * @param inputStream 瑕佷笂浼犵殑杈撳叆娴� + * @param suffix 瀵硅薄閿殑鍚庣紑 + * @param length 杈撳叆娴佺殑闀垮害 + * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅 + * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父 + */ + public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) { + return upload(inputStream, getPath(properties.getPrefix(), suffix), length); + } + + /** + * 涓婁紶鏂囦欢鍒� Amazon S3锛屼娇鐢ㄦ寚瀹氱殑鍚庣紑鏋勯�犲璞¢敭 + * + * @param file 瑕佷笂浼犵殑鏂囦欢 + * @param suffix 瀵硅薄閿殑鍚庣紑 + * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅 + * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父 + */ + public UploadResult uploadSuffix(File file, String suffix) { + return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null); + } + + /** + * 鑾峰彇鏂囦欢杈撳叆娴� + * + * @param path 瀹屾暣鏂囦欢璺緞 + * @return 杈撳叆娴� + */ + public InputStream getObjectContent(String path) throws IOException { + // 涓嬭浇鏂囦欢鍒颁复鏃剁洰褰� + Path tempFilePath = fileDownload(path); + // 鍒涘缓杈撳叆娴� + InputStream inputStream = Files.newInputStream(tempFilePath); + // 鍒犻櫎涓存椂鏂囦欢 + FileUtils.del(tempFilePath); + // 杩斿洖瀵硅薄鍐呭鐨勮緭鍏ユ祦 + return inputStream; + } + + /** + * 鑾峰彇 S3 瀹㈡埛绔殑缁堢鐐� URL + * + * @return 缁堢鐐� URL + */ + public String getEndpoint() { + // 鏍规嵁閰嶇疆鏂囦欢涓殑鏄惁浣跨敤 HTTPS锛岃缃崗璁ご閮� + String header = getIsHttps(); + // 鎷兼帴鍗忚澶撮儴鍜岀粓绔偣锛屽緱鍒板畬鏁寸殑缁堢鐐� URL + return header + properties.getEndpoint(); + } + + /** + * 鑾峰彇 S3 瀹㈡埛绔殑缁堢鐐� URL锛堣嚜瀹氫箟鍩熷悕锛� + * + * @return 缁堢鐐� URL + */ + public String getDomain() { + // 浠庨厤缃腑鑾峰彇鍩熷悕銆佺粓绔偣銆佹槸鍚︿娇鐢� HTTPS 绛変俊鎭� + String domain = properties.getDomain(); + String endpoint = properties.getEndpoint(); + String header = getIsHttps(); + + // 濡傛灉鏄簯鏈嶅姟鍟嗭紝鐩存帴杩斿洖鍩熷悕鎴栫粓绔偣 + if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { + return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint; + } + + // 濡傛灉鏄� MinIO锛屽鐞嗗煙鍚嶅苟杩斿洖 + if (StringUtils.isNotEmpty(domain)) { + return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain; + } + + // 杩斿洖缁堢鐐� + return header + endpoint; + } + + /** + * 鏍规嵁浼犲叆鐨� region 鍙傛暟杩斿洖鐩稿簲鐨� AWS 鍖哄煙 + * 濡傛灉 region 鍙傛暟闈炵┖锛屼娇鐢� Region.of 鏂规硶鍒涘缓骞惰繑鍥炲搴旂殑 AWS 鍖哄煙瀵硅薄 + * 濡傛灉 region 鍙傛暟涓虹┖锛岃繑鍥炰竴涓粯璁ょ殑 AWS 鍖哄煙锛堜緥濡傦紝us-east-1锛夛紝浣滀负骞挎硾鏀寔鐨勫尯鍩� + * + * @return 瀵瑰簲鐨� AWS 鍖哄煙瀵硅薄锛屾垨鑰呴粯璁ょ殑骞挎硾鏀寔鐨勫尯鍩燂紙us-east-1锛� + */ + public Region of() { + //AWS 鍖哄煙瀛楃涓� + String region = properties.getRegion(); + // 濡傛灉 region 鍙傛暟闈炵┖锛屼娇鐢� Region.of 鏂规硶鍒涘缓瀵瑰簲鐨� AWS 鍖哄煙瀵硅薄锛屽惁鍒欒繑鍥為粯璁ゅ尯鍩� + return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1; + } + + /** + * 鑾峰彇浜戝瓨鍌ㄦ湇鍔$殑URL + * + * @return 鏂囦欢璺緞 + */ + public String getUrl() { + String domain = properties.getDomain(); + String endpoint = properties.getEndpoint(); + String header = getIsHttps(); + // 浜戞湇鍔″晢鐩存帴杩斿洖 + if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { + return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint); + } + // MinIO 鍗曠嫭澶勭悊 + if (StringUtils.isNotEmpty(domain)) { + // 濡傛灉 domain 浠� "https://" 鎴� "http://" 寮�澶� + return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ? + domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName(); + } + return header + endpoint + StringUtils.SLASH + properties.getBucketName(); + } + + /** + * 鐢熸垚涓�涓鍚堢壒瀹氳鍒欑殑銆佸敮涓�鐨勬枃浠惰矾寰勩�傞�氳繃浣跨敤鏃ユ湡銆乁UID銆佸墠缂�鍜屽悗缂�绛夊厓绱犵殑缁勫悎锛岀‘淇濅簡鏂囦欢璺緞鐨勭嫭涓�鏃犱簩鎬� + * + * @param prefix 鍓嶇紑 + * @param suffix 鍚庣紑 + * @return 鏂囦欢璺緞 + */ + public String getPath(String prefix, String suffix) { + // 鐢熸垚uuid + String uuid = IdUtil.fastSimpleUUID(); + // 鐢熸垚鏃ユ湡璺緞 + String datePath = DateUtils.datePath(); + // 鎷兼帴璺緞 + String path = StringUtils.isNotEmpty(prefix) ? + prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid; + return path + suffix; + } + + /** + * 绉婚櫎璺緞涓殑鍩虹URL閮ㄥ垎锛屽緱鍒扮浉瀵硅矾寰� + * + * @param path 瀹屾暣鐨勮矾寰勶紝鍖呮嫭鍩虹URL鍜岀浉瀵硅矾寰� + * @return 鍘婚櫎鍩虹URL鍚庣殑鐩稿璺緞 + */ + public String removeBaseUrl(String path) { + return path.replace(getUrl() + StringUtils.SLASH, ""); + } + + /** + * 浠庢枃浠惰矾寰勪腑鎻愬彇鏂囦欢鍚� + * + * @param path 鏂囦欢璺緞 + * @return 鎻愬彇鐨勬枃浠跺悕鎴栭粯璁ゆ枃浠跺悕 + */ + public String extractFileName(String path) { + return FileUtils.getTmpDir() + StringUtils.SLASH + Paths.get(path).getFileName().toString(); + } + + /** + * 鏈嶅姟鍟� + */ + public String getConfigKey() { + return configKey; + } + + /** + * 鑾峰彇鏄惁浣跨敤 HTTPS 鐨勯厤缃紝骞惰繑鍥炵浉搴旂殑鍗忚澶撮儴銆� + * + * @return 鍗忚澶撮儴锛屾牴鎹槸鍚︿娇鐢� HTTPS 杩斿洖 "https://" 鎴� "http://" + */ + public String getIsHttps() { + return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP; } /** @@ -231,32 +493,77 @@ return AccessPolicyType.getByType(properties.getAccessPolicy()); } + /** + * 鐢熸垚 AWS S3 瀛樺偍妗惰闂瓥鐣� + * + * @param bucketName 瀛樺偍妗� + * @param policyType 妗剁瓥鐣ョ被鍨� + * @return 绗﹀悎 AWS S3 瀛樺偍妗惰闂瓥鐣ユ牸寮忕殑瀛楃涓� + */ private static String getPolicy(String bucketName, PolicyType policyType) { - StringBuilder builder = new StringBuilder(); - builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n"); - builder.append(switch (policyType) { - case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n"; - case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n"; - default -> "\"s3:GetBucketLocation\"\n"; - }); - builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); - builder.append(bucketName); - builder.append("\"\n},\n"); - if (policyType == PolicyType.READ) { - builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); - builder.append(bucketName); - builder.append("\"\n},\n"); - } - builder.append("{\n\"Action\": "); - builder.append(switch (policyType) { - case WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; - case READ_WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; - default -> "\"s3:GetObject\",\n"; - }); - builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); - builder.append(bucketName); - builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n"); - return builder.toString(); + 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); } } 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 a6f57e5..81a18e6 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 @@ -21,4 +21,10 @@ * 鏂囦欢鍚� */ private String filename; + + /** + * 宸蹭笂浼犲璞$殑瀹炰綋鏍囪锛堢敤鏉ユ牎楠屾枃浠讹級 + */ + private String eTag; + } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java index 9074d72..6d39133 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java @@ -1,8 +1,9 @@ package org.dromara.common.oss.enumd; -import com.amazonaws.services.s3.model.CannedAccessControlList; import lombok.AllArgsConstructor; import lombok.Getter; +import software.amazon.awssdk.services.s3.model.BucketCannedACL; +import software.amazon.awssdk.services.s3.model.ObjectCannedACL; /** * 妗惰闂瓥鐣ラ厤缃� @@ -16,27 +17,32 @@ /** * private */ - PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE), + PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE), /** * public */ - PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ), + PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE), /** * custom */ - CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ); + CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ); /** - * 妗� 鏉冮檺绫诲瀷 + * 妗� 鏉冮檺绫诲瀷锛堟暟鎹簱鍊硷級 */ private final String type; /** + * 妗� 鏉冮檺绫诲瀷 + */ + private final BucketCannedACL bucketCannedACL; + + /** * 鏂囦欢瀵硅薄 鏉冮檺绫诲瀷 */ - private final CannedAccessControlList acl; + private final ObjectCannedACL objectCannedACL; /** * 妗剁瓥鐣ョ被鍨� 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 c7d4719..565995d 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 @@ -138,7 +138,7 @@ OssClient storage = OssFactory.instance(); UploadResult uploadResult; try { - uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType()); + uploadResult = storage.uploadSuffix(file.getBytes(), suffix); } catch (IOException e) { throw new ServiceException(e.getMessage()); } -- Gitblit v1.9.3