📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻一周,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍
写在前面的话
对象存储服务(Object Storage Service, OSS),是一种云存储解决方案,专门用于存储和管理大量的非结构化数据,如图片、视频、日志文件等。在企业实战开发中运用范围较为广泛,当数据存储的需求量较大时,可以用来替换传统的FTP
技术方案,此时,使用OSS
更为合适。
本篇文章介绍一下Java如何操作常用的对象存储服务,包含阿里云OSS、亚马逊S3,Minio。
Java 操作阿里云 OSS
前置准备
阿里云平台添加对象存储OSS服务,创建一个Bucket,相关信息后续程序操作需要用到。
代码操作
Step1、添加Maven依赖
<!-- 阿里的oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
Step2、添加Yml配置
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、添加对应的配置类
@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、编写工具类
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、操作示例
//省略文件上传接口代码
AliOSSUtil.upload(newFile)
Step6、查看上传结果
Java 操作亚马逊 S3
前置准备
由于工作需要,服务偶尔也部署在亚马逊,因此顺道封装了对亚马逊的对象存储服务的集成。
亚马逊云平台也提供了OSS服务,但名字是叫S3,对标阿里云的OSS,基本概念和代码操作也类似OSS。
首先,也是先到亚马逊开通S3服务,一系列操作,此处省略一万字。
代码操作
Step1、添加Maven依赖
<!-- 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配置
aws:
s3:
endpoint: 同阿里云
access-key-id: 同阿里云
access-key-secret: 同阿里云
bucket-name: 存储桶明朝
root-directory: 根目录
region: 区域
url: 文件访问地址
Step3、编写工具类
@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演示效果。
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 的具体实现细节,后面另外开一篇介绍。
值得一提的是,如果您的框架要集成多种对象存储服务,那应该考虑如何统一出入口,使用策略模式等方式改造,而不应该都是单独的工具类。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。