diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java index 7e4a4b40f..0fc385069 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java @@ -11,7 +11,6 @@ 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 org.jspecify.annotations.NullMarked; import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; @@ -41,8 +40,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -93,7 +92,7 @@ public abstract class AbstractOssClientImpl implements OssClient { * 构造 S3 存储客户端基础实现。 * * @param clientId 客户端 ID - * @param config S3 存储客户端配置 + * @param config S3 存储客户端配置 */ public AbstractOssClientImpl(String clientId, OssClientConfig config) { Assert.notNull(config, () -> S3StorageException.form("S3StorageClientConfig must not be null")); @@ -103,22 +102,40 @@ public abstract class AbstractOssClientImpl implements OssClient { this.initialize(); } + /** + * 获取客户端 ID。 + * + * @return 客户端 ID + */ @Override public String clientId() { return this.clientId; } + /** + * 获取客户端配置副本。 + * + * @return 客户端配置副本 + */ @Override public OssClientConfig config() { // 仅返回copy副本,防篡改 return this.config.copy(); } + /** + * 判断客户端是否已经初始化。 + * + * @return 是否已初始化 + */ @Override public boolean isInitialized() { return initialized.get(); } + /** + * 初始化底层 S3 客户端资源。 + */ @Override public void initialize() { // 如果已经是初始化状态,则直接返回 @@ -134,24 +151,52 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 执行客户端具体初始化逻辑。 + */ abstract void doInitialize(); + /** + * 使用回调校验当前客户端配置。 + * + * @param verifyConfigAction 配置校验回调 + * @return 是否校验通过 + */ @Override public boolean verifyConfig(Function verifyConfigAction) { OssClientConfig config = config(); return Boolean.TRUE.equals(verifyConfigAction.apply(config)); } + /** + * 校验当前客户端配置是否与指定配置一致。 + * + * @param verifyConfig 待校验配置 + * @return 是否一致 + */ @Override public boolean verifyConfig(OssClientConfig verifyConfig) { return verifyConfig((config) -> Objects.equals(config, verifyConfig)); } + /** + * 根据文件名构建对象键。 + * + * @param fileName 原始文件名 + * @return 对象键 + */ @Override public String buildPathKey(String fileName) { return buildPathKey(null, fileName); } + /** + * 根据业务前缀和文件名构建对象键。 + * + * @param businessPrefix 业务前缀 + * @param fileName 原始文件名 + * @return 对象键 + */ @Override public String buildPathKey(String businessPrefix, String fileName) { String defaultPrefix = config.prefix() @@ -164,6 +209,16 @@ public abstract class AbstractOssClientImpl implements OssClient { return path + suffix; } + /** + * 执行自定义上传请求。 + * + * @param body 上传请求体 + * @param putObjectRequestBuilderConsumer PutObject 请求构建回调 + * @param transferListeners 传输监听器集合 + * @param handleAsyncAction 上传完成处理函数 + * @param 返回值类型 + * @return 上传处理结果 + */ @Override public T doCustomUpload(AsyncRequestBody body, Consumer putObjectRequestBuilderConsumer, Collection transferListeners, BiFunction handleAsyncAction) { try { @@ -180,11 +235,28 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 执行自定义上传请求。 + * + * @param body 上传请求体 + * @param putObjectRequestBuilderConsumer PutObject 请求构建回调 + * @param handleAsyncAction 上传完成处理函数 + * @param 返回值类型 + * @return 上传处理结果 + */ @Override public T doCustomUpload(AsyncRequestBody body, Consumer putObjectRequestBuilderConsumer, BiFunction handleAsyncAction) { return doCustomUpload(body, putObjectRequestBuilderConsumer, null, handleAsyncAction); } + /** + * 执行自定义上传请求并返回统一异步结果。 + * + * @param body 上传请求体 + * @param putObjectRequestBuilderConsumer PutObject 请求构建回调 + * @param transferListeners 传输监听器集合 + * @return 上传结果 + */ @Override public HandleAsyncResult doCustomUpload(AsyncRequestBody body, Consumer putObjectRequestBuilderConsumer, Collection transferListeners) { return doCustomUpload(body, putObjectRequestBuilderConsumer, transferListeners, (completedUpload, throwable) -> { @@ -195,6 +267,13 @@ public abstract class AbstractOssClientImpl implements OssClient { }); } + /** + * 执行自定义上传请求并返回统一异步结果。 + * + * @param body 上传请求体 + * @param putObjectRequestBuilderConsumer PutObject 请求构建回调 + * @return 上传结果 + */ @Override public HandleAsyncResult doCustomUpload(AsyncRequestBody body, Consumer putObjectRequestBuilderConsumer) { return doCustomUpload(body, putObjectRequestBuilderConsumer, null, (completedUpload, throwable) -> { @@ -205,28 +284,71 @@ public abstract class AbstractOssClientImpl implements OssClient { }); } + /** + * 上传本地路径文件到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param path 文件路径 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, Path path, Options options) { AsyncRequestBody body = AsyncRequestBody.fromFile(path); return bucketUpload(bucket, key, body, options); } + /** + * 上传本地路径文件到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param path 文件路径 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, Path path) { return bucketUpload(bucket, key, path, Options.builder()); } + /** + * 上传文件到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param file 文件对象 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, File file, Options options) { AsyncRequestBody body = AsyncRequestBody.fromFile(file); return bucketUpload(bucket, key, body, options); } + /** + * 上传文件到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param file 文件对象 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, File file) { return bucketUpload(bucket, key, file, Options.builder()); } + /** + * 上传随机访问文件到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param file 随机访问文件 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, RandomAccessFile file, Options options) { try { @@ -238,11 +360,29 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 上传随机访问文件到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param file 随机访问文件 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, RandomAccessFile file) { return bucketUpload(bucket, key, file, Options.builder()); } + /** + * 上传可读通道数据到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param channel 可读通道 + * @param contentLength 内容长度 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength, Options options) { // 让调用者自行处理通道的关闭 @@ -259,38 +399,105 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 上传可读通道数据到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param channel 可读通道 + * @param contentLength 内容长度 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength) { return bucketUpload(bucket, key, channel, contentLength, Options.builder()); } + /** + * 上传输入流数据到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param in 输入流 + * @param contentLength 内容长度 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength, Options options) { - options.setLength(contentLength); - AsyncRequestBody body = AsyncRequestBody.fromInputStream(in, contentLength, asyncExecutor); - return bucketUpload(bucket, key, body, options); + Path tempFile = null; + try { + tempFile = Files.createTempFile("ruoyi-oss-upload-", ".tmp"); + try (OutputStream out = Files.newOutputStream(tempFile)) { + in.transferTo(out); + } + options.setLength(Files.size(tempFile)); + return bucketUpload(bucket, key, tempFile, options); + } catch (Exception e) { + throw toStorageException(e); + } finally { + if (tempFile != null) { + try { + Files.deleteIfExists(tempFile); + } catch (IOException ignored) { + // 临时文件清理失败不影响上传结果。 + } + } + } } + /** + * 上传输入流数据到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param in 输入流 + * @param contentLength 内容长度 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength) { return bucketUpload(bucket, key, in, contentLength, Options.builder()); } + /** + * 上传字节数组到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param data 字节数组 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, byte[] data, Options options) { - try (ByteArrayInputStream in = new ByteArrayInputStream(data)) { - return bucketUpload(bucket, key, in, data.length, options); - } catch (Exception e) { - throw toStorageException(e); - } + options.setLength((long) data.length); + AsyncRequestBody body = AsyncRequestBody.fromBytes(data); + return bucketUpload(bucket, key, body, options); } + /** + * 上传字节数组到指定存储桶。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param data 字节数组 + * @return 上传结果 + */ @Override public PutObjectResult bucketUpload(String bucket, String key, byte[] data) { return bucketUpload(bucket, key, data, Options.builder()); } - @NullMarked + /** + * 执行指定存储桶的底层上传。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param body 上传请求体 + * @param options 上传选项 + * @return 上传结果 + */ private PutObjectResult bucketUpload(String bucket, String key, AsyncRequestBody body, Options options) { // 优先使用body中的内容大小,如果不存在,再获取可选项中的 Long contentLength = body.contentLength().orElse(options.getLength()); @@ -308,7 +515,7 @@ public abstract class AbstractOssClientImpl implements OssClient { .metadata(metadata); }, transferListeners); if (result.isFailure()) { - throw S3StorageException.form(result.error()); + throw toStorageException(result.error()); } Optional opt = result.getResult(); if (opt.isEmpty()) { @@ -324,6 +531,15 @@ public abstract class AbstractOssClientImpl implements OssClient { return PutObjectResult.form("%s/%s".formatted(bucketUrl, key), key, response.eTag(), size); } + /** + * 执行自定义下载请求。 + * + * @param getObjectRequestBuilderConsumer GetObject 请求构建回调 + * @param responseTransformer 下载响应转换器 + * @param transferListeners 传输监听器集合 + * @param 下载结果类型 + * @return 下载结果 + */ @Override public T doCustomDownload(Consumer getObjectRequestBuilderConsumer, AsyncResponseTransformer responseTransformer, Collection transferListeners) { try { @@ -341,6 +557,14 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 将指定存储桶对象下载到订阅器。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param downloadSubscriber 下载订阅器 + * @return 下载结果 + */ @Override public GetObjectResult bucketDownload(String bucket, String key, OutputStreamDownloadSubscriber downloadSubscriber) { try { @@ -353,6 +577,15 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 将指定存储桶对象下载到转换器。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param downloadTransformer 下载转换器 + * @param 下载结果类型 + * @return 下载结果 + */ @Override public T bucketDownload(String bucket, String key, BiFunction downloadTransformer) { try (ResponseInputStream responseInputStream = doCustomDownload(builder -> builder.bucket(bucket).key(key), AsyncResponseTransformer.toBlockingInputStream(), null)) { @@ -364,6 +597,14 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 将指定存储桶对象下载到本地路径。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param path 本地路径 + * @return 下载结果 + */ @Override public GetObjectResult bucketDownload(String bucket, String key, Path path) { try (OutputStream out = Files.newOutputStream(path)) { @@ -373,6 +614,14 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 将指定存储桶对象下载到文件。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param file 本地文件 + * @return 下载结果 + */ @Override public GetObjectResult bucketDownload(String bucket, String key, File file) { try (FileOutputStream out = new FileOutputStream(file)) { @@ -382,21 +631,52 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 将指定存储桶对象下载到随机访问文件。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param file 随机访问文件 + * @return 下载结果 + */ @Override public GetObjectResult bucketDownload(String bucket, String key, RandomAccessFile file) { return bucketDownload(bucket, key, file.getChannel()); } + /** + * 将指定存储桶对象下载到可写通道。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param channel 可写通道 + * @return 下载结果 + */ @Override public GetObjectResult bucketDownload(String bucket, String key, WritableByteChannel channel) { return bucketDownload(bucket, key, OutputStreamDownloadSubscriber.create(channel)); } + /** + * 将指定存储桶对象下载到输出流。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param out 输出流 + * @return 下载结果 + */ @Override public GetObjectResult bucketDownload(String bucket, String key, OutputStream out) { return bucketDownload(bucket, key, OutputStreamDownloadSubscriber.create(out)); } + /** + * 根据 S3 响应构建下载结果。 + * + * @param key 对象键 + * @param response S3 下载响应 + * @return 下载结果 + */ private GetObjectResult buildGetObjectResult(String key, GetObjectResponse response) { return GetObjectResult.form( key, @@ -412,6 +692,13 @@ public abstract class AbstractOssClientImpl implements OssClient { ); } + /** + * 删除指定存储桶中的对象。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @return 是否删除成功 + */ @Override public boolean bucketDelete(String bucket, String key) { try { @@ -422,6 +709,14 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 生成指定存储桶对象的下载预签名 URL。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param expiredTime 过期时间 + * @return 预签名下载 URL + */ @Override public String bucketPresignGetUrl(String bucket, String key, Duration expiredTime) { try { @@ -436,6 +731,15 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 生成指定存储桶对象的上传预签名 URL。 + * + * @param bucket 存储桶名称 + * @param key 对象键 + * @param expiredTime 过期时间 + * @param metadata 对象元数据 + * @return 预签名上传 URL + */ @Override public String bucketPresignPutUrl(String bucket, String key, Duration expiredTime, Map metadata) { try { @@ -450,122 +754,299 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 上传本地路径文件到默认存储桶。 + * + * @param key 对象键 + * @param path 文件路径 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, Path path, Options options) { return bucketUpload(defaultBucket(), key, path, options); } + /** + * 上传本地路径文件到默认存储桶。 + * + * @param key 对象键 + * @param path 文件路径 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, Path path) { return bucketUpload(defaultBucket(), key, path); } + /** + * 上传文件到默认存储桶。 + * + * @param key 对象键 + * @param file 文件对象 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, File file, Options options) { return bucketUpload(defaultBucket(), key, file, options); } + /** + * 上传文件到默认存储桶。 + * + * @param key 对象键 + * @param file 文件对象 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, File file) { return bucketUpload(defaultBucket(), key, file); } + /** + * 上传随机访问文件到默认存储桶。 + * + * @param key 对象键 + * @param file 随机访问文件 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, RandomAccessFile file, Options options) { return bucketUpload(defaultBucket(), key, file, options); } + /** + * 上传随机访问文件到默认存储桶。 + * + * @param key 对象键 + * @param file 随机访问文件 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, RandomAccessFile file) { return bucketUpload(defaultBucket(), key, file); } + /** + * 上传可读通道数据到默认存储桶。 + * + * @param key 对象键 + * @param channel 可读通道 + * @param contentLength 内容长度 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength, Options options) { return bucketUpload(defaultBucket(), key, channel, contentLength, options); } + /** + * 上传可读通道数据到默认存储桶。 + * + * @param key 对象键 + * @param channel 可读通道 + * @param contentLength 内容长度 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength) { return bucketUpload(defaultBucket(), key, channel, contentLength); } + /** + * 上传输入流数据到默认存储桶。 + * + * @param key 对象键 + * @param in 输入流 + * @param contentLength 内容长度 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, InputStream in, long contentLength, Options options) { return bucketUpload(defaultBucket(), key, in, contentLength, options); } + /** + * 上传输入流数据到默认存储桶。 + * + * @param key 对象键 + * @param in 输入流 + * @param contentLength 内容长度 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, InputStream in, long contentLength) { return bucketUpload(defaultBucket(), key, in, contentLength); } + /** + * 上传字节数组到默认存储桶。 + * + * @param key 对象键 + * @param data 字节数组 + * @param options 上传选项 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, byte[] data, Options options) { return bucketUpload(defaultBucket(), key, data, options); } + /** + * 上传字节数组到默认存储桶。 + * + * @param key 对象键 + * @param data 字节数组 + * @return 上传结果 + */ @Override public PutObjectResult upload(String key, byte[] data) { return bucketUpload(defaultBucket(), key, data); } + /** + * 将默认存储桶对象下载到订阅器。 + * + * @param key 对象键 + * @param downloadSubscriber 下载订阅器 + * @return 下载结果 + */ @Override public GetObjectResult download(String key, OutputStreamDownloadSubscriber downloadSubscriber) { return bucketDownload(defaultBucket(), key, downloadSubscriber); } + /** + * 将默认存储桶对象下载到转换器。 + * + * @param key 对象键 + * @param downloadTransformer 下载转换器 + * @param 下载结果类型 + * @return 下载结果 + */ @Override public T download(String key, BiFunction downloadTransformer) { return bucketDownload(defaultBucket(), key, downloadTransformer); } + /** + * 将默认存储桶对象下载到本地路径。 + * + * @param key 对象键 + * @param path 本地路径 + * @return 下载结果 + */ @Override public GetObjectResult download(String key, Path path) { return bucketDownload(defaultBucket(), key, path); } + /** + * 将默认存储桶对象下载到文件。 + * + * @param key 对象键 + * @param file 本地文件 + * @return 下载结果 + */ @Override public GetObjectResult download(String key, File file) { return bucketDownload(defaultBucket(), key, file); } + /** + * 将默认存储桶对象下载到随机访问文件。 + * + * @param key 对象键 + * @param file 随机访问文件 + * @return 下载结果 + */ @Override public GetObjectResult download(String key, RandomAccessFile file) { return bucketDownload(defaultBucket(), key, file); } + /** + * 将默认存储桶对象下载到可写通道。 + * + * @param key 对象键 + * @param channel 可写通道 + * @return 下载结果 + */ @Override public GetObjectResult download(String key, WritableByteChannel channel) { return bucketDownload(defaultBucket(), key, channel); } + /** + * 将默认存储桶对象下载到输出流。 + * + * @param key 对象键 + * @param out 输出流 + * @return 下载结果 + */ @Override public GetObjectResult download(String key, OutputStream out) { return bucketDownload(defaultBucket(), key, out); } + /** + * 删除默认存储桶中的对象。 + * + * @param key 对象键 + * @return 是否删除成功 + */ @Override public boolean delete(String key) { return bucketDelete(defaultBucket(), key); } + /** + * 生成默认存储桶对象的下载预签名 URL。 + * + * @param key 对象键 + * @param expiredTime 过期时间 + * @return 预签名下载 URL + */ @Override public String presignGetUrl(String key, Duration expiredTime) { return bucketPresignGetUrl(defaultBucket(), key, expiredTime); } + /** + * 生成默认存储桶对象的上传预签名 URL。 + * + * @param key 对象键 + * @param expiredTime 过期时间 + * @param metadata 对象元数据 + * @return 预签名上传 URL + */ @Override public String presignPutUrl(String key, Duration expiredTime, Map metadata) { return bucketPresignPutUrl(defaultBucket(), key, expiredTime, metadata); } + /** + * 获取默认存储桶名称。 + * + * @return 默认存储桶名称 + */ private String defaultBucket() { return config.bucket() .filter(bucket -> !bucket.isBlank()) .orElseThrow(() -> S3StorageException.form("bucket is not configured.")); } + /** + * 合并默认前缀与业务前缀。 + * + * @param defaultPrefix 默认前缀 + * @param businessPrefix 业务前缀 + * @return 合并后的前缀 + */ private String mergePrefix(String defaultPrefix, String businessPrefix) { String left = normalizePrefix(defaultPrefix); String right = normalizePrefix(businessPrefix); @@ -578,6 +1059,12 @@ public abstract class AbstractOssClientImpl implements OssClient { return left + StringUtils.SLASH + right; } + /** + * 规范化对象键前缀。 + * + * @param prefix 原始前缀 + * @return 规范化后的前缀 + */ private String normalizePrefix(String prefix) { if (prefix == null) { return ""; @@ -592,6 +1079,12 @@ public abstract class AbstractOssClientImpl implements OssClient { return normalized; } + /** + * 提取文件扩展名。 + * + * @param fileName 文件名 + * @return 文件扩展名 + */ private String suffix(String fileName) { if (fileName == null) { return ""; @@ -603,6 +1096,12 @@ public abstract class AbstractOssClientImpl implements OssClient { return fileName.substring(index); } + /** + * 转换为统一的 S3 存储异常。 + * + * @param e 原始异常 + * @return S3 存储异常 + */ private S3StorageException toStorageException(Throwable e) { Throwable cause = unwrapAsyncException(e); if (cause instanceof S3StorageException ex) { @@ -611,6 +1110,12 @@ public abstract class AbstractOssClientImpl implements OssClient { return S3StorageException.form(cause); } + /** + * 解包异步执行异常。 + * + * @param e 原始异常 + * @return 根因异常 + */ private Throwable unwrapAsyncException(Throwable e) { Throwable cause = e; while ((cause instanceof CompletionException || cause instanceof ExecutionException) && cause.getCause() != null) { @@ -619,6 +1124,11 @@ public abstract class AbstractOssClientImpl implements OssClient { return cause; } + /** + * 关闭底层 S3 客户端资源。 + * + * @throws Exception 关闭资源异常 + */ @Override public void close() throws Exception { if (s3TransferManager != null) {