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

本文将详细介绍如何在 SpringBoot 项目中整合 Minio 和 FastDFS 两种分布式文件存储方案,包括环境配置、依赖添加、功能实现等内容。

一、SpringBoot 整合 Minio

1. Minio 简介

Minio 是一款高性能的对象存储服务,兼容 Amazon S3 接口,具有以下优势:

  • 部署简单,支持单机和分布式模式
  • 高性能,专为对象存储设计
  • 功能丰富,支持标准 S3 协议
  • 支持分布式存储,具备高扩展性和高可用性

2. 安装 Minio (Docker 方式)

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 添加依赖
<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)
vehicle:
  minio:
    url: http://localhost:9000  # 连接地址
    username: minio  # 登录用户名
    password: minio123  # 登录密码
    bucketName: vehicle  # 存储文件的桶名称
3.3 配置类
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 工具类
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 控制器实现
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 添加依赖
<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>1.26.7</version>
</dependency>
2.2 FastDFS 配置类
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)
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 工具类
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 控制器实现
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 对比

特性MinioFastDFS
协议支持支持标准 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 的完整实现方案,您可以根据实际需求选择适合的文件存储方案。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]