Bootstrap

MinioClient

Minio 的基本食用方法

1. 创建MinioClient对象

MinioClient minioClient = MinioClient.builder()
    .endpoint("http://localhots:9000")
    .credentials("minioadmin", "minioadmin")
    .build();

2. 检查存储桶是否已经存在

boolean isExist = minioClient.bucketExists(
    BucketExistsArgs.builder()
        .bucket("桶名称")
        .build());
if (isExist) {
	System.out.println("Bucket already exists.");
} else {
	System.out.println("Bucket not exists.");
}

3. 创建一个存储桶

minioClient.makeBucket(
    MakeBucketArgs.builder()
        .bucket("桶名称")
        .build());

4. 列出所有桶的名称

List<Bucket> buckets = minioClient.listBuckets();
for (Bucket i : buckets){
    System.out.println(i.name());
}

5. 删除一个桶

如果删除的桶不为空,会抛异常。不能执行
minioClient.removeBucket(
    RemoveBucketArgs.builder()
        .bucket("桶名称")
        .build());

6. 获取链接,并设置超时时间。最长时间 7 天

String url = minioClient.getPresignedObjectUrl(
        GetPresignedObjectUrlArgs.builder()
                .bucket("rpa-oc")
                .object("DEMO.mp4")
                .expiry(5, TimeUnit.SECONDS) // 设置5秒的超时时间。
                .method(Method.GET)
                .build());
System.out.println("下载地址:"+url);

7. upload上传文件 + 查看

上传时,同名的会覆盖。
上传时,必须加文件后缀。否则识别不出文件类型。
minioClient.uploadObject(
    UploadObjectArgs.builder()
        .bucket("rpa-oc")
        .object("DEMO.mp4")
        .filename("d:\\desktop\\DEMO.mp4")
        .contentType("video/mp4").build());


Iterable<Result<Item>> myObjects = minioClient.listObjects(
	ListObjectsArgs.builder()
		.bucket("rpa-oc")
		.build());
for (Result<Item> result : myObjects) {
    Item item = result.get();
    listObjectNames.add(item.objectName());
}
System.out.println("集合大小:" + listObjectNames.size() + "存储桶对象名称集合:" + listObjectNames);

8. 存储桶文件详情

if (isExist) {
    //存储桶文件详情
    Iterable<Result<Item>> it = minioClient.listObjects(
        ListObjectsArgs.builder()
        .bucket("rpa-oc")
        .build()
    );
    Iterator<Result<Item>> iter = it.iterator();
    int i = 0;
    while (iter.hasNext()) {
        i = i + 1;
        Result<Item> result = iter.next();
        Item item = result.get();
        System.out.println("存储文件详情=================文件详情:" + i);
        System.out.println("etag:" + item.etag());
        System.out.println("objectName:" + item.objectName());
        System.out.println("isDir:" + item.isDir());
        System.out.println("lastModified:" + item.lastModified());
        System.out.println("owner:" + item.owner().displayName());
        System.out.println("size:" + item.size());
        System.out.println("userMetadata:" + item.userMetadata());
        System.out.println("storageClass:" + item.storageClass());
        System.out.println("item to String:" + item.toString());
    }
}

9. putObject 方式上传

前者是知道文件路径,此方式是随便一个什么文件,只要选中就行。
@PostMapping("/submit")
public ResponseData upload(MultipartFile file) {
    if (!file.isEmpty()) {
        String bucketName = prop.getBucketName();

        try {
            bucketExists(bucketName);

            String contentType = file.getContentType();
            InputStream inputStream = file.getInputStream();
            String originalFilename = file.getOriginalFilename();
            long size = file.getSize();

            minioClientBean.putObject(
                PutObjectArgs.builder()
                	.bucket(prop.getBucketName())
                	.stream(inputStream, size, -1)
                	.contentType(contentType)
                	.object("test/" + originalFilename)
                	.bucket(bucketName)
                	.build());
            return ResponseData.success("upload success");
        } catch (Exception e) {
            System.out.println("msg:" + e.getMessage() + "\n" + "cause:" + e.getCause());
            return ResponseData.error(null);
        }
    } else {
        return ResponseData.success("当前上传文件为空");
    }
}

10. 下载文件(3种方式)

  • 方式一

