前端上传大文件怎么处理 - 掘金

背景

当我们在做文件的导入功能的时候,如果导入的文件过大,可能会导所需要的时间够长,且失败后需要重新上传,我们需要前后端结合的方式解决这个问题

思路

我们需要做几件事情如下:

  • 对文件做切片,即将一个请求拆分成多个请求,每个请求的时间就会缩短,且如果某个请求失败,只需要重新发送这一次请求即可,无需从头开始
  • 通知服务器合并切片,在上传完切片后,前端通知服务器做合并切片操作
  • 控制多个请求的并发量,防止多个请求同时发送,造成浏览器内存溢出,导致页面卡死
  • 做断点续传,当多个请求中有请求发送失败,例如出现网络故障、页面关闭等,我们得对失败的请求做处理,让它们重复发送

实现

前端

示例代码仓库

仓库地址

步骤1-切片,合并切片

JavaScript中,文件FIle对象是Blob对象的子类,Blob对象包含一个重要的方法slice通过这个方法,我们就可以对二进制文件进行拆分,具体代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=s, initial-scale=1.0"><title>Document</title><script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
<body><input type="file" id="fileInput"><button id="uploadBtn">上传</button>
</body>
<script>
// 请求基准地址
axios.defaults.baseURL = 'http://localhost:3000'
// 选中的文件
var file = null
// 选择文件
document.getElementById('fileInput').onchange = function({target: {files}}){file = files[0]
}
// 开始上传
document.getElementById('uploadBtn').onclick = async function(){if (!file) return// 创建切片   // let size = 1024 * 1024 * 10 //10MB 切片大小let size = 1024 * 50  //50KB 切片大小let fileChunks = []let index = 0 //切片序号for(let cur = 0; cur < file.size; cur += size){fileChunks.push({hash: index++,chunk: file.slice(cur, cur + size)})}// 上传切片const uploadList = fileChunks.map((item, index) => {let formData = new FormData()formData.append('filename', file.name)formData.append('hash', item.hash)formData.append('chunk', item.chunk)return axios({method: 'post',url: '/upload',data: formData})})await Promise.all(uploadList)// 合并切片await axios({method: 'get',url: '/merge',params: {filename: file.name}});console.log('上传完成')
}
</script>
</html>
复制代码

步骤2-并发控制

结合Promise.race异步函数实现,多个请求同时并发的数量,防止浏览器内存溢出,具体代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=s, initial-scale=1.0"><title>Document</title><script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
<body><input type="file" id="fileInput"><button id="uploadBtn">上传</button>
</body>
<script>
// 请求基准地址
axios.defaults.baseURL = 'http://localhost:3000'
// 选中的文件
var file = null
// 选择文件
document.getElementById('fileInput').onchange = function({target: {files}}){file = files[0]
}
// 开始上传
document.getElementById('uploadBtn').onclick = async function(){if (!file) return// 创建切片   // let size = 1024 * 1024 * 10; //10MB 切片大小let size = 1024 * 50 //50KB 切片大小let fileChunks = []let index = 0 //切片序号for(let cur = 0; cur < file.size; cur += size){fileChunks.push({hash: index++,chunk: file.slice(cur, cur + size)});}// 控制并发let pool = []//并发池let max = 3 //最大并发量for(let i=0;i<fileChunks.length;i++){let item = fileChunks[i]let formData = new FormData()formData.append('filename', file.name)formData.append('hash', item.hash)formData.append('chunk', item.chunk)// 上传切片let task = axios({method: 'post',url: '/upload',data: formData})task.then((data)=>{//请求结束后将该Promise任务从并发池中移除let index = pool.findIndex(t=> t===task)pool.splice(index)})pool.push(task)if(pool.length === max){//每当并发池跑完一个任务,就再塞入一个任务await Promise.race(pool)}}//所有任务完成,合并切片await axios({method: 'get',url: '/merge',params: {filename: file.name}});console.log('上传完成')
}
</script>
</html>
复制代码

步骤3-断点续传

