如果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项目,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端口:
登录之后我们来看看当前的桶,当前没有桶:
然后我们启动一下Java程序,上传一个图像文件试试,可以看到上传成功:
根据这个文件的uuid调用文件下载接口下载一下文件:
download的值为1则代表下载:
文件系统当中也有这个文件的桶了: