feature 升级aws-s3到2.X版本

This commit is contained in:
David Wei 2023-12-28 02:26:06 +08:00
parent 9c84530593
commit 0d3d9d359b
4 changed files with 266 additions and 108 deletions

View File

@ -44,7 +44,7 @@
<ip2region.version>2.7.0</ip2region.version> <ip2region.version>2.7.0</ip2region.version>
<!-- OSS 配置 --> <!-- OSS 配置 -->
<aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version> <aws-java-sdk-s3.version>2.22.9</aws-java-sdk-s3.version>
<!-- SMS 配置 --> <!-- SMS 配置 -->
<sms4j.version>2.2.0</sms4j.version> <sms4j.version>2.2.0</sms4j.version>
<!-- 限制框架中的fastjson版本 --> <!-- 限制框架中的fastjson版本 -->
@ -236,10 +236,13 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.amazonaws</groupId> <groupId>software.amazon.awssdk</groupId>
<artifactId>aws-java-sdk-s3</artifactId> <artifactId>bom</artifactId>
<version>${aws-java-sdk-s3.version}</version> <version>${aws-java-sdk-s3.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency> </dependency>
<!--短信sms4j--> <!--短信sms4j-->
<dependency> <dependency>
<groupId>org.dromara.sms4j</groupId> <groupId>org.dromara.sms4j</groupId>

View File

@ -27,8 +27,36 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.amazonaws</groupId> <groupId>software.amazon.awssdk</groupId>
<artifactId>aws-java-sdk-s3</artifactId> <artifactId>s3</artifactId>
<exclusions>
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
</exclusion>
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>kms</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3control</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk.crt</groupId>
<artifactId>aws-crt</artifactId>
<version>0.29.1</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -2,18 +2,6 @@ package org.dromara.common.oss.core;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil; 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.DateUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.oss.constant.OssConstant; 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.enumd.PolicyType;
import org.dromara.common.oss.exception.OssException; import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.properties.OssProperties; 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.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.Date; import java.time.Duration;
/** /**
* S3 存储协议 所有兼容S3协议的云厂商均支持 * S3-v2 存储协议 所有兼容S3协议的云厂商均支持
* 阿里云 腾讯云 七牛云 minio * 阿里云 腾讯云 七牛云 minio
* *
* @author Lion Li * @author Lion Li / David Wei
*/ */
public class OssClient { public class OssClient {
@ -41,39 +40,40 @@ public class OssClient {
private final OssProperties properties; private final OssProperties properties;
private final AmazonS3 client; private final S3Client client;
private final S3Presigner presigner;
public OssClient(String configKey, OssProperties ossProperties) { public OssClient(String configKey, OssProperties ossProperties) {
this.configKey = configKey; this.configKey = configKey;
this.properties = ossProperties; this.properties = ossProperties;
try { try {
AwsClientBuilder.EndpointConfiguration endpointConfig = StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion()); AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey()); S3Configuration.Builder config = S3Configuration.builder()
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); .chunkedEncodingEnabled(false);
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)) { if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
// minio 使用https限制使用域名访问 需要此配置 站点填域名 // 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(); createBucket();
} catch (Exception e) { } catch (Exception e) {
if (e instanceof OssException) {
throw e;
}
throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
} }
} }
@ -81,14 +81,27 @@ public class OssClient {
public void createBucket() { public void createBucket() {
try { try {
String bucketName = properties.getBucketName(); String bucketName = properties.getBucketName();
if (client.doesBucketExistV2(bucketName)) { try {
client.headBucket(HeadBucketRequest.builder()
.bucket(bucketName)
.build());
return; return;
} catch (Exception e) {
// 桶不存在捕获异常
// 继续桶创建操作
} }
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
AccessPolicyType accessPolicy = getAccessPolicy(); 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.createBucket(createBucketRequest);
client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType())); client.putBucketPolicy(putBucketPolicyRequest);
} catch (Exception e) { } catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
} }
@ -103,13 +116,13 @@ public class OssClient {
inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
} }
try { try {
ObjectMetadata metadata = new ObjectMetadata(); PutObjectRequest putObjectRequest = PutObjectRequest.builder()
metadata.setContentType(contentType); .bucket(properties.getBucketName())
metadata.setContentLength(inputStream.available()); .key(path)
PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata); .contentType(contentType)
// 设置上传对象的 Acl 为公共读 .acl(getAccessPolicy().getObjectCannedACL()) // 设置上传对象的 Acl
putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); .build();
client.putObject(putObjectRequest); client.putObject(putObjectRequest, RequestBody.fromInputStream(inputStream, inputStream.available()));
} catch (Exception e) { } catch (Exception e) {
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
} }
@ -118,10 +131,12 @@ public class OssClient {
public UploadResult upload(File file, String path) { public UploadResult upload(File file, String path) {
try { try {
PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file); PutObjectRequest putObjectRequest = PutObjectRequest.builder()
// 设置上传对象的 Acl 为公共读 .bucket(properties.getBucketName())
putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); .key(path)
client.putObject(putObjectRequest); .acl(getAccessPolicy().getObjectCannedACL())// 设置上传对象的 Acl
.build();
client.putObject(putObjectRequest, RequestBody.fromFile(file));
} catch (Exception e) { } catch (Exception e) {
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
} }
@ -131,7 +146,11 @@ public class OssClient {
public void delete(String path) { public void delete(String path) {
path = path.replace(getUrl() + "/", ""); path = path.replace(getUrl() + "/", "");
try { try {
client.deleteObject(properties.getBucketName(), path); DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
.bucket(properties.getBucketName())
.key(path)
.build();
client.deleteObject(deleteObjectRequest);
} catch (Exception e) { } catch (Exception e) {
throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
} }
@ -149,21 +168,35 @@ public class OssClient {
return upload(file, getPath(properties.getPrefix(), suffix)); 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) { public InputStream getObjectContent(String path) {
path = path.replace(getUrl() + "/", ""); 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() { public String getUrl() {
@ -207,12 +240,24 @@ public class OssClient {
* @param second 授权时间 * @param second 授权时间
*/ */
public String getPrivateUrl(String objectKey, Integer second) { public String getPrivateUrl(String objectKey, Integer second) {
GeneratePresignedUrlRequest generatePresignedUrlRequest = try {
new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.withMethod(HttpMethod.GET) .bucket(properties.getBucketName())
.withExpiration(new Date(System.currentTimeMillis() + 1000L * second)); .key(objectKey)
URL url = client.generatePresignedUrl(generatePresignedUrlRequest); .build();
return url.toString(); 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()); return AccessPolicyType.getByType(properties.getAccessPolicy());
} }
/**
* 获取 minio policy 定义
* @param bucketName
* @param policyType
* @return
*/
private static String getPolicy(String bucketName, PolicyType policyType) { private static String getPolicy(String bucketName, PolicyType policyType) {
StringBuilder builder = new StringBuilder(); String policy = switch (policyType) {
builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n"); case WRITE -> """
builder.append(switch (policyType) { {
case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n"; "Version": "2012-10-17",
case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n"; "Statement": []
default -> "\"s3:GetBucketLocation\"\n"; }
}); """;
builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); case READ_WRITE -> """
builder.append(bucketName); {
builder.append("\"\n},\n"); "Version": "2012-10-17",
if (policyType == PolicyType.READ) { "Statement": [
builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); {
builder.append(bucketName); "Effect": "Allow",
builder.append("\"\n},\n"); "Principal": {
} "AWS": [
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"; "Action": [
default -> "\"s3:GetObject\",\n"; "s3:GetBucketLocation",
}); "s3:ListBucket",
builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); "s3:ListBucketMultipartUploads"
builder.append(bucketName); ],
builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n"); "Resource": [
return builder.toString(); "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);
}
} }

View File

@ -1,8 +1,9 @@
package org.dromara.common.oss.enumd; package org.dromara.common.oss.enumd;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; 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
*/ */
PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE), PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE),
/** /**
* public * public
*/ */
PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ), PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE),
/** /**
* custom * custom
*/ */
CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ); CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ);
/** /**
* 权限类型 * 权限类型数据库值
*/ */
private final String type; private final String type;
/**
* 权限类型
*/
private final BucketCannedACL bucketCannedACL;
/** /**
* 文件对象 权限类型 * 文件对象 权限类型
*/ */
private final CannedAccessControlList acl; private final ObjectCannedACL objectCannedACL;
/** /**
* 桶策略类型 * 桶策略类型