MinIO对象存储的基本封装使用

12/22/2021 MinIO对象存储

# 1. 前言

# 1.1 MinIO简介

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。 它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

项目地址:https://github.com/minio/minio (opens new window)

minio-console

# 1.2 对象存储简介

对象存储是将获取的每个数据片段指定为对象,数据保存在单独的存储库中,而不是以文件的形式保存在文件夹中,数据与关联的元数据和唯一标识符捆绑在一起,以形成存储池。

# 2. 搭建前的环境准备

以下我将采用Nginx+Docker部署的方式进行搭建,VPS系统用的是Debian 11 x86_64。VPS的购买及配置、域名解析、Docker的概念及使用...这些基本的就不再赘述了,如果不会的话见我的其他博客:VPS基本部署环境的搭建与配置 (opens new window)Docker容器化及项目环境管理 (opens new window)

# 2.1 Docker环境搭建

$ apt-get update -y && apt-get install curl -y  # 安装curl
$ curl https://get.docker.com | sh -   # 安装docker
$ sudo systemctl start docker  # 启动docker服务
$ docker version # 查看docker版本(客户端要与服务端一致)
1
2
3
4

# 2.2 使用OneinStack搭建Nginx并进行配置

# 2.2.1 OneinStack简介

项目简介:OneinStack是一套部署开发环境的脚本工具,它的部署方式是纯原生部署,我主要使用里面的Nginx,用来辅助生成配置。

项目地址:https://github.com/oneinstack/oneinstack (opens new window)

# 2.2.2 使用OneinStack搭建Nginx服务

$ wget -c http://mirrors.linuxeye.com/oneinstack-full.tar.gz && tar xzf oneinstack-full.tar.gz && ./oneinstack/install.sh --nginx_optio
1

注:OneinStack采用编译安装的方式搭建Nginx服务,速度比较慢,请耐心等待,常用的默认目录有:

Nginx安装目录: /usr/local/nginx
Nginx配置目录:/usr/local/nginx/conf
网站数据目录:/data/wwwroot/域名
1
2
3

# 2.2.3 配置反向代理并开启HTTPS

OneinStack 内置了 acme.sh,它会自动帮你申请 SSL 证书。

$ cd ./oneinstack
$ ./vhost.sh
1
2

配置过程的参考示例如下(示例域名为www.demo.com):

#######################################################################
#       OneinStack for CentOS/RedHat 7+ Debian 8+ and Ubuntu 16+      #
#       For more information please visit https://oneinstack.com      #
#######################################################################

What Are You Doing?
        1. Use HTTP Only
        2. Use your own SSL Certificate and Key
        3. Use Let's Encrypt to Create SSL Certificate and Key
        q. Exit
Please input the correct option: 2 【选择2】(使用了Cloudfare CDN的话,可以选择自签名证书)

Please input domain(example: www.example.com): www.demo.com 【输入域名】
domain=www.demo.com

Please input the directory for the domain:www.demo.com :
(Default directory: /data/wwwroot/www.demo.com): 【回车,使用默认配置】
Virtual Host Directory=/data/wwwroot/www.demo.com

Create Virtul Host directory......
set permissions of Virtual Host directory......

Do you want to add more domain name? [y/n]: n  【输入n,不添加其他域名了】

Do you want to redirect all HTTP requests to HTTPS? [y/n]: y  【输入y,强制HTTPS】

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

Country Name (2 letter code) [CN]: 【回车】

State or Province Name (full name) [Shanghai]: 【回车】

Locality Name (eg, city) [Shanghai]: 【回车】

Organization Name (eg, company) [Example Inc.]: 【回车】

Organizational Unit Name (eg, section) [IT Dept.]: 【回车】

Do you want to add hotlink protection? [y/n]: n 【输入n,不需要防盗链保护】

Allow Rewrite rule? [y/n]: y 【输入y,允许重写规则】

Please input the rewrite of programme :
wordpress,opencart,magento2,drupal,joomla,codeigniter,laravel
thinkphp,pathinfo,discuz,typecho,ecshop,nextcloud,zblog,whmcs rewrite was exist.
(Default rewrite: other): 【回车】
You choose rewrite=other

Allow Nginx/Tengine/OpenResty access_log? [y/n]: y  【输入y,允许日志记录】
You access log file=/data/wwwlogs/www.demo.com_nginx.log

#######################################################################
#       OneinStack for CentOS/RedHat 7+ Debian 8+ and Ubuntu 16+      #
#       For more information please visit https://oneinstack.com      #
#######################################################################
Your domain:                  www.demo.com
Virtualhost conf:             /usr/local/nginx/conf/vhost/www.demo.com.conf
Directory of:                 /data/wwwroot/www.demo.com
Rewrite rule:                 /usr/local/nginx/conf/rewrite/other.conf
Self-signed SSL Certificate:  /usr/local/nginx/conf/ssl/www.demo.com.crt
SSL Private Key:              /usr/local/nginx/conf/ssl/www.demo.com.key
SSL CSR File:                 /usr/local/nginx/conf/ssl/www.demo.com.csr
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

配置完成后,修改一下配置文件

$ vim /usr/local/nginx/conf/vhost/www.demo.com.conf
1

