本文采用前后端分离模式,后端给前端每个分片的上传临时凭证,前端请求临时 url,通过后端间接的去上传分片。前端分别提供了 vue3 (TypeScript)和 react(TypeScript) 版本的示例,代码逻辑保持 99% 一致,差异仅仅是为了适配两个框架不同组件库 API 的差异。
前端 React:React + TypeScript + Antd + axios + spark-md5 前端 Vue:vue@3.4 + TypeScript + arco design vue + axios + spark-md5后端:Springboot 3.x + minio + mybatisplus + redis + lombok + hutool-core
前后端的依赖都是截止 2024.05 的最新版。代码较多,文章内只讲实现思路,gitee 地址: https://gitee.com/jsonqi/minio-spring-web
为什么不直接前端对接 minio,而要走后端?
为什么要给前端每个分片的 url 上传,而不是前端将每个分片给后端,后端存入 minio?
ContentType
,可以在线访问浏览器支持的文件格式,如:图片、视频、PDF 等。展示及请求
前端控制台
浏览器可支持的文件预览
目录结构
一个文件的上传,对接后端的请求有三个
<检查文件 md5>
接口,判断文件的状态(已存在、未存在、传输部分)<初始化分片上传地址>
,得到该文件的分片地址<合并文件>
接口,合并文件,文件数据入库整体步骤:
uploadId
(minio 中的文件标识)和 listParts
(已上传的分片索引),前端请求初始化分片接口,后端重新生成上传地址。前端循环将已上传的分片过滤掉,未上传的分片和分片地址一一对应。该 bucket 开启了公共只读,所以链接地址是可以直接后台拼接的。如何开启公共只读,可参考文章:MinIO 基础 api
大文件在全量计算 md5 的情况下,耗时很长,所以之前添加了进度来显得不那么单调,此次优化项
未优化前,一个 2.1G 的文件上传耗时 18s
优化后,仅 2s
作者也说得很清楚,默克尔树 依赖于分片的 md5,分片大小改变会导致分片的 md5 改变,最终导致文件的 md5 和之前不同,采用此方法的前提是:分片大小不会轻易更改,否则慎重使用,至于哈希碰撞的情况,没有大量数据无法实测
下载时,比如附件这种,前端是循环渲染出来的,展示的也是原文件名,该数据的信息都知道。请求后台下载接口时,尽量不要使用文件名,而使用主键 id 去获取文件
下载及暂停操作展示
下载内容展示