fix(mail): Duplikate auch bei neu erzeugten PDFs erkennen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 45s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 45s
Absender erzeugen PDFs beim erneuten Versand oft neu (andere Erstellungszeit/Dokument-ID), dadurch unterscheidet sich die Checksumme und die Duplikatprüfung griff nicht (z.B. Mail 7071 vs. 7072). - Neuer Text-Fingerprint (SHA-256 über normalisierten PDF-Text via pdf-parse) in Attachments.ContentFingerprint, inkl. Migration und Index - Wird beim Mail-Abruf berechnet; Altbestand füllt ein Hintergrund- Backfill beim Start auf, fehlende Werte werden bei Bedarf nachberechnet - Duplikatprüfung und Import-Sperre berücksichtigen Anhänge mit gleichem Inhalt, die importiert sind oder gerade importiert werden - Jest braucht für pdf-parse --experimental-vm-modules (test-Skripte) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
534b313054
commit
797cbb9b71
12 files changed
+746
-7
No files matched your search
@@ -0,0 +1,37 @@
|
||||
import * as crypto from 'crypto';
|
||||
import { PDFParse } from 'pdf-parse';
|
||||
|
||||
/**
|
||||
* Unterhalb dieser Textlänge gilt ein PDF als "ohne Textebene" (z.B. Scan) –
|
||||
* ein paar Zeichen reichen nicht, um Dokumente sicher zu unterscheiden.
|
||||
*/
|
||||
const MIN_TEXT_LENGTH = 20;
|
||||
|
||||
/**
|
||||
* Inhaltsbasierter Fingerprint eines PDFs: SHA-256 über den extrahierten,
|
||||
* whitespace-normalisierten Text. Anders als die Datei-Checksumme bleibt er
|
||||
* gleich, wenn ein Absender dasselbe Dokument neu erzeugt (andere
|
||||
* Erstellungszeit, Dokument-ID o.ä.).
|
||||
*
|
||||
* @returns Fingerprint (64 Hex-Zeichen), `''` bei PDFs ohne verwertbaren Text
|
||||
* (bereits berechnet, aber nicht vergleichbar) oder `null`, wenn das
|
||||
* PDF nicht gelesen werden konnte.
|
||||
*/
|
||||
export async function computeContentFingerprint(
|
||||
pdf: Buffer,
|
||||
): Promise<string | null> {
|
||||
// pdf.js übernimmt den Puffer, deshalb eine Kopie übergeben
|
||||
const parser = new PDFParse({ data: new Uint8Array(pdf) });
|
||||
try {
|
||||
const { text } = await parser.getText();
|
||||
const normalized = text.replace(/\s+/g, ' ').trim();
|
||||
// Seitenmarker ("-- 1 of 1 --") zählen nicht als Inhalt
|
||||
const inhalt = normalized.replace(/-- \d+ of \d+ --/g, '').trim();
|
||||
if (inhalt.length < MIN_TEXT_LENGTH) return '';
|
||||
return crypto.createHash('sha256').update(normalized).digest('hex');
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
await parser.destroy().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user