Vue—大文件分片上传

背景

如题,最近遇到大文件上传慢的问题,用户需要经常上传一些超过一百多M的文件,系统由于历史原因上传功能并没有做分片上传的功能,是整个文件上传,并且服务器带宽限制和NGINX对文件大小的限制等问题,所以决定将文件上传功能改为分片上传。

决定将上传功能修改为分片上传后遂百度分片上传的相关开源项目,本项目使用的技术是Vue2+antd+SpringBoot,但是找到的开源项目基本不合适。

前端Vue代码

1、引入依赖

// 引入SparkMD5用于计算文件MD5值
npm install --save SparkMD5

2、编写UI及对应函数

在这里插入图片描述

3、设置分片大小

data() {return {CHUNK_SIZE: 20 * 1024 * 1024, // 分片上传大小20MB}
}

4、

async customRequest(data) {var that = this// 1、设置文件状态为上传中for (var ff of this.fileList) {if (ff.uid === data.file.uid) {ff.status = 'done'break;}}let file = data.file;let time = new Date().getTime();// 2、求出分片数量、计算文件MD5let chunks = Math.ceil(file.size / that.CHUNK_SIZE);let spark = new SparkMD5.ArrayBuffer();spark.append(file);let md5 = spark.end();console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);spark.destroy(); //释放缓存// 3、循环读取分片并上传let currentChunk = 0;// 3.1、读完第一个分片let blob = loadNext(currentChunk, that.CHUNK_SIZE);for (currentChunk = 1; currentChunk <= chunks; currentChunk++) {// 上传分片var params = {chunkNumber: currentChunk,totalChunks: chunks,chunkSize: that.CHUNK_SIZE,currentChunkSize: blob.size,totalSize: file.size,identifier: md5,filename: file.name}// 3.2、上传文件分片,阻塞等待返回再继续执行await this.uploadFileChunk(data, blob, params);// 3.3、加载下一分片if (currentChunk < chunks) {blob = loadNext(currentChunk, that.CHUNK_SIZE);}}// 4、发送合并文件请求var mergeParams = {totalChunks: chunks,chunkSize: that.CHUNK_SIZE,totalSize: file.size,identifier: md5,filename: file.name}await this.mergeFileChunk(data, mergeParams);// 获取文件分片方法function loadNext(currentChunk, CHUNK_SIZE) {let start = currentChunk * CHUNK_SIZE;let end = start + CHUNK_SIZE >= file.size ? file.size : start + CHUNK_SIZE;return file.slice(start, end);}},

后端Spring Boot代码

后端代码这块是基于开源项目(金鳞岂是池中物灬 / simple-uploader)做了点小改动,具体代码如下:

文件分片上传类FileChunk

@Data
public class FileChunk {/*** 主键id*/private Long id;/*** 当前块的次序,第一个块是 1,注意不是从 0 开始的*/private Integer chunkNumber;/*** 文件被分成块的总数。*/private Integer totalChunks;/*** 分块大小,根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。*/private Integer chunkSize;/*** 当前块的大小,实际大小。*/private Integer currentChunkSize;/*** 文件总大小。*/private Long totalSize;/*** 这个就是每个文件的唯一标示。*/private String identifier;/*** 文件名。*/private String filename;/*** 文件夹上传的时候文件的相对路径属性。*/private String relativePath;/*** 创建时间*/private Date createTime;/*** Spring MultipartFile*/private MultipartFile file;
}

文件分片上传接口

/*** Post方法:分片上传** @param fileChunk 分片* @return AjaxResult*/@PostMapping("/upload")public BaseResponse uploadChunk(FileChunk fileChunk) {logger.info("上传分片——开始:{}", fileChunk.toString());if (fileChunk.getFile().isEmpty()) {logger.error("上传文件不存在!");throw new RuntimeException("上传文件不存在!");}File chunkPath  = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getIdentifier());if (!chunkPath.exists()) {final boolean flag = chunkPath.mkdirs();if (!flag) {logger.error("创建目录失败!");return new BaseResponse().fail("上传失败");}}RandomAccessFile raFile = null;BufferedInputStream inputStream = null;try {File chuckFile = new File(chunkPath, String.valueOf(fileChunk.getChunkNumber()));raFile = new RandomAccessFile(chuckFile, "rw");raFile.seek(raFile.length());inputStream = new BufferedInputStream(fileChunk.getFile().getInputStream());byte[] buf = new byte[1024];int length = 0;while ((length = inputStream.read(buf)) != -1) {raFile.write(buf, 0, length);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {throw new RuntimeException(e);}}if (raFile != null) {try {raFile.close();} catch (IOException e) {throw new RuntimeException(e);}}}logger.info("上传分片——结束:{}", fileChunk.toString());return new BaseResponse().success();}

合并文件分片接口

/**
* 合并文件
*
* @param fileChunk 分片信息
* @return AjaxResult
*/
@PostMapping("/merge")
public BaseResponse merge(FileChunk fileChunk) {logger.info("合并文件——开始:{}", fileChunk.toString());//分片文件临时目录File tempPath = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getIdentifier());// 上传的文件File realFile = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getFilename());// 文件追加写入FileOutputStream os;try {os = new FileOutputStream(realFile, true);if (tempPath.exists()) {//获取临时目录下的所有文件File[] tempFiles = tempPath.listFiles();//按名称排序Arrays.sort(tempFiles, (o1, o2) -> {if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {return -1;}if (Integer.parseInt(o1.getName()) == Integer.parseInt(o2.getName())) {return 0;}return 1;});//每次读取10MB大小,字节读取byte[] bytes = new byte[10 * 1024 * 1024];int len;for (int i = 0; i < tempFiles.length; i++) {FileInputStream fis = new FileInputStream(tempFiles[i]);while ((len = fis.read(bytes)) != -1) {os.write(bytes, 0, len);}fis.close();//删除分片tempFiles[i].delete();}os.close();// TODO:验证合并文件的MD5值是否与传输过来的文件MD5值一致//删除临时目录if (tempPath.isDirectory() && tempPath.exists()) {System.gc(); // 回收资源tempPath.delete();}}} catch (Exception e) {logger.error("文件合并——失败 " + e.getMessage());return new BaseResponse().fail("文件合并失败");}logger.info("合并文件——结束:{}", fileChunk.toString());// 文件合并成功,下一步上传至到阿里云String ossUrl = uploadFileToOos(realFile, fileChunk.getFilename());if(StrUtil.isEmpty(ossUrl)){return new BaseResponse().fail("文件上传失败");}Map<String, Object> returnMap = new HashMap<>();returnMap.put("oosUrl", ossUrl);returnMap.put("attachmentName", fileChunk.getFilename());return new BaseResponse().success(returnMap);
}

上述合并文件代码其实缺少了验证文件MD5值这一步,当时写代码时没发现。哈哈。

总结

至此文件分片上传的功能已经开发完毕,基于上述代码其实还可以实现文件秒传、断点续传和失败重试功能。

参考

CSDN博文 vue—大文件分片上传
金鳞岂是池中物灬 / simple-uploader

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

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

相关文章

RabbitMQ中的手动应答和自动应答

当使用RabbitMQ来处理消息时&#xff0c;消息确认是一个重要的概念。RabbitMQ提供了两种不同的消息确认方式&#xff1a;自动应答&#xff08;Automatic Acknowledgment&#xff09;和手动应答&#xff08;Manual Acknowledgment&#xff09;。这两种方式适用于不同的应用场景&…

【excel技巧】如何在Excel表格中添加选项按钮?

不知道大家是否会9遇到需要勾中选项的情况&#xff0c;我们可以在电子表格中制作出可以勾选、选中的选项按钮&#xff0c;今天我们一起学习一下设置方法。 首先&#xff0c;我们需要先在excel工具栏中添加一个功能模块&#xff1a;开发工具 依次点击excel中的文件 – 选项 –…

炒现货白银的最佳时间

天时地利人和是我们进行现货白银投资最关键的因素。天时是指我们因时而动&#xff0c;在适合的时机出击。地利&#xff0c;就是我们对市场的定位&#xff0c;对自己入场的定位有清晰的了解&#xff0c;并且这些位置对我们有利。人和就是指投资者的状态很好&#xff0c;对如何进…

数学建模——确定性时间序列分析方法

目录 介绍 确定性时间序列分析方法 1、时间序列的常见趋势 &#xff08;1&#xff09;长期趋势 &#xff08;2&#xff09;季节变动 &#xff08;3&#xff09;循环变动 &#xff08;4&#xff09;不规则变动 常见的时间序列模型有以下几类 2、时间序列预测的具体方法 …

1000个已成功入职的软件测试工程师简历范文模板(含真实简历)

如果你想学习自动化测试&#xff0c;那么下面这套视频应该会帮到你很多 如何逼自己1个月学完自动化测试&#xff0c;学完即就业&#xff0c;小白也能信手拈来&#xff0c;拿走不谢&#xff0c;允许白嫖.... 最后我这里给你们分享一下我所积累和整理的一些文档和学习资料&#…

EDUSRC-记一个SHELL捡漏

目录 ​编辑 Jenkins - println绕过到shell命令执行 语法 Jenkins未授权访问(捡漏失败) Jenkins捡漏 弱口令 脚本执行(println失败) CHATGPT调教绕过 hack渗透视频教程&#xff0c;扫码免费领 Jenkins - println绕过到shell命令执行 语法 org"China Education and…

IDEA插件版本升级和兼容新版本idea

1.关于IDEA插件的版本设置问题 打开jetbrains插件市场&#xff0c;随意打开一个插件详情页面的Versions菜单&#xff0c;我们可以看见一个插件包不同时期发布的不同版本&#xff08;Versions&#xff09;&#xff0c;并且每个版本包含了可兼容IDEA或PyCharm的版本范围&#xf…

Avalonia常用小控件Menu

1.项目下载地址&#xff1a;https://gitee.com/confusedkitten/avalonia-demo 2.UI库Semi.Avalonia&#xff0c;项目地址 https://github.com/irihitech/Semi.Avalonia 样式预览&#xff1a; axaml代码 &#xff1a; <UserControl xmlns"https://github.com/avalo…

【个人博客公网访问】使用Cpolar+Emlog在Ubuntu上轻松搭建个人博客公网访问

文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总结 前言 博客作为使…

性能测试 —— 生成html测试报告、参数化、jvm监控

1.生成HTML的测试报告 1.1配置 (1)找到jmeter 的安装目录&#xff0c;下的bin中的jmeter.properties&#xff08;jmeter配置文件&#xff09; (2) ctrl f &#xff0c;搜索jmeter.save.saveservice.output_format&#xff0c;取消井号 并且 把等号后的xml改为csv&#xff0c;…

Vue 中 KeepAlive 内置缓存使用

KeepAlive 介绍及使用场景 KeepAlive 是 vue 中的内置组件&#xff0c;当多个组件动态切换时可以对实例状态进行缓存&#xff0c;用法如下 <router-view v-slot"{ Component }"><keep-alive><component :is"Component" /></keep-al…

[NOIP 2022] 建造军营 题解

题目 P1 边双缩点 观察样例二&#xff0c;可以发现边双内的边可选可不选。由此考虑边双缩点&#xff0c;Tarjan 找桥即可&#xff0c;缩点后变成一棵树。 P2 设计状态 用最终合法答案形态截这颗树&#xff0c;设计 f u f_u fu​ 表示 u u u 子树内非空&#xff0c;且子树…

yolov5+bytetrack算法在华为NPU上进行端到端开发

自从毕业后开始进入了华为曻腾生态圈&#xff0c;现在越来越多的公司开始走国产化路线了&#xff0c;现在国内做AI芯片的厂商比如&#xff1a;寒武纪、地平线等&#xff0c;虽然我了解的不多&#xff0c;但是相对于瑞芯微这样的AI开发板来说&#xff0c;华为曻腾的生态比瑞芯微…

Python读取zip文件并解压,循环解压文件内的每个文件

读取并解压zip文件需要用到工具zipfile 示例代码 import zipfile import osdef readfile(path):files os.listdir(path)file_list []for file in files: # 遍历文件夹if not os.path.isdir(file):file_list.append(path / file)return file_listdef read_zip(file_name)…

麻省理工学院与Meta AI共同开发StreamingLLM框架,实现语言模型无限处理长度

&#x1f989; AI新闻 &#x1f680; 麻省理工学院与Meta AI共同开发StreamingLLM框架&#xff0c;实现语言模型无限处理长度 摘要&#xff1a;麻省理工学院与Meta AI的研究人员联合研发了一款名为StreamingLLM的框架&#xff0c;解决了大语言模型在RAM与泛化问题上的挑战&am…

SpringBoot如何动态更新yml文件?

前言 在系统运行过程中&#xff0c;可能由于一些配置项的简单变动需要重新打包启停项目&#xff0c;这对于在运行中的项目会造成数据丢失&#xff0c;客户操作无响应等情况发生&#xff0c;针对这类情况对开发框架进行升级提供yml文件实时修改更新功能 项目依赖 项目基于的是…

JAVA练习百题之求矩阵对角线之和

题目&#xff1a;求一个3*3矩阵对角线元素之和 程序分析 求一个3x3矩阵的对角线元素之和&#xff0c;我们需要将矩阵的左上到右下以及左下到右上两条对角线上的元素相加。 一个3x3矩阵如下所示&#xff1a; 1 2 3 4 5 6 7 8 9左上到右下的对角线元素和为1 5 9 15&…

信息系统项目管理师第四版学习笔记——配置与变更管理

配置管理 管理基础 配置管理是为了系统地控制配置变更&#xff0c;在信息系统项目的整个生命周期中维持配置的完整性和可跟踪性&#xff0c;而标识信息系统建设在不同时间点上配置的学科。 配置项的版本号规则与配置项的状态定义相关。例如&#xff1a;①处于“草稿”状态的…

CSPM考试报名条件是什么?好考吗?

★CSPM是什么&#xff1f; CSPM——项目管理专业人员能力评价&#xff0c;是中国人自己的一套项目管理专业人士的评价指南&#xff0c;符合中国国情且符合中国未来发展的一套项目刊专业人员能力评价的标准。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff…

微信开发工具构建npm and git切换分支

目录 git切换分支NPM构建 git切换分支 案例&#xff1a; 再次查看分支就会发现自己的分支已切换&#xff0c;然后需要重新构建NPM一次 NPM构建 记得安装一下这个&#xff0c;然后在构建 如果未安装NPM&#xff0c;这时候需要打开命令端&#xff0c;安装操作&#xff0c;操作…