MinIO的安装与使用

MinIO的安装与使用
一、MinIO是什么?
二、MinIO安装(centos7)
2.1 下载MinIO
2.2 启动MinIO
2.3 修改配置
2.4 编写启动脚本,以及加入到systemctl中
三、Springboot集成MinIO
3.1 项目应用
四、Java中图片压缩上传
4.1 背景
4.2 开发准备
4.3 压缩上传
五、MinIO集群搭建(完善中~)
一、MinIO是什么?
MinIO 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。

MinIO与传统的存储和其他的对象存储不同的是:它一开始就针对性能要求更高的私有云标准进行软件架构设计。因为MinIO一开始就只为对象存储而设计。所以他采用了更易用的方式进行设计,它能实现对象存储所需要的全部功能,在性能上也更加强劲,它不会为了更多的业务功能而妥协,失去MinIO的易用性、高效性。 这样的结果所带来的好处是:它能够更简单的实现局有弹性伸缩能力的原生对象存储服务。

MinIO在传统对象存储用例(例如辅助存储,灾难恢复和归档)方面表现出色。同时,它在机器学习、大数据、私有云、混合云等方面的存储技术上也独树一帜。当然,也不排除数据分析、高性能应用负载、原生云的支持。

MinIO主要采用Golang语言实现,,客户端与存储服务器之间采用http/https通信协议。

它与 Amazon S3 云存储服务 API 兼容

MinIO的相关信息

中文官网: http://www.minio.org.cn/
中文文档: http://docs.minio.org.cn/docs/
中文下载地址:http://www.minio.org.cn/download.shtml#/linux
英文官网: https://min.io/
英文文档: https://docs.min.io/
英文下载地址:https://min.io/download#/linux
Github地址:https://github.com/minio/minio

二、MinIO安装(centos7)
2.1 下载MinIO
演示以官网下载二进制文件配置,下载地址见上方,,,,docker安装更简单哦~

#创建目录
mkdir /usr/local/minio/
cd /usr/local/minio/
#下载并添加权限
wget http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
#赋值权限
chmod +x minio
1
2
3
4
5
6
7
2.2 启动MinIO
#创建数据目录,数据目录存储需要大点
mkdir -p /home/data/minio
#创建日志目录
mkdir -p /home/data/minio/log
touch /home/data/minio/log/minio.log
#前台启动minio
./minio server /home/data/minio

#后台启动minio
nohup ./minio server /home/data/minio > /home/data/minio/log/minio.log &

# nohup端口自定义启动服务 指定文件存放路径 /home/data/minio 还有设置日志文件路径 /home/data/minio/log/minio.log
nohup ./minio server --address :9000 --console-address :9001 /home/data/minio > /home/data/minio/log/minio.log 2>&1 &
1
2
3
4
5
6
7
8
9
10
11
12
13
–address :9000 --console-address :9001 是配置端口,默认minio端口是9000,如果9000端口被占用了,那就加上这一串配置,端口号的冒号之前不需要特意写出ip,当然如果你的ip的动态变化的,而不是静态的话,前边的ip不用写上,当然最好是用静态的ip

 

关闭防火墙
如果未关闭,输入以下命令:

systemctl stop firewalld
1
注意:如果不想关闭防火墙,需要开放ip端口9000和设置静态控制访问IP

访问:http://192.168.92.100:9000查看控制台
这里我们都使用的默认配置账号密码 minioadmin:minioadmin

 

2.3 修改配置
这里账号和密码都是用的默认的,如果我们要修改可以在环境变量里设置
修改环境变量

打开 /etc/profile 文件

vim /etc/profile
1
在文件的最末尾加上以下信息(启动的时候看提示,新版本需要用MINIO_ROOT_USER和MINIO_ROOT_PASSWORD,旧版需要用MINIO_ACCESS_KEY和MINIO_SECRET_KEY)。

按 i 键后,在文档末尾输入
(1)新版:

export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=admin123
1
2
(2)旧版

export MINIO_ACCESS_KEY=minioadmin
export MINIO_SECRET_KEY=admin123
1
2
保存退出,刷新重载环境变量

