init: 导入RuoYi‑Vue‑Plus 6.X完整代码
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-common-oss</artifactId>
|
||||
|
||||
<description>
|
||||
ruoyi-common-oss oss服务
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 序列化模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-json</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AWS SDK for Java 2.x -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<exclusions>
|
||||
<!-- 东西 30M 特别大的 jar 包 性能跟 Netty 差不多 有需要可以自行替换使用 -->
|
||||
<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>
|
||||
|
||||
<!-- 适用于 Netty 的客户端 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 客户端的性能增强传输管理器 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3-transfer-manager</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+1252
File diff suppressed because it is too large
Load Diff
+100
@@ -0,0 +1,100 @@
|
||||
package org.dromara.common.oss.client;
|
||||
|
||||
import org.dromara.common.oss.config.OssAsyncExecutorConfig;
|
||||
import org.dromara.common.oss.config.OssClientConfig;
|
||||
import org.dromara.common.oss.exception.S3StorageException;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
|
||||
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
|
||||
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
|
||||
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.presigner.S3Presigner;
|
||||
import software.amazon.awssdk.transfer.s3.S3TransferManager;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* 默认S3存储客户端实现类。
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public class DefaultOssClientImpl extends AbstractOssClientImpl {
|
||||
|
||||
/**
|
||||
* 构造默认 S3 存储客户端。
|
||||
*
|
||||
* @param clientId 客户端 ID
|
||||
* @param config S3 存储客户端配置
|
||||
*/
|
||||
public DefaultOssClientImpl(String clientId, OssClientConfig config) {
|
||||
super(clientId, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化默认 S3 客户端、传输管理器和预签名生成器。
|
||||
*/
|
||||
@Override
|
||||
void doInitialize() {
|
||||
// 校验配置
|
||||
String accessKey = config.accessKey()
|
||||
.filter(bucket -> !bucket.isBlank())
|
||||
.orElseThrow(() -> S3StorageException.form("accessKey is not configured."));
|
||||
String secretKey = config.secretKey()
|
||||
.filter(bucket -> !bucket.isBlank())
|
||||
.orElseThrow(() -> S3StorageException.form("secretKey is not configured."));
|
||||
String endpointUrl = config.getEndpointUrl();
|
||||
String domainUrl = config.getDomainUrl();
|
||||
Region region = config.region().orElse(Region.US_EAST_1);
|
||||
// MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
|
||||
boolean usePathStyleAccess = config.usePathStyleAccess();
|
||||
|
||||
// 创建 AWS 认证信息
|
||||
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey));
|
||||
|
||||
// 创建AWS基于 Netty 的 S3 客户端
|
||||
this.s3AsyncClient = S3AsyncClient.builder()
|
||||
.credentialsProvider(credentialsProvider)
|
||||
.endpointOverride(URI.create(endpointUrl))
|
||||
.region(region)
|
||||
.forcePathStyle(usePathStyleAccess)
|
||||
.serviceConfiguration(S3Configuration.builder().build())
|
||||
.requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
|
||||
.responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
|
||||
.httpClient(
|
||||
NettyNioAsyncHttpClient.builder()
|
||||
.connectionTimeout(Duration.ofSeconds(60))
|
||||
.connectionAcquisitionTimeout(Duration.ofSeconds(30))
|
||||
.maxConcurrency(100)
|
||||
.maxPendingConnectionAcquires(1000)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
//AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
|
||||
this.s3TransferManager = S3TransferManager.builder().s3Client(this.s3AsyncClient).build();
|
||||
|
||||
// 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL
|
||||
this.s3Presigner = S3Presigner.builder()
|
||||
.region(region)
|
||||
.credentialsProvider(credentialsProvider)
|
||||
.endpointOverride(URI.create(domainUrl))
|
||||
.serviceConfiguration(S3Configuration.builder()
|
||||
.pathStyleAccessEnabled(usePathStyleAccess)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// 创建异步调度器对象
|
||||
OssAsyncExecutorConfig asyncExecutorConfig = config.asyncExecutorConfig();
|
||||
// 是否使用虚拟线程
|
||||
if (asyncExecutorConfig.enabledVirtualThread()) {
|
||||
this.asyncExecutor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
} else {
|
||||
this.asyncExecutor = Executors.newScheduledThreadPool(asyncExecutorConfig.corePoolSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
+598
@@ -0,0 +1,598 @@
|
||||
package org.dromara.common.oss.client;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import org.dromara.common.oss.config.OssClientConfig;
|
||||
import org.dromara.common.oss.io.OutputStreamDownloadSubscriber;
|
||||
import org.dromara.common.oss.model.GetObjectResult;
|
||||
import org.dromara.common.oss.model.HandleAsyncResult;
|
||||
import org.dromara.common.oss.model.Options;
|
||||
import org.dromara.common.oss.model.PutObjectResult;
|
||||
import software.amazon.awssdk.core.async.AsyncRequestBody;
|
||||
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
|
||||
import software.amazon.awssdk.transfer.s3.model.CompletedUpload;
|
||||
import software.amazon.awssdk.transfer.s3.progress.TransferListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* S3 存储客户端接口。
|
||||
* <p>
|
||||
* 本接口同时提供两套对象操作 API:
|
||||
* 一套通过 {@code bucketXxx(...)} 显式指定存储桶,
|
||||
* 另一套通过无前缀方法使用默认存储桶。
|
||||
* </p>
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public interface OssClient extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* S3 存储客户端ID
|
||||
* <p>
|
||||
* 用于标识客户端,初始化后不允许更改
|
||||
*
|
||||
* @return S3 存储客户端ID
|
||||
*/
|
||||
default String clientId() {
|
||||
return IdUtil.fastSimpleUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端配置copy副本
|
||||
*/
|
||||
OssClientConfig config();
|
||||
|
||||
/**
|
||||
* 是否已经初始化
|
||||
*/
|
||||
boolean isInitialized();
|
||||
|
||||
/**
|
||||
* 初始化客户端
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* 校验客户端配置
|
||||
*
|
||||
* <p>注意:该方法不会修改任何既有的配置和状态,你看可以理解为这仅仅是一个配置展示的方法,以供调用者根据当前的配置,自行决定是否需要重新构建客户端。</p>
|
||||
*
|
||||
* @param verifyConfigAction 校验配置动作函数
|
||||
* @return 是否一致
|
||||
*/
|
||||
boolean verifyConfig(Function<OssClientConfig, Boolean> verifyConfigAction);
|
||||
|
||||
/**
|
||||
* 校验客户端配置与传入的待校验配置是否一致
|
||||
*
|
||||
* <p>注意:该方法不会修改任何既有的配置和状态,你看可以理解为这仅仅是一个配置展示的方法,以供调用者根据当前的配置,自行决定是否需要重新构建客户端。</p>
|
||||
*
|
||||
* @param verifyConfig 待校验的配置
|
||||
* @return 是否一致
|
||||
*/
|
||||
boolean verifyConfig(OssClientConfig verifyConfig);
|
||||
|
||||
/**
|
||||
* 执行自定义上传请求。
|
||||
*
|
||||
* @param body 上传请求体
|
||||
* @param putObjectRequestBuilderConsumer PutObject 请求构建回调
|
||||
* @param transferListeners 传输监听器集合
|
||||
* @param handleAsyncAction 上传完成后的结果处理函数
|
||||
* @param <T> 返回值类型
|
||||
* @return 处理后的结果
|
||||
*/
|
||||
<T> T doCustomUpload(AsyncRequestBody body, Consumer<PutObjectRequest.Builder> putObjectRequestBuilderConsumer, Collection<TransferListener> transferListeners, BiFunction<CompletedUpload, Throwable, T> handleAsyncAction);
|
||||
|
||||
/**
|
||||
* 执行自定义上传请求。
|
||||
*
|
||||
* @param body 上传请求体
|
||||
* @param putObjectRequestBuilderConsumer PutObject 请求构建回调
|
||||
* @param handleAsyncAction 上传完成后的结果处理函数
|
||||
* @param <T> 返回值类型
|
||||
* @return 处理后的结果
|
||||
*/
|
||||
<T> T doCustomUpload(AsyncRequestBody body, Consumer<PutObjectRequest.Builder> putObjectRequestBuilderConsumer, BiFunction<CompletedUpload, Throwable, T> handleAsyncAction);
|
||||
|
||||
/**
|
||||
* 执行自定义上传请求,并返回统一异步处理结果。
|
||||
*
|
||||
* @param body 上传请求体
|
||||
* @param putObjectRequestBuilderConsumer PutObject 请求构建回调
|
||||
* @param transferListeners 传输监听器集合
|
||||
* @return 上传结果
|
||||
*/
|
||||
HandleAsyncResult<PutObjectResponse> doCustomUpload(AsyncRequestBody body, Consumer<PutObjectRequest.Builder> putObjectRequestBuilderConsumer, Collection<TransferListener> transferListeners);
|
||||
|
||||
/**
|
||||
* 执行自定义上传请求,并返回统一异步处理结果。
|
||||
*
|
||||
* @param body 上传请求体
|
||||
* @param putObjectRequestBuilderConsumer PutObject 请求构建回调
|
||||
* @return 上传结果
|
||||
*/
|
||||
HandleAsyncResult<PutObjectResponse> doCustomUpload(AsyncRequestBody body, Consumer<PutObjectRequest.Builder> putObjectRequestBuilderConsumer);
|
||||
|
||||
/**
|
||||
* 将本地路径对应的文件上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param path 文件路径
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, Path path, Options options);
|
||||
|
||||
/**
|
||||
* 将本地路径对应的文件上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param path 文件路径
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, Path path);
|
||||
|
||||
/**
|
||||
* 将文件上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param file 文件对象
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, File file, Options options);
|
||||
|
||||
/**
|
||||
* 将文件上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param file 文件对象
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, File file);
|
||||
|
||||
/**
|
||||
* 将随机访问文件上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param file 随机访问文件
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, RandomAccessFile file, Options options);
|
||||
|
||||
/**
|
||||
* 将随机访问文件上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param file 随机访问文件
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, RandomAccessFile file);
|
||||
|
||||
/**
|
||||
* 将可读通道中的数据上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param channel 数据通道
|
||||
* @param contentLength 内容长度
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength, Options options);
|
||||
|
||||
/**
|
||||
* 将可读通道中的数据上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param channel 数据通道
|
||||
* @param contentLength 内容长度
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength);
|
||||
|
||||
/**
|
||||
* 将可读通道中的数据上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param in 输入流
|
||||
* @param contentLength 内容长度
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength, Options options);
|
||||
|
||||
/**
|
||||
* 将输入流中的数据上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param in 输入流
|
||||
* @param contentLength 内容长度
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength);
|
||||
|
||||
/**
|
||||
* 将字节数组上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param data 字节数组
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, byte[] data, Options options);
|
||||
|
||||
/**
|
||||
* 将字节数组上传到指定存储桶。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param data 字节数组
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult bucketUpload(String bucket, String key, byte[] data);
|
||||
|
||||
/**
|
||||
* 执行自定义下载请求。
|
||||
*
|
||||
* @param getObjectRequestBuilderConsumer GetObject 请求构建回调
|
||||
* @param responseTransformer 下载响应转换器
|
||||
* @param transferListeners 传输监听器集合
|
||||
* @param <T> 下载结果类型
|
||||
* @return 下载结果
|
||||
*/
|
||||
<T> T doCustomDownload(Consumer<GetObjectRequest.Builder> getObjectRequestBuilderConsumer, AsyncResponseTransformer<GetObjectResponse, T> responseTransformer, Collection<TransferListener> transferListeners);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到订阅器。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param downloadSubscriber 下载订阅器
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult bucketDownload(String bucket, String key, OutputStreamDownloadSubscriber downloadSubscriber);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到转换器中,由使用者决定返回值。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param downloadTransformer 下载转换器
|
||||
* @return 下载结果
|
||||
*/
|
||||
<T> T bucketDownload(String bucket, String key, BiFunction<GetObjectResult, InputStream, T> downloadTransformer);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到本地路径。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param path 本地路径
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult bucketDownload(String bucket, String key, Path path);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到文件。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param file 本地文件
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult bucketDownload(String bucket, String key, File file);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到随机访问文件。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param file 随机访问文件
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult bucketDownload(String bucket, String key, RandomAccessFile file);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到可写通道。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param channel 可写通道
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult bucketDownload(String bucket, String key, WritableByteChannel channel);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到输出流。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param out 输出流
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult bucketDownload(String bucket, String key, OutputStream out);
|
||||
|
||||
/**
|
||||
* 删除指定存储桶中的对象。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
boolean bucketDelete(String bucket, String key);
|
||||
|
||||
/**
|
||||
* 生成指定存储桶中文件下载的预签名 URL。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param expiredTime URL 过期时间
|
||||
* @return 预签名下载 URL
|
||||
*/
|
||||
String bucketPresignGetUrl(String bucket, String key, Duration expiredTime);
|
||||
|
||||
/**
|
||||
* 生成指定存储桶中文件上传的预签名 URL。
|
||||
*
|
||||
* @param bucket 存储桶名称
|
||||
* @param key 对象键
|
||||
* @param expiredTime URL 过期时间
|
||||
* @param metadata 对象元数据
|
||||
* @return 预签名上传 URL
|
||||
*/
|
||||
String bucketPresignPutUrl(String bucket, String key, Duration expiredTime, Map<String, String> metadata);
|
||||
|
||||
/**
|
||||
* 将本地路径对应的文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param path 文件路径
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, Path path, Options options);
|
||||
|
||||
/**
|
||||
* 将本地路径对应的文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param path 文件路径
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, Path path);
|
||||
|
||||
/**
|
||||
* 将文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 文件对象
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, File file, Options options);
|
||||
|
||||
/**
|
||||
* 将文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 文件对象
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, File file);
|
||||
|
||||
/**
|
||||
* 将随机访问文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 随机访问文件
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, RandomAccessFile file, Options options);
|
||||
|
||||
/**
|
||||
* 将随机访问文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 随机访问文件
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, RandomAccessFile file);
|
||||
|
||||
/**
|
||||
* 将可读通道中的数据上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param channel 数据通道
|
||||
* @param contentLength 内容长度
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength, Options options);
|
||||
|
||||
/**
|
||||
* 将可读通道中的数据上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param channel 数据通道
|
||||
* @param contentLength 内容长度
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength);
|
||||
|
||||
/**
|
||||
* 将输入流中的数据上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param in 输入流
|
||||
* @param contentLength 内容长度
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, InputStream in, long contentLength, Options options);
|
||||
|
||||
/**
|
||||
* 将输入流中的数据上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param in 输入流
|
||||
* @param contentLength 内容长度
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, InputStream in, long contentLength);
|
||||
|
||||
/**
|
||||
* 将字节数组上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param data 字节数组
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, byte[] data, Options options);
|
||||
|
||||
/**
|
||||
* 将字节数组上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param data 字节数组
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, byte[] data);
|
||||
|
||||
/**
|
||||
* 将默认存储桶中的对象下载到订阅器。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param downloadSubscriber 下载订阅器
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult download(String key, OutputStreamDownloadSubscriber downloadSubscriber);
|
||||
|
||||
/**
|
||||
* 将指定存储桶中的对象下载到转换器中,由使用者决定返回值。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param downloadTransformer 下载转换器
|
||||
* @return 下载结果
|
||||
*/
|
||||
<T> T download(String key, BiFunction<GetObjectResult, InputStream, T> downloadTransformer);
|
||||
|
||||
/**
|
||||
* 将默认存储桶中的对象下载到本地路径。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param path 本地路径
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult download(String key, Path path);
|
||||
|
||||
/**
|
||||
* 将默认存储桶中的对象下载到文件。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 本地文件
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult download(String key, File file);
|
||||
|
||||
/**
|
||||
* 将默认存储桶中的对象下载到随机访问文件。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 随机访问文件
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult download(String key, RandomAccessFile file);
|
||||
|
||||
/**
|
||||
* 将默认存储桶中的对象下载到可写通道。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param channel 可写通道
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult download(String key, WritableByteChannel channel);
|
||||
|
||||
/**
|
||||
* 将默认存储桶中的对象下载到输出流。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param out 输出流
|
||||
* @return 下载结果
|
||||
*/
|
||||
GetObjectResult download(String key, OutputStream out);
|
||||
|
||||
/**
|
||||
* 删除默认存储桶中的对象。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
boolean delete(String key);
|
||||
|
||||
/**
|
||||
* 生成默认存储桶中文件下载的预签名 URL。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param expiredTime URL 过期时间
|
||||
* @return 预签名下载 URL
|
||||
*/
|
||||
String presignGetUrl(String key, Duration expiredTime);
|
||||
|
||||
/**
|
||||
* 生成默认存储桶中文件上传的预签名 URL。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param expiredTime URL 过期时间
|
||||
* @param metadata 对象元数据
|
||||
* @return 预签名上传 URL
|
||||
*/
|
||||
String presignPutUrl(String key, Duration expiredTime, Map<String, String> metadata);
|
||||
|
||||
/**
|
||||
* 根据客户端配置生成默认对象Key。
|
||||
*
|
||||
* @param fileName 原始文件名
|
||||
* @return 对象Key
|
||||
*/
|
||||
String buildPathKey(String fileName);
|
||||
|
||||
/**
|
||||
* 根据业务前缀和客户端默认前缀生成对象Key。
|
||||
*
|
||||
* @param businessPrefix 业务前缀
|
||||
* @param fileName 原始文件名
|
||||
* @return 对象Key
|
||||
*/
|
||||
String buildPathKey(String businessPrefix, String fileName);
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package org.dromara.common.oss.config;
|
||||
|
||||
import lombok.Builder;
|
||||
import org.dromara.common.oss.enums.AccessPolicy;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* S3 ACL访问策略配置
|
||||
*
|
||||
* @param enabled 是否启用ACL
|
||||
* @param accessPolicy 访问策略
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Builder
|
||||
public record AccessControlPolicyConfig(
|
||||
boolean enabled
|
||||
, AccessPolicy accessPolicy
|
||||
) implements Config<AccessControlPolicyConfig, AccessControlPolicyConfig.AccessControlPolicyConfigBuilder>, Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 默认访问策略配置
|
||||
*/
|
||||
public static final AccessControlPolicyConfig DEFAULT = AccessControlPolicyConfig.builder()
|
||||
.enabled(false)
|
||||
.accessPolicy(AccessPolicy.PUBLIC_READ_WRITE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 获取访问策略,未配置时返回默认策略。
|
||||
*
|
||||
* @return 访问策略
|
||||
*/
|
||||
@Override
|
||||
public @NonNull AccessPolicy accessPolicy() {
|
||||
return Optional.ofNullable(accessPolicy)
|
||||
.orElse(AccessPolicy.PUBLIC_READ_WRITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制访问策略配置。
|
||||
*
|
||||
* @return 配置副本
|
||||
*/
|
||||
@Override
|
||||
public AccessControlPolicyConfig copy() {
|
||||
return toBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为构建器。
|
||||
*
|
||||
* @return 配置构建器
|
||||
*/
|
||||
@Override
|
||||
public AccessControlPolicyConfigBuilder toBuilder() {
|
||||
return builder()
|
||||
.enabled(enabled)
|
||||
.accessPolicy(accessPolicy);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.dromara.common.oss.config;
|
||||
|
||||
/**
|
||||
* 配置对象接口
|
||||
*
|
||||
* @param <T> 配置类型
|
||||
* @param <B> 配置构建器类型
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public interface Config<T, B> {
|
||||
|
||||
/**
|
||||
* 配置对象拷贝
|
||||
*
|
||||
* @return 拷贝后的新配置对象
|
||||
*/
|
||||
T copy();
|
||||
|
||||
/**
|
||||
* 转为构建器对象
|
||||
*
|
||||
* @return 构建器对象
|
||||
*/
|
||||
B toBuilder();
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package org.dromara.common.oss.config;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* S3 异步执行器配置
|
||||
*
|
||||
* @param enabledVirtualThread 是否启用虚拟线程
|
||||
* @param corePoolSize 核心线程数
|
||||
* <p>
|
||||
* 默认为当前CPU核心数,该配置项在配置了虚拟线程后会失效
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Builder
|
||||
public record OssAsyncExecutorConfig(
|
||||
boolean enabledVirtualThread
|
||||
, int corePoolSize
|
||||
) implements Config<OssAsyncExecutorConfig, OssAsyncExecutorConfig.OssAsyncExecutorConfigBuilder>, Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 默认核心线程数 = 当前处理器核心数
|
||||
*/
|
||||
public static final int DEFAULT_CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/**
|
||||
* 默认异步执行器配置
|
||||
*/
|
||||
public static final OssAsyncExecutorConfig DEFAULT = OssAsyncExecutorConfig.builder()
|
||||
.enabledVirtualThread(true)
|
||||
.corePoolSize(DEFAULT_CORE_POOL_SIZE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 是否启用虚拟线程
|
||||
*/
|
||||
@Override
|
||||
public boolean enabledVirtualThread() {
|
||||
return enabledVirtualThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心线程数
|
||||
*/
|
||||
@Override
|
||||
public int corePoolSize() {
|
||||
return corePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制异步执行器配置。
|
||||
*
|
||||
* @return 配置副本
|
||||
*/
|
||||
@Override
|
||||
public OssAsyncExecutorConfig copy() {
|
||||
return toBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为构建器。
|
||||
*
|
||||
* @return 配置构建器
|
||||
*/
|
||||
@Override
|
||||
public OssAsyncExecutorConfigBuilder toBuilder() {
|
||||
return builder()
|
||||
.enabledVirtualThread(enabledVirtualThread)
|
||||
.corePoolSize(corePoolSize);
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
package org.dromara.common.oss.config;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.oss.constant.OssConstant;
|
||||
import org.dromara.common.oss.enums.AccessPolicy;
|
||||
import org.dromara.common.oss.exception.S3StorageException;
|
||||
import org.dromara.common.oss.properties.OssProperties;
|
||||
import org.dromara.common.oss.util.BucketUrlUtil;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* S3存储客户端配置
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode
|
||||
public class OssClientConfig implements Config<OssClientConfig, OssClientConfig.OssClientConfigBuilder>, Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 访问端点
|
||||
*/
|
||||
private final String endpoint;
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
private final String domain;
|
||||
|
||||
/**
|
||||
* 是否使用HTTPS协议
|
||||
*/
|
||||
private final boolean useHttps;
|
||||
|
||||
/**
|
||||
* 是否使用路径样式访问(使用域名需要启用路径样式访问)
|
||||
*/
|
||||
private final boolean usePathStyleAccess;
|
||||
|
||||
/**
|
||||
* ACCESS_KEY
|
||||
*/
|
||||
private final String accessKey;
|
||||
|
||||
/**
|
||||
* SECRET_KEY
|
||||
*/
|
||||
private final String secretKey;
|
||||
|
||||
/**
|
||||
* 存储桶
|
||||
*/
|
||||
private final String bucket;
|
||||
|
||||
/**
|
||||
* 存储区域
|
||||
*/
|
||||
private final Region region;
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* ACL访问策略配置
|
||||
*/
|
||||
private final AccessControlPolicyConfig accessControlPolicyConfig;
|
||||
|
||||
/**
|
||||
* 异步调度池配置
|
||||
*/
|
||||
private final OssAsyncExecutorConfig asyncExecutorConfig;
|
||||
|
||||
/**
|
||||
* 访问端点
|
||||
*/
|
||||
public Optional<String> endpoint() {
|
||||
return Optional.ofNullable(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
public Optional<String> domain() {
|
||||
return Optional.ofNullable(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用HTTPS协议
|
||||
*/
|
||||
public boolean useHttps() {
|
||||
return useHttps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用路径样式访问(使用域名需要启用路径样式访问)
|
||||
*/
|
||||
public boolean usePathStyleAccess() {
|
||||
return usePathStyleAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* ACCESS_KEY
|
||||
*/
|
||||
public Optional<String> accessKey() {
|
||||
return Optional.ofNullable(accessKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* SECRET_KEY
|
||||
*/
|
||||
public Optional<String> secretKey() {
|
||||
return Optional.ofNullable(secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储桶
|
||||
*/
|
||||
public Optional<String> bucket() {
|
||||
return Optional.ofNullable(bucket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储区域
|
||||
*/
|
||||
public Optional<Region> region() {
|
||||
return Optional.ofNullable(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
public Optional<String> prefix() {
|
||||
return Optional.ofNullable(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 OSS 配置属性构建客户端配置。
|
||||
*
|
||||
* @param properties OSS 配置属性
|
||||
* @return 客户端配置
|
||||
*/
|
||||
public static OssClientConfig formProperties(OssProperties properties) {
|
||||
return formPropertiesBuilder(properties).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 OSS 配置属性构建客户端配置构造器。
|
||||
*
|
||||
* @param properties OSS 配置属性
|
||||
* @return 客户端配置构造器
|
||||
*/
|
||||
public static OssClientConfigBuilder formPropertiesBuilder(OssProperties properties) {
|
||||
return builder()
|
||||
.endpoint(properties.getEndpoint())
|
||||
.domain(properties.getDomainUrl())
|
||||
.accessKey(properties.getAccessKey())
|
||||
.secretKey(properties.getSecretKey())
|
||||
.bucket(properties.getBucketName())
|
||||
.region(parseRegion(properties.getRegion()))
|
||||
.prefix(properties.getPrefix())
|
||||
.useHttps(SystemConstants.YES.equals(properties.getIsHttps()))
|
||||
.usePathStyleAccess(resolvePathStyleAccess(properties))
|
||||
.accessControlPolicyConfig(resolveAccessControlPolicy(properties.getAccessPolicy()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问站点URL地址
|
||||
*
|
||||
* @return 访问站点URL地址
|
||||
*/
|
||||
public String getEndpointUrl() {
|
||||
return BucketUrlUtil.rebuildUrlHeader(useHttps, getEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取域名URL地址
|
||||
*
|
||||
* @return 域名URL地址
|
||||
*/
|
||||
public String getDomainUrl() {
|
||||
return domain()
|
||||
// 如果已经配置了自定义域名,则优先使用域名
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(domain -> BucketUrlUtil.rebuildUrlHeader(useHttps, domain.trim()))
|
||||
// 否则使用站点
|
||||
.orElseGet(this::getEndpointUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取桶URL地址
|
||||
*
|
||||
* @return 桶URL地址
|
||||
*/
|
||||
public String getBucketUrl() {
|
||||
// 如果未配置桶,则抛异常
|
||||
String bucket = bucket()
|
||||
.filter(s -> !s.isBlank())
|
||||
.orElseThrow(() -> S3StorageException.form("bucket is not configured."));
|
||||
return getBucketUrl(bucket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取桶URL地址
|
||||
*
|
||||
* @return 桶URL地址
|
||||
*/
|
||||
public String getBucketUrl(String bucket) {
|
||||
String url = getAccessBaseUrl();
|
||||
// 根据是否使用路径风格配置项决定存储桶的URL风格
|
||||
return usePathStyleAccess ? BucketUrlUtil.getPathStyleBucketUrl(useHttps, url, bucket) : BucketUrlUtil.getSiteStyleBucketUrl(useHttps, url, bucket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 S3 Region。
|
||||
*
|
||||
* @param regionString Region 字符串
|
||||
* @return Region 对象
|
||||
*/
|
||||
private static Region parseRegion(String regionString) {
|
||||
if (StringUtils.isBlank(regionString)) {
|
||||
return Region.US_EAST_1;
|
||||
}
|
||||
return Region.of(regionString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧配置推断是否使用路径风格访问。
|
||||
*
|
||||
* @param properties OSS 配置属性
|
||||
* @return 是否使用路径风格访问
|
||||
*/
|
||||
private static boolean resolvePathStyleAccess(OssProperties properties) {
|
||||
// 旧配置没有显式路径风格字段,只能继续按内置云厂商 endpoint 做兼容推断。
|
||||
return !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 ACL 访问策略配置。
|
||||
*
|
||||
* @param accessPolicyString 访问策略字符串
|
||||
* @return ACL 访问策略配置
|
||||
*/
|
||||
private static AccessControlPolicyConfig resolveAccessControlPolicy(String accessPolicyString) {
|
||||
// 绝大多数云厂商不允许操作 ACL,默认禁用;当前业务只用访问策略判断是否生成预签名 URL。
|
||||
if (StringUtils.isBlank(accessPolicyString)) {
|
||||
return AccessControlPolicyConfig.DEFAULT;
|
||||
}
|
||||
return AccessControlPolicyConfig.builder()
|
||||
.enabled(true)
|
||||
.accessPolicy(AccessPolicy.formType(accessPolicyString))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用于访问对象的基础 URL。
|
||||
*
|
||||
* @return 基础 URL
|
||||
*/
|
||||
private String getAccessBaseUrl() {
|
||||
return domain()
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(String::trim)
|
||||
.orElseGet(this::getEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 endpoint 配置。
|
||||
*
|
||||
* @return endpoint
|
||||
*/
|
||||
private String getEndpoint() {
|
||||
return endpoint()
|
||||
.filter(s -> !s.isBlank())
|
||||
.orElseThrow(() -> S3StorageException.form("endpoint is not configured."));
|
||||
}
|
||||
|
||||
/**
|
||||
* ACL访问策略配置
|
||||
*/
|
||||
public @NonNull AccessControlPolicyConfig accessControlPolicyConfig() {
|
||||
return Optional.ofNullable(accessControlPolicyConfig)
|
||||
.orElse(AccessControlPolicyConfig.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* ACL访问策略配置
|
||||
*/
|
||||
public @NonNull OssAsyncExecutorConfig asyncExecutorConfig() {
|
||||
return Optional.ofNullable(asyncExecutorConfig)
|
||||
.orElse(OssAsyncExecutorConfig.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制S3存储客户端配置对象
|
||||
*/
|
||||
@Override
|
||||
public OssClientConfig copy() {
|
||||
return toBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为S3存储客户端配置构建器对象
|
||||
*/
|
||||
@Override
|
||||
public OssClientConfigBuilder toBuilder() {
|
||||
return builder()
|
||||
.endpoint(endpoint)
|
||||
.domain(domain)
|
||||
.useHttps(useHttps)
|
||||
.usePathStyleAccess(usePathStyleAccess)
|
||||
.accessKey(accessKey)
|
||||
.secretKey(secretKey)
|
||||
.bucket(bucket)
|
||||
.region(region)
|
||||
.prefix(prefix)
|
||||
.accessControlPolicyConfig(accessControlPolicyConfig().copy())
|
||||
.asyncExecutorConfig(asyncExecutorConfig().copy());
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.dromara.common.oss.constant;
|
||||
|
||||
import org.dromara.common.core.constant.GlobalConstants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 对象存储常量
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface OssConstant {
|
||||
|
||||
/**
|
||||
* 默认配置KEY
|
||||
*/
|
||||
String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config";
|
||||
|
||||
/**
|
||||
* 预览列表资源开关Key
|
||||
*/
|
||||
String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";
|
||||
|
||||
/**
|
||||
* 系统数据ids
|
||||
*/
|
||||
List<Long> SYSTEM_DATA_IDS = Arrays.asList(
|
||||
1761900000000000001L,
|
||||
1761900000000000002L,
|
||||
1761900000000000003L,
|
||||
1761900000000000004L
|
||||
);
|
||||
|
||||
/**
|
||||
* 云服务商
|
||||
*/
|
||||
String[] CLOUD_SERVICE = new String[]{"aliyun", "qcloud", "qiniu", "obs"};
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package org.dromara.common.oss.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.common.oss.exception.S3StorageException;
|
||||
import software.amazon.awssdk.services.s3.model.BucketCannedACL;
|
||||
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 访问策略
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AccessPolicy {
|
||||
|
||||
/**
|
||||
* 私有
|
||||
*/
|
||||
PRIVATE(0, BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
|
||||
|
||||
/**
|
||||
* 公有读写
|
||||
*/
|
||||
PUBLIC_READ_WRITE(1, BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
|
||||
|
||||
/**
|
||||
* 公有只读
|
||||
*/
|
||||
PUBLIC_READ(2, BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
|
||||
|
||||
/**
|
||||
* 访问策略类型
|
||||
*/
|
||||
private final Integer type;
|
||||
|
||||
/**
|
||||
* 桶权限
|
||||
*/
|
||||
private final BucketCannedACL bucketCannedACL;
|
||||
|
||||
/**
|
||||
* 文件对象权限
|
||||
*/
|
||||
private final ObjectCannedACL objectCannedACL;
|
||||
|
||||
/**
|
||||
* 根据策略类型查找访问策略。
|
||||
*
|
||||
* @param type 策略类型
|
||||
* @return 访问策略
|
||||
*/
|
||||
public static AccessPolicy formType(String type) {
|
||||
return Arrays.stream(values())
|
||||
.filter(policy -> policy.getType().toString().equals(type))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> S3StorageException.form("'type' not found By " + type));
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package org.dromara.common.oss.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* S3对象存储异常
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public class S3StorageException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 使用异常消息构造 S3 对象存储异常。
|
||||
*
|
||||
* @param message 异常消息
|
||||
*/
|
||||
public S3StorageException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用异常消息和原因构造 S3 对象存储异常。
|
||||
*
|
||||
* @param message 异常消息
|
||||
* @param cause 异常原因
|
||||
*/
|
||||
public S3StorageException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用异常原因构造 S3 对象存储异常。
|
||||
*
|
||||
* @param cause 异常原因
|
||||
*/
|
||||
public S3StorageException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用完整异常参数构造 S3 对象存储异常。
|
||||
*
|
||||
* @param message 异常消息
|
||||
* @param cause 异常原因
|
||||
* @param enableSuppression 是否启用抑制
|
||||
* @param writableStackTrace 是否写入堆栈
|
||||
*/
|
||||
public S3StorageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 S3 对象存储异常。
|
||||
*
|
||||
* @param message 异常消息
|
||||
* @return S3 对象存储异常
|
||||
*/
|
||||
public static S3StorageException form(String message) {
|
||||
return new S3StorageException(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 S3 对象存储异常。
|
||||
*
|
||||
* @param message 异常消息
|
||||
* @param cause 异常原因
|
||||
* @return S3 对象存储异常
|
||||
*/
|
||||
public static S3StorageException form(String message, Throwable cause) {
|
||||
return new S3StorageException(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 S3 对象存储异常。
|
||||
*
|
||||
* @param cause 异常原因
|
||||
* @return S3 对象存储异常
|
||||
*/
|
||||
public static S3StorageException form(Throwable cause) {
|
||||
return new S3StorageException(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 S3 对象存储异常。
|
||||
*
|
||||
* @param message 异常消息
|
||||
* @param cause 异常原因
|
||||
* @param enableSuppression 是否启用抑制
|
||||
* @param writableStackTrace 是否写入堆栈
|
||||
* @return S3 对象存储异常
|
||||
*/
|
||||
public static S3StorageException form(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
return new S3StorageException(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package org.dromara.common.oss.factory;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.oss.client.DefaultOssClientImpl;
|
||||
import org.dromara.common.oss.client.OssClient;
|
||||
import org.dromara.common.oss.config.OssClientConfig;
|
||||
import org.dromara.common.oss.constant.OssConstant;
|
||||
import org.dromara.common.oss.exception.S3StorageException;
|
||||
import org.dromara.common.oss.properties.OssProperties;
|
||||
import org.dromara.common.redis.utils.CacheUtils;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* S3存储客户端工厂
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Slf4j
|
||||
public class OssFactory {
|
||||
|
||||
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, ReentrantLock> CLIENT_LOCKS = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取默认实例
|
||||
*/
|
||||
public static OssClient instance() {
|
||||
// 获取redis 默认类型
|
||||
String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY);
|
||||
if (StringUtils.isEmpty(configKey)) {
|
||||
throw S3StorageException.form("文件存储服务类型无法找到!");
|
||||
}
|
||||
return instance(configKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型获取实例
|
||||
*/
|
||||
public static OssClient instance(String configKey) {
|
||||
if (StringUtils.isBlank(configKey)) {
|
||||
throw S3StorageException.form("文件存储服务类型无法找到!");
|
||||
}
|
||||
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
|
||||
if (json == null) {
|
||||
throw S3StorageException.form("系统异常, '" + configKey + "'配置信息不存在!");
|
||||
}
|
||||
OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
|
||||
OssClientConfig config = OssClientConfig.formProperties(properties);
|
||||
ReentrantLock lock = getClientLock(configKey);
|
||||
lock.lock();
|
||||
try {
|
||||
OssClient client = CLIENT_CACHE.get(configKey);
|
||||
if (client != null) {
|
||||
if (client.verifyConfig(config)) {
|
||||
return client;
|
||||
}
|
||||
closeClient(configKey, client);
|
||||
}
|
||||
OssClient newClient = new DefaultOssClientImpl(configKey, config);
|
||||
CLIENT_CACHE.put(configKey, newClient);
|
||||
return newClient;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除实例
|
||||
*/
|
||||
public static boolean remove(String configKey) {
|
||||
if (StringUtils.isBlank(configKey)) {
|
||||
return false;
|
||||
}
|
||||
ReentrantLock lock = getClientLock(configKey);
|
||||
lock.lock();
|
||||
try {
|
||||
OssClient client = CLIENT_CACHE.remove(configKey);
|
||||
if (client == null) {
|
||||
return false;
|
||||
}
|
||||
closeClient(configKey, client);
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定配置键对应的客户端锁。
|
||||
*
|
||||
* @param configKey 配置键
|
||||
* @return 客户端锁
|
||||
*/
|
||||
private static ReentrantLock getClientLock(String configKey) {
|
||||
return CLIENT_LOCKS.computeIfAbsent(configKey, key -> new ReentrantLock());
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭 OSS 客户端。
|
||||
*
|
||||
* @param configKey 配置键
|
||||
* @param client OSS 客户端
|
||||
*/
|
||||
private static void closeClient(String configKey, OssClient client) {
|
||||
try {
|
||||
client.close();
|
||||
} catch (Exception e) {
|
||||
log.warn("S3存储客户端 [{}] 关闭异常,错误信息: {}", configKey, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package org.dromara.common.oss.io;
|
||||
|
||||
import org.dromara.common.oss.exception.S3StorageException;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 输出流下载订阅器
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public class OutputStreamDownloadSubscriber implements Consumer<ByteBuffer>, AutoCloseable {
|
||||
|
||||
private final WritableByteChannel channel;
|
||||
|
||||
private final boolean allowAutoClose;
|
||||
|
||||
/**
|
||||
* 创建输出流下载订阅器。
|
||||
*
|
||||
* @param channel 可写通道
|
||||
* @param allowAutoClose 是否允许自动关闭通道
|
||||
*/
|
||||
private OutputStreamDownloadSubscriber(WritableByteChannel channel, boolean allowAutoClose) {
|
||||
this.channel = channel;
|
||||
this.allowAutoClose = allowAutoClose;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建输出流下载订阅器。
|
||||
*
|
||||
* @param out 输出流
|
||||
* @param allowAutoClose 是否允许自动关闭流
|
||||
*/
|
||||
private OutputStreamDownloadSubscriber(OutputStream out, boolean allowAutoClose) {
|
||||
this.allowAutoClose = allowAutoClose;
|
||||
// 创建可写入的字节通道
|
||||
if (out instanceof FileOutputStream outputStream) {
|
||||
// 如果是文件输入流,直接获取文件输出流的 Channel
|
||||
channel = outputStream.getChannel();
|
||||
} else {
|
||||
channel = Channels.newChannel(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入下载到的字节缓冲区。
|
||||
*
|
||||
* @param byteBuffer 字节缓冲区
|
||||
*/
|
||||
@Override
|
||||
public void accept(ByteBuffer byteBuffer) {
|
||||
try {
|
||||
while (byteBuffer.hasRemaining()) {
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw S3StorageException.form(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按配置关闭底层通道。
|
||||
*
|
||||
* @throws Exception 关闭通道异常
|
||||
*/
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (channel.isOpen() && allowAutoClose) {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个输出流下载订阅器
|
||||
*
|
||||
* @param out 输出流
|
||||
* @return 输出流下载订阅器
|
||||
*/
|
||||
public static OutputStreamDownloadSubscriber create(OutputStream out) {
|
||||
return create(out, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个输出流下载订阅器
|
||||
*
|
||||
* @param out 输出流
|
||||
* @param allowAutoClose 是否允许自动关闭流
|
||||
* @return 输出流下载订阅器
|
||||
*/
|
||||
public static OutputStreamDownloadSubscriber create(OutputStream out, boolean allowAutoClose) {
|
||||
return new OutputStreamDownloadSubscriber(out, allowAutoClose);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个输出流下载订阅器
|
||||
*
|
||||
* @param channel 可写字节通道
|
||||
* @return 输出流下载订阅器
|
||||
*/
|
||||
public static OutputStreamDownloadSubscriber create(WritableByteChannel channel) {
|
||||
return create(channel, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个输出流下载订阅器
|
||||
*
|
||||
* @param channel 可写字节通道
|
||||
* @param allowAutoClose 是否允许自动关闭流
|
||||
* @return 输出流下载订阅器
|
||||
*/
|
||||
public static OutputStreamDownloadSubscriber create(WritableByteChannel channel, boolean allowAutoClose) {
|
||||
return new OutputStreamDownloadSubscriber(channel, allowAutoClose);
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package org.dromara.common.oss.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Get文件对象结果
|
||||
*
|
||||
* @param key
|
||||
* @param eTag
|
||||
* @param lastModified
|
||||
* @param size
|
||||
* @param contentType
|
||||
* @param contentDisposition
|
||||
* @param contentRange
|
||||
* @param contentEncoding
|
||||
* @param contentLanguage
|
||||
* @param metadata
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public record GetObjectResult(
|
||||
String key,
|
||||
String eTag,
|
||||
LocalDateTime lastModified,
|
||||
long size,
|
||||
String contentType,
|
||||
String contentDisposition,
|
||||
String contentRange,
|
||||
String contentEncoding,
|
||||
String contentLanguage,
|
||||
Map<String, String> metadata
|
||||
) {
|
||||
|
||||
/**
|
||||
* 构建文件对象获取结果。
|
||||
*
|
||||
* @param key 对象 Key
|
||||
* @param eTag 对象 ETag
|
||||
* @param lastModified 最后修改时间
|
||||
* @param size 对象大小
|
||||
* @param contentType 内容类型
|
||||
* @param contentDisposition 内容处置方式
|
||||
* @param contentRange 内容范围
|
||||
* @param contentEncoding 内容编码
|
||||
* @param contentLanguage 内容语言
|
||||
* @param metadata 自定义元数据
|
||||
* @return 文件对象获取结果
|
||||
*/
|
||||
public static GetObjectResult form(String key, String eTag, LocalDateTime lastModified, long size
|
||||
, String contentType, String contentDisposition, String contentRange, String contentEncoding, String contentLanguage
|
||||
, Map<String, String> metadata) {
|
||||
return new GetObjectResult(key, eTag, lastModified, size, contentType, contentDisposition, contentRange, contentEncoding, contentLanguage, metadata);
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package org.dromara.common.oss.model;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 处理异步结果
|
||||
*
|
||||
* @param result 结果
|
||||
* @param error 异常错误
|
||||
* @param <T> 结果类型
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public record HandleAsyncResult<T>(
|
||||
T result,
|
||||
Throwable error
|
||||
) {
|
||||
|
||||
/**
|
||||
* 获取异步处理结果。
|
||||
*
|
||||
* @return 异步处理结果
|
||||
*/
|
||||
public Optional<T> getResult() {
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异步处理异常。
|
||||
*
|
||||
* @return 异步处理异常
|
||||
*/
|
||||
public Optional<Throwable> getError() {
|
||||
return Optional.ofNullable(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否处理成功。
|
||||
*
|
||||
* @return true 成功 false 失败
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return getError().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否处理失败。
|
||||
*
|
||||
* @return true 失败 false 成功
|
||||
*/
|
||||
public boolean isFailure() {
|
||||
return getError().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建异步处理结果。
|
||||
*
|
||||
* @param result 结果
|
||||
* @param error 异常
|
||||
* @param <T> 结果类型
|
||||
* @return 异步处理结果
|
||||
*/
|
||||
public static <T> HandleAsyncResult<T> of(T result, Throwable error) {
|
||||
return new HandleAsyncResult<>(result, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建成功的异步处理结果。
|
||||
*
|
||||
* @param result 结果
|
||||
* @param <T> 结果类型
|
||||
* @return 异步处理结果
|
||||
*/
|
||||
public static <T> HandleAsyncResult<T> success(T result) {
|
||||
return new HandleAsyncResult<>(result, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建失败的异步处理结果。
|
||||
*
|
||||
* @param error 异常
|
||||
* @param <T> 结果类型
|
||||
* @return 异步处理结果
|
||||
*/
|
||||
public static <T> HandleAsyncResult<T> failure(Throwable error) {
|
||||
return new HandleAsyncResult<>(null, error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.dromara.common.oss.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import software.amazon.awssdk.transfer.s3.progress.TransferListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可选项
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Options {
|
||||
|
||||
/**
|
||||
* 文件长度
|
||||
*/
|
||||
private Long length;
|
||||
|
||||
/**
|
||||
* 文件MD5摘要
|
||||
*/
|
||||
private String md5Digest;
|
||||
|
||||
/**
|
||||
* 内容类型
|
||||
*/
|
||||
private String contentType;
|
||||
|
||||
/**
|
||||
* 元数据
|
||||
*/
|
||||
private Map<String, String> metadata;
|
||||
|
||||
/**
|
||||
* 传输监听器
|
||||
*/
|
||||
private Collection<TransferListener> transferListeners;
|
||||
|
||||
/**
|
||||
* 添加元数据项
|
||||
*/
|
||||
public Options addMetadataItem(String key, String value) {
|
||||
if (this.metadata == null) {
|
||||
this.metadata = new HashMap<>();
|
||||
}
|
||||
this.metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加监听器
|
||||
*/
|
||||
public Options addTransferListener(TransferListener transferListener) {
|
||||
if (this.transferListeners == null) {
|
||||
this.transferListeners = new ArrayList<>();
|
||||
}
|
||||
this.transferListeners.add(transferListener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可选项对象
|
||||
*/
|
||||
public static Options builder() {
|
||||
return create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可选项对象
|
||||
*/
|
||||
public static Options create() {
|
||||
return new Options();
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package org.dromara.common.oss.model;
|
||||
|
||||
/**
|
||||
* Put文件对象结果
|
||||
*
|
||||
* @param url
|
||||
* @param key
|
||||
* @param eTag
|
||||
* @param size
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public record PutObjectResult(
|
||||
String url,
|
||||
String key,
|
||||
String eTag,
|
||||
long size
|
||||
) {
|
||||
|
||||
/**
|
||||
* 构建文件上传结果。
|
||||
*
|
||||
* @param url 文件地址
|
||||
* @param key 文件标识
|
||||
* @param eTag 文件 ETag
|
||||
* @param size 文件大小
|
||||
* @return 文件上传结果
|
||||
*/
|
||||
public static PutObjectResult form(String url, String key, String eTag, long size) {
|
||||
return new PutObjectResult(url, key, eTag, size);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package org.dromara.common.oss.properties;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* OSS对象存储 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
public class OssProperties {
|
||||
|
||||
/**
|
||||
* 访问站点
|
||||
*/
|
||||
private String endpoint;
|
||||
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
private String domainUrl;
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
private String prefix;
|
||||
|
||||
/**
|
||||
* ACCESS_KEY
|
||||
*/
|
||||
private String accessKey;
|
||||
|
||||
/**
|
||||
* SECRET_KEY
|
||||
*/
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* 存储空间名
|
||||
*/
|
||||
private String bucketName;
|
||||
|
||||
/**
|
||||
* 存储区域
|
||||
*/
|
||||
private String region;
|
||||
|
||||
/**
|
||||
* 是否https(Y=是,N=否)
|
||||
*/
|
||||
private String isHttps;
|
||||
|
||||
/**
|
||||
* 桶权限类型(0private 1public 2custom)
|
||||
*/
|
||||
private String accessPolicy;
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package org.dromara.common.oss.util;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 桶链接工具类
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class BucketUrlUtil {
|
||||
|
||||
public static final String HTTP_PROTOCOL_HEADER = "http://";
|
||||
public static final String HTTPS_PROTOCOL_HEADER = "https://";
|
||||
|
||||
public static final String EMPTY_STRING = "";
|
||||
|
||||
// 路径风格 例:https://s3examples.com/images
|
||||
private static final String PATH_STYLE_HTTP_FORMATE = "http://%s/%s";
|
||||
private static final String PATH_STYLE_HTTPS_FORMATE = "https://%s/%s";
|
||||
|
||||
// 站点风格 例:https://images.oss-cn-beijing.aliyuncs.com
|
||||
private static final String SITE_STYLE_HTTP_FORMATE = "http://%s.%s";
|
||||
private static final String SITE_STYLE_HTTPS_FORMATE = "https://%s.%s";
|
||||
|
||||
/**
|
||||
* 重建链接协议头(将IP、域名、站点的协议头改成HTTP或者HTTPS)
|
||||
*
|
||||
* @param isHttps 是否为HTTP
|
||||
* @param base 基础地址(可以是IP、站点或者域名)
|
||||
* @return 域名地址
|
||||
*/
|
||||
public static String rebuildUrlHeader(boolean isHttps, String base) {
|
||||
String baseUrl = removeHttpProtocolHeader(base);
|
||||
if (isHttps) {
|
||||
return HTTPS_PROTOCOL_HEADER + baseUrl;
|
||||
}
|
||||
return HTTP_PROTOCOL_HEADER + baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路径风格的桶地址 例:https://s3examples.com/images
|
||||
*
|
||||
* @param isHttps 是否为HTTP
|
||||
* @param base 基础地址(可以是IP、站点或者域名)
|
||||
* @param bucketName 桶名称
|
||||
* @return 路径风格的桶地址
|
||||
*/
|
||||
public static String getPathStyleBucketUrl(boolean isHttps, String base, String bucketName) {
|
||||
String baseUrl = removeHttpProtocolHeader(base);
|
||||
if (isHttps) {
|
||||
return String.format(PATH_STYLE_HTTPS_FORMATE, baseUrl, bucketName);
|
||||
}
|
||||
return String.format(PATH_STYLE_HTTP_FORMATE, baseUrl, bucketName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点风格的桶地址 例:https://images.oss-cn-beijing.aliyuncs.com
|
||||
*
|
||||
* @param isHttps 是否为HTTP
|
||||
* @param base 基础地址(可以是IP、站点或者域名)
|
||||
* @param bucketName 桶名称
|
||||
* @return 站点风格的桶地址
|
||||
*/
|
||||
public static String getSiteStyleBucketUrl(boolean isHttps, String base, String bucketName) {
|
||||
String baseUrl = removeHttpProtocolHeader(base);
|
||||
if (isHttps) {
|
||||
return String.format(SITE_STYLE_HTTPS_FORMATE, bucketName, baseUrl);
|
||||
}
|
||||
return String.format(SITE_STYLE_HTTP_FORMATE, bucketName, baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除HTTP/HTTPS协议头(如果有的话)
|
||||
*
|
||||
* @param url 链接地址
|
||||
* @return 移除HTTP/HTTPS协议头后的地址
|
||||
*/
|
||||
public static String removeHttpProtocolHeader(String url) {
|
||||
if (StringUtils.startsWithIgnoreCase(url, HTTP_PROTOCOL_HEADER) || StringUtils.startsWithIgnoreCase(url, HTTPS_PROTOCOL_HEADER)) {
|
||||
return url.replace(HTTP_PROTOCOL_HEADER, EMPTY_STRING)
|
||||
.replace(HTTPS_PROTOCOL_HEADER, EMPTY_STRING);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user