5.2、MinioStorageAdapter minio存储适配器
5.3、AliStorageAdapter 阿里云oss存储适配器
一、Minio概述
MinIO 是一个开源的对象存储服务器,它兼容 Amazon S3 服务的 API。MinIO 被设计为可扩展的、高性能的对象存储系统,可用于构建私有云存储、大规模文件存储和分布式存储系统
特点:
-
高性能:是一个轻量级的存储服务器,使用 Go 语言编写,具有出色的性能和低延迟。它能够充分利用现代硬件的多核处理能力和 SSD 存储设备的高速读写能力。
-
可扩展性: 支持水平扩展,可以在多个节点上部署构建分布式存储集群。通过增加节点数量,可以实现存储容量和吞吐量的线性扩展。
-
容错性: 采用了分布式的数据存储和容错机制,可以在节点故障时自动进行数据恢复和重建,确保数据的可靠性和持久性。
-
安全性:提供了多种安全功能,包括数据加密、访问控制、身份认证和传输加密等,确保存储的数据在传输和存储过程中的安全性。
-
兼容性: 完全兼容 Amazon S3 服务的 API,可以与现有的 S3 客户端和工具无缝集成,方便用户迁移和管理数据。
二、Docker安装Minio
1、查询minio镜像列表
docker search minio
2、拉取minio镜像并检查是否拉取成功
docker pull minio/minio
docker images
3、运行minio
docker run -p 9000:9000 -p 9090:9090 \
--name minio \
-d --restart=always \
-e "MINIO_ACCESS_KEY=minioadmin" \
-e "MINIO_SECRET_KEY=minioadmin" \
-v /mydata/minio/data:/data \
minio/minio server \
/data --console-address ":9090" -address ":9000"
4、查看minio容器是否运行
docker ps -a
三、Minio客户端
浏览器输入ip+端口,根据上述设置的用户名和密码进入minio客户端
创建存储桶:
四、项目架构
五、SpringBoot整合Minio
1、application.yml配置
server:
port: 4000
minio:
url: http://服务器ip:9000
accessKey: minioadmin
secretKey: minioadmin
storage:
service:
type: minio
2、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ysy</groupId>
<artifactId>sy-club-oss</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<!--log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
<!--打包成jar包时的名字-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!--定义了一个仓库(repository)配置,用于Maven项目构建中指定依赖库的下载地址-->
<repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<layout>default</layout>
<!--快照版本是否启用-->
<snapshots>
<enabled>true</enabled>
</snapshots>
<!--发布版本是否启用-->
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
</project>
3、OSSApplication启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* OssApplication oss服务启动器
*/
@SpringBootApplication
@ComponentScan("com.ysy")
public class OssApplication {
public static void main(String[] args) {
SpringApplication.run(OssApplication.class, args);
}
}
3、Minio客户端配置类
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description: minio配置管理 用于配置并获取MinIO客户端
*/
@Configuration
public class MinioConfig {
//minioUrl
@Value("${minio.url}")
private String url;
// minio账户
@Value("${minio.accessKey}")
private String accessKey;
// minio密码
@Value("${minio.secretKey}")
private String secretKey;
/**
* 获取minio客户端
* @return MinioClient MinIO客户端对象
*/
@Bean
public MinioClient getMinioClient() {
return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
}
}
4、MinioUtil
import com.ysy.oss.entity.FileInfo;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* minio文件操作工具类
*/
@Component
public class MinioUtil {
@Resource
private MinioClient minioClient;
/**
* 创建bucket桶
*/
public void createBucket(String bucket) throws Exception {
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
}
}
/**
* 上传文件
*/
public void uploadFile(InputStream inputStream, String bucket, String objectName) throws Exception {
minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName)
.stream(inputStream, -1, 5242889L).build());
}
/**
* 列出所有桶
*/
public List<String> getAllBucket() throws Exception {
List<Bucket> buckets = minioClient.listBuckets();
return buckets.stream().map(Bucket::name).collect(Collectors.toList());
}
/**
* 列出当前桶及文件
*/
public List<FileInfo> getAllFile(String bucket) throws Exception {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucket).build());
List<FileInfo> fileInfoList = new LinkedList<>();
for (Result<Item> result : results) {
FileInfo fileInfo = new FileInfo();
Item item = result.get();
fileInfo.setFileName(item.objectName());
fileInfo.setDirectoryFlag(item.isDir());
fileInfo.setEtag(item.etag());
fileInfoList.add(fileInfo);
}
return fileInfoList;
}
/**
* 下载文件
*/
public InputStream downLoad(String bucket, String objectName) throws Exception {
return minioClient.getObject(
GetObjectArgs.builder().bucket(bucket).object(objectName).build()
);
}
/**
* 删除桶
*/
public void deleteBucket(String bucket) throws Exception {
minioClient.removeBucket(
RemoveBucketArgs.builder().bucket(bucket).build()
);
}
/**
* 删除文件
*/
public void deleteObject(String bucket, String objectName) throws Exception {
minioClient.removeObject(
RemoveObjectArgs.builder().bucket(bucket).object(objectName).build()
);
}
/**
* 获取文件url
*/
public String getPreviewFileUrl(String bucketName, String objectName) throws Exception{
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName).object(objectName).build();
return minioClient.getPresignedObjectUrl(args);
}
}
4、文件实体类
import lombok.Data;
/**
* 文件类
*/
@Data
public class FileInfo {
private String fileName;
private Boolean directoryFlag;
private String etag;
}
5、采用适配器模式来实现具体文件存储服务
注意:考虑 oss 的扩展性和切换性。
目前对接的 minio,如果作为公共的 oss 服务,如何切换到其他的阿里云 oss 或者对接京东云的 oss。作为基础的 oss 服务,切换等等动作,不应该要求业务方进行改造,以及对切换有感知。
为什么采用适配器模式而不采用简单的service和impl实现service进行方法的重写?
适配器:将一个接口适配到另一个接口,让不兼容组件一起工作。意思就是:比如说StorageAdapter 里定义了Bucket的概念,并不是所有的OSS服务厂商都有这个概念,可能哟不一样的,但这些都是业务上的概念,所以要做一层转化,在转化过程中,就需要适配器做一些处理。
适配器吧,最主要还是封装和适配我们这些组件,比如阿里云的和京东云的等等,我们提供统一的接口,解决接口不兼容的问题。
,
5.1、StorageAdapter 文件存储适配器接口
import com.ysy.oss.entity.FileInfo;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
/**
* @Description: 文件存储适配器接口
*/
public interface StorageAdapter {
/**
* 创建bucket桶
*/
void createBucket(String bucket);
/**
* 上传文件
*/
void uploadFile(MultipartFile uploadFile, String bucket, String objectName);
/**
* 列出所有桶
*/
List<String> getAllBucket();
/**
* 列出当前桶及文件
*/
List<FileInfo> getAllFile(String bucket);
/**
* 下载文件
*/
InputStream downLoad(String bucket, String objectName);
/**
* 删除桶
*/
void deleteBucket(String bucket);
/**
* 删除文件
*/
void deleteObject(String bucket, String objectName);
String getUrl(String bucket, String objectName);
}
5.2、MinioStorageAdapter minio存储适配器
import com.ysy.oss.entity.FileInfo;
import com.ysy.oss.util.MinioUtil;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.List;
/**
* @Description: minio存储适配器
*/
public class MinioStorageAdapter implements StorageAdapter{
@Resource
private MinioUtil minioUtil;
/**
* minioUrl
*/
@Value("${minio.url}")
private String url;
@Override
@SneakyThrows
public void createBucket(String bucket) {
minioUtil.createBucket(bucket);
}
@Override
@SneakyThrows
public void uploadFile(MultipartFile uploadFile, String bucket, String objectName) {
minioUtil.createBucket(bucket);
if (objectName != null) {
minioUtil.uploadFile(uploadFile.getInputStream(), bucket, objectName + "/" + uploadFile.getOriginalFilename());
} else {
minioUtil.uploadFile(uploadFile.getInputStream(), bucket, uploadFile.getOriginalFilename());
}
}
@Override
@SneakyThrows
public List<String> getAllBucket() {
return minioUtil.getAllBucket();
}
@Override
@SneakyThrows
public List<FileInfo> getAllFile(String bucket) {
return minioUtil.getAllFile(bucket);
}
@Override
@SneakyThrows
public InputStream downLoad(String bucket, String objectName) {
return minioUtil.downLoad(bucket, objectName);
}
@Override
@SneakyThrows
public void deleteBucket(String bucket) {
minioUtil.deleteBucket(bucket);
}
@Override
@SneakyThrows
public void deleteObject(String bucket, String objectName) {
minioUtil.deleteObject(bucket, objectName);
}
@Override
@SneakyThrows
public String getUrl(String bucket, String objectName) {
return url + "/" + bucket + "/" + objectName;
}
}
5.3、AliStorageAdapter 阿里云oss存储适配器
import com.ysy.oss.entity.FileInfo;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
/**
* @Description: 阿里云oss存储适配器
*/
public class AliStorageAdapter implements StorageAdapter{
@Override
public void createBucket(String bucket) {
}
@Override
public void uploadFile(MultipartFile uploadFile, String bucket, String objectName) {
}
@Override
public List<String> getAllBucket() {
List<String> bucketNameList = new LinkedList<>();
bucketNameList.add("aliyun");
return bucketNameList;
}
@Override
public List<FileInfo> getAllFile(String bucket) {
return null;
}
@Override
public InputStream downLoad(String bucket, String objectName) {
return null;
}
@Override
public void deleteBucket(String bucket) {
}
@Override
public void deleteObject(String bucket, String objectName) {
}
@Override
public String getUrl(String bucket, String objectName) {
return null;
}
}
6、StorageConfig 文件存储配置
import com.ysy.oss.adapter.AliStorageAdapter;
import com.ysy.oss.adapter.MinioStorageAdapter;
import com.ysy.oss.adapter.StorageAdapter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 文件存储配置
* 用于根据不同的存储服务类型创建并返回对应的存储适配器(StorageAdapter)实例
*/
@Configuration
@RefreshScope
public class StorageConfig {
@Value("${storage.service.type}")
private String storageType;
@Bean
@RefreshScope
public StorageAdapter storageService() {
if ("minio".equals(storageType)) {
return new MinioStorageAdapter();
} else if ("aliyun".equals(storageType)) {
return new AliStorageAdapter();
} else {
throw new IllegalArgumentException("未找到对应的文件存储处理器");
}
}
}
7、 FileService 文件存储服务
import com.ysy.oss.adapter.StorageAdapter;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* @Description: 文件存储服务
* 主要实现了列出所有存储桶、获取文件路径以及上传文件等功能
*/
@Service
public class FileService {
private final StorageAdapter storageAdapter;
public FileService(StorageAdapter storageAdapter) {
this.storageAdapter = storageAdapter;
}
/**
* 列出所有桶
*/
public List<String> getAllBucket() {
return storageAdapter.getAllBucket();
}
/**
* 获取文件路径
*/
public String getUrl(String bucketName,String objectName) {
return storageAdapter.getUrl(bucketName,objectName);
}
/**
* 上传文件
*/
public String uploadFile(MultipartFile uploadFile, String bucket, String objectName){
storageAdapter.uploadFile(uploadFile,bucket,objectName);
objectName = objectName + "/" + uploadFile.getOriginalFilename();
return storageAdapter.getUrl(bucket, objectName);
}
}
8、FileController
import com.ysy.oss.entity.Result;
import com.ysy.oss.service.FileService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
/**
* @Description : OSS服务controller
*/
@RestController
public class FileController {
@Resource
private FileService fileService;
@RequestMapping("/testGetAllBuckets")
public String testGetAllBuckets() throws Exception {
List<String> allBucket = fileService.getAllBucket();
return allBucket.get(0);
}
@RequestMapping("/getUrl")
public String getUrl(String bucketName, String objectName) throws Exception {
return fileService.getUrl(bucketName, objectName);
}
/**
* 上传文件
*/
@RequestMapping("/upload")
public Result upload(MultipartFile uploadFile, String bucket, String objectName) throws Exception {
String url = fileService.uploadFile(uploadFile, bucket, objectName);
return Result.ok(url);
}
}
六、拓展:nacos动态配置
现在有一个问题,虽然我们抽取了适配器接口,但是我们切换不同oss服务时,还是需要不断地去application,yml配置文件里进行修改,不断地重启服务,很费事!!!当然不止用于该场景,也可以用去切换数据库环境等等..........
1、Docker安装Nacos
拉取nacos镜像
docer pull nacos/nacos-server
运行镜像并检查是是否与运行成功
docker run -d \
--name nacos \
--privileged \
--cgroupns host \
--env JVM_XMX=256m \
--env MODE=standalone \
--env JVM_XMS=256m \
-p 8848:8848/tcp \
-p 9848:9848/tcp \
--restart=always \
-w /home/nacos \
nacos/nacos-server
浏览器ip+8848:
2、配置管理
3、application.yml修改
server:
port: 4000
minio:
url: http://服务器ip:9000
accessKey: minioadmin
secretKey: minioadmin
4、pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ysy</groupId>
<artifactId>sy-club-oss</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<spring-cloud.version>2020.0.6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<!--nacos-config注册-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!--bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--nacos-discovery服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
<!--打包成jar包时的名字-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!--定义了一个仓库(repository)配置,用于Maven项目构建中指定依赖库的下载地址-->
<repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<layout>default</layout>
<!--快照版本是否启用-->
<snapshots>
<enabled>true</enabled>
</snapshots>
<!--发布版本是否启用-->
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
</project>
5、bootstrap.yml配置
spring:
application:
name: sy-club-oss
profiles:
active: dev
cloud:
nacos:
config:
server-addr: 服务器ip:8848
prefix: ${spring.application.name}
group: DEFAULT_GROUP
namespace:
file-extension: yaml
discovery:
enabled: true
server-addr: 服务器ip:8848
七、FileController
import com.ysy.oss.entity.Result;
import com.ysy.oss.service.FileService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
/**
* @Description : OSS服务controller
*/
@RestController
public class FileController {
@Resource
private FileService fileService;
@RequestMapping("/testGetAllBuckets")
public String testGetAllBuckets() throws Exception {
List<String> allBucket = fileService.getAllBucket();
return allBucket.get(0);
}
@RequestMapping("/getUrl")
public String getUrl(String bucketName, String objectName) throws Exception {
return fileService.getUrl(bucketName, objectName);
}
/**
* 上传文件
*/
@RequestMapping("/upload")
public Result upload(MultipartFile uploadFile, String bucket, String objectName) throws Exception {
String url = fileService.uploadFile(uploadFile, bucket, objectName);
return Result.ok(url);
}
}