AWS-S3通用存储操作,操作minio、oss、cos等所有兼容s3协议的云存储(含有大文件分片上传实现)

news2024/12/28 18:35:18

一、介绍

通用存储操作common包,支持所有兼容amazon-s3协议的云存储,如minio、oss、cos等,以后客户用啥云储存一套代码都能搞定了,真棒~

二、代码结构

在这里插入图片描述

三、代码实现

3.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://maven.apache.org/POM/4.0.0"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <artifactId>say-common-oss</artifactId>

  <dependencies>
    <dependency>
      <artifactId>aws-java-sdk-s3</artifactId>
      <groupId>com.amazonaws</groupId>
      <version>${aws.version}</version>
    </dependency>
    <!--大文件分片上传,引入MultipartFile使用,没有大文件分片上传需求,可以删除依赖、删除对应大文件分片上传代码-->
    <dependency>
      <artifactId>spring-web</artifactId>
      <groupId>org.springframework</groupId>
    </dependency>
    <dependency>
      <artifactId>lombok</artifactId>
      <groupId>org.projectlombok</groupId>
      <version>${lombok.version}</version>
    </dependency>
    <dependency>
      <artifactId>spring-boot</artifactId>
      <groupId>org.springframework.boot</groupId>
    </dependency>
    <dependency>
      <artifactId>hutool-all</artifactId>
      <groupId>cn.hutool</groupId>
      <version>${hutool.version}</version>
    </dependency>
    <dependency>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <groupId>org.springframework.boot</groupId>
    </dependency>
    <!--排除springboot-slf4j日志,使用lombok的日志-->
    <dependency>
      <artifactId>spring-boot-starter-logging</artifactId>
      <exclusions>
        <exclusion>
          <artifactId>log4j-to-slf4j</artifactId>
          <groupId>org.apache.logging.log4j</groupId>
        </exclusion>
        <exclusion>
          <artifactId>jul-to-slf4j</artifactId>
          <groupId>org.slf4j</groupId>
        </exclusion>
      </exclusions>
      <groupId>org.springframework.boot</groupId>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <artifactId>spring-boot-dependencies</artifactId>
        <groupId>org.springframework.boot</groupId>
        <scope>import</scope>
        <type>pom</type>
        <version>${spring-boot.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <description>aws-s3通用存储操作</description>
  <groupId>com.say.common</groupId>
  <modelVersion>4.0.0</modelVersion>

  <packaging>jar</packaging>

  <properties>
    <aws.version>1.12.470</aws.version>
    <hutool.version>5.8.0</hutool.version>

    <lombok.version>1.18.24</lombok.version>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <spring-boot.version>2.7.7</spring-boot.version>
  </properties>

  <version>1.0.0</version>
</project>

3.2 FileProperties

文件 配置信息 bucket 设置公共读权限

package com.say.common.oss.conf;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 文件 配置信息 <p> bucket 设置公共读权限
 *
 * @author zrs
 */
@Data
@ConfigurationProperties(prefix = "file")
public class FileProperties {

  /**
   * 默认的存储桶名称
   */
  private String bucketName = "test";

  /**
   * oss 文件配置信息
   */
  private OssProperties oss;

}

3.3 OssProperties

aws 配置信息

package com.say.common.oss.conf;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * aws 配置信息
 *
 * @author zrs
 */
@Data
@Component
@ConfigurationProperties(prefix = "oss")
public class OssProperties {

  /**
   * 对象存储服务的URL
   */
  private String endpoint;

  /**
   * 自定义域名
   */
  private String customDomain;

  /**
   * true path-style nginx 反向代理和S3默认支持 pathStyle {http://endpoint/bucketname} false
   * <p/>
   * supports virtual-hosted-style 阿里云等需要配置为 virtual-hosted-style
   * <p/>
   * 模式{http://bucketname.endpoint}
   */
  private Boolean pathStyleAccess = true;

  /**
   * 应用ID
   */
  private String appId;

  /**
   * 区域
   */
  private String region;

  /**
   * Access key就像用户ID,可以唯一标识你的账户
   */
  private String accessKey;

  /**
   * Secret key是你账户的密码
   */
  private String secretKey;

  /**
   * 最大线程数,默认: 100
   */
  private Integer maxConnections = 100;

}

3.4 FileTemplate

文件操作模板

package com.say.common.oss.core;

import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.services.s3.model.S3VersionSummary;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.multipart.MultipartFile;

/**
 * 文件操作模板
 *
 * @author zrs
 */
public interface FileTemplate extends InitializingBean {

  @Override
  default void afterPropertiesSet() {
  }

  /**
   * 创建bucket
   *
   * @param bucketName bucket名称
   */
  void createBucket(String bucketName);

  /**
   * 获取全部bucket
   *
   * @return Bucket 列表
   */
  List<Bucket> getAllBuckets();

  /**
   * 根据bucket获取bucket详情
   *
   * @param bucketName bucket名称
   * @return Optional<Bucket>
   */
  Optional<Bucket> getBucket(String bucketName);

  /**
   * @param bucketName bucket名称
   */
  void removeBucket(String bucketName);

  /**
   * 上传文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param stream 文件流
   * @param contextType 文件类型
   */
  void putObject(String bucketName, String objectName, InputStream stream, String contextType);

  /**
   * 上传文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param stream 文件流
   * @param size 大小
   * @param contextType 类型
   */
  PutObjectResult putObject(String bucketName, String objectName, InputStream stream,
      long size, String contextType);

  /**
   * 获取文件信息
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   */
  S3Object getObjectInfo(String bucketName, String objectName);

  /**
   * 上传文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param stream 文件流
   */
  void putObject(String bucketName, String objectName, InputStream stream);

  /**
   * 获取文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @return 二进制流
   */
  S3Object getObject(String bucketName, String objectName);

  /**
   * 删除文件
   *
   * @param bucketName bucketName
   * @param objectName objectName
   */
  void deleteObject(String bucketName, String objectName);

  /**
   * 大文件分段上传
   *
   * @param file MultipartFile
   * @param bucketName bucketName
   * @param objectName objectName
   * @param minPartSize 每片大小,单位:字节(eg:5242880 <- 5m)
   */
  void uploadMultipartFileByPart(MultipartFile file, String bucketName, String objectName,
      int minPartSize);

  /**
   * 根据文件前置查询文件
   *
   * @param bucketName bucket名称
   * @param prefix 前缀
   * @param recursive 是否递归查询
   * @return S3ObjectSummary 列表
   */
  List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive);

  /**
   * 查询文件版本
   *
   * @param bucketName bucket名称
   * @return S3ObjectSummary 列表
   */
  List<S3VersionSummary> getAllObjectsVersionsByPrefixV2(String bucketName, String objectName);

  /**
   * 获取文件外链
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param expires 过期时间 <=7
   * @return url
   */
  String generatePresignedUrl(String bucketName, String objectName, Integer expires);
}

