Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >居于H5的多文件、大文件、多线程上传解决方案

居于H5的多文件、大文件、多线程上传解决方案

作者头像
李海彬
发布于 2018-03-27 08:27:31
发布于 2018-03-27 08:27:31
3.3K00
代码可运行
举报
文章被收录于专栏:Golang语言社区Golang语言社区
运行总次数:0
代码可运行
文件上传在web应用中是比较常见的功能,前段时间做了一个多文件、大文件、多线程文件上传的功能,使用效果还不错,总结分享下。

一、 功能性需求与非功能性需求

  • 要求操作便利,一次选择多个文件进行上传;
  • 支持大文件上传(1G),同时需要保证上传期间用户电脑不出现卡死等体验;
  • 交互友好,能够及时反馈上传的进度;
  • 服务端的安全性,不因上传文件功能导致JVM内存溢出影响其他功能使用;
  • 最大限度利用网络上行带宽,提高上传速度;

二、 设计分析

  • 对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传
  • 从上传的效率来看,利用多线程并发上传能够达到最大效率。
  • 对于大文件切块、多线程上传,需要考虑服务端合并文件的时间点;

三、解决方案:

HTML5之前的标准是无法支持上面的功能,因此我们需要把功能实现居于H5提供的新特性上面: 1. H5新标准对file标签进行了增强,支持同时选择多个文件

  1. <input type="file" multiple=true onchange="doSomething(this.files)"/>
  2. 1

复制代码

注意multiple属性,设置为true; onchange:一般是选择文件确定后的响应事件 this.files:文件对象集合 2. File对象 H5提供的类似java的RandomAccessFile的文件操作对象,其中silce方法允许程序指定文件的起止字节进行读取。利用这个对象,实现对大文件的切分; 3. XMLHttpRequest 这个对象大家应该很熟悉了,属于web2.0的标准,我们最常用的ajax请求底层就是居于此对象。本质上XMLHttpRequest是一个线程对象,因此我们通过创建一定数量的XMLHttpRequest对象,实现多线程并行操作; 4. FormData对象 H5新增对象,可以理解为一个key-value的map,通过把文件的二进制流和业务参数封装到此对象,再交由XMLHttpRequest对象发送到服务端,服务端可以通过普通的request.getParamter方法获取这些参数; 5. progress标签 H5新增的标签,在页面显示一个进度条: value:当前进度条的值 max:最大值 利用这个标签,结合XMLHttpRequest的回调来反馈目前上传的进度

四、客户端代码示例

  • HTML代码:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file" multiple=true onchange="showFileList(this.files)"/> 

<input id="uploadBtn" type="button"  value="上传"  onclick="doUpload()"/> 

1

2



javascript脚本:
复制代码


var quence = new Array();//待上传的文件队列,包含切块的文件

/**

* 用户选择文件之后的响应函数,将文件信息展示在页面,同时对大文件的切块大小、块的起止进行计算、入列等

*/

function showFileList(files) {

  if(!files) {

   return;

  }



  var chunkSize = 5 * 1024 * 1024;  //切块的阀值:5M

  $(files).each(function(idx,e){

        //展示文件列表,略......



   if(e.size > chunkSize) {//文件大于阀值,进行切块

    //切块发送

    var chunks = Math.max(Math.floor(fileSize / chunkSize), 1)+1;//分割块数

    for(var i=0 ; i<chunks; i++) {

     var startIdx = i*chunkSize;//块的起始位置

     var endIdx = startIdx+chunkSize;//块的结束位置

     if(endIdx > fileSize) {

      endIdx = fileSize;

     }

     var lastChunk = false;

     if(i == (chunks-1)) {

      lastChunk = true;

     }

    //封装成一个task,入列

     var task = {

       file:e,

       uuid:uuid,//避免文件的重名导致服务端无法定位文件,需要给每个文件生产一个UUID

       chunked:true,

       startIdx:startIdx,

       endIdx:endIdx,

       currChunk:i,

       totalChunk:chunks

     }

     quence.push(task);



    }

   } else {//文件小于阀值



    var task = {

      file:e,

      uuid:uuid,

      chunked:false

    }

    quence.push(task);



   }

  });

}



