feat: Webhook-Warteschlange für sequenzielle, deduplizierte Verarbeitung
Build and Push Multi-Platform Images / build-and-push (push) Successful in 37s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 37s
Der Webhook reiht eingehende Dokument-IDs nur noch in eine Warteschlange ein und antwortet sofort (status "queued"). Ein separater Intervall-Prozess (WebhookQueueService) arbeitet die IDs nacheinander ab – ohne Überschneidung, falls ein Dokument mehrfach kurz hintereinander gespeichert wird: - Jede ID kommt nur einmal in der Liste vor (Dedup). - Beim Verarbeitungsstart wird die ID sofort entfernt; ein erneutes Feuern während der Verarbeitung reiht sie wieder ein (ein weiterer Lauf folgt). - Es läuft immer nur eine Verarbeitung gleichzeitig (isProcessing-Guard). - Prüfintervall sehr kurz (WEBHOOK_QUEUE_INTERVAL_MS, Default 1000 ms). Status-Recording (last_webhook_call) + GET /api/webhook/status wandern in den Queue-Service und liefern zusätzlich die aktuelle Warteschlangen-Größe. Frontend-Tab zeigt Status "In Warteschlange" und die Queue-Größe an. Unit-Test deckt Dedup, Remove-on-start, Re-Enqueue und No-Parallel ab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,13 +4,14 @@ export interface WebhookLastCall {
|
||||
at: string; // ISO-Zeitstempel
|
||||
documentId: number | null;
|
||||
action: string | null;
|
||||
status: 'processed' | 'skipped' | 'error' | 'bad-request' | string;
|
||||
status: 'queued' | 'processed' | 'error' | 'bad-request' | string;
|
||||
reason?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface WebhookStatus {
|
||||
lastCall: WebhookLastCall | null;
|
||||
queueSize?: number;
|
||||
}
|
||||
|
||||
export const webhookApi = {
|
||||
|
||||
@@ -2774,8 +2774,8 @@ function WebhookStatusTab() {
|
||||
|
||||
const renderStatusBadge = (s: string) => {
|
||||
switch (s) {
|
||||
case 'queued': return <Badge status="processing" text="In Warteschlange" />;
|
||||
case 'processed': return <Badge status="success" text="Verarbeitet" />;
|
||||
case 'skipped': return <Badge status="warning" text="Übersprungen" />;
|
||||
case 'error': return <Badge status="error" text="Fehler" />;
|
||||
case 'bad-request': return <Badge status="error" text="Ungültige Anfrage" />;
|
||||
default: return <Badge status="default" text={s} />;
|
||||
@@ -2783,6 +2783,7 @@ function WebhookStatusTab() {
|
||||
};
|
||||
|
||||
const lastCall = status?.lastCall ?? null;
|
||||
const queueSize = status?.queueSize ?? 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -2791,13 +2792,19 @@ function WebhookStatusTab() {
|
||||
<Typography.Paragraph type="secondary">
|
||||
Paperless-NGX ruft nach dem Bearbeiten eines Dokuments den Webhook{' '}
|
||||
<Typography.Text code>/api/webhook/paperless</Typography.Text> auf
|
||||
(per API-Key authentifiziert). Hier siehst du den zuletzt
|
||||
verarbeiteten Aufruf. Eine vollständige Historie steht in den
|
||||
Backend-Logs.
|
||||
(per API-Key authentifiziert). Die ID wird in eine Warteschlange
|
||||
gelegt und von einem separaten Prozess nacheinander verarbeitet.
|
||||
Hier siehst du den zuletzt festgehaltenen Vorgang. Eine vollständige
|
||||
Historie steht in den Backend-Logs.
|
||||
</Typography.Paragraph>
|
||||
<Button icon={<ReloadOutlined />} loading={loading} onClick={load}>
|
||||
Aktualisieren
|
||||
</Button>
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} loading={loading} onClick={load}>
|
||||
Aktualisieren
|
||||
</Button>
|
||||
<Tag color={queueSize > 0 ? 'processing' : 'default'}>
|
||||
Warteschlange: {queueSize}
|
||||
</Tag>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Card size="small" title="Letzter Webhook-Aufruf" loading={loading}>
|
||||
|
||||
Reference in New Issue
Block a user