阳光博文 你的空间 知识的容器

FamaUploader

大概是什么个情况

HTML5的files API兼容IE10+,所以对于无知的用户使用的老旧的IE789而言,一般的上传交互由flash来做;
我想不能老是这么惯着用户,看看什么时代了,微软都不要的玩意,你还不舍得换更好的。。。
好吧,其实我还是对IE789做了简单的兼容,一是能够上传,二是基本样子一致;但是不使用flash;
HTML5优先,因为当你抛开IE789后,你会发现:进度条、批量上传、拖拽上传、分段、断点续传什么的都是HTML5的用武之地,并且非常潇洒!
虽然这是个单独的页面,但是放心,FamaUploader并非大而全,只是对常见的上传及交互情况作了个整理,
因为场景不同,难度不一,交互有差异,可能思路更重要,这才是我单独把它拿出来的原因;


跨域

middlePage.html

一般情况下,其实是大多数情况下,跨域对于上传来说不可躲;推荐用iframe调用上传页面的形式引用;
相对来说,middlePage的方式更通用些,而且只需前端处理;CORS不兼容IE7;设置domain比较常见,但对于domain需动态获取的时候,增加了前后端的复杂度;

1、直接请求跨域接口
uploader.init里设置serverURL为跨域请求的接口,设置staticHost为访问上传后的文件的域,带https://;
如果判断为HTML5上传,不需再做跨域处理,因为后台已设置CORS;
如果为低版本IE,则前后端都采用设置domain的方式跨域;

2、iframe调用上传页面的方式
这种方式即把上传作为独立站点,便于统一调用和管理,这时候跨域采用middlePage的方式:
调用站根目录添加middlePage.html(view on github),同时iframe调用时带上setDomainPath参数;
如:3000端口的站点调用端口为5000的上传页面;

  
1<iframe src="https://localhost:5000/uploader?setDomainPath=https://localhost:3000/middlePage.html"></iframe>
  

以iframe的方式调用上传页面

动态创建多个iframe上传

如果将上传作为一个独立站点管理,或者位于静态资源站,我想以iframe调用页面的方式通用性更强些;
像页面上有多个独立的上传单元,并且可能会由用户操作动态生成上传单元,这时候,该方式的优势就很明显了;
多个上传单元通过other_data参数区分,并且other_data可携带任意扩展的参数;
由于iframe区域大小的限制以及将这种方式看作独立单元的原因,所以input[multiple]以及批量拖拽都将只上传一个文件;
如果要一次操作上传多个文件,则采用不跨域的方式或者跨域直接请求接口的方式;

  
1<!-- html -->
2<iframe src="" frameborder="0" width="600"></iframe>
3<iframe src="" frameborder="0" width="600"></iframe>
4<!-- js -->
5$(function(){
6    $('iframe').each(function(i){
7        var _src='https://localhost:5000/uploader?setDomainPath=https://localhost:3000/callback_data.html&other_data=';
8        _src+=JSON.stringify({
9            order:(i+1),
10            index:(i+1)
11        });
12        $(this).attr('src',_src).attr('data-ins',1+i);
13    });
14
15});
16function callback(data){
17    console.log(data);
18    $('iframe[data-ins="'+JSON.parse(data.other_data).index+'"]').attr('fileUrl',data.path);
19}
  

后端Nodejs

app.js

前端请求的统一入口为uploader.init里的serverURL;如:https://localhost:5000/uploader;
1、IE789下请求文件信息和上传进度

  
1$.post(serverURL,{
2    getfileinfo:2,
3    uploadtoken:formToken
4}).then(cb(data));
  

2、断点续传时,请求已上传的分块index值

  
1var _data={
2    token:tk,
3    getfileinfo:1
4}
5if (odata) {
6    _data.ins=ins;
7};
8$.post($self.serverURL,_data)
9.then(cb(data));
  

3、由于文件分段和断点的原因,HTML5的上传统一由formidable的onPart处理;详情见:扒一扒Nodejs formidable的onPart

4、年月日的形式生成文件目录

  
1function mkYYMMDDfilePath(fn){
2  var path='./public/imgs/';
3  var date=new Date(),
4    y=date.getFullYear(),
5    m=date.getMonth()+1,
6    d=date.getDate();
7
8    m=m.toString().length&lt;2?'0'+m:m;
9    d=d.toString().length&lt;2?'0'+d:d;
10  fs.exists(path+y,function(is){
11    if (is) {
12      fs.exists(path+y+'/'+m,function(s){
13        if (s) {
14          fs.exists(path+y+'/'+m+'/'+d,function(ss){
15            if (ss) {
16              fn&amp;&amp;fn();
17            }else{
18              fs.mkdirSync(path+y+'/'+m+'/'+d);
19              fn&amp;&amp;fn();
20            }
21          });
22        }else{
23          fs.mkdirSync(path+y+'/'+m);
24          fs.mkdirSync(path+y+'/'+m+'/'+d);
25          fn&amp;&amp;fn();
26        }
27      });
28    }else{
29      fs.mkdirSync(path+y);
30      fs.mkdirSync(path+y+'/'+m);
31      fs.mkdirSync(path+y+'/'+m+'/'+d);
32      fn&amp;&amp;fn();
33    }
34  });
35}
  

5、分段时陆续写入文件

  
1fs.open(path,'a',cb(err,fd));
2fs.write(fd,buffer,offset,length,position,cb);
  

HTML5

files API、sparkMD5、drag、drop

uploader.init里设置brokenSize,文件多大时启用断点续传,默认50M;
分段上传每段10M;
SparkMD5读取文件一段buffer、name、size、lastModifiedDate同时生成hash,确保断点时判断文件的准确性;
详情见:基于Nodejs的大文件上传之断点续传
批量拖拽与多文件上传的纠葛,详情见:Nodejs+HTML5兼容IE789的大文件上传完整版

url参数支持

扩展参数other_data

accept:接收的文件类型,多个逗号隔开
ms:接收文件的大小,byte为单位
btnText:上传按钮的文本
btnClass:上传按钮的自定义class
show:图片上传后是否可预览
setDomainPath:iframe跨域调用时middlePage的地址
other_data:iframe调用时可扩展的参数,如区分同一个页面上的多个iframe

问题修复

issues

1、跨域请求接口时先发出了OPTIONS请求

  
1app.options('*',function(req,res,next){
2  res.setHeader('Access-Control-Allow-Origin','*');
3  res.setHeader('Access-Control-Allow-Headers','X-Requested-With');
4  res.end();
5});
 





在线咨询