Firebase中通过哈希校验避免重复上传并获取存储文件的问题
问题解决方案
核心问题与修复步骤
1. 异步流程错误:哈希未生成就执行校验
FileReader是异步操作,你当前的代码会在hashKey还未生成时就发起Firestore查询,导致用空字符串匹配,自然无法找到对应记录。需要把哈希生成逻辑封装为Promise,确保后续校验逻辑等待哈希生成完成。
修复后的哈希生成代码:
async function generateFileHash(file) { return new Promise((resolve, reject) => { const fr = new FileReader(); // 读取二进制数组,避免图片文件的文本编码解析错误 fr.readAsArrayBuffer(file); fr.onload = async () => { // 将ArrayBuffer转为Uint8Array后计算哈希 const hashBuffer = await sha512(new Uint8Array(fr.result)); // 转为十六进制字符串,保证存储和比对的一致性 const hashKey = Array.from(new Uint8Array(hashBuffer)) .map(b => b.toString(16).padStart(2, '0')) .join(''); resolve(hashKey); }; fr.onerror = reject; }); }
2. 图片读取方式错误:误用文本读取二进制文件
readAsText会破坏图片的二进制数据结构,导致生成的哈希与文件实际内容不匹配。必须用readAsArrayBuffer读取原始二进制数据,才能生成准确的文件哈希。
3. Firestore查询错误:集合查询误用getDoc
query用于集合查询,需要搭配getDocs而非getDoc(getDoc仅用于单个文档查询)。修复后的校验与复用逻辑:
// 先生成文件哈希 const hashKey = await generateFileHash(image); const imagesCollection = collection(db, "images"); const existingImageQuery = query(imagesCollection, where('hashKey', '==', hashKey)); const existingImageSnap = await getDocs(existingImageQuery); if (!existingImageSnap.empty) { // 哈希已存在,获取存储的文件名并生成下载URL const targetDoc = existingImageSnap.docs[0]; const storedFileName = targetDoc.data().name; const imgRef = ref(storage, `images/${storedFileName}`); const downloadUrl = await getDownloadURL(imgRef); url = downloadUrl; console.log("文件已存在,复用URL:", downloadUrl); } else { // 哈希不存在,执行上传与记录存储 await addDoc(imagesCollection, { name: image.name, hashKey: hashKey, }); const imgRef = ref(storage, `images/${image.name}`); const snap = await uploadBytes(imgRef, image); const downloadUrl = await getDownloadURL(ref(storage, snap.ref.fullPath)); url = downloadUrl; console.log("文件已上传,新URL:", downloadUrl); }
4. 哈希格式一致性
确保哈希生成后统一转为十六进制字符串存储,避免因格式差异导致比对失败。
额外优化建议
- 为Firestore的
hashKey字段创建唯一索引,既防止重复存储相同哈希的文档,也能提升查询性能。 - 若存在文件名重复风险,建议直接用哈希值作为Storage的文件路径(如
images/${hashKey}),无需存储文件名,直接通过哈希定位文件,避免冲突。
内容的提问来源于stack exchange,提问作者Idris
相关产品推荐
相关产品推荐