3.5 OssTemplate

aws-s3 通用存储操作 支持所有兼容s3协议的云存储

package com.say.common.oss.service;

import cn.hutool.core.util.ObjectUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.AbortMultipartUploadRequest;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
import com.amazonaws.services.s3.model.CompleteMultipartUploadResult;
import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest;
import com.amazonaws.services.s3.model.InitiateMultipartUploadResult;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PartETag;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.services.s3.model.S3VersionSummary;
import com.amazonaws.services.s3.model.UploadPartRequest;
import com.amazonaws.services.s3.model.UploadPartResult;
import com.amazonaws.services.s3.model.VersionListing;
import com.amazonaws.util.IOUtils;
import com.say.common.oss.conf.FileProperties;
import com.say.common.oss.core.FileTemplate;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.multipart.MultipartFile;

/**
 * aws-s3 通用存储操作 支持所有兼容s3协议的云存储
 *
 * @author zrs
 */
@Slf4j
@RequiredArgsConstructor
public class OssTemplate implements InitializingBean, FileTemplate {

  private final FileProperties properties;

  private AmazonS3 amazonS3;

  @Override
  public void afterPropertiesSet() {
    ClientConfiguration clientConfiguration = new ClientConfiguration();
    clientConfiguration.setMaxConnections(properties.getOss().getMaxConnections());

    AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
        properties.getOss().getEndpoint(), properties.getOss().getRegion());
    AWSCredentials awsCredentials = new BasicAWSCredentials(properties.getOss().getAccessKey(),
        properties.getOss().getSecretKey());
    AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(
        awsCredentials);
    this.amazonS3 = AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
        .withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
        .disableChunkedEncoding()
        .withPathStyleAccessEnabled(properties.getOss().getPathStyleAccess()).build();
  }

  /**
   * 创建bucket
   *
   * @param bucketName bucket名称
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/CreateBucket">AWS API
   * Documentation</a>
   */
  @Override
  @SneakyThrows
  public void createBucket(String bucketName) {
    // 检验bucket是否存在
    if (!amazonS3.doesBucketExistV2(bucketName)) {
      amazonS3.createBucket((bucketName));
    }
  }

  /**
   * 获取全部bucket
   * <p>
   *
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public List<Bucket> getAllBuckets() {
    return amazonS3.listBuckets();
  }

  /**
   * 根据bucket获取bucket详情
   *
   * @param bucketName bucket名称
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public Optional<Bucket> getBucket(String bucketName) {
    return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();
  }

  /**
   * @param bucketName bucket名称
   * @see <a href=
   * "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteBucket">AWS API
   * Documentation</a>
   */
  @Override
  @SneakyThrows
  public void removeBucket(String bucketName) {
    amazonS3.deleteBucket(bucketName);
  }

  /**
   * 上传文件,指定文件类型
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param stream 文件流
   * @param contextType 文件类型
   * @throws Exception
   */
  @Override
  @SneakyThrows
  public void putObject(String bucketName, String objectName, InputStream stream,
      String contextType) {
    putObject(bucketName, objectName, stream, stream.available(), contextType);
  }

  /**
   * 上传文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param stream 文件流
   * @param size 大小
   * @param contextType 类型
   * @throws Exception
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/PutObject">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public PutObjectResult putObject(String bucketName, String objectName, InputStream stream,
      long size, String contextType) {
    byte[] bytes = IOUtils.toByteArray(stream);
    ObjectMetadata objectMetadata = new ObjectMetadata();
    objectMetadata.setContentLength(size);
    objectMetadata.setContentType(contextType);
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
    // 上传
    return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
  }

  /**
   * 获取文件信息
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public S3Object getObjectInfo(String bucketName, String objectName) {
    @Cleanup
    S3Object object = amazonS3.getObject(bucketName, objectName);
    return object;
  }

  /**
   * 上传文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param stream 文件流
   * @throws Exception
   */
  @Override
  @SneakyThrows
  public void putObject(String bucketName, String objectName, InputStream stream) {
    putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");
  }

  /**
   * 获取文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @return 二进制流
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public S3Object getObject(String bucketName, String objectName) {
    return amazonS3.getObject(bucketName, objectName);
  }

  /**
   * 删除文件
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @throws Exception
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteObject">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public void deleteObject(String bucketName, String objectName) {
    amazonS3.deleteObject(bucketName, objectName);
  }

  /**
   * 大文件分段上传
   *
   * @param file MultipartFile
   * @param bucketName bucketName
   * @param objectName objectName
   * @param minPartSize 每片大小,单位:字节(eg:5242880 <- 5m)
   */
  @Override
  public void uploadMultipartFileByPart(MultipartFile file, String bucketName, String objectName,
      int minPartSize) {
    if (ObjectUtil.isEmpty(file)) {
      log.error("file is empty");
    }
    // 计算分片大小
    long size = file.getSize();
    // 得到总共的段数,和 分段后,每个段的开始上传的字节位置
    List<Long> positions = Collections.synchronizedList(new ArrayList<>());
    long filePosition = 0;
    while (filePosition < size) {
      positions.add(filePosition);
      filePosition += Math.min(minPartSize, (size - filePosition));
    }
    if (log.isDebugEnabled()) {
      log.debug("总大小:{},分为{}段", size, positions.size());
    }

    // 创建一个列表保存所有分传的 PartETag, 在分段完成后会用到
    List<PartETag> partETags = Collections.synchronizedList(new ArrayList<>());

    // 第一步,初始化,声明下面将有一个 Multipart Upload
    // 设置文件类型
    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentType(file.getContentType());

    InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName,
        objectName, metadata);
    InitiateMultipartUploadResult initResponse = this.initiateMultipartUpload(initRequest);
    if (log.isDebugEnabled()) {
      log.debug("开始上传");
    }
    //声明线程池
    ExecutorService exec = Executors.newFixedThreadPool(3);
    long begin = System.currentTimeMillis();
    try {
      // MultipartFile 转 File
      File toFile = multipartFileToFile(file);
      for (int i = 0; i < positions.size(); i++) {
        int finalI = i;
        exec.execute(() -> {
          long time1 = System.currentTimeMillis();
          UploadPartRequest uploadRequest = new UploadPartRequest()
              .withBucketName(bucketName)
              .withKey(objectName)
              .withUploadId(initResponse.getUploadId())
              .withPartNumber(finalI + 1)
              .withFileOffset(positions.get(finalI))
              .withFile(toFile)
              .withPartSize(Math.min(minPartSize, (size - positions.get(finalI))));
          // 第二步,上传分段,并把当前段的 PartETag 放到列表中
          partETags.add(this.uploadPart(uploadRequest).getPartETag());
          if (log.isDebugEnabled()) {
            log.debug("第{}段上传耗时:{}", finalI + 1, (System.currentTimeMillis() - time1));
          }
        });
      }
      //任务结束关闭线程池
      exec.shutdown();
      //判断线程池是否结束,不加会直接结束方法
      while (true) {
        if (exec.isTerminated()) {
          break;
        }
      }

      // 第三步,完成上传,合并分段
      CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(
          bucketName,
          objectName,
          initResponse.getUploadId(), partETags);
      this.completeMultipartUpload(compRequest);

      //删除本地缓存文件
      if (toFile != null && !toFile.delete()) {
        log.error("Failed to delete cache file");
      }
    } catch (Exception e) {
      this.abortMultipartUpload(
          new AbortMultipartUploadRequest(bucketName, objectName,
              initResponse.getUploadId()));
      log.error("Failed to upload, " + e.getMessage());
    }
    if (log.isDebugEnabled()) {
      log.debug("总上传耗时:{}", (System.currentTimeMillis() - begin));
    }
  }

  /**
   * 根据文件前置查询文件集合
   *
   * @param bucketName bucket名称
   * @param prefix 前缀
   * @param recursive 是否递归查询
   * @return S3ObjectSummary 列表
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix,
      boolean recursive) {
    ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
    return new ArrayList<>(objectListing.getObjectSummaries());
  }

  /**
   * 查询文件版本
   *
   * @param bucketName bucket名称
   * @return S3ObjectSummary 列表
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
   * API Documentation</a>
   */
  @Override
  @SneakyThrows
  public List<S3VersionSummary> getAllObjectsVersionsByPrefixV2(String bucketName,
      String objectName) {
    VersionListing versionListing = amazonS3.listVersions(bucketName, objectName);
    return new ArrayList<>(versionListing.getVersionSummaries());
  }

  /**
   * 获取文件外链
   *
   * @param bucketName bucket名称
   * @param objectName 文件名称
   * @param expires 过期时间 <=7
   * @return url
   */
  @Override
  @SneakyThrows
  public String generatePresignedUrl(String bucketName, String objectName, Integer expires) {
    Date date = new Date();
    Calendar calendar = new GregorianCalendar();
    calendar.setTime(date);
    calendar.add(Calendar.DAY_OF_MONTH, expires);
    URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
    return url.toString();
  }

  /**
   * 初始化,声明有一个Multipart Upload
   *
   * @param initRequest 初始化请求
   * @return 初始化返回
   */
  private InitiateMultipartUploadResult initiateMultipartUpload(
      InitiateMultipartUploadRequest initRequest) {
    return amazonS3.initiateMultipartUpload(initRequest);
  }

  /**
   * 上传分段
   *
   * @param uploadRequest 上传请求
   * @return 上传分段返回
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/UploadPart">AWS
   * API Documentation</a>
   */
  private UploadPartResult uploadPart(UploadPartRequest uploadRequest) {
    return amazonS3.uploadPart(uploadRequest);
  }

  /**
   * 分段合并
   *
   * @param compRequest 合并请求
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/CompleteMultipartUpload">AWS
   * API Documentation</a>
   */
  private CompleteMultipartUploadResult completeMultipartUpload(
      CompleteMultipartUploadRequest compRequest) {
    return amazonS3.completeMultipartUpload(compRequest);
  }

  /**
   * 中止分片上传
   *
   * @param uploadRequest 中止文件上传请求
   * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/AbortMultipartUpload">AWS
   * API Documentation</a>
   */
  private void abortMultipartUpload(AbortMultipartUploadRequest uploadRequest) {
    amazonS3.abortMultipartUpload(uploadRequest);
  }

  /**
   * MultipartFile 转 File
   */
  private File multipartFileToFile(MultipartFile file) throws Exception {
    File toFile = null;
    if (file.equals("") || file.getSize() <= 0) {
      file = null;
    } else {
      InputStream ins = null;
      ins = file.getInputStream();
      toFile = new File(file.getOriginalFilename());
      //获取流文件
      OutputStream os = new FileOutputStream(toFile);
      int bytesRead = 0;
      byte[] buffer = new byte[8192];
      while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
        os.write(buffer, 0, bytesRead);
      }
      os.close();
      ins.close();
    }
    return toFile;
  }

}

