Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

dengsixing 1 день назад
Родитель
Сommit
6163a54360

+ 3 - 0
4dkankan-utils-filestorage/pom.xml

@@ -17,6 +17,9 @@
         <aws.version>1.12.481</aws.version>
         <minio.version>8.5.6</minio.version>
         <aliyun-sdk-oss.version>3.15.1</aliyun-sdk-oss.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
 
     </properties>
 

+ 87 - 36
4dkankan-utils-filestorage/src/main/java/com/fdkankan/filestorage/StorageAutoConfiguration.java

@@ -11,8 +11,10 @@ import com.amazonaws.services.s3.AmazonS3;
 import com.amazonaws.services.s3.AmazonS3ClientBuilder;
 import com.fdkankan.filestorage.aliyun.AliyunOssTemplate;
 import com.fdkankan.filestorage.aws.AwsTemplate;
+
 import com.fdkankan.filestorage.cos.CosTemplate;
 import com.fdkankan.filestorage.minio.MinioTemplate;
+import com.fdkankan.filestorage.s3.S3Template;
 import com.fdkankan.filestorage.properties.*;
 import com.qcloud.cos.COSClient;
 import com.qcloud.cos.ClientConfig;
@@ -33,61 +35,61 @@ import org.springframework.context.annotation.Configuration;
  * @date 2023/8/28
  */
 @Configuration
-@ConditionalOnProperty(prefix  = "filestorage",name= "active")
+@ConditionalOnProperty(prefix = "filestorage", name = "active")
 @Slf4j
 public class StorageAutoConfiguration {
 
     @Bean
-    @ConditionalOnProperty(prefix  = "filestorage",name= "active")
-    FileStroageProperties fileStroageProperties(){
+    @ConditionalOnProperty(prefix = "filestorage", name = "active")
+    FileStroageProperties fileStroageProperties() {
         return new FileStroageProperties();
     }
 
     @Bean
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "oss")
-    public AliyunOssProperties aliyunOssProperties(FileStroageProperties fileStroageProperties){
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "oss")
+    public AliyunOssProperties aliyunOssProperties(FileStroageProperties fileStroageProperties) {
         AliyunOssProperties aliyunOssProperties = new AliyunOssProperties();
-        aliyunOssProperties .setActive(fileStroageProperties.getActive());
+        aliyunOssProperties.setActive(fileStroageProperties.getActive());
         return aliyunOssProperties;
     }
 
     @Bean(name = "aliyunOssClient")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "oss")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "oss")
     public OSS aliyunOssClient(AliyunOssProperties properties) {
         return new OSSClientBuilder().build(properties.getRequestEndpoint(),
                 properties.getAccessKey(),
                 properties.getAccessKeySecret(),
                 properties.getConfig());
     }
+
     @Bean(name = "aliyunOssTemplate")
     @Qualifier("oss")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "oss")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "oss")
     public FileStorageTemplate aliyunOssTemplate(OSS oss, AliyunOssProperties properties) {
-        return new  AliyunOssTemplate(oss, properties);
+        return new AliyunOssTemplate(oss, properties);
     }
 
-
     @Bean
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "aws")
-    public AwsProperties awsProperties(FileStroageProperties fileStroageProperties){
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "aws")
+    public AwsProperties awsProperties(FileStroageProperties fileStroageProperties) {
         AwsProperties awsProperties = new AwsProperties();
         awsProperties.setActive(fileStroageProperties.getActive());
         return awsProperties;
     }
 
-
     @Bean(name = "amazonS3Client")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "aws")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "aws")
     public AmazonS3 amazonS3Client(AwsProperties properties) {
-        BasicAWSCredentials awsCreds = new BasicAWSCredentials(properties.getAccessKey(), properties.getAccessKeySecret());
+        BasicAWSCredentials awsCreds = new BasicAWSCredentials(properties.getAccessKey(),
+                properties.getAccessKeySecret());
         ClientConfiguration clientConfiguration = new ClientConfiguration();
         clientConfiguration.setMaxErrorRetry(5);
         clientConfiguration.setMaxConnections(300);
         clientConfiguration.setConnectionTimeout(5000);
         clientConfiguration.setSocketTimeout(5000);
         Regions regions = Regions.EU_WEST_2;
-        if (ObjectUtil.isNotEmpty(properties.getRegion())){
-            regions=Regions.fromName(properties.getRegion());
+        if (ObjectUtil.isNotEmpty(properties.getRegion())) {
+            regions = Regions.fromName(properties.getRegion());
         }
         AmazonS3 s3 = AmazonS3ClientBuilder.standard()
                 .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
@@ -97,34 +99,33 @@ public class StorageAutoConfiguration {
         return s3;
     }
 
-
-
     @Bean(name = "AwsOssTemplate")
     @Qualifier("aws")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "aws")
-    public FileStorageTemplate AwsOssTemplate(AmazonS3 oss, AwsProperties properties) {
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "aws")
+    public FileStorageTemplate AwsOssTemplate(@Qualifier("amazonS3Client") AmazonS3 oss, AwsProperties properties) {
         return new AwsTemplate(oss, properties);
     }
 
-
     @Bean
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "cos")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "cos")
     public CosProperties cosProperties(FileStroageProperties fileStroageProperties) {
         CosProperties cosProperties = new CosProperties();
         cosProperties.setActive(fileStroageProperties.getActive());
         return cosProperties;
     }
 
