自动化交付

文件同名不代表同一份:用内容哈希识别输入

客户先后传来两份都叫 `final.pdf` 的文件,哪一份做了翻译?靠文件名回答这个问题很危险。同名文件可以内容不同,改过名字的文件也可能字节完全相同。内容哈希适合建立版本对应关系,但不应该被赋予它没有的含义。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 客户先后传来两份都叫 final.pdf 的文件,哪一份做了翻译?靠文件名回答这个问题很危险。同名文件可以内容不同,改过名字的文件也可能字节完全相同。内容哈希适合建立版本对应关系,但不应该被赋予它没有的含义。 为输入留下字节指纹 SHA-256 根据文件字节计算摘要。大文件可以分块读取,不必一次装入内存。哈希结果与分块大小无关;分块只是读取策略,不是文件身份的一部分。下面的内存流是合成样本,不包含真实文档。 用同内容、不同切块验证实现 import hashlib from io import BytesIO def digest(stream, chunk_size=65536): if type(chunk_size) is not int or chunk_size <= 0: raise ValueError("positive chunk size required") h = hashlib.sha256() while block := stream.read(chunk_size): h.update(block) return h.hexdigest() raw = b"synthetic document version A\n" a = digest(BytesIO(raw), 3) assert a == digest(BytesIO(raw), 4096) assert a == hashlib.sha256(raw).hexdigest() assert a != digest(BytesIO(raw.replace(b"A", b"B"))) assert digest(BytesIO(b"")) == hashlib.sha256(b"").hexdigest() try: digest(BytesIO(raw), 0) except ValueError: pass else: raise AssertionError("zero chunk must be rejected") 函数从当前流位置读取。正式调用前应明确流是否位于开头,不能在解析器已经读过一部分后直接计算并称为“整份文件哈希”。最稳妥的方式是单独打开只读输入,或在可定位流上明确复位。 字节相同与内容相同有区别 情况 哈希可能怎样变化 如何解释 只改文件名 不变 名称不参与字节摘要 PDF 元数据被重写 改变 页面看起来相同也可能不同 重新保存压缩包 改变 压缩参数或时间戳可影响字节 翻译成另一语言 通常改变 输入输出应该分别记录 不要把哈希当权限凭证 知道一个文件摘要不代表有权下载它,也不代表拥有版权。跨租户去重还可能泄露“某个文件是否已存在”的信息。缓存命中之后仍需做访问控制,并把来源、租户和处理策略纳入业务记录。 翻译清单从这里开始 将文件送入 OmniDoc(产品入口) 前记录输入摘要,下载译稿后记录输出摘要,再关联语言与术语版本。这样同事说“结果不对”时,首先就能确定双方检查的是同一组文件,而不必围着多个 final 文件名猜测。 参考: Python hashlib 。 - 1 -
更新于 2026-10-08