实现腾讯通用票据OCR安全适配(07-28-travel-reimbursement-guide)

这个提交包含在:
2026-07-28 22:02:11 +08:00
父节点 b4bd484260
当前提交 ac17092fa0
共修改 19 个文件,包含 1277 行新增和 127 行删除
+39 -12
查看文件
@@ -1,6 +1,11 @@
const cron = require("node-cron");
const { buildMissingRequiredOcrError, runOcrForInvoice } = require("../services/ocr");
const {
buildMissingRequiredOcrError,
runOcrForInvoice,
sanitizeOcrResultForPersistence,
shouldDeduplicateByInvoiceNumberOnly,
} = require("../services/ocr");
const { publishInvoiceUpdated } = require("../services/events");
const { resolveInvoiceDedupBlocker } = require("../services/invoiceGovernance");
const { readFileBuffer } = require("../services/storage");
@@ -117,18 +122,29 @@ async function getOrCreateOcrResult(prisma, config, claimedInvoice, imageBase64)
});
if (cached) {
const normalized = sanitizeOcrResultForPersistence(ocrProvider, cached.result_json);
if (ocrProvider === "tencent") {
await prisma.ocrResult.update({
where: { id: cached.id },
data: { result_json: normalized },
});
}
return {
ocrResultId: cached.id,
normalized: cached.result_json,
normalized,
};
}
const normalized = await runOcrForInvoice({
config,
ocrType: claimedInvoice.ocr_type,
imageBase64,
sourceMimeType: claimedInvoice.mime_type,
});
const normalized = sanitizeOcrResultForPersistence(
ocrProvider,
await runOcrForInvoice({
config,
ocrType: claimedInvoice.ocr_type,
imageBase64,
sourceMimeType: claimedInvoice.mime_type,
}),
);
try {
const created = await prisma.ocrResult.create({
@@ -164,9 +180,17 @@ async function getOrCreateOcrResult(prisma, config, claimedInvoice, imageBase64)
throw error;
}
const racedNormalized = sanitizeOcrResultForPersistence(ocrProvider, raced.result_json);
if (ocrProvider === "tencent") {
await prisma.ocrResult.update({
where: { id: raced.id },
data: { result_json: racedNormalized },
});
}
return {
ocrResultId: raced.id,
normalized: raced.result_json,
normalized: racedNormalized,
};
}
}
@@ -246,10 +270,10 @@ async function markInvoiceAsQualityFailed(prisma, config, invoiceId, normalized,
});
}
async function markInvoiceAsFailed(prisma, claimedInvoice, errorMessage, config) {
async function markInvoiceAsFailed(prisma, claimedInvoice, errorMessage, config, { terminal = false } = {}) {
const now = new Date();
const nextAttempts = Number(claimedInvoice.ocr_attempts || 0) + 1;
const isTerminal = nextAttempts >= config.ocrMaxAttempts;
const isTerminal = terminal || nextAttempts >= config.ocrMaxAttempts;
const nextStatus = isTerminal ? "ocr_failed" : "pending_ocr";
await prisma.invoicePool.update({
@@ -313,6 +337,7 @@ async function processClaimedInvoice(prisma, config, claimedInvoice, workerId, l
fileSha256: claimedInvoice.file_sha256,
invoiceCode: normalized.invoiceCode,
invoiceNumber: normalized.invoiceNumber,
deduplicateByInvoiceNumberOnly: shouldDeduplicateByInvoiceNumberOnly(normalized),
ownerEmployeeId: claimedInvoice.owner_employee_id,
},
);
@@ -336,7 +361,9 @@ async function processClaimedInvoice(prisma, config, claimedInvoice, workerId, l
return { outcome: "available", status: "available" };
} catch (error) {
const message = trimErrorMessage(error);
const status = await markInvoiceAsFailed(prisma, claimedInvoice, message, config);
const status = await markInvoiceAsFailed(prisma, claimedInvoice, message, config, {
terminal: error?.terminal === true,
});
logger.error(
{
err: error,