SpringBoot 整合FastDFS/MinIO分布式文件系统

一、FastDFS

  1. Maven依赖

<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>1.26.5</version>
</dependency>

  2. 配置application.yml文件

fdfs:
  # 链接超时
  connect-timeout: 60
  # 读取时间
  so-timeout: 60
  # 生成缩略图参数
  thumb-image:
    width: 150
    height: 150
  tracker-list: 127.0.0.1:22122

  3. FastDFS配置类

@Configuration
@Import(FdfsClientConfig.class)
// Jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class DfsConfig {
}

   可参考:https://blog.51cto.com/14439672/2434896

 

二、MinIO

  简介:MinIO是一个非常轻量的对象存储服务,非常适合存储非结构化的数据,比如图片、视频、日志文件、容器镜像等,文件最大是5TB,这对一般的文件存储足够了,而且功能强大,还提供了可视化界面。

  1. pom.xml Maven依赖

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.0.3</version>
</dependency>

  2. application.yml 配置文件

minio:
    endpoint: http://localhost:9000
    access-key: minio
    secret-key: minio

   3. 配置类

复制代码
package com.ruhuanxingyun.minio.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @description: Minio文件服务参数
 * @author: ruphie
 * @date: Create in 2020/8/26 22:03
 * @company: ruhuanxingyun
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {

    /**
     * 对象存储服务的URL
     */
    private String endpoint;

    /**
     * 用户ID,可以唯一标识的账户
     */
    private String accessKey;

    /**
     * 账户的密码
     */
    private String secretKey;

}
复制代码

  4. 配置类

复制代码
package com.ruhuanxingyun.minio.config;

import com.ruhuanxingyun.minio.common.Constants;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @description: Minio客户端
 * @author: ruphie
 * @date: Create in 2020/8/26 22:08
 * @company: ruhuanxingyun
 */
@Configuration
public class MinioClientConfig {

    @Autowired
    private MinioProperties minioProperties;

    @Bean
    public MinioClient minioClient() {
        MinioClient.Builder builder = MinioClient.builder();
        MinioClient minioClient = builder.endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();

        try {
            for (String bucket : Constants.BUCKETS) {
                BucketExistsArgs args = BucketExistsArgs.builder()
                        .bucket(bucket)
                        .build();
                // 检查存储桶是否存在
                if (!minioClient.bucketExists(args)) {
                    MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder()
                            .bucket(bucket)
                            .build();
                    // 创建一个新的存储桶
                    minioClient.makeBucket(makeBucketArgs);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return minioClient;
    }

}
复制代码

  5. 文件服务类

复制代码
package com.ruhuanxingyun.minio.service;

import cn.hutool.core.util.StrUtil;
import com.ruhuanxingyun.minio.common.Constants;
import com.ruhuanxingyun.minio.util.FileUtils;
import io.minio.MinioClient;
import io.minio.ObjectWriteArgs;
import io.minio.PutObjectArgs;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;

/**
 * @description: 上传文件 服务层
 * @author: ruphie
 * @date: Create in 2020/8/26 22:15
 * @company: ruhuanxingyun
 */
@Service
public class FileUploadService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 上传文件
     *
     * @param file    文件
     * @param fileMd5 文件MD5值
     * @return 上传结果
     */
    public String upload(MultipartFile file, String fileMd5) {
        String md5 = FileUtils.getFileMd5(file);
        // 前端兼容MD5值大小写,设备端传递小写
        if (!StrUtil.equalsIgnoreCase(fileMd5, md5)) {
            return "上传文件MD5值不匹配!";
        }

        InputStream is = null;
        try {
            is = file.getInputStream();
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(Constants.BUCKETS[0])
                    .object(file.getOriginalFilename())
                    .stream(is, file.getSize(), ObjectWriteArgs.MAX_PART_SIZE)
                    .contentType(file.getContentType())
                    .build();
            // 通过InputStream上传对象
            minioClient.putObject(putObjectArgs);
        } catch (Exception e) {
            e.printStackTrace();

            return "文件上传失败!";
        } finally {
            IOUtils.closeQuietly(is);
        }

        return "文件上传成功!";
    }

    /**
     * 重置文件上传进度
     *
     * @param uuid 唯一标识符
     */
    public void reset(String uuid) {
        String key = String.format("%s:%s", Constants.FILE_UPLOAD_PROGRESS, uuid);
        stringRedisTemplate.delete(key);
    }

    /**
     * 文件上传进度
     *
     * @param uuid 唯一标识符
     * @return 进度
     */
    public int progress(String uuid) {
        String key = String.format("%s:%s", Constants.FILE_UPLOAD_PROGRESS, uuid);
        String percent = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotEmpty(percent)) {
            // 上传完全
            if (StrUtil.equals("100", percent)) {
                stringRedisTemplate.delete(key);
            }

            return Integer.parseInt(percent);
        }

        return 0;
    }

}
复制代码
复制代码
package com.ruhuanxingyun.minio.service;

import com.ruhuanxingyun.minio.common.Constants;
import com.ruhuanxingyun.minio.util.FileUtils;
import io.minio.*;
import io.minio.http.Method;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;

/**
 * @description: 下载文件 服务层
 * @author: ruphie
 * @date: Create in 2020/12/8 21:46
 * @company: ruhuanxingyun
 */
@Service
public class FileDownloadService {