/**

*  上传器,绑定一个XMLHttpRequest对象,处理分配给其的上传任务

**/

function Uploader(name) {

  this.url="";    //服务端处理url

  this.req = new XMLHttpRequest();

  this.tasks; //任务队列

  this.taskIdx = 0; //当前处理的tasks的下标

  this.name=name;

  this.status=0;  //状态,0:初始;1:所有任务成功;2:异常



  //上传 动作

  this.upload = function(uploader) {

   this.req.responseType = "json";



   //注册load事件(即一次异步请求收到服务端的响应)

   this.req.addEventListener("load", function(){

    //更新对应的进度条

     progressUpdate(this.response.uuid, this.response.fileSize);

     //从任务队列中取一个再次发送

     var task = uploader.tasks[uploader.taskIdx];

     if(task) {

      console.log(uploader.name + ":当前执行的任务编号:" +uploader.taskIdx);

      this.open("POST", uploader.url);

      this.send(uploader.buildFormData(task));

      uploader.taskIdx++;

     } else {

      console.log("处理完毕");

      uploader.status=1;

     }

   });



   //处理第一个

   var task = this.tasks[this.taskIdx];

   if(task) {

    console.log(uploader.name + ":当前执行的任务编号:" +this.taskIdx);

    this.req.open("POST", this.url);

    this.req.send(this.buildFormData(task));

    this.taskIdx++;

   } else {

    uploader.status=1;

   }

  }

  //提交任务

  this.submit = function(tasks) {

   this.tasks = tasks;

  }


  //构造表单数据

  this.buildFormData = function(task) {

   var file = task.file;

   var formData = new FormData();

   formData.append("fileName", file.name);

   formData.append("fileSize", file.size);

   formData.append("uuid", task.uuid);   

   var chunked = task.chunked;

   if(chunked) {//分块

    formData.append("chunked",  task.chunked);

    formData.append("data", file.slice(task.startIdx, task.endIdx));//截取文件块

    formData.append("currChunk", task.currChunk);

    formData.append("totalChunk", task.totalChunk);

   } else {

    formData.append("data", file);

   }

   return formData;

  }



}



/**

*用户点击“上传”按钮

*/

function doUpload() {



  //创建4个Uploader上传器(4条线程)

  var uploader0 = new Uploader("uploader0");

  var task0 = new Array();



  var uploader1 = new Uploader("uploader1");

  var task1 = new Array();



  var uploader2 = new Uploader("uploader2");

  var task2 = new Array();



  var uploader3 = new Uploader("uploader3");

  var task3 = new Array();



  //将文件列表取模hash,分配给4个上传器

  for(var i=0 ; i<quence.length; i++) {

   if(i%4==0) {

    task0.push(quence[i]);

   } else if(i%4==1) {

    task1.push(quence[i]);

   } else if(i%4==2) {

    task2.push(quence[i]);

   } else if(i%4==3) {

    task3.push(quence[i]);

   }

  }

  /提交任务,启动线程上传

  uploader0.submit(task0);

  uploader0.upload(uploader0);    

  uploader1.submit(task1);

  uploader1.upload(uploader1);    

  uploader2.submit(task2);

  uploader2.upload(uploader2);    

  uploader3.submit(task3);

  uploader3.upload(uploader3);    





  //注册一个定时任务,每2秒监控文件是否都上传完毕

  uploadCompleteMonitor = setInterval("uploadComplete()",2000);

}

五、服务端处理:

服务端处理逻辑相对比较传统,利用输入输出流、NIO等把文件写到磁盘即可。 这里需要特别考虑的是关于被切块文件的合并。前端在上传的时候,文件块是无序到达服务端,因此我们在每次接收到一个文件块的时候需要判断被切块的文件是否都传输完毕并进行合并,思路如下: 回到前端,我们在构造被切块的文件formData的数据结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
formData.append("fileName", file.name);

formData.append("fileSize", file.size);

formData.append("uuid", task.uuid);   

formData.append("chunked",  task.chunked);

formData.append("data", file.slice(task.startIdx, task.endIdx));//截取文件块

formData.append("currChunk", task.currChunk);

formData.append("totalChunk", task.totalChunk);

fileName:文件的原始名字 fileSize:文件的大小,KB uuid:文件的uuid chunked:true,标识是分段上传的文件块 data:文件二进制流 currChunk:当前上传的块编号 totalChunk:总块数

服务端以文件的UUID为key,维护一个chunk计数器,每接收到一块就找到对应的uuid执行计数器+1,同时考虑到并发情况,需采用同步关键字,避免出现逻辑错误。当计数器等于totalChunk的时候,进行文件合并

六、运行效果

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-09-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
大文件上传原理及实现方案
在网络应用中,大文件上传是一个技术挑战。本文详细解析了大文件上传的核心原理,并探讨了多种实现方案。从基本的文件分割、断点续传到复杂的并行上传,文章涵盖了一系列技术细节和最佳实践,包括如何处理网络波动、提高数据传输效率等关键问题。此外,还介绍了相关的前端和后端技术支持。无论是开发者还是架构师,这篇文章都将提供有力的技术指导和实战参考,帮助读者高效解决大文件上传问题。
京东技术
2024/02/27
2.7K0
大文件上传原理及实现方案
大文件上传代码片段记录
记录一下自己写的PHP大文件分段上传代码,方面以后要用的时候直接复制粘贴。使用了Layui、JQuery和ThinkPHP,还有一些优化空间,等下次用到的时候再完善~
jwj
2022/05/18
5900
django+python大文件上传
大文件上传服务 一、前端 [webuploader](http://fex.baidu.com/webuploader/ ''webuploader'') 二、后端 django 2.0.0 这里只贴出核心的代码: 前端的:
py3study
2020/01/07
2.9K0
前端:选取、预览、裁剪、上传、断点续传,关于图片上传那点事
进入 Html5 时代,在页面中开启上传功能,只需要一个 input 组件。type 代表上传,accept 代表接受的文件类型,capture 代表从摄像头拍照获取,capture="user" 代表默认打开前置摄像头,multiple 代表一次可上传多个文件。
LIYI
2019/11/19
1.6K0
前端:选取、预览、裁剪、上传、断点续传,关于图片上传那点事
大文件上传与流下载
在现代网站中,越来越多的个性化图片,视频,去展示,因此我们的网站一般都会支持文件上传。今天我们以大文件上传和下载为主题来分享总结一下.
用户10501441
2024/11/24
1220
大文件上传与流下载
用骚操作解决Spring Boot上传大文件的问题
最近遇见一个需要上传超大大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现。
二哥聊运营工具
2021/12/17
1.8K0
用骚操作解决Spring Boot上传大文件的问题
使用分块传输编码突破CDN限制上传大文件
内容分发网络(CDN)旨在将内容缓存到离终端用户更近的位置,以减少延迟并提升性能。然而,一些CDN服务可能对上传文件的大小有限制。这就需要一种策略来绕过这些限制,而分块传输编码(Transfer-Encoding: chunked)正是一种可以用于上传大文件的技术。
若海
2024/01/27
3180
字节面试官:请你实现一个大文件上传和断点续传
原 作 者:yeyan1996原文链接:https://url.cn/5h66afn
Nealyang
2020/02/19
3K0
面试官:大文件上传如何做断点续传?
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传
用户3806669
2021/04/15
8.3K0
面试官:大文件上传如何做断点续传?
《大胖 • 小课》- 说说大文件分片和断点续传
这是《大胖小课》栏目的专题一《说说文件上传那些事儿》的第6节-《大文件分片和断点续传》。
zz_jesse
2020/03/17
1.3K0
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传(Chunked Upload)是将大文件分成多个较小的部分(分片)来逐个上传到服务器。上传完成后,服务器将这些分片重新组装成原始文件。这个过程通常包括以下几个步骤:
watermelo37
2025/01/22
1490
分片上传技术全解析:原理、优势与应用(含简单实现源码)
PHP大文件分割上传 PHP分片上传
upload_max_filesize = 2M //PHP最大能接受的文件大小 post_max_size = 8M //PHP能收到的最大POST值' memory_limit = 128M //内存上限 max_execution_time = 30 //最大执行时间
用户2323866
2021/07/02
5.1K0
大文件上传切片上传 vue java
使用vue+elementui进行前端开发, 实现在dialog中 带进度条的上传大文件页面
solate
2020/06/18
6.7K1
文件切片上传如何防止切片丢失
上篇文章咱们介绍了大文件切片上传的原理,但是在传输过程中难免出现切片丢失的情况,传输过程中网速卡顿,服务器链接超时,等等都会造成切片信息的丢失,那如何避免文件切片信息丢失呢?
挥刀北上
2019/08/06
2.6K0
Nest 实现大文件分片上传
文件上传是常见需求,只要指定 content-type 为 multipart/form-data,内容就会以这种格式被传递到服务端:
神说要有光zxg
2024/01/02
4431
Nest 实现大文件分片上传
聚是一团火散作满天星,前端Vue.js+elementUI结合后端FastAPI实现大文件分片上传
    其实现在市面上有很多前端的三方库都集成了分片上传的功能,比如百度的WebUploader,遗憾的是它已经淡出历史舞台,无人维护了。现在比较推荐主流的库是vue-simple-uploader,不过饿了么公司开源的elementUI市场占有率还是非常高的,但其实大家所不知道的是,这个非常著名的前端UI库也已经许久没人维护了,Vue3.0版本出来这么久了,也没有做适配,由此可见大公司的开源产品还是需要给业务让步。本次我们利用elementUI的自定义上传结合后端的网红框架FastAPI来实现分片上传。
用户9127725
2022/08/08
1.7K1
聚是一团火散作满天星,前端Vue.js+elementUI结合后端FastAPI实现大文件分片上传
如何使用 Web Worker 处理大文件上传
大家好,我是猫头虎博主🐯。今天,我要带领大家探索一个非常有趣且实用的技术话题:如何使用 Web Worker 来提升大文件上传的速度。在前端开发中,大文件的上传可能会导致页面的响应变得缓慢,但幸运的是,我们有 Web Worker 这一利器可以解决这个问题。
猫头虎
2024/04/09
5010
如何使用 Web Worker 处理大文件上传
【总结】1941- 上传、下载终极解决方案:切片!!!
在前端开发中,文件流操作是指通过数据流的方式处理文件,对文件进行读取、写入和展示等操作。下面详细介绍了前端文件流操作的几个基本概念和技术。
pingan8787
2024/02/06
4080
【总结】1941- 上传、下载终极解决方案:切片!!!
Electron文件分片上传
获取文件分片 let stats = fs.statSync(filepath);//读取文件信息 let chunkSize = 3*1024*1024;//每片分块的大小3M let size = stats.size;//文件大小 let pieces = Math.ceil(size / chunkSize);//总共的分片数 function uploadPiece (i){ //计算每块的结束位置 let startdata = i * chunkSize; let
码客说
2021/01/29
1.5K0
实现大文件上传和断点续传实践经验总结
原文:https://juejin.cn/post/7118671489615790094
达达前端
2022/09/08
1.8K0
推荐阅读
相关推荐
大文件上传原理及实现方案
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文