source /etc/profile
1
2.4 编写启动脚本,以及加入到systemctl中
为了方便管理,我们这里给命令添加到脚本中

在/usr/local/minio/目录下新建run.sh

vim run.sh
1
然后将以下内容保存到run.sh,并为其赋予执行权限chmod +x run.sh

#!/bin/bash
#配置登陆账号密码
export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=admin123
# nohup启动服务 指定文件存放路径 /home/data/minio 还有设置日志文件路径 /home/data/minio/log/minio.log
nohup ./minio server --address :9000 --console-address :9001 /home/data/minio > /home/data/minio/log/minio.log 2>&1 &
1
2
3
4
5
6
然后启动minio

# 启动minio服务
bash run.sh
# 查看日志
tail -200f /home/data/minio/log/minio.log
1
2
3
4


然后会有日志打印信息,然后可以看到minio服务器地址,和控制台信息地址

然后在浏览器中访问地址http://192.168.92.100:9000,输入这个地址后会重定向到控制台登录地址http://192.168.92.100:9001/login

添加到systemctl启动命令中

编写minio.service文件

vim /usr/lib/systemd/system/minio.service
1
填写以下内容

[Unit]
Description=minio
Documentation=https://docs.min.io
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/minio/minio

[Service]
#User and group
User=root
Group=root


ExecStart=/usr/local/minio/run.sh
#Let systemd restart this service always
Restart=always

#Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65536

#Disable timeout logic and wait until process is stopped
TimeoutStopSec=infinity
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
赋值权限并且启动

chmod +x /usr/lib/systemd/system/minio.service
1
启动、查看、设置开机启动

systemctl daemon-reload
systemctl start minio
systemctl enable minio
systemctl status minio
1
2
3
4
启动报错处理

 

systemctl start minio
Assertion failed on job for minio.service.
# 是 minio.service 的 AssertFileIsExecutable 路径错误
AssertFileIsExecutable=/usr/local/minio
# 改为
AssertFileIsExecutable=/usr/local/minio/minio
1
2
3
4
5
6
到这里单机版的已经安装完毕!

三、Springboot集成MinIO
3.1 项目应用
项目的pom文件中引入minio依赖
<properties>
<minio.version>7.1.0</minio.version>
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
在application.yml文件中配置minio
minio:
endpoint: http://192.168.92.100
port: 9000
accessKey: minioadmin
secretKey: admin123
bucketName: test
secure: false

spring:
#设置文件上传大小限制
servlet:
multipart:
max-file-size: 100MB
max-request-size: 150MB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
创建minio配置类和工具类
配置类:
package cn.cvzhanshi.wechatpush.config;

import io.minio.MinioClient;
import io.minio.errors.InvalidPortException;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;


