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

相关推荐
间彧5 小时前
CAS技术原理与应用详解
后端
华仔啊5 小时前
35岁程序员失业了,除了送外卖,还能做什么?
前端·后端·程序员
big狼王5 小时前
SonarQube本地化搭建及代码检测并导出报告PDF
java·pdf·sonarqube·sonarscanner
杨筱毅5 小时前
【Android】Handler/Looper机制相关的类图和流程图
android·java·流程图
SimonKing5 小时前
【开发者必备】Spring Boot 2.7.x:WebMvcConfigurer配置手册来了(二)!
java·后端·程序员
程序员飞哥5 小时前
别再说“对接接口没技术含量了”,这才是高手的打开方式!
后端·程序员
DokiDoki之父5 小时前
Spring—容器
java·后端·spring
一个龙的传说5 小时前
springboot优雅停止的流程梳理
java·spring boot·rpc
摇滚侠5 小时前
Spring Boot 3零基础教程,WEB 开发 国际化 Spring Boot + Thymeleaf 笔记45
spring boot·笔记·后端