《企业实战分享 · 对象存储服务OSS、S3、MinIO》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻一周,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍

文章目录

写在前面的话

对象存储服务(Object Storage Service, OSS),是一种云存储解决方案,专门用于存储和管理大量的非结构化数据,如图片、视频、日志文件等。在企业实战开发中运用范围较为广泛,当数据存储的需求量较大时,可以用来替换传统的FTP技术方案,此时,使用OSS更为合适。

本篇文章介绍一下Java如何操作常用的对象存储服务,包含阿里云OSS、亚马逊S3,Minio。


Java 操作阿里云 OSS

前置准备

阿里云平台添加对象存储OSS服务,创建一个Bucket,相关信息后续程序操作需要用到。

代码操作

Step1、添加Maven依赖

xml 复制代码
<!-- 阿里的oss -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
</dependency>

Step2、添加Yml配置

yaml 复制代码
ali:
  oss:
    # 阿里云账户的accessKeyId
    accessKeyId: LTAIaP4LbPNVGoPy
    # 阿里云账户的accessKeySecret
    accessKeySecret: sebe63FjqLEGEpXyYAOtR2gZN4Xa3K
    # 阿里云账户的endpoint,地域
    endPoint: oss-cn-shenzhen.aliyuncs.com
    # 阿里云账户的bucketName
    bucketName: photo-admin
    # 最终文件访问的路径
    fileHost: https://photo-admin.oss-cn-shenzhen.aliyuncs.com/

Step3、添加对应的配置类

java 复制代码
@Component
@ConfigurationProperties(prefix = "ali.oss")
@Data
public class AliOSSProperties {

    private String accessKeyId;

    private String accessKeySecret;

    private String endPoint;

    private String bucketName;

    private String fileHost;
}

Step4、编写工具类

java 复制代码
public class AliOSSUtil {

    private static final String CATALOG = "mzxiao/";

    public static String upload(File file) {
        if (file == null) {
            return null;
        }
        AliOSSProperties properties = SpringContextHolder.getBean(AliOSSProperties.class);
        // 创建OSS客户端
        OSSClient ossClient = new OSSClient(properties.getEndPoint(), properties.getAccessKeyId(), properties.getAccessKeySecret());
        try {
            // 判断文件容器是否存在,不存在则创建
            if (!ossClient.doesBucketExist(properties.getBucketName())) {
                ossClient.createBucket(properties.getBucketName());
                CreateBucketRequest createBucketRequest = new CreateBucketRequest(properties.getBucketName());
                createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
                ossClient.createBucket(createBucketRequest);
            }
            // 创建文件路径
            String fileUrl = CATALOG + DateUtils.toShortStringFormat(new Date()) + "/" + IdUtils.uuid(false);

            // 创建上传文件的元信息,可以通过文件元信息设置HTTP header。
            ObjectMetadata meta = new ObjectMetadata();

            // 上传文件
            PutObjectResult result = ossClient.putObject(new PutObjectRequest(properties.getBucketName(), fileUrl, file, meta));
            if (null != result) {
                return properties.getFileHost() + fileUrl;
            }
        } catch (OSSException oe) {
            log.error(oe.getMessage());
        } finally {
            // 关闭OSS服务,一定要关闭
            ossClient.shutdown();
        }
        return null;
    }
}

Step5、操作示例

java 复制代码
//省略文件上传接口代码
AliOSSUtil.upload(newFile)

Step6、查看上传结果


Java 操作亚马逊 S3

前置准备

由于工作需要,服务偶尔也部署在亚马逊,因此顺道封装了对亚马逊的对象存储服务的集成。

亚马逊云平台也提供了OSS服务,但名字是叫S3,对标阿里云的OSS,基本概念和代码操作也类似OSS。

首先,也是先到亚马逊开通S3服务,一系列操作,此处省略一万字。


代码操作

Step1、添加Maven依赖

xml 复制代码
<!-- AWS SDK start -->
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.11.803</version>
</dependency>
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-sts</artifactId>
    <version>1.11.803</version>
</dependency>
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-core</artifactId>
    <version>1.11.803</version>
</dependency>
<!-- AWS SDK end -->

Step2、添加Yml配置

yaml 复制代码
aws:
  s3:
    endpoint: 同阿里云
    access-key-id: 同阿里云
    access-key-secret: 同阿里云
    bucket-name: 存储桶明朝
    root-directory: 根目录
    region: 区域
    url: 文件访问地址

Step3、编写工具类

java 复制代码
@Component
@Slf4j
public class S3ObjectStorage extends BaseObjectStorage {

    @Data
    @Component
    @ConfigurationProperties(prefix = "aws.s3")
    public static class OssInfo {
        private String host;
        private String endpoint;
        private String accessKeyId;
        private String accessKeySecret;
        private String bucketName;
        private String rootDirectory;
        private String stsEndpoint;
        private String region;
        private String url;
    }

