左上方关注“Thinking曹”,勾选“设为星标”
沉淀、分享、持之以恒的学习
在前面的一篇文章中,详细的给大家介绍了如何在Centos7.x
环境下从零开始搭建分布式文件系统MinIo
,错过的朋友可以点这里:从零搭建分布式文件系统MinIO比FastDFS要更合适
那么继上一篇文章之后,本篇文章将带领大家开始实战,选用当下主流的SpringBoot2.x
框架来整合分布式文件系统MinIo
,关于MinIo
的使用,接下来我们一探究竟。
一、SpringBoot整合MinIo
关于SpringBoot
整合MinIo
以及Minio
相关操作的API
可参考官方文档,官方文档给出了详细的API
解释:
参考: http://docs.minio.org.cn/docs/master/java-client-quickstart-guide
参考: https://docs.min.io/cn/java-client-api-reference.html
Minio
支持接入JavaScript
、Java
、Python
、Golang
等多种语言,这里我们选择最熟悉的Java
语言,使用最流行的框架 SpringBoot 2.x
。
-
完整项目目录如下:

1. 引入minio依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
2. 完整的Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-minio</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-minio</name>
<description>SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok代码简化-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- test单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- minio文件服务 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.0.2</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- commons-lang3-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- hutool-core-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.4.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. application.yml
这时候需要在application.yml
文件中配置关于minio
文件服务器的相关配置参数
#服务端口
server:
port: 8899
#minio配置
minio:
#minio的服务端访问地址
endpoint: http://192.168.15.135:9000
#账号
accessKey: minioadmin
#密码
secretKey: minioadmin
#桶名称(根目录文件夹名称)
bucketName: dev
4. MinIoProperties
将minio的属性配置信息映射到Java配置类,并注册成Bean, 如下:
package com.example.minio.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @desc: minio的属性配置信息映射到Java配置类,并注册成Bean
* @author: cao_wencao
* @date: 2020-11-19 17:13
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIoProperties {
/**
* 服务地址
*/
private String endpoint;
/**
* 用户名
*/
private String accessKey;
/**
* 密码
*/
private String secretKey;
/**
* 桶名称
*/
private String bucketName;
}
5. MinIoUtils工具类
根据Minio官方API文档封装MinIo工具类,如下:
package com.example.minio.config;
import com.example.minio.properties.MinIoProperties;
import io.minio.MinioClient;
import io.minio.PutObjectOptions;
import io.minio.Result;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.List;
/**
* http://docs.minio.org.cn/docs/master/java-client-api-reference
* 参考:https://www.jianshu.com/p/7f493105b2b2
*
* @desc: 根据Minio官方API文档封装MinIo工具类
* @author: cao_wencao
* @date: 2020-11-19 17:16
*/
@Component
@EnableConfigurationProperties(value = {MinIoProperties.class})
@ConditionalOnProperty(prefix = "minio", name = {"endpoint", "accessKey", "secretKey", "bucketName"})
public class MinIoUtils {
private final MinioClient minioClient;
private final String bucketName;
@Autowired
private MinIoProperties minIoProperties;
/**
* 初始化MinioClient
*/
@SneakyThrows
public MinIoUtils(MinIoProperties minIoProperties) {
if (!StringUtils.hasText(minIoProperties.getBucketName())) {
throw new RuntimeException("bucket name can not be empty or null");
}
this.bucketName = StringUtils.trimTrailingCharacter(minIoProperties.getBucketName().trim(), '/');
minioClient = new MinioClient(minIoProperties.getEndpoint(), minIoProperties.getAccessKey(), minIoProperties.getSecretKey());
}
/**
* 判断bucket是否存在
*
* @param bucketName bucket名称
* @return
*/
@SneakyThrows
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(bucketName);
}
/**
* 创建bucket
*
* @param bucketName bucket名称
*/
@SneakyThrows
public void makeBucket(String bucketName) {
boolean isExist = minioClient.bucketExists(bucketName);
if (!isExist) {
minioClient.makeBucket(bucketName);
}
}
/**
* 获取全部bucket
*/
@SneakyThrows
public List<Bucket> getAllBuckets() {
return minioClient.listBuckets();
}
/**
* 判断文件是否存在
*
* @param objectName 文件名称
* @return
*/
public boolean objectExists(String objectName) {
boolean isExists = false;
try {
InputStream inputStream = minioClient.getObject(bucketName, objectName);
if (null != inputStream) {
isExists = true;
}
return isExists;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取单个文件
*
* @param objectName 文件名称
* @return
*/
@SneakyThrows
public InputStream getObject(String objectName) {
return minioClient.getObject(bucketName, objectName);
}
/**
* 获取文件集合
*
* @param bucketN bucket名称
* @return
*/
@SneakyThrows
public Iterable<Result<Item>> listObjects(String bucketN) {
if (StringUtils.hasText(bucketN)) {
bucketN = bucketName;
}
return minioClient.listObjects(bucketN);
}
/**
* 根据prefix获取object集合
*
* @param prefix
* @return
*/
@SneakyThrows
public Iterable<Result<Item>> listObjectsByPrefix(String prefix) {
return minioClient.listObjects(bucketName, prefix);
}
/**
* 获取文件外链
*
* @param objectName 文件名称
* @return
*/
@SneakyThrows
public String buildObjectUrl(String objectName) {
if (!StringUtils.hasText(objectName)) {
throw new RuntimeException("object name can not be empty or null");
}
objectName = StringUtils.trimLeadingCharacter(StringUtils.trimTrailingCharacter(objectName.trim(), '/'), '/');
String objectUrl = String.format("%s/%s", bucketName, objectName);
if (StringUtils.hasText(minIoProperties.getEndpoint())) {
objectUrl = String.format("%s/%s", minIoProperties.getEndpoint(), objectUrl);
}
return objectUrl;
}
/**
* 上传文件-InputStream
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
*/
@SneakyThrows
public void putObject(String bucketName, String objectName, InputStream stream) {
minioClient.putObject(bucketName, objectName, stream, new PutObjectOptions(stream.available(), -1));
}
/**
* 上传文件-InputStream
*
* @param objectName 文件名称
* @param stream 文件流
*/
@SneakyThrows
public void putObject(String objectName, InputStream stream) {
minioClient.putObject(bucketName, objectName, stream, new PutObjectOptions(stream.available(), -1));
}
/**
* 上传文件
*
* @param objectName 文件名称
* @param stream 文件流
* @param size 大小
* @param contentType 类型
* @return the object url string
*/
@SneakyThrows
public String putObject(String objectName, InputStream stream, Long size, String contentType) {
if (!StringUtils.hasText(objectName)) {
throw new RuntimeException("object name can not be empty or null");
}
minioClient.putObject(bucketName, objectName, stream, new PutObjectOptions(stream.available(), -1));
return buildObjectUrl(objectName);
}
/**
* 删除文件
*
* @param bucketName bucket名称
* @param objectName 文件名称
*/
@SneakyThrows
public void removeObject(String bucketName, String objectName) {
minioClient.removeObject(bucketName, objectName);
}
/**
* 删除文件
*
* @param objectName 文件名称
*/
@SneakyThrows
public void removeObject(String objectName) {
minioClient.removeObject(bucketName, objectName);
}
}
6. MinioFileController
根据已经封装好的MinioUtils
工具类,我们写几个简单的图片上传、图片删除、图片下载、获取图片信息、图片预览功能,如下Api
接口所示:
package com.example.minio.controller;
import cn.hutool.core.date.DateUtil;
import com.example.minio.config.MinIoUtils;
import com.example.minio.result.RestResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
* @desc: 文件上传工具类
* @author: cao_wencao
* @date: 2020-11-19 22:00
*/
@RestController
@Slf4j
public class MinioFileController {
@Autowired
private MinIoUtils minIoUtils;
/**
* 创建资源桶
* @param bucketName
* @return
*/
@GetMapping(value = "/createBucket")
public RestResponse createBucket(String bucketName){
if (!minIoUtils.bucketExists(bucketName)) {
minIoUtils.makeBucket(bucketName);
return RestResponse.success("桶创建成功");
}
return RestResponse.fail("该桶已存在,请勿重复创建");
}
/**
* @desc: 上传图片
* @auth: cao_wencao
* @date: 2020/11/19 22:22
*/
@PostMapping(value = "uploadImg")
public RestResponse uploadImage(@RequestParam("file") MultipartFile file) throws Exception {
if (file.isEmpty()) {
throw new RuntimeException("上传文件不能为空");
} else {
// 得到文件流
final InputStream inputStream = file.getInputStream();
// 文件名
final String originalFilename = file.getOriginalFilename();
String filelName = "bucketName" + "_" +
System.currentTimeMillis() +
"wechat.jpg".substring("wechat.jpg".lastIndexOf("."));
String objectName = String.format("images/%s/%s", DateUtil.today(),+
System.currentTimeMillis() +
originalFilename.substring(originalFilename.lastIndexOf(".")));
log.info("【objectName的名称】:{}", objectName);
//String objectName = String.format("upload/images/%s/%s/%s", DateUtil.today(),
// UUID.randomUUID().toString().replace("-", "").substring(0, 10),
// originalFilename);
// 把文件放到minio的boots桶里面
minIoUtils.putObject(objectName, inputStream);
// 关闭输入流
inputStream.close();
return RestResponse.success("上传成功");
}
}
/**
* 获取图片的URL访问地址
* @param objectName
* @return
*/
@RequestMapping("/getObjectURL")
public RestResponse getObjectURL(String objectName){
String url = minIoUtils.buildObjectUrl(objectName);
return RestResponse.success(url);
}
/**
* 删除文件
* @param objectName
* @return
*/
@GetMapping(value = "removeImg")
public RestResponse removeImage(@RequestParam("objectName") String objectName) {
if (!StringUtils.hasText(objectName)){
throw new RuntimeException("资源名称不能为空");
}
minIoUtils.removeObject(objectName);
return RestResponse.success("删除成功");
}
/**
* 下载文件到本地
* @param objectName
* @param response
* @return
* @throws Exception
*/
@GetMapping("/downloadImage")
public RestResponse downloadImage(@RequestParam("objectName") String objectName, HttpServletResponse response) throws Exception {
ResponseEntity<byte[]> responseEntity = null;
InputStream stream = null;
ByteArrayOutputStream output = null;
try {
// 获取"myobject"的输入流。
stream = minIoUtils.getObject(objectName);
if (stream == null) {
System.out.println("文件不存在");
}
//用于转换byte
output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = stream.read(buffer))) {
output.write(buffer, 0, n);
}
byte[] bytes = output.toByteArray();
//设置header
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Accept-Ranges", "bytes");
httpHeaders.add("Content-Length", bytes.length + "");
// objectName = new String(objectName.getBytes("UTF-8"), "ISO8859-1");
//把文件名按UTF-8取出并按ISO8859-1编码,保证弹出窗口中的文件名中文不乱码,中文不要太多,最多支持17个中文,因为header有150个字节限制。
httpHeaders.add("Content-disposition", "attachment; filename=" + objectName);
httpHeaders.add("Content-Type", "text/plain;charset=utf-8");
// httpHeaders.add("Content-Type", "image/jpeg");
responseEntity = new ResponseEntity<byte[]>(bytes, httpHeaders, HttpStatus.CREATED);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null) {
stream.close();
}
if (output != null) {
output.close();
}
}
return RestResponse.success(responseEntity);
}
/**
* 在浏览器预览图片,预览功能,需要设置contextType = “image/jpeg”
* @param objectName
* @param response
* @throws Exception
*/
@RequestMapping("/preViewPicture")
public void preViewPicture(@RequestParam("objectName") String objectName, HttpServletResponse response) throws Exception{
response.setContentType("image/jpeg");
ServletOutputStream out = null;
try {
out = response.getOutputStream();
InputStream stream = minIoUtils.getObject(objectName);
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = stream.read(buffer))) {
output.write(buffer, 0, n);
}
byte[] bytes = output.toByteArray();
out.write(bytes == null ? new byte[0]:bytes);
out.flush();
}finally {
if (out != null) {
out.close();
}
}
}
}
7. REST接口工具类RestResponse
总所周知,Rest
风格的API
接口在返回数据到客户端时,需要统一返回数据格式,这既是规范也是基本要求,封装的 REST
接口工具类如下:
package com.example.minio.result;
import java.io.Serializable;
/**
* REST接口统一返回数据工具类封装RestResponse
* @param <T>
*/
public class RestResponse<T> implements Serializable {
private static final long serialVersionUID = 3728877563912075885L;
private int code;
private String msg;
private T data;
public RestResponse(){
}
public RestResponse(int code, String message, T data) {
this.code = code;
this.setMsg(message);
this.data = data;
}
public RestResponse(int code, T data) {
this.code = code;
this.data = data;
}
public RestResponse(int code, String message) {
this.code = code;
this.setMsg(message);
}
/**
* 成功时-返回data
* @param <T>
* @return
*/
public static <T> RestResponse<T> success(T data){
return new RestResponse<T>(200, null, data);
}
/**
* 成功-不返回data
* @param <T>
* @return
*/
public static <T> RestResponse<T> success(String msg){
return new RestResponse<T>(200, msg);
}
/**
* 成功-返回data+msg
* @param <T>
* @return
*/
public static <T> RestResponse<T> success(String msg, T data){
return new RestResponse<T>(200, msg, data);
}
/**
* 失败
* @param <T>
* @return
*/
public static <T> RestResponse<T> fail(String msg){
return new RestResponse<T>(500, msg,null);
}
/**
* 失败-code
* @param <T>
* @return
*/
public static <T> RestResponse<T> fail(int code, String msg){
return new RestResponse<T>(code, msg,null);
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public T getData() {
return data;
}
public void setCode(int code) {
this.code = code;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "RestResponse{" + "code=" + code + ", msg='" + msg + ''' +", data=" + data +'}';
}
}
二、文件上传页面
1. 简单页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试minio上传</title>
</head>
<body>
<form th:action="@{/uploadImg}" accept-charset="UTF-8" method="post" enctype="multipart/form-data" target="_blank">
文件:<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
2. 页面跳转
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @desc: 页面跳转
* @author: cao_wencao
* @date: 2020-11-20 10:53
*/
@Controller
public class PageController {
@RequestMapping("/toPage")
public String toPage(){
return "upload";
}
}
三、简单测试
1. 上传图片(http://127.0.0.1:8899/toPage)

访问上传图片的页面后,选择一张图片进行上传,上传完成后,登录控制台查看上传成功的文件如下所示:

2. 预览功能
-
URL : http://127.0.0.1:8899/preViewPicture?objectName=images/2020-11-20/1605857319982.png

四、参考文档
https://docs.min.io/cn/java-client-quickstart-guide.html
https://docs.min.io/cn/java-client-api-reference.html
五、源码
https://github.com/Thinkingcao/SpringBootLearning/tree/master/springboot-minio
觉得文章还不错,按住下图二维码,关注公众号「Thinking曹」,架构路上太漫长,我们一起前行吧。
喜欢你就点个在看吧,点赞也可以来一下哦
原文始发于微信公众号(Thinking曹):SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/26829.html