SpringBoot框架中使用云存储,如何优雅地在亚马逊、华为、阿里、腾...
共 8554字,需浏览 18分钟
·
2022-04-13 18:46
Hello,各位小伙伴,好久又没有更新文章了,不是我不更新,而是产品经理太懒了。
最近产品经理又与我扛上了,因为系统使用云环境不同,产品经理提出按环境变化的一个需求:
我们系统有些资料,比如文件、图片要使用云存储,测试环境使用的是亚马逊云存储;生产环境要使用华为云存储。所以系统必须要实现这2套存储,而且还要按环境动态切换。如果后面要实用阿里或者腾讯云,都可以动态切换。让系统不做太多的改变。
当时听到这需求,心想不是很简单嘛,只需要下面三步:
1、定义一个接口
2、按不同的云厂商实现即可
3、在使用的时候,根据使用的厂商名字注入 Service 即可
可后来仔细一想,这样虽然能完成产品经理提出的功能需求,可每次都要修改注入的 Service 代码,还需要重新打包,实在很麻烦。作为一名 JAVA 懒汉编程人员,这肯定不是我想要的。
有没有更好的方式呢?
有!
作为开发人员一定要干翻产品经理,不能丢了开发人员的颜面,只要你提的出来,我就有对策。所以在产品经理提出需要的时候,我想也没想就回答有。
回答虽然很爽快,但要怎么去实现呢?
整理下面的过程:
1、定义一个接口
2、按厂商实现接口
3、根据厂商注入接口实现
So Easy! 简单三步完成,下面一起来看代码实现:
定义接口:
package com.hx.module.system.v2.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.URISyntaxException;
public interface AmazonS3OperationService {
/**
* 上传文件到S3服务器
*
* key唯一,否则会变成覆盖相同key的内容
*
*
* @param file /
* @param key /
* @throws IOException /
*/
void uploadFile(MultipartFile file, String key) throws IOException;
/**
* 下载文件
*
* @param key /
*/
byte[] downloadFile(String key) throws IOException;
/**
* 删除文件
*
* @param key /
*/
void deleteFile(String key);
/**
* 获取文件匿名访问URL
*
* @param key /
* @return /
* @throws URISyntaxException /
*/
String getFileUrl(String key) throws URISyntaxException;
}
我们现在只有亚马逊、华为云,其实现如下:
亚马逊云实现:
package com.hx.module.system.v2.service.impl;
import com.hx.module.system.v2.service.AmazonS3OperationService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import javax.validation.constraints.NotBlank;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Objects;
public class AmazonS3OperationServiceImpl implements AmazonS3OperationService {
private String TEST_BUCKET = "hx-aqgl-test";
private Integer URL_OUT_TIME = 10;
private final S3Client client;
private final S3Presigner presigner;
public AmazonS3OperationServiceImpl(S3Client client, S3Presigner presigner) {
this.client = client;
this.presigner = presigner;
}
public void uploadFile(MultipartFile file, String key) throws IOException {
if (Objects.isNull(file)) {
return;
}
String originalFilename = file.getOriginalFilename();
if (Objects.isNull(originalFilename)) {
return;
}
// int indexOf = originalFilename.lastIndexOf(".");
// String suffix = originalFilename.substring(indexOf);
// key = key + suffix;
PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(TEST_BUCKET)
.key(key)
.build();
client.putObject(objectRequest, RequestBody.fromBytes(file.getBytes()));
}
public byte[] downloadFile( String key) throws IOException {
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.bucket(TEST_BUCKET)
.key(key)
.build();
ResponseInputStream
object = client.getObject(getObjectRequest); return object.readAllBytes();
}
public void deleteFile( String key) {
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
.bucket(TEST_BUCKET)
.key(key)
.build();
client.deleteObject(deleteObjectRequest);
}
public String getFileUrl( String key) throws URISyntaxException {
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.bucket(TEST_BUCKET)
.key(key)
.build();
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(URL_OUT_TIME))
.getObjectRequest(getObjectRequest)
.build();
PresignedGetObjectRequest presignedGetObjectRequest =
presigner.presignGetObject(getObjectPresignRequest);
return presignedGetObjectRequest.url().toURI().toString();
}
}
华为云实现:
package com.hx.module.system.v2.service.impl;
import com.hx.config.ObsConfig;
import com.hx.module.system.utils.UploadFileUtil;
import com.hx.module.system.v2.service.AmazonS3OperationService;
import com.obs.services.ObsClient;
import com.obs.services.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
public class HuaWeiServerImpl implements AmazonS3OperationService {
private ObsConfig obsConfig;
public void uploadFile(MultipartFile file, String key) throws IOException {
PutObjectResult putObjectResult = UploadFileUtil.uploadNetworkStream(obsConfig.createObsClient(), obsConfig.getBucketName(), file.getInputStream(), key);
log.info("上传文件:{}", putObjectResult);
}
public byte[] downloadFile(String key) throws IOException {
ObsObject obsObject = obsConfig.createObsClient().getObject(obsConfig.getBucketName(), key);
InputStream input = obsObject.getObjectContent();
log.info("下载文件:{}", input.toString());
return input.readAllBytes();
}
public void deleteFile(String key) {
DeleteObjectResult deleteObjectResult = obsConfig.createObsClient().deleteObject( obsConfig.getBucketName(), key);
log.info("删除文件:{}", deleteObjectResult);
}
public String getFileUrl(String key) {
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.PUT, obsConfig.getExpireSeconds() > 60 ? obsConfig.getExpireSeconds() : 300L);
request.setBucketName(obsConfig.getBucketName());
request.setObjectKey(key);
// 创建ObsClient实例
ObsClient obsClient = obsConfig.createObsClient();
TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
return response.getSignedUrl();
}
}
不同厂商的云实现已经有了,现在需要在配制文件里添加配制参数:
## 配制亚马逊存储或者华为云存储
service:
# Amazon 亚马逊存储
# huaWei 华为云存储
type: Amazon
最后根据参数修改亚马逊云、华为云实现的注解:
亚马逊云实现:
// 将 @Service 替换为
"service.type", havingValue="Amazon”) (name =
华为云实现:
// 将 @Service 替换为
"service.type", havingValue="HuaWei”) (name =
现在使用的 亚马逊云 启动系统:
OK,项目启动成功!!
后面即使使用阿里去,腾讯云或者其它,我只需要pugm实现一个接口,在修改配制文件即可启动对应的云Servie
总结:
动态切换主要用到了2个注解:
1、@Configuration 是 Spring-conten 中的一个注解,将该类声明为一个配置类
2、@ConditionalOnProperty 是 spring-boot-autoconfigure 中的一个注解,根据注解从配制文件中读取到的值,决定是否把当前类注入到 Spring 中
扩展, 以 Conditional 开头的还有以下注解:
@ConditionalOnBean:仅在当前上下文中存在某个对象时,才会实例化一个Bean。
@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
@ConditionalOnExpression:当表达式值为true的时候,才会实例化一个Bean。
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
@ConditionalOnNotWebApplication:非web应用,才会实例化一个Bean。
@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnExpression:基于SpEL表达式的条件判断。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时,才会触发实例化。