3.6 OssAutoConfiguration

aws 自动配置类

package com.say.common.oss;


import com.say.common.oss.conf.FileProperties;
import com.say.common.oss.service.OssTemplate;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

/**
 * aws 自动配置类
 *
 * @author zrs
 */
@AllArgsConstructor
@EnableConfigurationProperties({FileProperties.class})
public class OssAutoConfiguration {

  private final FileProperties properties;

  @Bean
  @Primary
  @ConditionalOnMissingBean(OssTemplate.class)
  @ConditionalOnProperty(name = "file.oss.enable", havingValue = "true")
  public OssTemplate ossTemplate() {
    return new OssTemplate(properties);
  }


}

四、已发布到Gitee,可以直接下载使用

点击进入Gitee,获取代码

五、想了解Minio工具类,MinIO-SDK的方式实现MinIO对象存储操作?

Minio工具类 - Java

手写不易,有用请点赞!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/568095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

平安银行广州分行立足地域文化,增强差异化权益服务软实力

立足地域文化&#xff0c;拓展差异化权益服务 瓦屋纸窗之下&#xff0c;一盏清茶&#xff0c;三五好友&#xff0c;怡然自若。中国人对茶的喜爱由来已久&#xff0c;茶文化已成为中华传统文化中一张亮丽的名片&#xff0c;而广东茶文化则是中国四大茶文化系列之一。平安银行广州…

