XMLHttpRequest2.0的实践之路--山寨某云盘

  最近使用云盘的频率比较高,用久了就开始琢磨它相关功能的实现,碰巧最近在拜读一本关于设计模式的书籍,上面也提到了*云里面对于上传下载的一些实现,于是乎自己动手山寨了一下,然后今天写下这篇文章记录一下。

github地址:https://github.com/839305939wang/weiCloud

效果图:
这里写图片描述
使用的技术栈:
    前端:html.css,js,jquery.
    后端:nodejs(koa)
    插件:ImageMagick-6.2.7-6-Q16-windows-dll.exe;ffmpeg

  一看图就知道确实是山寨,不过我的目的是将知识串起来,探索通过web页面来管理远程文件的解决方案,仅此而已,希望如果有内行的朋友看见了别笑话,方便的话可以留下以宝贵的见解。好了,不讲这么多虚的,开始进入正题.

目录

  1. 山寨【*云】的文件上传下载功能
    1.1 .文件列表
    1.2 .上传文件
    1.3 .下载文件
    1.4 .新建文件夹
    1.5 .删除文件及文件夹
  2. 图片处理模块gm和视频处理模块ffmpeg
    2.1 gm
    2.2 ffmpeg
  3. XMLHttpRequest2.0新功能
    3.1 HTTP请求的时限
    3.2 支持跨域请求
    3.3 支持上传FormData对象
    3.4 支持接受二进制文件
    3.5 获取文件传输的进度信息

一 山寨【*云】的文件上传下载功能

  山寨*云主要是受到各种云盘的影响,于是乎就突然有这么一个想法,然后就是干,砸门程序员就时这么简单。其次就是复习一下XHR的相关知识,也只有在实际动手中才会对他的使用场景更加熟悉,同时在脑海中形成一套比较完善的解决方案,将来有需要的时候,才能即使即插即用。下面讲讲主要实现的功能:

  1. 文件列表
  2. 上传文件
  3. 下载文件
  4. 新建文件夹
  5. 删除文件及文件夹

下面介绍一下我实现的功能以及一些功能点的实现思路。

1.1 文件列表

文件列表
   文件列表实现功能

  1. 按照目录层级关系,显示对应目录下的文件列表
  2. 显示不同文件类型的缩略图,文件大小,以及最近一次的修改事件
  3. 文件复选框操作(主要是为下载和删除用的)

1.2 上传文件

文件上传
   文件上传主实现功能:

  1. 上传进度条
  2. 上传内容成功后在文件列表的回显
  3. 针对大文件传输进行了处理,在进行大文件传输的时候利用了XRH2可以对文件对象进行slice处理;然后将分好的数据块儿一块儿一块儿按顺序上传。这样可以比较方便的实现暂停和恢复下载,尽管现在还没下载。这样如果再上传过程成意外中断,也可以即使恢复,不然要是再上传一个1G的文件时,上传到95%的时候中断了,那就sky!sky!了。
  4. 针对视频和图片,服务端在判断上传的文件类型为图片时使用图片界的军刀gm生成图片的缩略图,如果上传的文件是视频就采用ffmpeg生成视频文件首帧的缩略图。
    注意上面提到的这两个插件是需要在宿主机上安装的,后面我会介绍一下安装,以及在过程中可能会遇到的一些问题以及解决方式。

  代码只是一部分,主要是结合代码分析一下实现。

1.3 下载文件

这里写图片描述

  主要实现功能

  1. 下载进度条,
    这里解释一下,其实在web端我们很少看见有下载进度条,因为浏览器有默认的下载进度条,不需要我们在前端自己去实现,往往实现进度条的需求主要在客户端应用,我主要是想尝尝鲜。
    实现主要用到了xhr的process事件,然后根据已经下载的的数据大小(loaded)和文件的总大小来计算下载的进度.这需要注意在下载大文件的时候需要将req.responseType 设置成 “blob”,不然会造成浏览器假死,分析原因就是大文件占用的内存将系统分配给浏览器的内存榨干了,一般32位的浏览内存大小在512M左右,64位的有1G左右,当然这里有一个特别的主,就是IE,IE的内存比一般的浏览器都小,这也是IE经常会出现假死的原因之一。然后回到req.responseType=”blob”;这是xhr2新增的类型,他的声明是告诉浏览器接下来要接受的文件是以文件流的形传来的,浏览器看见这个类型的时候就不会使用自己的内存来存放数据,而是使用系统的内存在存放数据,这样只要系统内存是足够的,下载大文件的时候就不会有问题。

1.4 新建文件夹

实现功能:

  1. 新建文件夹
  2. 文件夹重命名

    这两个功能没什么特别,主要就是将需要新增的文件夹名字和在哪里木下新增的告诉服务,然后后台在收到请求的时候在对应的目录下生产对应的文件夹,然后将新生成的文件夹信息返回给前端,前端刷新列表。

1.5 删除文件

实现功能:

  1. 删除文件
  2. 删除文件夹
      删除文件夹的时候后端需要使用递归的方式去删除文件夹里面的内容最后在删除该文件夹

二 图片处理gm模块和视频处理模块ffmpeg