@Configuration
@Component
@ConfigurationProperties(prefix = "minio")
@Getter
@Setter
public class MinioConfig {
private String endpoint;
private int port;
private String accessKey;
private String secretKey;
private Boolean secure;
private String bucketName;

@Bean
public MinioClient getMinioClient() throws InvalidPortException {
MinioClient minioClient = MinioClient.builder().endpoint(endpoint, port, secure)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
//
// @Bean(name = "multipartResolver")
// public MultipartResolver multipartResolver(){
// CommonsMultipartResolver resolver = new CommonsMultipartResolver();
// resolver.setDefaultEncoding("UTF-8");
// //resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
// resolver.setResolveLazily(true);
// resolver.setMaxInMemorySize(40960);
// //上传文件大小 50M 50*1024*1024
// resolver.setMaxUploadSize(50*1024*1024);
// return resolver;
// }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
工具类:

package cn.cvzhanshi.wechatpush.utils;

import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

/**
* MinIO 客户端工具类
*/
@Component
@Slf4j
public class MinioClientUtils {

@Autowired
private MinioClient minioClient;

private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600;

/**
* 检查存储桶是否存在
*
* @param bucketName 存储桶名称
* @return boolean
*/
public boolean bucketExists(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = false;
flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (flag) {
return true;
}
return false;
}

/**
* 创建存储桶
*
* @param bucketName 存储桶名称
*/
public boolean makeBucket(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, RegionConflictException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = bucketExists(bucketName);
if (!flag) {
minioClient.makeBucket(
MakeBucketArgs.builder()
.bucket(bucketName)
.build());
return true;
} else {
return false;
}
}

/**
* 列出所有存储桶名称
*
* @return List<String>
*/
public List<String> listBucketNames() throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException {
List<Bucket> bucketList = listBuckets();
List<String> bucketListName = new ArrayList<>();
for (Bucket bucket : bucketList) {
bucketListName.add(bucket.name());
}
return bucketListName;
}

/**
* 列出所有存储桶
*
* @return List<Bucket>
*/
public List<Bucket> listBuckets() throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
return minioClient.listBuckets();
}

/**
* 删除存储桶
*
* @param bucketName 存储桶名称
* @return boolean
*/
public boolean removeBucket(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = bucketExists(bucketName);
if (flag) {
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects) {
Item item = result.get();
// 有对象文件,则删除失败
if (item.size() > 0) {
return false;
}
}
// 删除存储桶,注意,只有存储桶为空时才能删除成功。
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
flag = bucketExists(bucketName);
if (!flag) {
return true;
}

}
return false;
}

/**
* 列出存储桶中的所有对象名称
*
* @param bucketName 存储桶名称
* @return List<String>
*/
public List<String> listObjectNames(String bucketName) throws XmlParserException, IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, InvalidBucketNameException, InsufficientDataException, InternalException {
List<String> listObjectNames = new ArrayList<>();
boolean flag = bucketExists(bucketName);
if (flag) {
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects) {
Item item = result.get();
listObjectNames.add(item.objectName());
}
}
return listObjectNames;
}

/**
* 列出存储桶中的所有对象
*
* @param bucketName 存储桶名称
* @return Iterable<Result<Item>>
*/
public Iterable<Result<Item>> listObjects(String bucketName) throws XmlParserException, IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, InvalidBucketNameException, InsufficientDataException, InternalException {
boolean flag = bucketExists(bucketName);
if (flag) {
return minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).build());
}
return null;
}

/**
* 通过文件上传到对象
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param fileName File name
* @return boolean
*/
public boolean uploadObject(String bucketName, String objectName, String fileName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = bucketExists(bucketName);
if (flag) {
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(bucketName).object(objectName).filename(fileName).build());
ObjectStat statObject = statObject(bucketName, objectName);
if (statObject != null && statObject.length() > 0) {
return true;
}
}
return false;

}

/**
* 文件上传
*
* @param bucketName 存储捅名称
* @param multipartFile 文件
* @param filename 文件名
*/
public void putObject(String bucketName, MultipartFile multipartFile, String filename) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
PutObjectOptions putObjectOptions = new PutObjectOptions(multipartFile.getSize(), PutObjectOptions.MIN_MULTIPART_SIZE);
putObjectOptions.setContentType(multipartFile.getContentType());
minioClient.putObject(
PutObjectArgs.builder().bucket(bucketName).object(filename).stream(
multipartFile.getInputStream(), multipartFile.getSize(), -1).contentType(multipartFile.getContentType())
.build());
}

/**
* 通过InputStream上传对象
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param inputStream 要上传的流
* @param contentType 上传的文件类型 例如 video/mp4 image/jpg
* @return boolean
*/
public boolean putObject(String bucketName, String objectName, InputStream inputStream,String contentType) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException {
boolean flag = bucketExists(bucketName);
if (flag) {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
//不清楚文件的大小时,可以传-1,10485760。如果知道大小也可以传入size,partsize。
inputStream, -1, 10485760)
.contentType(contentType)
.build());
ObjectStat statObject = statObject(bucketName, objectName);
if (statObject != null && statObject.length() > 0) {
return true;
}
}
return false;
}

