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.mzph.cn/pingmian/34737.shtml

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

相关文章

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

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

重生奇迹MU整理装备技巧

除了注意一些基本的事项外&#xff0c;还有一些技巧可以帮助我们更快更好地整理装备&#xff0c;提升自己在游戏中的表现。 1. 理性地管理装备是取得胜利的关键。在整理装备时&#xff0c;根据不同的属性和需求&#xff0c;可以将装备分类&#xff0c;比如攻击型、防御型、辅助…

CRMEB 多门店后台登录入口地址修改(默认admin)

一、>2.4版本 1、修改后端 config/admin.php 配置文件,为自定义的后缀 2、修改 平台后台前端源码中 view/admin/src/settings.js 文件,修改为和上面一样的配置 3、修改后重新打包前端代码,并且覆盖到后端的 public 目录下&#xff1a;打包方法 4、重启swoole 二、<2.4版…

云函数-uniapp + uniCloud 订阅消息实战教程(二)

一、创建uniCloud项目 在 HBuilderX 新建项目界面&#xff0c;选择 uni-app项目&#xff0c;输入项目名称&#xff0c;选择默认模板即可&#xff0c;根据个人需要选择 vue版本&#xff0c;并勾选 启用uniCloud&#xff0c;在右侧选择服务供应商&#xff08;支付宝云、阿里云、…

再不挖掘就晚了!本周三分之二二区以上 | GBD数据库周报(6.12~6.18)

全球疾病负担&#xff08;GBD&#xff09;是迄今为止规模最大、最全面的一项研究&#xff0c;旨在量化不同地区和不同时期的健康损失&#xff0c;从而改善卫生系统并消除差异。 该研究由华盛顿大学健康指标与评估研究所 (IHME) 牵头&#xff0c;是一项真正的全球性研究&#xf…

Tensorflow入门实战 T06-Vgg16 明星识别

目录 1、前言 2、 完整代码 3、运行过程结果 4、遇到的问题 5、小结 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 1、前言 这周主要是使用VGG16模型&#xff0c;完成明星照片识别。 2、 完整代…

奇点临近:人类与智能时代的未来

在信息爆炸的时代&#xff0c;我们每天都被海量的信息所淹没&#xff0c;如何才能在这个嘈杂的世界中找到真正有价值的信息&#xff1f;如何才能利用信息的力量&#xff0c;提升我们的认知水平&#xff0c;重塑我们的未来&#xff1f; 这些问题的答案&#xff0c;或许都能在雷…

无需高配置 怎么获得超流畅的VR体验?

传统VR眼镜在使用中存在一些显著不足&#xff0c;而实时渲染技术又是如何解决的&#xff1f;接下来与大家共同探讨遇到的问题以及实时渲染在VR眼镜中的实际应用。 1、高配置要求 目前主流VR一体机的眼镜需要较高配置才能运行普通VR内容&#xff0c;且受限于VR眼镜的算力限制&…

【小程序】聊天功能

文章目录 聊天功能实现功能实现思路后端前端效果展示 聊天功能 实现功能 要实现一个聊天机器人&#xff0c;它能够解答用户疑问&#xff0c;并且能够识别到用户聊天的主题&#xff0c;涉及到饮食方面时&#xff0c;会自动决定是否要去数据库中读取用户的相关喜好信息&#xf…

【ARM】MDK自动备份源文件

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决MDK在编写文档的时候需要找回上一版代码的问题。 2、 问题场景 目前大部分情况下对于源代码的管理都是使用的Git等第三方的代码管理平台。这样的第三方代码管理平台都是针对与代码的版本更新进行管理。对于本地…

2024年6月上半月30篇大语言模型的论文推荐

大语言模型&#xff08;LLMs&#xff09;在近年来取得了快速发展。本文总结了2024年6月上半月发布的一些最重要的LLM论文&#xff0c;可以让你及时了解最新进展。 LLM进展与基准测试 1、WildBench: Benchmarking LLMs with Challenging Tasks from Real Users in the Wild Wi…

