303 lines
8.6 KiB
Markdown
303 lines
8.6 KiB
Markdown
# 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": "<MongoId als String>",
|
|
"completedBy": "<optional: Name/ID des Ausführenden>",
|
|
"note": "<optional: Kommentar>"
|
|
}
|
|
```
|
|
|
|
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.
|