前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >文件上传那些事儿

文件上传那些事儿

原创
作者头像
谭伟华
修改于 2017-06-19 11:02:53
修改于 2017-06-19 11:02:53
10.8K00
代码可运行
举报
文章被收录于专栏:谭伟华的专栏谭伟华的专栏
运行总次数:0
代码可运行

导语

作为一枚初入鹅厂的鲜鹅,对这里的一切都充满着求知欲。看到我们的KM平台如此生机勃勃,各种技术分享交流如火如荼,在努力的汲取着养分的同时也期待自己能为这个生态圈做出贡献。正好新人导师让我看看能否把产品目前使用的FileUploader从老的组件库分离出来的,自己也查阅了相关的各种资料,对文件上传的这些事有了更进一步的了解。把这些知识点总结一下,供自己日后回顾,也供有需要的同学参考,同时也欢迎各位大牛拍砖指点共同学习。

FileUpload 对象

在网页上传文件,最核心元素就是这个HTML DOM的FileUpload对象了。什么鬼?好像不太熟啊~别急,看到真人就熟了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file">

就是他啊!其实在 HTML 文档中该标签每出现一次,一个 FileUpload 对象就会被创建。该标签包含一个按钮,用来打开文件选择对话框,以及一段文字显示选中的文件名或提示没有文件被选中。

把这个标签放在<form>标签内,设置form的action为服务器目标上传地址,并点击submit按钮或通过JS调用form的submit()方法就可以实现最简单的文件上传了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<form id="uploadForm" method="POST" action="upload" enctype="multipart/form-data">
      <input type="file" id="myFile" name="file"></input>
      <input type="submit" value="提交"></input>
 </form>

这样就完成功能啦?没错。但是你要是敢提交这样的代码,估计脸要被打肿

都什么年代了,我们要的是页面无刷新上传!

更优雅的上传

现代网页通过什么来实现用户与服务器的无刷新交互?

——XMLHttpRequest

对,就是这个你很熟悉的家伙。如果你开发的产品支持的浏览器是现代浏览器,那么恭喜你,文件上传就是这么easy!特别强调强调现代浏览器是因为我们接下来讨论的XMLHttpRequest指的是XMLHttpRequest Level 2。

那什么是Level 1?为什么不行?因为它有如下限制:

  • 仅支持文本数据传输, 无法传输二进制数据.
  • 传输数据时, 没有进度信息提示, 只能提示是否完成.
  • 受浏览器 同源策略 限制, 只能请求同域资源.
  • 没有超时机制, 不方便掌控ajax请求节奏.

而XMLHttpRequest Level 2针对这些缺陷做出了改进:

  • 支持二进制数据, 可以上传文件, 可以使用FormData对象管理表单.
  • 提供进度提示, 可通过 xhr.upload.onprogress 事件回调方法获取传输进度.
  • 依然受 同源策略 限制, 这个安全机制不会变. XHR2新提供 Access-Control-Allow-Origin 等headers, 设置为 * 时表示允许任何域名请求, 从而实现跨域CORS访问(有关CORS详细介绍请耐心往下读).
  • 可以设置timeout 及 ontimeout, 方便设置超时时长和超时后续处理.

关于XMLHttpRequest的细节就不在这里赘述了,有兴趣可以移步这篇博客。目前, 主流浏览器基本上都支持XHR2, 除了IE系列需要IE10及更高版本. 因此IE10以下是不支持XHR2的.

上面提到的FormData就是我们最常用的一种方式。通过在脚本里新建FormData对象,把File对象设置到表单项中,然后利用XMLHttpRequest异步上传到服务器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var xhr = new XMLHttpRequest();
var formData = new FormData();
var fileInput = document.getElementById("myFile");
var file = fileInput.files[0];
formdata.append('myFile', file);

xhr.open("POST", "/upload.php");

xhr.onload = function(){
    if(this.status === 200){
        //对请求成功的处理
    }
}

xhr.send(formData);
xhr = null;

完成最基本的需求无法满足我们对用户体验的追求,所以我们还想要支持上传进度显示和上传图片预览。

上传进度

因为是XMLHttpRequest Level 2, 所以很容易就可以支持对上传进度的监听。细心地小伙伴会发现在chrome的developer tools的console里new一个XHR对象,调用点运算符就可以看到智能提示出来一个onprogress事件监听器,那是不是我们只要绑定XHR对象的progress事件就可以了呢?

