Nodejs中文件夹的哈希值

3
在我的项目中,我想计算文件夹的哈希值。例如,有10个文件夹,这些文件夹有许多子文件。我知道很多方法可以获取文件的哈希值,但是否有办法获取每个文件夹的哈希值呢?
我这样做的目的是了解文件夹中的文件是否已更改。
我乐意听取建议和不同的想法,需要您的帮助。提前致谢。

1
你是在问如何迭代这些文件夹并获取每个文件的哈希值吗? - Andy
不,我只想从文件夹中的所有文件中获取一个哈希码。我只有一个文件夹,你可以把它看作是一个React项目。这个文件夹应该有一个哈希码,我需要知道其中的任何文件是否已更改。 难道没有一种方法可以在不对每个文件进行单独哈希的情况下完成这个任务吗? - rider
1
这个有帮助吗?(https://dev59.com/kmvXa4cB1Zd3GeqPKpi6) - Andy
我会调查一下,非常感谢。但如果我做不到,我还能再次向您寻求帮助吗? :) - rider
1
如果您想对文件的实际内容进行哈希,那么没有办法可以在不读取每个文件的每个字节并为每个文件或文件夹计算哈希值的情况下完成。我猜您可以仅对文件名和文件大小进行哈希,并希望这足以检测到更改,但显然,长度相同但内容不同(例如更改文件中的一个字符)的文件将无法以此方式被检测到。然后,您可以添加每个文件的修改日期,可能会捕获更多的更改。 - jfriend00
1
一个简单的执行 find -type f -exec md5sum "{}" + 将会返回每个文件路径的哈希值,而 find -type f -exec md5sum "{}" + | md5sum | cut -c 1-32 则会哈希整个路径。此外,如果你想要获取项目提交状态的哈希值,请获取最近的提交哈希值 git rev-parse HEAD 和/或调用远程 git ls-remote origin -h refs/heads/master 并将其与本地 git rev-parse refs/heads/master 进行比较。 - Lawrence Cherone
2个回答

7
这取决于你希望修改检测的可靠程度。最可靠的方法是遍历每个文件夹中的每个文件,通过读取每个文件的每个字节,计算实际文件内容的哈希值。
此外,您还可以检查文件元数据,如文件名、修改日期、文件大小。这些任何一个的更改都表明文件内容发生了更改。但是,这些中任何一个没有更改并不能确定地表明文件内容没有发生更改。有可能修改文件内容,保持相同的文件名、相同的文件大小,并手动将修改日期设置回原来的日期,从而欺骗仅检查元数据的检查。
但是,如果您愿意接受它可能会被操纵,但通常可以检测变化,那么您可以迭代文件夹中的所有文件,并计算使用元数据的组合哈希:文件名、文件大小和文件修改日期,并为文件夹生成单个哈希。根据您的目的,这可能足够或不足够 - 您必须做出决定。
除此之外,您需要读取每个文件的每个字节,并计算实际文件内容的哈希值。
以下是元数据哈希算法的演示代码:
const fsp = require("fs/promises");
const { createHash } = require("crypto");
const path = require('path');

// -----------------------------------------------------
// Returns a buffer with a computed hash of all file's metadata:
//    full path, modification time and filesize
// If you pass inputHash, it must be a Hash object from the crypto library
//   and you must then call .digest() on it yourself when you're done
// If you don't pass inputHash, then one will be created automatically
//   and the digest will be returned to you in a Buffer object
// -----------------------------------------------------

async function computeMetaHash(folder, inputHash = null) {
    const hash = inputHash ? inputHash : createHash('sha256');
    const info = await fsp.readdir(folder, { withFileTypes: true });
    // construct a string from the modification date, the filename and the filesize
    for (let item of info) {
        const fullPath = path.join(folder, item.name);
        if (item.isFile()) {
            const statInfo = await fsp.stat(fullPath);
            // compute hash string name:size:mtime
            const fileInfo = `${fullPath}:${statInfo.size}:${statInfo.mtimeMs}`;
            hash.update(fileInfo);
        } else if (item.isDirectory()) {
            // recursively walk sub-folders
            await computeMetaHash(fullPath, hash);
        }
    }
    // if not being called recursively, get the digest and return it as the hash result
    if (!inputHash) {
        return hash.digest();
    }
}

computeMetaHash(__dirname).then(result => {
    console.log(result);
}).catch(err => {
    console.log(err);
});

1
@rider - 我在我的回答中添加了演示代码。 - jfriend00
1
@rider - 你不需要自己传递任何关于inputHash的内容。它会默认适当的值。该参数在递归到嵌套文件夹时内部使用。 - jfriend00
@rider - 你必须决定哪种算法适合你。仅仅通过文件大小很容易被欺骗。只需编辑一个文件并更改其中一个字符,文件大小就不会改变。 - jfriend00

0
基于 @jfriend00 的实现(谢谢!),此解决方案接受多个路径并且基于 TypeScript。
import { Hash, createHash } from "node:crypto";
import { readdirSync, statSync } from "node:fs";
import { join } from "node:path";

/**
 * Creates hash of given files/folders. Used to conditionally deploy custom
 * resources depending if source files have changed
 */
export function computeMetaHash(paths: string[], inputHash?: Hash) {
  const hash = inputHash ? inputHash : createHash("sha1");
  for (const path of paths) {
    const statInfo = statSync(path);
    if (statInfo.isDirectory()) {
      const directoryEntries = readdirSync(path, { withFileTypes: true });
      const fullPaths = directoryEntries.map((e) => join(path, e.name));
      // recursively walk sub-folders
      computeMetaHash(fullPaths, hash);
    } else {
      const statInfo = statSync(path);
      // compute hash string name:size:mtime
      const fileInfo = `${path}:${statInfo.size}:${statInfo.mtimeMs}`;
      hash.update(fileInfo);
    }
  }
  // if not being called recursively, get the digest and return it as the hash result
  if (!inputHash) {
    return hash.digest().toString("base64");
  }
  return;
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接