精华 获取文件上传进度
发布于 4个月前 作者 yyrdl 1336 次浏览 来自 分享

国庆节对于一个大三党来说是一整块难得的时间,出去玩也好,做其他的事也好,都可以随心所欲地安排。想着利用这个时间开发 一个社交网站作为学习node的一个总结,不怎么精,也算凑合,无奈还有太多的事儿要做啊。废话不多说,转入正题:

今天遇到图片上传的问题,服务器能成功收到图片信息,但如何将进度返给客户端呢?用 <form method='post' action="" target="hidden- iframe" enctype='multipart/form-data'></form>发送文件是一个post请求,服务器只可能回复一次,而且这个回复主要用来回复前端上传 文件的获取地址,不适合再做他用。

网上查了查,说是可以用flash做,好吧,表示不会flash… 目前主要的问题是如何不断地从服务器获得上传进度,socket貌似能做到这一点,问题是不知道上传结束时客户端如何主动断开socket链接,有知道的朋友麻烦告诉一声^-^,于是决定采用轮询了。 先贴前端部分代码: html部分

      <iframe name="hidden_iframe" id='hidden_iframe' style="display:none" src=""> </iframe>
      <form method='post' action='/uploadimage' enctype='multipart/form-data' target="hidden_iframe">
               <input type="text" name="randomID" id="uploadTag" style="display:none" />
               选择图片:<input type='file' name='img' id='filearea' onchange="setTag()"/>
               <input type='submit' value='提交' onclick=" startRequest()" />
     </form>
     <div id="showbox" ></div>

JS部分

       var uploadTag;
       function startRequest()
       {
             var json={"randomID":uploadTag};
            setTimeout(post_data(obj,'/getuploadprocess',dealResponse),1000);
       }
       function dealResponse(json)
       {
         var box=document.getElementById("showbox");
         box.innerText=json.progress+'%';
         if(json.progress!=100)
         {
           startRequest();
         }
       }   

//上面这俩个function 组成一个不达目的不罢休的轮询,post_data是个ajax,就不贴了

       function setTag()
       {
           uploadTag=Math.ceil(Math.random()*1000000);
           var tag=document.getElementById("uploadTag");
           tag.value=uploadTag;
       }

//设置随机id,避免搞不清是请求哪个文件的进度

       function uploadErr(message)
       {
          alert(message);
       }

//处理错误,当然这里只是简单alert,没有考虑用户体验,就写简单点吧

       function uploadEnd(json)
       {
         var obj=JSON.parse(json)
         alert(obj.message)
       }

//上传结束的callback,返回图片的获取地址,前端应该将其设置成一个img元素的src属性,也从简了:)
** 后端:** 后端大部分人都是使用的express,然后就顺便设置了app.use(multer(dest:""))解析请求,那可就做不了进度条了,建议把这个设置删了,然后 var formidable=require(“formidable”);(写在路由模块里); 代码:

       var uploadprogress={};//全局变量,存进度
       module.exports=function(app)
       {
       ...........................................................................
       ...........................................................................
         app.post('/uploadimage',function(req,res){
             var id=1;
             var uploadfile;
             var form=new formidable.IncomingForm();//formidable模块的用法自己去github看文档吧
             form.uploadDir='';
             from.keepExtensions=true;
             form.on("field",function(name,value){//设置唯一可标识的id
             id=req.session.user._id+req.body.randomID;//这个value就是表单中文本框的值
       });
             form.on("err",function(err){
               var callback="<script>top.uploadErr('"+err+"');</script>";
                res.end(callback);//这段文本发回前端就会被同名的函数执行
       })
              from.on("progress",function(bytesRecieved,bytesExpected){
                 uploadprogress.id=Math.ceil((bytesRecieved/bytesExpected)*100);//更新进度
              //这里有个id的值是否已经设置的问题,不过没关系,就算仍是最初的1,也只耗一丁点内存
       })
              form.on("abort",function(){
                ............
       })
              form.on("file",function(err,file){
                  if(err)
                 {
                      console.log(err);
                       var callback="<script>top.uploadErr('"+err+"');</script>";
                       res.end(callback);           
                    }
                 uploadfile=file;//记录文件信息
       })
             form.on("end",function(){
                 var file=new File()
                 .......
                 ...... 
                file.save();//将文件信息存入数据库,如果是文章里面的图片的话,就没有必要存了,在前端直接设置src就相当于存了
                var fpath=uploadfile.path;
                var callback="<script>top.uploadEnd('"+fpath+"');</script>";
                res.end(callback);
       })
             form.parse(req);
       })
       
       app.post('/getuploadprocess',function(req,res){
            var id=req.session.user._id+req.body.randomID;
            var pr;
            if(uploadprogress.id)
             {
               pr=uploadprogress.id;
             }
            else
           {
               pr=0;
           }
           if(pr==100)
           {
              delete uploadprogress.id;
           }
             res.status(200).json({"progress":pr});
       })
       }

