|
|
|
@ -2,73 +2,115 @@ 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.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 存储协议 所有兼容S3协议的云厂商均支持
|
|
|
|
|
* 阿里云 腾讯云 七牛云 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());
|
|
|
|
|
|
|
|
|
|
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 认证信息
|
|
|
|
|
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
|
|
|
|
|
AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
|
|
|
|
|
|
|
|
|
|
//创建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,MinIO 使用 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,141 +120,361 @@ public class OssClient {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 同步创建存储桶
|
|
|
|
|
* 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
|
|
|
|
|
*
|
|
|
|
|
* @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();
|
|
|
|
|
|
|
|
|
|
// 设置存储桶的访问策略(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() + "]");
|
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
/**
|
|
|
|
|
* 下载文件从 Amazon S3 到临时目录
|
|
|
|
|
*
|
|
|
|
|
* @param path 文件在 Amazon S3 中的对象键
|
|
|
|
|
* @return 下载后的文件在本地的临时路径
|
|
|
|
|
* @throws OssException 如果下载失败,抛出自定义异常
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除云存储服务中指定路径下文件
|
|
|
|
|
*
|
|
|
|
|
* @param path 指定路径
|
|
|
|
|
*/
|
|
|
|
|
public void delete(String path) {
|
|
|
|
|
path = path.replace(getUrl() + "/", "");
|
|
|
|
|
try {
|
|
|
|
|
client.deleteObject(properties.getBucketName(), path);
|
|
|
|
|
client.deleteObject(
|
|
|
|
|
x -> x.bucket(properties.getBucketName())
|
|
|
|
|
.key(removeBaseUrl(path))
|
|
|
|
|
.build());
|
|
|
|
|
} 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);
|
|
|
|
|
/**
|
|
|
|
|
* 获取私有URL链接
|
|
|
|
|
*
|
|
|
|
|
* @param objectKey 对象KEY
|
|
|
|
|
* @param second 授权时间
|
|
|
|
|
*/
|
|
|
|
|
public String getPrivateUrl(String objectKey, Integer second) {
|
|
|
|
|
// 使用 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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
|
|
|
|
|
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
|
|
|
|
/**
|
|
|
|
|
* 上传 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, getPath(properties.getPrefix(), suffix));
|
|
|
|
|
return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件元数据
|
|
|
|
|
* 获取文件输入流
|
|
|
|
|
*
|
|
|
|
|
* @param path 完整文件路径
|
|
|
|
|
* @return 输入流
|
|
|
|
|
*/
|
|
|
|
|
public ObjectMetadata getObjectMetadata(String path) {
|
|
|
|
|
path = path.replace(getUrl() + "/", "");
|
|
|
|
|
S3Object object = client.getObject(properties.getBucketName(), path);
|
|
|
|
|
return object.getObjectMetadata();
|
|
|
|
|
public InputStream getObjectContent(String path) throws IOException {
|
|
|
|
|
// 下载文件到临时目录
|
|
|
|
|
Path tempFilePath = fileDownload(path);
|
|
|
|
|
// 创建输入流
|
|
|
|
|
InputStream inputStream = Files.newInputStream(tempFilePath);
|
|
|
|
|
// 删除临时文件
|
|
|
|
|
FileUtils.del(tempFilePath);
|
|
|
|
|
// 返回对象内容的输入流
|
|
|
|
|
return inputStream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public InputStream getObjectContent(String path) {
|
|
|
|
|
path = path.replace(getUrl() + "/", "");
|
|
|
|
|
S3Object object = client.getObject(properties.getBucketName(), path);
|
|
|
|
|
return object.getObjectContent();
|
|
|
|
|
/**
|
|
|
|
|
* 获取 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 = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
|
|
|
|
|
String header = getIsHttps();
|
|
|
|
|
// 云服务商直接返回
|
|
|
|
|
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
|
|
|
|
|
if (StringUtils.isNotBlank(domain)) {
|
|
|
|
|
return header + domain;
|
|
|
|
|
}
|
|
|
|
|
return header + properties.getBucketName() + "." + endpoint;
|
|
|
|
|
return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint);
|
|
|
|
|
}
|
|
|
|
|
// minio 单独处理
|
|
|
|
|
if (StringUtils.isNotBlank(domain)) {
|
|
|
|
|
return header + domain + "/" + properties.getBucketName();
|
|
|
|
|
// 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 + "/" + properties.getBucketName();
|
|
|
|
|
return header + endpoint + StringUtils.SLASH + properties.getBucketName();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成一个符合特定规则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合,确保了文件路径的独一无二性
|
|
|
|
|
*
|
|
|
|
|
* @param prefix 前缀
|
|
|
|
|
* @param suffix 后缀
|
|
|
|
|
* @return 文件路径
|
|
|
|
|
*/
|
|
|
|
|
public String getPath(String prefix, String suffix) {
|
|
|
|
|
// 生成uuid
|
|
|
|
|
String uuid = IdUtil.fastSimpleUUID();
|
|
|
|
|
// 文件路径
|
|
|
|
|
String path = DateUtils.datePath() + "/" + uuid;
|
|
|
|
|
if (StringUtils.isNotBlank(prefix)) {
|
|
|
|
|
path = prefix + "/" + path;
|
|
|
|
|
}
|
|
|
|
|
// 生成日期路径
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取私有URL链接
|
|
|
|
|
* 获取是否使用 HTTPS 的配置,并返回相应的协议头部。
|
|
|
|
|
*
|
|
|
|
|
* @param objectKey 对象KEY
|
|
|
|
|
* @param second 授权时间
|
|
|
|
|
* @return 协议头部,根据是否使用 HTTPS 返回 "https://" 或 "http://"
|
|
|
|
|
*/
|
|
|
|
|
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();
|
|
|
|
|
public String getIsHttps() {
|
|
|
|
|
return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -231,6 +493,13 @@ public class OssClient {
|
|
|
|
|
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");
|
|
|
|
|