Spring Boot 集成 MinIO 实现文件上传

Spring Boot 集成 MinIO 实现文件上传

一、 Minio 服务准备

MinIO的搭建过程参考 Docker 搭建 MinIO 对象存储。

登录MinIO控制台,新建一个 Bucket,修改 Bucket 权限为公开。

在这里插入图片描述

二、MinIO 集成

  1. 添加 MinIO 依赖
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>${minio.version}</version>
</dependency>
  1. 在项目配置文件application.yml中添加自定义配置。properties 文件自行转换
minio:host: http://【服务器公网ip】:【minio运行端口号,默认9000】/access-key: 账号secret-key: 密码
  1. 创建配置文件类
@Data
@Component
public class MinioConfig {@Value(value = "${minio.host}")private String host;@Value(value = "${minio.access-key}")private String accessKey;@Value(value = "${minio.secret-key}")private String secretKey;@Beanpublic MinioClient minioClient(){return MinioClient.builder().endpoint(host).credentials(accessKey, secretKey).build();}
}
  1. 创建文件上传工具类
@Component
@Slf4j
@AllArgsConstructor
public class MinioUtils {private final MinioClient minioClient;private final MinioConfig minioConfig;/*** 初始化Bucket*/private void createBucket(String bucketName) {// 设置公开读写String POLICY_PATTERN = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::%s/*\"]}]}";try {// 判断 BucketName 是否存在if (!bucketExists(bucketName)) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(String.format(POLICY_PATTERN, bucketName)).build());} catch (Exception e) {e.printStackTrace();}}/*** 验证bucketName是否存在** @return boolean true:存在*/public boolean bucketExists(String bucketName) {if (StringUtils.isBlank(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);}boolean flag = true;try {flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());} catch (Exception e) {e.printStackTrace();}return flag;}/*** 获取全部bucket* <p>*/public List<String> getAllBuckets() {List<String> list = null;try {final List<Bucket> buckets = minioClient.listBuckets();list = new ArrayList<>(buckets.size());for (Bucket bucket : buckets) {list.add(bucket.name());}} catch (Exception e) {e.printStackTrace();}return list;}/*** 根据bucketName获取信息** @param bucketName bucket名称* @return*/public String getBucket(String bucketName) throws Exception {final Optional<Bucket> first = minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();String name = null;if (first.isPresent()) {name = first.get().name();}return name;}/*** 获取桶中文件名和大小列表** @param bucketName bucket名称* @param recursive  查询是否递归* @return*/public List<Object> getFileList(String bucketName, boolean recursive) {if (StringUtils.isEmpty(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);}List<Object> items = new ArrayList<>();try {Iterable<Result<Item>> myObjects = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix("/2022-08-03/4674a894-abaf-48cb-9ea9-40a4e8560af9/Desktop").recursive(recursive).build());Iterator<Result<Item>> iterator = myObjects.iterator();String format = "{'fileName':'%s','fileSize':'%s'}";for (Result<Item> myObject : myObjects) {System.out.println(myObject.get().objectName());}while (iterator.hasNext()) {Item item = iterator.next().get();items.add(JSON.parse(String.format(format, item.objectName(), formatFileSize(item.size()))));
//                items.add(JSON.parse(String.format(format, "/".concat("test").concat("/").concat(item.objectName()), formatFileSize(item.size()))));}} catch (Exception e) {e.printStackTrace();log.info(e.getMessage());}items.remove(0);return items;}/*** 文件上传** @param bucketName 存储桶名称* @param file       file* @return map*/public Map<String, Object> uploadFile(String bucketName, MultipartFile[] file) {if (file == null || file.length == 0) {throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL);}createBucket(bucketName);List<String> urlList = new ArrayList<>(file.length);for (MultipartFile multipartFile : file) {String originFileName = multipartFile.getOriginalFilename();if (StringUtils.isBlank(originFileName)) {throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL);}String[] originFileNameArr = originFileName.split("\\.");String suffix = originFileNameArr[originFileNameArr.length - 1];String newFileName = UUID.randomUUID().toString().replace("-", "").concat(".").concat(suffix);urlList.add(String.format("%s%s/%s", minioConfig.getHost(), bucketName, newFileName));try {// 文件上传InputStream in = multipartFile.getInputStream();minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(newFileName).stream(in, multipartFile.getSize(), -1).contentType(multipartFile.getContentType()).build());in.close();} catch (Exception e) {log.error(e.getMessage());}}Map<String, Object> data = new HashMap<>();data.put("bucketName", bucketName);data.put("urlList", urlList);return data;}/*** 获取上传文件的完整路径** @param bucketName 桶名称* @param fileName   文件名* @return*/public String getPresignedObjectUrl(String bucketName, String fileName) {if (StringUtils.isEmpty(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);}if (StringUtils.isEmpty(fileName)) {throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL);}// 验证桶是否存在在final boolean validationBucket = bucketExists(bucketName);if (!validationBucket) {throw new ServerException(ErrorCode.BUCKET_NOT_EXIST);}// 验证文件是否存在final boolean validationFileName = doFileNameExist(bucketName, fileName);if (!validationFileName) {throw new ServerException(ErrorCode.FILE_NOT_EXIST);}String url = null;try {// 获取桶和文件的完整路径url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).method(Method.GET).build());} catch (MinioException e) {log.error("Error occurred: " + e);} catch (Exception e) {e.printStackTrace();}return url;}/*** 创建文件夹或目录** @param bucketName 存储桶* @param objectName 目录路径*/public Map<String, String> putDirObject(String bucketName, String objectName) throws Exception {// 判断桶是否存在if (!bucketExists(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_EXIST);}final ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());Map<String, String> map = new HashMap<>();map.put("etag", response.etag());map.put("versionId", response.versionId());return map;}/*** 判断文件是否存在** @param fileName 对象* @return true:存在*/public boolean doFileNameExist(String bucketName, String fileName) {if (StringUtils.isEmpty(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);}if (StringUtils.isEmpty(fileName)) {throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL);}boolean exist = true;try {minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());} catch (Exception e) {exist = false;}return exist;}/*** 文件下载** @param response* @param fileName*/public void downloadFile(HttpServletResponse response, String bucketName, String fileName) {if (StringUtils.isEmpty(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);}if (StringUtils.isEmpty(fileName)) {throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL);}// 判断文件是否存在final boolean flag = doFileNameExist(bucketName, fileName);if (!flag) {throw new ServerException(ErrorCode.FILE_NOT_EXIST);}InputStream in = null;try {// 获取对象信息StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());response.setContentType(stat.contentType());response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));// 文件下载in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());IOUtils.copy(in, response.getOutputStream());} catch (Exception e) {log.error(e.getMessage());} finally {if (in != null) {try {in.close();} catch (IOException e) {log.error(e.getMessage());}}}}/*** 删除文件** @param bucketName bucket名称* @param fileName   文件名称*                   说明:当前方法不能真正删除,需要验证*/public void deleteFile(String bucketName, String fileName) {if (StringUtils.isEmpty(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);}if (StringUtils.isEmpty(fileName)) {throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL);}try {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());} catch (Exception e) {log.error(e.getMessage());e.printStackTrace();}}/*** 批量文件删除** @param bucketName bucket名称* @param fileNames  文件名*/public void deleteBatchFile(String bucketName, List<String> fileNames) {if (StringUtils.isEmpty(bucketName)) {throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);}if (CollectionUtils.isEmpty(fileNames)) {throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL);}try {List<DeleteObject> objects = new LinkedList<>();for (String fileName : fileNames) {objects.add(new DeleteObject(fileName));}Iterable<Result<DeleteError>> results =minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());for (Result<DeleteError> result : results) {DeleteError error = result.get();log.error("Error occurred: " + error);}} catch (Exception e) {log.error("批量删除失败!error:{}", e);}}/*** 文件大小** @param fileS* @return*/private static String formatFileSize(long fileS) {DecimalFormat df = new DecimalFormat("#.00");String fileSizeString = "";String wrongSize = "0B";if (fileS == 0) {return wrongSize;}if (fileS < 1024) {fileSizeString = df.format((double) fileS) + " B";} else if (fileS < 1048576) {fileSizeString = df.format((double) fileS / 1024) + " KB";} else if (fileS < 1073741824) {fileSizeString = df.format((double) fileS / 1048576) + " MB";} else {fileSizeString = df.format((double) fileS / 1073741824) + " GB";}return fileSizeString;}
}

三、上传文件实战

新建 UploadController,实现上传文件接口。

@Tag(name = "基础接口")
@AllArgsConstructor
@RestController
@RequestMapping("/file")
public class UploadController {private final MinioUtils minioUtils;@PostMapping("upload")@Operation(summary = "上传文件")public Result<Map<String, Object>> upload(@RequestParam(defaultValue = "common") String bucketName,@RequestParam(name = "file", required = false) MultipartFile[] file) {return Result.ok(minioUtils.uploadFile(bucketName, file));}
}

调用上传文件接口后,系统会根据 bucketName 首先判断 bucket 是否存在,不存在则会开始创建,并且设置成公共读写。然后遍历文件数组,对文件重命名,并且记录下上传后的文件访问 url。最后进行文件上传。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/360135.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法】数组-基础知识与应用

一.基础理论 数组是存放在连续内存空间上的相同类型数据的集合。数组可以方便的通过下标索引的方式获取到下标对应的数据。 数组下标都是从0开始的。数组内存空间的地址是连续的 因为数组在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添元素的时候&#xff0c…

STM32 I2C总线锁死原因及解决方法

本文介绍STM32 I2C总线锁死原因及解决方法。 在使用STM32 I2C总线操作外设时&#xff0c;有时会遇到I2C总线锁死&#xff08;I2C总线为Busy状态&#xff09;的问题&#xff0c;即便复位MCU也无法解决&#xff0c;本文介绍其锁死的原因和解决方法&#xff0c;并给出相应的参考代…

干货分享 | TSMaster 中不同总线报文消息过滤的操作方式

TSMaster软件平台支持对不同总线&#xff08;CAN、LIN、FlexRay&#xff09;报文和信号的过滤&#xff0c;包括全局接收过滤、数据流过滤、窗口过滤、字符串过滤、可编程过滤&#xff0c;针对不同的总线信号过滤器的使用方法基本相同。今天重点和大家分享一下关于TSMaster中报文…

【数据结构】线性表之《队列》超详细实现

队列 一.队列的概念及结构二.顺序队列与链队列1.顺序队列2.链队列 三.链队列的实现1.创建队列2.初始化队列3.入队4.出队5.获取队头元素6.获取队尾元素7.队列的大小8.队列的判空9.清空队列10.销毁队列 四.队列的盲区五.模块化源代码1.Queue.h2.Queue.c3.test.c 六.栈和队列必做O…

FlinkCDC介绍及使用

CDC简介 什么是CDC&#xff1f; cdc是Change Data Capture(变更数据获取)的简称。核心思想是&#xff0c;监测并捕获数据库的 变动(包括数据或数据表的插入&#xff0c;更新以及删除等)&#xff0c;将这些变更按发生的顺序完整记录下来&#xff0c;写入到消息中间件以供其它服…

Flutter 像素编辑器#05 | 缩放与平移

theme: cyanosis 本系列&#xff0c;将通过 Flutter 实现一个全平台的像素编辑器应用。源码见开源项目 【pix_editor】。在前三篇中&#xff0c;我们已经完成了一个简易的图像编辑器&#xff0c;并且简单引入了图层的概念&#xff0c;支持切换图层显示不同的像素画面。 《Flutt…

【工具测评】ONLYOFFICE——你的下一款桌面编辑器

文章目录 前言一、安装1.1 跳转官网下载安装包1.2 安装步骤 二、功能介绍2.1 功能全面的 PDF 编辑器2.2 PDF 表单2.3 文本文档编辑器的更新2.4 电子表格编辑器的更新2.5 演示文稿编辑器有哪些更新2.6 所有编辑器中的改进内容2.7 从右至左显示 & 新的本地化选项2.8 可用性提…

基于Java超市库存管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

10.2 JavaEE——Spring MVC入门程序

要求在浏览器发起请求&#xff0c;由Spring MVC接收请求并响应&#xff0c;具体实现步骤如下。 一、创建项目 在IDEA中&#xff0c;创建一个名称为chapter10的Maven Web项目。 &#xff08;一&#xff09;手动设置webapp文件夹 1、单击IDEA工具栏中的File→“Project Structu…

学校校园考场电子钟,同步授时,助力考场公平公正-讯鹏科技

随着教育技术的不断发展&#xff0c;学校对于考场管理的需求也日益提高。传统的考场时钟往往存在时间误差、维护不便等问题&#xff0c;这在一定程度上影响了考试的公平性和公正性。为了解决这些问题&#xff0c;越来越多的学校开始引入考场电子钟&#xff0c;通过同步授时技术…

Nginx实现动静分离

目录 静态资源 动态资源 区别和应用场景 1. 准备环境 2. 配置代理 3. 静态资源主机配置 4. 动态资源主机配置 5. 访问静态和动态资源测试 测试1&#xff1a;访问静态资源 测试2&#xff1a;访问动态资源 动态资源和静态资源是在网络和Web开发中常用的两个概念&#…

「全新升级,性能更强大——ONLYOFFICE 桌面编辑器 8.1 深度评测」

文章目录 一、背景二、界面设计与用户体验三、主要新功能亮点3.1 高效协作处理3.2 共同编辑&#xff0c;毫无压力3.3 批注与提及3.4 追踪更改3.5 比较与合并3.6 管理版本历史 四、性能表现4.1 集成 AI 工具4.2 插件强化 五、用户反馈与使用案例 一、背景 Ascensio System SIA -…

48、基于深度学习的离群值输入向量(matlab)

1、基于深度学习的离群值输入向量原理及流程 基于深度学习的离群值检测的输入向量原理是通过神经网络模型对数据进行学习和表示&#xff0c;在该表示中探测异常样本。其流程大致如下&#xff1a; 数据预处理&#xff1a;将数据进行归一化处理&#xff0c;确保神经网络模型能够…

Java Scanner 类

Java Scanner 类 java.util.Scanner 是 Java5 的新特征&#xff0c;我们可以通过 Scanner 类来获取用户的输入。 下面是创建 Scanner 对象的基本语法&#xff1a; Scanner s new Scanner(System.in);接下来我们演示一个最简单的数据输入&#xff0c;并通过 Scanner 类的 nex…

如何评估LabVIEW需求中功能的必要性和可行性

评估LabVIEW需求中功能的必要性和可行性涉及多个方面的分析&#xff0c;包括需求的重要性、技术可行性、资源需求以及潜在风险。以下是一个详细的评估方法&#xff1a; ​ 一、功能必要性评估 需求来源和目的&#xff1a; 来源&#xff1a;需求来自哪里&#xff1f;是客户、市…

Windows系统下安装RabbitMQ详细步骤

声明&#xff1a;原文参考链接出自&#xff1a; 如何在Windows系统下安装RabbitMQ_rabbitmq windows安装-CSDN博客 https://zhuanlan.zhihu.com/p/693160757 一、RabbitMQ安装软件资源准备 因为RabbitMQ是Erlang语言开发的&#xff0c;因此安装Erlang环境在进行安装RbbitMQ的…

贺尔碧格流量阀比例放大器PSR2BE10P25、PSR2BE10P30、PSR2BE10P25

PSR2BE04N06、PSR2BE04P10、PSR2BE04P06、PSR2BE04N10、PSR2BE10N12、PSR2BE10P25、PSR2BE10P30、PSR2BE10P25、PSR3BE10N25、PSR3BE10P30、PSR3BE10P12贺尔碧格HOERBIGER液压比例流量阀由比例电磁铁和流量阀组合而成&#xff0c;利用输入的电信号来改变节流阀的开度&#xff0…

深入解析 iOS 应用启动过程:main() 函数前的四大步骤

深入解析 iOS 应用启动过程&#xff1a;main() 函数前的四大步骤 背景描述&#xff1a;使用 Objective-C 开发的 iOS 或者 MacOS 应用 在开发 iOS 应用时&#xff0c;我们通常会关注 main() 函数及其之后的执行逻辑&#xff0c;但在 main() 函数之前&#xff0c;系统已经为我们…

[Django学习]前端+后端两种方式处理图片流数据

方式1&#xff1a;数据库存放图片地址,图片存放在Django项目文件中 1.首先&#xff0c;我们现在models.py文件中定义模型来存放该图片数据,前端传来的数据都会存放在Django项目文件里的images文件夹下 from django.db import modelsclass Image(models.Model):title models.C…

SiLM5350系列SiLM5350SABCA-DG 10A30V提供分离输出 单通道隔离栅极驱动器

SiLM5350系列SiLM5350SABCA-DG是具体有10A峰值输出电流能力&#xff0c;单通道隔离式栅极驱动器。SiLM5350系列SiLM5350SABCA-DG提供分离输出&#xff0c;可分别控制上升和下降时间。驱动电源电压为4V至30V。3V至18V的宽输入VDDI范围使驱动器适合与模拟和数字控制器接口。所有电…