# STOMP Messaging Integration Die Anwendung unterstützt jetzt STOMP (Simple Text Oriented Messaging Protocol) für die Kommunikation mit externen Apps über WebSocket-Verbindungen. ## Übersicht Das System bietet folgende STOMP-Funktionalitäten: ### WebSocket-Endpunkte - **`/ws`** - STOMP-Endpunkt mit SockJS-Fallback-Unterstützung - **`/websocket`** - Reiner WebSocket-Endpunkt ohne SockJS ### Nachrichtendestinationen #### Eingehende Nachrichten (Client → Server) - **`/app/message`** - Allgemeine Nachrichten - **`/app/job/status`** - Job-Status-Updates - **`/app/device/location`** - Gerätestandort-Updates - **`/app/auth/login`** - Anmeldung eines App-Users (Payload: { email, password }) #### Ausgehende Nachrichten (Server → Client) - **`/topic/messages`** - Broadcast aller allgemeinen Nachrichten - **`/topic/job-updates`** - Job-Status-Updates für alle Abonnenten - **`/topic/device-locations`** - Gerätestandort-Updates - **`/topic/broadcasts`** - System-weite Broadcast-Nachrichten - **`/queue/notifications`** - Benutzerspezifische Benachrichtigungen ## Verwendung für Apps ### 1. Verbindung aufbauen ```javascript // Mit SockJS const socket = new SockJS('http://192.168.180.196:8080/ws'); const stompClient = Stomp.over(socket); // Oder mit nativem WebSocket (WICHTIG: ws:// verwenden, nicht http://) const socket = new WebSocket('ws://192.168.180.196:8080/websocket'); const stompClient = Stomp.over(socket); ``` ### Flutter/Dart STOMP Client **Für Flutter-Apps verwenden Sie diese Konfiguration:** ```dart import 'package:stomp_dart_client/stomp.dart'; import 'package:stomp_dart_client/stomp_config.dart'; import 'package:stomp_dart_client/stomp_frame.dart'; import 'dart:convert'; // WICHTIG: Verwenden Sie ws:// für WebSocket-Verbindungen, NICHT http:// final stompClient = StompClient( config: StompConfig( url: 'ws://192.168.180.196:8080/websocket', // Beachten Sie ws:// statt http:// onConnect: onConnectCallback, onWebSocketError: (dynamic error) => print('WebSocket Error: $error'), onStompError: (StompFrame frame) => print('Stomp Error: ${frame.body}'), onDisconnect: (StompFrame frame) => print('Disconnected'), // Heartbeat-Konfiguration heartbeatIncoming: Duration(seconds: 20), heartbeatOutgoing: Duration(seconds: 20), ), ); void onConnectCallback(StompFrame frame) { print('Connected to STOMP server'); // Nachrichten abonnieren stompClient.subscribe( destination: '/topic/messages', callback: (StompFrame frame) { print('Received message: ${frame.body}'); }, ); } // Verbindung herstellen stompClient.activate(); // Nachricht senden void sendMessage(String content) { stompClient.send( destination: '/app/message', body: jsonEncode({ 'content': content, 'sender': 'FlutterApp', }), ); } ``` **Alternative Endpunkte für Flutter:** Falls Probleme mit `/websocket` auftreten, versuchen Sie: - `ws://192.168.180.196:8080/stomp` (zusätzlicher Endpunkt) - `ws://192.168.180.196:8080/ws` (SockJS-Endpunkt ohne SockJS-Protokoll) ### 2. Verbindung herstellen ```javascript stompClient.connect({}, function(frame) { console.log('Verbunden: ' + frame); // Nachrichten abonnieren stompClient.subscribe('/topic/messages', function(message) { console.log('Nachricht erhalten:', JSON.parse(message.body)); }); }); ``` ### 3. Nachrichten senden ```javascript // Allgemeine Nachricht senden stompClient.send('/app/message', {}, JSON.stringify({ content: 'Hallo vom App', sender: 'MobileApp' })); // Job-Status-Update senden stompClient.send('/app/job/status', {}, JSON.stringify({ jobId: '12345', status: 'IN_PROGRESS', progress: 75 })); // Gerätestandort senden stompClient.send('/app/device/location', {}, JSON.stringify({ deviceId: 'device-001', latitude: 52.5200, longitude: 13.4050, accuracy: 10 })); // Anmeldung eines App-Users // Zuerst die Antwort-Warteschlange abonnieren (user-spezifisch) const authSubscription = stompClient.subscribe('/user/queue/auth', function(message) { const resp = JSON.parse(message.body); console.log('Login-Antwort:', resp); }); // Login-Request senden stompClient.send('/app/auth/login', {}, JSON.stringify({ email: 'user@example.com', password: 'geheimesPasswort' })); ``` ## Backend-Integration ### Programmatische Nachrichten senden ```java @Autowired private MessageController messageController; // Benachrichtigung an spezifischen Benutzer messageController.sendNotificationToUser("username", "Neue Aufgabe verfügbar"); // Broadcast-Nachricht an alle messageController.sendBroadcastMessage("Systemwartung in 10 Minuten"); ``` ## Zeroconf (mDNS) Veröffentlichung Die Anwendung veröffentlicht die STOMP-Schnittstelle via Zeroconf (DNS-SD/mDNS), sofern verfügbar. Es wird der Service-Typ `_stomp._tcp.local.` mit folgenden TXT-Records publiziert: - path = Pfad für SockJS-Endpoint (Standard: /ws) - websocket = Pfad für nativen WebSocket (Standard: /websocket) - protocol = "stomp" Clients können per Bonjour/mDNS nach `_stomp._tcp` suchen und erhalten Port und Metadaten. Hinweise: - Die Implementierung nutzt JmDNS, falls die Bibliothek auf dem Klassenpfad vorhanden ist. In Umgebungen ohne JmDNS bleibt Zeroconf stillschweigend deaktiviert (es wird ein Hinweis im Log ausgegeben). - Konfigurierbare Properties: - app.zeroconf.enabled (default: true) - app.zeroconf.serviceName (default: votianlt-stomp) - app.stomp.wsPath (default: /ws) - app.stomp.websocketPath (default: /websocket) ## Konfiguration Die STOMP-Konfiguration befindet sich in: - **`WebSocketConfig.java`** - WebSocket und STOMP-Konfiguration - **`MessageController.java`** - Nachrichtenbehandlung - **`application.properties`** - Zusätzliche WebSocket-Einstellungen ### Wichtige Konfigurationsparameter ```properties # Nachrichtenpuffergröße spring.websocket.servlet.max-text-message-buffer-size=8192 spring.websocket.servlet.max-binary-message-buffer-size=8192 # STOMP aktivieren spring.websocket.stomp.enabled=true # Heartbeat-Einstellungen spring.websocket.stomp.heartbeat.outgoing=10000 spring.websocket.stomp.heartbeat.incoming=10000 ``` ## Sicherheitshinweise - WebSocket-Verbindungen verwenden die gleiche Authentifizierung wie die Web-Anwendung - Nachrichten werden automatisch mit Zeitstempel versehen - Alle Nachrichten werden in JSON-Format verarbeitet ## Testing Zum Testen der STOMP-Funktionalität können Sie: 1. Eine WebSocket-Client-Bibliothek verwenden 2. Browser-Entwicklertools für WebSocket-Verbindungen nutzen 3. Spezialisierte STOMP-Testing-Tools einsetzen Die Implementierung ist vollständig und bereit für die Integration mit externen Apps. ## Neue STOMP-Schnittstelle: Task-Erledigung melden Mit dieser Schnittstelle kann ein Client die Erledigung eines Tasks melden. - Senden (Client → Server): `/app/task/completed` - Broadcasts (Server → Client): - Global: `/topic/task-updates` - Task-spezifisch: `/topic/tasks/{taskId}` Payload (JSON): ```json { "taskId": "", "completedBy": "", "note": "" } ``` Antwort (Beispiel): ```json { "timestamp": "2025-09-05T09:25:00", "type": "taskCompletedAck", "success": true, "taskId": "...", "jobId": "...", "completed": true, "completedAt": "2025-09-05T09:25:00", "completedBy": "driver01", "note": "Übergabe erfolgreich", "event": "taskCompleted" } ``` JavaScript Beispiel: ```javascript // Abonnieren der globalen Updates stompClient.subscribe('/topic/task-updates', (frame) => { console.log('Task update:', JSON.parse(frame.body)); }); // Abonnieren eines spezifischen Tasks stompClient.subscribe('/topic/tasks/' + taskId, (frame) => { console.log('Task-specific update:', JSON.parse(frame.body)); }); // Task als erledigt melden stompClient.send('/app/task/completed', {}, JSON.stringify({ taskId: taskId, completedBy: 'driver01', note: 'Übergabe erfolgreich' })); ``` Flutter/Dart Beispiel: ```dart stompClient.subscribe( destination: '/topic/task-updates', callback: (frame) => print('Task update: ${frame.body}'), ); stompClient.subscribe( destination: '/topic/tasks/$taskId', callback: (frame) => print('Task-specific update: ${frame.body}'), ); stompClient.send( destination: '/app/task/completed', body: jsonEncode({ 'taskId': taskId, 'completedBy': 'driver01', 'note': 'Übergabe erfolgreich', }), ); ``` Hinweise: - `taskId` ist Pflicht. Bei ungültiger oder unbekannter `taskId` wird `success=false` zurückgegeben. - Der Server setzt `completed=true` und `completedAt` automatisch. - Zusätzlich zum globalen Broadcast wird ein task-spezifisches Event auf `/topic/tasks/{taskId}` versendet.