-
     @Bean(name = "cosClient")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "cos")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "cos")
     public COSClient cosClient(CosProperties properties) {
 
         // 1 初始化用户身份信息(secretId, secretKey)。
-        // SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
+        // SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi
+        // 进行查看和管理
         COSCredentials cred = new BasicCOSCredentials(properties.getAccessKey(), properties.getAccessKeySecret());
-        // 2 设置 bucket 的地域, COS 地域的简称请参见 https://cloud.tencent.com/document/product/436/6224
-        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
+        // 2 设置 bucket 的地域, COS 地域的简称请参见
+        // https://cloud.tencent.com/document/product/436/6224
+        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题
+        // Java SDK 部分。
         Region region = new Region(properties.getRegionId());
         ClientConfig clientConfig = properties.getConfig();
         clientConfig.setRegion(region);
@@ -137,25 +138,23 @@ public class StorageAutoConfiguration {
         return cosClient;
     }
 
-
-
     @Bean(name = "cosTemplate")
     @Qualifier("cos")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "cos")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "cos")
     public FileStorageTemplate cosTemplate(COSClient ossClient, CosProperties cosProperties) {
         return new CosTemplate(ossClient, cosProperties);
     }
 
-
     @Bean
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "minio")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "minio")
     public MinioProperties minioProperties(FileStroageProperties fileStroageProperties) {
         MinioProperties minioProperties = new MinioProperties();
         minioProperties.setActive(fileStroageProperties.getActive());
         return minioProperties;
     }
+
     @Bean(name = "minioClient")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "minio")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "minio")
     public MinioClient minioClient(MinioProperties minioProperties) {
         return MinioClient.builder()
                 .endpoint(minioProperties.getEndpoint())
@@ -163,12 +162,64 @@ public class StorageAutoConfiguration {
                 .build();
     }
 
-
     @Bean(name = "minioOssTemplate")
     @Qualifier("minio")
-    @ConditionalOnProperty(name = "filestorage.active",havingValue = "minio")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "minio")
     public FileStorageTemplate minioOssTemplate(MinioClient oss, MinioProperties properties) {
         return new MinioTemplate(oss, properties);
     }
 
+    @Bean
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "s3")
+    public S3Properties s3Properties(FileStroageProperties fileStroageProperties) {
+        S3Properties s3Properties = new S3Properties();
+        s3Properties.setActive(fileStroageProperties.getActive());
+        return s3Properties;
+    }
+
+    @Bean(name = "s3Client")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "s3")
+    public AmazonS3 s3Client(S3Properties properties) {
+        BasicAWSCredentials awsCreds = new BasicAWSCredentials(properties.getAccessKey(),
+                properties.getAccessKeySecret());
+        ClientConfiguration clientConfiguration = new ClientConfiguration();
+        clientConfiguration.setMaxErrorRetry(5);
+        clientConfiguration.setMaxConnections(300);
+        clientConfiguration.setConnectionTimeout(5000);
+        clientConfiguration.setSocketTimeout(5000);
+
+        AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard()
+                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
+                .withClientConfiguration(clientConfiguration);
+
+        // 设置Endpoint和Region
+        if (ObjectUtil.isNotEmpty(properties.getEndpoint())) {
+            // 如果指定了Endpoint,通常也建议开启PathStyleAccess (MinIO/RustFS等兼容S3的服务通常需要)
+            builder.withEndpointConfiguration(new AmazonS3ClientBuilder.EndpointConfiguration(
+                    properties.getEndpoint(),
+                    properties.getRegion() != null ? properties.getRegion() : Regions.DEFAULT_REGION.getName()));
+            // S3兼容服务(如MinIO, RustFS)通常需要开启 path-style access
+            // 默认 flase (Virtual-Hosted-Style), 设置为 true 即使用 Path-Style
+            if (properties.isPathStyleAccessEnabled()) {
+                builder.withPathStyleAccessEnabled(true);
+            }
+        } else {
+            // 如果没有Endpoint,尝试使用Region
+            Regions regions = Regions.DEFAULT_REGION;
+            if (ObjectUtil.isNotEmpty(properties.getRegion())) {
+                regions = Regions.fromName(properties.getRegion());
+            }
+            builder.withRegion(regions);
+        }
+
+        return builder.build();
+    }
+
+    @Bean(name = "s3Template")
+    @Qualifier("s3")
+    @ConditionalOnProperty(name = "filestorage.active", havingValue = "s3")
+    public FileStorageTemplate s3Template(@Qualifier("s3Client") AmazonS3 s3Client, S3Properties properties) {
+        return new S3Template(s3Client, properties);
+    }
+
 }

+ 95 - 0
4dkankan-utils-filestorage/src/main/java/com/fdkankan/filestorage/properties/S3Properties.java

