From 9dc5708d045c564667cdc4d5fda4779a3f1e3e06 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 24 Feb 2025 18:31:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81s3=20storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 12 ++ .../ycwl/basic/storage/StorageFactory.java | 3 + .../basic/storage/adapters/AwsOssAdapter.java | 196 ++++++++++++++++++ .../storage/entity/AwsOssStorageConfig.java | 35 ++++ .../ycwl/basic/storage/enums/StorageType.java | 1 + src/main/resources/application-dev.yml | 10 + src/main/resources/application-prod.yml | 10 + 7 files changed, 267 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/storage/adapters/AwsOssAdapter.java create mode 100644 src/main/java/com/ycwl/basic/storage/entity/AwsOssStorageConfig.java diff --git a/pom.xml b/pom.xml index 1fbb567..2c6411e 100644 --- a/pom.xml +++ b/pom.xml @@ -175,6 +175,18 @@ 3.17.4 + + + com.amazonaws + aws-java-sdk-core + 1.11.24 + + + com.amazonaws + aws-java-sdk-s3 + 1.11.24 + + com.aliyun diff --git a/src/main/java/com/ycwl/basic/storage/StorageFactory.java b/src/main/java/com/ycwl/basic/storage/StorageFactory.java index 34b83c0..5ece8d6 100644 --- a/src/main/java/com/ycwl/basic/storage/StorageFactory.java +++ b/src/main/java/com/ycwl/basic/storage/StorageFactory.java @@ -1,6 +1,7 @@ package com.ycwl.basic.storage; import com.ycwl.basic.storage.adapters.AliOssAdapter; +import com.ycwl.basic.storage.adapters.AwsOssAdapter; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.adapters.LocalStorageAdapter; import com.ycwl.basic.storage.entity.StorageConfig; @@ -23,6 +24,8 @@ public class StorageFactory { switch (storageType) { case LOCAL: return new LocalStorageAdapter(); + case AWS_OSS: + return new AwsOssAdapter(); case ALI_OSS: return new AliOssAdapter(); default: diff --git a/src/main/java/com/ycwl/basic/storage/adapters/AwsOssAdapter.java b/src/main/java/com/ycwl/basic/storage/adapters/AwsOssAdapter.java new file mode 100644 index 0000000..7cbbc3b --- /dev/null +++ b/src/main/java/com/ycwl/basic/storage/adapters/AwsOssAdapter.java @@ -0,0 +1,196 @@ +package com.ycwl.basic.storage.adapters; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.HttpMethod; +import com.amazonaws.Protocol; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.S3ClientOptions; +import com.amazonaws.services.s3.model.*; +import com.ycwl.basic.storage.entity.AwsOssStorageConfig; +import com.ycwl.basic.storage.entity.StorageConfig; +import com.ycwl.basic.storage.entity.StorageFileObject; +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.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class AwsOssAdapter extends AStorageAdapter { + private AwsOssStorageConfig config; + + @Override + public void loadConfig(Map _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; + } + + @Override + public void setConfig(StorageConfig config) { + if (config == null) { + throw new StorageConfigException("配置为空"); + } + if (config instanceof AwsOssStorageConfig) { + this.config = (AwsOssStorageConfig) config; + } else { + throw new StorageConfigException("配置类型错误,传入的类为:" + config.getClass().getName()); + } + } + + @Override + public String uploadFile(InputStream inputStream, String... path) { + if (inputStream == null) { + return null; + } + String fullPath = buildPath(path); + AmazonS3 s3Client = getS3Client(); + try { + ObjectMetadata metadata = new ObjectMetadata(); + s3Client.putObject(new PutObjectRequest(config.getBucketName(), fullPath, inputStream, metadata)); + return getUrl(path); + } catch (Exception e) { + throw new UploadFileFailedException("上传文件失败:" + e.getMessage()); + } + } + + @Override + public boolean deleteFile(String... path) { + AmazonS3 s3Client = getS3Client(); + try { + s3Client.deleteObject(config.getBucketName(), buildPath(path)); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public String getUrl(String... path) { + return config.getUrl() + "/" + buildPath(path); + } + + @Override + public String getUrlForDownload(Date expireDate, String... path) { + AmazonS3 s3Client = getS3Client(); + URL url = s3Client.generatePresignedUrl(config.getBucketName(), buildPath(path), expireDate); + return url.toString(); + } + + @Override + public String getUrlForUpload(Date expireDate, String contentType, String... path) { + AmazonS3 s3Client = getS3Client(); + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(config.getBucketName(), buildPath(path)); + request.setMethod(HttpMethod.PUT); + if (StringUtils.isNotBlank(contentType)) { + request.setContentType(contentType); + } + request.setExpiration(expireDate); + URL url = s3Client.generatePresignedUrl(request); + return url.toString(); + } + + @Override + public List listDir(String... path) { + AmazonS3 s3Client = getS3Client(); + ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request() + .withBucketName(config.getBucketName()) + .withPrefix(buildPath(path) + "/") + .withMaxKeys(1000); + boolean isTruncated = true; + String continuationToken = null; + List objectList = new ArrayList<>(); + try { + while (isTruncated) { + if (continuationToken != null) { + listObjectsV2Request.setContinuationToken(continuationToken); + } + + // 列举文件。 + ListObjectsV2Result result = s3Client.listObjectsV2(listObjectsV2Request); + + objectList.addAll(result.getObjectSummaries()); + + isTruncated = result.isTruncated(); + continuationToken = result.getNextContinuationToken(); + } + return objectList.stream().map(item -> { + StorageFileObject object = new StorageFileObject(); + object.setPath(getRelativePath(item.getKey().substring(0, item.getKey().lastIndexOf("/")))); + object.setName(item.getKey().substring(item.getKey().lastIndexOf("/") + 1)); + object.setSize(item.getSize()); + object.setRawObject(item); + return object; + }).collect(Collectors.toList()); + } catch (Exception e) { + throw new StorageException("列举文件失败:" + e.getMessage()); + } + } + + @Override + public boolean deleteDir(String... path) { + List objectList = listDir(buildPath(path)); + AmazonS3 s3Client = getS3Client(); + if (objectList.isEmpty()) { + return true; + } + int idx = 0; + int batchSize = 999; + while (objectList.size() > idx) { + if (objectList.size() - idx < batchSize) { + batchSize = objectList.size() - idx; + } + List subList = objectList.subList(idx, idx + batchSize); + idx += batchSize; + DeleteObjectsRequest request = new DeleteObjectsRequest(config.getBucketName()) + .withKeys(subList.stream().map(StorageFileObject::getFullPath).toArray(String[]::new)); + try { + s3Client.deleteObjects(request); + } catch (Exception e) { + return false; + } + } + return true; + } + + private AmazonS3Client 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 s3; + } + + private String buildPath(String... paths) { + if (StringUtils.isNotBlank(config.getPrefix())) { + return StorageUtil.joinPath(config.getPrefix(), paths); + } else { + return StorageUtil.joinPath(paths); + } + } + + private String getRelativePath(String path) { + return StorageUtil.getRelativePath(path, config.getPrefix()); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/storage/entity/AwsOssStorageConfig.java b/src/main/java/com/ycwl/basic/storage/entity/AwsOssStorageConfig.java new file mode 100644 index 0000000..643871c --- /dev/null +++ b/src/main/java/com/ycwl/basic/storage/entity/AwsOssStorageConfig.java @@ -0,0 +1,35 @@ +package com.ycwl.basic.storage.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class AwsOssStorageConfig extends StorageConfig { + private String endpoint; + private String accessKeyId; + private String accessKeySecret; + private String bucketName; + private String url; + private String region; + private String prefix; + + @Override + public void checkEverythingOK() { + // TODO: 检查配置是否正确 + } + + public String getUrl() { + String url = this.url; + if (url == null) { + url = bucketName + "." + endpoint; + } + if (!url.startsWith("http")) { + url = "https://" + url; + } + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + return url; + } +} diff --git a/src/main/java/com/ycwl/basic/storage/enums/StorageType.java b/src/main/java/com/ycwl/basic/storage/enums/StorageType.java index e1bfa8a..d88a780 100644 --- a/src/main/java/com/ycwl/basic/storage/enums/StorageType.java +++ b/src/main/java/com/ycwl/basic/storage/enums/StorageType.java @@ -4,6 +4,7 @@ import lombok.Getter; public enum StorageType { LOCAL("LOCAL"), + AWS_OSS("AWS_OSS"), ALI_OSS("ALI_OSS"); @Getter diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 54be5a7..bd43f48 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -157,6 +157,16 @@ storage: prefix: "user-video/" url: "https://oss.zhentuai.com" region: "cn-shanghai" + - name: "chaosheng" + type: "AWS_OSS" + config: + endpoint: "https://obs-cq.cucloud.cn" + accessKeyId: "5E628198FFEC47CEAFC211C341C60F767900" + accessKeySecret: "944346D1940E4AC6B5FCF981C7E589116498" + bucketName: "wsaiphoto" + prefix: "user-video/" + url: "https://wsaiphoto.obs-cq.cucloud.cn" + region: "obs-cq" #阿里云人脸检测 aliFace: accessKeyId: "LTAI5tMwrmxVcUEKoH5QzLHx" diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 3ac802b..5928a25 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -157,6 +157,16 @@ storage: prefix: "user-video/" url: "https://oss.zhentuai.com" region: "cn-shanghai" + - name: "chaosheng" + type: "AWS_OSS" + config: + endpoint: "https://obs-cq.cucloud.cn" + accessKeyId: "5E628198FFEC47CEAFC211C341C60F767900" + accessKeySecret: "944346D1940E4AC6B5FCF981C7E589116498" + bucketName: "wsaiphoto" + prefix: "user-video/" + url: "https://wsaiphoto.obs-cq.cucloud.cn" + region: "obs-cq" #阿里云人脸检测 aliFace: