【前端学习记录】记一次分片上传逻辑的调试过程

前言

在项目开发的过程中,经常会遇到上传和下载,对于上传来说,如果是小文件的话,接口响应会比较快,但是对于大文件,则需要对其分片以减少请求体的大小和上传时间。

小文件上传

以Vue框架使用<el-upload>为例,直接上代码

<template><div><el-uploadclass="upload-demo"action="your_upload_api_url":on-success="handleSuccess":before-upload="beforeUpload"><el-button size="small" type="primary">点击上传</el-button><div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div></el-upload></div>
</template><script>
export default {methods: {handleSuccess(response, file) {// 处理上传成功的逻辑console.log(response, file);},beforeUpload(file) {// 在上传之前的操作,例如限制文件类型、大小等const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';if (!isJPG) {this.$message.error('只能上传jpg/png文件');}const isLt500K = file.size / 1024 < 500;if (!isLt500K) {this.$message.error('文件大小不能超过500KB');}return isJPG && isLt500K;},},
};
</script><style scoped>
/* 样式可以根据自己的需求进行调整 */
.upload-demo {display: flex;justify-content: center;align-items: center;height: 180px;
}
</style>

在上述代码中:

<el-upload> 组件用于处理文件上传,通过 action 属性指定文件上传的接口。
:on-success 属性绑定一个方法,在文件上传成功后触发。
:before-upload 属性绑定一个方法,在文件上传之前触发,可以在该方法中进行一些操作,如限制文件类型和大小。
元素用于触发文件选择。
请注意替换 your_upload_api_url 为实际的文件上传接口。

分片上传

文件过大时就需要进行文件分片上传,文件分片上传是一种将大文件拆分成小块(分片)并分别上传的策略,这样可以更有效地处理大文件上传,避免一次性上传整个文件可能遇到的网络问题和服务器限制。FormData 对象和一些前端框架/库(如 axios)通常与文件分片上传一起使用。

下面是一个简单的实现示例,使用 FormData 和 axios 进行文件分片上传:

<template><div><input type="file" ref="fileInput" @change="handleFileChange" /><button @click="startUpload">开始上传</button></div>
</template><script>
import axios from 'axios';export default {data() {return {selectedFile: null,chunkSize: 1024 * 1024, // 每个分片的大小,这里设置为1MB};},methods: {handleFileChange(event) {this.selectedFile = event.target.files[0];},async startUpload() {if (!this.selectedFile) {alert('请选择文件');return;}// 计算总分片数量const totalChunks = Math.ceil(this.selectedFile.size / this.chunkSize);// 循环上传分片for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {const start = chunkIndex * this.chunkSize;const end = Math.min(start + this.chunkSize, this.selectedFile.size);const chunk = this.selectedFile.slice(start, end);const formData = new FormData();formData.append('file', chunk);formData.append('chunkIndex', chunkIndex);formData.append('totalChunks', totalChunks);try {await axios.post('your_chunk_upload_api_url', formData);console.log(`分片 ${chunkIndex + 1} / ${totalChunks} 上传成功`);} catch (error) {console.error(`分片 ${chunkIndex + 1} / ${totalChunks} 上传失败`, error);// 处理上传失败的逻辑,可以选择中止上传或重试return;}}console.log('文件上传完成');},},
};
</script>

上面是一个分片上传的示例,在实际操作时遇到了一些问题

分片上传遇到的问题

问题1:请求体过大,如何处理?

项目中遇到的文件最大约1个GB,此时直接上传,会报请求体过大的报错,经过调试后发现文件最大传输为50MB。由于项目是依赖于平台,属于平台的子项目,因此在前后端联调时,前端通过nginx转发到对应的接口上。在nginx配置里,有50M大小的限制,修改后生效。后台同事在排查后台代码及配置也发现了请求不能过大的限制条件,即ingress中设置了请求的大小,最终两者同时修改后生效

nginx中的配置修改如下:
在这里插入图片描述

问题2:前端如何获取上传进度条?

在使用 axios 进行文件上传时,你可以通过配置 onUploadProgress 属性来监听上传进度。onUploadProgress 允许你在上传过程中获取上传进度,并执行相应的操作。

axios.post('your_upload_api_url', formData, {onUploadProgress: progressEvent => {const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);console.log(`上传进度: ${percentCompleted}%`);// 在这里可以更新进度条或执行其他操作},}).then(response => {// 处理上传成功的逻辑console.log(response.data);}).catch(error => {// 处理上传失败的逻辑console.error('上传失败', error);});

需要注意的是,这里的progressEvent.total并不一定和文件的大小相等,处理百分比时,尽量使用total而不是file.size。

问题3:串行上传时文件上传没有问题,但是并行上传时,后台取到的文件片组装后错误?

经过定位,发现是在分片时,最后一片会比较小,如果串行上传时,前端一片片按着顺序依次上传,想优化上传速度,使用并行上传时,最后一片通常都会比前面的分片小,导致最后一片先上传,然后发生组装错误。经过测试,可以采取以下思路:1、前端最后一片在前面的分片都上传完毕后,上传最后一片,可使用for循环配合Promise.all处理;2、后端在拿到所有的分片后再开始组装,而不是边上传边组装。

问题4:进度条上传达到100%后,没有立刻返回结果

这是因为后台接收到文件后,可能还有处理的时间,但是文件已经传到了后台,如果后台没有其他逻辑处理,可以直接返回结果,告知用户上传已完成
最后,放上去部分代码

async handleUpload() {const file = this.formData.file;// 初始化分片的大小,可以自定义const chunkSize = 200 * 1024 * 1024;// 计算分片的数量, 传参时会用到const chunkCount = Math.ceil(file.size / chunkSize);// 用于保存每个分片的信息const chunks = [];if (file.size > chunkSize) {// 分割文件为多个分片for(let i = 0; i < chunkCount; i++) {const start = i * chunkSize;const end = Math.min(file.size, (i + 1) * chunkSize);const chunk = file.raw.slice(start, end);chunks.push(chunk);}} else {chunks.push(file.raw);}try {this.uploadLoading = true;// 创建一个数组来存储每个分片上传的 Promiseconst uploadPromises = [];for(let i = 0; i < chunks.length; i++) {this.loadedSizeArr[i] = 0;this.totalSizeArr[i] = 0;}const fileFlag = getRandomName();let percentage = 1;if (chunks.length > 1) {percentage = (chunks.length-1) / chunks.length;}const headers = {fileFlag,fileName: file.name,'Content-Type': 'multipart/form-data',}// 遍历并上传每个分片for(let i = 0; i < Math.max(chunks.length - 1, 1); i++) {const formData = new FormData();formData.append('file', chunks[i]);formData.append('chunkNumber', String(i+1));formData.append('totalChunks', String(chunkCount));// 创建分片上传的 Promiseconst uploadPromise = util.post('your_upload_api_url', formData, {headers,onUploadProgress: (progressEvent) => {if (progressEvent.lengthComputable) {this.loadedSizeArr[i] = progressEvent.loaded;this.totalSizeArr[i] = progressEvent.total;const loadedSizeTotal = this.loadedSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);const totalSizeTotal = this.totalSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);this.percent = Math.round(loadedSizeTotal / totalSizeTotal * percentage * 100);}},})// 将 Promise 存储到数组中uploadPromises.push(uploadPromise);}// 使用 Promise.all 来等待所有分片上传完成Promise.all(uploadPromises).then(async () => {if (chunkCount > 1) {const formData = new FormData();formData.append('file', chunks[chunks.length-1]);formData.append('chunkNumber', String(chunkCount));formData.append('totalChunks', String(chunkCount));await util.post('your_upload_api_url', formData, {headers,onUploadProgress: (progressEvent) => {if (progressEvent.lengthComputable) {this.loadedSizeArr[chunks.length-1] = progressEvent.loaded;this.totalSizeArr[chunks.length-1] = progressEvent.total;const loadedSizeTotal = this.loadedSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);const totalSizeTotal = this.totalSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);this.percent = Math.round((percentage + loadedSizeTotal / totalSizeTotal / chunkCount) *100);}},})}// 所有分片上传完成后执行的逻辑this.percent = 100;this.uploadLoading = false;this.$notify({type: 'success',title: '成功',message:  '上传成功!',})})} catch (e) {this.$notify({type: 'error',title: '失败',message: '上传失败!',});}},

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

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

相关文章

官宣 鸿雁成为2023汇丰世界羽联世界巡回赛总决赛官方供应商

全屋智能和羽毛球运动能擦出怎样的火花&#xff1f; 鸿雁给你答案&#xff01; 12月13日&#xff0c;2023汇丰世界羽联世界巡回赛总决赛将在杭州举行。 鸿雁签约成为2023汇丰世界羽联世界巡回赛总决赛官方供应商&#xff0c;将携手世界羽联&#xff0c;为广大羽毛球爱好者们…

Yolov5双目测距-双目相机计数及测距教程(附代码)

引言 在计算机视觉领域&#xff0c;Yolov5-Binocular相机距离计数及测距是一个引人注目的研究方向。本教程将为小白用户提供一个简明扼要的学习指南&#xff0c;涵盖了关键步骤&#xff0c;包括标定、公示推倒以及重要的代码片段。 第一步&#xff1a;环境搭建 首先&#x…

【Geoserver】将geoserver迁移到jetty的发行包中

之前讲了在Geosever的二进制发行包中升级jetty的内容&#xff0c;我测试之后发现有些问题&#xff0c;本地运行可能没有问题&#xff0c;但是在linux上运行报错了。 于是我想着换个思路好了&#xff0c;总是想着将Geosever中的jetty包替换掉&#xff0c;干脆反过来&#xff0c;…

2023_Spark_实验二十六:编写Shell模拟生成点击实时数据

引言&#xff1a;流式数据处理主要处理实时数据&#xff0c;由于实验教学过程中&#xff0c;每个同学无法拿到实时数据&#xff0c;因此我们开发shell脚本模拟实时数据生成&#xff0c;支持后续实验。 实验目的&#xff1a;通过开发模拟实时点击流shell脚本&#xff0c;模拟实时…

【Database】什么是数据库?常见的数据库类型有哪些?

什么是数据库&#xff1f;常见的数据库类型有哪些&#xff1f; 首先&#xff0c;什么是数据库&#xff1f;把它想象成一个数字游乐场&#xff0c;我们以结构化的方式组织和存储大量信息。现在&#xff0c;让我们来谈谈数据库的主要类型。 关系型数据库&#xff1a; 想象一下…

深度学习的目标检测算法综述

信息记录材料 2022年10月 第23卷第10期 【摘要】目标检测是深度学习的一个重要应用&#xff0c;目前在智能驾驶、工业检测相关领域都获得应用&#xff0c;具有重要的现实意义。本文对基于深度学习目标检测算法原理和应用情况进行简述&#xff0c;首先介绍结合区域提取和卷积神经…

Spring上IOC之@EnableAspectJAutoProxy

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

3分钟,掌握“曲面屏显示屏”

在3分钟内掌握“曲面屏显示屏”的概念和特点&#xff0c;可以按照以下步骤进行&#xff1a; 一、了解曲面屏显示屏的基本概念 曲面屏显示屏是一种采用柔性塑料的显示屏&#xff0c;主要通过OLED面板来实现。相比直面屏幕&#xff0c;曲面屏幕弹性更好&#xff0c;不易破碎。此外…

扫描电镜(SEM)样品在进行扫描电镜观察前需要进行哪些处理

对于扫描电镜&#xff08;Scanning Electron Microscope&#xff0c;SEM&#xff09;样品的制备&#xff0c;需要经过一系列处理步骤以确保样品表面的干净、导电性好&#xff0c;并且能够提供高质量的显微图像。以下是一些常见的处理步骤&#xff1a; 1. 固定样品&#xff08;…

锂电3V升12V1A升压芯片WT3209

锂电3V升12V1A升压芯片WT3209 WT3209是一款高功率密度全集成BOOST升压转换器&#xff0c;具备高效能解决方案。3V升12V1A,5V升12V1A WT3209内部集成的功率MOSFET管导通电阻为上管13mΩ和下管11mΩ&#xff0c;具备2A开关电流能力&#xff0c;并且能够提供高达12.6V的输出电压。…

算法:只出现一次的数字(位运算:异或运算)

异或运算 时间复杂度 O(n) 空间复杂度 O(1) /*** param {number[]} nums* return {number}*/ var singleNumber function (nums) {let item nums[0]if (nums.length > 1) {for (let i 1; i < nums.length; i) {// 将10进制转成2进制进行异或运算&#xff08;相同得0&…

CB400X即将停产?NX400上线,本田最新的外观设计直接就国产了?

NX500 之前米兰车展的时候给大家分享过本田对于500系列的升级&#xff0c;并且宣布NX500代替CB500X&#xff0c;采用了全新的外观设计&#xff0c;没有看过的小伙伴可以查阅下之前的文章内容&#xff0c;不过最新的工信部的信息&#xff0c;可以看到NX500的外观设计应用到了CB…

光栅化渲染:光栅化算法实现

光栅化是将图元转换为二维图像的过程。 该图像的每个点都包含颜色和深度等信息。 因此&#xff0c;对图元进行光栅化由两部分组成。 第一个是确定窗口坐标中整数网格的哪些方格被图元占据。 第二个是为每个这样的方块分配颜色和深度值。 &#xff08;OpenGL 规范&#xff09; N…

CTD测试流程

连接 连接17Plus&#xff0c;用usb转232线&#xff0c;db9公针2、3分别接Data I/O的2、3。DB9的5接Data I/O的1。尼龙塞子打开状态。不用闭合。 软件连接 打开SeaTermAF V2&#xff0c;注意打开前先把串口插上&#xff0c;否则软件读不到串口。如果读不到&#xff0c;就在插…

C/C++ 快乐数: 编写一个算法来判断一个数n是不是快乐数

题目&#xff1a; 编写一个算法来判断一个数n是不是快乐数。 快乐数的定义&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 如果这个过…

使用不同的颜色映射绘制热力图Seaborn

使用不同的颜色映射绘制热力图 一、使用 Seaborn 库创建热力图二、cmap关于其他颜色的设置三、自己设置颜色映射 一、使用 Seaborn 库创建热力图 选择Seaborn颜色官网 使用 Seaborn 库创建热力图&#xff0c;你可以使用 seaborn.heatmap() 函数。下面是一个示例代码&#xf…

网络基础(七):传输层协议介绍

目录 一、TCP协议&#xff08;传输控制协议&#xff09; 1、TCP协议介绍 2、TCP协议特性 3、TCP报文格式 4、TCP的三次握手 4.1TCP三次握手的概念 4.2TCP三次握手流程图 4.3 TCP三次握手阐释说明 5、TCP的四次挥手 5.1TCP四次挥手的概念 5.2TCP四次挥手的流程图 5.…

【C语言】RDMACM、Verbs API与epoll一起使用的示例

一、epoll介绍 epoll是Linux内核为处理大批量文件描述符而作了改进的poll&#xff0c;是Linux下多路复用IO接口select/poll的增强版本&#xff0c;它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。 以下是epoll的主要使用方法和优点&#xff1a; epo…

jsp+servlet+图书交流平台 有filter过滤器

在线图书推荐与交流平台 随着数字化的进展和人们对持续学习的追求&#xff0c;在线资源变得越来越受欢迎。对于众多读者来说&#xff0c;找到合适的书籍和与其他读者交流阅读体验是非常有价值的。为了满足这一需求&#xff0c;我们提出了一个在线图书推荐与交流平台的设计。此…