使用 [6. 获取链接,并设置超时时间。最长时间 7 天](#6. 获取链接,并设置超时时间。最长时间 7 天) 也能下载

貌似使用这种方法是比较妥当的一种方式了。访问到的资源是.zip 、.exe 可以直接下载保存

  • 方式二

后端代码直接下载。

minioClientBean.downloadObject(
    DownloadObjectArgs.builder()
    	.filename(期望下载到的文件路径和名称)
    	.bucket(桶名称)
    	.object(minio服务器上要下载的对象路径)
    	.overwrite(true) // 是否覆盖
    	.build());
  • 方式三

使用发送请求的方式下载

—bug: 在网址上面

content-type 指示响应内容的格式

content-disposition 指示如何处理响应内容。

一般有两种方式:

  • inline:直接在页面显示
  • attchment:以附件形式下载

@GetMapping("/download")
    public Object download(String filePath, HttpServletResponse response) {
        try {
            MinioClient minioClientBean = MinioClient.builder()
                    .endpoint(minioProp.getEndpoint())
                    .credentials(minioProp.getAccessKey(), minioProp.getSecretKey())
                    .build();
            GetObjectResponse object = minioClientBean.getObject(
                    GetObjectArgs.builder()
                            .object(filePath)
                            .bucket(minioProp.getBucketName())
                            .build());
            String[] fileName = object.object().split("/");
            
        	// 以附件的形式下载。就会出现弹出框询问保存地址了。
            response.addHeader("Content-Disposition", "attachment; filename=" + fileName[fileName.length - 1]);
            return object.readAllBytes();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

前端上传模块代码

  • 界面
<el-upload
           class="upload-demo"
           drag
           action=""
           accept=".avi,.mp4"
           :auto-upload="true"
           :on-change="handleChange"
           :before-upload="handleBeforeUpload"
           :on-exceed="handleExceed"
           :http-request="myUpload"
           >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">
        将文件拖到此处,或<em>点击上传</em><br />
        支持扩展名:.avi .mp4(仅支持1个文件)
    </div>
</el-upload>
  • js
// 自定义上传方式
myUpload(file) {
    console.log(file);
    let form = new FormData();
    form.append("file", file.file);
    console.log("文件类型", file.file.type);
    this.$axios.post(`/rpa/uploader/submit`, form).then((res) => {
        console.log("res", JSON.parse(JSON.stringify(res)));
    });
},

minio 踩坑记录:

1 、错误:存储桶命名不规范

bucket name does not follow Amazon S3 standards

结局:存储桶的命名定义规则为:

  1. 存储桶名称必须介于 3 到 63 个字符之间

  2. 存储桶名称只能由小写字母、数字、句点 (.) 和连字符 (-) 组成

  3. 存储桶名称必须以字母或数字开头和结尾

  4. 存储桶名称不得采用 IP 地址格式(例如,192.168.5.4)

2 、错误:创建存储同客户端时,端口选择错误

Error occurred: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,3]
Message: 文档中根元素前面的标记必须格式正确。

解决:创建 MinioClient 时要用9000,而9001用于网页访问控制台。endpoint 构造函数比较重要

MinioClient minioClient = MinioClient.builder()
    .endpoint("http://localhots:9000")
    .credentials("minioadmin", "minioadmin")
    .build();

3 、错误:okhttp3 …Must use okhttp >= 4.8.1 …少 maven 包

Exception in thread "main" java.lang.ExceptionInInitializerError
	at com.rpaoc.controller.FileUploader.main(FileUploader.java:20)
Caused by: java.lang.RuntimeException: Unsupported OkHttp library found. Must use okhttp >= 4.8.1
	at io.minio.S3Base.<clinit>(S3Base.java:101)
	... 1 more
Caused by: java.lang.NoSuchMethodError: okhttp3.RequestBody.create([BLokhttp3/MediaType;)Lokhttp3/RequestBody;
	at io.minio.S3Base.<clinit>(S3Base.java:99)
	... 1 more

解决:导入包

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.0</version>
</dependency>

4 、错误:springboot 配置顶上爆红(不影响运行)

springboot configuration annotation processor not configured

解决:添加 maven 包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

5 、错误:前后端传参,无法识别参数 || 后端接收不到参数。

后端接收参数类型定义

解决:后端接口参数是 MultipartFile 对象

且前端传参对象是 FormData 对象

let form = new FormData();
form.append("file", file.file);

6 、错误:上传文件报错

The difference between the request time and the server’s time is too large

系统时区与硬件时区不一致导致的

解决:修改一下时间就行了

完整功能代码::

1具体功能实现

FileUploaderController.java

package com.rpaoc.controller;


import com.rpaoc.entity.MinioProp;
import com.touch.base.web.ResponseData;
import io.minio.*;
import io.minio.http.Method;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 对象存储、上传资源 管理
 *
 * @author Liu RuiLin
 * @date 2022/4/26 10:34
 * @since 1.0
 **/
@RestController
@RequestMapping("/uploader")
public class FileUploaderController {

    @Autowired
    MinioProp minioProp;

    /**
     * 上传项目包文件、演示视频
     *
     * @param file 前端传参文件
     * @return
     */
    @PostMapping("/submit")
    public ResponseData upload(MultipartFile file) {

        if (!file.isEmpty()) {
            String bucketName = minioProp.getBucketName();

            try {
                String contentType = file.getContentType();
                InputStream inputStream = file.getInputStream();
                long size = file.getSize();

                int year = LocalDateTime.now().getYear();
                int month = LocalDateTime.now().getMonthValue();
                int day = LocalDateTime.now().getDayOfMonth();


                String filePath = null;
                String uuid = UUID.randomUUID().toString();

                // 封装路径
                if ("video/mp4".equals(contentType)) {
                    filePath = "mp4/" + year + "/" + month + "/" + day + "/" + uuid + ".mp4";
                } else if ("application/x-zip-compressed".equals(contentType)) {
                    filePath = "zip/" + year + "/" + month + "/" + day + "/" + uuid + ".zip";
                } else if ("image/jpeg".equals(contentType)) {
                    filePath = "user_avatar/" + year + "/" + month + "/" + day + "/" + uuid + ".jpg";
                } else if ("image/png".equals(contentType)) {
                    filePath = "user_avatar/" + year + "/" + month + "/" + day + "/" + uuid + ".png";
                }

                System.out.println("上传文件路径" + filePath);

                // MinioClient 实例对象
                MinioClient minioClientBean = MinioClient.builder()
                        .endpoint(minioProp.getEndpoint())
                        .credentials(minioProp.getAccessKey(), minioProp.getSecretKey())
                        .build();

                // 判断桶 是否存在
                Boolean exists = minioClientBean.bucketExists(
                        BucketExistsArgs.builder()
                                .bucket(bucketName)
                                .build());

                if (exists) {
                    minioClientBean.putObject(
                            PutObjectArgs.builder()
                                    .bucket(minioProp.getBucketName())
                                    .stream(inputStream, size, -1)
                                    .contentType(contentType)
                                    .object(filePath)
                                    .bucket(bucketName)
                                    .build());
                    return ResponseData.success(filePath, "上传成功");
                } else {
                    minioClientBean.makeBucket(
                            MakeBucketArgs.builder()
                                    .bucket(bucketName)
                                    .build());

                    MinioClient client = MinioClient.builder()
                            .endpoint(minioProp.getEndpoint())
                            .credentials(minioProp.getAccessKey(), minioProp.getSecretKey())
                            .build();
                    client.putObject(
                            PutObjectArgs.builder()
                                    .bucket(minioProp.getBucketName())
                                    .stream(inputStream, size, -1)
                                    .contentType(contentType)
                                    .object(filePath)
                                    .bucket(bucketName)
                                    .build());
                    return ResponseData.success(filePath, "上传成功");
                }
            } catch (Exception e) {
                System.out.println("msg:" + e.getMessage() + "\n" + "cause:" + e.getCause());
                return ResponseData.error("上传失败");
            }
        } else {
            return ResponseData.success(null, "当前上传文件为空");
        }
    }


    /**
     * 从 minio桶中 删除一个文件
     *
     * @param filePath 文件路径
     * @return
     */
    @DeleteMapping
    public ResponseData deleteFile(String filePath) {
        System.out.println("删除文件路径:" + filePath);
        if (filePath != null) {
            try {
                MinioClient minioClientBean = MinioClient.builder()
                        .endpoint(minioProp.getEndpoint())
                        .credentials(minioProp.getAccessKey(), minioProp.getSecretKey())
                        .build();

                minioClientBean.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(minioProp.getBucketName())
                                .object(filePath)
                                .build());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ResponseData.success(null, "当前上传文件为空");
    }


    /**
     * 通过 文件路径获取链接
     *
     * @param filePath 文件路径
     * @return 链接
     */
    @GetMapping
    public ResponseData getObjectUrl(String filePath) {
        try {
            MinioClient minioClientBean = MinioClient.builder()
                    .endpoint(minioProp.getEndpoint())
                    .credentials(minioProp.getAccessKey(), minioProp.getSecretKey())
                    .build();
            String url = minioClientBean.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(minioProp.getBucketName())
                            .object(filePath)
                            .expiry(30, TimeUnit.MINUTES)
                            .build());
            return ResponseData.success(url);
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseData.error(null);
        }
    }
}

2 配置服务器参数等

MinioProp.java

package com.rpaoc.entity;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * minio 的配置
 * @author Liu RuiLin
 * @date 2022/4/26 9:32
 * @since 1.0
 **/
@Component
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioProp {
    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucketName;
}

3 修改上传文件大小限制,设置服务器参数(以后方便修改)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/rpa_oc?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: local
    password: 123
  jpa:
    database-platform: org.hibernate.dialect.MySQL5Dialect
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true

  # 设置文件上传的默认大小
  servlet:
    multipart:
      max-file-size: 10MB   # 单个文件的最大大小
      max-request-size: 10MB # 单次请求的文件最大大小

server:
  port: 9002
minio:
  bucket-name: rpa-oc
  endpoint: http://192.168.3.200:3000
  access-key: minioadmin
  secret-key: minioadmin

4 maven 包

<!--使用minio需要的包-->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.3.9</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.0</version>
</dependency>

<!--设置配置文件时,顶上标红-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
;