Spring Boot 集成 MinIO 实现文件上传
Spring Boot 集成 MinIO 实现文件上传
一、 Minio 服务准备
MinIO的搭建过程参考 Docker 搭建 MinIO 对象存储。
登录MinIO控制台,新建一个 Bucket,修改 Bucket 权限为公开。

二、MinIO 集成
- 添加 MinIO 依赖
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>${minio.version}</version>
</dependency>
- 在项目配置文件
application.yml中添加自定义配置。properties 文件自行转换
minio:host: http://【服务器公网ip】:【minio运行端口号,默认9000】/access-key: 账号secret-key: 密码
- 创建配置文件类
@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();}
}
- 创建文件上传工具类
@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。最后进行文件上传。
相关文章:
Spring Boot 集成 MinIO 实现文件上传
Spring Boot 集成 MinIO 实现文件上传 一、 Minio 服务准备 MinIO的搭建过程参考 Docker 搭建 MinIO 对象存储。 登录MinIO控制台,新建一个 Bucket,修改 Bucket 权限为公开。 二、MinIO 集成 添加 MinIO 依赖 <!-- https://mvnrepository.com/ar…...
目标跟踪——KCF源码用python实现
from numpy.fft import fft2, ifft2, fftshift import cv2 import numpy as npclass HOG:def __init__(self, winSize):""":param winSize: 检测窗口的大小"""self.winSize winSizeself.blockSize (8, 8)self.blockStride (4, 4)self.cellSiz…...
前端 转换笔记
<!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <title>转换</title> <style> .box{ /* 盒子摆在body的正中间 */ position: absolut…...
个人开发笔记
开发笔记 开发常见问题Vue开发中页面flex滚动布局,内容置顶问题功能快捷键 开发常见问题 Vue开发中页面flex滚动布局,内容置顶问题 直接操作路由: const router createRouter({routes: routes,history: createWebHashHistory(),scrollBeha…...
pdf压缩,pdf压缩在线,pdf文件太大怎么变小
在数字化时代,PDF文档因其跨平台、保持原样、易于阅读和打印等特点,成为了我们日常工作和生活中不可或缺的一部分。然而,随着PDF文件的不断累积,存储空间逐渐变得紧张,特别是在处理大量大型PDF文件时,如何有…...
Go 如何使用指针灵活操作内存
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…...
【面试干货】Java中的++操作符与线程安全性
【面试干货】Java中的操作符与线程安全性 1、什么是线程安全性?2、 操作符的工作原理3、 操作符与线程安全性4、如何确保线程安全?5、 结论 💖The Begin💖点点关注,收藏不迷路💖 在Java编程中,操…...
NLP学习与踩坑记录(持续更新版)
NLP学习与踩坑记录(持续更新版) OSError: Cant load tokenizer for bert-base-uncased.google.protobuf.message.DecodeError: Error parsing messageDeepspeed 本博客记录了博主在学习NLP时遇到了各种各样的问题与解决方法,供大家参考&#…...
Java也能做OCR!SpringBoot 整合 Tess4J 实现图片文字识别
文章目录 1. 环境准备1.1 安装 Tesseract OCR 引擎1.2 引入 Tess4J 依赖 2. 创建 Spring Boot 项目2.1 初始化项目2.2 目录结构 3. 编写 OCR 功能代码3.1 创建服务层3.2 创建控制器层 4. 配置 Tesseract 语言包5. 运行和测试5.1 启动 Spring Boot 应用5.2 使用 Postman 或 cURL…...
微信小程序常用标签及其用法
大家好,我是linzi,今天我来给大家分享一下微信小程序一些个常用的标签及其用法 1. <view> 标签 <view> 标签是小程序中最常用的标签之一,用于组织和布局页面上的内容,类似于HTML中的 <div> 标签。 <view …...
开发查询订单信息fastGPT智能体工作流 将工作流接入到人工客服系统
我在抖音上发布了视频 https://www.douyin.com/video/7382446337482099977 下面是主要内容介绍 【视频标题:】开发查询订单信息fastGPT智能体工作流 将工作流接入到人工客服系统 #智能体 #FastGPT #客服系统-----------【视频行业分类:】<3C数码>-…...
Flink集群运行模式
我们了解了flink的一个集群的一个基础架构,包括里面核心的一些组件,比如说job manager,task manager等一些组件的一些主要的一些组成。本节课程开始我们学习flink的一个集群部署模式。首先我们来看一下flink集群部署模式究竟应该有哪一些种类…...
XSS 安全漏洞介绍及修复方案
简介 XSS(Cross Site Scripting)是一种常见的 Web 安全漏洞,攻击者通过在网页中注入恶意脚本代码,使得网页在用户端执行这些脚本,从而窃取用户信息或者进行其他恶意操作。为了防止 XSS 攻击,可以使用正则表…...
基于STM32的智能仓库管理系统
目录 引言环境准备智能仓库管理系统基础代码实现:实现智能仓库管理系统 4.1 数据采集模块4.2 数据处理与分析4.3 通信模块实现4.4 用户界面与数据可视化应用场景:仓库管理与优化问题解决方案与优化收尾与总结 1. 引言 智能仓库管理系统通过使用STM32嵌…...
LeetCode —— 只出现一次的数字
只出现一次的数字 I 本题依靠异或运算符的特性,两个相同数据异或等于0,数字与0异或为本身即可解答。代码如下: class Solution { public:int singleNumber(vector<int>& nums) {int ret 0;for (auto e : nums){ret ^ e;}return ret;} };只出…...
python遍历文件夹中所有图片
python遍历文件夹中的图片-CSDN博客 这个是之前的版本,现在这个版本会更好,直接进来就在列表中 path glob.glob("1/*.jpg")print(path)print(len(path))path_img glob.glob("1/*.jpg")path_img.extend(path)print(len(path_img))…...
速盾:DDOS能打死高防ip吗?
DDoS攻击是一种利用大量计算机或设备发起的分布式拒绝服务攻击。它的目标是通过发送大量流量或请求,使目标服务器或网络资源无法正常工作。高防IP是一种具有强大防御能力的网络服务,能够抵御各种形式的网络攻击,包括DDoS攻击。然而࿰…...
3dsMax怎样让渲染效果更逼真出色?三套低中高参数设置
渲染是将精心构建的3D模型转化为逼真图像的关键步骤。但要获得令人惊叹的渲染效果,仅仅依赖默认设置是不够的。 实现在追求极致画面效果的同时,兼顾渲染速度和时间还需要进行一些调节设置,如何让渲染效果更加逼真? 一、全局照明与…...
Android的OverlayFS原理与作用
标签: OverlayFS; Android;Overlay Filesystem; Android的OverlayFS原理与作用 概述 OverlayFS(Overlay Filesystem)是一种联合文件系统,允许将一个或多个文件系统叠加在一起,使它们表现为一个单一的文件系统。Android系统利用OverlayFS来实现动态文件系统的叠加和管…...
奇点临近:人类与智能时代的未来
在信息爆炸的时代,我们每天都被海量的信息所淹没,如何才能在这个嘈杂的世界中找到真正有价值的信息?如何才能利用信息的力量,提升我们的认知水平,重塑我们的未来? 这些问题的答案,或许都能在雷…...
Allegro与OrCAD联动实操:如何安全高效地完成PCB位号‘反向同步’而不飞器件?
Allegro与OrCAD协同设计中的位号反标:规避风险的全流程实战指南 在复杂电子系统设计领域,Cadence Allegro与OrCAD的协同工作流程已成为行业标准配置。当PCB布局工程师经过多轮迭代优化器件位置后,如何将最终的位号变更安全地反向同步到原理图…...
VMware Unlocker终极指南:3分钟免费解锁macOS虚拟机支持
VMware Unlocker终极指南:3分钟免费解锁macOS虚拟机支持 【免费下载链接】unlocker VMware Workstation macOS 项目地址: https://gitcode.com/gh_mirrors/unloc/unlocker 想要在Windows或Linux电脑上体验macOS系统,却苦于VMware没有苹果选项&am…...
从NeoPixel到CircuitPython:打造智能LED眼镜的完整硬件与软件实践
1. 项目概述 如果你对可穿戴电子设备、酷炫的LED光效以及用代码创造物理交互感兴趣,那么这个项目绝对能让你兴奋起来。今天要分享的,是如何亲手制作一副灵感来源于电子音乐人REZZ标志性风格的NeoPixel LED眼镜。这不仅仅是一个简单的焊接和组装教程&…...
AI行业的“隐形赛道”:AI伦理与合规人才缺口到底有多大
一、AI狂飙下的“隐形刚需”:被忽视的伦理与合规赛道当软件测试从业者还在为功能测试、性能测试的技术迭代焦头烂额时,AI行业的另一股暗流正汹涌袭来——伦理与合规人才的缺口,正成为制约AI产业可持续发展的隐形瓶颈。从ChatGPT引发生成式AI热…...
量子退火优化CPS测试用例生成的技术解析
1. 量子退火在CPS测试用例生成中的应用概述在安全关键系统(如自动驾驶、工业控制系统)的开发过程中,测试用例的质量直接关系到系统的可靠性。传统测试方法面临两大核心挑战:一是如何在庞大的输入空间中找到最具检测效力的测试用例…...
从零搭建一个智能视频监控系统:3D定位、ONVIF控制与Python UI实战
从零搭建智能视频监控系统:3D定位、ONVIF控制与Python UI实战 在智能安防和物联网应用蓬勃发展的今天,具备3D定位功能的视频监控系统正成为行业新宠。本文将带您从零开始,基于树莓派或普通PC,结合支持ONVIF协议的球型摄像机&#…...
ARM SVE指令集饱和运算原理与应用解析
1. ARM SVE指令集与饱和运算概述在当代处理器架构中,向量化计算已成为提升性能的关键技术。作为ARMv8.2引入的重要扩展,SVE(Scalable Vector Extension)指令集通过创新的"向量长度无关"设计,为高性能计算和机…...
STM32与PS2手柄的无线交互:从硬件对接到按键解析
1. 认识PS2手柄与STM32的无线交互 第一次接触PS2手柄和STM32的对接时,我完全被这个经典游戏手柄的通信协议吸引了。你可能不知道,这个2000年推出的手柄至今仍在嵌入式领域发光发热,主要得益于它简单的通信协议和稳定的性能。我实测过市面上常…...
从Educoder到真实项目:新手用Python处理用户输入的3个避坑点与最佳实践
从Educoder到真实项目:Python用户输入处理的3个避坑指南与工程实践 当你在Educoder上完美运行input()函数时,是否思考过这段代码在真实项目中可能引发的灾难?教学平台的理想环境与真实世界的复杂输入之间存在巨大鸿沟。本文将揭示那些在线练习…...
PyCharm 运行 FastAPI 接口请求阻塞?竟是后台多进程残留导致
问题描述在 PyCharm 中启动 FastAPI 项目进程后,使用 Postman 发起接口请求出现明显阻塞现象,不仅请求迟迟无法得到响应,项目控制台也完全接收不到任何请求日志,接口调用彻底失效。 问题根源分析日常开发中习惯性直接关闭运行终端…...
