SpringBoot整合minio分布式文件系统

  • 如果SpringBoot搭建还有问题,可以参考我的文章:https://blog.csdn.net/m0_51510236/article/details/113967278

  • minio官方网站:https://min.io/

  • 程序包可以官网下载,本次演示的版本下载:https://download.csdn.net/download/m0_51510236/46165373

运行步骤

linux:

#!/bin/bash

MINIO_ROOT_USER=xiaohh MINIO_ROOT_PASSWORD=xiaohh1234 ./minio server /data/minio --console-address ":9001" >> /dev/null &

Windows:

setx MINIO_ROOT_USER xiaohh
setx MINIO_ROOT_PASSWORD xiaohh1234
minio.exe server D:\Data\minio --console-address ":9001"

注意 MINIO_ROOT_USER 后面的是用户名,而 MINIO_ROOT_PASSWORD 后面的是密码, --console-address 后面的是minio的控制台端口,minio默认程序端口为9000

SpringBoot项目

首先搭建SpringBoot项目,pom文件:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.12.RELEASEversion>
    parent>

    <groupId>love.xiaohhgroupId>
    <artifactId>xiaohh-minioartifactId>
    <version>1.0.0version>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    properties>

    
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>

        
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.3version>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        
        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.0.0version>
        dependency>

        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.78version>
        dependency>

        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
        dependency>
    dependencies>

    <build>
        <plugins>
            
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

重点导入的为:


<dependency>
    <groupId>io.miniogroupId>
    <artifactId>minioartifactId>
    <version>8.0.0version>
dependency>

启动类代码:

package love.xiaohh.minio;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 