链式哈希,一致性哈希,倒排表

在普通的查询中&#xff0c;通过关键码的比较进行查找&#xff0c;而哈希是根据关键码直接定位到数据项 哈希冲突&#xff1a;同一个关键码经过哈希函数后指向同一个记录集 链式哈希 using namespace std; #define M 13 typedef int KeyType; //typedef struct //{ // KeyTyp…

开放式耳机和封闭式耳机的区别有哪些?开放式耳机有哪些推荐?

开放式耳机和封闭式耳机的区别主要在以下几个方面&#xff1a; 设计结构&#xff1a;开放式耳机通常有一个开放的设计&#xff0c;不需要塞入耳即可收听音乐&#xff0c;同时与外部环境进行交互。封闭式耳机则是封闭的设计&#xff0c;耳机驱动单元之间是封闭和隔离的&#xf…

电子科技大学编译原理复习笔记(一):绪论

目录 前言 重点一览 语言的分类 冯诺依曼体系结构 绑定的概念 变量 虚拟机 程序单元 本章小结 前言 本复习笔记基于张老师的课堂PPT&#xff0c;供自己期末复习与学弟学妹参考用。 重点一览 语言的分类 命令式语言&#xff08;强制式语言&#xff09;&#xff1a;冯…

Activity的预览窗口StartingWindow添加

