你还不知道的大文件上传
点击关注公众号,“技术干货” 及时达!说起文件上传,在我们的开发中是绕不过去的话题。但要是碰到几十 MB 甚至 GB 级别的大文件,传统上传方式就有点 “拉胯” 了。今天就给大伙唠唠前端大文件上传,讲讲它的原理、能解决啥问题、有哪些关键功能,再给大家推荐一个超实用的大文件上传库,让大文件上传不再是难题。
一、大文件上传是啥?(一)大文件上传的定义简单来说,大文件上传就是把个头很大的文件,从咱们的客户端,像浏览器啥的,传到服务器上。这些大文件要是用传统上传方法,很容易出现上传失败、网络超时,还可能把服务器资源占得死死的。所以,大文件上传一般得靠分片上传、断点续传、并行上传这些技术,才能顺顺利利完成。
(二)大文件上传解决的问题「网络不稳定」:网络这东西,时好时坏。大文件上传的时候,网络要是波动或者断了,很容易就上传失败。大文件上传技术就能应对这种情况,保证上传能完成。「服务器资源限制」:大文件上传要是处理不好,服务器的内存和 CPU 就遭老罪了,搞不好服务器都得崩溃。用对技术,就能减少对服务器资源的占用,让服务器稳稳地运行。「用户体验」:有了上传进度展示、断点续传这些功能,用户能随时知道上传到啥程度了,就算上传中断,也不用从头再来,体验感直接拉满。「文件完整性验证」:文件传输的时候,数据有可能损坏或者丢失。大文件上传技术有文件完整性验证,能保证上传的文件和原来的一模一样。(三)大文件上传对比普通文件上传的优势「能处理大文件」:普通文件上传碰到 GB 级别的大文件,基本就歇菜了。大文件上传用分片技术,再大的文件都能搞定。「断点续传」:普通文件上传中断了就得重新开始,大文件上传能接着没传完的地方继续,省时间又省带宽。「并行上传」:大文件上传可以把文件分成好多片同时上传,速度 “蹭蹭” 往上涨。「优化资源利用」:分片上传后,服务器每次处理的文件片段小,内存占用就少,服务器性能也能提升。「精准进度监控」:大文件上传能更准确地显示上传进度,用户心里有数。二、前端实现大文件上传的关键要点从前端角度看,要实现大文件上传,得搞定下面这些关键功能:
(一)文件分片把大文件按照固定大小,比如 1MB 或者 5MB,切成一个个小文件,也就是分片。这可以用 File API 里的 File.slice () 方法来实现。
(二)分片上传用 XMLHttpRequest 或者 Fetch API,把每个分片一个一个传到服务器。为了更快,还能同时上传好几个分片,也就是并行上传。
(三)断点续传上传要是中断了,前端得记住哪些分片已经传了。下次接着传没传完的。这得前端和服务器一起配合,服务器一般会记录已经上传的分片信息,像文件哈希值或者分片索引。
(四)上传进度监控通过 XMLHttpRequest 的 progress 事件,或者 Fetch API 的 ReadableStream,实时监控上传进度。然后算出已经上传的百分比,展示给用户。
(五)文件完整性校验文件上传完了,前端算一下文件的哈希值,比如 MD5 或者 SHA-256,再和服务器那边校验一下,保证文件没出问题。这得用 FileReader 读取文件内容来生成哈希值。
(六)错误处理与重试机制上传的时候难免会遇到网络错误、服务器错误啥的。前端得有一套完善的错误处理机制,每个分片还要有重试机制,保证上传失败的分片能重新传。
(七)并发控制要控制好同时上传的分片数量,不然太多请求会占满带宽,服务器也扛不住。可以用队列或者 Promise 池来管理并发上传任务。
(八)用户体验优化得给用户一个清晰直观的界面反馈,像进度条、上传速度、剩余时间这些都得展示出来。另外,还要支持拖拽上传、文件选择、批量上传这些实用功能。
(九)安全性考虑为了防止有人上传恶意文件,得校验文件类型,限制文件大小。要是有敏感数据,就用 HTTPS 这些加密方式传输。
三、宝藏大文件上传库 ——enlarge - file - upload自己从头写一个大文件上传库,难度可不小,要实现这么多功能,短时间内根本搞不定。给大家推荐一个超好用的库,叫 enlarge - file - upload。上面说的这些功能,它都实现了,不管你用 vue2、vue3、react,还是 jquery 原生 js 开发项目,都能直接用,真正做到开箱即用。
下面讲讲它在不同环境里咋用:
(一)安装npm install enlarge-file-upload
安装很简单,就按常规的 npm 安装方式就行。在项目目录下打开命令行,输入npm install enlarge-file-upload,等安装完,就能在项目里用这个库了。具体安装命令也可以去 npm 官网瞅瞅:https://www.npmjs.com/package/enlarge-file-upload
(二)参数介绍这个库的参数挺多,能满足各种项目需求。详细的参数说明可以看官方文档。这里给大家讲讲常用的几个参数:
file:这个是要上传的文件对象,必须得有。你选好要上传的文件,获取到的文件对象就填这儿。serverUrl:这是服务器接收文件上传的接口地址,得填对,不然文件传不到服务器上。onProgress:这是个回调函数,用来监听上传进度。上传的时候,它会实时被触发,通过它能拿到当前的上传进度百分比,方便展示给用户看。onSuccess:文件上传成功了,就会执行这个回调函数。你可以在里面给用户提示上传成功,或者更新一下页面状态啥的。onError:要是上传出错了,这个回调函数就会被调用,它会带着错误信息,方便你排查问题。(三)使用案例「jquery 原生 js 中使用示例」!DOCTYPEhtml
htmllang="en"
head
metacharset="UTF-8"/
metaname="viewport"content="width=device-width, initial-scale=1.0"/
title文件上传/title
/head
scriptsrc="https://cdn.jsdelivr.net/npm/enlarge-file-upload/dist/upload.min.js"/script
!-- 引入 Axios 库,用于发送 HTTP 请求 --
scriptsrc="https://cdn.jsdelivr.net/npm/axios"/script
body
inputtype="file"id="fileInput"/
buttonid="pauseButton"暂停上传/button
buttonid="resumeButton"继续上传/button
divid="progress"上传进度:0%/div
divid="speed"上传速度:0 MB/s/div
script
// 定义上传函数
asyncfunctionuploadFunction({ chunk, index, hash, cancelToken }){
constformData =newFormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("index", index);
awaitaxios.post("http://localhost:3000/api/users", formData, {
cancelToken,
}
// 使用示例
constconfig = {
chunkSize:5*1024*1024,// 5MB
concurrency:5,
maxRetries:3,
// startOffset: 6, // 从索引为10的切片位置开始传
// includeChunks:[1,6], // 只上传索引为1和6的切片,只有startOffset为0或空时才生效
uploadFunction,
onProgress:(progress) ={
document.getElementById(
"progress"
).innerText =`上传进度:${state.progress.toFixed(2)}%`;
},
onSuccess:()={
console.log("上传完毕");
},
onSpeed:(speed) ={
document.getElementById("speed").innerText =`上传速度:${speed}`;
},
const{ upload, pause, resume, state } = createUploader(config);
constfileInput =document.getElementById("fileInput");
fileInput.addEventListener("change", () = {
constfile = fileInput.files[0];
upload(file);
// 暂停上传
document.getElementById("pauseButton").addEventListener("click", () = {
pause();
// 继续上传
document.getElementById("resumeButton").addEventListener("click", () = {
resume();
/script
/body
/html
/html
在这个例子里,先引入了jquery库和enlarge-file-upload库。页面加载完后,给上传按钮绑定了点击事件。点按钮的时候,就去获取文件输入框里选的文件。要是有文件,就创建EnlargeFileUpload实例,把文件对象、服务器上传接口地址,还有各种回调函数传进去,最后调用start方法开始上传文件。
「在 vue3 中使用」template
div
inputtype="file"@change="handleFileChange"
button@click="uploadFile"上传文件/button
/div
/template
scriptsetup
importEnlargeFileUploadfrom'enlarge-file-upload';
constfileRef = ref(null);
consthandleFileChange =(e) ={
fileRef.value = e.target.files[0];
};
constuploadFile =()={
if(fileRef.value) {
constuploader =newEnlargeFileUpload({
file: fileRef.value,
serverUrl:'http://your-server-url.com/upload',
onProgress:(progress) ={
console.log(`上传进度:${progress}%`);
},
onSuccess:()={
console.log('文件上传成功');
},
onError:(error) ={
console.error('文件上传失败', error);
}
uploader.start();
}
};
/script
在 vue3 项目里,用ref定义了一个fileRef来存选的文件。文件选择框内容变了,handleFileChange函数就把选的文件赋值给fileRef。点上传按钮的时候,看看fileRef有没有值,有值就创建EnlargeFileUpload实例开始上传文件,还设置了上传进度、成功和失败的回调函数。
「在 vue2 中使用」template
div
inputtype="file"@change="handleFileChange"/
button@click="handlePause"暂停上传/button
button@click="handleResume"继续上传/button
div上传进度:{{ progress.toFixed(2) }}%/div
div上传速度:{{ speed }}/div
/div
/template
script
importVuefrom"vue";
importcreateUploaderfrom"enlarge-file-upload";
importaxiosfrom"axios";
exportdefaultVue.extend({
data() {
return{
progress:0,// 进度
speed:"0 MB/s",// 速度
uploader:null,// 上传器实例
},
methods: {
// 定义上传函数
asyncuploadFunction({ chunk, index, hash, cancelToken }) {
constformData =newFormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("index", index.toString());
awaitaxios.post("http://localhost:3000/api/users", formData, {
cancelToken,
},
// 文件选择处理
handleFileChange(event) {
constfile = event.target.files?.[0];
if(file this.uploader) {
this.uploader.upload(file);
}
},
// 暂停上传
handlePause() {
if(this.uploader) {
this.uploader.pause();
}
},
// 继续上传
handleResume() {
if(this.uploader) {
this.uploader.resume();
}
},
},
created() {
constuploaderConfig = {
chunkSize:5*1024*1024,// 5MB
concurrency:5,
maxRetries:3,
uploadFunction:this.uploadFunction,
onProgress:(progressValue) ={
this.progress = progressValue;
},
onSuccess:()={
console.log("上传完毕");
},
onSpeed:(speedValue) ={
this.speed = speedValue;
},
// 创建上传器实例
this.uploader = createUploader(uploaderConfig);
},
});
/script
stylescoped/style
vue2 项目里,在data里定义了file变量存文件。handleFileChange方法获取选的文件,点上传按钮的时候,要是file有值,就创建EnlargeFileUpload实例上传文件,设置好回调函数处理上传过程里的各种情况。
「React 中使用示例」importReact, { useRef, useMemo }from'react';
importEnlargeFileUploadfrom'enlarge-file-upload';
constFileUploadComponent =()={
constfileInputRef = useRef(null);
constuploadFile =()={
constfile = fileInputRef.current.files[0];
if(file) {
constuploader = useMemo(()=newEnlargeFileUpload({
file: file,
serverUrl:'http://your-server-url.com/upload',
onProgress:(progress) ={
console.log(`上传进度:${progress}%`);
},
onSuccess:()={
console.log('文件上传成功');
},
onError:(error) ={
console.error('文件上传失败', error);
}
}), [file]);
uploader.start();
}
return(
div
inputtype="file"ref={fileInputRef}/
buttononClick={uploadFile}上传文件/button
/div
};
exportdefaultFileUploadComponent;
React 项目里,用useRef创建了fileInputRef来引用文件输入框。点上传按钮的时候,从fileInputRef里拿选的文件。为了防止组件重新渲染的时候重复创建EnlargeFileUpload实例,用useMemo来创建上传实例,设置好回调函数。最后在页面里渲染文件输入框和上传按钮。
「封装为 react Hooks」importReact, { useState, useMemo }from"react";
importcreateUploaderfrom"enlarge-file-upload";
importtype { Config, UploadOptions }from"enlarge-file-upload";
importaxiosfrom"axios";
constFileUpload =()={
const[progress, setProgress] = useState(0);
const[speed, setSpeed] = useState("0 MB/s");
// 定义上传函数
asyncfunctionuploadFunction({
chunk,
index,
hash,
cancelToken,
}: UploadOptions){
constformData =newFormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("index", index);
awaitaxios.post("http://localhost:3000/api/users", formData, {
cancelToken,
}
constuploaderConfig: Config = useMemo(
()=({
chunkSize:5*1024*1024,// 5MB
concurrency:5,
maxRetries:3,
// startOffset: 6, // 从索引为10的切片位置开始传
// includeChunks:[1,6], // 只上传索引为1和6的切片,只有startOffset为0或空时才生效
uploadFunction,
onProgress:(progress) ={
setProgress(progress);
},
onSuccess:()={
console.log("上传完毕");
},
onSpeed:(speed) ={
setSpeed(speed);
},
}),
[]
constuploader = useMemo(
()=createUploader(uploaderConfig),
[uploaderConfig]
consthandleFileChange =(event) ={
constfile = event.target.files[0];
uploader?.upload(file);
consthandlePause =()={
uploader?.pause();
consthandleResume =()={
uploader?.resume();
return(
div
inputtype="file"onChange={handleFileChange}/
buttononClick={handlePause}暂停上传/button
buttononClick={handleResume}继续上传/button
div上传进度:{progress.toFixed(2)}%/div
div上传速度:{speed}/div
/div
};
exportdefaultFileUpload;
这段代码把文件上传功能封装成了 React Hook。Hook 里用useRef创建文件输入框的引用,uploadFile函数处理文件上传逻辑,最后返回fileInputRef和uploadFile,方便在其他组件里复用文件上传功能。
「使用上面封装好的 Hooks 示例」import{ useState, useMemo, useCallback }from"react";
importcreateUploaderfrom"enlarge-file-upload";
importtype { Config, UploadOptions }from"enlarge-file-upload";
importaxiosfrom"axios";
constuseFileUploader =()={
const[progress, setProgress] = useState(0);
const[speed, setSpeed] = useState("0 MB/s");
constuploadFunction = useCallback(
async({ chunk, index, hash, cancelToken }: UploadOptions) = {
constformData =newFormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("index", index);
awaitaxios.post("http://localhost:3000/api/users", formData, {
cancelToken,
},
[]
constuploaderConfig: Config = useMemo(
()=({
chunkSize:5*1024*1024,// 5MB
concurrency:5,
maxRetries:3,
// startOffset: 6, // 从索引为10的切片位置开始传
// includeChunks:[1,6], // 只上传索引为1和6的切片,只有startOffset为0或空时才生效
uploadFunction,
onProgress:(progress) ={
setProgress(progress);
},
onSuccess:()={
console.log("Upload complete");
},
onSpeed:(speed) ={
setSpeed(speed);
},
}),
[uploadFunction]
constuploader = useMemo(
()=createUploader(uploaderConfig),
[uploaderConfig]
constuploadFile = useCallback(
(file) ={
uploader?.upload(file);
},
[uploader]
constpauseUpload = useCallback(()={
uploader?.pause();
}, [uploader]);
constresumeUpload = useCallback(()={
uploader?.resume();
}, [uploader]);
return{
progress,
speed,
uploadFile,
pauseUpload,
resumeUpload,
};
exportdefaultuseFileUploader;
这个示例里,引入封装好的useFileUploadHook,通过解构拿到fileInputRef和uploadFile。在组件里用fileInputRef关联文件输入框,点按钮就调用uploadFile函数上传文件,在 React 项目里用起来就更方便了。
大文件上传在前端开发里很重要,搞懂原理和实现方法,再用好工具,就能给用户更好的上传体验。希望这篇文章能帮大家深入了解前端大文件上传,在项目开发里用得得心应手。
关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding
点击"阅读原文"了解详情~
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线