server模块删除内容:

  location ~ [^/]\.php(/|$) {
    #fastcgi_pass remote_php_ip:9000;
    fastcgi_pass unix:/dev/shm/php-cgi.sock;
    fastcgi_index index.php;
    include fastcgi.conf;
  }
  location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
    expires 30d;
    access_log off;
  }
  location ~ .*\.(js|css)?$ {
    expires 7d;
    access_log off;
  }
    location /.well-known {
    allow all;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

server模块新增内容:

location / {
  proxy_set_header HOST $host;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_pass http://demo;  # 与下面 upstream 的名称一致即可
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
  proxy_pass http://demo;  # 与下面 upstream 的名称一致即可
  expires 30d;
  access_log off;
}
location ~ .*\.(js|css)?$ {
  proxy_pass http://demo;   # 与下面 upstream 的名称一致即可
  expires 7d;
  access_log off;
}
location ^~ /.well-known/acme-challenge/ {
  default_type "text/plain";
  allow all;
  root /data/wwwroot/www.demo.com/;  # 把路径换一下
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

与server模块同级新增内容:

upstream demo {
  server 127.0.0.1:9001;  # 9001换成自己的反向代理端口号
}
1
2
3

检查并重载Nginx配置

$ nginx -t
$ nginx -s reload
1
2

注:使用nginx -t命令可以检查Nginx配置,若出现如下内容则说明配置正确。

nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
1
2

# 3. 搭建MinIO对象存储

# 3.1 拉取镜像并运行容器

$ docker pull minio/minio
$ mkdir -p /home/data/minio/data
$ mkdir -p /home/data/minio/config
$ docker run -d --restart always \
   -p 9000:9000 -p 9001:9001 --name minio \
   -e "MINIO_ACCESS_KEY=admin" \
   -e "MINIO_SECRET_KEY=password" \
   -v /home/data/minio/data:/data \
   -v /home/data/minio/config:/root/.minio \
   minio/minio server --console-address ":9001" /data
1
2
3
4
5
6
7
8
9
10

注意事项:

1)密码不可以设置的太简单了(会导致创建失败),出现此问题请查看容器日志。

2)本文MinIO所使用的版本为RELEASE.2022-08-02T23-59-16Z,建议与之保持一致。

# 3.2 Minio的管理面板

浏览器打开:http://IP:9001 查看即可。MINIO_ACCESS_KEY为账号,MINIO_SECRET_KEY为密码。进去之后创建存储桶,即可进行使用。

# 3.3 添加反向代理并开启 HTTPS

通用的创建网站开启HTTPS并配置反向代理见本文2.2节。

注:9000端口是用来访问图片的,9001端口是用来访问面板的,建议用两个域名都加上反向代理。

# 4. Python与MinIO的封装集成

MinIO-Python API 官方文档:https://github.com/minio/minio-py/blob/master/docs/zh_CN/API.md (opens new window)

# 4.1 安装依赖并编写配置文件

安装依赖

$ pip install minio
1

config.ini

[minio]
minio_url = xxx.xxx.xxx.xxx:9000
access_key = minioadmin
secret_key = minioadmin
1
2
3
4

注:minio_url不要带上http://的前缀,否则会报如下错误

ValueError: path in endpoint is not allowed. Exception ignored in: <function Minio.__del__ at 0x0C0B9A98>
1

# 4.2 MinIO上传文件封装代码

# -*- coding: utf-8 -*-

import logging
import os
from minio import Minio
from minio.error import S3Error
from configparser import ConfigParser