Activity的预览窗口StartingWindow添加 1、Activity组件启动2、ActivityStarter.java#startActivityInner() > 主要查看Task.java#startActivityLocked3、ActivityRecord.java#addStartingWindow到WindowManagerService.java#addWindow3.1 ActivityRecord.java#addStartingW…

一文搞定验证码(下部分)

文章目录 1.背景2.验证3.valid接口具体实现类SimpleImageCaptchaValidator 1.背景 上一篇文章讲了验证码生成的逻辑. 验证码-上篇. 大概来说就是: 服务端保存一些默认的验证码图片. 然后需要生成时创建一个包含随机字符的验证码字符图片根据随机字符和一些参数&#xff08;如…

新入职一个00后卷王,每天加班到2点,太让人崩溃了····

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&…

chatgpt赋能python:PythonScheme:为什么你应该考虑在你的下一个项目中使用它

Python Scheme&#xff1a;为什么你应该考虑在你的下一个项目中使用它 介绍 Python是一种常用的编程语言&#xff0c;被广泛应用于各种应用程序&#xff0c;包括Web开发、数据分析和人工智能。今天&#xff0c;我们要讨论的是Python编程语言的一种方言&#xff0c;名为“Pyth…

HR不会告诉你!Java程序员月薪8K和20K的区别!

