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

相关推荐
Croa-vo1 天前
Tesla Spring 2026 Co-op 面经:CodeSignal真题解析与通关攻略
java·后端·spring
C***u1761 天前
【springboot】Spring 官方抛弃了 Java 8!新idea如何创建java8项目
java·spring boot·spring
CodeAmaz1 天前
Spring 事务失效的 8 种常见场景总结
java·spring·事务失效
隔山打牛牛1 天前
Spring 整合 MyBatis 深度详解(原理 + 实操 + 源码级解析)
java·spring·mybatis
华仔啊1 天前
SpringBoot 动态菜单权限系统设计的企业级解决方案
java·后端
S***q3771 天前
Java进阶-在Ubuntu上部署SpringBoot应用
java·spring boot·ubuntu
棋啊_Rachel1 天前
Spring Boot深度解析:从零开始构建企业级应用
java·spring boot·后端
小王不爱笑1321 天前
代码生成器
java·mybatis
计算机毕设小月哥1 天前
【Hadoop+Spark+python毕设】中式早餐店订单数据分析与可视化系统、计算机毕业设计、包括数据爬取、数据分析、数据可视化
后端·python
Slow菜鸟1 天前
Java开发规范(五)| 接口设计规范—前后端/跨服务协作的“架构级契约”
java·状态模式·设计规范