不能查 files.md5 判断文件是否存在,因其常为空、base64 编码或仅旧版驱动生成,且 mongodb 4.4+ 已弃用;必须本地流式计算十六进制小写 md5 并存入 metadata.file_md5 建索引。

直接用 files.md5 字段做秒传不可靠,它常为空、是 Base64 编码、或仅由旧版驱动生成,且 MongoDB 4.4+ 已弃用;必须在上传前本地计算文件 MD5(十六进制小写),显式存入 metadata.file_md5 并建索引。
为什么不能查 files.md5 判断是否已存在
这个字段不是自动计算的:PyMongo 和 Node.js 官方驱动默认不填,除非你手动传 md5 选项(且仅部分版本支持)。更常见的是字段为 null、空字符串,或存了 Base64 编码的二进制哈希(比如 "MTIz"),而你的查询值是十六进制字符串(比如 "d41d8cd98f00b204e9800998ecf8427e"),类型和编码都不匹配,必然查不到。
即使查到了,也不能保证是同一文件——因为 files.md5 在某些驱动里只是拼接 chunk 后算的弱校验,不等价于原始文件完整 MD5。
如何安全计算并存入 MD5(以 PyMongo 为例)
核心是流式读取、边读边算,避免大文件 OOM:
- 用
hashlib.md5()初始化哈希器,每次fileobj.read(8192)分块更新,最后.hexdigest()得十六进制小写字符串 - 上传时把结果塞进
metadata,例如metadata={"file_md5": "d41d8cd98f00b204e9800998ecf8427e"} - 不要用
open(...).read()一次性加载整个文件,GB 级别会直接内存溢出 - 注意:Node.js 中若用
crypto.createHash('md5'),也要配stream.pipe()或分块update(),不能fs.readFileSync().toString()
如何快速查询并复用已有文件
查的是 fs.files 集合,不是 fs.chunks,且必须提前建索引:
- 执行
db.fs.files.createIndex({ "metadata.file_md5": 1 }),否则每次find()都是全表扫描 - 查询语句必须路径精确:
{"metadata.file_md5": "d41d8cd98f00b204e9800998ecf8427e"},不能写成{"file_md5": ...}或{"metadata.md5": ...} - 用
fs.find().limit(1).next()获取首个匹配项,比count_documents()更快(能利用索引提前终止) - 命中后拿到
existing["_id"],业务层可直接返回该 ID 或生成访问链接,跳过上传
上传流程中三个高频漏点
很多团队写了哈希逻辑却仍重复存文件,问题往往卡在这些细节:
- 前端上传请求没带
file_md5字段,或字段名和服务端约定不一致(比如前端传hash,后端却查metadata.file_md5) - 文件流被消费一次后无法重用:先用 stream 算了 MD5,再传给
upload_from_stream()时 stream 已 EOF —— 正确做法是用io.BytesIO缓存或stream.seek(0)(仅限 seekable 流) - 没处理唯一索引冲突:虽然 MD5 碰撞概率极低,但万一两个不同文件哈希相同,
createIndex({ "metadata.file_md5": 1 }, { unique: true })会报E11000 duplicate key,需捕获异常并 fallback 到完整内容比对(极少触发,但逻辑必须存在)
真正决定秒传成败的,从来不是哈希算法本身,而是上传前那一次流式计算是否可靠、索引是否建对、查询路径是否精确、以及流是否还能再用一次——这些地方错一个,就退回“全量上传”模式。











