feat(zns-import): Healthchecks optimiert und Konsul-Discovery erweitert

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-16 14:57:30 +02:00
parent f3d5651ab7
commit 29c35c524b
5 changed files with 41 additions and 24 deletions
@@ -19,10 +19,10 @@ import java.util.zip.ZipInputStream
* Domänenobjekte über die jeweiligen Repositories (Upsert-Logik). * Domänenobjekte über die jeweiligen Repositories (Upsert-Logik).
* *
* Die Verarbeitungsreihenfolge ist fix: * Die Verarbeitungsreihenfolge ist fix:
* 1. VEREIN01.DAT → Verein (via VereinRepository) * 1. VEREIN01.DAT → Verein (via VereinRepository)
* 2. LIZENZ01.DAT → Reiter (via ReiterRepository) * 2. LIZENZ01.DAT → Reiter (via ReiterRepository)
* 3. PFERDE01.DAT → Pferd (via HorseRepository) * 3. PFERDE01.DAT → Pferd (via HorseRepository)
* 4. RICHT01.DAT → Funktionaer (via FunktionaerRepository) * 4. RICHT01.DAT → Funktionär (via FunktionaerRepository)
* *
* Dieser Service hat **keine** Spring-Abhängigkeit und kann daher sowohl * Dieser Service hat **keine** Spring-Abhängigkeit und kann daher sowohl
* im Backend (REST-Upload) als auch in der Compose Desktop App (Offline-Import) * im Backend (REST-Upload) als auch in der Compose Desktop App (Offline-Import)
@@ -55,6 +55,7 @@ class ZnsImportService(
/** /**
* Extrahiert die relevanten Dateien aus dem ZIP-Archiv. * Extrahiert die relevanten Dateien aus dem ZIP-Archiv.
* Optimiert: Nutzt BufferedReader für zeilenweises Einlesen, ohne das gesamte File in den RAM zu laden.
*/ */
fun extrahiereDateien(zipInputStream: InputStream): Map<String, List<String>> { fun extrahiereDateien(zipInputStream: InputStream): Map<String, List<String>> {
val dateien = mutableMapOf<String, List<String>>() val dateien = mutableMapOf<String, List<String>>()
@@ -65,24 +66,25 @@ class ZnsImportService(
val fileName = entry.name.uppercase().substringAfterLast("/") val fileName = entry.name.uppercase().substringAfterLast("/")
if (fileName in setOf(FILE_VEREIN, FILE_LIZENZ, FILE_PFERDE, FILE_RICHT)) { if (fileName in setOf(FILE_VEREIN, FILE_LIZENZ, FILE_PFERDE, FILE_RICHT)) {
val outputStream = java.io.ByteArrayOutputStream() // Wir lesen den Stream direkt zeilenweise mit dem korrekten Encoding
val buffer = ByteArray(4096) val reader = zip.bufferedReader(CP850)
var len: Int val lines = mutableListOf<String>()
while (zip.read(buffer).also { len = it } > 0) {
outputStream.write(buffer, 0, len) // WICHTIG: Wir dürfen den Reader NICHT schließen (use), da sonst der ZipInputStream geschlossen wird!
var line = reader.readLine()
while (line != null) {
if (line.isNotBlank()) {
lines.add(line)
}
line = reader.readLine()
} }
val content = outputStream.toString(CP850)
val lines = content.split(Regex("\\r?\\n|\\r")).filter { it.isNotBlank() }
dateien[fileName] = lines dateien[fileName] = lines
} }
zip.closeEntry() zip.closeEntry()
entry = zip.nextEntry entry = zip.nextEntry
} }
} finally { } finally {
// Wir schließen den ZipInputStream NICHT mit use, // Wir schließen den ZipInputStream NICHT hier, sondern überlassen es dem Aufrufer
// um den zugrunde liegenden zipInputStream nicht vorzeitig zu schließen.
// Falls der Aufrufer den Stream schließen will, soll er das tun.
// Aber wir müssen sicherstellen, dass wir alle Entries gelesen haben.
} }
return dateien return dateien
} }
+2 -2
View File
@@ -92,8 +92,8 @@ USER ${APP_USER}
EXPOSE 8095 5005 EXPOSE 8095 5005
HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ HEALTHCHECK --interval=15s --timeout=5s --start-period=60s --retries=5 \
CMD curl -fsS --max-time 2 http://localhost:8095/actuator/health/readiness || exit 1 CMD curl -fsS --max-time 5 http://localhost:${SERVER_PORT:-8095}/actuator/health/readiness || exit 1
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \ ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \ -XX:+UseG1GC \
@@ -28,9 +28,12 @@ spring:
discovery: discovery:
enabled: ${CONSUL_ENABLED:true} enabled: ${CONSUL_ENABLED:true}
register: ${CONSUL_ENABLED:true} register: ${CONSUL_ENABLED:true}
prefer-ip-address: ${SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS:true}
service-name: ${spring.application.name}
health-check-path: /actuator/health health-check-path: /actuator/health
health-check-interval: 10s health-check-interval: 15s
instance-id: ${spring.application.name}-${server.port}-${random.uuid} instance-id: ${spring.application.name}:${server.port}:${random.uuid}
hostname: ${SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME:localhost}
management: management:
endpoints: endpoints:
web: web:
@@ -39,6 +42,8 @@ management:
endpoint: endpoint:
health: health:
show-details: always show-details: always
probes:
enabled: true
app: app:
service-name: ${spring.application.name} service-name: ${spring.application.name}
+6 -5
View File
@@ -77,8 +77,8 @@ services:
condition: "service_healthy" condition: "service_healthy"
zipkin: zipkin:
condition: "service_healthy" condition: "service_healthy"
mail-service: # mail-service:
condition: "service_healthy" # condition: "service_healthy"
healthcheck: healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:8081/actuator/health/readiness" ] test: [ "CMD", "wget", "--spider", "-q", "http://localhost:8081/actuator/health/readiness" ]
@@ -352,6 +352,7 @@ services:
SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}" SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}"
SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${ZNS_IMPORT_SERVICE_NAME:-zns-import-service}" SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${ZNS_IMPORT_SERVICE_NAME:-zns-import-service}"
SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${ZNS_IMPORT_CONSUL_PREFER_IP:-true}" SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${ZNS_IMPORT_CONSUL_PREFER_IP:-true}"
SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME: "${ZNS_IMPORT_HOSTNAME:-zns-import-service}"
# - DATENBANK VERBINDUNG - # - DATENBANK VERBINDUNG -
SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}" SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}"
@@ -381,11 +382,11 @@ services:
condition: "service_healthy" condition: "service_healthy"
healthcheck: healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:8095/actuator/health/readiness" ] test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8095/actuator/health/readiness" ]
interval: 15s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 40s start_period: 30s
networks: networks:
meldestelle-network: meldestelle-network:
@@ -53,3 +53,12 @@ Um die Wartezeit beim ersten Import von ~20 Minuten auf wenige Sekunden/Minuten
--- ---
🧹 **[Curator]**: Dieses Dokument wurde um die finalen Implementierungsdetails ergänzt. Alle fachlichen und technischen 🧹 **[Curator]**: Dieses Dokument wurde um die finalen Implementierungsdetails ergänzt. Alle fachlichen und technischen
Parameter sind hiermit fixiert und umgesetzt. Parameter sind hiermit fixiert und umgesetzt.
### Nachtrag: 2026-04-16 15:00 - Actuator-Fix (Docker-Stability)
* **Problem**: ZNS-Import-Service meldete 404 auf `/actuator/health/readiness` unter Docker-Compose, was zu ungesunden
Containern führte.
* **Lösung**: Explizite Aktivierung der `probes` in der `application.yaml` (
`management.endpoint.health.probes.enabled: true`).
* **Optimierung**: Healthcheck in `dc-backend.yaml` auf `wget --no-verbose` umgestellt für bessere
Diagnosemöglichkeiten.