* XiaoHHMinioApplication 模块启动类 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:21:54 * @file XiaoHHMinioApplication.java */
@SpringBootApplication public class XiaoHHMinioApplication { public static void main(String[] args) { // 启动文件系统 SpringApplication.run(XiaoHHMinioApplication.class, args); System.out.println("^V^ 文件系统启动成功 ^V^\n" + "/-\\ /-\\ /-\\ /-\\ /-\\ /-\\\n" + " X i a o H H\n" + "\\-/ \\-/ \\-/ \\-/ \\-/ \\-/"); } }

一些工具类代码:R.java(返回给前端的响应对象)

package love.xiaohh.minio.utils.http;

import love.xiaohh.minio.constants.BizCodeEnum;

import java.io.Serializable;

/**
 * 

* 响应对象 *

* * @author XiaoHH * @version 1.0 * @date 2021-08-21 星期六 16:08:28 * @file R.java */
public class R<T> implements Serializable { /** * 只允许内部实例化 */ private R() { } /** * 消息状态码 */ private int code; /** * 消息内容 */ private String message; /** * 响应内容 */ private T data; public int getCode() { return code; } public String getMessage() { return message; } public T getData() { return data; } public void setData(T data) { this.data = data; } /** * 创建一个响应对象 * * @param data 响应数据 * @return 响应内容 */ public static <T> R<T> success(T data) { R<T> r = new R<T>(); r.code = BizCodeEnum.SUCCESS.getCode(); r.message = "success"; r.data = data; return r; } /** * 创建一个响应对象 * * @return 响应内容 */ public static <T> R<T> success() { return success("success"); } /** * 创建一个响应对象 * * @param message 消息内容 * @return 响应内容 */ public static <T> R<T> success(String message) { return success(BizCodeEnum.SUCCESS.getCode(), message); } /** * 创建一个响应对象 * * @param code 状态码 * @param message 消息内容 * @return 响应内容 */ public static <T> R<T> success(int code, String message) { R<T> r = new R<T>(); r.code = code; r.message = message; return r; } /** * 创建一个响应对象 * * @return 响应内容 */ public static <T> R<T> error() { return error(BizCodeEnum.SUCCESS.getCode(), "error"); } /** * 创建一个响应对象 * * @param message 消息内容 * @return 响应内容 */ public static <T> R<T> error(String message) { return error(BizCodeEnum.SUCCESS.getCode(), message); } /** * 创建一个响应对象 * * @param code 状态码 * @param message 消息内容 * @return 响应内容 */ public static <T> R<T> error(int code, String message) { R<T> r = new R<T>(); r.code = code; r.message = message; return r; } /** * 创建一个错误信息 * * @param bizCodeEnum 错误信息的对象 * @return 错误信息 */ public static <T> R<T> error(BizCodeEnum bizCodeEnum) { R<T> r = new R<T>(); r.code = bizCodeEnum.getCode(); r.message = bizCodeEnum.getMessage(); return r; } /** * 创建一个错误信息 * * @param bizCodeEnum 错误信息的对象 * @return 错误信息 */ public static R<Object> error(Object data, BizCodeEnum bizCodeEnum) { R<Object> r = new R<Object>(); r.code = bizCodeEnum.getCode(); r.message = bizCodeEnum.getMessage(); r.data = data; return r; } }

BizCodeEnum:

package love.xiaohh.minio.constants;

/**
 * 

* 异常统一声明 *

* * @author XiaoHH * @version 1.0 * @date 2021-08-21 星期六 16:19:00 * @file BizCodeEnum.java */
public enum BizCodeEnum { /** * 成功 */ SUCCESS(200, "操作成功"), /** * 未知异常 */ UNKNOWN_EXCEPTION(10000, "系统未知异常"), /** * 参数校验异常 */ VALIDATE_EXCEPTION(10001, "参数格式校验失败"), /** * 数据完整性异常 */ DATA_INTEGRITY_VIOLATION_EXCEPTION(10002, "数据完整性校验失败"), /** * SQL 语句错误 */ BAD_SQL_GRAMMAR_EXCEPTION(10003, "SQL 语句错误"), /** * 客户端异常 */ CUSTOMER_EXCEPTION(10004, null), /** * 图形验证码异常 */ CAPTCHA_EXCEPTION(10005, null); /** * 错误码 */ private final int code; /** * 错误信息 */ private final String message; /** * @param code 错误码 * @param message 错误信息 */ BizCodeEnum(int code, String message) { this.code = code; this.message = message; } /** * 获取错误码 * * @return 错误码 */ public int getCode() { return code; } /** * 获取错误信息 * * @return 错误信息 */ public String getMessage() { return message; } }

FileUtils:

package love.xiaohh.minio.utils.file;

import love.xiaohh.minio.utils.StringUtils;

/**
 * 

* 文件相关的工具类 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 10:02:17 * @file FileUtils.java */
public class FileUtils { /** * 不允许被实例化 */ private FileUtils() { } /** * 返回文件的扩展名 * * @param filename 文件的名称 * @return 扩展名 */ public static String getExtension(String filename) { if (StringUtils.isNotEmpty(filename)) { int index = filename.lastIndexOf('.'); if (index > 0) return filename.substring(index); else return StringUtils.NULL_STR; } else return null; } /** * 根据文件类型获取文件扩展名,如 image/png 则返回 .png * * @param contentType 文件类型 * @return 文件扩展名 */ public static String getExtensionByContentType(String contentType) { if (StringUtils.isNotEmpty(contentType)) { int index = contentType.indexOf("/"); if (index > 0) return "." + contentType.substring(index + 1); else return StringUtils.NULL_STR; } else return null; } /** * 判断一个内容类型是不是图片类型 * * @param contentType 内容类型 * @return 判断结果 */ public static boolean isImage(String contentType) { if (StringUtils.isEmpty(contentType)) return false; return contentType.matches("^image/[a-zA-Z]*$"); } }

StringUtils:

package love.xiaohh.minio.utils;

import java.io.*;
import java.util.Set;

/**
 * 

* 字符串的工具类 *

* * @author XiaoHH * @version 1.0 * @date 2021-08-21 星期六 15:58:44 * @file StringUtils.java */
public class StringUtils extends org.apache.commons.lang3.StringUtils { /** * 空字符串 */ public static final String NULL_STR = ""; /** * 不允许被实例化 */ private StringUtils() { } /** * 将对象经行toString操作,但可以避免空指针异常 * * @param o 需要被转换为字符串的对象 * @return 转换结果 */ public static String toString(Object o) { if (null == o) return null; return o.toString(); } /** * 判断字符串是否为空 * * @param s 需要被判断的字符串 * @return 是否为空 */ public static boolean isEmpty(String s) { if (null == s) return true; return s.trim().length() == 0; } /** * 判断字符串是否不为空 * * @param s 需要被判断的字符串 * @return 是否不为空 */ public static boolean isNotEmpty(String s) { return !isEmpty(s); } }

Base64.java(主要用decode方法,将base64的图片转换为二进制格式的数组)

package love.xiaohh.minio.utils.sign;

/**
 * 

* Base64工具类 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 10:29:54 * @file Base64.java */
public final class Base64 { static private final int BASELENGTH = 128; static private final int LOOKUPLENGTH = 64; static private final int FOURBYTE = 4; static private final char PAD = '='; static final private byte[] base64Alphabet = new byte[BASELENGTH]; static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; static { for (int i = 0; i < BASELENGTH; ++i) { base64Alphabet[i] = -1; } for (int i = 'Z'; i >= 'A'; i--) { base64Alphabet[i] = (byte) (i - 'A'); } for (int i = 'z'; i >= 'a'; i--) { base64Alphabet[i] = (byte) (i - 'a' + 26); } for (int i = '9'; i >= '0'; i--) { base64Alphabet[i] = (byte) (i - '0' + 52); } base64Alphabet['+'] = 62; base64Alphabet['/'] = 63; for (int i = 0; i <= 25; i++) { lookUpBase64Alphabet[i] = (char) ('A' + i); } for (int i = 26, j = 0; i <= 51; i++, j++) { lookUpBase64Alphabet[i] = (char) ('a' + j); } for (int i = 52, j = 0; i <= 61; i++, j++) { lookUpBase64Alphabet[i] = (char) ('0' + j); } lookUpBase64Alphabet[62] = (char) '+'; lookUpBase64Alphabet[63] = (char) '/'; } private static boolean isWhiteSpace(char octect) { return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); } private static boolean isPad(char octect) { return (octect == PAD); } private static boolean isData(char octect) { return (octect >= BASELENGTH || base64Alphabet[octect] == -1); } /** * Decodes Base64 data into octects * * @param encoded string containing Base64 data * @return Array containind decoded data. */ public static byte[] decode(String encoded) { if (encoded == null) { return null; } char[] base64Data = encoded.toCharArray(); // remove white spaces int len = removeWhiteSpace(base64Data); if (len % FOURBYTE != 0) { return null;// should be divisible by four } int numberQuadruple = (len / FOURBYTE); if (numberQuadruple == 0) { return new byte[0]; } byte[] decodedData; byte b1, b2, b3, b4; char d1, d2, d3, d4; int i = 0; int encodedIndex = 0; int dataIndex = 0; decodedData = new byte[(numberQuadruple) * 3]; for (; i < numberQuadruple - 1; i++) { if (isData((d1 = base64Data[dataIndex++])) || isData((d2 = base64Data[dataIndex++])) || isData((d3 = base64Data[dataIndex++])) || isData((d4 = base64Data[dataIndex++]))) { return null; } // if found "no data" just return null b1 = base64Alphabet[d1]; b2 = base64Alphabet[d2]; b3 = base64Alphabet[d3]; b4 = base64Alphabet[d4]; decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); } if (isData((d1 = base64Data[dataIndex++])) || isData((d2 = base64Data[dataIndex++]))) { return null;// if found "no data" just return null } b1 = base64Alphabet[d1]; b2 = base64Alphabet[d2]; d3 = base64Data[dataIndex++]; d4 = base64Data[dataIndex++]; if (isData((d3)) || isData((d4))) {// Check if they are PAD characters if (isPad(d3) && isPad(d4)) { if ((b2 & 0xf) != 0)// last 4 bits should be zero { return null; } byte[] tmp = new byte[i * 3 + 1]; System.arraycopy(decodedData, 0, tmp, 0, i * 3); tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); return tmp; } else if (!isPad(d3) && isPad(d4)) { b3 = base64Alphabet[d3]; if ((b3 & 0x3) != 0)// last 2 bits should be zero { return null; } byte[] tmp = new byte[i * 3 + 2]; System.arraycopy(decodedData, 0, tmp, 0, i * 3); tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); return tmp; } else { return null; } } else { // No PAD e.g 3cQl b3 = base64Alphabet[d3]; b4 = base64Alphabet[d4]; decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); } return decodedData; } /** * remove WhiteSpace from MIME containing encoded Base64 data. * * @param data the byte array of base64 data (with WS) * @return the new length */ private static int removeWhiteSpace(char[] data) { if (data == null) { return 0; } // count characters that's not whitespace int newSize = 0; int len = data.length; for (int i = 0; i < len; i++) { if (!isWhiteSpace(data[i])) { data[newSize++] = data[i]; } } return newSize; } }

然后开始对接minio的编码,首先我们建立一个数据库,用于存放文件信息:

DROP DATABASE IF EXISTS `minio_file`;
CREATE DATABASE `minio_file`;
USE `minio_file`;

CREATE TABLE `file_store` (
    `uuid` varchar(60) NOT NULL COMMENT '文件的UUID',
    `bucket` varchar(50) NOT NULL COMMENT '文件的桶名称',
    `object_name` varchar(255) NOT NULL COMMENT '文件的对象名称',
    `file_name` varchar(255) NOT NULL COMMENT '文件名称',
    PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文件属性存储表';

还有其对应的实体类:

package love.xiaohh.minio.entities;

import com.alibaba.fastjson.JSON;

import java.io.Serializable;

/**
 * 

* 文件属性存储实体 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:47:26 * @file FileStore.java */
public class FileStore implements Serializable { /** * 无参构造 */ public FileStore() { } /** * 全参构造 * * @param uuid 文件的UUID * @param bucket 文件的桶名称 * @param objectName 文件的对象名称 * @param fileName 文件名称 */ public FileStore(String uuid, String bucket, String objectName, String fileName) { this.uuid = uuid; this.bucket = bucket; this.objectName = objectName; this.fileName = fileName; } /** * 文件的UUID */ private String uuid; /** * 文件的桶名称 */ private String bucket; /** * 文件的对象名称 */ private String objectName; /** * 文件名称 */ private String fileName; public String getUuid() { return uuid; } public FileStore setUuid(String uuid) { this.uuid = uuid; return this; } public String getBucket() { return bucket; } public FileStore setBucket(String bucket) { this.bucket = bucket; return this; } public String getObjectName() { return objectName; } public FileStore setObjectName(String objectName) { this.objectName = objectName; return this; } public String getFileName() { return fileName; } public FileStore setFileName(String fileName) { this.fileName = fileName; return this; } /** * 将本对象转换为json格式 * * @return 转换后的json */ @Override public String toString() { return JSON.toJSONString(this); } }

然后我们建立一个类来存储minio的一些配置:

package love.xiaohh.minio.config;

import com.alibaba.fastjson.JSON;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * 

* minio的属性配置类 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:30:31 * @file MinioProperties.java */
@Configuration @ConfigurationProperties(prefix = "minio") public class MinioProperties { /** * minio的主机地址 */ private String host; /** * minio的端口 */ private int port = 9000; /** * minio的用户名 */ private String accessKey; /** * minio的密码 */ private String secretKey; /** * 是否是https安全的链接 */ private boolean secure; public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public boolean isSecure() { return secure; } public void setSecure(boolean secure) { this.secure = secure; } /** * 将本对象转换为json格式 * * @return 转换后的json */ @Override public String toString() { return JSON.toJSONString(this); } }

然后我们就可以在 application.yml 当中配置了:

# 文件系统的配置
minio:
  host: 127.0.0.1
  port: 9000
  accessKey: xiaohh
  secretKey: xiaohh1234
  secure: false

整个程序的配置:

server:
  # 端口
  port: 8080

spring:
  datasource:
    # 数据库配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/minio_file?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: xiaohh
  redis:
    # 缓存配置
    host: 127.0.0.1
    port: 6379

# 打印SQL日志
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: love.xiaohh.minio.entities
  mapper-locations: classpath:mapper/*Mapper.xml

# 日志设置
logging:
  level:
    root: info

# 文件系统的配置
minio:
  host: 127.0.0.1
  port: 9000
  accessKey: xiaohh
  secretKey: xiaohh1234
  secure: false

我们现在新建一个业务接口,用于与minio对接:

package love.xiaohh.minio.services;

import io.minio.GetObjectResponse;
import io.minio.StatObjectResponse;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;

/**
 * 

* 文件系统的服务层 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:34:50 * @file MinioService.java */
public interface MinioService { /** * 上传一个文件 * * @param bucket 桶名字 * @param objectName 对象名字 * @param file 文件 */ void putObject(String bucket, String objectName, MultipartFile file); /** * 上传一个文件 * * @param bucket 桶名字 * @param objectName 对象名字 * @param contentType 文件类型 * @param inputStream 输入流 */ void putObject(String bucket, String objectName, String contentType, InputStream inputStream); /** * 获取一个文件对象 * * @param bucket 桶名称 * @param objectName 对象名称 * @return 输入流 */ GetObjectResponse getObject(String bucket, String objectName); /** * 获取文件状态 * * @param bucket 桶名称 * @param objectName 对象名称 * @return 获取文件状态 */ StatObjectResponse getStatObject(String bucket, String objectName); /** * 在分布式文件系统当中删除一个文件 * * @param bucketName 同名称 * @param objectName 对象名称 */ void removeObject(String bucketName, String objectName); }

然后在实现类中导入minio的配置并且实现链接,minio的链接获取方式是构建者模式,创建的代码(MinioProperties是实现写好的配置类对象):

/**
 * minio文件系统的客户端
 */
private final MinioClient minioClient;

/**
 * 初始化文件系统
 *
 * @param minioConfig 文件系统的配置
 */
public MinioServiceImpl(MinioProperties minioConfig) {
    this.minioClient = MinioClient.builder()
            .endpoint(minioConfig.getHost(), minioConfig.getPort(), false)
            .credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build();
}

在这之前讲一下 对象 的简单概念。桶不用多说,就是存储对象的容器,而对象说白了就是文件,一个文件只能在一个桶里,但是一个桶可以有多个文件。同时桶可以有多个,用于存储不同的文件,接下来我们来看看上面的这些接口如何实现:

package love.xiaohh.minio.services.impl;

import io.minio.*;
import io.minio.errors.*;
import love.xiaohh.minio.config.MinioProperties;
import love.xiaohh.minio.services.MinioService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * 

* 文件系统的服务层实现 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:35:42 * @file MinioServiceImpl.java */
@Service public class MinioServiceImpl implements MinioService { /** * 记录日志的工具 */ private static final Logger log = LoggerFactory.getLogger(MinioServiceImpl.class); /** * minio文件系统的客户端 */ private final MinioClient minioClient; /** * 初始化文件系统 * * @param minioConfig 文件系统的配置 */ public MinioServiceImpl(MinioProperties minioConfig) { this.minioClient = MinioClient.builder() .endpoint(minioConfig.getHost(), minioConfig.getPort(), false) .credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build(); } /** * 上传一个文件 * * @param bucket 桶名字 * @param objectName 对象名字 * @param file 文件 */ @Override public void putObject(String bucket, String objectName, MultipartFile file) { try { this.putObject(bucket, objectName, file.getContentType(), file.getInputStream()); } catch (IOException e) { log.error("上传文件时出错", e); } } /** * 上传一个文件 * * @param bucket 桶名字 * @param objectName 对象名字 * @param contentType 文件类型 * @param inputStream 输入流 */ @Override public void putObject(String bucket, String objectName, String contentType, InputStream inputStream) { try { // 判断桶是否存在,不存在则创建 BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucket).build(); if (!this.minioClient.bucketExists(bucketExistsArgs)) { synchronized (this) { if (!this.minioClient.bucketExists(bucketExistsArgs)) { MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucket).build(); this.minioClient.makeBucket(makeBucketArgs); } } } // 存入分布式文件系统 PutObjectArgs putObjectArgs = PutObjectArgs.builder() .bucket(bucket).object(objectName).contentType(contentType) .stream(inputStream, inputStream.available(), -1).build(); this.minioClient.putObject(putObjectArgs); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidResponseException | InvalidKeyException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { log.error("上传文件时出错", e); } } /** * 获取一个文件对象 * * @param bucket 桶名称 * @param objectName 对象名称 * @return 输入流 */ @Override public GetObjectResponse getObject(String bucket, String objectName) { GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucket).object(objectName).build(); try { return this.minioClient.getObject(getObjectArgs); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { log.error("获取一个文件对象时出错", e); } return null; } /** * 获取文件状态 * * @param bucket 桶名称 * @param objectName 对象名称 * @return 获取文件状态 */ @Override public StatObjectResponse getStatObject(String bucket, String objectName) { try { StatObjectArgs statObjectArgs = StatObjectArgs.builder().bucket(bucket).object(objectName).build(); return this.minioClient.statObject(statObjectArgs); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { log.error("获取文件状态时出错", e); return null; } } /** * 在分布式文件系统当中删除一个文件 * * @param bucketName 同名称 * @param objectName 对象名称 */ @Override public void removeObject(String bucketName, String objectName) { try { this.minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { log.error("在分布式文件系统当中删除一个文件时出错", e); } } }

大部分都是用构建者模式,具体说明注释当中有,现在我们写几个接口来测试一下(增加、查询、删除的mybatis代码忽略)。

Controller:

package love.xiaohh.minio.controllers;

import love.xiaohh.minio.entities.FileStore;
import love.xiaohh.minio.entities.dto.Base64ImageUploadDTO;
import love.xiaohh.minio.services.FileStoreService;
import love.xiaohh.minio.utils.http.R;
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.sql.SQLException;

/**
 * 

* 文件存取的前端控制器 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:39:39 * @file FileStoreController.java */
@RestController @RequestMapping("/fileStore") public class FileStoreController { @Autowired private FileStoreService fileStoreService; /** * 上传一个文件 * * @param file 文件对象 * @return 上传成功之后会返回文件的信息 * @throws SQLException 与数据库交互的时候可能抛出的数据库异常 */ @PostMapping("/upload") public R<FileStore> upload(@RequestParam("file") MultipartFile file) throws SQLException { return R.success(this.fileStoreService.upload(file)); } /** * 上传base64格式的图片的接口 * * @param base64ImageUpload base64格式的图片的传输对象 * @return 上传成功之后会返回文件的信息 * @throws IOException 会因为解析base64、上传文件而抛出io异常 */ @PostMapping("/base64") public R<FileStore> base64(@RequestBody Base64ImageUploadDTO base64ImageUpload) throws IOException { return R.success(this.fileStoreService.uploadBase64(base64ImageUpload)); } /** * 下载文件时候的uuid * * @param download 是否下载;0=在线预览,1=下载 * @param uuid 文件的uuid * @param response 响应对象,由Spring向方法里面传递 * @throws IOException 可能会抛出输入输出流异常 */ @GetMapping("/download/{download}/{uuid}") public void download(@PathVariable("download") Integer download, @PathVariable("uuid") String uuid, HttpServletResponse response) throws IOException { this.fileStoreService.download(download, uuid, response); } /** * 删除一个文件 * * @param uuid 文件的uuid * @return 是否成功 */ @DeleteMapping("/{uuid}") public R<Void> remove(@PathVariable("uuid") String uuid) { this.fileStoreService.removeFileByUuid(uuid); return R.success(); } }

桶名称常量类:

package love.xiaohh.minio.constants;

/**
 * 

* minio文件系统的常量类 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:41:39 * @file MinioConstant.java */
public class MinioConstant { /** * 不允许被实例化 */ private MinioConstant() { } /** * 用户存储图片的同名称 */ public static final String IMAGE_BUCKET = "image-bucket"; /** * 存储普通文件的同名称 */ public static final String FILE_BUCKET = "file-bucket"; }

业务层接口:

package love.xiaohh.minio.services;

import love.xiaohh.minio.entities.FileStore;
import love.xiaohh.minio.entities.dto.Base64ImageUploadDTO;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;

/**
 * 

* 文件存取的服务层 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:51:33 * @file FileStoreService.java */
public interface FileStoreService { /** * 上传普通文件 * * @param file 普通文件 * @return 文件对象 */ FileStore upload(MultipartFile file) throws SQLException; /** * 上传base64格式的图片 * * @param base64ImageUpload base64格式图片的数据传输对象 * @return 文件对象 */ FileStore uploadBase64(Base64ImageUploadDTO base64ImageUpload) throws IOException; /** * 下载或者在线预览文件 * * @param download 是否下载;0=在线预览,1=下载 * @param uuid 文件的uuid */ void download(Integer download, String uuid, HttpServletResponse response) throws IOException; /** * 删除一个文件 * * @param uuid 文件的uuid */ void removeFileByUuid(String uuid); }

业务层接口实现:

package love.xiaohh.minio.services.impl;

import com.google.common.io.ByteStreams;
import io.minio.GetObjectResponse;
import io.minio.StatObjectResponse;
import love.xiaohh.minio.constants.MinioConstant;
import love.xiaohh.minio.entities.FileStore;
import love.xiaohh.minio.entities.dto.Base64ImageUploadDTO;
import love.xiaohh.minio.mapper.FileStoreMapper;
import love.xiaohh.minio.services.FileStoreService;
import love.xiaohh.minio.services.MinioService;
import love.xiaohh.minio.utils.file.FileUtils;
import love.xiaohh.minio.utils.sign.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 

* 文件存取的服务层实现 *

* * @author XiaoHH * @version 1.0 * @date 2021-11-21 星期日 9:58:44 * @file FileStoreServiceImpl.java */
@Service public class FileStoreServiceImpl implements FileStoreService { @Autowired private MinioService minioService; @Autowired private FileStoreMapper fileStoreMapper; @Autowired private RedisTemplate redisTemplate; /** * 文件目录的分隔符 */ private static final String FILE_SEPARATOR = "/"; /** * 文件在redis当中存储的前缀 */ private static final String REDIS_KEY_PREFIX = "CostFile:uuid:"; /** * 上传普通文件 * * @param file 普通文件 * @return 文件对象 */ @Override public FileStore upload(MultipartFile file) throws SQLException { // 文件的uuid String uuid = UUID.randomUUID().toString(); // 文件名 String originalFilename = file.getOriginalFilename(); // 存储桶 String bucket = FileUtils.isImage(file.getContentType()) ? MinioConstant.IMAGE_BUCKET : MinioConstant.FILE_BUCKET; // 生成对象名称 LocalDate localDate = LocalDate.now(); String objectName = localDate.getYear() + FILE_SEPARATOR + localDate.getMonthValue() + FILE_SEPARATOR + localDate.getDayOfMonth() + FILE_SEPARATOR + uuid + FileUtils.getExtension(originalFilename); // 文件存储对象 FileStore fileStore = new FileStore(uuid, bucket, objectName, originalFilename); int rows = this.fileStoreMapper.insertFileStore(fileStore); if (rows < 1) throw new SQLException("存储文件存储对象时失败"); // 将文件存入文件系统 this.minioService.putObject(bucket, objectName, file); // 将文件属性存入缓存 this.storeFile(fileStore); return fileStore; } /** * 上传base64格式的图片 * * @param base64ImageUpload base64格式图片的数据传输对象 * @return 文件对象 */ @Override public FileStore uploadBase64(Base64ImageUploadDTO base64ImageUpload) throws IOException { // 读取文件内容 String base64 = base64ImageUpload.getBase64(); // 文件类型 String contentType = base64.substring(5, base64.indexOf(";base64,")); // 提取base64字符串并转换为字节数组 base64 = base64.substring(base64.indexOf(",") + 1); byte[] decode = Base64.decode(base64); // 如果为空则代表转换错误 if (null == decode || decode.length == 0) { throw new IOException("传入base64格式错误"); } // 文件唯一标识 final String uuid = UUID.randomUUID().toString(); // 生成文件名 String fileName = uuid + FileUtils.getExtensionByContentType(contentType); // 生成对象名称 LocalDate localDate = LocalDate.now(); final String objectName = localDate.getYear() + FILE_SEPARATOR + localDate.getMonthValue() + FILE_SEPARATOR + localDate.getDayOfMonth() + FILE_SEPARATOR + fileName; // 生成文件属性存储实体 final FileStore fileStore = new FileStore(uuid, MinioConstant.IMAGE_BUCKET, objectName, fileName); // 将文件插入数据库 int rows = this.fileStoreMapper.insertFileStore(fileStore); if (rows < 1) throw new IOException("哎呀,累趴了,请稍后再试"); // 将字节数组转换为输入流并上传文件系统 InputStream inputStream = new ByteArrayInputStream(decode); this.minioService.putObject(MinioConstant.IMAGE_BUCKET, objectName, contentType, inputStream); // 将文件对象存入缓存 storeFile(fileStore); return fileStore; } /** * 下载或者在线预览文件 * * @param download 是否下载;0=在线预览,1=下载 * @param uuid 文件的uuid */ @Override public void download(Integer download, String uuid, HttpServletResponse response) throws IOException { // 文件信息 FileStore fileStore = this.getFileStore(uuid); if (null == fileStore) { response.setStatus(HttpStatus.NOT_FOUND.value()); throw new IOException("您的文件离家出走了"); } // 在minio中获取文件信息 final GetObjectResponse minioFileSteam = this.minioService.getObject(fileStore.getBucket(), fileStore.getObjectName()); final StatObjectResponse statObject = this.minioService.getStatObject(fileStore.getBucket(), fileStore.getObjectName()); if (null == minioFileSteam || null == statObject) { response.setStatus(HttpStatus.NOT_FOUND.value()); throw new IOException("您的文件离家出走了"); } // 设置响应属性 response.setContentType(statObject.contentType()); // 设置响应文件的大小 response.setContentLength((int) statObject.size()); // 如果是下载,那么添加下载的文件名 if (download == 1) response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileStore.getFileName(), "UTF-8")); // 输出文件 ServletOutputStream outputStream = response.getOutputStream(); ByteStreams.copy(minioFileSteam, outputStream); outputStream.flush(); outputStream.close(); response.flushBuffer(); minioFileSteam.close(); } /** * 删除一个文件 * * @param uuid 文件的uuid */ @Override public void removeFileByUuid(String uuid) { // 文件信息 FileStore fileStore = this.getFileStore(uuid); if (null == fileStore) { return; } // 在文件系统中删除文件 this.minioService.removeObject(fileStore.getBucket(), fileStore.getObjectName()); // 从缓存中删除文件信息 this.removeFileOnCache(uuid); // 从数据库中删除文件信息 this.fileStoreMapper.deleteFileStore(uuid); } /** * 将文件对象存入缓存中 * * @param fileStore 文件属性存储对象 */ private void storeFile(FileStore fileStore) { this.redisTemplate.opsForValue().set(REDIS_KEY_PREFIX + fileStore.getUuid(), fileStore, 1L, TimeUnit.HOURS); } /** * 在缓存中删除对应uuid文件的缓存 * * @param uuid 文件的uuid */ private void removeFileOnCache(String uuid) { this.redisTemplate.delete(REDIS_KEY_PREFIX + uuid); } /** * 根据文件的uuid找出文件的属性存储实体 * * @param uuid 属性存储实体的uuid * @return 属性存储实体 */ private FileStore getFileStore(String uuid) { Object fileObject = this.redisTemplate.opsForValue().get(REDIS_KEY_PREFIX + uuid); if (null != fileObject) { return (FileStore) fileObject; } else { FileStore fileStore = this.fileStoreMapper.selectFileStoreByUuid(uuid); this.storeFile(fileStore); return fileStore; } } }

(Mapper代码忽略,可去代码仓库查看)

现在我们启动一下minio文件系统,启动命令在文章的开始有,然后访问服务器的9001端口:

SpringBoot整合minio分布式文件系统_第1张图片

登录之后我们来看看当前的桶,当前没有桶:

SpringBoot整合minio分布式文件系统_第2张图片

然后我们启动一下Java程序,上传一个图像文件试试,可以看到上传成功:

SpringBoot整合minio分布式文件系统_第3张图片

根据这个文件的uuid调用文件下载接口下载一下文件:

SpringBoot整合minio分布式文件系统_第4张图片

download的值为1则代表下载:

SpringBoot整合minio分布式文件系统_第5张图片

文件系统当中也有这个文件的桶了:

SpringBoot整合minio分布式文件系统_第6张图片

你可能感兴趣的:(框架,文件系统,spring,boot,spring)