博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
居于H5的多文件、大文件、多线程上传解决方案
阅读量:2811 次
发布时间:2019-05-13

本文共 4915 字,大约阅读时间需要 16 分钟。

http://blog.csdn.net/zengzm163/article/details/50539854

1479人阅读
(1)
分类:

目录

文件上传在web应用中是比较常见的功能,前段时间做了一个多文件、大文件、多线程文件上传的功能,使用效果还不错,总结分享下。

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

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

二、 设计分析

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

三、解决方案:

在HTML5之前的标准是无法支持上面的功能,因此我们需要把功能实现居于H5提供的新特性上面:

1. H5新标准对file标签进行了增强,支持同时选择多个文件

  
1
1

注意multiple属性,设置为true;

onchange:一般是选择文件确定后的响应事件
this.files:文件对象集合
2. File对象
H5提供的类似的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代码:
    
1
2
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
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

服务端处理逻辑相对比较传统,利用输入输出流、NIO等把文件写到磁盘即可。

这里需要特别考虑的是关于被切块文件的合并。前端在上传的时候,文件块是无序到达服务端,因此我们在每次接收到一个文件块的时候需要判断被切块的文件是否都传输完毕并进行合并,思路如下:
回到前端,我们在构造被切块的文件formData的数据结构:

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的时候,进行文件合并

六、运行效果

这里写图片描述

你可能感兴趣的文章
SaiProbe V1.0 内网渗透辅助脚本 20160605 php脚本
查看>>
腾讯某论坛存在SSRF漏洞(附批量捡漏脚本) ------ 20160614
查看>>
陌陌web服务器Path处理不当可以正向代理(idc机器/打不到办公网) 找到相关案例20160614
查看>>
入门———— Linux后门技术及实践 20160616
查看>>
Penetration Testing Tools Cheat Sheet 20160630
查看>>
基于E-Mail的隐蔽控制:机理与防御
查看>>
利用SSH隧道加密、隐蔽C&C通信流量
查看>>
c# string作为参数传递时 是值拷贝还是引用拷贝
查看>>
【pyecharts | 颜色配置】关于pyecharts中自定义颜色问题详解
查看>>
【Pyecharts | Scatter】气泡图实现 / 1990 与 2015 年各国家人均寿命与GDP
查看>>
【第七次全国人口普查 | Pyecharts】数据可视化~
查看>>
【Pyecharts | Map3D】带光影效果的3D地图~
查看>>
【Pyecharts | heatmap】解决GEO-Heatmap图表中热力区域混成一堆的情况
查看>>
【Pyecharts | TreeMap】中国各省市拥有高校数量对比图~
查看>>
Harmony OS — TextField输入框
查看>>
ubuntu两种卸载JDK的方式
查看>>
Harmony OS — Image图片
查看>>
Harmony OS — ListContainer列表
查看>>
quarkchain和以太坊go版本交易执行流程简单分析
查看>>
maven无法拉取设置阿里云,lombok无法下载
查看>>