更新后的storageAdapter

This commit is contained in:
2025-07-21 23:32:25 +08:00
parent a425c155d0
commit 22dea22fc1
3 changed files with 401 additions and 76 deletions

View File

@@ -33,25 +33,37 @@ import java.util.Map;
import java.util.stream.Collectors;
final public class AliOssAdapter extends AStorageAdapter {
private AliOssStorageConfig config;
private volatile AliOssStorageConfig config;
private volatile OSS ossClient;
private final Object clientLock = new Object();
private final Object configLock = new Object();
@Override
public String identity() {
return config.identity();
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
return "ALI_OSS_UNCONFIGURED";
}
return currentConfig.identity();
}
@Override
public void loadConfig(Map<String, String> _config) {
AliOssStorageConfig config = new AliOssStorageConfig();
config.setAccessKeyId(_config.get("accessKeyId"));
config.setAccessKeySecret(_config.get("accessKeySecret"));
config.setBucketName(_config.get("bucketName"));
config.setEndpoint(_config.get("endpoint"));
config.setRegion(_config.get("region"));
config.setUrl(_config.get("url"));
config.setPrefix(_config.get("prefix"));
config.checkEverythingOK();
this.config = config;
AliOssStorageConfig newConfig = new AliOssStorageConfig();
newConfig.setAccessKeyId(_config.get("accessKeyId"));
newConfig.setAccessKeySecret(_config.get("accessKeySecret"));
newConfig.setBucketName(_config.get("bucketName"));
newConfig.setEndpoint(_config.get("endpoint"));
newConfig.setRegion(_config.get("region"));
newConfig.setUrl(_config.get("url"));
newConfig.setPrefix(_config.get("prefix"));
newConfig.checkEverythingOK();
synchronized (configLock) {
this.config = newConfig;
// 配置更新后,需要重置客户端连接
resetClient();
}
}
@Override
@@ -59,11 +71,15 @@ final public class AliOssAdapter extends AStorageAdapter {
if (config == null) {
throw new StorageConfigException("配置为空");
}
if (config instanceof AliOssStorageConfig) {
this.config = (AliOssStorageConfig) config;
} else {
if (!(config instanceof AliOssStorageConfig)) {
throw new StorageConfigException("配置类型错误,传入的类为:" + config.getClass().getName());
}
synchronized (configLock) {
this.config = (AliOssStorageConfig) config;
// 配置更新后,需要重置客户端连接
resetClient();
}
}
@Override
@@ -75,12 +91,25 @@ final public class AliOssAdapter extends AStorageAdapter {
try (OSSWrapper wrapper = getOssClient()) {
OSS ossClient = wrapper.getOSSClient();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(inputStream.available());
// 读取完整内容并计算实际长度
byte[] bytes = inputStream.readAllBytes();
metadata.setContentLength(bytes.length);
if (StringUtils.isNotBlank(contentType)) {
metadata.setContentType(contentType);
}
PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, inputStream);
ossClient.putObject(putObjectRequest);
// 使用字节数组创建新的 InputStream
try (InputStream byteInputStream = new java.io.ByteArrayInputStream(bytes)) {
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
PutObjectRequest putObjectRequest = new PutObjectRequest(currentConfig.getBucketName(), fullPath, byteInputStream, metadata);
ossClient.putObject(putObjectRequest);
}
return getUrl(path);
} catch (Exception e) {
throw new UploadFileFailedException("上传文件失败:" + e.getMessage());
@@ -92,7 +121,11 @@ final public class AliOssAdapter extends AStorageAdapter {
public boolean deleteFile(String... path) {
try (OSSWrapper wrapper = getOssClient()) {
OSS ossClient = wrapper.getOSSClient();
ossClient.deleteObject(config.getBucketName(), buildPath(path));
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
return false;
}
ossClient.deleteObject(currentConfig.getBucketName(), buildPath(path));
return true;
} catch (ClientException e) {
return false;
@@ -101,14 +134,22 @@ final public class AliOssAdapter extends AStorageAdapter {
@Override
public String getUrl(String... path) {
return config.getUrl() + "/" + buildPath(path);
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
return currentConfig.getUrl() + "/" + buildPath(path);
}
@Override
public String getUrlForDownload(Date expireDate, String... path) {
try (OSSWrapper wrapper = getOssClient()) {
OSS ossClient = wrapper.getOSSClient();
URL url = ossClient.generatePresignedUrl(config.getBucketName(), buildPath(path), expireDate, HttpMethod.GET);
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
URL url = ossClient.generatePresignedUrl(currentConfig.getBucketName(), buildPath(path), expireDate, HttpMethod.GET);
return url.toString();
}
}
@@ -117,7 +158,11 @@ final public class AliOssAdapter extends AStorageAdapter {
public String getUrlForUpload(Date expireDate, String contentType, String... path) {
try (OSSWrapper wrapper = getOssClient()) {
OSS ossClient = wrapper.getOSSClient();
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(config.getBucketName(), buildPath(path), HttpMethod.PUT);
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(currentConfig.getBucketName(), buildPath(path), HttpMethod.PUT);
if (StringUtils.isNotBlank(contentType)) {
request.setContentType(contentType);
}
@@ -129,7 +174,11 @@ final public class AliOssAdapter extends AStorageAdapter {
@Override
public List<StorageFileObject> listDir(String... path) {
ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request(config.getBucketName());
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request(currentConfig.getBucketName());
listObjectsV2Request.setPrefix(buildPath(path) + "/");
listObjectsV2Request.setMaxKeys(1000);
boolean isTruncated = true;
@@ -180,7 +229,11 @@ final public class AliOssAdapter extends AStorageAdapter {
}
List<StorageFileObject> subList = objectList.subList(idx, idx + batchSize);
idx += batchSize;
DeleteObjectsRequest request = new DeleteObjectsRequest(config.getBucketName());
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
return false;
}
DeleteObjectsRequest request = new DeleteObjectsRequest(currentConfig.getBucketName());
request.setKeys(subList.stream().map(StorageFileObject::getFullPath).collect(Collectors.toList()));
try {
ossClient.deleteObjects(request);
@@ -208,7 +261,11 @@ final public class AliOssAdapter extends AStorageAdapter {
public boolean setAcl(StorageAcl acl, String... path) {
try (OSSWrapper wrapper = getOssClient()) {
OSS ossClient = wrapper.getOSSClient();
ossClient.setObjectAcl(config.getBucketName(), buildPath(path), convertAcl(acl));
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
return false;
}
ossClient.setObjectAcl(currentConfig.getBucketName(), buildPath(path), convertAcl(acl));
return true;
} catch (OSSException e) {
return false;
@@ -219,32 +276,85 @@ final public class AliOssAdapter extends AStorageAdapter {
public boolean isExists(String ...path) {
try (OSSWrapper wrapper = getOssClient()) {
OSS ossClient = wrapper.getOSSClient();
return ossClient.doesObjectExist(config.getBucketName(), buildPath(path));
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
return false;
}
return ossClient.doesObjectExist(currentConfig.getBucketName(), buildPath(path));
}
}
private OSSWrapper getOssClient() {
OSS ossClient = new OSSClientBuilder().build(config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
return new OSSWrapper(ossClient);
AliOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
if (ossClient == null) {
synchronized (clientLock) {
if (ossClient == null) {
// 在同步块内再次检查配置,确保配置一致性
currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
ossClient = new OSSClientBuilder().build(
currentConfig.getEndpoint(),
currentConfig.getAccessKeyId(),
currentConfig.getAccessKeySecret()
);
}
}
}
return new OSSWrapper(ossClient, false); // false 表示不自动关闭共享客户端
}
// 重置客户端连接(配置更新时调用)
private void resetClient() {
synchronized (clientLock) {
if (ossClient != null) {
ossClient.shutdown();
ossClient = null;
}
}
}
// 手动关闭客户端的方法,用于适配器销毁时
public void shutdown() {
synchronized (clientLock) {
if (ossClient != null) {
ossClient.shutdown();
ossClient = null;
}
}
}
private String buildPath(String ...paths) {
if (StringUtils.isNotBlank(config.getPrefix())) {
return StorageUtil.joinPath(config.getPrefix(), paths);
AliOssStorageConfig currentConfig = this.config;
if (currentConfig != null && StringUtils.isNotBlank(currentConfig.getPrefix())) {
return StorageUtil.joinPath(currentConfig.getPrefix(), paths);
} else {
return StorageUtil.joinPath(paths);
}
}
private String getRelativePath(String path) {
return StorageUtil.getRelativePath(path, config.getPrefix());
AliOssStorageConfig currentConfig = this.config;
String prefix = currentConfig != null ? currentConfig.getPrefix() : null;
return StorageUtil.getRelativePath(path, prefix);
}
public static class OSSWrapper implements AutoCloseable {
private final OSS ossClient;
private final boolean autoClose;
public OSSWrapper(OSS ossClient) {
this(ossClient, true);
}
public OSSWrapper(OSS ossClient, boolean autoClose) {
this.ossClient = ossClient;
this.autoClose = autoClose;
}
// 提供对原始对象的方法访问
@@ -254,8 +364,10 @@ final public class AliOssAdapter extends AStorageAdapter {
@Override
public void close() {
// 在此处实现资源关闭逻辑(如调用 OSS 的关闭方法)
ossClient.shutdown(); // 假设 OSS 提供 shutdown 方法关闭资源
// 只有非共享客户端才自动关闭
if (autoClose && ossClient != null) {
ossClient.shutdown();
}
}
}
}

View File

@@ -26,25 +26,37 @@ import java.util.Map;
import java.util.stream.Collectors;
public class AwsOssAdapter extends AStorageAdapter {
private AwsOssStorageConfig config;
private volatile AwsOssStorageConfig config;
private volatile AmazonS3Client s3Client;
private final Object clientLock = new Object();
private final Object configLock = new Object();
@Override
public String identity() {
return config.identity();
AwsOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
return "AWS_OSS_UNCONFIGURED";
}
return currentConfig.identity();
}
@Override
public void loadConfig(Map<String, String> _config) {
AwsOssStorageConfig config = new AwsOssStorageConfig();
config.setAccessKeyId(_config.get("accessKeyId"));
config.setAccessKeySecret(_config.get("accessKeySecret"));
config.setBucketName(_config.get("bucketName"));
config.setEndpoint(_config.get("endpoint"));
config.setRegion(_config.get("region"));
config.setUrl(_config.get("url"));
config.setPrefix(_config.get("prefix"));
config.checkEverythingOK();
this.config = config;
AwsOssStorageConfig newConfig = new AwsOssStorageConfig();
newConfig.setAccessKeyId(_config.get("accessKeyId"));
newConfig.setAccessKeySecret(_config.get("accessKeySecret"));
newConfig.setBucketName(_config.get("bucketName"));
newConfig.setEndpoint(_config.get("endpoint"));
newConfig.setRegion(_config.get("region"));
newConfig.setUrl(_config.get("url"));
newConfig.setPrefix(_config.get("prefix"));
newConfig.checkEverythingOK();
synchronized (configLock) {
this.config = newConfig;
// 配置更新后,需要重置客户端连接
resetClient();
}
}
@Override
@@ -52,11 +64,15 @@ public class AwsOssAdapter extends AStorageAdapter {
if (config == null) {
throw new StorageConfigException("配置为空");
}
if (config instanceof AwsOssStorageConfig) {
this.config = (AwsOssStorageConfig) config;
} else {
if (!(config instanceof AwsOssStorageConfig)) {
throw new StorageConfigException("配置类型错误,传入的类为:" + config.getClass().getName());
}
synchronized (configLock) {
this.config = (AwsOssStorageConfig) config;
// 配置更新后,需要重置客户端连接
resetClient();
}
}
@Override
@@ -68,13 +84,22 @@ public class AwsOssAdapter extends AStorageAdapter {
try (S3Wrapper wrapper = getS3Client()) {
AmazonS3Client s3Client = wrapper.s3Client();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(inputStream.available());
// 读取完整内容并计算实际长度
byte[] bytes = inputStream.readAllBytes();
metadata.setContentLength(bytes.length);
if (StringUtils.isNotBlank(contentType)) {
metadata.setContentType(contentType);
}
PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, inputStream, metadata);
putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead); // 设置访问权限,让所有用户都允许访问
s3Client.putObject(putObjectRequest);
// 使用字节数组创建新的 InputStream
try (InputStream byteInputStream = new java.io.ByteArrayInputStream(bytes)) {
PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, byteInputStream, metadata);
putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead); // 设置访问权限,让所有用户都允许访问
s3Client.putObject(putObjectRequest);
}
return getUrl(path);
} catch (Exception e) {
throw new UploadFileFailedException("上传文件失败:" + e.getMessage());
@@ -150,6 +175,7 @@ public class AwsOssAdapter extends AStorageAdapter {
object.setPath(getRelativePath(item.getKey().substring(0, item.getKey().lastIndexOf("/"))));
object.setName(item.getKey().substring(item.getKey().lastIndexOf("/") + 1));
object.setSize(item.getSize());
object.setModifyTime(item.getLastModified());
object.setRawObject(item);
return object;
}).collect(Collectors.toList());
@@ -220,14 +246,57 @@ public class AwsOssAdapter extends AStorageAdapter {
}
private S3Wrapper getS3Client() {
BasicAWSCredentials basicAwsCred = new BasicAWSCredentials(config.getAccessKeyId(), config.getAccessKeySecret());
ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setProtocol(Protocol.HTTPS);
AmazonS3Client s3 = new AmazonS3Client(basicAwsCred,clientConfiguration);
S3ClientOptions options = S3ClientOptions.builder().setPathStyleAccess(true).setPayloadSigningEnabled(true).disableChunkedEncoding().build();
s3.setS3ClientOptions(options);
s3.setEndpoint(config.getEndpoint());
return new S3Wrapper(s3);
AwsOssStorageConfig currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
if (s3Client == null) {
synchronized (clientLock) {
if (s3Client == null) {
// 在同步块内再次检查配置,确保配置一致性
currentConfig = this.config;
if (currentConfig == null) {
throw new StorageConfigException("存储适配器未配置");
}
BasicAWSCredentials basicAwsCred = new BasicAWSCredentials(
currentConfig.getAccessKeyId(),
currentConfig.getAccessKeySecret()
);
ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setProtocol(Protocol.HTTPS);
s3Client = new AmazonS3Client(basicAwsCred, clientConfiguration);
S3ClientOptions options = S3ClientOptions.builder()
.setPathStyleAccess(true)
.setPayloadSigningEnabled(true)
.disableChunkedEncoding()
.build();
s3Client.setS3ClientOptions(options);
s3Client.setEndpoint(currentConfig.getEndpoint());
}
}
}
return new S3Wrapper(s3Client, false); // false 表示不自动关闭共享客户端
}
// 重置客户端连接(配置更新时调用)
private void resetClient() {
synchronized (clientLock) {
if (s3Client != null) {
s3Client.shutdown();
s3Client = null;
}
}
}
// 手动关闭客户端的方法,用于适配器销毁时
public void shutdown() {
synchronized (clientLock) {
if (s3Client != null) {
s3Client.shutdown();
s3Client = null;
}
}
}
private String buildPath(String... paths) {
@@ -242,10 +311,18 @@ public class AwsOssAdapter extends AStorageAdapter {
return StorageUtil.getRelativePath(path, config.getPrefix());
}
public record S3Wrapper(AmazonS3Client s3Client) implements AutoCloseable {
public record S3Wrapper(AmazonS3Client s3Client, boolean autoClose) implements AutoCloseable {
public S3Wrapper(AmazonS3Client s3Client) {
this(s3Client, true);
}
@Override
public void close() {
s3Client.shutdown();
// 只有非共享客户端才自动关闭
if (autoClose && s3Client != null) {
s3Client.shutdown();
}
}
}
}

View File

@@ -3,71 +3,207 @@ package com.ycwl.basic.storage.adapters;
import com.ycwl.basic.storage.entity.StorageConfig;
import com.ycwl.basic.storage.entity.StorageFileObject;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.storage.exceptions.StorageConfigException;
import com.ycwl.basic.storage.exceptions.StorageException;
import com.ycwl.basic.storage.exceptions.UploadFileFailedException;
import com.ycwl.basic.storage.utils.StorageUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class LocalStorageAdapter extends AStorageAdapter {
private volatile String basePath;
private volatile String baseUrl;
private volatile String prefix = "";
private final Object configLock = new Object();
public class LocalStorageAdapter extends AStorageAdapter{
@Override
public String identity() {
return "";
return "LOCAL_STORAGE";
}
@Override
public void loadConfig(Map<String, String> config) {
String newBasePath = config.get("basePath");
String newBaseUrl = config.get("baseUrl");
String newPrefix = StringUtils.isNotBlank(config.get("prefix")) ? config.get("prefix") : "";
if (StringUtils.isBlank(newBasePath)) {
throw new StorageConfigException("本地存储配置错误:basePath 不能为空");
}
if (StringUtils.isBlank(newBaseUrl)) {
throw new StorageConfigException("本地存储配置错误:baseUrl 不能为空");
}
// 确保基础目录存在
try {
Files.createDirectories(Paths.get(newBasePath));
} catch (IOException e) {
throw new StorageConfigException("创建本地存储目录失败:" + e.getMessage());
}
synchronized (configLock) {
this.basePath = newBasePath;
this.baseUrl = newBaseUrl;
this.prefix = newPrefix;
}
}
@Override
public void setConfig(StorageConfig config) {
throw new StorageConfigException("LocalStorageAdapter 不支持 StorageConfig 类型配置");
}
@Override
public String uploadFile(String contentType, InputStream inputStream, String... path) {
return "";
if (inputStream == null) {
return null;
}
String fullPath = buildLocalPath(path);
try {
// 确保父目录存在
Path filePath = Paths.get(fullPath);
Files.createDirectories(filePath.getParent());
// 复制文件内容到本地
Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
return getUrl(path);
} catch (IOException e) {
throw new UploadFileFailedException("本地文件保存失败:" + e.getMessage());
}
}
@Override
public boolean deleteFile(String... path) {
return false;
try {
String fullPath = buildLocalPath(path);
return Files.deleteIfExists(Paths.get(fullPath));
} catch (IOException e) {
return false;
}
}
@Override
public String getUrl(String... path) {
return "";
String relativePath = buildRelativePath(path);
return baseUrl + "/" + relativePath;
}
@Override
public String getUrlForDownload(Date expireDate, String... path) {
return "";
// 本地存储不支持带过期时间的URL,直接返回普通URL
return getUrl(path);
}
@Override
public String getUrlForUpload(Date expireDate, String contentType, String... path) {
return "";
// 本地存储不支持预签名上传URL
return getUrl(path);
}
@Override
public List<StorageFileObject> listDir(String... path) {
return Collections.emptyList();
String fullPath = buildLocalPath(path);
Path dirPath = Paths.get(fullPath);
if (!Files.exists(dirPath) || !Files.isDirectory(dirPath)) {
return new ArrayList<>();
}
List<StorageFileObject> result = new ArrayList<>();
try (Stream<Path> paths = Files.list(dirPath)) {
paths.forEach(filePath -> {
try {
StorageFileObject obj = new StorageFileObject();
obj.setName(filePath.getFileName().toString());
obj.setPath(getRelativePath(filePath.getParent().toString()));
obj.setSize(Files.size(filePath));
obj.setModifyTime(new Date(Files.getLastModifiedTime(filePath).toMillis()));
obj.setRawObject(filePath.toFile());
result.add(obj);
} catch (IOException e) {
// 忽略无法读取的文件
}
});
} catch (IOException e) {
throw new StorageException("列举本地文件失败:" + e.getMessage());
}
return result;
}
@Override
public boolean deleteDir(String... path) {
return false;
String fullPath = buildLocalPath(path);
Path dirPath = Paths.get(fullPath);
if (!Files.exists(dirPath)) {
return true;
}
try {
// 递归删除目录及其内容
try (Stream<Path> paths = Files.walk(dirPath)) {
paths.sorted((a, b) -> b.compareTo(a)) // 从深到浅排序,确保先删除文件再删除目录
.forEach(filePath -> {
try {
Files.deleteIfExists(filePath);
} catch (IOException e) {
// 忽略删除失败的文件
}
});
}
return !Files.exists(dirPath);
} catch (IOException e) {
return false;
}
}
@Override
public boolean setAcl(StorageAcl acl, String... path) {
return false;
// 本地存储不支持ACL设置
return true;
}
@Override
public boolean isExists(String... path) {
return false;
String fullPath = buildLocalPath(path);
return Files.exists(Paths.get(fullPath));
}
private String buildLocalPath(String... paths) {
String relativePath = buildRelativePath(paths);
return Paths.get(basePath, relativePath).toString();
}
private String buildRelativePath(String... paths) {
if (StringUtils.isNotBlank(prefix)) {
return StorageUtil.joinPath(prefix, paths);
} else {
return StorageUtil.joinPath(paths);
}
}
private String getRelativePath(String fullPath) {
String basePathNormalized = Paths.get(basePath).toString();
if (fullPath.startsWith(basePathNormalized)) {
String relative = fullPath.substring(basePathNormalized.length());
if (relative.startsWith(File.separator)) {
relative = relative.substring(1);
}
return StorageUtil.getRelativePath(relative, prefix);
}
return fullPath;
}
}