SpringBoot框架中使用云存储,如何优雅地在亚马逊、华为、阿里、腾...

momory

共 8554字,需浏览 18分钟

 · 2022-04-13

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;
@Servicepublic class AmazonS3OperationServiceImpl implements AmazonS3OperationService {
@Value("${amazon-server.bucket}") private String TEST_BUCKET = "hx-aqgl-test";
@Value("${amazon-server.url-out-time}") 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; }
@Override public void uploadFile(MultipartFile file, @NotBlank 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())); }
@Override public byte[] downloadFile(@NotBlank String key) throws IOException { GetObjectRequest getObjectRequest = GetObjectRequest.builder() .bucket(TEST_BUCKET) .key(key) .build(); ResponseInputStream object = client.getObject(getObjectRequest); return object.readAllBytes();
}
@Override public void deleteFile(@NotBlank String key) { DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() .bucket(TEST_BUCKET) .key(key) .build(); client.deleteObject(deleteObjectRequest); }
@Override public String getFileUrl(@NotBlank 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;
@Servicepublic class HuaWeiServerImpl implements AmazonS3OperationService {
@Resource private ObsConfig obsConfig;
@Override public void uploadFile(MultipartFile file, String key) throws IOException { PutObjectResult putObjectResult = UploadFileUtil.uploadNetworkStream(obsConfig.createObsClient(), obsConfig.getBucketName(), file.getInputStream(), key); log.info("上传文件:{}", putObjectResult); }
@Override 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(); }
@Override public void deleteFile(String key) { DeleteObjectResult deleteObjectResult = obsConfig.createObsClient().deleteObject( obsConfig.getBucketName(), key); log.info("删除文件:{}", deleteObjectResult); }
@Override 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 替换为
@Configuration@ConditionalOnProperty(name = "service.type", havingValue="Amazon”)

华为云实现: 

// 将 @Service 替换为
@Configuration@ConditionalOnProperty(name = "service.type", havingValue="HuaWei”)

现在使用的 亚马逊云 启动系统:

0bfcab6e1aeca132fe6c9c71e3f1a857.webp

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时,才会触发实例化。

浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报