logging.basicConfig(filename='logging_minio.log', level=logging.INFO,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


# 读取配置文件
def read_config():
    cfg = ConfigParser()
    cfg.read('./config.ini', encoding='utf-8')
    minio_url = cfg.get('minio', 'minio_url')
    access_key = cfg.get('minio', 'access_key')
    secret_key = cfg.get('minio', 'secret_key')
    config_dict = {}
    config_dict['minio_url'] = minio_url
    config_dict['access_key'] = access_key
    config_dict['secret_key'] = secret_key
    return config_dict


# 初始化minio客户端
def get_minio_client(config_dict):
    # 使用endpoint、access key和secret key来初始化minioClient对象。
    minio_client = Minio(config_dict['minio_url'],
                         access_key=config_dict['access_key'],
                         secret_key=config_dict['secret_key'],
                         secure=False)
    return minio_client


# 创建一个存储桶(判断桶是否已经存在,不存在则创建,存在忽略)
def minio_make_bucket_ifnotexist(minio_client, bucket_name):
    bucket_name = bucket_name.replace('_', "-")
    try:
        if not minio_client.bucket_exists(bucket_name):
            logging.info("该存储桶不存在:" + bucket_name)
            minio_client.make_bucket(bucket_name)
            logging.info("存储桶创建:" + bucket_name)
    except S3Error as e:
        if "InvalidAccessKeyId" in str(e):
            logging.error("minio 的 access_key 可能有误")
        elif "SignatureDoesNotMatch" in str(e):
            logging.error("minio 的 secret_key 可能有误")
        else:
            logging.error("minio 的 endpoint、access_key、secret_key 可能有误")
        raise e


# 删除存储桶
def remove_bucket(minio_client, bucket_name):
    try:
        minio_client.remove_bucket(bucket_name)
        logging.info("删除存储桶成功:" + bucket_name)
    except S3Error as e:
        logging.error(e)


# 文件上传
def minio_upload_file(minio_client, bucket_name, object_name, file_path):
    logging.info(file_path)
    result = minio_client.fput_object(bucket_name, object_name, file_path)
    return result


# 级联遍历目录,获取目录下的所有文件路径
def find_filepaths(dir):
    result = []
    for root, dirs, files in os.walk(dir):
        for name in files:
            filepath = os.path.join(root, name)
            if os.path.exists(filepath):
                result.append(filepath)
    return result


# 获取object_name(上传到minio的路径)
def get_object_name(file_path):
    file_dir, file_full_name = os.path.split(file_path)
    return file_full_name


if __name__ == '__main__':
    # 读取配置文件
    config_dict = read_config()
    # 初始化minio客户端
    minio_client = get_minio_client(config_dict)
    # 创建一个存储桶(判断桶是否已经存在,不存在则创建,存在忽略)
    minio_make_bucket_ifnotexist(minio_client, 'test')
    # 删除存储桶
    remove_bucket(minio_client, 'test')
    # 文件上传
    minio_make_bucket_ifnotexist(minio_client, 'test')
    img_list = find_filepaths('./img')
    for img_path in img_list:
        object_name = get_object_name(img_path)
        minio_upload_file(minio_client, 'test', object_name, img_path)
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

# 5. Springboot与MinIO的封装集成

MinIO-Java API官方文档:https://github.com/minio/minio-java/blob/master/docs/zh_CN/API.md (opens new window)

# 5.1 模块的项目结构及封装代码

有关springboot-minio的通用集成,我这里单独封装了一个minio_common模块,需要用到该模块只需要在调用处的pom里引用即可。

.
├── pom.xml
└── src.main.java.com.yoyo.admin.minio_common
    ├── aop
    │   └── MinioServiceAspect.java
    ├── config
    │   └── MinioConfig.java
    ├── service
    │   └── MinioService.java
    └── util
        ├── FileTypeUtils.java
        └── MinioUtils.java
1
2
3
4
5
6
7
8
9
10
11
12

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>com.yoyo</groupId>
        <artifactId>yoyo-admin</artifactId>
        <version>0.0.1</version>
    </parent>
    <groupId>com.yoyo.admin</groupId>
    <artifactId>minio_common</artifactId>
    <version>0.0.1</version>
    <name>minio_common</name>
    <description>yoyo-admin minio_common module</description>

    <properties>
        <java.version>1.8</java.version>
        <hutool.all.version>5.7.7</hutool.all.version>
        <minio.version>8.2.1</minio.version>
        <aspectjweaver.version>1.9.7</aspectjweaver.version>
    </properties>
    <packaging>jar</packaging>

    <dependencies>
        <!-- Hutool 工具类库依赖 https://www.hutool.cn/docs/ -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.all.version}</version>
        </dependency>
        <!-- lombok依赖 让代码更简洁 https://www.projectlombok.org/-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- commons-lang3 依赖 https://mvnrepository.com/artifact/org.apache.commons/commons-lang3-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!-- minio 依赖 https://mvnrepository.com/artifact/io.minio/minio -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>${minio.version}</version>
        </dependency>
        <!-- aspectjweaver 依赖 https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>
    </dependencies>

</project>
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

MinioServiceAspect.java

package com.yoyo.admin.minio_common.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MinioServiceAspect {

    @Before(value="execution(* com.yoyo.admin.minio_common.service.MinioService.*(..))")
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("MinioServiceAspect | Before MinioService method got called");
    }

    @After(value="execution(* com.yoyo.admin.minio_common.service.MinioService.*(..))")
    public void afterAdvice(JoinPoint joinPoint){
        System.out.println("MinioServiceAspect | After MinioService method got called");
    }

    @AfterReturning(value="execution(* com.yoyo.admin.minio_common.service.MinioService.*(..))")
    public void afterReturningAdvice(JoinPoint joinPoint){
        System.out.println("MinioServiceAspect | AfterReturning MinioService method got called");
    }
}
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

MinioConfig.java

package com.yoyo.admin.minio_common.config;

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    /** It's a URL, domain name, IPv4 perhaps IPv6 Address */
    private String endpoint;

    /** TCP/IP Port number */
    private Integer port;

    /** AccessKey Similar to user ID, Used to uniquely identify your account */
    private String accessKey;

    /** SecretKey,It's the password for your account */
    private String secretKey;

    /** If it is true, It uses https instead of http, The default value is true */
    private boolean secure;

    /** Default bucket */
    private String bucketName;

    /** The maximum size of the picture  */
    private long imageSize;

    /** Maximum size of other files  */
    private long fileSize;

    @Bean
    public MinioClient minioClient() {
        MinioClient minioClient =
                MinioClient.builder()
                        .credentials(accessKey, secretKey)
                        .endpoint(endpoint,port,secure)
                        .build();
        return minioClient;
    }
}
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

MinioService.java

package com.yoyo.admin.minio_common.service;

