update 优化 oss 模块代码实现

This commit is contained in:
疯狂的狮子Li
2026-03-27 16:02:56 +08:00
parent 5f53681b95
commit 58f1e2ba25
6 changed files with 96 additions and 99 deletions

View File

@@ -2,6 +2,7 @@ package org.dromara.common.oss.client;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.oss.config.OssClientConfig; import org.dromara.common.oss.config.OssClientConfig;
import org.dromara.common.oss.exception.S3StorageException; import org.dromara.common.oss.exception.S3StorageException;
@@ -127,27 +128,6 @@ public abstract class AbstractOssClientImpl implements OssClient {
abstract void doInitialize(); 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 @Override
public boolean verifyConfig(Function<OssClientConfig, Boolean> verifyConfigAction) { public boolean verifyConfig(Function<OssClientConfig, Boolean> verifyConfigAction) {
OssClientConfig config = config(); OssClientConfig config = config();
@@ -159,6 +139,23 @@ public abstract class AbstractOssClientImpl implements OssClient {
return verifyConfig((config) -> Objects.equals(config, verifyConfig)); 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 @Override
public <T> T doCustomUpload(AsyncRequestBody body, Consumer<PutObjectRequest.Builder> putObjectRequestBuilderConsumer, Collection<TransferListener> transferListeners, BiFunction<CompletedUpload, Throwable, T> handleAsyncAction) { public <T> T doCustomUpload(AsyncRequestBody body, Consumer<PutObjectRequest.Builder> putObjectRequestBuilderConsumer, Collection<TransferListener> transferListeners, BiFunction<CompletedUpload, Throwable, T> handleAsyncAction) {
try { try {
@@ -230,7 +227,7 @@ public abstract class AbstractOssClientImpl implements OssClient {
try { try {
// 以文件的大小为准 // 以文件的大小为准
options.setLength(file.length()); options.setLength(file.length());
return bucketUpload(bucket, key, file.getChannel(), -1L); return bucketUpload(bucket, key, file.getChannel(), -1L, options);
} catch (Exception e) { } catch (Exception e) {
if (e instanceof S3StorageException ex) { if (e instanceof S3StorageException ex) {
throw ex; throw ex;
@@ -438,8 +435,8 @@ public abstract class AbstractOssClientImpl implements OssClient {
@Override @Override
public boolean bucketDelete(String bucket, String key) { public boolean bucketDelete(String bucket, String key) {
try { try {
DeleteObjectResponse response = s3AsyncClient.deleteObject(builder -> builder.bucket(bucket).key(key)).join(); s3AsyncClient.deleteObject(builder -> builder.bucket(bucket).key(key)).join();
return Boolean.TRUE.equals(response.deleteMarker()); return true;
} catch (Exception e) { } catch (Exception e) {
throw S3StorageException.form(e); throw S3StorageException.form(e);
} }
@@ -589,6 +586,43 @@ public abstract class AbstractOssClientImpl implements OssClient {
.orElseThrow(() -> S3StorageException.form("bucket is not configured.")); .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 @Override
public void close() throws Exception { public void close() throws Exception {
if (s3TransferManager != null) { if (s3TransferManager != null) {

View File

@@ -68,13 +68,6 @@ public interface OssClient extends AutoCloseable {
*/ */
void initialize(); void initialize();
/**
* 刷新客户端配置
*
* @param config 配置项
*/
void refresh(OssClientConfig config);
/** /**
* 校验客户端配置 * 校验客户端配置
* *
@@ -585,4 +578,21 @@ public interface OssClient extends AutoCloseable {
* @return 预签名上传 URL * @return 预签名上传 URL
*/ */
String presignPutUrl(String key, Duration expiredTime, Map<String, String> metadata); 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);
} }

View File

@@ -182,6 +182,7 @@ public class OssClientConfig implements Config<OssClientConfig, OssClientConfig.
.secretKey(properties.getSecretKey()) .secretKey(properties.getSecretKey())
.bucket(properties.getBucketName()) .bucket(properties.getBucketName())
.region(region) .region(region)
.prefix(properties.getPrefix())
.useHttps(SystemConstants.YES.equals(properties.getIsHttps())) .useHttps(SystemConstants.YES.equals(properties.getIsHttps()))
.usePathStyleAccess(usePathStyleAccess) .usePathStyleAccess(usePathStyleAccess)
.accessControlPolicyConfig(accessControlPolicyConfig); .accessControlPolicyConfig(accessControlPolicyConfig);

View File

@@ -52,19 +52,16 @@ public class OssFactory {
OssClientConfig config = OssClientConfig.formProperties(properties); OssClientConfig config = OssClientConfig.formProperties(properties);
LOCK.lock(); LOCK.lock();
try { try {
// 如果已经存在,则校验配置一致性 OssClient client = CLIENT_CACHE.get(configKey);
if (CLIENT_CACHE.containsKey(configKey)) { if (client != null) {
OssClient client = CLIENT_CACHE.get(configKey); if (client.verifyConfig(config)) {
if (!client.verifyConfig(config)) { return client;
// 配置不一致,刷新配置
client.refresh(config);
CLIENT_CACHE.put(configKey, client);
} }
return client; closeClient(configKey, client);
} }
DefaultOssClientImpl client = new DefaultOssClientImpl(configKey, config); OssClient newClient = new DefaultOssClientImpl(configKey, config);
CLIENT_CACHE.put(configKey, client); CLIENT_CACHE.put(configKey, newClient);
return client; return newClient;
} finally { } finally {
LOCK.unlock(); LOCK.unlock();
} }
@@ -78,12 +75,16 @@ public class OssFactory {
if (client == null) { if (client == null) {
return false; return false;
} }
try { closeClient(configKey, client);
client.close();
} catch (Exception e) {
log.warn("S3存储客户端关闭异常错误信息: {}", e.getMessage(), e);
}
return true; return true;
} }
private static void closeClient(String configKey, OssClient client) {
try {
client.close();
} catch (Exception e) {
log.warn("S3存储客户端 [{}] 关闭异常,错误信息: {}", configKey, e.getMessage(), e);
}
}
} }

View File

@@ -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);
}
}

View File

@@ -26,7 +26,6 @@ import org.dromara.common.oss.enums.AccessPolicy;
import org.dromara.common.oss.factory.OssFactory; import org.dromara.common.oss.factory.OssFactory;
import org.dromara.common.oss.model.Options; import org.dromara.common.oss.model.Options;
import org.dromara.common.oss.model.PutObjectResult; import org.dromara.common.oss.model.PutObjectResult;
import org.dromara.common.oss.util.S3ObjectUtil;
import org.dromara.system.domain.SysOss; import org.dromara.system.domain.SysOss;
import org.dromara.system.domain.SysOssExt; import org.dromara.system.domain.SysOssExt;
import org.dromara.system.domain.bo.SysOssBo; import org.dromara.system.domain.bo.SysOssBo;
@@ -232,11 +231,9 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
String originalfileName = file.getOriginalFilename(); String originalfileName = file.getOriginalFilename();
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
OssClient instance = OssFactory.instance(); OssClient instance = OssFactory.instance();
try { String pathKey = instance.buildPathKey(originalfileName);
String pathKey = S3ObjectUtil.buildPathKey(originalfileName); try (InputStream inputStream = file.getInputStream()) {
InputStream inputStream = file.getInputStream();
PutObjectResult result = instance.upload(pathKey, inputStream, file.getSize(), Options.builder().setContentType(file.getContentType())); PutObjectResult result = instance.upload(pathKey, inputStream, file.getSize(), Options.builder().setContentType(file.getContentType()));
IoUtil.close(inputStream);
SysOssExt ext1 = new SysOssExt(); SysOssExt ext1 = new SysOssExt();
ext1.setFileSize(file.getSize()); ext1.setFileSize(file.getSize());
ext1.setContentType(file.getContentType()); ext1.setContentType(file.getContentType());
@@ -261,7 +258,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
String originalfileName = file.getName(); String originalfileName = file.getName();
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
OssClient instance = OssFactory.instance(); OssClient instance = OssFactory.instance();
String pathKey = S3ObjectUtil.buildPathKey(originalfileName); String pathKey = instance.buildPathKey(originalfileName);
PutObjectResult result = instance.upload(pathKey, file, Options.builder().setContentType(FileUtils.getMimeType(file.toPath()))); PutObjectResult result = instance.upload(pathKey, file, Options.builder().setContentType(FileUtils.getMimeType(file.toPath())));
SysOssExt ext1 = new SysOssExt(); SysOssExt ext1 = new SysOssExt();
ext1.setFileSize(result.size()); ext1.setFileSize(result.size());