SpringBoot 整合 Minio 和 FastDFS 实现分布式文件存储

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 兼容的场景 传统分布式文件存储,对性能要求高的场景

四、选择建议

  1. 如果您的应用是云原生架构 ,或者需要与 AWS S3 等云存储服务兼容,建议选择 Minio

  2. 如果您需要一个稳定可靠的传统分布式文件系统 ,并且对部署复杂性要求不高,建议选择 FastDFS

  3. 如果您更关注部署的简便性,Minio 提供了更简单的部署方式,特别是通过 Docker。

  4. 如果您需要处理大量小文件或对文件存储性能有极高要求,FastDFS 可能更适合。

五、注意事项

  1. 在生产环境中,请确保配置适当的安全措施,如设置访问密钥、配置防火墙规则等。

  2. 对于高可用需求,Minio 和 FastDFS 都支持分布式部署,建议配置多个节点以提高可用性。

  3. 定期备份数据,防止数据丢失。

  4. 根据实际业务需求,合理配置文件大小限制和连接池参数。

以上就是 SpringBoot 整合 Minio 和 FastDFS 的完整实现方案,您可以根据实际需求选择适合的文件存储方案。

相关推荐
l0sgAi3 分钟前
SpringAI 整合MCP实现联网搜索 (基于tavily)
java·后端
朝新_4 分钟前
【统一功能处理】从入门到源码:拦截器学习指南(含适配器模式深度解读)
数据库·后端·mybatis·适配器模式·javaee
q***7487 分钟前
私有化部署DeepSeek并SpringBoot集成使用(附UI界面使用教程-支持语音、图片)
spring boot·后端·ui
❀͜͡傀儡师13 分钟前
使用DelayQueue 分布式延时队列,干掉定时任务!
java·分布式·delayqueue·spingboot
失散1317 分钟前
分布式专题——55 ElasticSearch性能调优最佳实践
java·分布式·elasticsearch·架构
Java水解20 分钟前
Spring WebFlux 核心操作符详解:map、flatMap 与 Mono 常用方法
后端·spring
Java水解22 分钟前
MySQL 慢查询 debug:索引没生效的三重陷阱
后端·mysql
jonyleek40 分钟前
【JVS更新日志】低代码、APS排产、物联网、企业计划11.12更新说明!
java·物联网·低代码·前端框架·团队开发
keke_俩个科1 小时前
实战派 JMeter 指南:核心功能、并发压测实操与常见问题解决方案
java·jmeter·spring·spring cloud·tomcat
青梅主码1 小时前
介绍一下我开发的一款新工具:函数图像绘制工具
后端