SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务

        

   左上方关注“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支持接入JavaScriptJavaPythonGolang等多种语言,这里我们选择最熟悉的Java语言,使用最流行的框架 SpringBoot 2.x

  • 完整项目目录如下:
SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务
完整项目结构图

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<Timplements 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>(200null, 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)

SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务
在这里插入图片描述

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

SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务
在这里插入图片描述

2. 预览功能

  • URL : http://127.0.0.1:8899/preViewPicture?objectName=images/2020-11-20/1605857319982.png
SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务
在这里插入图片描述

四、参考文档

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曹」,架构路上太漫长,我们一起前行吧。


SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务


         喜欢你就点个在看吧,点赞也可以来一下哦


原文始发于微信公众号(Thinking曹):SpringBoot 系列教程(一百零四):SpringBoot 整合 MinIo 文件服务

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/26829.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之家——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!