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 的完整实现方案,您可以根据实际需求选择适合的文件存储方案。

相关推荐
装不满的克莱因瓶2 分钟前
【cursor】前后端分离项目下的AI跨工程管理方案
java·人工智能·ai·ai编程·cursor·trae·qoder
何中应5 分钟前
使用Spring自带的缓存注解维护数据一致性
java·数据库·spring boot·后端·spring·缓存
ZeroToOneDev6 分钟前
Mybatis
java·数据库·mybatis
步步为营DotNet7 分钟前
深度解读.NET中ConcurrentDictionary:高效线程安全字典的原理与应用
java·安全·.net
heartbeat..9 分钟前
Spring Boot 学习:原理、注解、配置文件与部署解析
java·spring boot·学习·spring
零度@9 分钟前
Java 消息中间件 - 云原生多租户:Pulsar 保姆级全解2026
java·开发语言·云原生
一路向北⁢10 分钟前
企业级敏感词拦截检查系统设计方案(Spring Boot)
spring boot·后端·bootstrap·敏感词·敏感词拦截
七夜zippoe11 分钟前
分布式事务解决方案(二) 消息队列实现最终一致性
java·kafka·消息队列·rocketmq·2pc
Honmaple11 分钟前
DeepSeek-OCR + AgentScope:打造私有化智能文档处理智能体
后端
野犬寒鸦12 分钟前
从零起步学习RabbitMQ || 第一章:认识消息队列及项目实战中的技术选型
java·数据库·后端