@@ -0,0 +1,95 @@
+package com.fdkankan.filestorage.properties;
+
+import com.fdkankan.filestorage.InnerUtils;
+import lombok.Data;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 通用S3配置属性
+ * filestorage:
+ *   active: s3
+ *   s3:
+ *     endpoint: http://rustfs-endpoint.example.com
+ *     access-key: your-access-key
+ *     access-key-secret: your-secret-key
+ *     bucket: your-bucket-name
+ *     path-style-access-enabled: true # Recommended for RustFS/MinIO
+ */
+@Data
+@ConfigurationProperties(prefix = "filestorage.s3")
+public class S3Properties implements Serializable, InitializingBean {
+
+    public static final String PREFIX = "filestorage.s3";
+    
+    private String active;
+
+    /**
+     * 服务地址 (Endpoint)
+     */
+    private String endpoint;
+    /**
+     * 服务地址(内网)
+     */
+    private String internalEndpoint;
+
+    /**
+     * 访问标识 (Access Key)
+     */
+    private String accessKey;
+
+    /**
+     * 访问密钥 (Secret Key)
+     */
+    private String accessKeySecret;
+
+    /**
+     * 是否开启 PathStyleAccess (MinIO/RustFS 通常需要开启)
+     */
+    private boolean pathStyleAccessEnabled = true;
+
+    /**
+     * 自定义域名, bucket名称忽略大小写
+     */
+    private Map<String, String> bucketCustomDomain;
+
+    /**
+     * Bucket名称
+     */
+    private String bucket;
+
+    /**
+     * Region (部分S3兼容服务需要指定)
+     */
+    private String region;
+
+    /**
+     * return fullPath
+     */
+    private boolean fullPath = true;
+
+    @Override
+    public void afterPropertiesSet() {
+        InnerUtils.checkArgument(!StringUtils.isEmpty(endpoint), "'endpoint' can't be empty");
+        InnerUtils.checkArgument(!StringUtils.isEmpty(bucket), "'bucket' can't be empty");
+        InnerUtils.checkArgument(!StringUtils.isEmpty(accessKey), "'accessKey' can't be empty");
+        InnerUtils.checkArgument(!StringUtils.isEmpty(accessKeySecret), "'accessKeySecret' can't be empty");
+    }
+
+    /**
+     * 获取访问访问前缀, 优先级: customDomain > endpoint
+     */
+    public String getHostByBucket(String bucket) {
+        if (bucketCustomDomain != null) {
+            String domain = bucketCustomDomain.get(bucket);
+            if (domain != null && domain.length() > 0) {
+                return domain;
+            }
+        }
+        return InnerUtils.generateHost(this.endpoint, bucket);
+    }
+}

+ 554 - 0
4dkankan-utils-filestorage/src/main/java/com/fdkankan/filestorage/s3/S3Template.java

