当前位置: 首页 > news >正文

前端计算文件 hash

需要计算 文件 hash 传给后端进行核验;
网上搜到了几种方案:

名称arrayBuffer支持追加支持npm地址
spark-md5支持https://www.npmjs.com/package/spark-md5
crypto-js/sha256支持crypto定义的wordArray类型否 https://www.npmjs.com/package/crypto-js
sha.js支持buffer对nodejs友好
js-sha256https://www.npmjs.com/package/js-sha256
sha3-jshttps://www.npmjs.com/package/js-sha3

小文件

如果是小文件,直接采用 js-sha256 即可,使用方法如下:

import { sha256 } from 'js-sha256';

const getHash = (blob: Blob) =>
    new Promise((resolve) => {
      const file = new FileReader();
      file.onload = (e: ProgressEvent<FileReader>) => {
        resolve(sha256(e.target?.result));
      };
      file.readAsArrayBuffer(blob);
    });

但是超过 1G 的大文件计算 hash 的话,把文件一次性读入内存,非常耗浏览器内存,当文件比较大时,容易导致浏览器崩溃,电脑可能会卡死,因此需要考虑到 分片 计算;

分片计算hash

spark-md5

在网上找到了 spark-md5 可以分片读取文件计算hash,最后 append 一下,代码如下:

document.getElementById('file').addEventListener('change', function () {
    var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
        file = this.files[0],
        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) {
        console.log('read chunk nr', currentChunk + 1, 'of', chunks);
        spark.append(e.target.result);                   // Append array buffer
        currentChunk++;

        if (currentChunk < chunks) {
            loadNext();
        } else {
            console.log('finished loading');
            console.info('computed hash', spark.end());  // Compute hash
        }
    };

    fileReader.onerror = function () {
        console.warn('oops, something went wrong.');
    };

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

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

但是计算出来的 hash 是不对的,网上搜了一下 说是 编码方式问题 云云,最后放弃治疗;

crypto-js

使用 crypto-js 最后发现 16G 文件计算出来大概需要 8min 左右时间,时间比较理想,电脑也没卡,试了几台 win 和 mac ,20G 以内的文件是没什么问题的。代码如下:

import CryptoJs from 'crypto-js';
import encHex from 'crypto-js/enc-hex';

// 计算hash
  const hashFile = (file) => {
    const { size = 0 } = file;
 
    /**
     * 使用指定的算法计算hash值
     */
    const hashFileInternal = (alog) => {
      // 指定块的大小,这里设置为 20MB,可以根据实际情况进行配置,超过 100MB 的分片可能会卡
      const chunkSize = 20 * 1024 * 1024;
      /**
       * 更新文件块的hash值
       */
      const hashBlob = (blob, index) => {
        // 格式化 fileList
        formatFile(file, fileType.uploading, index / size);
        return new Promise((resolve) => {
          const reader = new FileReader();
          reader.onload = ({ target }) => {
            const wordArray = CryptoJs.lib.WordArray.create(target.result);
            // CryptoJS update的方式,增量更新计算结果
            alog.update(wordArray);
            resolve();
          };
          reader.readAsArrayBuffer(blob);
        });
      };
      let promise = Promise.resolve();
      // 使用promise来串联hash计算的顺序。
      // 因为FileReader是在事件中处理文件内容的,必须要通过某种机制来保证update的顺序是文件正确的顺序
      for (let index = 0; index < size; index += chunkSize) {
        promise = promise.then(async () => {
          return hashBlob(file.slice(index, index + chunkSize), index);
        });
      }
      // 使用promise返回最终的计算结果
      return promise.then(() => encHex.stringify(alog.finalize()));
    };
    // 计算文件的sha256,MD5 计算:CryptoJs.algo.MD5.create()
    return hashFileInternal(CryptoJs.algo.SHA256.create());
  };

通过对文件进行切片和增量更新hash的方式对文件进行 hash 计算,可以避免浏览器out of memory错误
使用异步计算hash值,计算返回promise,在promise.then()中获取计算结果。

相关文章:

  • ChatGPT写作文章-快速使用ChatGPT不用注册方式
  • 2万字60道MySQL经典面试题总结(附答案)
  • Maven生命周期、mvn命令、Maven插件
  • 【ChatGPT】这是一篇ChatGPT写的关于Python的文章
  • Ubuntu之NVIDIA GeForce显卡驱动安装
  • 【华为OD机试 2023最新 】 通信误码(C++)
  • 为Activity的启动添加约束条件
  • 2022年河南省高等职业教育技能大赛云计算赛项竞赛方案
  • 时间序列教程 一、数据的三个组成部分
  • 微前端:angular 8版本以上使用qiankun
  • 1.2、shell编程
  • 基于WebSocket的网页聊天室
  • JAVA 注解机制
  • 做算法题的正确姿势(不断更新)
  • [3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • C# 免费离线人脸识别 2.0 Demo
  • ComponentOne 2017 V2版本正式发布
  • IndexedDB
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • nginx 负载服务器优化
  • Nodejs和JavaWeb协助开发
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • Vue2 SSR 的优化之旅
  • 闭包,sync使用细节
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 后端_ThinkPHP5
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 前端之React实战:创建跨平台的项目架构
  • 数据仓库的几种建模方法
  • 为视图添加丝滑的水波纹
  • 物联网链路协议
  • 项目实战-Api的解决方案
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • 优秀架构师必须掌握的架构思维
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • 回归生活:清理微信公众号
  • ​queue --- 一个同步的队列类​
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • #、%和$符号在OGNL表达式中经常出现
  • (0)Nginx 功能特性
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (python)数据结构---字典
  • (Redis使用系列) SpringBoot中Redis的RedisConfig 二
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (九十四)函数和二维数组
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (十六)Flask之蓝图
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