整理前端工作中的可复用代码(二):拓展spark-md5,支持计算网络文件md5

3,766 阅读4分钟

这里是《整理前端开发中的可复用代码》中的第二篇,最初此系列文章的标题不是这个,但觉得标题要准确、明白一些,便做了修改。这里的经验都来自作者的工作实践,入了前端坑的摸爬滚打。

背景

在工作中接到一个需求,需要在浏览器端计算文件的md5值,然后在上传文件时传递给后端储存。按照以往的思路,后端只需要三两行代码即可实现,比如使用php中的md5_file。

<?php
  $filename = "file.jpg";
  $md5 = md5_file($filename);
  echo $md5;
?>

然而文件并不是上传到后端服务器,而是直接上传到阿里云的oss。如果后端去请求oss上的文件计算md5,就容易造成服务器的压力,所以计算文件md5的最好方式是交给客户端实现。

尽管以前没有接触过浏览器端计算文件md5,但在谷歌、百度搜索一番后,便用spark-md5解决了这一需求。但如果事情就这样结束了的话,就没有后续计算网络文件md5的想法,以及这篇文章的出现了。

接着计算本地文件md5的需求,又接到了一个需求。上传的图片要进行压缩,压缩使用plupload的自带功能实现,而压缩后的图片md5已改变。经过一些时间还没有找到解决方案,加上技术总监对我说项目后续要增加新东西,让我最好在审核上传资源的时候计算md5,这样便开始了前端计算网络文件md5的摸索。

File 对象

在谷歌、百度了N久之后,没有找到一个准确的答案,包括flash方案。我想应该很少有在浏览器端计算网络文件md5的需求或实现,也展开了自己的一些猜想。

第一应该先要有一个文件,然后从文件中计算md5,类似:

var file = new File(['www.domain.com/test.jpg'], 'test.jpg', {type: 'image/jpg'});

然而File对象并不接收url参数来生成文件,这里的url被当成字符串处理了,这不是想要的答案。

Blob 对象

Blob对象是一个类文件对象,File对象继承于Blob对象,它们的用法类似,Blob也不能直接处理url为Blob对象。但要计算文件md5,需要FileReader读取一个File或Blob对象,再由spark-md5进一步计算得出md5。

所幸在XMLHttpRequest中可以指定responseType为blod,请求文件并指定responseType为blod时,XMLHttpRequest将返回一个Blob对象,相关介绍可参考这里responseType,如下:

var request = new XMLHttpRequest();

request.open('GET', url, true);
request.responseType = 'blob';
request.onload = function() {
  //类似Blob(3275) {size: 3275, type: "image/vnd.microsoft.icon"}
  console.log(request.response);
};
request.send();

至此,下一步便可以用spark-md5计算Blob对象来返回md5了(spark-md5官方示例)。

var request = new XMLHttpRequest();

request.open('GET', url, true);
request.responseType = 'blob';
request.onload = function() {
 var file = request.response;

var blobSlice = File.prototype.slice || File.prototype.mozSlice || 
    File.prototype.webkitSlice,
    chunkSize = 2097152, // Read in chunks of 2MB
    chunks = Math.ceil(file.size / chunkSize),
    currentChunk = 0,
    spark = new SparkMD5.ArrayBuffer(),
    fileReader = new FileReader();

fileReader.onload = function (e) {
    spark.append(e.target.result);
    currentChunk++;

    if (currentChunk < chunks) {
        loadNext();
    } else {
    	//获取md5
        var md5 = spark.end();
        console.log(md5);
    }
};

fileReader.onerror = function () {
    console.error('文件读取失败');
};

function loadNext() {
    var start = currentChunk * chunkSize,
        end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;

    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}

loadNext();
};
request.send();

拓展spark-md5

通过上面的代码,便实现了浏览器端js计算网络文件的md5。而据spark-md5的使用方式,直接计算字符串md5时比较简单:

SparkMD5.hash('hello world');//"5eb63bbbe01eeed093cb22bb8f5acdc3"

如上只需要一行代码即可,但在计算文件时略复杂些,代码不利于复用。出于本系列文章整理可复用代码的初衷,以及想要加入计算网络文件md5功能,便封装了一个插件。



md5-util

使用方式:

<script src="md5-util.min.js"></script>
//计算本地文件md5
SparkMD5.file(file,function(md5){
 console.log(md5)
})

//计算网络文件md5
SparkMD5.file(url,function(md5){
 console.log(md5)
})

这里参考过browser-md5-file,不同的是此插件只扩展了spark-md5,增加了file方法,spark-md5的所有方法均可在插件中使用。

浏览器兼容性

这里只讨论计算网络文件md5的兼容性,在pc端一些主流浏览器中测试了多次,得出的md5均是正确的。但由于XMLHttpRequest的responseType指定为blob,在移动端发现一些兼容性问题,已知ios uc浏览器及安卓5.1.1系统浏览器中返回blob异常,导致md5计算错误。

所以要计算网络文件的md5时,请慎用。如果哪位大神有更好的解决办法,还请分享下。

后记

一直以来web端计算md5的任务主要分配给后端,但渐渐的前端技术也能实现了,这意味着前端技术的逐渐繁荣。不过用“痛并快乐着”很适合描述前端开发,使用一个个新技术完成了以前不能实现的功能,也面临着各种浏览器兼容性问题带来的困扰。

还愿我们心怀向往,并砥砺前行吧。


此系列相关文章(同步更新):

  1. 整理前端工作中的可复用代码(一):做一个整合存储的插件
  2. 整理前端工作中的可复用代码(二):拓展spark-md5,支持计算网络文件md5