Erweiterungen
This commit is contained in:
398
MQTT_MIGRATION_GUIDE.md
Normal file
398
MQTT_MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# MQTT Migration Guide - Client Implementation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieses Dokument beschreibt die Änderungen am MQTT-Messaging-System und wie mobile Clients (Flutter/Dart) auf den neuen Messaging-Layer umgestellt werden müssen.
|
||||
|
||||
## Änderungen am Server
|
||||
|
||||
### 1. Neue MQTT-Broker-Konfiguration
|
||||
|
||||
**Wichtig: Der MQTT-Broker-Port hat sich geändert!**
|
||||
|
||||
```
|
||||
Alter Port: 1883
|
||||
Neuer Port: 42099
|
||||
Broker: mqtt-2.assecutor.de:42099
|
||||
```
|
||||
|
||||
### 2. Server-Konfiguration
|
||||
|
||||
Die Server-Konfiguration wurde von der alten `app.mqtt.*` Konfiguration auf das neue Plugin-System umgestellt:
|
||||
|
||||
**Alte Konfiguration (nicht mehr verwendet):**
|
||||
```properties
|
||||
app.mqtt.enabled=true
|
||||
app.mqtt.broker-uri=mqtt://mqtt-2.assecutor.de
|
||||
app.mqtt.client-id=server-${random.uuid}
|
||||
```
|
||||
|
||||
**Neue Konfiguration:**
|
||||
```properties
|
||||
# Messaging Plugin Configuration
|
||||
app.messaging.plugin.type=mqtt
|
||||
app.messaging.plugin.mqtt.broker.host=mqtt-2.assecutor.de
|
||||
app.messaging.plugin.mqtt.broker.port=42099
|
||||
app.messaging.plugin.mqtt.username=app
|
||||
app.messaging.plugin.mqtt.password=apppwd
|
||||
app.messaging.plugin.mqtt.client.id=votianlt-server
|
||||
```
|
||||
|
||||
### 3. Verbesserte Fehlerbehandlung
|
||||
|
||||
Der Server hat jetzt:
|
||||
- Erhöhten Connection-Timeout (60 Sekunden statt 30)
|
||||
- Detaillierte Fehlerdiagnose für Verbindungsprobleme
|
||||
- Automatische Wiederverbindung mit exponentieller Backoff-Strategie
|
||||
|
||||
## Client-Anpassungen erforderlich
|
||||
|
||||
### 1. MQTT-Broker-Verbindung aktualisieren
|
||||
|
||||
**Flutter/Dart Beispiel:**
|
||||
|
||||
```dart
|
||||
import 'package:mqtt_client/mqtt_client.dart';
|
||||
import 'package:mqtt_client/mqtt_server_client.dart';
|
||||
|
||||
class MqttService {
|
||||
// WICHTIG: Neuer Port!
|
||||
static const String BROKER_HOST = 'mqtt-2.assecutor.de';
|
||||
static const int BROKER_PORT = 42099; // Geändert von 1883
|
||||
|
||||
static const String USERNAME = 'app';
|
||||
static const String PASSWORD = 'apppwd';
|
||||
|
||||
late MqttServerClient client;
|
||||
|
||||
Future<void> connect(String clientId) async {
|
||||
client = MqttServerClient.withPort(
|
||||
BROKER_HOST,
|
||||
clientId,
|
||||
BROKER_PORT, // Neuer Port
|
||||
);
|
||||
|
||||
client.logging(on: true);
|
||||
client.keepAlivePeriod = 60;
|
||||
client.connectTimeoutPeriod = 60000; // 60 Sekunden
|
||||
client.autoReconnect = true;
|
||||
|
||||
// Authentifizierung
|
||||
client.setProtocolV311();
|
||||
|
||||
final connMessage = MqttConnectMessage()
|
||||
.withClientIdentifier(clientId)
|
||||
.authenticateAs(USERNAME, PASSWORD)
|
||||
.withWillQos(MqttQos.atLeastOnce)
|
||||
.startClean()
|
||||
.keepAliveFor(60);
|
||||
|
||||
client.connectionMessage = connMessage;
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
print('MQTT Connected successfully');
|
||||
} catch (e) {
|
||||
print('MQTT Connection failed: $e');
|
||||
client.disconnect();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Topic-Struktur (unverändert)
|
||||
|
||||
Die Topic-Struktur bleibt gleich:
|
||||
|
||||
**Client → Server:**
|
||||
```
|
||||
/server/{clientId}/{messageType}
|
||||
```
|
||||
|
||||
**Server → Client:**
|
||||
```
|
||||
/client/{clientId}/{messageType}
|
||||
```
|
||||
|
||||
**Acknowledgments:**
|
||||
```
|
||||
Client → Server: /ack/server/{messageId}
|
||||
Server → Client: /ack/client/{clientId}/{messageId}
|
||||
```
|
||||
|
||||
### 3. Message Envelope Format
|
||||
|
||||
Der Server verwendet jetzt ein Message-Envelope-Format für alle Nachrichten:
|
||||
|
||||
```json
|
||||
{
|
||||
"messageId": "uuid-v4",
|
||||
"timestamp": "2025-10-22T10:30:00Z",
|
||||
"topic": "/client/app-user-123/jobs/assigned",
|
||||
"payload": {
|
||||
// Ihre eigentliche Nachricht
|
||||
},
|
||||
"requiresAck": true,
|
||||
"retryCount": 0,
|
||||
"expiresAt": "2025-10-23T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Client-Implementierung:**
|
||||
|
||||
```dart
|
||||
class MessageEnvelope {
|
||||
final String messageId;
|
||||
final DateTime timestamp;
|
||||
final String topic;
|
||||
final Map<String, dynamic> payload;
|
||||
final bool requiresAck;
|
||||
final int retryCount;
|
||||
final DateTime? expiresAt;
|
||||
|
||||
MessageEnvelope({
|
||||
required this.messageId,
|
||||
required this.timestamp,
|
||||
required this.topic,
|
||||
required this.payload,
|
||||
this.requiresAck = true,
|
||||
this.retryCount = 0,
|
||||
this.expiresAt,
|
||||
});
|
||||
|
||||
factory MessageEnvelope.fromJson(Map<String, dynamic> json) {
|
||||
return MessageEnvelope(
|
||||
messageId: json['messageId'],
|
||||
timestamp: DateTime.parse(json['timestamp']),
|
||||
topic: json['topic'],
|
||||
payload: json['payload'],
|
||||
requiresAck: json['requiresAck'] ?? true,
|
||||
retryCount: json['retryCount'] ?? 0,
|
||||
expiresAt: json['expiresAt'] != null
|
||||
? DateTime.parse(json['expiresAt'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'messageId': messageId,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'topic': topic,
|
||||
'payload': payload,
|
||||
'requiresAck': requiresAck,
|
||||
'retryCount': retryCount,
|
||||
'expiresAt': expiresAt?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Acknowledgment-Handling
|
||||
|
||||
Wenn eine Nachricht mit `requiresAck: true` empfangen wird, muss der Client eine Bestätigung senden:
|
||||
|
||||
```dart
|
||||
class AcknowledgmentMessage {
|
||||
final String messageId;
|
||||
final DateTime timestamp;
|
||||
final String status; // "SUCCESS" oder "FAILED"
|
||||
final String? errorMessage;
|
||||
|
||||
AcknowledgmentMessage({
|
||||
required this.messageId,
|
||||
required this.timestamp,
|
||||
required this.status,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'messageId': messageId,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'status': status,
|
||||
if (errorMessage != null) 'errorMessage': errorMessage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Verwendung:
|
||||
void sendAcknowledgment(String messageId, bool success, [String? error]) {
|
||||
final ack = AcknowledgmentMessage(
|
||||
messageId: messageId,
|
||||
timestamp: DateTime.now(),
|
||||
status: success ? 'SUCCESS' : 'FAILED',
|
||||
errorMessage: error,
|
||||
);
|
||||
|
||||
final topic = '/ack/server/$messageId';
|
||||
final payload = jsonEncode(ack.toJson());
|
||||
|
||||
final builder = MqttClientPayloadBuilder();
|
||||
builder.addString(payload);
|
||||
|
||||
client.publishMessage(
|
||||
topic,
|
||||
MqttQos.exactlyOnce,
|
||||
builder.payload!,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Nachrichten empfangen und verarbeiten
|
||||
|
||||
```dart
|
||||
void setupMessageHandlers() {
|
||||
client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> messages) {
|
||||
for (var message in messages) {
|
||||
final topic = message.topic;
|
||||
final payload = MqttPublishPayload.bytesToStringAsString(
|
||||
(message.payload as MqttPublishMessage).payload.message,
|
||||
);
|
||||
|
||||
try {
|
||||
final json = jsonDecode(payload);
|
||||
|
||||
// Prüfen, ob es ein Envelope ist
|
||||
if (json.containsKey('messageId') && json.containsKey('payload')) {
|
||||
final envelope = MessageEnvelope.fromJson(json);
|
||||
|
||||
// Nachricht verarbeiten
|
||||
handleMessage(envelope);
|
||||
|
||||
// ACK senden, wenn erforderlich
|
||||
if (envelope.requiresAck) {
|
||||
sendAcknowledgment(envelope.messageId, true);
|
||||
}
|
||||
} else {
|
||||
// Legacy-Nachricht ohne Envelope
|
||||
handleLegacyMessage(topic, json);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error processing message: $e');
|
||||
// Bei Envelope-Nachrichten: Fehler-ACK senden
|
||||
if (json.containsKey('messageId')) {
|
||||
sendAcknowledgment(json['messageId'], false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Nachrichten senden
|
||||
|
||||
```dart
|
||||
Future<void> sendMessage(
|
||||
String messageType,
|
||||
Map<String, dynamic> payload,
|
||||
{bool requiresAck = true}
|
||||
) async {
|
||||
final envelope = MessageEnvelope(
|
||||
messageId: Uuid().v4(),
|
||||
timestamp: DateTime.now(),
|
||||
topic: '/server/$clientId/$messageType',
|
||||
payload: payload,
|
||||
requiresAck: requiresAck,
|
||||
);
|
||||
|
||||
final topic = envelope.topic;
|
||||
final message = jsonEncode(envelope.toJson());
|
||||
|
||||
final builder = MqttClientPayloadBuilder();
|
||||
builder.addString(message);
|
||||
|
||||
client.publishMessage(
|
||||
topic,
|
||||
MqttQos.exactlyOnce,
|
||||
builder.payload!,
|
||||
);
|
||||
|
||||
print('Message sent: $messageType');
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Checklist für Clients
|
||||
|
||||
- [ ] **MQTT-Broker-Port auf 42099 ändern**
|
||||
- [ ] **Connection-Timeout auf mindestens 60 Sekunden erhöhen**
|
||||
- [ ] **Keep-Alive auf 60 Sekunden setzen**
|
||||
- [ ] **Authentifizierung hinzufügen** (username: `app`, password: `apppwd`)
|
||||
- [ ] **MessageEnvelope-Klasse implementieren**
|
||||
- [ ] **AcknowledgmentMessage-Klasse implementieren**
|
||||
- [ ] **Envelope-basierte Nachrichtenverarbeitung implementieren**
|
||||
- [ ] **ACK-Handling für eingehende Nachrichten implementieren**
|
||||
- [ ] **Ausgehende Nachrichten in Envelopes verpacken**
|
||||
- [ ] **Fehlerbehandlung für abgelaufene Nachrichten implementieren**
|
||||
- [ ] **Retry-Logik für fehlgeschlagene Nachrichten implementieren**
|
||||
|
||||
## Abwärtskompatibilität
|
||||
|
||||
Der Server unterstützt derzeit noch Legacy-Nachrichten ohne Envelope-Format, aber es wird empfohlen, so schnell wie möglich auf das neue Format umzustellen.
|
||||
|
||||
**Legacy-Format (wird noch unterstützt):**
|
||||
```json
|
||||
{
|
||||
"taskId": "123",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
**Neues Format (empfohlen):**
|
||||
```json
|
||||
{
|
||||
"messageId": "uuid",
|
||||
"timestamp": "2025-10-22T10:30:00Z",
|
||||
"topic": "/server/client-123/task_completed",
|
||||
"payload": {
|
||||
"taskId": "123",
|
||||
"status": "completed"
|
||||
},
|
||||
"requiresAck": true
|
||||
}
|
||||
```
|
||||
|
||||
## Vorteile des neuen Systems
|
||||
|
||||
1. **Zuverlässige Zustellung**: ACK-basiertes System mit automatischen Wiederholungen
|
||||
2. **Nachrichtenverfolgung**: Jede Nachricht hat eine eindeutige ID
|
||||
3. **Ablaufverwaltung**: Nachrichten können ablaufen und werden automatisch bereinigt
|
||||
4. **Bessere Fehlerbehandlung**: Detaillierte Fehlerinformationen
|
||||
5. **Monitoring**: Vollständige Nachrichtenverfolgung im System
|
||||
|
||||
## Testen der Verbindung
|
||||
|
||||
```dart
|
||||
Future<void> testConnection() async {
|
||||
try {
|
||||
final mqttService = MqttService();
|
||||
await mqttService.connect('test-client-${Uuid().v4()}');
|
||||
|
||||
// Test-Nachricht senden
|
||||
await mqttService.sendMessage(
|
||||
'test',
|
||||
{'message': 'Hello from client'},
|
||||
);
|
||||
|
||||
print('Connection test successful!');
|
||||
} catch (e) {
|
||||
print('Connection test failed: $e');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Support und Fragen
|
||||
|
||||
Bei Fragen oder Problemen während der Migration:
|
||||
1. Prüfen Sie die Logs auf beiden Seiten (Client und Server)
|
||||
2. Stellen Sie sicher, dass der Port 42099 erreichbar ist
|
||||
3. Überprüfen Sie die Authentifizierungsdaten
|
||||
4. Testen Sie die Verbindung mit einem MQTT-Client-Tool (z.B. MQTT Explorer)
|
||||
|
||||
## Weitere Dokumentation
|
||||
|
||||
- `MESSAGING_LAYER.md` - Detaillierte Architektur des Messaging-Systems
|
||||
- `MQTT_README.md` - MQTT-API-Dokumentation
|
||||
- `CLAUDE.md` - Allgemeine Systemarchitektur
|
||||
|
||||
Reference in New Issue
Block a user