在单个请求失败后,触发catch的方法的时候,讲当前请求放到失败列表中,在本轮请求完成后,重复对失败请求做处理,具体代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=s, initial-scale=1.0"><title>Document</title><script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
<body><input type="file" id="fileInput"><button id="uploadBtn">上传</button>
</body>
<script>
// 请求基准地址
axios.defaults.baseURL = 'http://localhost:3000'
// 选中的文件
var file = null
// 选择文件
document.getElementById('fileInput').onchange = function({target: {files}}){file = files[0]
}
// 开始上传
document.getElementById('uploadBtn').onclick = function(){if (!file) return;// 创建切片   // let size = 1024 * 1024 * 10; //10MB 切片大小let size = 1024 * 50; //50KB 切片大小let fileChunks = [];let index = 0 //切片序号for(let cur = 0; cur < file.size; cur += size){fileChunks.push({hash: index++,chunk: file.slice(cur, cur + size)})}// 控制并发和断点续传const uploadFileChunks = async function(list){if(list.length === 0){//所有任务完成,合并切片await axios({method: 'get',url: '/merge',params: {filename: file.name}});console.log('上传完成')return}let pool = []//并发池let max = 3 //最大并发量let finish = 0//完成的数量let failList = []//失败的列表for(let i=0;i<list.length;i++){let item = list[i]let formData = new FormData()formData.append('filename', file.name)formData.append('hash', item.hash)formData.append('chunk', item.chunk)// 上传切片let task = axios({method: 'post',url: '/upload',data: formData})task.then((data)=>{//请求结束后将该Promise任务从并发池中移除let index = pool.findIndex(t=> t===task)pool.splice(index)}).catch(()=>{failList.push(item)}).finally(()=>{finish++//所有请求都请求完成if(finish===list.length){uploadFileChunks(failList)}})pool.push(task)if(pool.length === max){//每当并发池跑完一个任务,就再塞入一个任务await Promise.race(pool)}}}uploadFileChunks(fileChunks)}
</script>
</html>
复制代码

后端

步骤1.安装依赖

npm i express@4.17.2
npm i multiparty@4.2.2
复制代码

步骤2.接口实现

const express = require('express')
const multiparty = require('multiparty')
const fs = require('fs')
const path = require('path')
const { Buffer } = require('buffer')
// 上传文件最终路径
const STATIC_FILES = path.join(__dirname, './static/files')
// 上传文件临时路径
const STATIC_TEMPORARY = path.join(__dirname, './static/temporary')
const server = express()
// 静态文件托管
server.use(express.static(path.join(__dirname, './dist')))
// 切片上传的接口
server.post('/upload', (req, res) => {const form = new multiparty.Form();form.parse(req, function(err, fields, files) {let filename = fields.filename[0]let hash = fields.hash[0]let chunk = files.chunk[0]let dir = `${STATIC_TEMPORARY}/${filename}`// console.log(filename, hash, chunk)try {if (!fs.existsSync(dir)) fs.mkdirSync(dir)const buffer = fs.readFileSync(chunk.path)const ws = fs.createWriteStream(`${dir}/${hash}`)ws.write(buffer)ws.close()res.send(`${filename}-${hash} 切片上传成功`)} catch (error) {console.error(error)res.status(500).send(`${filename}-${hash} 切片上传失败`)}})
})
//合并切片接口
server.get('/merge', async (req, res) => {const { filename } = req.querytry {let len = 0const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`).map((hash,index) => {const buffer = fs.readFileSync(`${STATIC_TEMPORARY}/${filename}/${index}`)len += buffer.lengthreturn buffer});//合并文件const buffer = Buffer.concat(bufferList, len);const ws = fs.createWriteStream(`${STATIC_FILES}/${filename}`)ws.write(buffer);ws.close();res.send(`切片合并完成`);} catch (error) {console.error(error);}
})server.listen(3000, _ => {console.log('http://localhost:3000/')
})
复制代码

其他实现

如果使用腾讯云阿里云文件上传的服务,它们提供了npm库,例如腾讯云的cos-js-sdk-v5,它自身提供的切片相关的配置

作者:黄金林
链接:https://juejin.cn/post/7053658552472174605
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

前端上传大文件怎么处理相关推荐

  1. html5 上传超大文件,HTML5教程 如何拖拽上传大文件

    本篇教程探讨了HTML5教程 如何拖拽上传大文件,希望阅读本篇文章以后大家有所收获,帮助大家HTML5+CSS3从入门到精通 . < 前言: 大文件传输一直是技术上的一大难点.文件过大时,一些性 ...

  2. antd upload手动上传_SpringBoot 如何上传大文件?

    最近遇见一个需要上传超大大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表 ...

  3. SpringBoot如何上传大文件

    最近遇见一个需要上传超大大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表 ...

  4. ssm上传文件进度条_SSM框架+Plupload实现分块上传大文件示例

    关于Plupload的介绍,相信它的官网http://www.plupload.com/已经给得很详细了.Plupload的上传原理简单点说,就是将用户选中的文件(可多个)分隔成一个个小块,依次向服务 ...

  5. JavaScript上传大文件并支持中途取消上传

    最近遇见一个需要上传超大大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表 ...

  6. 使用webuploader上传大文件

    遇到一个需求,用户想上传超过1.2G的视频文件. 根据这个需求做了一些上传速度对比,ftp上传1G多点的文件用时三分钟左右 1.使用ftp上传 (1) 创建ftp服务站点 在服务器上依次选择控制面板- ...

  7. 利用PLUPLOAD上传大文件

    利用PLUPLOAD上传大文件 大容量文件上传早已不是什么新鲜问题,在.net 2.0时代,HTML5也还没有问世,要实现这样的功能,要么是改web.config,要么是用flash,要么是用一些第三 ...

  8. 用ASP.NET上传大文件

    作者:思归     微软MVP   http://blog.joycode.com/saucer/ 我们在上传大文件时都遇到过这样或那样的问题.设置很大的maxRequestLength值并不能完全解 ...

  9. php webuploader大文件,web uploader 上传大文件总结

    由于业务需要,需要上传大文件,已有的版本无法处理IE版本,经过调研,百度的 webuploader 支持 IE 浏览器,而且支持计算MD5值,进而可以实现秒传的功能. 大文件上传主要分为三部分,预上传 ...

最新文章

  1. 【深度学习】高效读取数据的方法(TFRecord)
  2. 波卡链Substrate (6)SubstrateUI界面
  3. P7137-[THUPC2021 初赛]切切糕【dp】
  4. toj 4597 字符识别?
  5. Postgres XL 集群中各节点的角色和作用
  6. VC++6.0 按F1无法打开 MSDN 的解决办法
  7. 如何清洗 Git Repo 代码仓库
  8. Nginx PHP Apache 隐藏版本号/禁止显示版本号
  9. C语言 二进制文件读写实例讲解
  10. 参考文献空格怎么空_参考文献中的标点符号后要不要加空格
  11. 安装esxi时候的No Network Adapters报错 解决办法
  12. iphone拍照标注转发微博应用--Gurgle 发布
  13. antv图例出现分页_自定义图例组件
  14. 10台世界上最快的超级计算机
  15. NameError: name ‘XXX‘ is not defined
  16. java 抽象方法 大括号_为什么Java抽象类的方法必须加大括号?我写错了吗?
  17. 用html和css设计QQ注册页面,html和css制作QQ企鹅教程
  18. python串口编程整理(更新完)
  19. 矩阵实验:图形图像处理
  20. python大鱼吃小鱼_写简单游戏,学编程语言-python篇:大鱼吃小鱼

热门文章

  1. CodeForces - 89A - Robbery
  2. 腾达ac1200远端服务器无响应,连3个磊科MG1200ac必死机
  3. java上看小说软件_i悦读小说阅读软件 For java
  4. 常见HTTP请求错误
  5. java.lang.IllegalArgumentException: View=DecorView not attached to window manager(Android Dialog崩溃)
  6. CentOS 安装指南
  7. mysql reorg 命令_DB2 runstats、reorgchk、reorg 命令
  8. 贵州杰赛s65机顶盒子CPU S905M-B 刷机教程及纯尽版固件
  9. Git安装【Windows环境安装配置】详细教程
  10. 说说org.json.JSONObject功能和源码(二)