Cloud Vision OCR词数统计异常及Firestore重复写入问题咨询
问题分析与解决方案
核心问题原因
1. Firestore 多次写入
Firebase Storage 的 object.finalize 触发器可能因重试机制、元数据更新或重复事件,导致云函数针对同一 JSON 文件多次执行。代码中未添加幂等性校验,每次触发都会向 Firestore 写入数据。
2. 词数统计结果不一致
- 文本拼接错误:使用
readyArray.join()默认以逗号拼接各页文本,会导致跨页面的单词被合并(如“page1end,page2start”会被视为单个单词),干扰词数统计。 - 潜在的文件未完全写入:若云函数在 JSON 文件未完全上传至 Storage 时触发,下载到的内容不完整,导致文本提取和词数统计结果不稳定。
- 冗余异步包装:在
map中使用不必要的async函数处理同步数据,虽不直接引发问题,但可能导致逻辑混乱。 - 词数统计逻辑局限性:仅通过
/\s+/分割文本,未处理标点符号与单词粘连的情况(如“hello,”会被计为一个单词),若 OCR 输出标点格式不稳定,也会影响统计结果。
解决方案
1. 确保 Firestore 单次写入(幂等性处理)
添加幂等校验,仅当 Firestore 文档未存在有效数据时才执行写入:
// 写入前检查文档状态 const docSnapshot = await docRef.get(); if (!docSnapshot.exists || !docSnapshot.data().wordCount) { await docRef.set({ fullText: fullTextReady, textConfidence: textConfidence3, type: "application/pdf", pageCount: jsObject.responses.length, languages: languages, fileName: fileName, location: jsonLocation, wordCount: words }, { merge: true }); }
或通过 Storage 文件元数据标记已处理状态,避免重复执行:
const file = bucket.file(filePath); const [metadata] = await file.getMetadata(); if (metadata.metadata?.processed === 'true') { return; // 已处理过,直接终止函数 } // 处理完成后标记元数据 await file.setMetadata({ metadata: { processed: 'true' } });
2. 修正词数统计逻辑
- 正确拼接文本:用空格拼接各页文本,保留单词边界:
const fullTextReady = readyArray.join(' '); - 移除冗余异步代码:处理同步数据时无需
async包装,并添加空值 fallback:const textArray = jsObject.responses.map(response => { return response.fullTextAnnotation.text || ''; }); const readyArray = textArray; // 无需Promise.all,直接使用 - 优化词数统计函数:处理空文本情况,可选添加标点清理逻辑:
function countWords(str) { if (!str.trim()) return 0; // 可选:移除标点后再统计(根据需求调整) const cleanedStr = str.replace(/[^\w\s]/g, ''); return cleanedStr.trim().split(/\s+/).length; } const words = countWords(fullTextReady); // 无需await,直接调用 - 确保文件完整下载:添加错误捕获,避免解析不完整的 JSON:
let jsObject; try { const downloadResponse = await bucket.file(filePath).download(); const bufferToJson = downloadResponse.toString(); jsObject = JSON.parse(bufferToJson); } catch (err) { console.error('文件下载或解析失败:', err); return; // 终止执行,避免无效写入 }
3. 其他代码优化
移除不必要的 Promise.all 调用(处理同步数组时无需):
// 原代码中pages是同步数组,无需Promise.all const pages = jsObject.responses.map(response => { return response.fullTextAnnotation.pages.map(page => { return page.property.detectedLanguages; }); }); const detectedLanguages = pages.flat(2); // languages数组是同步处理的,无需Promise.all const languages = detectedLanguages.map(language => { const langCode = language.languageCode; const confidence = Number((language.confidence).toFixed(1)) * 100; return { languageCode: langCode, languageConfidence: confidence }; });
内容的提问来源于stack exchange,提问作者DonBergen
相关产品推荐
相关产品推荐