很接近了,但是XHR对象的直属progress事件并不是用来监听上传资源的进度的。XHR对象还有一个属性upload, 它返回一个XMLHttpRequestUpload 对象,这个对象拥有下列下列方法:

  • onloadstart
  • onprogress
  • onabort
  • onerror
  • onload
  • ontimeout
  • onloadend

这些方法在XHR对象中都存在同名版本,区别是后者是用于加载资源时,而前者用于资源上传时。其中onprogress 事件回调方法可用于跟踪资源上传的进度,它的event参数对象包含两个重要的属性loaded和total。分别代表当前已上传的字节数(number of bytes)和文件的总字节数。比如我们可以这样计算进度百分比:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
xhr.upload.onprogress = function(event) {
    if (event.lengthComputable) {
        var percentComplete = (event.loaded / event.total) * 100;
        // 对进度进行处理
    }
}

其中事件的lengthComputable属性代表文件总大小是否可知。如果 lengthComputable 属性的值是 false,那么意味着总字节数是未知并且 total 的值为零。

如果是现代浏览器,可以直接配合HTML5提供的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<progress id="myProgress" value="50" max="100">
</progress>

其value属性绑定上面代码中的percentComplete的值即可。再进一步我们还可以对<progress>的样式统一调整,实现优雅降级方案,具体参见这篇文章

再说说我在测试这个progress事件时遇到的一个问题。一开始我设在onprogress事件回调里的断点总是只能走到一次,并且loaded值始终等于total。觉得有点诡异,改用console.log打印loaded值不见效,于是直接加大上传文件的大小到50MB,终于看到了5个不同的百分比值。

因为xhr.upload.onprogress在上传阶段(即xhr.send()之后,xhr.readystate=2之前)触发,每50ms触发一次。所以文件太小网络环境好的时候是直接到100%的。

图片预览

普通青年的图片预览方式是待文件上传成功后,后台返回上传文件的url,然后把预览图片的img元素的src指向该url。这其实达不到预览的效果和目的。

属于文艺青年的现代浏览器又登场了:“使用HTML5的FileReader API吧!” 让我们直接上代码,直奔主题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function handleImageFile(file) {
       var previewArea = document.getElementById('previewArea');
       var img = document.createElement('img');
       var fileInput = document.getElementById("myFile");
       var file = fileInput.files[0];
       img.file = file;
       previewArea.appendChild(img);

       var reader = new FileReader();
       reader.onload = (function(aImg) {
            return function(e) {
                 aImg.src = e.target.result;
            }
       })(img);
       reader.readAsDataURL(file);
}

这里我们使用FileReader来处理图片的异步加载。在创建新的FileReader对象之后,我们建立了onload函数,然后调用readAsDataURL()开始在后台进行读取操作。当图像文件加载后,转换成一个 data: URL,并传递到onload回调函数中设置给img的src。

另外我们还可以通过使用对象URL来实现预览

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var img = document.createElement("img");
img.src = window.URL.createObjectURL(file);;
img.onload = function() {
    // 明确地通过调用释放
    window.URL.revokeObjectURL(this.src);
}
previewArea.appendChild(img);

多文件支持

什么?一个一个添加文件太烦?别急,打开一个开关就好了。别忘了我们文章一开头就登场的FileUpload对象,它有一个multiple属性。只要这样

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input id="myFile" type="file" multiple>

我们就能在打开的文件选择对话框中选中多个文件了。然后你在代码里拿到的FileUpload对象的files属性就是一个选中的多文件的数组了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var fileInput = document.getElementById("myFile");
var files = fileInput.files;
var formData = new FormData();

for(var i = 0; i < files.length; i++) {
    var file = files[i];
    formData.append('files[]', file, file.name);
}

FormData的append方法提供第三个可选参数用于指定文件名,这样就可以使用同一个表单项名,然后用文件名区分上传的多个文件。这样也方便前后台的循环操作。

二进制上传

有了FileReader,其实我们还有一种上传的途径,读取文件内容后直接以二进制格式上传。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var reader = new FileReader();
reader.onload = function(){
    xhr.sendAsBinary(this.result);
}
// 把从input里读取的文件内容,放到fileReader的result字段里
reader.readAsBinaryString(file);

不过chrome已经把XMLHttpRequest的sendAsBinary方法移除了。所以可能得自行实现一个

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
XMLHttpRequest.prototype.sendAsBinary = function(text){
    var data = new ArrayBuffer(text.length);
    var ui8a = new Uint8Array(data, 0);
    for (var i = 0; i < text.length; i++){ 
        ui8a[i] = (text.charCodeAt(i) & 0xff);
    }
    this.send(ui8a);
}

