6.2 KiB
Architektur — Dateieingang Service
Überblick
Der Service empfängt einen HTTP-Trigger von der APEX Automation, verbindet sich mit einem
SFTP-Server, lädt neue ZIP-Dateien herunter, entpackt sie und lädt den Inhalt in OCI Object
Storage hoch. Anschließend wird der ORDS-Endpunkt pck_auto_import.p_process_incoming_ba_data
aufgerufen, der die DB-Verarbeitung anstößt. Das Scheduling bleibt bei APEX — der Service
selbst hat keinen eigenen Scheduler.
Datenfluss
APEX Automation (stündlich)
│
▼ HTTP POST /api/process-incoming (Header: X-Api-Key)
FileProcessingResource
│ 202 Accepted (sofort)
▼
FileProcessingPipeline [ManagedExecutor — Hintergrund-Thread]
│
│ für jede *.zip auf dem SFTP-Server:
│
├─→ SftpService.listZipFiles() [SSHJ]
├─→ SftpService.download() [SSHJ]
│
├─→ ZipExtractionService.extract() [Apache Commons Compress]
│ └─ ProcessingContext mit List<FileEntry>
│
├─→ OciUploadService.upload() [OCI SDK]
│ └─ Dateien in eingang/<zip-name>/ + Marker
│
├─→ SftpService.deleteRemote() [SSHJ]
│ └─ ZIP gelöscht (Erfolg) oder .error (Fehler)
│
└─→ Cleanup: lokale Dateien löschen [immer, im finally]
│
│ nach allen ZIPs (einmalig):
│
└─→ OrdsNotificationService.notify() [MicroProfile REST Client]
└─ POST pck_auto_import.p_process_incoming_ba_data
│
▼
Oracle DB (pck_auto_import verarbeitet alle eingang/-Unterordner)
Pipeline-Steps
| Step-Name | Klasse | Paket | Bibliothek |
|---|---|---|---|
api-trigger |
FileProcessingResource |
api |
Quarkus REST |
sftp-list |
SftpService |
sftp |
SSHJ |
sftp-download |
SftpService |
sftp |
SSHJ |
zip-extract |
ZipExtractionService |
zip |
Apache Commons Compress |
oci-upload |
OciUploadService |
oci |
OCI Java SDK |
sftp-rename |
SftpService |
sftp |
SSHJ |
ords-notify |
OrdsNotificationService |
ords |
MicroProfile REST Client |
cleanup |
FileProcessingPipeline |
pipeline |
pure Java |
Orchestrierung
FileProcessingPipeline ist der einzige Orchestrierer — kein Framework-Routing.
Die Pipeline läuft in einem ManagedExecutor-Thread (Quarkus Context Propagation),
damit MDC-Kontext und CDI-Scope im Hintergrund-Thread verfügbar bleiben.
FileProcessingResource [REST-Thread]
│ 202 sofort
▼
FileProcessingPipeline [ManagedExecutor-Thread]
│ AtomicBoolean verhindert Doppelläufe
│
└─ processAll()
└─ für jede ZIP: try { ... } finally { cleanup(); MDC.clear(); }
Der AtomicBoolean isRunning-Guard stellt sicher, dass ein zweiter APEX-Trigger während
eines laufenden Durchgangs mit 409 Conflict abgewiesen wird, statt einen zweiten Lauf zu starten.
Konfiguration (application.properties)
# API-Absicherung
galabau.api.key=${GALABAU_API_KEY}
# SFTP
galabau.sftp.host=sftp.lieferant.de
galabau.sftp.port=22
galabau.sftp.username=sftp_user
galabau.sftp.password=${GALABAU_SFTP_PASSWORD}
galabau.sftp.host-key-fingerprint=SHA256:... # ssh-keyscan host | ssh-keygen -lf -
galabau.sftp.remote-path=/outgoing
galabau.sftp.local-work-dir=/tmp/sftp-work
# galabau.sftp.private-key-path=/etc/secrets/sftp-key
# galabau.sftp.private-key-passphrase=${SFTP_KEY_PASSPHRASE}
# OCI Object Storage
galabau.oci.namespace=mycompany
galabau.oci.region=eu-frankfurt-1
galabau.oci.bucket=my-bucket
galabau.oci.tenant-prefix=mandant_42/
galabau.oci.incoming-prefix=eingang/
galabau.oci.tenancy-id=${OCI_TENANCY_ID}
galabau.oci.user-id=${OCI_USER_ID}
galabau.oci.fingerprint=${OCI_FINGERPRINT}
galabau.oci.private-key-path=${OCI_PRIVATE_KEY_PATH}
# ORDS
galabau.ords.base-url=http://ords:8080
galabau.ords.process-incoming-path=/ords/.../net_storage/process_incoming_ba_data
galabau.ords.api-key=${GALABAU_ORDS_API_KEY}
quarkus.rest-client.ords-client.url=${galabau.ords.base-url}
# Retry
galabau.retry.max-attempts=3
galabau.retry.backoff-ms=1000
Fehlerbehandlung
| Fehler | Typ | Handling |
|---|---|---|
| SFTP-Verbindung fehlgeschlagen | transient | Pipeline bricht ab, nächster APEX-Lauf (1h) |
| ZIP beschädigt | persistent | SFTP-Rename zu .error, Log, weiter mit nächster ZIP |
| OCI 503 | transient | @Retry (3x mit Backoff) |
| OCI 403 Auth Failed | persistent | SFTP-Rename zu .error, Log — Credentials prüfen! |
| ORDS-Aufruf fehlgeschlagen | transient | Marker liegt vor, APEX findet ihn nächsten Lauf |
OCI-Authentifizierung
SimpleAuthenticationDetailsProvider mit Credentials aus Umgebungsvariablen (skalare Werte)
und gemounteter PEM-Datei (Private Key via Kubernetes Secret Volume).
SimpleAuthenticationDetailsProvider auth =
SimpleAuthenticationDetailsProvider.builder()
.tenantId(config.tenancyId())
.userId(config.userId())
.fingerprint(config.fingerprint())
.region(Region.fromRegionId(config.region()))
.privateKeySupplier(new FilePrivateKeySupplier(config.privateKeyPath()))
.build();
Datenmodell
ProcessingContext
| Feld | Typ | Beschreibung |
|---|---|---|
runId |
UUID |
Eindeutige ID dieses Verarbeitungslaufs (MDC-Kontext) |
zipFilename |
String |
z.B. export_2026-04-08.zip |
zipNameWithoutExt |
String |
z.B. export_2026-04-08 |
localZipPath |
Path |
Lokale ZIP-Datei (für Cleanup) |
localExtractDir |
Path |
Lokales Entpack-Verzeichnis (für Cleanup) |
extractedFiles |
List<FileEntry> |
Liste der entpackten Dateien |
markerUploaded |
boolean |
Marker in OCI hochgeladen? |
startTime |
LocalDateTime |
Startzeitpunkt |
status |
ProcessingStatus |
PENDING / PARTIALLY_UPLOADED / MARKER_UPLOADED / ORDS_NOTIFIED / FAILED |
FileEntry
| Feld | Typ | Beschreibung |
|---|---|---|
relativePath |
String |
z.B. subdir/file.csv |
ociKey |
String |
z.B. eingang/export_2026-04-08/subdir/file.csv |
fileSize |
long |
Dateigröße in Bytes |
isMarker |
boolean |
true nur für _READY_FOR_DB_PROCESSING_ |