2.1 gm模块

   gm是一个nodejs模块,用它对图片在服务器端进行缩放裁切。使用gm模块还需要在宿主机上安装应用程序ImageMagick,号称图片界的瑞士军刀。我是在windows上跑的服务,所以安装的是ImageMagick-6.2.7-6-Q16-windows-dll.exe,这里建议安装6.xxxx的版本,我安装7.xx时,在使用nodejs调用gm模块的方法时会出现文件路径乱码,然后切换到6.xx的版本,重启一下机器,一定要重启一下。然后就ok了。

 function uploadIsImage(filePath,ext){
            return new Promise((resolve,reject)=>{
                let surports = [".png",".jpg"];
                if(surports.indexOf(fileExt)!=-1){
                    //判断是不是分段传输的最后一次上传
                    if(current<total){
                        resolve({"thumbPath:":""});
                    }
                    let thumbPath =path.join(config.thumbPath,path.basename(filePath,path.extname(filePath)))+"_1.jpg";
                    gm(filePath)
                    .resizeExact(40,40)
                    .write(thumbPath,function(err){
                        if(err){
                            console.log("err:",err);
                            resolve({thumbPath:""})
                        }
                        let url = path.join(config.staticPath,path.basename(filePath,path.extname(filePath))+"_1.jpg");
                        resolve({"thumbPath:":url});
                    });

                }else{
                    resolve("next")
                }
            })

        }

  具体操作去看gm包里面的readme.md文件就ok了。这里主要解决的就是文件路径乱码问题,然后见按照demo来就好了。

2.2 ffmpeg模块

   这是一个nodejs的视频处理模块,同样需要安装,这个和前面提到的ImageMagick不同的是,在官网上下载下来的安装包不是一个可执行安装文件,需要手动的去配置环境变量
