使用 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…...

开发了一个成人学位英语助考微信小程序
微信小程序名称:石榴英语 全称:石榴英语真题助手 功能定位 北京成人学士学位英语辅助学习工具,包含记高频单词,高频词组,专项练习,模拟考试等功能。 开发背景 个人工作需要提高学习英文水平ÿ…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...