mirror of
https://gitee.com/dromara/RuoYi-Cloud-Plus.git
synced 2026-05-12 14:52:07 +08:00
update 优化 oss 模块代码实现
This commit is contained in:
@@ -2,20 +2,25 @@ package org.dromara.common.oss.client;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.oss.config.OssClientConfig;
|
||||
import org.dromara.common.oss.exception.S3StorageException;
|
||||
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 org.jspecify.annotations.NullMarked;
|
||||
import software.amazon.awssdk.core.ResponseInputStream;
|
||||
import software.amazon.awssdk.core.async.AsyncRequestBody;
|
||||
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
|
||||
import software.amazon.awssdk.core.async.ResponsePublisher;
|
||||
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
||||
import software.amazon.awssdk.services.s3.model.*;
|
||||
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.services.s3.presigner.S3Presigner;
|
||||
import software.amazon.awssdk.transfer.s3.S3TransferManager;
|
||||
import software.amazon.awssdk.transfer.s3.model.CompletedUpload;
|
||||
@@ -24,8 +29,8 @@ import software.amazon.awssdk.transfer.s3.progress.TransferListener;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -46,7 +51,6 @@ import java.util.function.Function;
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractOssClientImpl implements OssClient {
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
@@ -127,27 +131,6 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
|
||||
abstract void doInitialize();
|
||||
|
||||
@Override
|
||||
public void refresh(OssClientConfig config) {
|
||||
if (Objects.equals(this.config, config)) {
|
||||
return;
|
||||
}
|
||||
// 如果状态本来就是未初始化,直接则调用初始化
|
||||
if (!initialized.get()) {
|
||||
this.initialize();
|
||||
}
|
||||
// 将状态转为未初始化
|
||||
if (initialized.compareAndSet(false, true)) {
|
||||
try {
|
||||
this.close();
|
||||
} catch (Exception e) {
|
||||
// 异常不影响刷新逻辑,此处屏蔽异常
|
||||
}
|
||||
// 状态交换成功才进行刷新
|
||||
this.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyConfig(Function<OssClientConfig, Boolean> verifyConfigAction) {
|
||||
OssClientConfig config = config();
|
||||
@@ -159,6 +142,23 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
return verifyConfig((config) -> Objects.equals(config, verifyConfig));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildPathKey(String fileName) {
|
||||
return buildPathKey(null, fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildPathKey(String businessPrefix, String fileName) {
|
||||
String defaultPrefix = config.prefix()
|
||||
.orElse("");
|
||||
String mergedPrefix = mergePrefix(defaultPrefix, businessPrefix);
|
||||
String suffix = suffix(fileName);
|
||||
String datePath = DateUtils.datePath();
|
||||
String uuid = IdUtil.fastSimpleUUID();
|
||||
String path = mergedPrefix.isEmpty() ? datePath + StringUtils.SLASH + uuid : mergedPrefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid;
|
||||
return path + suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T doCustomUpload(AsyncRequestBody body, Consumer<PutObjectRequest.Builder> putObjectRequestBuilderConsumer, Collection<TransferListener> transferListeners, BiFunction<CompletedUpload, Throwable, T> handleAsyncAction) {
|
||||
try {
|
||||
@@ -204,21 +204,57 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, Path path) {
|
||||
public PutObjectResult bucketUpload(String bucket, String key, Path path, Options options) {
|
||||
AsyncRequestBody body = AsyncRequestBody.fromFile(path);
|
||||
return bucketUpload(bucket, key, body);
|
||||
return bucketUpload(bucket, key, body, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, Path path) {
|
||||
return bucketUpload(bucket, key, path, Options.builder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, File file, Options options) {
|
||||
AsyncRequestBody body = AsyncRequestBody.fromFile(file);
|
||||
return bucketUpload(bucket, key, body, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, File file) {
|
||||
AsyncRequestBody body = AsyncRequestBody.fromFile(file);
|
||||
return bucketUpload(bucket, key, body);
|
||||
return bucketUpload(bucket, key, file, Options.builder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, RandomAccessFile file, Options options) {
|
||||
try {
|
||||
// 以文件的大小为准
|
||||
options.setLength(file.length());
|
||||
return bucketUpload(bucket, key, file.getChannel(), -1L, options);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof S3StorageException ex) {
|
||||
throw ex;
|
||||
}
|
||||
throw S3StorageException.form(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, RandomAccessFile file) {
|
||||
return bucketUpload(bucket, key, file, Options.builder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength, Options options) {
|
||||
// 让调用者自行处理通道的关闭
|
||||
InputStream in = Channels.newInputStream(channel);
|
||||
try {
|
||||
return bucketUpload(bucket, key, file.getChannel(), -1L);
|
||||
// 如果可以实时获取文件大小,则优先是有实时获取的
|
||||
long size = contentLength;
|
||||
if (channel instanceof SeekableByteChannel byteChannel) {
|
||||
size = byteChannel.size();
|
||||
}
|
||||
return bucketUpload(bucket, key, in, size, options);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof S3StorageException ex) {
|
||||
throw ex;
|
||||
@@ -229,30 +265,25 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, ReadableByteChannel channel, long contentLength) {
|
||||
long size = contentLength;
|
||||
try (channel; InputStream in = Channels.newInputStream(channel)) {
|
||||
if (channel instanceof FileChannel fileChannel) {
|
||||
size = fileChannel.size();
|
||||
}
|
||||
return bucketUpload(bucket, key, in, size);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof S3StorageException ex) {
|
||||
throw ex;
|
||||
}
|
||||
throw S3StorageException.form(e);
|
||||
}
|
||||
return bucketUpload(bucket, key, channel, contentLength, Options.builder());
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, InputStream in, long contentLength) {
|
||||
AsyncRequestBody body = AsyncRequestBody.fromInputStream(in, contentLength, asyncExecutor);
|
||||
return bucketUpload(bucket, key, body);
|
||||
return bucketUpload(bucket, key, in, contentLength, Options.builder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, byte[] data) {
|
||||
public PutObjectResult bucketUpload(String bucket, String key, byte[] data, Options options) {
|
||||
try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
|
||||
return bucketUpload(bucket, key, in, data.length);
|
||||
return bucketUpload(bucket, key, in, data.length, options);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof S3StorageException ex) {
|
||||
throw ex;
|
||||
@@ -261,15 +292,28 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult bucketUpload(String bucket, String key, byte[] data) {
|
||||
return bucketUpload(bucket, key, data, Options.builder());
|
||||
}
|
||||
|
||||
private PutObjectResult bucketUpload(String bucket, String key, AsyncRequestBody body) {
|
||||
Long contentLength = body.contentLength().orElse(null);
|
||||
@NullMarked
|
||||
private PutObjectResult bucketUpload(String bucket, String key, AsyncRequestBody body, Options options) {
|
||||
// 优先使用body中的内容大小,如果不存在,再获取可选项中的
|
||||
Long contentLength = body.contentLength().orElse(options.getLength());
|
||||
// 优先使用body中的内容类型,如果不存在,再获取可选项中的
|
||||
String contentType = StringUtils.isBlank(options.getContentType()) ? body.contentType() : options.getContentType();
|
||||
String md5Digest = options.getMd5Digest();
|
||||
Map<String, String> metadata = options.getMetadata();
|
||||
Collection<TransferListener> transferListeners = options.getTransferListeners();
|
||||
HandleAsyncResult<PutObjectResponse> result = doCustomUpload(body, builder -> {
|
||||
builder.bucket(bucket)
|
||||
.key(key)
|
||||
.contentMD5(md5Digest)
|
||||
.contentType(contentType)
|
||||
.contentLength(contentLength)
|
||||
;
|
||||
});
|
||||
.metadata(metadata);
|
||||
}, transferListeners);
|
||||
if (result.isFailure()) {
|
||||
throw S3StorageException.form(result.error());
|
||||
}
|
||||
@@ -278,11 +322,13 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
throw S3StorageException.form("response is empty.");
|
||||
}
|
||||
PutObjectResponse response = opt.get();
|
||||
String bucketUrl = config.getBucketUrl(bucket);
|
||||
// 不知道什么原因导致 response.size() 返回了一个 null size ,此处做一个适配...
|
||||
Long size = response.size();
|
||||
size = size == null ? contentLength : size;
|
||||
return PutObjectResult.form("%s/%s".formatted(bucketUrl, key), key, response.eTag(), size == null ? 0 : size);
|
||||
if (size == null) {
|
||||
size = contentLength == null ? 0 : contentLength;
|
||||
}
|
||||
String bucketUrl = config.getBucketUrl(bucket);
|
||||
return PutObjectResult.form("%s/%s".formatted(bucketUrl, key), key, response.eTag(), size);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -392,8 +438,8 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
@Override
|
||||
public boolean bucketDelete(String bucket, String key) {
|
||||
try {
|
||||
DeleteObjectResponse response = s3AsyncClient.deleteObject(builder -> builder.bucket(bucket).key(key)).join();
|
||||
return Boolean.TRUE.equals(response.deleteMarker());
|
||||
s3AsyncClient.deleteObject(builder -> builder.bucket(bucket).key(key)).join();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw S3StorageException.form(e);
|
||||
}
|
||||
@@ -427,31 +473,61 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, Path path, Options options) {
|
||||
return bucketUpload(defaultBucket(), key, path, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, Path path) {
|
||||
return bucketUpload(defaultBucket(), key, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, File file, Options options) {
|
||||
return bucketUpload(defaultBucket(), key, file, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, File file) {
|
||||
return bucketUpload(defaultBucket(), key, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, RandomAccessFile file, Options options) {
|
||||
return bucketUpload(defaultBucket(), key, file, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, RandomAccessFile file) {
|
||||
return bucketUpload(defaultBucket(), key, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength, Options options) {
|
||||
return bucketUpload(defaultBucket(), key, channel, contentLength, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, ReadableByteChannel channel, long contentLength) {
|
||||
return bucketUpload(defaultBucket(), key, channel, contentLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, InputStream in, long contentLength, Options options) {
|
||||
return bucketUpload(defaultBucket(), key, in, contentLength, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, InputStream in, long contentLength) {
|
||||
return bucketUpload(defaultBucket(), key, in, contentLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, byte[] data, Options options) {
|
||||
return bucketUpload(defaultBucket(), key, data, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutObjectResult upload(String key, byte[] data) {
|
||||
return bucketUpload(defaultBucket(), key, data);
|
||||
@@ -513,6 +589,43 @@ public abstract class AbstractOssClientImpl implements OssClient {
|
||||
.orElseThrow(() -> S3StorageException.form("bucket is not configured."));
|
||||
}
|
||||
|
||||
private String mergePrefix(String defaultPrefix, String businessPrefix) {
|
||||
String left = normalizePrefix(defaultPrefix);
|
||||
String right = normalizePrefix(businessPrefix);
|
||||
if (left.isEmpty()) {
|
||||
return right;
|
||||
}
|
||||
if (right.isEmpty()) {
|
||||
return left;
|
||||
}
|
||||
return left + StringUtils.SLASH + right;
|
||||
}
|
||||
|
||||
private String normalizePrefix(String prefix) {
|
||||
if (prefix == null) {
|
||||
return "";
|
||||
}
|
||||
String normalized = prefix.trim();
|
||||
while (normalized.startsWith(StringUtils.SLASH)) {
|
||||
normalized = normalized.substring(1);
|
||||
}
|
||||
while (normalized.endsWith(StringUtils.SLASH)) {
|
||||
normalized = normalized.substring(0, normalized.length() - 1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String suffix(String fileName) {
|
||||
if (fileName == null) {
|
||||
return "";
|
||||
}
|
||||
int index = fileName.lastIndexOf('.');
|
||||
if (index < 0) {
|
||||
return "";
|
||||
}
|
||||
return fileName.substring(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (s3TransferManager != null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -67,13 +68,6 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* 刷新客户端配置
|
||||
*
|
||||
* @param config 配置项
|
||||
*/
|
||||
void refresh(OssClientConfig config);
|
||||
|
||||
/**
|
||||
* 校验客户端配置
|
||||
*
|
||||
@@ -136,6 +130,17 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将本地路径对应的文件上传到指定存储桶。
|
||||
*
|
||||
@@ -146,6 +151,17 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将文件上传到指定存储桶。
|
||||
*
|
||||
@@ -156,6 +172,17 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将随机访问文件上传到指定存储桶。
|
||||
*
|
||||
@@ -166,6 +193,18 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将可读通道中的数据上传到指定存储桶。
|
||||
*
|
||||
@@ -177,6 +216,18 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将输入流中的数据上传到指定存储桶。
|
||||
*
|
||||
@@ -188,6 +239,17 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将字节数组上传到指定存储桶。
|
||||
*
|
||||
@@ -309,6 +371,16 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将本地路径对应的文件上传到默认存储桶。
|
||||
*
|
||||
@@ -318,6 +390,16 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
PutObjectResult upload(String key, Path path);
|
||||
|
||||
/**
|
||||
* 将文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 文件对象
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, File file, Options options);
|
||||
|
||||
/**
|
||||
* 将文件上传到默认存储桶。
|
||||
*
|
||||
@@ -327,6 +409,16 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
PutObjectResult upload(String key, File file);
|
||||
|
||||
/**
|
||||
* 将随机访问文件上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param file 随机访问文件
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, RandomAccessFile file, Options options);
|
||||
|
||||
/**
|
||||
* 将随机访问文件上传到默认存储桶。
|
||||
*
|
||||
@@ -336,6 +428,17 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将可读通道中的数据上传到默认存储桶。
|
||||
*
|
||||
@@ -346,6 +449,17 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 将输入流中的数据上传到默认存储桶。
|
||||
*
|
||||
@@ -356,6 +470,16 @@ public interface OssClient extends AutoCloseable {
|
||||
*/
|
||||
PutObjectResult upload(String key, InputStream in, long contentLength);
|
||||
|
||||
/**
|
||||
* 将字节数组上传到默认存储桶。
|
||||
*
|
||||
* @param key 对象键
|
||||
* @param data 字节数组
|
||||
* @param options 可选项
|
||||
* @return 上传结果
|
||||
*/
|
||||
PutObjectResult upload(String key, byte[] data, Options options);
|
||||
|
||||
/**
|
||||
* 将字节数组上传到默认存储桶。
|
||||
*
|
||||
@@ -454,4 +578,21 @@ public interface OssClient extends AutoCloseable {
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ public class OssClientConfig implements Config<OssClientConfig, OssClientConfig.
|
||||
.secretKey(properties.getSecretKey())
|
||||
.bucket(properties.getBucketName())
|
||||
.region(region)
|
||||
.prefix(properties.getPrefix())
|
||||
.useHttps(SystemConstants.YES.equals(properties.getIsHttps()))
|
||||
.usePathStyleAccess(usePathStyleAccess)
|
||||
.accessControlPolicyConfig(accessControlPolicyConfig);
|
||||
|
||||
@@ -52,21 +52,18 @@ public class OssFactory {
|
||||
OssClientConfig config = OssClientConfig.formProperties(properties);
|
||||
LOCK.lock();
|
||||
try {
|
||||
// 如果已经存在,则校验配置一致性
|
||||
if (CLIENT_CACHE.containsKey(configKey)) {
|
||||
OssClient client = CLIENT_CACHE.get(configKey);
|
||||
if (!client.verifyConfig(config)) {
|
||||
// 配置不一致,刷新配置
|
||||
client.refresh(config);
|
||||
CLIENT_CACHE.put(configKey, client);
|
||||
OssClient client = CLIENT_CACHE.get(configKey);
|
||||
if (client != null) {
|
||||
if (client.verifyConfig(config)) {
|
||||
return client;
|
||||
}
|
||||
return client;
|
||||
closeClient(configKey, client);
|
||||
}
|
||||
DefaultOssClientImpl client = new DefaultOssClientImpl(configKey, config);
|
||||
CLIENT_CACHE.put(configKey, client);
|
||||
return client;
|
||||
OssClient newClient = new DefaultOssClientImpl(configKey, config);
|
||||
CLIENT_CACHE.put(configKey, newClient);
|
||||
return newClient;
|
||||
} finally {
|
||||
LOCK.lock();
|
||||
LOCK.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +75,16 @@ public class OssFactory {
|
||||
if (client == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
client.close();
|
||||
} catch (Exception e) {
|
||||
log.warn("S3存储客户端关闭异常,错误信息: {}", e.getMessage(), e);
|
||||
}
|
||||
closeClient(configKey, client);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void closeClient(String configKey, OssClient client) {
|
||||
try {
|
||||
client.close();
|
||||
} catch (Exception e) {
|
||||
log.warn("S3存储客户端 [{}] 关闭异常,错误信息: {}", configKey, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.dromara.common.oss.util;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* S3文件对象工具类
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class S3ObjectUtil {
|
||||
|
||||
/**
|
||||
* 生成一个 【自定义前缀 + 日期路径 + SimpleUUID.文件后缀】 的对象Key 示例: images/20260321/019d0f89c9b1130a48c90dbca0475a.jpg
|
||||
*
|
||||
* @param prefix 前缀
|
||||
* @param withSuffixFileName 带后缀的文件名
|
||||
* @return 文件路径对象Key
|
||||
*/
|
||||
public static String buildPathKey(String prefix, String withSuffixFileName) {
|
||||
// 获取后缀
|
||||
String suffix = StringUtils.substring(withSuffixFileName, withSuffixFileName.lastIndexOf("."), withSuffixFileName.length());
|
||||
// 生成日期路径
|
||||
String datePath = DateUtils.datePath();
|
||||
// 生成uuid
|
||||
String uuid = IdUtil.fastSimpleUUID();
|
||||
// 拼接路径
|
||||
String path = StringUtils.isNotEmpty(prefix) ? prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
|
||||
return path + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个 【日期路径 + SimpleUUID.文件后缀】 的对象Key 示例: 20260321/019d0f89c9b1130a48c90dbca0475a.jpg
|
||||
*
|
||||
* @param withSuffixFileName 带后缀的文件名
|
||||
* @return 文件路径对象Key
|
||||
*/
|
||||
public static String buildPathKey(String withSuffixFileName) {
|
||||
return buildPathKey("", withSuffixFileName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.oss.client.OssClient;
|
||||
import org.dromara.common.oss.factory.OssFactory;
|
||||
import org.dromara.common.oss.model.PutObjectResult;
|
||||
import org.dromara.common.oss.util.S3ObjectUtil;
|
||||
import org.dromara.resource.api.RemoteFileService;
|
||||
import org.dromara.resource.api.domain.RemoteFile;
|
||||
import org.dromara.resource.domain.SysOssExt;
|
||||
@@ -45,7 +44,7 @@ public class RemoteFileServiceImpl implements RemoteFileService {
|
||||
try {
|
||||
String suffix = StringUtils.substring(originalFilename, originalFilename.lastIndexOf("."), originalFilename.length());
|
||||
OssClient instance = OssFactory.instance();
|
||||
String pathKey = S3ObjectUtil.buildPathKey(originalFilename);
|
||||
String pathKey = instance.buildPathKey(originalFilename);
|
||||
PutObjectResult result = instance.upload(pathKey, file);
|
||||
// 保存文件信息
|
||||
SysOssBo oss = new SysOssBo();
|
||||
|
||||
@@ -21,8 +21,8 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.oss.client.OssClient;
|
||||
import org.dromara.common.oss.enums.AccessPolicy;
|
||||
import org.dromara.common.oss.factory.OssFactory;
|
||||
import org.dromara.common.oss.model.Options;
|
||||
import org.dromara.common.oss.model.PutObjectResult;
|
||||
import org.dromara.common.oss.util.S3ObjectUtil;
|
||||
import org.dromara.resource.domain.SysOss;
|
||||
import org.dromara.resource.domain.SysOssExt;
|
||||
import org.dromara.resource.domain.bo.SysOssBo;
|
||||
@@ -38,6 +38,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -194,9 +195,9 @@ public class SysOssServiceImpl implements ISysOssService {
|
||||
String originalfileName = file.getOriginalFilename();
|
||||
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
|
||||
OssClient instance = OssFactory.instance();
|
||||
try {
|
||||
String pathKey = S3ObjectUtil.buildPathKey(originalfileName);
|
||||
PutObjectResult result = instance.upload(pathKey, file.getInputStream(), file.getSize());
|
||||
String pathKey = instance.buildPathKey(originalfileName);
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
PutObjectResult result = instance.upload(pathKey, inputStream, file.getSize(), Options.builder().setContentType(file.getContentType()));
|
||||
SysOssExt ext1 = new SysOssExt();
|
||||
ext1.setFileSize(file.getSize());
|
||||
ext1.setContentType(file.getContentType());
|
||||
@@ -221,8 +222,8 @@ public class SysOssServiceImpl implements ISysOssService {
|
||||
String originalfileName = file.getName();
|
||||
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
|
||||
OssClient instance = OssFactory.instance();
|
||||
String pathKey = S3ObjectUtil.buildPathKey(originalfileName);
|
||||
PutObjectResult result = instance.upload(pathKey, file);
|
||||
String pathKey = instance.buildPathKey(originalfileName);
|
||||
PutObjectResult result = instance.upload(pathKey, file, Options.builder().setContentType(FileUtils.getMimeType(file.toPath())));
|
||||
SysOssExt ext1 = new SysOssExt();
|
||||
ext1.setFileSize(result.size());
|
||||
// 保存文件信息
|
||||
|
||||
Reference in New Issue
Block a user