/**
* 以流的形式获取一个文件对象
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @return InputStream
*/
public InputStream getObject(String bucketName, String objectName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = bucketExists(bucketName);
if (flag) {
ObjectStat statObject = statObject(bucketName, objectName);
if (statObject != null && statObject.length() > 0) {
InputStream stream = minioClient.getObject( GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
return stream;
}
}
return null;
}

/**
* 以流的形式获取一个文件对象(断点下载)
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param offset 起始字节的位置
* @param length 要读取的长度 (可选,如果无值则代表读到文件结尾)
* @return InputStream
*/
public InputStream getObject(String bucketName, String objectName, long offset, Long length) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = bucketExists(bucketName);
if (flag) {
ObjectStat statObject = statObject(bucketName, objectName);
if (statObject != null && statObject.length() > 0) {
InputStream stream = minioClient.getObject( GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.offset(1024L)
.length(4096L)
.build());
return stream;
}
}
return null;
}

/**
* 下载并将文件保存到本地
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param fileName File name
* @return boolean
*/
public boolean downloadObject(String bucketName, String objectName, String fileName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = bucketExists(bucketName);
if (flag) {
ObjectStat statObject = statObject(bucketName, objectName);
if (statObject != null && statObject.length() > 0) {
minioClient.downloadObject(DownloadObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.filename(fileName)
.build());
return true;
}
}
return false;
}

/**
* 删除一个对象
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
*/
public boolean removeObject(String bucketName, String objectName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
boolean flag = bucketExists(bucketName);
if (flag) {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
return true;
}
return false;
}

/**
* 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
*
* @param bucketName 存储桶名称
* @param objectNames 含有要删除的多个object名称的迭代器对象
* @return
* eg:
* List<DeleteObject> objects = new LinkedList<>();
* objects.add(new DeleteObject("my-objectname1"));
* objects.add(new DeleteObject("my-objectname2"));
* objects.add(new DeleteObject("my-objectname3"));
*/
public List<String> removeObjects(String bucketName, List<DeleteObject> objectNames) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
List<String> deleteErrorNames = new ArrayList<>();
boolean flag = bucketExists(bucketName);
if (flag) {
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objectNames).build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
deleteErrorNames.add(error.objectName());
}
}
return deleteErrorNames;
}

/**
* 生成一个给HTTP GET请求用的presigned URL。
* 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param expires 失效时间(以秒为单位),默认是7天,不得大于七天
* @return
*/
public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws InvalidExpiresRangeException, IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException {
boolean flag = bucketExists(bucketName);
String url = "";
if (flag) {
if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
throw new InvalidExpiresRangeException(expires,
"expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME);
}
try {
url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(expires)//动态参数
// .expiry(24 * 60 * 60)//用秒来计算一天时间有效期
// .expiry(1, TimeUnit.DAYS)//按天传参
// .expiry(1, TimeUnit.HOURS)//按小时传参数
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidBucketNameException | InvalidExpiresRangeException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
}
return url;
}

/**
* 生成一个给HTTP PUT请求用的presigned URL。
* 浏览器/移动端的客户端可以用这个URL进行上传,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param expires 失效时间(以秒为单位),默认是7天,不得大于七天
* @return String
*/
public String presignedPutObject(String bucketName, String objectName, Integer expires) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException {
boolean flag = bucketExists(bucketName);
String url = "";
if (flag) {
if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
try {
throw new InvalidExpiresRangeException(expires,
"expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME);
} catch (InvalidExpiresRangeException e) {
e.printStackTrace();
}
}
try {
url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(bucketName)
.object(objectName)
.expiry(expires)//动态参数
// .expiry(24 * 60 * 60)//用秒来计算一天时间有效期
// .expiry(1, TimeUnit.DAYS)//按天传参
// .expiry(1, TimeUnit.HOURS)//按小时传参数
.build());
} catch (ErrorResponseException | InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
log.error("InternalException",e);
} catch (InvalidBucketNameException e) {
log.error("InvalidBucketNameException",e);
} catch (InvalidExpiresRangeException e) {
log.error("InvalidExpiresRangeException",e);
} catch (InvalidKeyException e) {
log.error("InvalidKeyException",e);
} catch (InvalidResponseException e) {
log.error("InvalidResponseException",e);
} catch (IOException e) {
log.error("IOException",e);
} catch (NoSuchAlgorithmException e) {
log.error("NoSuchAlgorithmException",e);
} catch (ServerException e) {
log.error("ServerException",e);
} catch (XmlParserException e) {
log.error("XmlParserException",e);
}
}
return url;
}