这段代码将字符串转成8位无符号整型,然后存放到一个8位无符号整型数组里面,再把整个数组发送出去。

到这里,我们应该可以结合业务需求实现一个比较优雅的文件上传组件了。等等,哪里优雅了?都不支持拖拽!

拖拽的支持

利用HTML5的drag & drop事件,我们可以很快实现对拖拽的支持。首先我们可能需要确定一个允许拖放的区域,然后绑定相应的事件进行处理。看代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var dropArea;

dropArea = document.getElementById("dropArea");
dropArea.addEventListener("dragenter", handleDragenter, false);
dropArea.addEventListener("dragover", handleDragover, false);
dropArea.addEventListener("drop", handleDrop, false);

// 阻止dragenter和dragover的默认行为,这样才能使drop事件被触发
function handleDragenter(e) {
    e.stopPropagation();
    e.preventDefault();
}

function handleDragover(e) {
    e.stopPropagation();
    e.preventDefault();
}

function handleDrop(e) {
    e.stopPropagation();
    e.preventDefault();

    var dt = e.dataTransfer;
    var files = dt.files;

    // handle files ...
}

这里可以把通过事件对象的dataTransfer拿到的files数组和之前相同处理,以实现预览上传等功能。有了这些事件回调,我们也可以在不同的事件给我们UI元素添加不同的class来实现更好交互效果。

好了,一个比较优雅的上传组件可以进入生产模式了。什么?还要支持IE9?好吧,让我们来看看IE10以下的浏览器如何实现无刷新上传。

借用iframe

之前说了要实现文件上传使用FileUpload对象()即可。这在低版本的IE里也是适用的。那我们为什么还要用iframe呢?

因为在现代浏览器中我们可以用XMLHttpRequest Level 2来支持二进制数据,异步文件上传,并且动态创建FormData。而低版本的IE里的XMLHttpRequest是Level 1。所以我们通过XHR异步向服务器发上传请求的路走不通了。只能老老实实的用form的submit。

而form的submit会导致页面的刷新。原因分析好了,那么答案就近在咫尺了。我们能不能让form的submit不刷新整个页面呢?答案就是利用iframe。把form的target指定到一个看不见的iframe,那么返回的数据就会被这个iframe接受,于是乎就只有这个iframe会刷新。而它又是看不见的,用户自然就感知不到了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
window.__iframeCount = 0;
var hiddenframe = document.createElement("iframe");
var frameName = "upload-iframe" + ++window.__iframeCount;
hiddenframe.name = frameName;
hiddenframe.id = frameName;
hiddenframe.setAttribute("style", "width:0;height:0;display:none");
document.body.appendChild(hiddenframe);

var form = document.getElementById("myForm");
form.target = frameName;

然后响应iframe的onload事件,获取response

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hiddenframe.onload = function(){
    // 获取iframe的内容,即服务返回的数据
    var resData = this.contentDocument.body.textContent || this.contentWindow.document.body.textContent;
    // 处理数据 。。。

    //删除iframe
    setTimeout(function(){
        var _frame = document.getElementById(frameName);
        _frame.parentNode.removeChild(_frame);
    }, 100);
}

iframe的实现大致如此,但是如果文件上传的地址与当前页面不在同一个域下就会出现跨域问题。导致iframe的onload回调里的访问服务返回的数据失败。

这时我们再祭出JSONP这把利剑,来解决跨域问题。首先在上传之前注册一个全局的函数,把函数名发给服务器。服务器需要配合在response里让浏览器直接调用这个函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 生成全局函数名,避免冲突
var CALLBACK_NAME = 'CALLBACK_NAME';
var genCallbackName = (function () {
    var i = 0;
    return function () {
        return CALLBACK_NAME + ++i;
    };
})();

var curCallbackName = genCallbackName();
window[curCallbackName] = function(res) {
    // 处理response 。。。

    // 删除iframe
    var _frame = document.getElementById(frameName);
    _frame.parentNode.removeChild(_frame);
    // 删除全局函数本身
    window[curCallbackName] = undefined;
}

// 如果已有其他参数,这里需要判断一下,改为拼接 &callback=
form.action = form.action + '?callback=' + curCallbackName;