@@ -0,0 +1,554 @@
+package com.fdkankan.filestorage.s3;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.amazonaws.AmazonServiceException;
+import com.amazonaws.HttpMethod;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.model.*;
+import com.fdkankan.filestorage.Consumer;
+import com.fdkankan.filestorage.FileStorageTemplate;
+import com.fdkankan.filestorage.InnerUtils;
+import com.fdkankan.filestorage.properties.S3Properties;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Base64Utils;
+import org.springframework.util.ObjectUtils;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * 通用S3操作模版类
+ */
+@Getter
+@Slf4j
+@Service("S3Template")
+@ConditionalOnProperty(prefix = S3Properties.PREFIX, name = "filestorage.active", havingValue = "s3")
+public class S3Template implements FileStorageTemplate {
+
+    private final AmazonS3 amazonS3Client;
+    private final S3Properties s3Properties;
+
+    @Override
+    public String getActive() {
+        return this.s3Properties.getActive();
+    }
+
+    public S3Template(AmazonS3 amazonS3Client, S3Properties s3Properties) {
+        this.amazonS3Client = amazonS3Client;
+        this.s3Properties = s3Properties;
+    }
+
+    @Override
+    public String uploadFile(String pathKey, String filePath) {
+        return uploadFile(s3Properties.getBucket(), pathKey, filePath);
+    }
+
+    @Override
+    public String uploadFile(String pathKey, String filePath, Map<String, String> headers) {
+        return uploadFile(s3Properties.getBucket(), pathKey, filePath, headers);
+    }
+
+    @Override
+    public String uploadFile(String bucket, String pathKey, String filePath) {
+        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
+        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
+        uploadFileStream(bucket, pathKey, FileUtil.getInputStream(new File(filePath)));
+        return calculateUrl(bucket, pathKey);
+    }
+
+    @Override
+    public String uploadFile(String bucket, String pathKey, String filePath, Map<String, String> headers) {
+        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
+        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
+        BufferedInputStream stream = null;
+        try {
+            ObjectMetadata metadata = new ObjectMetadata();
+            if (ObjectUtil.isNotEmpty(headers)) {
+                for (Map.Entry<String, String> header : headers.entrySet()) {
+                    metadata.setHeader(header.getKey(), header.getValue());
+                }
+            }
+            stream = FileUtil.getInputStream(new File(filePath));
+            metadata.setContentLength(stream.available());
+            PutObjectRequest request = new PutObjectRequest(bucket, pathKey, stream, metadata);
+            request.withCannedAcl(CannedAccessControlList.PublicRead);
+            PutObjectResult putObjectResult = amazonS3Client.putObject(request);
+            if (StrUtil.isNotEmpty(putObjectResult.getETag())) {
+                log.info("s3上传文件成功:" + pathKey);
+            }
+
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return calculateUrl(bucket, pathKey);
+    }
+
+    @Override
+    public String uploadFileText(String pathKey, String content) {
+        return uploadFileText(s3Properties.getBucket(), pathKey, content);
+    }
+
+    @Override
+    public String uploadFileText(String bucket, String pathKey, String content) {
+        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
+        return uploadFileBytes(bucket, pathKey, bytes);
+    }
+
+    @Override
+    public String uploadFileBase64Image(String pathKey, String content) {
+        return uploadFileBase64Image(s3Properties.getBucket(), pathKey, content);
+    }
+
+    @Override
+    public String uploadFileBase64Image(String bucket, String pathKey, String content) {
+        byte[] bytes = Base64Utils.decodeFromString(content);
+        return uploadFileBytes(bucket, pathKey, bytes);
+    }
+
+    @Override
+    public String uploadFileBytes(String pathKey, byte[] bytes) {
+        return uploadFileBytes(s3Properties.getBucket(), pathKey, bytes);
+    }
+
+    public void createbucket() {
+        try {
+            if (StrUtil.isEmpty(s3Properties.getBucket())) {
+                throw new IllegalArgumentException("桶名称不能为空!");
+            }
+            try {
+                if (!amazonS3Client.doesBucketExistV2(s3Properties.getBucket())) {
+                    amazonS3Client.createBucket(s3Properties.getBucket());
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public String uploadFileBytes(String bucket, String pathKey, byte[] bytes) {
+        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
+        return uploadFileStream(bucket, pathKey, stream);
+    }
+
+    @Override
+    public String uploadFileStream(String pathKey, InputStream stream) {
+        return uploadFileStream(s3Properties.getBucket(), pathKey, stream);
+    }
+
+    @Override
+    public String uploadFileStream(String bucket, String pathKey, InputStream stream) {
+
+        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
+        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
+        InnerUtils.checkArgument(stream != null, "stream can't be null");
+        try {
+            ObjectMetadata metadata = new ObjectMetadata();
+            if (pathKey.contains(".jpg")) {
+                metadata.setContentType("image/jpeg");
+            } else if (pathKey.contains(".png")) {
+                metadata.setContentType("image/png");
+            } else if (pathKey.contains(".json")) {
+                metadata.setContentType("application/json");
+
+            }
+            metadata.setContentLength(stream.available());
+            PutObjectRequest request = new PutObjectRequest(bucket, pathKey, stream, metadata);
+            request.withCannedAcl(CannedAccessControlList.PublicRead);
+            PutObjectResult putObjectResult = amazonS3Client.putObject(request);
+            if (StrUtil.isNotEmpty(putObjectResult.getETag())) {
+                log.info("s3上传文件成功:" + pathKey);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return calculateUrl(bucket, pathKey);
+    }
+
+    @Override
+    public File downloadFileTmp(String pathKey) {
+        return downloadFileTmp(s3Properties.getBucket(), pathKey);
+    }
+
+    @Override
+    public File downloadFileTmp(String bucket, String pathKey) {
+        String tmpDir = System.getProperty("java.io.tmpdir");
+        String file = tmpDir + UUID.randomUUID();
+        downloadFile(bucket, pathKey, file);
+        return new File(file);
+    }
+
+    @Override
+    public ObjectMetadata downloadFile(String pathKey, String file) {
+        return downloadFile(s3Properties.getBucket(), pathKey, file);
+    }
+
+    @Override
+    public ObjectMetadata downloadFile(String bucket, String pathKey, String file) {
+        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
+        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
+        InnerUtils.checkArgument(file != null && file.length() > 0, "file can't be empty");
+        GetObjectRequest request = new GetObjectRequest(bucket, pathKey);
+        log.info("下载开始:下载bucket={},下载pathKey={},下载filePath={}", bucket, pathKey, file);
+        try {
+            File downloadFile = new File(file);
+            if (!FileUtil.exist(downloadFile.getParent())) {
+                FileUtil.mkdir(downloadFile.getParent());
+            }
+            return amazonS3Client.getObject(request, downloadFile);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    @Override
+    public ObjectMetadata downloadFile(String pathKey, Consumer<InputStream> handler) {
+        return downloadFile(s3Properties.getBucket(), pathKey, handler);
+    }
+
+    @Override
+    public ObjectMetadata downloadFile(String bucket, String pathKey, Consumer<InputStream> handler) {
+
+        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
+        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
+        InnerUtils.checkArgument(handler != null, "handler can't be null");
+        GetObjectRequest request = new GetObjectRequest(bucket, pathKey);
+        S3Object object = amazonS3Client.getObject(request);
+        InputStream content = object.getObjectContent();
+        try {
+            handler.accept(content);
+        } finally {
+            if (content != null) {
+                try {
+                    object.close();
+                    content.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return object.getObjectMetadata();
+    }
+
+    public String calculateUrl(String pathKey) {
+        return calculateUrl(s3Properties.getBucket(), pathKey);
+    }
+
+    public String calculateUrl(String bucket, String pathKey) {
+        String host = s3Properties.getHostByBucket(bucket);
+        return s3Properties.isFullPath() ? host + "/" + pathKey : pathKey;
+    }
+
+    @Override
+    public String getHostByBucket(String bucket) {
+        return s3Properties.getHostByBucket(bucket);
+    }
+
+    public String calculatePathKey(String url) {
+        String urlPath = InnerUtils.getUrlPath(url);
+        if (urlPath != null && urlPath.startsWith("/")) {
+            String pathKey = urlPath.substring(1);
+            if (pathKey.length() > 0) {
+                return InnerUtils.decodeUrl(pathKey);
+            }
+        }
+        return null;
+    }
+
+    public String calculateHost(String endpoint, String bucket) {
+        return InnerUtils.generateHost(endpoint, bucket);
+    }
+
+    @Override
+    public Boolean copyObject(String oldPath, String newPath) {
+        return copyObject(s3Properties.getBucket(), oldPath, s3Properties.getBucket(), newPath);
+    }
+
+    @Override
+    public Boolean copyObject(String bucket, String oldPath, String toBucket, String newPath) {
+        try {
+            List<String> sourceKeyList = this.getFileFolder(oldPath);
+
+            // 复制文件
+            sourceKeyList.parallelStream().forEach(key -> {
+                amazonS3Client.copyObject(bucket, key, toBucket, key.replace(oldPath, newPath));
+            });
+            CopyObjectResult copyObjectResult = amazonS3Client.copyObject(bucket, oldPath, toBucket, newPath);
+
+            if (StrUtil.isNotBlank(copyObjectResult.getVersionId())) {
+                return true;
+            }
+            return false;
+        } catch (Exception e) {
+            log.error("文件不存在,KeyName{}", e.getMessage());
+            return false;
+        }
+    }
+
+    @Override
+    public void deleteObject(String keyName) {
+        if (doesObjectExist(keyName)) {
+            deleteObject(s3Properties.getBucket(), keyName);
+        } else {
+            log.warn("文件不存在,KeyName{}", keyName);
+        }
+    }
+
+    @Override
+    public void deleteObject(String bucket, String keyName) {
+        try {
+            InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
+            InnerUtils.checkArgument(keyName != null && keyName.length() > 0, "keyName can't be empty");
+            amazonS3Client.deleteObject(bucket, keyName);
+        } catch (Exception e) {
+            log.info("文件删除失败" + e.getMessage());
+        }
+    }
+
+    @Override
+    public List<String> getFileFolder(String keyName) {
+        return getFileFolder(s3Properties.getBucket(), keyName);
+    }
+
+    @Override
+    public List<String> getFileFolder(String bucket, String keyName) {
+        List<String> list = new ArrayList<>();
+        try {
+            String nextMaker = null;
+            boolean flag = true;
+            ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
+            listObjectsRequest.setBucketName(bucket);
+            listObjectsRequest.setPrefix(keyName);
+            listObjectsRequest.setMaxKeys(200);
+            do {
+                listObjectsRequest.setMarker(nextMaker);
+                ObjectListing objectListing = amazonS3Client.listObjects(listObjectsRequest);
+                List<S3ObjectSummary> objectSummaries = objectListing.getObjectSummaries();
+                List<String> collect = objectSummaries.stream().map(summary -> {
+                    return summary.getKey();
+                }).collect(Collectors.toList());
+                if (CollUtil.isNotEmpty(collect)) {
+                    list.addAll(collect);
+                }
+                nextMaker = objectListing.getNextMarker();
+                flag = objectListing.isTruncated();
+            } while (flag);
+        } catch (Exception e) {
+            log.error("获取文件夹集合失败={}", e.getMessage());
+        }
+
+        return list;
+    }
+
+    @Override
+    public boolean doesObjectExist(String bucket, String keyName) {
+        try {
+            if ("/".equals(keyName.substring(0, 1))) {
+                keyName = keyName.substring(1);
+            }
+            return amazonS3Client.doesObjectExist(bucket, keyName);
+        } catch (AmazonServiceException e) {
+            if (e.getErrorCode().equalsIgnoreCase("NoSuchKey")) {
+                log.error("NoSuchKey", e.getMessage());
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("连接异常", e.getMessage());
+            return false;
+        }
+        return false;
+    }
+
+    @Override
+    public String getBucket() {
+        return s3Properties.getBucket();
+    }
+
+    @Override
+    public boolean ossDownloadFileToLocal(String bucket, String keyName, String localPath) {
+        try {
+            GetObjectRequest request = new GetObjectRequest(bucket, keyName);
+            amazonS3Client.getObject(request, new File(localPath));
+            return true;
+        } catch (Exception e) {
+            log.error("s3下载文件失败,key=" + amazonS3Client, e);
+        } finally {
+        }
+        return false;
+    }
+
+    @Override
+    public boolean ossDownloadFileToLocal(String path, String localPath) {
+        return ossDownloadFileToLocal(s3Properties.getBucket(), path, localPath);
+    }
+
+    @Override
+    public boolean doesObjectExist(String keyName) {
+        return doesObjectExist(s3Properties.getBucket(), keyName);
+    }
+
+    @Override
+    public String uploadFileImage(String pathKey, String filePath) {
+        return uploadFileImage(s3Properties.getBucket(), pathKey, filePath);
+    }
+
+    @Override
+    public String uploadFileImage(String bucket, String pathKey, String filePath) {
+        InputStream stream = null;
+        try {
+            ObjectMetadata metadata = new ObjectMetadata();
+            if (pathKey.contains(".jpg")) {
+                metadata.setContentType("image/jpeg");
+            } else if (pathKey.contains(".png")) {
+                metadata.setContentType("image/png");
+            } else if (pathKey.contains(".json")) {
+                metadata.setContentType("application/json");
+            }
+            stream = FileUtil.getInputStream(new File(filePath));
+            metadata.setContentLength(stream.available());
+            PutObjectRequest request = new PutObjectRequest(bucket, pathKey, stream, metadata);
+            request.withCannedAcl(CannedAccessControlList.PublicRead);
+            PutObjectResult putObjectResult = amazonS3Client.putObject(request);
+        } catch (Exception e) {
+            return "";
+        } finally {
+            IoUtil.close(stream);
+        }
+        return calculateUrl(bucket, pathKey);
+    }
+
+    @Override
+    public String getFileContent(String bucket, String keyName) throws Exception {
+        try (S3Object object = amazonS3Client.getObject(new GetObjectRequest(bucket, keyName));
+                S3ObjectInputStream inputStream = object.getObjectContent();
+                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+            StringBuilder content = new StringBuilder();
+            while (true) {
+                String line = reader.readLine();
+                if (line == null)
+                    break;
+                content.append(line);
+            }
+            return content.toString();
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+    @Override
+    public String getFileContent(String keyName) throws Exception {
+        return this.getFileContent(this.getBucket(), keyName);
+    }
+
+    @Override
+    public Long getSpace(String bucket, String key) {
+        List<String> keyList = new ArrayList<>();
+        Long total = 0L;
+        boolean flag = true;
+        String nextMaker = null;
+        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
+        listObjectsRequest.setBucketName(bucket);
+        listObjectsRequest.setPrefix(key);
+        listObjectsRequest.setMaxKeys(200);
+
+        do {
+            listObjectsRequest.setMarker(nextMaker);
+            ObjectListing objectListing = amazonS3Client.listObjects(listObjectsRequest);
+            List<S3ObjectSummary> objectSummaries = objectListing.getObjectSummaries();
+            Long space = objectSummaries.stream().mapToLong(S3ObjectSummary::getSize).sum();
+            total += space;
+            nextMaker = objectListing.getNextMarker();
+            flag = objectListing.isTruncated();
+        } while (flag);
+
+        return total;
+    }
+
+    @Override
+    public Long getSpace(String key) {
+        return getSpace(s3Properties.getBucket(), key);
+    }
+
+    @SneakyThrows
+    @Override
+    public String getInternalEndpoint(String bucket, String key) {
+        return s3Properties.getInternalEndpoint()+"/"+bucket+"/"+key;
+
+    }
+
+    @Override
+    @SneakyThrows
+    public String getInternalEndpoint(String key) {
+        return getInternalEndpoint(s3Properties.getBucket(), key);
+    }
+
+    @Override
+    public void copyFolder(String sourcePath, String targetPath) {
+        copyFolder(s3Properties.getBucket(), sourcePath, s3Properties.getBucket(), targetPath);
+    }
+
+    @Override
+    public void copyFolder(String sourceBucketName, String sourcePath, String targetBucketName, String targetPath) {
+        try {
+            List<String> files = this.getFileFolder(sourceBucketName, sourcePath);
+            if (ObjectUtils.isEmpty(files)) {
+                return;
+            }
+            files.stream().forEach(file -> {
+                this.copyObject(sourceBucketName, file, targetBucketName, file.replace(sourcePath, targetPath));
+            });
+        } catch (Exception e) {
+            log.error("列举文件目录失败,key:" + sourcePath, e);
+        }
+    }
+
+    @Override
+    public URL getPresignedUrl(String bucket, String key) {
+        java.util.Date expiration = new java.util.Date();
+        long expTimeMillis = expiration.getTime();
+        expTimeMillis += 1000 * 60 * 60 * 8;
+        expiration.setTime(expTimeMillis);
+        GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucket, key)
+                .withMethod(HttpMethod.PUT).withExpiration(expiration);
+        return amazonS3Client.generatePresignedUrl(generatePresignedUrlRequest);
+    }
+
+    @Override
+    public URL getPresignedUrl(String key) {
+        return getPresignedUrl(s3Properties.getBucket(), key);
+    }
+}

+ 9 - 0
4dkankan-utils-fyun-cos/src/main/java/com/fdkankan/fyun/oss/CosFileService.java

@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSON;
 import com.fdkankan.common.util.DateExtUtil;
 import com.fdkankan.fyun.constant.FYunTypeEnum;
 import com.fdkankan.fyun.face.AbstractFYunFileService;
+import com.fdkankan.fyun.util.FileInfoVo;
 import com.qcloud.cos.COSClient;
 import com.qcloud.cos.ClientConfig;
 import com.qcloud.cos.auth.BasicCOSCredentials;
@@ -533,4 +534,12 @@ public class CosFileService extends AbstractFYunFileService {
         }
     }
 
+    @Override
+    public FileInfoVo getFileInfo(String key) {
+        return this.getFileInfo(fYunFileConfig.getBucket(),key);
+    }
+    @Override
+    public FileInfoVo getFileInfo(String bucket, String key) {
+        return null;
+    }
 }

+ 12 - 0
4dkankan-utils-fyun-local/src/main/java/com/fdkankan/fyun/local/LocalFileService.java

@@ -13,6 +13,9 @@ import java.net.URL;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+
+import com.fdkankan.fyun.util.FileInfoVo;
+import com.fdkankan.fyun.util.MD5Checksum;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -234,4 +237,13 @@ public class LocalFileService extends AbstractFYunFileService {
         }
         return FileUtil.loopFiles(ossPath).stream().mapToLong(File::length).sum();
     }
+
+    @Override
+    public FileInfoVo getFileInfo(String key) {
+        return this.getFileInfo(fYunFileConfig.getBucket(),key);
+    }
+    @Override
+    public FileInfoVo getFileInfo(String bucket, String key) {
+        return MD5Checksum.getFileInfo(getOssPath(bucket, key));
+    }
 }

+ 9 - 0
4dkankan-utils-fyun-obs/src/main/java/com/fdkankan/fyun/oss/ObsFileService.java

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import com.fdkankan.fyun.constant.FYunTypeEnum;
 import com.fdkankan.fyun.face.AbstractFYunFileService;
+import com.fdkankan.fyun.util.FileInfoVo;
 import com.obs.services.ObsClient;
 import com.obs.services.model.*;
 import org.apache.http.HttpHeaders;
@@ -498,4 +499,12 @@ public class ObsFileService extends AbstractFYunFileService {
         return total;
     }
 
+    @Override
+    public FileInfoVo getFileInfo(String key) {
+        return this.getFileInfo(fYunFileConfig.getBucket(),key);
+    }
+    @Override
+    public FileInfoVo getFileInfo(String bucket, String key) {
+        return null;
+    }
 }

+ 37 - 3
4dkankan-utils-fyun-oss/src/main/java/com/fdkankan/fyun/oss/OssFileService.java

@@ -1,15 +1,15 @@
 package com.fdkankan.fyun.oss;
 
 import cn.hutool.core.collection.CollUtil;
-import com.alibaba.fastjson.JSON;
 import com.aliyun.oss.HttpMethod;
 import com.aliyun.oss.OSS;
 import com.aliyun.oss.OSSClientBuilder;
-import com.aliyun.oss.common.comm.ResponseMessage;
 import com.aliyun.oss.model.*;
 import com.fdkankan.common.util.FileMd5Util;
 import com.fdkankan.fyun.constant.FYunTypeEnum;
+import com.fdkankan.fyun.util.FileInfoVo;
 import com.fdkankan.fyun.face.AbstractFYunFileService;
+import com.fdkankan.fyun.util.MD5Checksum;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -17,7 +17,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import java.io.*;
 import java.math.BigDecimal;
@@ -509,4 +508,39 @@ public class OssFileService extends AbstractFYunFileService {
         return total;
     }
 
+    @Override
+    public FileInfoVo getFileInfo(String key) {
+        return this.getFileInfo(fYunFileConfig.getBucket(),key);
+    }
+
+    @Override
+    public FileInfoVo getFileInfo(String bucket, String key) {
+        InputStream inputStream = null;
+        try {
+            GetObjectRequest getObjectMetadataRequest = new GetObjectRequest(bucket, key);
+            Date LastMo = ossClient.getObjectMetadata(getObjectMetadataRequest).getLastModified();
+            String md5 = ossClient.getObjectMetadata(getObjectMetadataRequest).getETag();
+            Long size = ossClient.getObjectMetadata(getObjectMetadataRequest).getContentLength();
+
+            inputStream = ossClient.getObject(bucket, key).getObjectContent();
+            String sha1 = MD5Checksum.getSHA1(inputStream);
+            return new FileInfoVo(md5,sha1.toUpperCase(),LastMo.getTime(),size);
+        }catch (Exception e){
+            log.info("oss-getFileInfo-error:{}",key,e);
+        }finally {
+            closeQuietly(inputStream);
+        }
+        return null;
+    }
+
+
+    private void closeQuietly(InputStream stream) {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                log.warn("关闭oss对象流时发生异常", e);
+            }
+        }
+    }
 }

+ 3 - 0
4dkankan-utils-fyun-parent/src/main/java/com/fdkankan/fyun/face/FYunFileServiceInterface.java

@@ -1,5 +1,6 @@
 package com.fdkankan.fyun.face;
 
+import com.fdkankan.fyun.util.FileInfoVo;
 import org.springframework.stereotype.Component;
 
 import java.io.IOException;
@@ -373,4 +374,6 @@ public interface FYunFileServiceInterface {
     public void restoreFile(String bucket, String objectName, Integer priority);
 
     public Long getSpace(String bucket, String key);
+    public FileInfoVo getFileInfo(String bucket, String key);
+    public FileInfoVo getFileInfo( String key);
 }

+ 28 - 0
4dkankan-utils-fyun-parent/src/main/java/com/fdkankan/fyun/util/FileInfoVo.java

@@ -0,0 +1,28 @@
+package com.fdkankan.fyun.util;
+
+
+import cn.hutool.core.date.DateUtil;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@AllArgsConstructor
+public class FileInfoVo {
+
+    private String MD5;
+    private String SHA1;
+    private Long lastModified;
+    private Long size;
+
+    @Override
+    public String toString() {
+        return
+                "MD5:" + MD5 + '\n' +
+                "SHA1:" + SHA1 + '\n' +
+                "修改时间:" + DateUtil.format(new Date(lastModified),"yyyy-MM-dd HH:mm:ss") +'\n' +
+                "大小:" + size +"字节"
+                ;
+    }
+}

+ 136 - 0
4dkankan-utils-fyun-parent/src/main/java/com/fdkankan/fyun/util/MD5Checksum.java

@@ -0,0 +1,136 @@
+package com.fdkankan.fyun.util;
+
+import cn.hutool.core.io.FileUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.MessageDigest;
+
+public class MD5Checksum {
+    public static byte[] createChecksum(String filePath,String sign)  {
+        try {
+            InputStream fis = new FileInputStream(filePath);
+            byte[] buffer = new byte[1024];
+            MessageDigest complete = MessageDigest.getInstance(sign);
+            int numRead;
+
+            do {
+                numRead = fis.read(buffer);
+                if (numRead > 0) {
+                    complete.update(buffer, 0, numRead);
+                }
+            } while (numRead != -1);
+
+            fis.close();
+            return complete.digest();
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    public static byte[] createChecksum(InputStream fis,String sign)  {
+        try {
+            byte[] buffer = new byte[1024];
+            MessageDigest complete = MessageDigest.getInstance(sign);
+            int numRead;
+
+            do {
+                numRead = fis.read(buffer);
+                if (numRead > 0) {
+                    complete.update(buffer, 0, numRead);
+                }
+            } while (numRead != -1);
+
+            return complete.digest();
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    public static String getMD5(String filePath)  {
+        byte[] b = createChecksum(filePath,"MD5");
+        if(b == null){
+            return null;
+        }
+        StringBuilder result = new StringBuilder();
+
+        for (byte element : b) {
+            result.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1));
+        }
+
+        return result.toString();
+    }
+
+    public static String getMD5(InputStream filePath)  {
+        byte[] b = createChecksum(filePath,"MD5");
+        if(b == null){
+            return null;
+        }
+        StringBuilder result = new StringBuilder();
+
+        for (byte element : b) {
+            result.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1));
+        }
+
+        return result.toString();
+    }
+
+    public static String getSHA1(String filePath)  {
+        byte[] b = createChecksum(filePath,"SHA1");
+        StringBuilder result = new StringBuilder();
+        if(b == null){
+            return null;
+        }
+        for (byte element : b) {
+            result.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1));
+        }
+
+        return result.toString();
+    }
+    public static String getSHA1(InputStream filePath)  {
+        byte[] b = createChecksum(filePath,"SHA1");
+        StringBuilder result = new StringBuilder();
+        if(b == null){
+            return null;
+        }
+        for (byte element : b) {
+            result.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1));
+        }
+
+        return result.toString();
+    }
+
+    public static Long getLastModifiedTime(String filePath)  {
+        File file = new File(filePath);
+        return file.lastModified();
+    }
+    public static Long getSize(String filePath)  {
+        File file = new File(filePath);
+        return file.length();
+    }
+
+
+    public static FileInfoVo getFileInfo(String filePath){
+        try {
+            return new FileInfoVo(
+                    getMD5(filePath).toUpperCase(),
+                    getSHA1(filePath).toUpperCase(),
+                    getLastModifiedTime(filePath),
+                    getSize(filePath));
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static void main(String[] args) {
+        FileInfoVo fileInfo = getFileInfo("D:\\abc\\1.txt");
+        FileUtil.writeString(fileInfo.toString(),new File("D:\\abc\\hash.txt"),"UTF-8");
+        System.out.println();
+    }
+}

+ 64 - 0
4dkankan-utils-fyun-s3/src/main/java/com/fdkankan/fyun/s3/S3FileService.java

@@ -12,6 +12,8 @@ import com.amazonaws.services.s3.AmazonS3ClientBuilder;
 import com.amazonaws.services.s3.model.*;
 import com.fdkankan.fyun.constant.FYunTypeEnum;
 import com.fdkankan.fyun.face.AbstractFYunFileService;
+import com.fdkankan.fyun.util.FileInfoVo;
+import com.fdkankan.fyun.util.MD5Checksum;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -545,4 +547,66 @@ public class S3FileService extends AbstractFYunFileService {
             amazonS3.copyObject(request);
         });
     }
+
+    @Override
+    public FileInfoVo getFileInfo(String key) {
+        return this.getFileInfo(fYunFileConfig.getBucket(),key);
+    }
+
+    @Override
+    public FileInfoVo getFileInfo(String bucket, String key) {
+        S3Object s3Object = null;
+        S3ObjectInputStream objectInputStream = null;
+
+        try {
+            // 1. 获取文件元数据
+            GetObjectMetadataRequest metadataRequest = new GetObjectMetadataRequest(bucket, key);
+            ObjectMetadata metadata = s3.getObjectMetadata(metadataRequest);
+            Date lastModified = metadata.getLastModified();
+            String eTag = metadata.getETag();
+            Long size = metadata.getContentLength();
+            String md5 = eTag != null ? eTag.replace("\"", "") : "";
+
+            GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, key);
+            s3Object = s3.getObject(getObjectRequest);
+            objectInputStream = s3Object.getObjectContent();
+
+            String sha1 = MD5Checksum.getSHA1(objectInputStream);
+
+            return new FileInfoVo(
+                    md5,
+                    sha1.toUpperCase(),
+                    lastModified.getTime(),
+                    size
+            );
+
+        } catch (Exception e) {
+            log.error("处理S3文件信息异常 - bucket:{}, key:{}", bucket, key, e);
+
+        } finally {
+            closeQuietly(objectInputStream);
+            closeQuietly(s3Object);
+        }
+        return null;
+    }
+    private void closeQuietly(S3ObjectInputStream stream) {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                log.warn("关闭S3对象流时发生异常", e);
+            }
+        }
+    }
+
+    private void closeQuietly(S3Object s3Object) {
+        if (s3Object != null) {
+            try {
+                s3Object.close();
+            } catch (IOException e) {
+                log.warn("关闭S3对象时发生异常", e);
+            }
+        }
+    }
+
 }