/**
* 获取对象的元数据
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @return
*/
public ObjectStat statObject(String bucketName, String objectName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException {
boolean flag = bucketExists(bucketName);
if (flag) {
ObjectStat statObject = null;
try {
statObject = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (ErrorResponseException e) {
log.error("ErrorResponseException",e);
} catch (InsufficientDataException e) {
log.error("ErrorResponseException",e);
e.printStackTrace();
} catch (InternalException e) {
log.error("InternalException",e);
} catch (InvalidBucketNameException e) {
log.error("InvalidBucketNameException",e);
} catch (InvalidKeyException e) {
log.error("InvalidKeyException",e);
} catch (InvalidResponseException e) {
log.error("InvalidResponseException",e);
} catch (IOException e) {
log.error("IOException",e);
} catch (NoSuchAlgorithmException e) {
log.error("NoSuchAlgorithmException",e);
} catch (ServerException e) {
log.error("ServerException",e);
} catch (XmlParserException e) {
log.error("XmlParserException",e);
}
return statObject;
}
return null;
}

/**
* 文件访问路径
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @return String
*/
public String getObjectUrl(String bucketName, String objectName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException {
boolean flag = bucketExists(bucketName);
String url = "";
if (flag) {
try {
url = minioClient.getObjectUrl(bucketName, objectName);
} catch (ErrorResponseException e) {
log.error("XmlParserException",e);
} catch (InsufficientDataException e) {
log.error("InsufficientDataException",e);
} catch (InternalException e) {
log.error("InternalException",e);
} catch (InvalidBucketNameException e) {
log.error("InvalidBucketNameException",e);
} catch (InvalidKeyException e) {
log.error("InvalidKeyException",e);
} catch (InvalidResponseException e) {
log.error("InvalidResponseException",e);
} catch (IOException e) {
log.error("IOException",e);
} catch (NoSuchAlgorithmException e) {
log.error("NoSuchAlgorithmException",e);
} catch (ServerException e) {
log.error("ServerException",e);
} catch (XmlParserException e) {
log.error("XmlParserException",e);
}
}
return url;
}



public void downloadFile(String bucketName, String fileName, String originalName, HttpServletResponse response) {
try {

InputStream file = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build());
String filename = new String(fileName.getBytes("ISO8859-1"), StandardCharsets.UTF_8);
if (StringUtils.isNotEmpty(originalName)) {
fileName = originalName;
}
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
ServletOutputStream servletOutputStream = response.getOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = file.read(buffer)) > 0) {
servletOutputStream.write(buffer, 0, len);
}
servletOutputStream.flush();
file.close();
servletOutputStream.close();
} catch (ErrorResponseException e) {
log.error("ErrorResponseException",e);
} catch (Exception e) {
log.error("Exception",e);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
创建一个数据表,用于保存上传到minio的文件的信息

CREATE TABLE `minio_file` (
`id` bigint(20) NOT NULL COMMENT '文件id',
`original_file_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '原始文件名称',
`file_ext_name` varchar(15) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '文件拓展名',
`file_size` bigint(20) DEFAULT NULL COMMENT '文件大小(单位:字节)',
`file_name` varchar(35) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '存入minio时的文件名称',
`mime` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '文件的content-type',
`file_url` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '文件路径',
`is_delete` tinyint(1) DEFAULT NULL COMMENT '是否删除 0 否 1 是',
`create_by` varchar(25) COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(25) COLLATE utf8mb4_bin DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
创建minio上传接口

package cn.cvzhanshi.wechatpush.controller;

import cn.cvzhanshi.wechatpush.config.MinioConfig;
import cn.cvzhanshi.wechatpush.utils.MinioClientUtils;
import cn.hutool.core.io.FileUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileInputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping("/fileHandle")
@Slf4j
@AllArgsConstructor
@Api(tags = "文件处理模块")
public class FileHandleController {

private MinioClientUtils minioClientUtils;

private MinioConfig minioConfig;

@ApiOperation(value = "上传文件,支持批量上传")

@ApiImplicitParams({@ApiImplicitParam(name = "files", value = "文件流对象,接收数组格式", paramType = "query",required = true, dataType = "MultipartFile", allowMultiple = true)}
)
@PostMapping("/uploadFile")
public ApiResult uploadFile(@RequestParam(value = "files", required = true) MultipartFile[] files) {
log.info(files.toString());
/* if (CollectionUtils.isEmpty(files)){
return ApiResult.error("未选择文件!");
}*/

List<String> MinioResponseDTOList = new ArrayList<>();
for (MultipartFile file : files) {
String originalFilename = file.getOriginalFilename();
// 获取文件拓展名
String extName = FileUtil.extName(originalFilename);
log.info("文件拓展名:" + extName);
// 生成新的文件名,存入到minio
long millSeconds = Instant.now().toEpochMilli();
String minioFileName = millSeconds + RandomStringUtils.randomNumeric(12) + "." + extName;
String contentType = file.getContentType();
log.info("文件mime:{}", contentType);
// 返回文件大小,单位字节
long size = file.getSize();
log.info("文件大小:" + size);
try {
String bucketName = minioConfig.getBucketName();
minioClientUtils.putObject(bucketName, file, minioFileName);
String fileUrl = minioClientUtils.getObjectUrl(bucketName, minioFileName);
/* MinioFile minioFile = new MinioFile();
minioFile.setOriginalFileName(originalFilename);
minioFile.setFileExtName(extName);
minioFile.setFileName(minioFileName);
minioFile.setFileSize(size);
minioFile.setMime(contentType);
minioFile.setIsDelete(NumberUtils.INTEGER_ZERO);
minioFile.setFileUrl(fileUrl);
boolean insert = minioFile.insert();
if (insert) {
MinioResponseDTO minioResponseDTO = new MinioResponseDTO();
minioResponseDTO.setFileId(minioFile.getId());
minioResponseDTO.setOriginalFileName(originalFilename);
minioResponseDTO.setFileUrl(fileUrl);
MinioResponseDTOList.add(minioResponseDTO);
}*/
MinioResponseDTOList.add(fileUrl);


} catch (Exception e) {
log.error("上传文件出错:{}", e);
return ApiResult.error("上传文件出错");

}
}

return ApiResult.success(MinioResponseDTOList);
}


/**
* 仅仅用于测试,是否可以正常上传文件
*
* @return
* @throws Exception
*/
@GetMapping("/test")
@ApiOperation(value = "测试minio文件上传")
public ApiResult testPutObject() throws Exception {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\MSI\\Desktop\\新建文本文档.txt");
boolean bs = minioClientUtils.putObject("fsp-dev", "新建文本文档.txt", fileInputStream, "image/jpg");
log.info("上传成功?" + bs);
return ApiResult.success("上传成功");
}


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
测试接口


四、Java中图片压缩上传
4.1 背景
现在大家都是用的智能手机拍照,拍出来的照片小则 2-3 M,大则十几 M,所以导致图片显示较慢。思考再三,决定将图片进行压缩再上传图片服务器来解决图片显示慢的问题

4.2 开发准备
引入 maven 依赖

<!-- 图片压缩 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
1
2
3
4
5
6
本次我们选择了使用 thumbnailator 来作为压缩的工具

thumbnailator 简介

Thumbnailator 是一个用来生成图像缩略图的 Java 类库,通过很简单的代码即可生成图片缩略图,也可直接对一整个目录的图片生成缩略图
支持图片缩放,区域裁剪,水印,旋转,保持比例

压缩准备
判断是否是图片方法

/**
* 判断文件是否为图片
*/
public boolean isPicture(String imgName) {
boolean flag = false;
if (StringUtils.isBlank(imgName)) {
return false;
}
String[] arr = {"bmp", "dib", "gif", "jfif", "jpe", "jpeg", "jpg", "png", "tif", "tiff", "ico"};
for (String item : arr) {
if (item.equals(imgName)) {
flag = true;
break;
}
}
return flag;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
4.3 压缩上传
/**
* 上传文件
*
* @param file 文件
* @return
*/
public JSONObject uploadFile(MultipartFile file) throws Exception {
JSONObject res = new JSONObject();
res.put("code", 500);
// 判断上传文件是否为空
if (null == file || 0 == file.getSize()) {
res.put("msg", "上传文件不能为空");
return res;
}
// 判断存储桶是否存在
if (!client.bucketExists("test")) {
client.makeBucket("test");
}
// 拿到文件后缀名,例如:png
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
// UUID 作为文件名
String uuid = String.valueOf(UUID.randomUUID());
// 新的文件名
String fileName = DateUtils.getYyyymmdd() + "/" + uuid + "." + suffix;
/**
* 判断是否是图片
* 判断是否超过了 100K
*/
if (isPicture(suffix) && (1024 * 1024 * 0.1) <= file.getSize()) {
// 在项目根目录下的 upload 目录中生成临时文件
File newFile = new File(ClassUtils.getDefaultClassLoader().getResource("upload").getPath() + uuid + "." + suffix);
// 小于 1M 的
if ((1024 * 1024 * 0.1) <= file.getSize() && file.getSize() <= (1024 * 1024)) {
Thumbnails.of(file.getInputStream()).scale(1f).outputQuality(0.3f).toFile(newFile);
}
// 1 - 2M 的
else if ((1024 * 1024) < file.getSize() && file.getSize() <= (1024 * 1024 * 2)) {
Thumbnails.of(file.getInputStream()).scale(1f).outputQuality(0.2f).toFile(newFile);
}
// 2M 以上的
else if ((1024 * 1024 * 2) < file.getSize()) {
Thumbnails.of(file.getInputStream()).scale(1f).outputQuality(0.1f).toFile(newFile);
}
// 获取输入流
FileInputStream input = new FileInputStream(newFile);
// 转为 MultipartFile
MultipartFile multipartFile = new MockMultipartFile("file", newFile.getName(), "text/plain", input);
// 开始上传
client.putObject("test", fileName, multipartFile.getInputStream(), file.getContentType());
// 删除临时文件
newFile.delete();
// 返回状态以及图片路径
res.put("code", 200);
res.put("msg", "上传成功");
res.put("url", minioProp.getEndpoint() + "/" + "test" + "/" + fileName);
}
// 不需要压缩,直接上传
else {
// 开始上传
client.putObject("test", fileName, file.getInputStream(), file.getContentType());
// 返回状态以及图片路径
res.put("code", 200);
res.put("msg", "上传成功");
res.put("url", minioProp.getEndpoint() + "/" + "test" + "/" + fileName);
}
return res;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
这里我们判断了当文件为图片的时候,且当它大小超过了 (1024 * 1024 * 0.1),约为 100K 的时候,才进行压缩
我们首先在根目录下的 upload 目录中创建了一个临时文件 newFile
Thumbnails.of(file.getInputStream()).scale(1f).outputQuality(0.3f).toFile(newFile);将压缩后的文件输出到临时文件中
然后将 FileInputStream 转为 MultipartFile 上传
最后删除临时文件 newFile.delete();
完成图片压缩上传
遇到的问题:
Thumbnails.scale效果会导致图片大小变大


Thumbnails应该是存在bug,但是也一直没有更新版本,所以根据多次测试得来的结果:用jpg转成jpg效果最佳。所以当图片为png时,先改成jpg格式,再进行压缩。

public static String imgConvert(String tempDirPath, String fileName, String fileExt) throws IOException {
String srcPath = tempDirPath + fileName; //原始图片路径
if("png".equals(fileExt)) {

//生成新图片名称
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String fileString = df.format(new Date()) + "_" + new Random().nextInt(1000) + ".jpg";

//新图片全路径
String newJpg = tempDirPath + fileString;

// 1、先转换成jpg
Thumbnails.of(srcPath).scale(1f).toFile(newJpg);

//2.jpg图片压缩
Thumbnails.of(newJpg).scale(1f).outputQuality(0.25d).toFile(newJpg);

//压缩成功后,删除png图片
File f = new File(srcPath);
f.delete();

return fileString;
} else {
Thumbnails.of(srcPath).scale(1f).outputQuality(0.25d).toFile(srcPath);
}
return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

posted on 2023-11-30 23:18  卖小女孩的小男孩  阅读(780)  评论(0)    收藏  举报