这里写图片描述
然后就可以通过nodejs使用ffmpeg模块了

 //视频
        function uploadIsVideo(url,ext){
            return new Promise((resolve,reject)=>{
                let surports = [".avi",".mp4"];
                if(surports.indexOf(ext)!=-1){
                    //判断是不是分段传输的最后一次上传
                    if(current<total){
                        resolve({"thumbPath:":""});
                    }else{
                        try {
                            let basename = path.basename(url)
                            let process = new ffmpeg(url);
                            process.then(function (video) {
                                let thumbFilePath = basename;
                                video.fnExtractFrameToJPG(config.thumbPath, {
                                    frame_rate : 1,
                                    number :1,
                                    size:"40x40",
                                    file_name : basename
                                }, function (error, files) {
                                    if (error){
                                        return;
                                    }
                                    let file = files[0].split("thumb")[1];
                                    let returnPath = path.join(config.staticPath,file)
                                    resolve({thumbPath:returnPath});
                                });
                            }, function (err) {
                                resolve({thumbPath:""});
                            });
                        } catch (e) {
                            resolve({thumbPath:""});
                        }
                    }  
                }else{
                    resolve("next")
                }
            })

  同样可以通过ffmpeg模块下的readme.md文件了解模块是使用方式。已发布的npm模块都市有这个文件的,该文件介绍了模块的使用方式。

  前面主要介绍了一下我实现的一些功能点,没有深入的分析代码,有需要的话可以去https://github.com/839305939wang/weiCloud看看,由于我没有使用到数据库保存文件信息,所以所有信息都是返回文件列表的时候一次性全部返回的,然后前端代码还进行了一些处理,所以这一块看起开会很乱。后期要是有时间,我把数据加进来,这样可能代码会精简很多。
  说了实现,最后来回忆一下XHR2.0

三 XMLHttpRequest2.0新功能

  XMLHttpRequest是前端和后端进行数据异步交互的基础,随着现在低版本浏览器的逐步退出市场以及各种前端框架的普及,出现了大量已经将XMLHttpRequest封装好了的插件,前有dom操作神器jQuery,后有现在的axios,request…。这些让我们逐步忘记了还有XMLHttpRequest这给个大哥。但是这并不代表我们没有必要去了解他了,因为最原始的东西往往能擦出更激烈的火花,做出用户体验更好的的需求,我们也不用去生搬硬套那些插件。应为插件在设计的时候考虑的是大众需求,不可能放方面都设计到。所以对于XMLHttpRequest我们还是很有必要了解的,似乎也是各大公司招聘必问的内容。
  这里不详细介绍XMLHttpRequest,如果有需要可以去W3school上去看看,这里面介绍的很详细的,这里我们主要介绍一下XMLHttpRequest2.0针对XMLHttpRequest有了那些新增,这些新增的功能能帮我们实现什么需求,讲到到需求我就想起了前两天网络上流传的开发人员和产品经理干架的段子,从这个例子中我们可以发现,只有不断提高自己的技能才有可能满足产品经理的某些变态需求。扯远了,我们言归正传。下面罗列一下新增的功能:

1. 设置HTTP请求的时限;

2. 请求不同域名下的数据(跨域请求);

3. 使用FormData对象管理表单数据;

4.  获取服务器端的二进制数据。

5. 获取数据传输的进度信息。

3.1 HTTP请求的时限

  http请求响应时间是不确定的,有的时候可能会需要很长的事件,如果这个请求由于某种原因一直没有返回,如果这种请求过多,一方面可能会导致页面的其他正常请求处于pendding状态,另一方面,也可能导致服务器由于tcp链接过多造成服务器资源的大量消耗。所以给请求设置超时时间是非常有必要的,在XMLHttpRequest2.0中我们可以通过像下面这样设置请超时间,当请求超时时,会主动触发超时事件,我们在事件里面可以做一些友好的处理.

xhr.timeout = 5000;//毫秒数
xhr.ontimeout = function(event){

    alert('请求超时');

}

  这里需要注意一点,所有的事件和设置都是需要在xhr.open(…)执行之后在设置,不然设置是无效的.

3.2 支持跨域请求

  说到跨域请求,我们脑子里面可能最先想到的是jsonp,现在XMLHttpRequest2.0也支持了,像下面这样:

xhr.open('GET', 'http://other.server/getCors');

  这看起来和我们以前的请求没有什么区别,但是XMLHttpRequest2.0请求跨域资源需要服务器端的配置,服务端在返回资源的时候需要指定支持跨域请求的URI,例如:

ctx.set({
   "Access-Control-Allow-Origin":"http://other.server/getCors"
})

  如果所有的请求都支持,则:

ctx.set({
   "Access-Control-Allow-Origin":"*"
})

3.3 支持上传FormData对象

  通常在上传表单对象的时候,通过form标签来实现,例如:

  <form id="loginForm" action="/uploadForm" method="POST" enctype="application/x-www-form-urlencoded">
     <input type="text" name="usename" value="" placeholder="请输输入用户名"/>
     <input type="password" name="passw" value="" placeholder="请输输入密码"/>
     <input type="submit" value="提交" id="submitBtn"/>
  </form>

  一种是直接通过点击提交按钮同步提交,但是这种方式一般使用的比较少;另外一种就是异步提交,

function submit(){
    document.forms[0].submit();
}

  通过查看http请求,我们可以看见表单数据被封装成一个FormData对象被传输出去了
这里写图片描述

  现在处理通过这种直接使用from标签来上传FromData数据外,HttpXMLRequest也可以上传FormData数据,像下面这样:

var form = new FormData();
          form.append("username","zxj");
          form.append("password",123456);
          var req = new XMLHttpRequest();
          req.open("post", "${pageContext.request.contextPath}/public/testupload", false);
          req.send(form);

3.4 支持接收二进制文件

  通过查看HttpXMLRequest接口文档我们可以发现目前ajax请求的响应类型(ResponseType)支持的格式为:

    ""  DOMString (this is the default value)
    "arraybuffer"   ArrayBuffer
    "blob"  Blob
    "document"  Document
    "json"  JavaScript object, parsed from a JSON string returned by the server
    "text"  DOMString

  在HttpXMLRequest2.0中新增了arraybuffer,blob两种二进制类型

  ArrayBuffer代表接受的响应主体是二进制数组:

req.responseType="arraybuffer";
req.onreadystatechange = function () {
         if (req.readyState === 4 && req.status === 200) {
             var arrayBuffer = req.response;
         }
     };

  这样我们就能接收到服务器传来的二进制文件了;这里需要注意在指定了响应头为二进制数据的时候只能通过req.response来获取响应体,访问req.responseText会抛出一个错误

这里写图片描述

  blob:表示一个不可变的, 文件对象,里面可以储存大量的二进制编码格式的数据

req.responseType = "blob";

  指定req.responseType==”blob”后请求响应类型必须是文件流
这里写图片描述
  content-Type为application/octet-stream.这种使用场景一般在进行大文件传输的时候使用,服务端通过管道的形式获取文件流,然后再将该文件流作为响应返回客户端

 let fileReaderStream = fs.createReadStream(url);
            this.ctx.body = fileReaderStream.on('error',this.ctx.onerror).pipe(PassThrough());

3.5 获取文件传输的进度信息
  云盘之类的应用上传或者下载文件的时候往往都伴随这个一个进度条,这样可以给用户更好的体验,在httpXMLrequest中也实现了文件上传和下载的进度事件,我们可以监听相关操作的process事件来获取进度,上传和下载进度事件是有区别的

上传进度事件监听:

req.upload.addEventListener("progress",function(evt){
 //通过evt.loaded来判断文件加载多少了
})

  这里需要注意,通过测试发现,这里的上传进度其实不是指文件成功上传到服务器的的进度,而是读取本地文件的进度,即使你上传失败,这里的事件依然会触发,直到文件全部读取.所以这可以用来实现文件加载进度条或文件扫描

下载进度事件监听

req.addEventListener("progress",function(evt){
//通过evt.loaded来判断文件下载多少了
})

  这里需要注意的是通过流获取文件的方式Content-Length的值不是文件的总大小,我们可以再响应头里面返回文件的总大小.这样方便计算文件下载进度.

总结

实践出真知,只有动手操作,才能将书本上和别人分享知识变成自己的,同时提高自己的编码和代码组织能力。

展开阅读全文

没有更多推荐了,返回首页