import com.yoyo.admin.minio_common.config.MinioConfig;
import com.yoyo.admin.minio_common.util.MinioUtils;
import io.minio.messages.Bucket;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class MinioService {

    private static final Logger LOGGER = LoggerFactory.getLogger(MinioService.class);

    private final MinioUtils minioUtils;
    private final MinioConfig minioProperties;

    /**
     * 检查存储桶是否已经存在
     * @param bucketName
     * @return
     */
    public boolean bucketExists(String bucketName) {
        LOGGER.info("MinioService | bucketExists is called");
        return minioUtils.bucketExists(bucketName);
    }

    /**
     * 创建存储桶
     * @param bucketName
     */
    public void makeBucket(String bucketName) {
        LOGGER.info("MinioService | makeBucket is called");
        LOGGER.info("MinioService | makeBucket | bucketName : " + bucketName);
        minioUtils.makeBucket(bucketName);
    }

    /**
     * 列出所有存储桶名称
     * @return
     */
    public List<String> listBucketName() {
        LOGGER.info("MinioService | listBucketName is called");
        return minioUtils.listBucketNames();
    }

    /**
     * 列出所有存储桶
     * @return
     */
    public List<Bucket> listBuckets() {
        LOGGER.info("MinioService | listBuckets is called");
        return minioUtils.listBuckets();
    }

    /**
     * 根据名称删除存储桶
     * @param bucketName
     * @return
     */
    public boolean removeBucket(String bucketName) {
        LOGGER.info("MinioService | removeBucket is called");
        LOGGER.info("MinioService | removeBucket | bucketName : " + bucketName);
        return minioUtils.removeBucket(bucketName);
    }

    /**
     * 列出指定存储桶的所有文件对象
     * @param bucketName
     * @return
     */
    public List<String> listObjectName(String bucketName) {
        LOGGER.info("MinioService | listObjectName is called");
        LOGGER.info("MinioService | listObjectName | bucketName : " + bucketName);
        return minioUtils.listObjectName(bucketName);
    }

    /**
     * 上传文件对象到指定存储桶
     * @param multipartFile
     * @param bucketName
     * @param fileType
     */
    @SneakyThrows
    public void putObject(MultipartFile multipartFile, String bucketName, String fileType) {
        LOGGER.info("MinioService | putObject is called");
        try {
            bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : minioProperties.getBucketName();
            LOGGER.info("MinioService | putObject | bucketName : " + bucketName);
            if (!this.bucketExists(bucketName)) {
                this.makeBucket(bucketName);
                LOGGER.info("MinioService | putObject | bucketName : " + bucketName + " created");
            }
            String fileName = multipartFile.getOriginalFilename();
            LOGGER.info("MinioService | getFileType | fileName : " + fileName);
            long fileSize = multipartFile.getSize();
            LOGGER.info("MinioService | getFileType | fileSize : " + fileSize);
            assert fileName != null;
            String objectName = UUID.randomUUID().toString().replaceAll("-", "")
                    + fileName.substring(fileName.lastIndexOf("."));
            LOGGER.info("MinioService | getFileType | objectName : " + objectName);
            LocalDateTime createdTime = LocalDateTime.now();
            LOGGER.info("MinioService | getFileType | createdTime : " + createdTime);
            minioUtils.putObject(bucketName, multipartFile, objectName,fileType);
            LOGGER.info("MinioService | getFileType | url : " + minioProperties.getEndpoint()+"/"+bucketName+"/"+objectName);
        } catch (Exception e) {
            LOGGER.info("MinioService | getFileType | Exception : " + e.getMessage());
        }
    }

    /**
     * 从指定存储桶下载文件
     * @param bucketName
     * @param objectName
     * @return
     */
    public InputStream downloadObject(String bucketName, String objectName) {
        LOGGER.info("MinioService | downloadObject is called");
        LOGGER.info("MinioService | downloadObject | bucketName : " + bucketName);
        LOGGER.info("MinioService | downloadObject | objectName : " + objectName);
        return minioUtils.getObject(bucketName,objectName);
    }

    /**
     * 从指定存储桶删除文件对象
     * @param bucketName
     * @param objectName
     * @return
     */
    public boolean removeObject(String bucketName, String objectName) {
        LOGGER.info("MinioService | removeObject is called");
        LOGGER.info("MinioService | removeObject | bucketName : " + bucketName);
        LOGGER.info("MinioService | removeObject | objectName : " + objectName);
        return minioUtils.removeObject(bucketName, objectName);
    }

    /**
     * 从指定存储桶批量删除文件对象
     * @param bucketName
     * @param objectNameList
     * @return
     */
    public boolean removeListObject(String bucketName, List<String> objectNameList) {
        LOGGER.info("MinioService | removeListObject is called");
        LOGGER.info("MinioService | removeObject | bucketName : " + bucketName);
        LOGGER.info("MinioService | removeObject | objectNameList size : " + objectNameList.size());
        return minioUtils.removeObject(bucketName, objectNameList);
    }

    /**
     * 从存储桶中获取文件路径
     * @param bucketName
     * @param objectName
     * @return
     */
    public String getObjectUrl(String bucketName, String objectName) {
        LOGGER.info("MinioService | getObjectUrl is called");
        LOGGER.info("MinioService | getObjectUrl | bucketName : " + bucketName);
        LOGGER.info("MinioService | getObjectUrl | objectName : " + objectName);
        return minioUtils.getObjectUrl(bucketName, objectName);
    }
}
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

FileTypeUtils.java

package com.yoyo.admin.minio_common.util;

import cn.hutool.core.io.FileTypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;