昨天有同学问好程序员&#xff0c;为啥都是干Java程序员&#xff0c;别人可以拿20k&#xff0c;我才拿8k呢&#xff1f;为啥人家能提前转正我就得晚俩月&#xff1f;小源一听大事不妙&#xff0c;赶紧连夜整理了以下清单供大家check&#xff01; 对于刚入职场还有跳槽成功的同学…

【2023 · CANN训练营第一季】昇腾AI入门课(TensorFlow)微认证考试

1、下面哪个AI框架开发模型可以不用适配&#xff0c;直接在昇腾AI处理器上进行训练&#xff1f; A. PyTorch B. Caffe C. Mindspore D. Tensorflow C 2、使用Estimator进行脚本训练开发的一般步骤为&#xff08;A&#xff09; A. 数据预处理 --> 模型构建 --> 运行配置 -…

苏诗:医疗器械企业增长秘籍之CRM系统的 4 大能力建设

近些年&#xff0c;在国家医疗产业政策支持的推动下&#xff0c;医疗器械产业已进入蓬勃发展的“黄金时期”&#xff0c;医疗器械产品丰富度增加&#xff0c;配套服务体系逐渐完善&#xff0c;国产品牌效应进一步凸显。 医疗健康行业是纷享的战略行业之一&#xff0c;自2017年…

chatgpt如何自动生成角色prompt模板

chatgpt如何自动生成角色prompt模板 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、角色prompt模板 下面是套图的chatgpt模板&#xff0c;你可以手动的进行填写。将角色的描述填…

如何把数据从 TDengine 2.x 迁移到 3.x ?

一.迁移背景&#xff1a; 随着时序数据库&#xff08;Time Series Database&#xff09; TDengine 3.0 的发布至今&#xff0c;我们除了在持续地优化产品质量的本身&#xff0c;也一直在努力地提升用户体验。但由于 3.0 底层有大量的重构优化&#xff0c;导致开源版的 2.0 用户…

集权攻击-无权限条件下AD域凭据获取与利用分析

前言 对AD域攻击的前期&#xff0c;在没有任何域内据点或域用户凭据时&#xff0c;攻击者往往会使用用户名枚举、密码爆破、密码喷洒、Roasting等手段进行域用户凭据的窃取&#xff0c;本篇文章将针对AD域攻击时无权限环境下对域用户的信息收集及凭据窃取进行分析。 用户名枚举…

四、CNNs网络架构-深度可分离卷积

《A review of convolutional neural network architectures and their optimizations》论文指出一些高性能的卷积神经网络方法不可避免地带来巨大的计算成本&#xff0c;往往需要高性能GPU或高度优化的分布式CPU架构的支持。尽管CNNs应用向移动终端扩展&#xff0c;但大多数移…

如何在Spring Boot服务端实现公网远程调试并进行HTTP服务监听

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…

Go语言 -- Web开发基础学习 net/http包

前言 Go 是一个开源的编程语言&#xff0c;它能让构造简单、可靠且高效的软件变得容易。 Go语言最擅长的领域就是Web开发&#xff0c;此贴是本人入门完go语法基础后学习Web开发的学习笔记。 第一个Go Web 新建go文件hello_world.go 写入&#xff1a; package mainimport (&…

企业级信息系统开发——初探Spring AOP

文章目录 一、提出游吟诗人唱赞歌任务&#xff08;一&#xff09;采用传统方式实现&#xff08;二&#xff09;采用传统方式实现的缺点 二、采用配置方式使用AOP&#xff08;一&#xff09;创建本讲所需子包&#xff08;二&#xff09;创建杀龙任务类&#xff08;三&#xff09…

VESD静电监控系统:提高静电防护效果与管理效率

随着科学技术不断发展&#xff0c;现代的工业对静电防护的要求越来越高。因为静电的存在可能会对产品质量、工作环境、甚至是人身产生威胁。静电监控系统是一项高效的管理工具&#xff0c;能够有效地控制和监测静电产生的情况&#xff0c;提高静电防护效果和管理效率。 VESD静电…

群晖DS920 video station使用教程

群晖DS920 video station使用教程 为了更好的浏览体验&#xff0c;欢迎光顾勤奋的凯尔森同学个人博客http://www.huerpu.cc:7000 安装video station在群晖套件里点一下就好&#xff0c;这里不说了。 一、添加视频库 可以添加电视剧、电视节目等类型。 比如我在国产剧这个视频…