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
结局:存储桶的命名定义规则为:
-
存储桶名称必须介于 3 到 63 个字符之间
-
存储桶名称只能由小写字母、数字、句点 (.) 和连字符 (-) 组成
-
存储桶名称必须以字母或数字开头和结尾
-
存储桶名称不得采用 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>