好了,实现一个文件上传组件的基本知识点大致总结了一下。在这些基础知识之上我们开始可以为我们的业务开发各种酷炫的File Uploader了。在之后的开发中会把相关的更细的知识点也总结进来,不足之处也欢迎大家指正。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
flink 1.11.2 学习笔记(3)-统计窗口window
接上节继续,通常在做数据分析时需要指定时间范围,比如:"每天凌晨1点统计前一天的订单量" 或者 "每个整点统计前24小时的总发货量"。这个统计时间段,就称为统计窗口。Flink中支持多种Window统计,今天介绍二种常见的窗口:TumbingWindow及SlidingWindow。
菩提树下的杨过
2020/12/22
1.4K0
flink 1.11.2 学习笔记(3)-统计窗口window
Flink异步之矛-锋利的Async I/O
在Flink 流处理过程中,经常需要和外部系统进行交互,用维度表补全事实表中的字段。
王知无-import_bigdata
2020/02/10
1.2K0
flink 1.11.2 学习笔记(4)-状态示例
接上节继续,今天学习Flink中状态的使用。数据处理的过程中,对当前数据的处理,有时候要依赖前一条数据的值,这种被称为“有状态”的计算。
菩提树下的杨过
2020/12/22
1.1K0
flink 1.11.2 学习笔记(4)-状态示例
工业互联网的现状、发展与未来(中国工业互联网研究报告)
面对不断提升的成本、运营压力,以及回报率走低的趋势,老牌工业企业“通用电气”在2012年提出了“工业互联网(Industrial Internet)”的概念,随后美国五家行业龙头企业联手组建了工业互联网联盟(IIC),将这一概念推广开来。之后,除了像GE这样的制造业巨头,IBM、思科、英特尔和AT&T等IT企业也加入了该联盟。
庞九公子
2020/06/02
30.9K4
工业互联网的现状、发展与未来(中国工业互联网研究报告)
当工业互联网遇上SaaS
来源 :我思锅我在 作者:我思锅我在GN ---- 标题中“工业”在先,其次“互联网”,最后触电SaaS,顺序不能颠倒,原因与现在大家对“AI+”的理解相似,先有产业,然后“+AI”。  听不少投资人说“工业互联网”看得越久,越困惑。然而真正让我们困惑和敬畏的其实是“工业”,不是“互联网”。 从2012年末GE首次提出“工业互联网(Industrial Internet)”,到2013年德国在汉诺威展提出“工业4.0”,再到2015年中国国务院发布“中国制造2025”,工业互联网和先进制造已经在多
腾讯SaaS加速器
2020/06/09
2.5K0
六大方法彻底解决Flink Table & SQL维表Join
随着 Flink Table & SQL的发展,Flink SQL中用于进行维表Join也成为了很多场景的选择。
大数据真好玩
2021/11/16
4K0
手把手构建基于 GBase8s 的 Flink connector
本篇文章,首先会向大家阐述什么是 Flink connector 和 CDC , 然后会通过手把手的方式和大家一起构建一个简单的GBase8s的Flink connector,并完成实践项目,即通过Mysql CDC实时通过connector同步数据到GBase8s中。
麒思妙想
2021/08/12
8720
手把手构建基于 GBase8s 的 Flink connector
Flink的DataSource三部曲之二:内置connector
本文是《Flink的DataSource三部曲》系列的第二篇,上一篇《Flink的DataSource三部曲之一:直接API》学习了StreamExecutionEnvironment的API创建DataSource,今天要练习的是Flink内置的connector,即下图的红框位置,这些connector可以通过StreamExecutionEnvironment的addSource方法使用:
程序员欣宸
2020/05/26
4800
工业互联网平台 GE predix
1998年麻省理工学院首次提出物联网概念,这正是今天工业互联网的起点。最初,物联网几乎只包括射频识别技术——也就是俗称的“电子标签”,随后慢慢延伸至传感器、网络、应用平台。经过十几年光景,物联网在很大程度上仍旧像是20世纪60、70年代的计算机——在许多领域都有着出色的应用,但却是一个个“应用孤岛”。 随后 ,在二十一世纪的第一个十年里,在金融危机时期,随着经济增长的不确定性增加,工业客户开始将注意力从提高生产力转向提高利润率。大数据的概念也越来越火爆,利用大数据/IOT等新技术提升和革命传统制造业是工业客
大数据和云计算技术
2018/03/08
2.7K0
工业互联网平台 GE predix
工业互联网的价值体现——工业APP时代的到来
2017年11月,国务院印发《关于深化“互联网+先进制造业”发展工业互联网的指导意见》,明确提出到2020年培育30万个面向特定行业、特定场景的工业APP的目标任务。
广州接点智能
2019/11/01
2K0
工业互联网的价值体现——工业APP时代的到来
flink 1.11.2 学习笔记(5)-处理消息延时/乱序的三种机制
* 按时间顺序发生的数据1 -> 2,本来应该是1先发送,1先到达,但是在1发送过程中,因为网络延时之类的原因,导致1反而到达晚了,变成2先到达,也就造成所谓的接收乱序;
菩提树下的杨过
2021/09/09
1.2K0
flink 1.11.2 学习笔记(5)-处理消息延时/乱序的三种机制
Flink connecton for gbase8c
上一次发文,好像还是上一次,鸽了这么久,开始还是有一些心理负担的,但是时间长了,好像就坦然了一些,但问题终究还是要面对的,所以今天我来了。。。
麒思妙想
2022/11/11
4960
Flink connecton for gbase8c
案例| 腾讯WeMake工业互联网平台的边缘容器化实践:打造更高效的工业互联网
腾讯WeMake工业互联网平台基于强大的数据、算力、算法与连接能力,并叠加上大量的工业Know-how, 机理模型与OT技术,搭建了一套强大的工业互联网平台架构。应用和数据是企业的核心资源,如何保证应用和数据的可靠性、安全性是腾讯WeMake最关心的问题之一。出于安全考虑,多数用户强调“数据落本地”,单靠数据中心难以满足其需求。此外,随着物联网技术的发展,平台中大量的智能终端位于网络边缘,集中计算模式不能满足所有应用场景。基于以上问题,腾讯WeMake工业互联网平台选用了边缘容器打造了一套安全高效的工
腾讯云原生
2021/03/15
2.1K0
使用小程序容器技术,使工业互联网平台建设加速
随着我国新一代信息技术与制造技术的深度融合,在工业数字化、网络化、智能化转型需求的带动下,以泛在互联、全面感知、智能优化、和安全稳固为特征的工业互联网应运而生。
pak
2022/09/21
4010
Flink史上最简单双十一实时分析案例
上期带大家用StructredStreaming做了双十一实时报表分析,没看过的朋友可以看看,
Maynor
2022/05/08
7160
Flink史上最简单双十一实时分析案例
使用小程序容器技术,使工业互联网平台建设加速
2021年是“十四五”规划的开局之年,也是在经历疫情后我国经济重回发展正轨的关键之年,期间工业互联网相关政策规划密集出台。2021年3月12日,《“十四五”规划和2035年远景目标纲要》发布,该文件三提“工业互联网”,为我国在“十四五”期间工业互联网的发展指明了前进的方向;随后,工信部印发的《“十四五”信息化和工业化深度融合发展规划》《“十四五”软件和信息技术服务业发展规划》等文件也对工业互联网、工业大数据、工业软件等产业未来五年发展作出明确部署。
pak
2022/06/14
3980
物联网数据库 IoTDB —— 从协议到数据
在这个系列之前的文章里,我们介绍了Iotdb的LSM,以及Iot中的最佳实践,这次我们看看如何将mqtt和Iotdb整合起来。下面我们开始:
麒思妙想
2021/06/15
1.6K0
物联网数据库 IoTDB —— 从协议到数据
2021年大数据Flink(四十二):​​​​​​​BroadcastState
在开发过程中,如果遇到需要下发/广播配置、规则等低吞吐事件流到下游所有 task 时,就可以使用 Broadcast State。Broadcast State 是 Flink 1.5 引入的新特性。
Lansonli
2021/10/11
8410
Apache-Flink深度解析-DataStream-Connectors之Kafka
Apache Kafka是一个分布式发布-订阅消息传递系统。 它最初由LinkedIn公司开发,LinkedIn于2010年贡献给了Apache基金会并成为顶级开源项目。Kafka用于构建实时数据管道和流式应用程序。它具有水平扩展性、容错性、极快的速度,目前也得到了广泛的应用。
王知无-import_bigdata
2019/03/22
1.9K0
Apache-Flink深度解析-DataStream-Connectors之Kafka
全网最详细4W字Flink全面解析与实践(上)
在大数据技术栈的探索中,我们曾讨论了离线计算的Spark,而当谈到实时计算,就不得不提Flink。本文将集中讨论Flink,旨在详尽展示其核心概念,从而助力你在大数据旅程中向前迈进。
BookSea
2023/10/28
1.2K2
全网最详细4W字Flink全面解析与实践(上)
推荐阅读
相关推荐
flink 1.11.2 学习笔记(3)-统计窗口window
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验