纯前端文件名生成算法(七牛ETag算法)示例

这几天在研究唯一文件名生成与文件特征验证解决方案,之前都是使用MD5算法,但是除了MD5外还有没有其他办法呢?后来无意看到了七牛云的ETag就稍微研究了下。
七牛ETag算法说明:https://developer.qiniu.com/kodo/manual/1231/appendix#qiniu-etag
github:https://github.com/aisanjiaomiao/qetag-browser
源码:https://gitee.com/baojuhua/qetag-browser

1.算法原理

七牛的 hash/etag 算法是公开的。算法大体如下:

  • 小于或等于4M的文件
1. 对文件内容做sha1计算;
  +---------------+
  |     <=4MB     |
  +---------------+
   \      |      /
    \   sha1()  /
     \    |    /
      \   V   /
    +--+-----+
    |1B| 20B |              2. 在sha1值(20字节)前拼上单个字节,值为0x16;
    +--+-----+
     |  |
     |  \--- 文件内容的sha1值 
     |
     \------ 固定为0x16
3. 对拼接好的21字节的二进制数据做url_safe_base64计算,所得结果即为ETag值。
  • 大于4M的文件
1. 对文件内容按4M大小切块;
2. 对每个块做sha1计算;
         +----------+----------+-------
         |    4MB   |   4MB    | ...
         +----------+----------+-------
          \    |    |   |     /
           \ sha1() | sha1() /
            \  |    |   |   /
             \ V    |   V  /
              +-----+-----+-------
              | 20B | 20B | ...
              +-----+-----+-------
               \      |      /
                \   sha1()  /
                 \    |    /
                  \   V   /
                +--+-----+
                |1B| 20B |      3. 对所有的 sha1 值拼接后做二次 sha1,
                +--+-----+         然后在二次 sha1 值前拼上单个字节,值为0x96;
                 |  |
                 |  \---- 二次sha1的值
                 \------- 固定为0x96
4. 对拼接好的21字节的二进制数据做url_safe_base64计算,所得结果即为ETag值。
  • 为何需要公开 hash/etag 算法?
    这个和 “消重” 问题有关,详细见:如何避免用户上传相同的文件
  • 为何在 sha1 值前面加一个字节的标记位0x160x96
    0x16 = 22,而 2^22 = 4M。所以前面的 0x16 其实是文件按 4M 分块的意思。
    0x96 = 0x80 | 0x16。其中 0x80 表示这个文件是大文件(有多个分块),hash 值也经过了2重的 sha1 计算

2.qetag.js使用教程(NodeJS)

前往(https://github.com/qiniu/qetag)下载源码

使用示例:

//demo.js
var getEtag = require("./qetag");
var fs = require("fs")

fs.readFile("文件路径", function (err, buf) { 
    getEtag(buf, function (v) {
        console.log(v);
    })
});
/////////或
getEtag("文件路径", function (v) {
     console.log(v);
})

3.纯前端JS实现qETag

其实我们已经知道原理和NodeJS的源文件了我们只要稍微改一下就可以了
完整源码在:https://gitee.com/baojuhua/lutils/blob/master/others/qetag.js

1.sha1算法

2.Uint8Array与原生Array来替代Buffer操作

  • 我们观察源码中的Buffer操作
  • 我们稍微改一下,用Uint8Array原生Array替代Buffer

3.完整源码

  • qetag.js
function getEtag(buffer, callback) {
    // sha1算法
    var shA1 =  sha1.digest;

    // 以4M为单位分割
    var blockSize = 4 * 1024 * 1024;
    var sha1String = [];
    var prefix = 0x16;
    var blockCount = 0;

    var bufferSize = buffer.size || buffer.length || buffer.byteLength;
    blockCount = Math.ceil(bufferSize / blockSize);

    for (var i = 0; i < blockCount; i++) {
        sha1String.push(shA1(buffer.slice(i * blockSize, (i + 1) * blockSize)));
    }
    function concatArr2Uint8(s) {//Array 2 Uint8Array
        var tmp = [];
        for (var i of s) tmp = tmp.concat(i);
        return new Uint8Array(tmp);
    }
    function Uint8ToBase64(u8Arr, urisafe) {//Uint8Array 2 Base64
        var CHUNK_SIZE = 0x8000; //arbitrary number
        var index = 0;
        var length = u8Arr.length;
        var result = '';
        var slice;
        while (index < length) {
            slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
            result += String.fromCharCode.apply(null, slice);
            index += CHUNK_SIZE;
        }
        return urisafe ? btoa(result).replace(/\//g, '_').replace(/\+/g, '-') : btoa(result);
    }
    function calcEtag() {
        if (!sha1String.length) return 'Fto5o-5ea0sNMlW_75VgGJCv2AcJ';
        var sha1Buffer = concatArr2Uint8(sha1String);
        // 如果大于4M,则对各个块的sha1结果再次sha1
        if (blockCount > 1) {
            prefix = 0x96;
            sha1Buffer = shA1(sha1Buffer.buffer);
        } else {
            sha1Buffer = Array.apply([], sha1Buffer);
        }
        sha1Buffer = concatArr2Uint8([[prefix], sha1Buffer]);
        return Uint8ToBase64(sha1Buffer, true);
    }
    return (calcEtag());
}

4.使用示例

  • demo.html
<!-- https://github.com/emn178/js-sha1 -->
<script src="./sha1.js"></script> 
<script src="./qetag.js"></script>
<script>
    function fload(input) {
        var fs = input.files;
        if (fs.length) {
            var f = fs[0];
            var reader = new FileReader(); 
            reader.onload = function () { 
                document.getElementById("etagDemo").innerHTML = getEtag(this.result);
            }
            reader.readAsArrayBuffer(f);
        }
    }
</script>
<input id="f" type="file" onchange="fload(this)" />
<div id="etagDemo"></div>
效果
点击查看在线效果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 155,541评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 65,987评论 1 285
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,387评论 0 236
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,325评论 0 201
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,657评论 3 284
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,125评论 1 202
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,519评论 2 305
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,242评论 0 193
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,863评论 1 234
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,198评论 2 239
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,747评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,106评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,635评论 3 227
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,926评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,631评论 0 190
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,131评论 2 261
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,083评论 2 257

推荐阅读更多精彩内容