SpringBoot 整合 Minio 和 FastDFS 实现分布式文件存储
本文将详细介绍如何在 SpringBoot 项目中整合 Minio 和 FastDFS 两种分布式文件存储方案,包括环境配置、依赖添加、功能实现等内容。
一、SpringBoot 整合 Minio
1. Minio 简介
Minio 是一款高性能的对象存储服务,兼容 Amazon S3 接口,具有以下优势:
- 部署简单,支持单机和分布式模式
- 高性能,专为对象存储设计
- 功能丰富,支持标准 S3 协议
- 支持分布式存储,具备高扩展性和高可用性
2. 安装 Minio (Docker 方式)
bash
docker pull minio/minio
docker run -d \
-p 9000:9000 \
-p 9001:9001 \
--name minio \
-v /mnt/minio/data:/data \
-v /mnt/minio/config:/root/.minio \
-e MINIO_ROOT_USER=minio \
-e MINIO_ROOT_PASSWORD=minio123 \
minio/minio server /data --console-address ":9001"
3. SpringBoot 整合 Minio
3.1 添加依赖
xml
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
3.2 配置文件 (application.yml)
yaml
vehicle:
minio:
url: http://localhost:9000 # 连接地址
username: minio # 登录用户名
password: minio123 # 登录密码
bucketName: vehicle # 存储文件的桶名称
3.3 配置类
java
package com.example.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "vehicle.minio")
public class MinioProperties {
private String url;
private String username;
private String password;
private String bucketName;
}
3.4 Minio 工具类
java
package com.example.utils;
import cn.hutool.core.lang.UUID;
import com.example.config.MinioProperties;
import io.minio.*;
import io.minio.errors.*;
import lombok.RequiredArgsConstructor;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;
@Component
@RequiredArgsConstructor
public class MinioUtil {
private final MinioProperties minioProperties;
private MinioClient minioClient;
@PostConstruct
public void init() {
minioClient = MinioClient.builder()
.endpoint(minioProperties.getUrl())
.credentials(minioProperties.getUsername(), minioProperties.getPassword())
.build();
createBucket(minioProperties.getBucketName());
}
// 创建桶
public void createBucket(String bucketName) {
try {
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 文件上传
public String uploadFile(MultipartFile file) throws Exception {
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(""));
minioClient.putObject(
PutObjectArgs.builder()
.bucket(minioProperties.getBucketName())
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
return fileName;
}
// 文件下载
public void downloadFile(String fileName, HttpServletResponse response) throws Exception {
InputStream inputStream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(minioProperties.getBucketName())
.object(fileName)
.build());
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
OutputStream outputStream = response.getOutputStream();
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
// 删除文件
public void deleteFile(String fileName) throws Exception {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(minioProperties.getBucketName())
.object(fileName)
.build());
}
// 获取文件预览地址
public String getPresignedObjectUrl(String fileName) throws Exception {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(io.minio.http.Method.GET)
.bucket(minioProperties.getBucketName())
.object(fileName)
.expiry(2, TimeUnit.HOURS)
.build());
}
}
3.5 控制器实现
java
package com.example.controller;
import com.example.utils.MinioUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/minio")
@Api(tags = "Minio文件管理")
public class MinioController {
@Autowired
private MinioUtil minioUtil;
@ApiOperation("文件上传")
@PostMapping("/upload")
public Map<String, String> upload(@RequestParam("file") MultipartFile file) throws Exception {
String fileName = minioUtil.uploadFile(file);
Map<String, String> result = new HashMap<>();
result.put("fileName", fileName);
result.put("url", minioUtil.getPresignedObjectUrl(fileName));
return result;
}
@ApiOperation("文件下载")
@GetMapping("/download/{fileName}")
public void download(@PathVariable("fileName") String fileName, HttpServletResponse response) throws Exception {
minioUtil.downloadFile(fileName, response);
}
@ApiOperation("文件删除")
@DeleteMapping("/delete/{fileName}")
public String delete(@PathVariable("fileName") String fileName) throws Exception {
minioUtil.deleteFile(fileName);
return "删除成功";
}
@ApiOperation("获取文件预览地址")
@GetMapping("/preview/{fileName}")
public Map<String, String> preview(@PathVariable("fileName") String fileName) throws Exception {
Map<String, String> result = new HashMap<>();
result.put("url", minioUtil.getPresignedObjectUrl(fileName));
return result;
}
}
二、SpringBoot 整合 FastDFS
1. FastDFS 简介
FastDFS 是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。
FastDFS 架构包括:
- Tracker Server:追踪服务器,做负载均衡和调度
- Storage Server:存储服务器,实际存储文件
2. SpringBoot 整合 FastDFS
2.1 添加依赖
xml
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.7</version>
</dependency>
2.2 FastDFS 配置类
java
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;
import com.github.tobato.fastdfs.FdfsClientConfig;
@Configuration
@Import(FdfsClientConfig.class)
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class FastDFSConfiguration {
// 解决jmx重复注册bean的问题
}
2.3 配置文件 (application.yml)
yaml
fdfs:
so-timeout: 1501
connect-timeout: 6000
thumb-image: # 缩略图生成参数
width: 150
height: 150
tracker-list: # TrackerList参数,支持多个
- 192.168.0.1:22122 # 替换为实际的Tracker服务器地址
- 192.168.0.2:22122
pool:
max-total: 200 # 连接池最大数量
max-total-per-key: 20 # 每个tracker地址的最大连接数
max-wait-millis: 5000 # 连接池等待时间
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 20MB
2.4 FastDFS 工具类
java
package com.example.utils;
import com.github.tobato.fastdfs.domain.MataData;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
@Component
public class FastDFSUtil {
@Autowired
private FastFileStorageClient fastFileStorageClient;
// 文件上传
public StorePath uploadFile(MultipartFile file) throws IOException {
// 设置文件信息
Set<MataData> mataData = new HashSet<>();
mataData.add(new MataData("author", "admin"));
mataData.add(new MataData("description", file.getOriginalFilename()));
// 上传文件
return fastFileStorageClient.uploadFile(
file.getInputStream(),
file.getSize(),
FilenameUtils.getExtension(file.getOriginalFilename()),
mataData
);
}
// 文件下载
public void downloadFile(String groupName, String path, String fileName, HttpServletResponse response) throws IOException {
byte[] bytes = fastFileStorageClient.downloadFile(groupName, path, new DownloadByteArray());
response.reset();
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(), "ISO-8859-1"));
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.close();
}
// 删除文件
public void deleteFile(String filePath) {
fastFileStorageClient.deleteFile(filePath);
}
// 删除指定组和路径的文件
public void deleteFile(String groupName, String path) {
fastFileStorageClient.deleteFile(groupName, path);
}
// 获取文件完整路径
public String getFullPath(String groupName, String path) {
return groupName + "/" + path;
}
}
2.5 控制器实现
java
package com.example.controller;
import com.example.utils.FastDFSUtil;
import com.github.tobato.fastdfs.domain.StorePath;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/fastdfs")
@Api(tags = "FastDFS文件管理")
public class FastDFSController {
@Autowired
private FastDFSUtil fastDFSUtil;
@ApiOperation("文件上传")
@PostMapping("/upload")
public Map<String, String> upload(@RequestParam("file") MultipartFile file) throws IOException {
StorePath storePath = fastDFSUtil.uploadFile(file);
Map<String, String> result = new HashMap<>();
result.put("groupName", storePath.getGroup());
result.put("path", storePath.getPath());
result.put("fullPath", storePath.getFullPath());
return result;
}
@ApiOperation("文件下载")
@GetMapping("/download")
public void download(
@RequestParam("groupName") String groupName,
@RequestParam("path") String path,
@RequestParam("fileName") String fileName,
HttpServletResponse response) throws IOException {
fastDFSUtil.downloadFile(groupName, path, fileName, response);
}
@ApiOperation("文件删除")
@DeleteMapping("/delete")
public String delete(@RequestParam("filePath") String filePath) {
fastDFSUtil.deleteFile(filePath);
return "删除成功";
}
@ApiOperation("按组和路径删除文件")
@DeleteMapping("/delete/group")
public String deleteByGroupAndPath(
@RequestParam("groupName") String groupName,
@RequestParam("path") String path) {
fastDFSUtil.deleteFile(groupName, path);
return "删除成功";
}
}
三、Minio 与 FastDFS 对比
特性 | Minio | FastDFS |
---|---|---|
协议支持 | 支持标准 S3 协议 | 自定义协议 |
部署难度 | 简单,支持 Docker 快速部署 | 相对复杂,需要配置 Tracker 和 Storage |
功能丰富度 | 功能丰富,支持版本控制等高级特性 | 功能相对基础,但稳定可靠 |
社区活跃度 | 活跃,更新频繁 | 相对稳定,更新较慢 |
适用场景 | 云原生应用,需要与 S3 兼容的场景 | 传统分布式文件存储,对性能要求高的场景 |
四、选择建议
-
如果您的应用是云原生架构 ,或者需要与 AWS S3 等云存储服务兼容,建议选择 Minio。
-
如果您需要一个稳定可靠的传统分布式文件系统 ,并且对部署复杂性要求不高,建议选择 FastDFS。
-
如果您更关注部署的简便性,Minio 提供了更简单的部署方式,特别是通过 Docker。
-
如果您需要处理大量小文件或对文件存储性能有极高要求,FastDFS 可能更适合。
五、注意事项
-
在生产环境中,请确保配置适当的安全措施,如设置访问密钥、配置防火墙规则等。
-
对于高可用需求,Minio 和 FastDFS 都支持分布式部署,建议配置多个节点以提高可用性。
-
定期备份数据,防止数据丢失。
-
根据实际业务需求,合理配置文件大小限制和连接池参数。
以上就是 SpringBoot 整合 Minio 和 FastDFS 的完整实现方案,您可以根据实际需求选择适合的文件存储方案。