    @Autowired
    private OssInfo ossInfo;

    @Override
    public String upload(String pathAndName, File file) {
        String result = "";
        AWSStaticCredentialsProvider credential = new AWSStaticCredentialsProvider(new BasicAWSCredentials(ossInfo.accessKeyId, ossInfo.accessKeySecret));
        EndpointConfiguration endpointConfiguration = new EndpointConfiguration(ossInfo.endpoint, null);
        AmazonS3 s3 = AmazonS3ClientBuilder.standard()
                .withCredentials(credential)
                .withEndpointConfiguration(endpointConfiguration)
                .build();
        try {
            String bucketPath = ossInfo.bucketName + "/" + ossInfo.rootDirectory;
            s3.putObject(new PutObjectRequest(bucketPath, pathAndName, file).withCannedAcl(CannedAccessControlList.PublicRead));
            result = ossInfo.url + ossInfo.rootDirectory + "/" + pathAndName;
            log.info("===s3===上传文件记录:成功");
        } catch (AmazonServiceException ase) {
            log.error("===s3===文件上传服务端异常:", ase);
        } catch (AmazonClientException ace) {
            log.error("===s3===文件上传客户端异常:", ace);
        } finally {
            s3.shutdown();
        }
        return result;
    }

    @Override
    public String authorize(String pathAndName, long time) {
        AWSStaticCredentialsProvider credential = new AWSStaticCredentialsProvider(new BasicAWSCredentials(ossInfo.accessKeyId, ossInfo.accessKeySecret));
        EndpointConfiguration endpointConfiguration = new EndpointConfiguration(ossInfo.endpoint, null);
        AmazonS3 s3 = AmazonS3ClientBuilder.standard()
                .withCredentials(credential)
                .withEndpointConfiguration(endpointConfiguration)
                .build();
        try {
            Date expiration = new Date(System.currentTimeMillis() + time);
            URL url = s3.generatePresignedUrl(ossInfo.bucketName, ossInfo.rootDirectory + "/" + pathAndName, expiration);
            String resultUrl = url.toString();
            log.info("===s3===文件上传客户端返回url:{}", resultUrl);
            resultUrl = resultUrl.substring(0, resultUrl.indexOf("?"));
            resultUrl = resultUrl.replaceAll(ossInfo.host, ossInfo.endpoint);
            log.info("===s3===文件上传客户端返回url:{}", resultUrl);
            return resultUrl;
        } finally {
            s3.shutdown();
        }
    }

    @Override
    public String authorizeAllName(String pathAndName, long time) {
        AWSStaticCredentialsProvider credential = new AWSStaticCredentialsProvider(new BasicAWSCredentials(ossInfo.accessKeyId, ossInfo.accessKeySecret));
        EndpointConfiguration endpointConfiguration = new EndpointConfiguration(ossInfo.endpoint, null);
        AmazonS3 s3 = AmazonS3ClientBuilder.standard()
                .withCredentials(credential)
                .withEndpointConfiguration(endpointConfiguration)
                .build();
        try {
            Date expiration = new Date(System.currentTimeMillis() + time);
            URL url = s3.generatePresignedUrl(ossInfo.bucketName, pathAndName, expiration);
            String resultUrl = url.toString();
            resultUrl = resultUrl.replaceAll(ossInfo.host, ossInfo.endpoint);
            log.info("===s3==========authorizeAllName,S3文件上传客户端返回url:{}", resultUrl);
            return resultUrl;
        } finally {
            s3.shutdown();
        }
    }

    @Override
    public Map<String, Object> tokens(String dir) {
        Map<String, Object> result = null;
        AWSSecurityTokenService stsClient = null;
        try {
            result = Maps.newHashMap();
            AWSStaticCredentialsProvider credential = new AWSStaticCredentialsProvider(new BasicAWSCredentials(ossInfo.accessKeyId, ossInfo.accessKeySecret));
            EndpointConfiguration endpointConfiguration = new EndpointConfiguration(ossInfo.stsEndpoint, null);
            stsClient = AWSSecurityTokenServiceAsyncClientBuilder.standard()
                    .withCredentials(credential)
                    .withEndpointConfiguration(endpointConfiguration)
                    .build();
            GetFederationTokenRequest request = new GetFederationTokenRequest().withName("Bob")
                    .withPolicy("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Sid1\",\"Effect\":\"Allow\",\"Action\":[\"s3:*\"],\"Resource\":[\"*\"]}]}")
                    .withDurationSeconds(3600);
            GetFederationTokenResult response = stsClient.getFederationToken(request);
            Credentials tempCredentials = response.getCredentials();

            result.put("storeType", "s3");
            result.put("accessKeyId", tempCredentials.getAccessKeyId());
            result.put("sessionToken", tempCredentials.getSessionToken());
            result.put("secretKey", tempCredentials.getSecretAccessKey());
            result.put("expire", tempCredentials.getExpiration());
            result.put("dir", dir);
            result.put("bucketName", ossInfo.bucketName);
            result.put("region", ossInfo.region);
            result.put("host", "https://" + ossInfo.endpoint + "/" + ossInfo.bucketName);
            log.info("===s3===上传文件记录:accessKeyId:{},sessionToken:{}", tempCredentials.getAccessKeyId(), tempCredentials.getSessionToken());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != stsClient) {
                stsClient.shutdown();
            }
        }
        return result;
    }

    @Override
    public void deleteFile(String pathAndName) {
        AWSStaticCredentialsProvider credential = new AWSStaticCredentialsProvider(new BasicAWSCredentials(ossInfo.accessKeyId, ossInfo.accessKeySecret));
        EndpointConfiguration endpointConfiguration = new EndpointConfiguration(ossInfo.endpoint, null);
        AmazonS3 s3 = AmazonS3ClientBuilder.standard()
                .withCredentials(credential)
                .withEndpointConfiguration(endpointConfiguration)
                .build();
        try {
            s3.deleteObject(ossInfo.bucketName, ossInfo.bucketName + pathAndName);
        } finally {
            s3.shutdown();
        }
    }
}

