使用 Axios 上传大文件分片上传
背景
在上传大文件时,分片上传是一种常见且有效的策略。由于大文件在上传过程中可能会遇到内存溢出、网络不稳定等问题,分片上传可以显著提高上传的可靠性和效率。通过将大文件分割成多个小分片,不仅可以减少单次上传的数据量,降低内存消耗,还能在遇到网络中断时仅需重传失败的分片,从而提高整体上传的成功率和用户体验。
步骤
安装 Axios
如果你还没有安装 Axios,可以通过 npm 或 yarn 来安装:
npm install axios
# 或者
yarn add axios
获取文件
点击按钮选择文件上传,通过 event 事件对象拿到文件。
<template><div><input type="file" @change="uploadFile"></input></div>
</template>
<script>
import axios from "axios";export default {methods: {uploadFile(event) {const files = event.target.files || event.dataTransfer.files;const file = files[0];console.log('file::: ', file);this.uploadChunks(file, file.name, progress => {console.log(`Upload progress: ${progress * 100}%`);});},},
}
</script>
文件切片并使用 Axios 上传切片:
1. 文件切片:
- 定义
chunkSize每片大小为 1MB,计算文件需要分割成的总分块数totalChunks。
2. 循环分块上传:
-
遍历每个分块,计算每个分块的起始位置
start和结束位置end。 -
使用
file.slice方法创建blob对象表示当前分块。 -
创建
FormData对象,并添加分块数据及其他元数据(文件名、分块索引、总分块数)。
3. 循环分块上传:
-
使用
axios.post发送 POST 请求到/upload接口,携带分块数据。 -
设置请求头
Content-Type为multipart/form-data。
4. 循环分块上传:
-
成功上传分块后,记录已上传的分块数量,并调用上传进度的回调函数
onProgress。 -
设如果上传失败,捕获并记录错误信息。
async uploadChunks(file, fileName, onProgress) {const chunkSize = 1 * 1024 * 1024; // 1MBconst totalChunks = Math.ceil(file.size / chunkSize);let uploadedChunks = 0;for (let i = 0; i < totalChunks; i++) {const start = i * chunkSize;const end = Math.min(start + chunkSize, file.size);const blob = file.slice(start, end);const formData = new FormData();formData.append('file', blob, `${fileName}_${i}`);formData.append('filename', fileName);formData.append('chunkIndex', i.toString());formData.append('totalChunks', totalChunks.toString());try {const response = await axios.post('/upload', formData, {headers: {'Content-Type': 'multipart/form-data',},});console.log(`Chunk ${i} uploaded successfully.`);uploadedChunks++;if (onProgress) {onProgress(uploadedChunks / totalChunks);}} catch (error) {console.error(`Failed to upload chunk ${i}:`, error);}}}
完整代码
<template><div><input type="file" @change="uploadFile"></input></div>
</template><script>
import axios from "axios";export default {data() {},methods: {uploadFile(event) {console.log('event::: ', event);// 获取文件对象const files = event.target.files || event.dataTransfer.files;console.log('files::: ', files);const file = files[0];this.uploadChunks(file, file.name, progress => {console.log(`Upload progress: ${progress * 100}%`);});},async uploadChunks(file, fileName, onProgress) {// 定义每个分片的大小为 1MBconst chunkSize = 1 * 1024 * 1024; // 1MB// 计算总分片数const totalChunks = Math.ceil(file.size / chunkSize);let uploadedChunks = 0;// 遍历所有分片for (let i = 0; i < totalChunks; i++) {// 计算当前分片的起始位置const start = i * chunkSize;// 计算当前分片的结束位置const end = Math.min(start + chunkSize, file.size);// 创建当前分片的 Blob 对象const blob = file.slice(start, end);// 创建表单数据对象const formData = new FormData();// 添加当前分片的文件formData.append('file', blob, `${fileName}_${i}`);// 添加文件名formData.append('filename', fileName);// 添加分片索引formData.append('chunkIndex', i.toString());// 添加总分片数formData.append('totalChunks', totalChunks.toString());try {// 上传分片const response = await axios.post('/upload', formData, {headers: {'Content-Type': 'multipart/form-data',},});console.log(`Chunk ${i} uploaded successfully.`);uploadedChunks++;// 上传进度if (onProgress) {onProgress(uploadedChunks / totalChunks);}} catch (error) {console.error(`Failed to upload chunk ${i}:`, error);}}}}
}
</script><style lang="scss" scoped></style>
注意:
- 使用
FormData上传文件切片,确保文件部分是以二进制格式上传的。- 设置
Content-Type为multipart/form-data。
服务端合并切片
实现原理
1. 搭建服务
-
服务搭建:引入
express模块,创建了一个express应用实例app。 -
设置端口号
PORT并使用app.listen()启动express应用,使其监听指定的端口。
2. 接受并存储切片
-
接收切片:服务端定义了一个
/upload路由,使用multer中间件处理上传的文件切片。multer会将上传的文件暂存到指定的目录(例如uploads/)。 -
保存切片:服务端根据
filename和chunkIndex创建一个临时目录,并将上传的切片移动到该目录中。例如,切片路径可能为uploads/filename/chunkIndex。 -
创建目录:如果临时目录不存在,服务端会使用 mkdir 方法递归创建目录。
3. 切片合并
-
检测最后一个切片:当接收到的切片索引等于
totalChunks - 1时,说明这是最后一个切片,触发切片合并操作。 -
读取所有切片:在
mergeChunks函数中,服务端遍历所有已上传的切片,按顺序读取每个切片的内容。 -
合并切片:将所有切片的内容按顺序拼接成一个完整的文件。这里使用
Buffer.concat方法将多个Buffer对象合并成一个。 -
写入合并后的文件:将合并后的文件内容写入到目标目录(例如
merged/)。 -
删除临时文件:合并完成后,删除所有临时切片文件,释放存储空间。
使用 node 示例
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const util = require('util');const app = express();
const upload = multer({ dest: 'uploads/' });// 设置静态文件夹
app.use(express.static('uploads'));// 将 fs 方法转换为 Promise 版本
const mkdir = util.promisify(fs.mkdir);
const rename = util.promisify(fs.rename);
const unlink = util.promisify(fs.unlink);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);// 文件合并函数
async function mergeChunks(filename, totalChunks) {// 定义存储切片临时文件夹路径const tempDir = `uploads/${filename}/`;// 定义最终合并文件的路径const outputFilePath = `merged/${filename}`;// 创建输出目录await mkdir(path.dirname(outputFilePath), { recursive: true });// 初始化一个空的 Buffer 用于存储合并后的数据let combinedData = Buffer.alloc(0);// 遍历所有切片文件并读取内容for (let i = 0; i < totalChunks; i++) {// 获取每个切片文件的路径const chunkPath = `${tempDir}${i}`;// 读取当前切片文件的内容const chunkData = await readFile(chunkPath);// 合并切片文件的内容追加到 combinedData 中combinedData = Buffer.concat([combinedData, chunkData]);}// 将合并后的数据写入最终的输出文件await writeFile(outputFilePath, combinedData);console.log('File merged successfully.');// 删除临时切片文件for (let i = 0; i < totalChunks; i++) {const chunkPath = `${tempDir}${i}`;try {await unlink(chunkPath);} catch (err) {console.error(`Error deleting chunk ${i}:`, err);}}// 删除临时文件夹try {await rmdir(tempDir, { recursive: true });console.log('Temporary directory deleted successfully.');} catch (err) {console.error('Error deleting temporary directory:', err);}
}// 处理文件上传
app.post('/upload', upload.single('file'), async (req, res) => {const { filename, chunkIndex, totalChunks } = req.body;const chunkPath = `uploads/${filename}/${chunkIndex}`;try {// 创建文件切片目录await mkdir(path.dirname(chunkPath), { recursive: true });// 移动上传的文件到切片目录await rename(req.file.path, chunkPath);console.log(`Chunk ${chunkIndex} saved successfully`);// 如果这是最后一个切片,则合并所有切片if (parseInt(chunkIndex) === parseInt(totalChunks) - 1) {await mergeChunks(filename, totalChunks);}res.status(200).send('Chunk received');} catch (err) {console.error(`Error handling chunk ${chunkIndex}:`, err);res.status(500).send('Internal Server Error');}
});// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`Server is running on port ${PORT}`);
});
注意:
- 将
fs模块的方法转换为Promise版本,以便防止文件合并顺序错误而导致文件损坏。- 在创建输出文件流时,设置
flags: 'w'和encoding: null,确保以二进制格式写入文件。- 在创建输入文件流时,设置
encoding: null,确保以二进制格式读取文件。
总结
前端:
点击按钮选取文件后,通过事件对象 event 拿到文件并按指定大小(如 1MB)进行分片,使用循环遍历每个分片,创建 blob 对象表示分片,将分片及其相关信息(文件名、分片索引、总分片数)封装到 FormData 对象中,最后使用 axios 发送 POST 请求上传每个分片。
服务端:
服务端通过 API 接口(如 /upload)接收前端上传的每个分片,解析请求中的 formData,提取分片数据、文件名、分片索引和总分片数,使用 express 和 multer 接收这些片段,将其保存到临时目录,并在接收到最后一个片段时调用 mergeChunks 函数将所有片段合并成一个完整的文件。合并完成后,删除临时文件。整个过程包括文件切片、上传、保存、合并和清理,确保了大文件的高效传输和处理。
相关文章:
使用 Axios 上传大文件分片上传
背景 在上传大文件时,分片上传是一种常见且有效的策略。由于大文件在上传过程中可能会遇到内存溢出、网络不稳定等问题,分片上传可以显著提高上传的可靠性和效率。通过将大文件分割成多个小分片,不仅可以减少单次上传的数据量,降…...
Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP
1 、安装OpenResty 安装使用 OpenResty,这是一个集成了各种 Lua 模块的 Nginx 服务器,是一个以Nginx为核心同时包含很多第三方模块的Web应用服务器,使用Nginx的同时又能使用lua等模块实现复杂的控制。 (1)安装编译工具…...
PART 1 数据挖掘概论 — 数据挖掘方法论
目录 数据库知识发掘步骤 数据挖掘技术的产业标准 CRISP-DM SEMMA 数据库知识发掘步骤 数据库知识发掘(Knowledge Discovery in Database,KDD)是从数据库中的大量数据中发现不明显、之前未知、可能有用的知识。 知识发掘流程(Knowledge Discovery Process)包括属性选择…...
Centos安装ffmpeg的方法
推荐第一个,不要自己编译安装,太难了,坑多。 在 CentOS 上安装 FFmpeg 有几种方法,以下是两种常见的方法: ### 方法一:使用 RPM Fusion 仓库安装 1. **启用 RPM Fusion 仓库**: RPM Fusion 是一个第三方仓库,提供了许多 CentOS 官方仓库中没有的软件包。 ```bash…...
理解SQL中通配符的使用
前言 SQL 是一种标准化的结构化查询语言,涉及结构化查询时,高效地检索数据至关重要。而通配符是SQL中模式匹配的有效的方法。使用通配符可以更轻松地检索到所需的确切数据。通配符允许我们定义多功能查询条件。本文将 介绍SQL通配符的基础知识及用法。 …...
SpringBoot篇(简化操作的原理)
目录 一、代码位置 二、统一版本管理(parent) 三、提供 starter简化 Maven 配置 四、自动配置 Spring(引导类) 五、嵌入式 servlet 容器 一、代码位置 二、统一版本管理(parent) SpringBoot项目都会继…...
Cesium的模型(ModelVS)顶点着色器浅析
来自glTF和3D Tiles的模型会走ModelVS.glsl。这个文件不单独是把模型顶点转换为屏幕坐标,还包含了丰富的处理过程。 Cesium是根据定义的Define判断某个行为是否需要被执行,比如#define HAS_SILHOUETTE,说明需要计算模型外轮廓线。 Cesium的…...
机器人领域中的scaling law:通过复现斯坦福机器人UMI——探讨数据规模化定律(含UMI的复现关键)
前言 在24年10.26/10.27两天,我司七月在线举办的七月大模型机器人线下营时,我们带着大家一步步复现UMI,比如把杯子摆到杯盘上(其中1-2位学员朋友还亲自自身成功做到该任务) 此外,我还特地邀请了针对UMI做了改进工作的fastumi作者…...
C++之多态的深度剖析
目录 前言 1.多态的概念 2.多态的定义及实现 2.1多态的构成条件 2.1.1重要条件 2.1.2 虚函数 2.1.3 虚函数的重写/覆盖 2.1.4 选择题 2.1.5 虚函数其他知识 协变(了解) 析构函数的重写 override 和 final关键字 3. 重载,重写&…...
Microsoft Office PowerPoint制作科研论文用图
Microsoft Office PowerPoint制作科研论文用图 1. 获取高清图片2. 导入PPT3. 另存为“增强型windows元文件”emf格式4. 画图剪裁 1. 获取高清图片 这里指通过绘图软件画分辨率高的图片,我一般使用python画dpi600的图片。 2. 导入PPT 新建一个PPT(注意&a…...
go语言进阶之并发基础
并发 什么是并发,也就是我们常说的多线程,多个程序同时执行。 并发的基础 线程和进程 进程 进程是操作系统中一个重要的概念,指的是一个正在运行的程序的实例。它包含程序代码、当前活动的状态、变量、程序计数器和内存等资源。进程是系…...
po、dto、vo的使用场景
现在项目中有两类模型类:DTO数据传输对象、PO持久化对象,DTO用于接口层向业务层之间传输数据,PO用于业务层与持久层之间传输数据,有些项目还会设置VO对象,VO对象用在前端与接口层之间传输数据,如下图&#…...
聊一聊Elasticsearch的一些基本信息
一、Elasticsearch是什么 Elasticsearch简称ES,是一款分布式搜索引擎。它是在Apache Lucene基础之上采用Java语言开发的。 Elasticsearch的官方网站对它的解释是:Elasticsearch是一个分布式、RESTful的搜索和数据分析引擎。 通过上边的官方解释&#…...
Unity 两篇文章熟悉所有编辑器拓展关键类 (上)
本专栏基础资源来自唐老狮和siki学院,仅作学习交流使用,不作任何商业用途,吃水不忘打井人,谨遵教诲 编辑器扩展内容实在是太多太多了(本篇就有五千字) 所以分为两个篇章而且只用一些常用api举例,…...
Spring SPI、Solon SPI 有点儿像(Maven 与 Gradle)
一、什么是 SPI SPI 全名 Service Provider interface,翻译过来就是“服务提供接口”。基本效果是,申明一个接口,然后通过配置获取它的实现,进而实现动态扩展。 Java SPI 是 JDK 内置的一种动态加载扩展点的实现。 一般的业务代…...
合并排序算法(C语言版)
#include <stdio.h> void Copy(int *a, int *b, int left, int right) { int i; for(i0;i<right-left1;i) { a[ileft] b[i]; } } // 将 a[left,middle] 和 a[middle1,right]合并到 b[left, right]中 void Merge(int *a, int left, int midd…...
C++——输入一行文字,找出其中的大写字母、小写字母、空格数字以及其他字符各有多少。用指针或引用方法处理。
没注释的源代码 #include <iostream> using namespace std; int main() { char c; int ul0,ll0,sp0,di0,other0; cout<<"please input script c:"; while(cin.get(c)) { if(c\n) break; else if(c>A&&…...
【skywalking】maximum query complexity exceeded 3336 > 3000
问题 skywalking相关版本信息 jdk:17skywalking:10.1.0apache-skywalking-java-agent:9.3.0ElasticSearch : 8.8.2 问题描述 maximum query complexity exceeded 3336 > 3000 最大查询复杂度超过3336>3000 可能原因 查询条件过于复…...
开源一个开发的聊天应用与AI开发框架,集成 ChatGPT,支持私有部署的源码
大家好,我是一颗甜苞谷,今天分享一个开发的聊天应用与AI开发框架,集成 ChatGPT,支持私有部署的源码。 介绍 当前系统集成了ChatGPT的聊天应用,不仅提供了基本的即时通讯功能,还引入了先进的AI技术&#x…...
开发了一个成人学位英语助考微信小程序
微信小程序名称:石榴英语 全称:石榴英语真题助手 功能定位 北京成人学士学位英语辅助学习工具,包含记高频单词,高频词组,专项练习,模拟考试等功能。 开发背景 个人工作需要提高学习英文水平ÿ…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
