赞
踩
目录
springboot作为目前主流的java开发框架,用来简化spring程序的创建和开发过程。在实际开发过程中我们往往会遇到部分涉及文件上传、下载的场景。经过多方筛选最终选用了Minio作为项目与中的文件存储工具。
Minio作为一款高性能、可扩展、分布式对象存储系统,并且它是完全开源免费的一款工具,非常适合实际项目的使用!!!
关于Minio的Windows的安装教程,各位小伙伴可以参考上一篇Minio安装的教程
地址:Minio基于Window系统的安装与使用-CSDN博客
下面让我们一起来了解一下SpringBoot整合Minio的具体实现方式。
在使用Minio之前我们要做的第一件事情就是有一个SpringBoot项目,需要小伙伴自行创建或者使用现有项目。土豆这里使用的是自己原有的一个若以框架改过来的一个项目,但是这个项目此前没有使用过Minio,确认这点有助于更加顺利的完成我们的测试。
如果小伙伴的项目中原本使用过Minio的,也没关系哈,可以根据本文做一个参考进一步完善项目内容。
准备好项目之后咱们就可以开始本次的任务了,请跟随土豆一起搞起来,呦吼!!!
在pom.xml中引入Minio的依赖:
- <dependencies>
- <!--Minio-->
- <dependency>
- <groupId>io.minio</groupId>
- <artifactId>minio</artifactId>
- <version>8.4.6</version>
- </dependency>
- <!--okhttp3 依赖-->
- <dependency>
- <groupId>com.squareup.okhttp3</groupId>
- <artifactId>okhttp</artifactId>
- <version>4.9.3</version>
- </dependency>
- </dependencies>
application.yml配置信息
- # 配置minio
- minio:
- server: http://localhost
- port: 9000
- endpoint: ${minio.server}:${minio.port}
- access-key: admin # minio账号
- secret-key: admin118110 # minio密码
- bucket: demo001 # 指定桶名
完成项目的基础配置之后,接下来咱开始来编写我们的代码
-
- import io.minio.MinioClient;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.context.annotation.Bean;
- import org.springframework.stereotype.Component;
-
- /**
- * @author YHL
- * @date 2024/4/24 9:36
- * @description 当前配置类不建议使用@Data注解,需要手动生成get,set方法
- */
- @Component
- @ConfigurationProperties(prefix = "minio")
- public class MinIoClientConfig {
-
- @Value("${minio.server}")
- private String server;
- @Value("${minio.port}")
- private int port;
- @Value("${minio.endpoint}")
- private String endpoint;
- @Value("${minio.access-key}")
- private String accessKey;
- @Value("${minio.secret-key}")
- private String secretKey;
- @Value("${minio.bucket}")
- private String bucket;
-
- /**
- * 创建minio连接对象
- * @return
- */
- @Bean
- public MinioClient minioClient(){
- return MinioClient.builder()
- .endpoint(server,port,false)
- .credentials(accessKey,secretKey)
- .build();
- }
-
- public String getServer() {
- return server;
- }
-
- public void setServer(String server) {
- this.server = server;
- }
-
- public int getPort() {
- return port;
- }
-
- public void setPort(int port) {
- this.port = port;
- }
-
- public String getEndpoint() {
- return endpoint;
- }
-
- public void setEndpoint(String endpoint) {
- this.endpoint = endpoint;
- }
-
- public String getAccessKey() {
- return accessKey;
- }
-
- public void setAccessKey(String accessKey) {
- this.accessKey = accessKey;
- }
-
- public String getSecretKey() {
- return secretKey;
- }
-
- public void setSecretKey(String secretKey) {
- this.secretKey = secretKey;
- }
-
- public String getBucket() {
- return bucket;
- }
-
- public void setBucket(String bucket) {
- this.bucket = bucket;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- package com.psy.common.utils;
-
- import cn.hutool.core.util.ObjectUtil;
- 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.SneakyThrows;
- import org.apache.commons.lang.BooleanUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.stereotype.Component;
- import org.springframework.web.multipart.MultipartFile;
-
- import javax.servlet.http.HttpServletResponse;
- import java.io.*;
- import java.net.URLEncoder;
- import java.nio.charset.StandardCharsets;
- import java.text.SimpleDateFormat;
- import java.util.*;
- import java.util.concurrent.TimeUnit;
- import java.util.stream.Collectors;
-
- /**
- * @author YHL
- * @date 2024/4/24 9:51
- * @description
- */
- @Component
- public class MinioUtil {
-
- @Autowired
- private MinioClient minioClient;
- @Value("${minio.bucket}")
- private String bucket;
-
- /**
- * description: 判断bucket是否存在,不存在则创建
- */
- @SneakyThrows
- public boolean existBucket(String name) {
- boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
- if (!exists) {
- minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
- }
- return exists;
- }
-
- /**
- * 创建存储bucket
- *
- * @param bucketName 存储bucket名称
- * @return Boolean
- */
- @SneakyThrows
- public Boolean makeBucket(String bucketName) {
- boolean exist = existBucket(bucketName);
- if (!exist) {
- minioClient.makeBucket(MakeBucketArgs.builder()
- .bucket(bucketName)
- .build());
- return true;
- }
- return false;
- }
-
- /**
- * 删除存储bucket
- *
- * @param bucketName 存储bucket名称
- * @return Boolean
- */
- @SneakyThrows
- public Boolean removeBucket(String bucketName) {
- boolean exist = existBucket(bucketName);
- if (!exist) return false;
-
- Iterable<Result<Item>> results =
- minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
- for (Result<Item> result : results) {
- Item item = result.get();
- // 桶不为空不允许删除
- if (item.size() > 0) {
- return false;
- }
- }
- minioClient.removeBucket(RemoveBucketArgs.builder()
- .bucket(bucketName)
- .build());
- return true;
- }
-
- /**
- * 列出所有存储桶
- */
- @SneakyThrows
- public List<Bucket> listBuckets() {
- return minioClient.listBuckets();
- }
-
- /**
- * 列出所有存储桶名称
- */
- public List<String> listBucketNames() {
- List<Bucket> bucketList = listBuckets();
- if (ObjectUtil.isEmpty(bucketList))
- return null;
- List<String> bucketListName = new ArrayList<>();
- for (Bucket bucket : bucketList) {
- bucketListName.add(bucket.name());
- }
- return bucketListName;
- }
-
- /**
- * 列出存储桶中的所有对象名称
- *
- * @param bucketName 存储桶名称
- */
- @SneakyThrows
- public List<String> listObjectNames(String bucketName) {
- boolean exist = existBucket(bucketName);
- if (!exist) return null;
-
- List<String> listObjectNames = new ArrayList<>();
- Iterable<Result<Item>> results =
- minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
- for (Result<Item> result : results) {
- Item item = result.get();
- listObjectNames.add(item.objectName());
- }
- return listObjectNames;
- }
-
- /**
- * 查看文件对象
- *
- * @param bucketName 存储bucket名称
- * @return 存储bucket内文件对象信息
- */
- public Map<String, Object> listObjects(String bucketName) {
- boolean exist = existBucket(bucketName);
- if (!exist) return null;
-
- Iterable<Result<Item>> results =
- minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
- Map<String, Object> map = new HashMap<>();
- try {
- for (Result<Item> result : results) {
- Item item = result.get();
- map.put(item.objectName(), item);
- }
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- return map;
- }
-
- /**
- * 文件访问路径
- *
- * @param bucketName 存储桶名称
- * @param objectName 存储桶里的对象名称
- */
- @SneakyThrows
- public String getObjectUrl(String bucketName, String objectName) {
- boolean exist = existBucket(bucketName);
- if (!exist) return null;
-
- return minioClient.getPresignedObjectUrl(
- GetPresignedObjectUrlArgs.builder()
- .method(Method.GET)
- .bucket(bucketName)
- .object(objectName)
- .expiry(2, TimeUnit.MINUTES)
- .build());
- }
-
- /**
- * 删除一个对象
- *
- * @param bucketName 存储桶名称
- * @param objectName 存储桶里的对象名称
- */
- @SneakyThrows
- public boolean removeObject(String bucketName, String objectName) {
- boolean exist = existBucket(bucketName);
- if (!exist) return false;
-
- minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
- return true;
- }
-
- /**
- * 删除指定桶的多个文件对象
- *
- * @param bucketName 存储桶名称
- * @param objectNames 含有要删除的多个object名称的迭代器对象
- */
- @SneakyThrows
- public boolean removeObject(String bucketName, List<String> objectNames) {
- boolean exist = existBucket(bucketName);
- if (!exist) return false;
-
- List<DeleteObject> objects = new LinkedList<>();
- for (String objectName : objectNames) {
- objects.add(new DeleteObject(objectName));
- }
- minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
- return true;
- }
-
- /**
- * 批量删除文件对象
- *
- * @param bucketName 存储bucket名称
- * @param objects 对象名称集合
- */
- public Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> objects) {
- List<DeleteObject> dos = objects.stream().map(DeleteObject::new).collect(Collectors.toList());
- return minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
- }
-
- /**
- * 文件上传
- */
- public String upload(MultipartFile multipartFile) {
- String fileName = multipartFile.getOriginalFilename();
- // 注意,这里需要加上 \\ 将 特殊字符 . 转意 \\. ,否则异常
- assert fileName != null;
- String[] fileArray = fileName.split("\\.");
-
- // 获取当前日期
- Date now = new Date();
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
- String format = sdf.format(now);
-
- fileName = format + "/" + fileArray[0] + System.currentTimeMillis() + "." + fileArray[1];
-
- try {
- InputStream inputStream = multipartFile.getInputStream();
- // 上传到minio服务器
- minioClient.putObject(PutObjectArgs.builder()
- .bucket(bucket)
- .object(fileName)
- .stream(inputStream, -1L, 10485760L)
- .build());
- } catch (Exception e) {
- e.printStackTrace();
- }
- // 返回地址
- return fileName;
- }
-
- /**
- * 文件下载
- *
- * @param fileName 文件名
- * @param delete 是否删除
- */
- public void fileDownload(String fileName, Boolean delete, HttpServletResponse response) {
-
- InputStream inputStream = null;
- OutputStream outputStream = null;
- try {
- if (StringUtils.isBlank(fileName)) {
- response.setHeader("Content-type", "text/html;charset=UTF-8");
- String data = "文件下载失败";
- OutputStream ps = response.getOutputStream();
- ps.write(data.getBytes(StandardCharsets.UTF_8));
- return;
- }
-
- outputStream = response.getOutputStream();
- // 获取文件对象
- inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(fileName).build());
- byte[] buf = new byte[1024];
- int length = 0;
- response.reset();
- response.setHeader("Content-Disposition", "attachment;filename=" +
- URLEncoder.encode(fileName.substring(fileName.lastIndexOf("/") + 1), "UTF-8"));
- response.setContentType("application/octet-stream");
- response.setCharacterEncoding("UTF-8");
- // 输出文件
- while ((length = inputStream.read(buf)) > 0) {
- outputStream.write(buf, 0, length);
- }
- inputStream.close();
- // 判断:下载后是否同时删除minio上的存储文件
- if (BooleanUtils.isTrue(delete)) {
- minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(fileName).build());
- }
- } catch (Throwable ex) {
- response.setHeader("Content-type", "text/html;charset=UTF-8");
- String data = "文件下载失败";
- try {
- OutputStream ps = response.getOutputStream();
- ps.write(data.getBytes(StandardCharsets.UTF_8));
- } catch (IOException e) {
- e.printStackTrace();
- }
- } finally {
- try {
- outputStream.close();
- if (inputStream != null) {
- inputStream.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
在util中我们已经针对Minio的使用定义好了各种常用方法,如果有遗漏欢迎小伙伴们私信我做出补充。
代码编写完成之后我们就开始测试我们代码的可用性,在这土豆简单编写了一个controller,主要用来测试我们常用的文件上传,文件下载方法。
代码如下:
- import com.psy.common.core.controller.BaseController;
- import com.psy.common.core.domain.AjaxResult;
- import com.psy.common.utils.MinioUtil;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.multipart.MultipartFile;
-
- import javax.annotation.Resource;
- import javax.servlet.http.HttpServletResponse;
-
- /**
- * @author YHL
- * @date 2024/4/24 8:54
- * @description
- */
- @RestController
- @RequestMapping("/fileDemo")
- public class FileDemoController extends BaseController {
-
- @Resource
- private MinioUtil minioUtil;
-
- @RequestMapping("/upload")
- public AjaxResult upload(MultipartFile multipartFile) {
- return AjaxResult.success(minioUtil.upload(multipartFile));
- }
-
- @RequestMapping("/download")
- public void download(String fileName, HttpServletResponse response) {
- minioUtil.fileDownload(fileName, false, response);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
然后我们可以使用测试工具对我们的代码进行测试,查看功能是否实现。
土豆这里使用的测试工具是ApiFox,我个人觉得是这款测试工具比传统的PostMan要美观要好用,有想去的小伙伴可以尝试下载使用一下。
好了,废话不多说,咱开测。
首先查看我们的Minio指定的桶的状态是否正常,可以看到咱的桶中是没有文件的:
然后我们在ApiFox中输入接口地址以及选择要上传的文件
点击发送之后,返回值显示文件上传完成
然后我们再到Minio中查看
发现所上传的图片已经上传到Minio中指定的桶中了,并且文件的上级地址是当前的日期。
所以说我们的代码是没有问题的,在实际使用中,我们肯定会有将文件地址存储起来的操作
在这里我要提醒大家,
1)为了方便以后的维护或者是数据迁移等操作,存储时只需要存储文件的相对路径,如果有需要存储桶名或者其他相关信息的话,建议区分字段存储,以避免不必要的麻烦。
2)这里文件上传使用的文件名称采用了原文件名称,但是在实际场景中,这种操作并不可取,为了避免文件冲突以及方便区分文件,建议在文件上传时在数据库中保存文件原名称,并重命名上传文件的名称(可采用时间戳等方式确保文件名称唯一)
上面完成了文件上传操作后Minio中已经存在一个文件了,现在我们需要将这个文件下载下来,我们继续使用ApiFox来测试:
点击发送:
到这里之后我们的文件下载和文件上传都是成功的。
以上内容就是结合上篇Minio基于Window系统的安装与使用的分享整合的SpringBoot整合Minio的内容
最后还是希望本次分享对您能够有所帮助,如有不足还请各位多多指点,多多支持。
感激不尽,感激不尽。。。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。