MinIo前后端实现
这几天想玩玩Minio,整体来说简单使用起来不复杂(当然也有可能是我配置的太少了)
Minio下载
我是通过Dokcer在虚拟机上下载的(Docker真好用啊)
拉取Minio镜像
docker pull minio/minio
启动Minio容器
docker run -d --name minio -p 9000:9000 -p 9001:9001
-v /root/minio/data:/data
-v /root/minio/config:/root/.minio
-e MINIO_ROOT_USER=账号名 -e MINIO_ROOT_PASSWORD=密码
minio/minio server /data --console-address ":9001"
Minio需要暴露两个端口,9000是API接口,9001是浏览器界面,后端通过9000端口发送请求,9001端口是可视化界面
还需要设置账号和密码MINIO_ROOT_USER MINIO_ROOT_PASSWORD
(密码至少8位)
以及配置数据卷,Bucket(MinIo中的文件夹)会建立在/data目录下
启动完成后可以打开9001端口查看
后端配置
Minio通过okhttp发送请求,okhttp版本过低会报错
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.3</version></dependency><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.5</version></dependency>
编写配置文件
access-key和secret-key需要在9001端口上手动获取,登录Minio后,点击AccessKey点击CreateAccessKey点击Create将accessKey和secretKey记住就行
minio:endpoint: ip:9000access-key: eSKt88NNU3PNKs8htCtfsecret-key: YadifAfciM8Q5OaShJcSmG0NkEm5dN58UJYFPmO7
编写配置类(记得加上set和get,配置数据注入依赖这两)
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIoConfig {public String endpoint;public String accessKey;public String secretKey;@Beanpublic MinioClient minioClient(){return MinioClient.builder().endpoint(endpoint).credentials(accessKey,secretKey).build();}
}
此时就可以通过自动注入获取到MinioClient对象,完成文件的上传下载等
Minio中的几乎所有方法都是通过构造器模式编写的,参数大多都是xxxArgs.builder().build();
private final MinioClient minio;// 通过构造器注入public MinioService(MinioClient minio) {this.minio = minio;}// 创建Bucket 相当于创建一个文件夹public boolean createBucket(String bucketName) {boolean exist = minio.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if (!exist){minio.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}return exist;}// 在指定桶中上传文件public boolean uploadFile(String bucketName,MultipartFile file,String newFileName) {InputStream inputStream = null;try{inputStream = file.getInputStream();minio.putObject(PutObjectArgs.builder().bucket(bucketName).object(newFileName).stream(inputStream,file.getSize(),-1).contentType(file.getContentType()).build());} catch (Exception e){return false;}finally {inputStream.close();}return true;}// 根据文件名获取对应桶中的文件public InputStream downloadFile(String bucketName,String objectName) {return minio.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());}// 删除指定桶中的文件public boolean deleteFile (String bucketName,String objectName){try{minio.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());}catch (Exception e){return false;}return true;}// 获取指定桶中的全部数据public List<String> getAllFilesInBucket(String bucketName){Iterable<Result<Item>> results = minio.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());List<String> items = new ArrayList<>();for (Result<Item> result : results) {Item item = result.get();items.add(item.objectName());}return items;}
编写完Service类后可以通过访问Controller去创建文件删除文件等操作
因为上传文件名不应该重复,又不确定Minio是否会对文件名重复进行什么操作,所以我将上传的文件通过UUID进行重命名,并通过建立一张sql表存储新文件名和旧文件名之间的对应关系
// 获取指定桶中的全部文件@GetMapping("/fileList")public Result<List<String>> fileList() {List<String> fileList = minIo.getAllFilesInBucket("test");return Result.success(fileNameConvert(fileList));}// 上传文件是旧的文件名,通过SQL查询将文件名转换成新文件名public List<String> fileNameConvert(List<String> fileList){for(int i = 0;i<fileList.size();i++){String oldNameByNewName = sqlMapper.getOldNameByNewName(fileList.get(i));fileList.set(i,oldNameByNewName);}return fileList;}// 获取指定桶中的指定文件// 文件的下载需要通过HttpServletResponse类完成,返回数据无所谓(写不写return都行)@GetMapping("/getFile")public Result<String> getFile(@RequestParam("fileName") String fileName, HttpServletResponse response) {MinIoFile file = sqlMapper.getFile(fileName, "1");InputStream inputStream = minIo.downloadFile("test", file.getNewFileName());response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(fileName, "UTF-8"));ServletOutputStream outputStream = response.getOutputStream();inputStream.transferTo(outputStream);return Result.success("ok");}// 文件上传@Transactional@PostMapping("/hello/upload")public Result<String> upload(@RequestParam("file") MultipartFile multipartFile){String oldFileName = multipartFile.getOriginalFilename();String[] files = oldFileName.split("\\.");String endName = files[files.length-1];if (!endName.equals("md")){return Result.error("返回一个md结尾的数据");}String uuid = UUID.randomUUID().toString();// 文件存储,原名: 新名 每次查询时从数据库中查找对应的原名String newName = uuid +"."+endName;// 将数据存储到SQL中MinIoFile minIoFile = new MinIoFile();minIoFile.setOldFileName(oldFileName);minIoFile.setNewFileName(newName);minIoFile.setIsDelete(0);minIoFile.setCreateTime(new java.sql.Date(System.currentTimeMillis()));String userId = "1";minIoFile.setCreateUser(userId);boolean createFileSuccess = sqlMapper.createNewFile(minIoFile);if (!createFileSuccess){throw new BaseException("创建文件失败");}try {minIo.uploadFile("test",multipartFile,newName);} catch (Exception e) {throw new BaseException("创建文件失败");}return Result.success("创建文件成功");}
创表语句
create table file(id int primary key auto_increment,old_file_name varchar(50) not null,new_file_name varchar(50) not null unique,is_delete int default 0,create_time datetime,create_user varchar(50)
)
Mapper接口
@Insert("insert into file values(null,#{oldFileName},#{newFileName},#{isDelete},#{createTime},#{createUser})")boolean createNewFile (MinIoFile minIoFile);@Select("select * from file where old_file_name = #{oldFileName} and create_user = #{userId}")MinIoFile getFile(@Param("oldFileName") String oldFileName,@Param("userId") String userId);@Select("select old_file_name from file where new_file_name = #{newFileName}")String getOldNameByNewName(String newFileName);
前端
前端直接调用后端Controller接口就可以,没有什么特殊写法,所以我界面写的很简陋
唯二需要注意的是后端接收不到file的问题和Liunx系统下换行符是\r\n需要用正则匹配替换成\n
因为我上传下载的都是纯文本文件,如果有需要的话可以整个富文本框架展示
<body><button id="fileList">查看文件列表</button><input type="file" id="file"><button id="uploadFile">上传文件</button><input type="text" id="fileName" placeholder="输入文件名"><button id="downloadFile">查看文件</button><p></p><script>const fileListButton = document.querySelector('#fileList')const uploadFileButton = document.querySelector('#uploadFile')const p = document.querySelector('p')fileListButton.addEventListener("click", () => {axios({method: 'get',url: "http://localhost:8080/fileList"}).then((res) => {console.log(res)})})uploadFileButton.addEventListener("click", () => {const file = document.querySelector('#file').files[0]const data = new FormData();data.append('file', file);axios({method: 'post',url: 'http://localhost:8080/upload',data: data,headers: {'Content-Type': 'multipart/form-data'}})})const downloadFileButton = document.querySelector('#downloadFile')downloadFileButton.addEventListener("click", () => {const fileName = document.querySelector('#fileName').valueaxios({method: 'get',url: "http://localhost:8080/getFile",params: { fileName: fileName }}).then((res) => {const str = res.data.replace('/\r\n/g', '\n')console.log(str)p.innerText = strconsole.log(res)})})</script>
</body>
总结
用起来还是十分方便的,我没有做JWT之类区分用户,正常来说估计需要通过用户信息创建不同的桶实现数据隔离(多租户),以及前端界面的展示,数据的流式上传下载等
Minio还有许多功能比如设置桶的权限,文件访问权限等,大伙可以自行研究
相关文章:
MinIo前后端实现
这几天想玩玩Minio,整体来说简单使用起来不复杂(当然也有可能是我配置的太少了) Minio下载 我是通过Dokcer在虚拟机上下载的(Docker真好用啊) 拉取Minio镜像 docker pull minio/minio启动Minio容器 docker run -d …...
HarmonyOS NEXT开发进阶(十二):build-profile.json5 文件解析
文章目录 一、前言二、Hvigor脚本文件三、任务与任务依赖图四、多模块管理4.1 静态配置模块 五、分模块编译六、配置多目标产物七、配置APP多目标构建产物八、定义 product 中包含的 target九、拓展阅读 一、前言 编译构建工具DevEco Hvigor(以下简称Hvigor&#x…...
利用 OpenCV 库进行实时目标物体检测
一、代码概述 此代码利用 OpenCV 库实现了基于特征匹配的实时物体检测系统。通过摄像头捕获实时视频帧,将其与预先加载的参考图像进行特征匹配,从而识别出视频帧中是否存在与参考图像匹配的物体。 二、环境依赖 OpenCV:用于图像处理、特征提…...
深度学习笔记(37周)
目录 摘要 Abstracts 1. 介绍 2. 相关工作 3. 模型 3.1 时序段网络TSN 3.2 学习时序段网络 4. 训练结果 5. 结论 摘要 本周阅读的论文是《Temporal Segment Networks: Towards Good Practices for Deep Action Recognition》。作者主要想通过较少的训练样本ÿ…...
Vue2+Vant2 项目初学
Vant 2 - Mobile UI Components built on Vue Vue.js 安装 | 菜鸟教程 // 通过脚手架安装 // 在新项目中使用 Vant 时,推荐使用 Vue 官方提供的脚手架 Vue Cli 创建项目并安装 Vant。 // # 安装 Vue Cli // npm install -g vue/cli // # 创建一个项目 // vue …...
ELK+Filebeat+Kafka+Zookeeper安装部署
1.安装zookeeper zookpeer下载地址:apache-zookeeper-3.7.1-bin.tar.gzhttps://link.csdn.net/?targethttps%3A%2F%2Fwww.apache.org%2Fdyn%2Fcloser.lua%2Fzookeeper%2Fzookeeper-3.7.1%2Fapache-zookeeper-3.7.1-bin.tar.gz%3Flogin%3Dfrom_csdn 1.1解压安装zookeeper软件…...
【接口封装】——21、解析Json对象数组的文本块
解释: 1、封装内容:Json数组的数据处理 Json 数组:[[ {"txt" : "你好"}, { "img", "1"} , {"txt" : "世界"} ], [ {"txt" : "你好"} ]] 数组内的文本块&am…...
【软考-架构】3.3、模式分解-事务并发-封锁协议
✨资料&文章更新✨ GitHub地址:https://github.com/tyronczt/system_architect 文章目录 模式分解(难点)无损分解💯考试真题并发控制封锁协议💯考试真题第一题第二题 模式分解(难点) 保持函…...
审批工作流系统xFlow
WorkFlow-审批流程系统 该项目为完全开源免费项目 可用于学习或搭建初始化审批流程系统 希望有用的小伙伴记得点个免费的star gitee仓库地址 仿钉钉飞书工作审批流系统 介绍 前端技术栈: vue3 ts vite arcodesign eslint 后端技术栈:springbootspring mvc mybatis mavenmysq…...
【数据结构初阶第十九节】八大排序系列(下篇)—[详细动态图解+代码解析]
hello,好久不见! 云边有个稻草人-CSDN博客 上篇内容,回顾一下吧【数据结构初阶第十八节】八大排序系列(上篇)—[详细动态图解代码解析]-CSDN博客 今天我们来学习下篇 目录 (2)快速排序 【挖坑法】 —思路 —思路…...
定制开发开源 AI 智能名片 S2B2C 商城小程序源码在小程序直播营销中的应用与价值
摘要: 本文主要探讨了定制开发开源 AI 智能名片 S2B2C 商城小程序源码在小程序直播营销中的应用与价值。首先详细阐述了小程序直播的基本概念、特点、发展历程及营销意义,包括其便捷性、广泛的受众连接能力以及对企业推广的重要作用。接着深入剖析了定制…...
蓝桥杯Python赛道备赛——Day3:排序算法(二)(归并排序、堆排序、桶排序)
本博客是蓝桥杯备赛系列中排序算法的第二期,包括:归并排序、堆排序和桶排序。每一个算法都在给出概念解释的同时,给出了示例代码,以供低年级师弟师妹们学习和练习。 由于本期的三个算法的复杂度相对来说要高于上一期的三个算法&am…...
Type-C:智能家居的电力革命与空间美学重构
在万物互联的时代浪潮中,家居空间正经历着从功能容器到智慧终端的蜕变。当意大利设计师安东尼奥奇特里奥提出"消失的设计"理念二十年后,Type-C充电技术正以润物无声的方式重塑着现代家居的形态与内核,开启了一场静默的居住革命。 【…...
第十五届蓝桥杯C/C++组:宝石组合题目(从小学奥数到编程题详解)
这道题目真的一看就不好做,如果直接暴力去做百分之90必挂掉,那么这道题目到底应该怎么去做呢?这我们就得从小学奥数开始聊了。(闲话:自从开始蓝桥杯备赛后,每天都在被小学奥数震惊,为什么我小的…...
@RequestParam、@RequestBody、@PathVariable
1. RequestParam RequestParam:重要的是它的属性,如果它的属性用不到,这个注解可以不用 要点: 可用于任何类型的请求(get请求数据在请求行中, post请求数据在请求体中)无论时在请求行还是请求体…...
ECharts中Map(地图)样式配置、渐变色生成
前言 ECharts是我们常用的图表控件,功能特别强大,每次使用都要查API比较繁琐,这里就记录开发中常用的配置。 官网:https://echarts.apache.org/handbook/zh/get-started 配置项:https://echarts.apache.org/zh/opti…...
什么是 slot-scope?怎么理解。
1. 什么是 slot-scope? slot-scope 是 Vue 2 中用于作用域插槽的语法。它的作用是让子组件可以把一些数据传递给父组件,父组件可以根据这些数据自定义渲染内容。 简单来说: 子组件:负责提供数据。 父组件:负责根据数…...
MySQL | MySQL表的增删改查(CRUD)
目录 前言:什么是 CRUD ?一、Creat 新增1.1 语法1.2 示例1.2.1 单行数据全列插入1.2.2 单行数据指定列插入1.2.3 多行数据指定列插入 二、Retrieve 检索2.1 语法2.2 示例2.2.1 全列查询2.2.2 指定列查询2.2.3 查询字段为表达式2.2.4 结果去重查询2.2.5 where条件查…...
电子电气架构 --- 分布到集中的动カ系统及基于域控制器的架构
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所有人的看法和评价都是暂时的,只有自己的经历是伴随一生的,几乎所有的担忧和畏惧,都是来源于自己的想象,只有你真的去做了,才会发现有多快乐。…...
Docker系列——从零开始打包FunASR的Http服务
一、项目结构准备 funasr-docker/ ├── Dockerfile ├── requirements.txt ├── models/ # 预下载模型目录(可选) ├── config/ # 自定义配置文件 │ └── server_config.py └── run.sh # 服务…...
基于微信小程序开发的宠物领养平台——代码解读
项目前端 一、项目的技术架构概况 一句话概括:该项目是基于微信小程序开发的宠物领养平台,采用原生小程序框架进行用户界面的构建,使用 wx.request 进行 API 请求,并通过 getApp() 和本地存储来管理全局状态和用户信息。 一&am…...
基于SpringBoot的“考研互助平台”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“考研互助平台”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 局部E-R图 系统首页界面 系统注册…...
基于javaweb的SpringBoot足球俱乐部管理系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...
DQN 玩 2048 实战|第一期!搭建游戏环境(附 PyGame 可视化源码)
视频讲解: DQN 玩 2048 实战|第一期!搭建游戏环境(附 PyGame 可视化源码) 代码仓库:GitHub - LitchiCheng/DRL-learning: 深度强化学习 2048游戏介绍,引用维基百科 《2048》在44的网格上进行。…...
高频面试题(含笔试高频算法整理)基本总结回顾24
干货分享,感谢您的阅读! (暂存篇---后续会删除,完整版和持续更新见高频面试题基本总结回顾(含笔试高频算法整理)) 备注:引用请标注出处,同时存在的问题请在相关博客留言…...
大模型token和字符串的关系
一 主要区别 token 是使用分词器拆分后的最小单位,不同的分词方式会导致同样的字符具有不同的token数量。如你好,可以拆分为【你、好】两个token, 【你好】一个token。 同一个文本的 Token 数量可能远少于字符数(英文)…...
第八节:红黑树(初阶)
【本节要点】 红黑树概念红黑树性质红黑树结点定义红黑树结构红黑树插入操作的分析 一、红黑树的概念与性质 1.1 红黑树的概念 红黑树 ,是一种 二叉搜索树 ,但 在每个结点上增加一个存储位表示结点的颜色,可以是 Red和 Black 。 通过对 任何…...
【动态规划】- 线性dp
目录 第 N 个泰波那契数 三步问题 使用最小花费爬楼梯 解码方法 第 N 个泰波那契数 1137. 第 N 个泰波那契数 - 力扣(LeetCode) 状态表示 是什么?dp表里面的值所表示的含义怎么来?①题目要求->dp[ i ]表示第 i 个泰波那契…...
Python Cookbook-4.3 若列表中某元素存在则返回之
任务 你有一个列表L,还有一个索引号i,你希望当i是L,的有效索引时获取L[i],若不是有效索引,则返回一个默认值v。如果L是字典,可以使用L.get(i,v)来满足需求,可是列表并没有 get这个方法。 解决方案 很明显…...
Webpack vs Rollup vs Parcel:构建工具深度对比
文章目录 1. 核心特性对比1.1 功能定位1.2 技术架构对比 2. 配置与使用2.1 Webpack 配置示例2.2 Rollup 配置示例2.3 Parcel 使用示例 3. 性能对比3.1 构建速度3.2 输出质量 4. 生态系统4.1 插件生态4.2 学习曲线 5. 适用场景分析5.1 Webpack 适用场景5.2 Rollup 适用场景5.3 P…...