public class FileTypeUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(FileTypeUtils.class);

    private final static String IMAGE_TYPE = "image/";
    private final static String AUDIO_TYPE = "audio/";
    private final static String VIDEO_TYPE = "video/";
    private final static String APPLICATION_TYPE = "application/";
    private final static String TXT_TYPE = "text/";

    /**
     * 获取文件类型
     * @param multipartFile
     * @return
     */
    public static String getFileType(MultipartFile multipartFile) {
        InputStream inputStream = null;
        String type = null;
        try {
            inputStream = multipartFile.getInputStream();
            type = FileTypeUtil.getType(inputStream);
            LOGGER.info("FileTypeUtils | getFileType | type : " + type);
            if ("JPG".equalsIgnoreCase(type) || "JPEG".equalsIgnoreCase(type)
                    || "GIF".equalsIgnoreCase(type) || "PNG".equalsIgnoreCase(type)
                    || "BMP".equalsIgnoreCase(type) || "PCX".equalsIgnoreCase(type)
                    || "TGA".equalsIgnoreCase(type) || "PSD".equalsIgnoreCase(type)
                    || "TIFF".equalsIgnoreCase(type)) {

                LOGGER.info("FileTypeUtils | getFileType | IMAGE_TYPE+type : " + IMAGE_TYPE + type);
                return IMAGE_TYPE+type;
            }
            if ("mp3".equalsIgnoreCase(type) || "OGG".equalsIgnoreCase(type)
                    || "WAV".equalsIgnoreCase(type) || "REAL".equalsIgnoreCase(type)
                    || "APE".equalsIgnoreCase(type) || "MODULE".equalsIgnoreCase(type)
                    || "MIDI".equalsIgnoreCase(type) || "VQF".equalsIgnoreCase(type)
                    || "CD".equalsIgnoreCase(type)) {
                LOGGER.info("FileTypeUtils | getFileType | AUDIO_TYPE+type : " + AUDIO_TYPE + type);
                return AUDIO_TYPE+type;
            }
            if ("mp4".equalsIgnoreCase(type) || "avi".equalsIgnoreCase(type)
                    || "MPEG-1".equalsIgnoreCase(type) || "RM".equalsIgnoreCase(type)
                    || "ASF".equalsIgnoreCase(type) || "WMV".equalsIgnoreCase(type)
                    || "qlv".equalsIgnoreCase(type) || "MPEG-2".equalsIgnoreCase(type)
                    || "MPEG4".equalsIgnoreCase(type) || "mov".equalsIgnoreCase(type)
                    || "3gp".equalsIgnoreCase(type)) {
                LOGGER.info("FileTypeUtils | getFileType | VIDEO_TYPE+type : " + VIDEO_TYPE + type);
                return VIDEO_TYPE+type;
            }
            if ("doc".equalsIgnoreCase(type) || "docx".equalsIgnoreCase(type)
                    || "ppt".equalsIgnoreCase(type) || "pptx".equalsIgnoreCase(type)
                    || "xls".equalsIgnoreCase(type) || "xlsx".equalsIgnoreCase(type)
                    || "zip".equalsIgnoreCase(type)|| "jar".equalsIgnoreCase(type)) {
                LOGGER.info("FileTypeUtils | getFileType | APPLICATION_TYPE+type : " + APPLICATION_TYPE + type);
                return APPLICATION_TYPE+type;
            }
            if ("txt".equalsIgnoreCase(type)) {
                LOGGER.info("FileTypeUtils | getFileType | TXT_TYPE+type : " + TXT_TYPE + type);
                return TXT_TYPE+type;
            }
        }catch (IOException e){
            LOGGER.info("FileTypeUtils | getFileType | IOException : " + e.getMessage());
        }
        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
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

MinioUtils.java

package com.yoyo.admin.minio_common.util;

import com.yoyo.admin.minio_common.config.MinioConfig;
import io.minio.*;
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.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
@RequiredArgsConstructor
public class MinioUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(MinioUtils.class);

    private final MinioClient minioClient;
    private final MinioConfig minioConfig;

    /**
     * 上传文件到指定存储桶
     * @param bucketName
     * @param multipartFile
     * @param filename
     * @param fileType
     */
    @SneakyThrows
    public void putObject(String bucketName, MultipartFile multipartFile, String filename, String fileType) {
        LOGGER.info("MinioUtil | putObject is called");
        LOGGER.info("MinioUtil | putObject | filename : " + filename);
        LOGGER.info("MinioUtil | putObject | fileType : " + fileType);
        InputStream inputStream = new ByteArrayInputStream(multipartFile.getBytes());
        minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(filename).stream(
                        inputStream, -1, minioConfig.getFileSize())
                        .contentType(fileType)
                        .build());
    }

    /**
     * 检查存储桶是否存在
     * @param bucketName
     * @return
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        LOGGER.info("MinioUtil | bucketExists is called");
        boolean found =
                minioClient.bucketExists(
                        BucketExistsArgs.builder().
                                bucket(bucketName).
                                build());
        LOGGER.info("MinioUtil | bucketExists | found : " + found);
        if (found) {
            LOGGER.info("MinioUtil | bucketExists | message : " + bucketName + " exists");
        } else {
            LOGGER.info("MinioUtil | bucketExists | message : " + bucketName + " does not exist");
        }
        return found;
    }

    /**
     * 创建存储桶
     * @param bucketName
     * @return
     */
    @SneakyThrows
    public boolean makeBucket(String bucketName) {
        LOGGER.info("MinioUtil | makeBucket is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | makeBucket | flag : " + flag);
        if (!flag) {
            minioClient.makeBucket(
                    MakeBucketArgs.builder()
                            .bucket(bucketName)
                            .build());

            return true;
        } else {
            return false;
        }
    }

    /**
     * 列出存储桶
     * @return
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        LOGGER.info("MinioUtil | listBuckets is called");
        return minioClient.listBuckets();
    }

    /**
     * 列出所有存储桶名称
     * @return
     */
    @SneakyThrows
    public List<String> listBucketNames() {
        LOGGER.info("MinioUtil | listBucketNames is called");
        List<Bucket> bucketList = listBuckets();
        LOGGER.info("MinioUtil | listBucketNames | bucketList size : " + bucketList.size());
        List<String> bucketListName = new ArrayList<>();
        for (Bucket bucket : bucketList) {
            bucketListName.add(bucket.name());
        }
        LOGGER.info("MinioUtil | listBucketNames | bucketListName size : " + bucketListName.size());
        return bucketListName;
    }

    /**
     * 列出指定存储桶中的所有对象
     * @param bucketName
     * @return
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName) {
        LOGGER.info("MinioUtil | listObjects is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | listObjects | flag : " + flag);
        if (flag) {
            return minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).build());
        }
        return null;
    }

    /**
     * 按名称删除存储桶
     * @param bucketName
     * @return
     */
    @SneakyThrows
    public boolean removeBucket(String bucketName) {
        LOGGER.info("MinioUtil | removeBucket is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | removeBucket | flag : " + flag);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                // 当存储桶中有文件对象时删除失败
                LOGGER.info("MinioUtil | removeBucket | item size : " + item.size());
                if (item.size() > 0) {
                    return false;
                }
            }
            // 桶为空时删除桶
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            flag = bucketExists(bucketName);
            LOGGER.info("MinioUtil | removeBucket | flag : " + flag);
            if (!flag) {
                return true;
            }
        }
        return false;
    }

    /**
     * 列出指定存储桶中的所有对象名称
     * @param bucketName
     * @return
     */
    @SneakyThrows
    public List<String> listObjectName(String bucketName) {
        LOGGER.info("MinioUtil | listObjectName is called");
        List<String> listObjectName = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | listObjectName | flag : " + flag);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                listObjectName.add(item.objectName());
            }
        } else {
            listObjectName.add(" Bucket does not exist ");
        }
        LOGGER.info("MinioUtil | listObjectName | listObjectNames size : " + listObjectName.size());
        return listObjectName;
    }

    /**
     * 从指定存储桶中删除指定对象
     * @param bucketName
     * @param objectName
     * @return
     */
    @SneakyThrows
    public boolean removeObject(String bucketName, String objectName) {
        LOGGER.info("MinioUtil | removeObject is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | removeObject | flag : " + flag);
        if (flag) {
            minioClient.removeObject(
                    RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return true;
        }
        return false;
    }

    /**
     * 从指定存储桶获取文件路径
     * @param bucketName
     * @param objectName
     * @return
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName) {
        LOGGER.info("MinioUtil | getObjectUrl is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | getObjectUrl | flag : " + flag);
        String url = "";
        if (flag) {
            url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(2, TimeUnit.MINUTES)
                            .build());
            LOGGER.info("MinioUtil | getObjectUrl | url : " + url);
        }
        return url;
    }

    /**
     * 从指定的存储桶中获取对象的元数据
     * @param bucketName
     * @param objectName
     * @return
     */
    @SneakyThrows
    public StatObjectResponse statObject(String bucketName, String objectName) {
        LOGGER.info("MinioUtil | statObject is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | statObject | flag : " + flag);
        if (flag) {
            StatObjectResponse stat =
                    minioClient.statObject(
                            StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
            LOGGER.info("MinioUtil | statObject | stat : " + stat.toString());
            return stat;
        }
        return null;
    }

    /**
     * 从指定的存储桶中获取文件对象作为流
     * @param bucketName
     * @param objectName
     * @return
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        LOGGER.info("MinioUtil | getObject is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | getObject | flag : " + flag);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                InputStream stream =
                        minioClient.getObject(
                                GetObjectArgs.builder()
                                        .bucket(bucketName)
                                        .object(objectName)
                                        .build());
                LOGGER.info("MinioUtil | getObject | stream : " + stream.toString());
                return stream;
            }
        }
        return null;
    }

    /**
     * 从指定的存储桶中获取一个文件对象作为流(断点下载)
     * @param bucketName
     * @param objectName
     * @param offset
     * @param length
     * @return
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName, long offset, Long length) {
        LOGGER.info("MinioUtil | getObject is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | getObject | flag : " + flag);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                InputStream stream =
                        minioClient.getObject(
                                GetObjectArgs.builder()
                                        .bucket(bucketName)
                                        .object(objectName)
                                        .offset(offset)
                                        .length(length)
                                        .build());
                LOGGER.info("MinioUtil | getObject | stream : " + stream.toString());
                return stream;
            }
        }
        return null;
    }

    /**
     * 从指定存储桶中删除多个文件对象
     * @param bucketName
     * @param objectNames
     * @return
     */
    @SneakyThrows
    public boolean removeObject(String bucketName, List<String> objectNames) {
        LOGGER.info("MinioUtil | removeObject is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | removeObject | flag : " + flag);
        if (flag) {
            List<DeleteObject> objects = new LinkedList<>();
            for (int i = 0; i < objectNames.size(); i++) {
                objects.add(new DeleteObject(objectNames.get(i)));
            }
            Iterable<Result<DeleteError>> results =
                    minioClient.removeObjects(
                            RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                LOGGER.info("MinioUtil | removeObject | error : " + error.objectName() + " " + error.message());
                return false;
            }
        }
        return true;
    }

    /**
     * 将 InputStream 对象上传到指定存储桶
     * @param bucketName
     * @param objectName
     * @param inputStream
     * @param contentType
     * @return
     */
    @SneakyThrows
    public boolean putObject(String bucketName, String objectName, InputStream inputStream, String contentType) {
        LOGGER.info("MinioUtil | putObject is called");
        boolean flag = bucketExists(bucketName);
        LOGGER.info("MinioUtil | putObject | flag : " + flag);
        if (flag) {
            minioClient.putObject(
                    PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                            inputStream, -1, minioConfig.getFileSize())
                            .contentType(contentType)
                            .build());
            StatObjectResponse statObject = statObject(bucketName, objectName);
            LOGGER.info("MinioUtil | putObject | statObject != null : " + (statObject != null));
            LOGGER.info("MinioUtil | putObject | statObject.size() : " + statObject.size());
            if (statObject.size() > 0) {
                return true;
            }
        }
        return false;
    }
}
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

