diff --git a/pom.xml b/pom.xml
index 6441d6a7b..a7f6a9eb9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,7 +44,7 @@
2.7.0
- 1.12.600
+ 2.22.9
2.2.0
@@ -236,10 +236,13 @@
- com.amazonaws
- aws-java-sdk-s3
+ software.amazon.awssdk
+ bom
${aws-java-sdk-s3.version}
+ pom
+ import
+
org.dromara.sms4j
diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml
index 8e7affff4..90cc13c9c 100644
--- a/ruoyi-common/ruoyi-common-oss/pom.xml
+++ b/ruoyi-common/ruoyi-common-oss/pom.xml
@@ -27,8 +27,36 @@
- com.amazonaws
- aws-java-sdk-s3
+ software.amazon.awssdk
+ s3
+
+
+ software.amazon.awssdk
+ netty-nio-client
+
+
+ software.amazon.awssdk
+ apache-client
+
+
+
+
+
+ software.amazon.awssdk
+ kms
+
+
+ software.amazon.awssdk
+ s3control
+
+
+ software.amazon.awssdk
+ s3-transfer-manager
+
+
+ software.amazon.awssdk.crt
+ aws-crt
+ 0.29.1
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 53e05c927..62986049e 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,18 +2,6 @@ package org.dromara.common.oss.core;
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.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.oss.constant.OssConstant;
@@ -22,18 +10,29 @@ 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.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.*;
+import software.amazon.awssdk.services.s3.model.*;
+
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
+import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
+import java.net.URI;
import java.net.URL;
-import java.util.Date;
+import java.time.Duration;
/**
- * S3 存储协议 所有兼容S3协议的云厂商均支持
+ * S3-v2 存储协议 所有兼容S3协议的云厂商均支持
* 阿里云 腾讯云 七牛云 minio
*
- * @author Lion Li
+ * @author Lion Li / David Wei
*/
public class OssClient {
@@ -41,39 +40,40 @@ public class OssClient {
private final OssProperties properties;
- private final AmazonS3 client;
+ private final S3Client client;
+
+ private final S3Presigner presigner;
public OssClient(String configKey, OssProperties ossProperties) {
this.configKey = configKey;
this.properties = ossProperties;
try {
- AwsClientBuilder.EndpointConfiguration endpointConfig =
- new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
+ 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();
+ S3Configuration.Builder config = S3Configuration.builder()
+ .chunkedEncodingEnabled(false);
if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
// minio 使用https限制使用域名访问 需要此配置 站点填域名
- build.enablePathStyleAccess();
+ config.pathStyleAccessEnabled(true);
}
- this.client = build.build();
+ S3ClientBuilder client = S3Client.builder()
+ .credentialsProvider(credentialsProvider)
+ .endpointOverride(URI.create(getEndpoint()))
+ .region(Region.of(properties.getRegion()))
+ .serviceConfiguration(config.build());
+
+ S3Presigner.Builder presigner = S3Presigner.builder()
+ .credentialsProvider(credentialsProvider)
+ .endpointOverride(URI.create(getPresignerEndpoint()))
+ .region(Region.of(properties.getRegion()))
+ .serviceConfiguration(config.build());
+
+ this.client = client.build();
+ this.presigner = presigner.build();
createBucket();
} catch (Exception e) {
- if (e instanceof OssException) {
- throw e;
- }
throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
}
}
@@ -81,14 +81,27 @@ public class OssClient {
public void createBucket() {
try {
String bucketName = properties.getBucketName();
- if (client.doesBucketExistV2(bucketName)) {
+ try {
+ client.headBucket(HeadBucketRequest.builder()
+ .bucket(bucketName)
+ .build());
return;
+ } catch (Exception e) {
+ // 桶不存在,捕获异常。
+ // 继续桶创建操作
}
- CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
AccessPolicyType accessPolicy = getAccessPolicy();
- createBucketRequest.setCannedAcl(accessPolicy.getAcl());
+ CreateBucketRequest createBucketRequest = CreateBucketRequest.builder()
+ .bucket(bucketName)
+ .acl(accessPolicy.getBucketCannedACL())
+ .build();
+
+ PutBucketPolicyRequest putBucketPolicyRequest = PutBucketPolicyRequest.builder()
+ .bucket(bucketName)
+ .policy(getPolicy(bucketName, accessPolicy.getPolicyType()))
+ .build();
client.createBucket(createBucketRequest);
- client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
+ client.putBucketPolicy(putBucketPolicyRequest);
} catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
}
@@ -103,13 +116,13 @@ public class OssClient {
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);
+ PutObjectRequest putObjectRequest = PutObjectRequest.builder()
+ .bucket(properties.getBucketName())
+ .key(path)
+ .contentType(contentType)
+ .acl(getAccessPolicy().getObjectCannedACL()) // 设置上传对象的 Acl
+ .build();
+ client.putObject(putObjectRequest, RequestBody.fromInputStream(inputStream, inputStream.available()));
} catch (Exception e) {
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
}
@@ -118,10 +131,12 @@ public class OssClient {
public UploadResult upload(File file, String path) {
try {
- PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
- // 设置上传对象的 Acl 为公共读
- putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
- client.putObject(putObjectRequest);
+ PutObjectRequest putObjectRequest = PutObjectRequest.builder()
+ .bucket(properties.getBucketName())
+ .key(path)
+ .acl(getAccessPolicy().getObjectCannedACL())// 设置上传对象的 Acl
+ .build();
+ client.putObject(putObjectRequest, RequestBody.fromFile(file));
} catch (Exception e) {
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
}
@@ -131,7 +146,11 @@ public class OssClient {
public void delete(String path) {
path = path.replace(getUrl() + "/", "");
try {
- client.deleteObject(properties.getBucketName(), path);
+ DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
+ .bucket(properties.getBucketName())
+ .key(path)
+ .build();
+ client.deleteObject(deleteObjectRequest);
} catch (Exception e) {
throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
}
@@ -149,21 +168,35 @@ public class OssClient {
return upload(file, getPath(properties.getPrefix(), suffix));
}
- /**
- * 获取文件元数据
- *
- * @param path 完整文件路径
- */
- public ObjectMetadata getObjectMetadata(String path) {
- path = path.replace(getUrl() + "/", "");
- S3Object object = client.getObject(properties.getBucketName(), path);
- return object.getObjectMetadata();
- }
-
public InputStream getObjectContent(String path) {
path = path.replace(getUrl() + "/", "");
- S3Object object = client.getObject(properties.getBucketName(), path);
- return object.getObjectContent();
+
+ GetObjectRequest getObjectRequest = GetObjectRequest.builder()
+ .bucket(properties.getBucketName())
+ .key(path)
+ .build();
+ return client.getObject(getObjectRequest);
+ }
+
+ public String getEndpoint() {
+ String endpoint = properties.getEndpoint();
+ String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
+ return header + endpoint;
+ }
+
+ /**
+ * 获取预签名地址的 Endpoint 地址
+ * 注意:此地址不能带bucket,「云服务商」使用非路径形式,生成的url会自动带上bucket前缀
+ * @return
+ */
+ public String getPresignerEndpoint() {
+ String domain = properties.getDomain();
+ String endpoint = properties.getEndpoint();
+ String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
+ if (StringUtils.isNotBlank(domain)) {
+ return header + domain;
+ }
+ return header + endpoint;
}
public String getUrl() {
@@ -207,12 +240,24 @@ public class OssClient {
* @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);
- return url.toString();
+ try {
+ GetObjectRequest getObjectRequest = GetObjectRequest.builder()
+ .bucket(properties.getBucketName())
+ .key(objectKey)
+ .build();
+ GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
+ .signatureDuration(Duration.ofSeconds(second))
+ .getObjectRequest(getObjectRequest)
+ .build();
+
+ PresignedGetObjectRequest presignedGetObjectRequest = presigner
+ .presignGetObject(getObjectPresignRequest);
+ URL url = presignedGetObjectRequest.url();
+
+ return url.toString();
+ } catch (Exception e) {
+ return null;
+ }
}
/**
@@ -231,32 +276,108 @@ public class OssClient {
return AccessPolicyType.getByType(properties.getAccessPolicy());
}
+ /**
+ * 获取 minio 的 policy 定义
+ * @param bucketName
+ * @param policyType
+ * @return
+ */
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": {
+ "AWS": [
+ "*"
+ ]
+ },
+ "Action": [
+ "s3:GetBucketLocation",
+ "s3:ListBucket",
+ "s3:ListBucketMultipartUploads"
+ ],
+ "Resource": [
+ "arn:aws:s3:::bucketName"
+ ]
+ },
+ {
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": [
+ "*"
+ ]
+ },
+ "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": {
+ "AWS": [
+ "*"
+ ]
+ },
+ "Action": [
+ "s3:GetBucketLocation"
+ ],
+ "Resource": [
+ "arn:aws:s3:::bucketName"
+ ]
+ },
+ {
+ "Effect": "Deny",
+ "Principal": "*",
+ "Action": [
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ "arn:aws:s3:::bucketName"
+ ]
+ },
+ {
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": [
+ "*"
+ ]
+ },
+ "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/enumd/AccessPolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java
index 9074d72de..6d39133d5 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 @@ public enum AccessPolicyType {
/**
* 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;
/**
* 桶策略类型