Step4、操作示例

如果同一套代码同时需要集成多种对象存储服务,可以改造策略模式,下方先用if-else演示效果。

java 复制代码
if ("aws".equals(flag)) {
    // 上传到亚马逊
    String fileType = fileName.substring(fileName.lastIndexOf("."));
    files.add(baseObjectStorage.upload(IdUtils.uuid() + fileType, newFile));
} else if ("oss".equals(flag)) {
    // 上传到阿里云固定目录
    files.add(AliOSSUtil.upload(newFile));
} else {
    // 其他文件上传逻辑
}

Step5、补充说明

亚马逊S3用法基本和阿里云类似,没什么好说的。

但较复杂的就是其存储桶配置,很多坑,后续再展开介绍。


Java 操作 Minio

前置准备

阿里云 OSS、亚马逊 S3 和 MinIO 都是对象存储系统,它们的基本作用相似,即存储和管理大量非结构化数据,如文件、图片、视频等。不过,它们在提供商、部署方式、特性和适用场景上有所不同。

主要区别如下:

  • OSS 和 S3 由各自的云服务提供商(阿里云和 AWS)提供,MinIO 是一个开源项目。
  • OSS 和 S3 适合希望利用云服务生态系统的用户, MinIO 适合需要自托管、高性能或数据隐私和合规性的用户。

选择哪种对象存储解决方案,取决于用户的具体需求、技术栈、预算和对数据管理的偏好。

上面都是官方套话,简单说明一下:OSS和S3是一个类型的东西,而MinIO是可以独立部署在私人服务器,保密性更好。

技术简介

MinIO 基于 Apache License v2.0开 源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。MinIO 兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据。

MinIO 是一个分布式对象存储系统,设计用于高性能和高可用性的大规模数据存储,而 FTP 主要用于文件传输,适合小规模、简单的文件存储和传输需求。两者在数据存储模型、扩展性、冗余和安全性方面有显著差异。

实战做法

由于篇幅所限,这边不展开介绍 Minio 如何部署和编码,直接介绍其在企业实战开发中使用。

  • 首先,采用 MinIO 是为了替换原有 FTP 的文件存储方案;
  • 其次,单独部署 MinIO,再创建一个单独的 storage-service 服务,完成 MinIO的交互和各类辅助功能;
  • 最后,其他服务通过 Feign 的方式,和 storage-service 完成交互;

总结陈词

上文介绍了对象存储服务的用法,仅供参考。

关于 MinIO 的具体实现细节,后面另外开一篇介绍。

值得一提的是,如果您的框架要集成多种对象存储服务,那应该考虑如何统一出入口,使用策略模式等方式改造,而不应该都是单独的工具类。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关推荐
cfm_29147 小时前
SpringBoot启动原理
java·spring boot·spring
漫随流水7 小时前
Java——springboot web案例
java·开发语言·spring boot
从此以后自律7 小时前
Java Object 类常用方法全讲解
java·开发语言
念何架构之路8 小时前
Docker 容器状态机 学习
java·学习·docker
你有我备注吗8 小时前
SQL学习之查询
java·sql·学习
xixi09248 小时前
JMeter5.6.3基础使用教程1
java·开发语言
带刺的坐椅8 小时前
用 Solon ReActAgent 落地发票识别与智能报销
java·llm·solon·react-agent
罗超驿9 小时前
15.Java异常处理:从束手无策到从容应对
java·开发语言
Co_Hui9 小时前
Java 基础
java·开发语言
半夜修仙9 小时前
RabbitMQ的推模式和拉模式
java·分布式·中间件·rabbitmq·github·java-rabbitmq