# 5.2 配置文件及Controller调用代码

配置文件 application.properties

## minio config
minio.endpoint=http://127.0.0.1:9000
minio.port=9000
# Login account
minio.accessKey=your_user
# Login password
minio.secretKey=your_password
minio.secure=false
# Default bucket name
minio.bucket-name=default
# Maximum size of picture file
minio.image-size=10485760
# Maximum file size
minio.file-size=1073741824
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在项目总的pom.xml里添加moudle

    <modules>
        <module>minio_common</module>
    </modules>
1
2
3

MinioController.java

package com.yoyo.admin.web_manage.controller;

import com.yoyo.admin.common.utils.ResultDataUtils;
import com.yoyo.admin.minio_common.service.MinioService;
import com.yoyo.admin.minio_common.util.FileTypeUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Api(tags = "Minio对象存储")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/web/minio")
public class MinioController {

    private static final Logger LOGGER = LoggerFactory.getLogger(MinioController.class);

    private final MinioService minioService;

    @Value("${minio.endpoint}")
    private String minioBaseUrl;

    @ApiOperation("上传文件对象到指定存储桶")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "file", value = "文件", dataType = "file", paramType = "form"),
    })
    @RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
    public ResponseEntity<?> uploadFile(MultipartFile file, String bucketName) {
        try {
            LOGGER.info("MinioController | uploadFile is called");
            LOGGER.info("MinioController | uploadFile | bucketName : " + bucketName);
            String fileType = FileTypeUtils.getFileType(file);
            LOGGER.info("MinioController | uploadFile | fileType : " + fileType);
            if (fileType != null) {
                minioService.putObject(file, bucketName, fileType);
            }
            return ResultDataUtils.success("Upload successfully");
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

    @ApiOperation("创建存储桶")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "bucketName", value = "存储桶名称", dataType = "String", paramType = "path"),
    })
    @RequestMapping(value = "/addBucket/{bucketName}", method = RequestMethod.POST)
    public ResponseEntity<?> addBucket(@PathVariable String bucketName) {
        try {
            LOGGER.info("MinioController | addBucket is called");
            LOGGER.info("MinioController | addBucket | bucketName : " + bucketName);
            minioService.makeBucket(bucketName);
            return ResultDataUtils.success("Bucket name "+ bucketName +" created");
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

    @ApiOperation("列出指定存储桶的所有文件对象")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "bucketName", value = "存储桶名称", dataType = "String", paramType = "path"),
    })
    @RequestMapping(value = "/listObjectName/{bucketName}", method = RequestMethod.GET)
    public ResponseEntity<?> listObjectName(@PathVariable String bucketName) {
        LOGGER.info("MinioController | listObjectName is called");
        LOGGER.info("MinioController | listObjectName | bucketName : " + bucketName);
        return ResultDataUtils.success(minioService.listObjectName(bucketName));
    }

    @ApiOperation("列出所有存储桶名称")
    @RequestMapping(value = "/listBucketName", method = RequestMethod.GET)
    public ResponseEntity<?> listBucketName() {
        LOGGER.info("MinioController | listBucketName is called");
        return ResultDataUtils.success(minioService.listBucketName());
    }

    @ApiOperation("根据名称删除存储桶")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "bucketName", value = "存储桶名称", dataType = "String", paramType = "path"),
    })
    @RequestMapping(value = "/removeBucket/{bucketName}", method = RequestMethod.GET)
    public ResponseEntity<?> removeBucket(@PathVariable String bucketName) {
        LOGGER.info("MinioController | removeBucket is called");
        LOGGER.info("MinioController | removeBucket | bucketName : " + bucketName);
        boolean state =  minioService.removeBucket(bucketName);
        LOGGER.info("MinioController | removeBucket | state : " + state);
        if(state){
            return ResultDataUtils.success("Delete bucket successfully");
        }else{
            return ResultDataUtils.error("Delete bucket failed");
        }
    }

    @ApiOperation("从指定存储桶删除文件对象")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "bucketName", value = "存储桶名称", dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "objectName", value = "文件对象名称", dataType = "String", paramType = "path"),
    })
    @RequestMapping(value = "/removeObject/{bucketName}/{objectName}", method = RequestMethod.GET)
    public ResponseEntity<?> removeObject(@PathVariable("bucketName") String bucketName,
                            @PathVariable("objectName") String objectName) {
        LOGGER.info("MinioController | removeObject is called");
        LOGGER.info("MinioController | removeObject | bucketName : " + bucketName);
        LOGGER.info("MinioController | removeObject | objectName : " + objectName);
        boolean state =  minioService.removeObject(bucketName, objectName);
        LOGGER.info("MinioController | removeObject | state : " + state);
        if(state){
            return ResultDataUtils.success("Delete object successfully");
        }else{
            return ResultDataUtils.error("Delete object failed");
        }
    }

    @ApiOperation("从指定存储桶批量删除文件对象")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "bucketName", value = "存储桶名称", dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "objectNameList", value = "文件对象列表", dataType = "List", paramType = "body"),
    })
    @RequestMapping(value = "/removeListObject/{bucketName}", method = RequestMethod.GET)
    public ResponseEntity<?> removeListObject(@PathVariable("bucketName") String bucketName,
                                   @RequestBody List<String> objectNameList) {
        LOGGER.info("MinioController | removeListObject is called");
        LOGGER.info("MinioController | removeListObject | bucketName : " + bucketName);
        LOGGER.info("MinioController | removeListObject | objectNameList size : " + objectNameList.size());
        boolean state =  minioService.removeListObject(bucketName, objectNameList) ;
        LOGGER.info("MinioController | removeListObject | state : " + state);
        if(state){
            return ResultDataUtils.success("Delete object successfully");
        }else{
            return ResultDataUtils.error("Delete object failed");
        }
    }

    @ApiOperation("查看指定存储桶的文件对象名称列表和及下载URL")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "bucketName", value = "存储桶名称", dataType = "String", paramType = "path"),
    })
    @RequestMapping(value = "/showListObjectNameAndDownloadUrl/{bucketName}", method = RequestMethod.GET)
    public ResponseEntity<?> showListObjectNameAndDownloadUrl(@PathVariable String bucketName) {
        try {
            LOGGER.info("MinioController | showListObjectNameAndDownloadUrl is called");
            LOGGER.info("MinioController | showListObjectNameAndDownloadUrl | bucketName : " + bucketName);
            Map<String, String> map = new HashMap<>();
            List<String> listObjectNames = minioService.listObjectName(bucketName);
            LOGGER.info("MinioController | showListObjectNameAndDownloadUrl | listObjectNames size : " + listObjectNames.size());
            String url = minioBaseUrl + "/" + bucketName + "/";
            LOGGER.info("MinioController | showListObjectNameAndDownloadUrl | url : " + url);
            for (int i = 0; i <listObjectNames.size() ; i++) {
                map.put(listObjectNames.get(i),url+listObjectNames.get(i));
            }
            LOGGER.info("MinioController | showListObjectNameAndDownloadUrl | map : " + map);
            return ResultDataUtils.success(map);
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

    @ApiOperation("从指定存储桶下载文件")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "bucketName", value = "存储桶名称", dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "objectName", value = "文件对象名称", dataType = "String", paramType = "path"),
    })
    @RequestMapping(value = "/download/{bucketName}/{objectName}", method = RequestMethod.GET)
    public ResponseEntity<?> download(HttpServletResponse response,
                         @PathVariable("bucketName") String bucketName,
                         @PathVariable("objectName") String objectName) {
        LOGGER.info("MinioController | download is called");
        LOGGER.info("MinioController | download | bucketName : " + bucketName);
        LOGGER.info("MinioController | download | objectName : " + objectName);
        InputStream in = null;
        try {
            in = minioService.downloadObject(bucketName, objectName);
            response.setHeader("Content-Disposition", "attachment;filename="
                    + URLEncoder.encode(objectName, "UTF-8"));
            response.setCharacterEncoding("UTF-8");
            // 从 InputStream 中删除字节复制到 OutputStream
            IOUtils.copy(in, response.getOutputStream());
        } catch (UnsupportedEncodingException e) {
            LOGGER.info("MinioController | download | UnsupportedEncodingException : " + e.getMessage());
        } catch (IOException e) {
            LOGGER.info("MinioController | download | IOException : " + e.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    LOGGER.info("MinioController | download | IOException : " + e.getMessage());
                }
            }
        }
        return ResultDataUtils.success("Download successfully");
    }
}
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

