《企业实战分享 · 对象存储服务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 的具体实现细节,后面另外开一篇介绍。

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

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

相关推荐
小安同学iter3 分钟前
Java进阶五 -IO流
java·开发语言·intellij-idea
尽兴-14 分钟前
Redis模拟延时队列 实现日程提醒
java·redis·java-rocketmq·mq
书埋不住我34 分钟前
java第三章
java·开发语言·servlet
boy快快长大36 分钟前
将大模型生成数据存入Excel,并用增量的方式存入Excel
java·数据库·excel
孟秋与你39 分钟前
【spring】spring单例模式与锁对象作用域的分析
java·spring·单例模式
菜菜-plus43 分钟前
java 设计模式 模板方法模式
java·设计模式·模板方法模式
萨达大44 分钟前
23种设计模式-模板方法(Template Method)设计模式
java·c++·设计模式·软考·模板方法模式·软件设计师·行为型设计模式
tian-ming1 小时前
(十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器
java·开发语言·前端
不能只会打代码1 小时前
大学课程项目中的记忆深刻 Bug —— 一次意外的数组越界
java·github·intellij-idea·话题博客
快意咖啡~1 小时前
java.nio.charset.MalformedInputException: Input length = 1
java·开发语言·nio