数字心动+华为运动健康服务 使用体验指导

一、应用介绍 “数字心动”是一个体育生态平台APP&#xff0c;践行“体育大健康娱乐数字营销”模式&#xff0c;打造深度融合体育平台。APP集跑步运动记录、赛事活动报名、成绩/大众等级证书查询等多功能于一体&#xff0c;采取“线上线下”模式&#xff0c;结合协会、行业、品…

【CT】LeetCode手撕—56. 合并区间

目录 题目1- 思路2- 实现⭐56. 合并区间——题解思路 3- ACM 实现 题目 原题连接&#xff1a;56. 合并区间 1- 思路 模式识别&#xff1a;合并区间 ——> 数组先排序 思路 1.先对数组内容进行排序 ——> 定义 left、right 根据排序后的结果&#xff0c;更新 right2.遍…

高性能的多媒体播放器(提供补帧功能)

一、简介 1、一款高性能的多媒体播放器&#xff0c;支持几乎所有主流和部分罕见的音视频格式。无需额外安装coder插件&#xff0c;即可顺利播放各种媒体文件。此外&#xff0c;它还提供补帧功能&#xff0c;显著提升了视频播放的流畅性和视觉效果 二、下载 1、文末有下载链接,不…

Shopee API接口:一键获取商品买家评论数据,赋能电商运营新智慧

一、核心功能介绍——一键获取商品买家评论数据 在电商领域&#xff0c;买家评论是反映商品质量和市场反馈的重要指标。为了帮助商家更好地了解买家需求&#xff0c;优化产品和服务&#xff0c;Shopee接口特别推出了获取商品买家评论数据的功能。以下是该功能的核心介绍&#…

数据库设计文档编写

PS&#xff1a;建议使用第三种方法 方法1&#xff1a;使用 Navicat 生成数据库设计文档 效果 先看简单的效果图&#xff0c;如果效果合适&#xff0c;大家在进行测试使用&#xff0c;不合适直接撤退&#xff0c;也不浪费时间。 随后在docx文档中生成目标字段的表格&#xf…

人工智能赋能数据资产分析:借助先进的人工智能技术,优化数据处理流程,显著提升数据资产分析的准确性和效率,为企业决策提供强大支撑,推动业务快速发展

一、引言 在数字化浪潮席卷全球的今天&#xff0c;数据已经成为企业最宝贵的资产之一。如何有效地分析这些数据&#xff0c;挖掘其中的价值&#xff0c;为企业决策提供有力支持&#xff0c;是每个企业都面临的挑战。近年来&#xff0c;人工智能技术的快速发展&#xff0c;为数…

【面试干货】Java中的++操作符与线程安全性

【面试干货】Java中的操作符与线程安全性 1、什么是线程安全性&#xff1f;2、 操作符的工作原理3、 操作符与线程安全性4、如何确保线程安全&#xff1f;5、 结论 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java编程中&#xff0c;操…

Kendryte K210 固件烧录

本章将为读者介绍 Kendryte K210 的固件烧录&#xff0c;以及 Kendryte K210 外部 NOR Flash 的空间 分布。 本章分为如下几个小节&#xff1a; 6.1 外部 NOR Flash 的空间分布 6.2 Ubuntu 下的固件烧录 6.3 Windows 下的固件烧录 外部 NOR Flash 的空间分布 Kendryte K210 的…

mac 常用工具快捷键集合

一、vim 快捷键 1、移动光标 h j k l 左 下 上 右 箭头上 上移一行 箭头下 下移一行 0 跳至行首&#xff0c;不管有无缩进&#xff0c;就是跳到第0个字符 ^ 跳至行首的第一个字符 $ 跳至行尾 gg 跳至文首 G 调至文尾 5gg/5G 调至第5行w 跳到下一个字首&#xff0c;按标点或…