    @Autowired
    private MinioClient minioClient;

    /**
     * 下载文件URL
     *
     * @param filePath 文件路径
     * @return URL
     */
    public String downloadUrl(String filePath) {
        String bucketName = Constants.BUCKETS[0];
        String objectName = FileUtils.getObjectName(filePath, bucketName);
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(GetPresignedObjectUrlArgs.DEFAULT_EXPIRY_TIME)
                .build();

        try {
            return minioClient.getPresignedObjectUrl(args);
        } catch (Exception e) {
            e.printStackTrace();

            return null;
        }
    }

    /**
     * 下载文件
     *
     * @param filePath 文件路径
     * @param response 响应流
     */
    public void download(String filePath, HttpServletResponse response) {
        String bucketName = Constants.BUCKETS[0];
        String objectName = FileUtils.getObjectName(filePath, bucketName);
        StatObjectArgs args = StatObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build();
        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build();

        InputStream is = null;
        BufferedInputStream ins = null;
        BufferedOutputStream bos = null;
        try {
            // 获取对象的元数据
            ObjectStat objectStat = minioClient.statObject(args);
            is = minioClient.getObject(getObjectArgs);

            response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
            response.setHeader(HttpHeaders.PRAGMA, "no-cache");
            response.setDateHeader(HttpHeaders.EXPIRES, 0);
            response.setContentType(objectStat.contentType());
            response.setContentLengthLong(objectStat.length());
            response.setHeader("Content-Disposition", "attachment;filename=" + objectStat.name());

            ins = new BufferedInputStream(is);
            bos = new BufferedOutputStream(response.getOutputStream());
            byte[] buffer = new byte[1024 * 1024];
            int bytesRead = -1;
            while ((bytesRead = ins.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }

            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(bos);
            IOUtils.closeQuietly(ins);
            IOUtils.closeQuietly(is);
        }
    }
}
复制代码
复制代码
package com.ruhuanxingyun.minio.service;

import com.ruhuanxingyun.minio.common.Constants;
import com.ruhuanxingyun.minio.util.FileUtils;
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @description: 删除文件 服务层
 * @author: ruphie
 * @date: Create in 2020/12/8 21:49
 * @company: ruhuanxingyun
 */
@Service
public class FileDeleteService {

    @Autowired
    private MinioClient minioClient;

    /**
     * 删除文件
     *
     * @param filePath 文件路径
     * @return 删除状态
     */
    public String delete(String filePath) {
        String bucketName = Constants.BUCKETS[0];
        String objectName = FileUtils.getObjectName(filePath, bucketName);
        RemoveObjectArgs args = RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build();

        try {
            minioClient.removeObject(args);

            return "删除文件成功!";
        } catch (Exception e) {
            e.printStackTrace();

            return "删除文件失败!";
        }
    }

}
复制代码
复制代码
package com.ruhuanxingyun.minio.util;

import org.springframework.web.multipart.MultipartFile;

import java.math.BigInteger;
import java.security.MessageDigest;

/**
 * @description: 文件工具类
 * @author: ruphie
 * @date: Create in 2020/12/8 22:25
 * @company: ruhuanxingyun
 */
public class FileUtils {

    /**
     * 获取对象名
     *
     * @param filePath 文件路径
     * @param bucket   存储桶
     * @return 对象名
     */
    public static String getObjectName(String filePath, String bucket) {
        return filePath.replace(String.format("%s/", bucket), "");
    }

    /**
     * 获取文件MD5值
     *
     * @param file 文件
     * @return MD5
     */
    public static String getFileMd5(MultipartFile file) {
        try {
            byte[] bytes = file.getBytes();
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            byte[] digest = messageDigest.digest(bytes);

            return new BigInteger(1, digest).toString(16);
        } catch (Exception e) {
            e.printStackTrace();

            return null;
        }
    }

}
复制代码

   6. 断点续传:https://www.cnblogs.com/liyhbk/p/16810243.html

 

  可参考:MinIO官方文档

      SpringBoot通过Minio实现大文件分片上传

 

posted @ 2020-05-19 22:43  如幻行云  阅读(2334)  评论(0编辑  收藏  举报