ok主要的代码已经贴完了,下面来说一下工作的流程,首先由表单提交文件,在确定文件后会自动生成一个随机ID用于标识,这个ID发给后台和user的id组合为文件的标识;后台接受文件上传请求,formidable开始解析文件,主要监听progress事件,由其返回的参数来更新上传进度。在表单点击提交的时候同时触发startRequest函数,向后台获取上传进度,这个请求由/getuploadprogress处理,里面的代码就是简单的响应和清理功能。自此全部结束,前端获得进度后想要以什么方式展现就随看官发挥吧,我就简单展现其百分比了

PS:如果有大神路过,小弟请教一个问题:这个代码 是实现了功能,但获得进度是个长轮询,不知道对服务器来讲压力大不大。。测试时我同时上传俩个400多M的文件,电脑风扇就转个不停。虽然文件上传成功了,但还是担心性能问题(我用本机做的服务器),求大神留下高见!

27 回复

代码格式化做好点嘛

@alsotang 不会markdown 啊。。大神对后面提的那个问题能给点建议吗?

@alsotang 刚给缩进了一下

@yyrdl 用 markdown 排版一下,我再帮你加精华。学一下 md。

@alsotang 已排版,师兄我这个方法在性能上有没有缺陷啊?

@yyrdl 长轮询对服务器来说压力并不大,但你的风扇为什么那样转我就没想到了…

@alsotang 额,只要对服务器压力不大就好,可能是同时快速往硬盘里写文件的原因

@yyrdl @alsotang雾空

  • 从express端并没有看出 randomID 有什么作用,可能你的意思是 progess[id]
  • 前端JS部分,你的代码最终结果是,ajax->callback->ajax->callback 无时间间隔,setTimeout只是对开始做了延迟,必然导致风扇狂转
  • 如果考虑多均衡负载的话,怎么保证request都打到同一个服务器的同一个node进程上去?(我不清楚node执行机制,好像node是单进程就够了,可能有问题,还得去好好拜 piao灵大大**)

新手问一下,为什么不用WebSocket呢?

@hacke2 简单明了,因为不会…

虽然不懂,但是之前有看过 HTML5的进度事件 , 上传处理不是那么麻烦的事情吧…

@hacke2 在上面我也有提socket,只是不知道在客户端如何主动关闭连接,总不能一直保持连接吧

@think2011 这个不错,点赞

@think2011 嗯嗯,那是利用了html5的api,这个没有用html5的api,避免因为兼容性出bug

@SenYu啊,原来是你啊。 randomID最终和用户ID构成每个上传文件的独一无二的标识。 前端JS中每次执行startRequest都会有1000毫秒延时,ajax->callback这个流程只会执行一次 关于第三点不明白你的意思,干嘛要打到同一个进程里面去,觉得没必要啊

@yyrdl 使用Socket.io就没兼容性问题了~

@yyrdl socket.io是这样处理的:如果客户端停止传输信息,并且一定时间没有正常的关闭,则认为连接已经断开

不明白为什么这样复杂处理。xhr本身就支持监听上传的progress事件。

@hacke2 不能手动关闭吗? 之前有用socket写过一个聊天室,一直以为关掉页面才能关掉连接

@yyrdl 好忧桑阿里同事竟然不理我。。。

  • 我说的是,randomID没错,但是 你代码写错了 .id 改成[id]吧。。。。
  • startRequest那个我看错了。但是。。。你确定你的变量作用域都对?obj,json那几个作用域链不大对啊。。。
  • 服务器的全局变量global.XXX 是针对 node进程而言的,多进程global对象不同

@SenYu 我只是对这个问题没有感觉…所以就没有发言了…

@SenYu 代码肯定是没错的 .id 和[id]都行,对象是类数组,所以可以通过[id]这种方式获取,javascript权威指南不是写的很清楚么! 说多了没意思,不服气,回学校直接跑给你看,至于风扇不停的转估计是我同时往硬盘快速写大文件造成的,笔记本不能喝服务器比

看下jquery-file-upload

@violet-day 谢谢分享 :) 现在还处于学习阶段,觉得用jquery库不好,前端都是用原生javascript

@yyrdl 不要重复造轮子啊

看到iframe就浑身难受 iframe真不是个好东西啊

回到顶部