# 5.3 简略版接口文档

以下是简略版接口文档,完整版详见Swagger。

  • 创建存储桶 URL:/api/web/minio/addBucket/{bucketName} POST请求 请求参数bucketName
  • 上传文件对象到指定存储桶 URL:/api/web/minio/uploadFile POST请求 请求参数bucketName,file
  • 列出所有存储桶名称 URL:/api/web/minio/listBucketName GET请求 无请求参数
  • 列出指定存储桶的所有文件对象 URL:/api/web/minio/listObjectName/{bucketName} GET请求 请求参数bucketName
  • 从指定存储桶下载文件 URL:/api/web/minio/download/{bucketName}/{objectName} GET请求 请求参数bucketName,objectName
  • 查看指定存储桶的文件对象名称列表和及下载URL URL:/api/web/minio/showListObjectNameAndDownloadUrl/{bucketName} GET请求 请求参数bucketName
  • 从指定存储桶删除文件对象 URL:/api/web/minio/removeObject/{bucketName}/{objectName} GET请求 请求参数bucketName,objectName
  • 从指定存储桶批量删除文件对象 URL:/api/web/minio/removeListObject/{bucketName} GET请求 请求参数bucketName,objectNameList(放body里)
  • 根据名称删除存储桶 URL:/api/web/minio/removeBucket/{bucketName} GET请求 请求参数bucketName

# 6. 参考资料

[1] MinIO 基于 python 把目录下的文件上传到 minio from 夏来风 (opens new window)

[2] Python连接MINIO Api, 实现上传下载等功能 from 一只技术小白 (opens new window)

[3] Spring Boot 使用在 Docker 上运行的 Minio、AOP 和异常处理 from Github (opens new window)

[4] Minio starter for Spring Boot from Github (opens new window)

Last Updated: 3/30/2023, 5:29:27 PM