Erweiterungen
This commit is contained in:
@@ -17,6 +17,7 @@ Das System bietet folgende STOMP-Funktionalitäten:
|
|||||||
- **`/app/message`** - Allgemeine Nachrichten
|
- **`/app/message`** - Allgemeine Nachrichten
|
||||||
- **`/app/job/status`** - Job-Status-Updates
|
- **`/app/job/status`** - Job-Status-Updates
|
||||||
- **`/app/device/location`** - Gerätestandort-Updates
|
- **`/app/device/location`** - Gerätestandort-Updates
|
||||||
|
- **`/app/auth/login`** - Anmeldung eines App-Users (Payload: { email, password })
|
||||||
|
|
||||||
#### Ausgehende Nachrichten (Server → Client)
|
#### Ausgehende Nachrichten (Server → Client)
|
||||||
- **`/topic/messages`** - Broadcast aller allgemeinen Nachrichten
|
- **`/topic/messages`** - Broadcast aller allgemeinen Nachrichten
|
||||||
@@ -75,6 +76,19 @@ stompClient.send('/app/device/location', {}, JSON.stringify({
|
|||||||
longitude: 13.4050,
|
longitude: 13.4050,
|
||||||
accuracy: 10
|
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
|
## Backend-Integration
|
||||||
@@ -92,6 +106,23 @@ messageController.sendNotificationToUser("username", "Neue Aufgabe verfügbar");
|
|||||||
messageController.sendBroadcastMessage("Systemwartung in 10 Minuten");
|
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
|
## Konfiguration
|
||||||
|
|
||||||
Die STOMP-Konfiguration befindet sich in:
|
Die STOMP-Konfiguration befindet sich in:
|
||||||
|
|||||||
7
pom.xml
7
pom.xml
@@ -103,6 +103,13 @@
|
|||||||
<artifactId>spring-messaging</artifactId>
|
<artifactId>spring-messaging</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Zeroconf mDNS (JmDNS) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jmdns</groupId>
|
||||||
|
<artifactId>jmdns</artifactId>
|
||||||
|
<version>3.6.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package de.assecutor.votianlt.controller;
|
package de.assecutor.votianlt.controller;
|
||||||
|
|
||||||
|
import de.assecutor.votianlt.dto.AppLoginRequest;
|
||||||
|
import de.assecutor.votianlt.dto.AppLoginResponse;
|
||||||
|
import de.assecutor.votianlt.model.AppUser;
|
||||||
|
import de.assecutor.votianlt.pages.service.AppUserService;
|
||||||
|
import de.assecutor.votianlt.repository.AppUserRepository;
|
||||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||||
import org.springframework.messaging.handler.annotation.SendTo;
|
import org.springframework.messaging.handler.annotation.SendTo;
|
||||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||||
|
import org.springframework.messaging.simp.annotation.SendToUser;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
@@ -20,6 +26,12 @@ public class MessageController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SimpMessagingTemplate messagingTemplate;
|
private SimpMessagingTemplate messagingTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AppUserRepository appUserRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AppUserService appUserService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles messages sent to /app/message and broadcasts them to all subscribers of /topic/messages
|
* Handles messages sent to /app/message and broadcasts them to all subscribers of /topic/messages
|
||||||
*/
|
*/
|
||||||
@@ -82,4 +94,30 @@ public class MessageController {
|
|||||||
|
|
||||||
messagingTemplate.convertAndSend("/topic/broadcasts", broadcast);
|
messagingTemplate.convertAndSend("/topic/broadcasts", broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication endpoint for mobile app users via STOMP.
|
||||||
|
* Client sends to /app/auth/login with payload { email, password }.
|
||||||
|
* The response is sent back to the requesting user on /user/queue/auth
|
||||||
|
*/
|
||||||
|
@MessageMapping("/auth/login")
|
||||||
|
@SendToUser("/queue/auth")
|
||||||
|
public AppLoginResponse handleAppLogin(AppLoginRequest request) {
|
||||||
|
if (request == null || request.getEmail() == null || request.getPassword() == null
|
||||||
|
|| request.getEmail().isBlank() || request.getPassword().isBlank()) {
|
||||||
|
return new AppLoginResponse(false, "E-Mail und Passwort sind erforderlich", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppUser user = appUserRepository.findByEmail(request.getEmail());
|
||||||
|
if (user == null) {
|
||||||
|
return new AppLoginResponse(false, "Benutzer nicht gefunden", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean ok = appUserService.verifyPassword(request.getPassword(), user.getPassword());
|
||||||
|
if (!ok) {
|
||||||
|
return new AppLoginResponse(false, "Ungültige Anmeldedaten", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppLoginResponse(true, "Anmeldung erfolgreich", user.getId() != null ? user.getId().toHexString() : null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.assecutor.votianlt.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AppLoginRequest {
|
||||||
|
private String email;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.assecutor.votianlt.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AppLoginResponse {
|
||||||
|
private boolean success;
|
||||||
|
private String message;
|
||||||
|
private String appUserId; // MongoDB ObjectId as hex string
|
||||||
|
}
|
||||||
@@ -13,8 +13,9 @@ public interface AppUserRepository extends MongoRepository<AppUser, ObjectId> {
|
|||||||
// Find all AppUsers created by a specific user
|
// Find all AppUsers created by a specific user
|
||||||
List<AppUser> findByErstelltVon(ObjectId erstelltVon);
|
List<AppUser> findByErstelltVon(ObjectId erstelltVon);
|
||||||
|
|
||||||
|
// Find AppUser by email for login
|
||||||
|
AppUser findByEmail(String email);
|
||||||
|
|
||||||
// Custom query methods can be added here if needed
|
// Custom query methods can be added here if needed
|
||||||
// For example:
|
|
||||||
// List<AppUser> findByEmail(String email);
|
|
||||||
// List<AppUser> findByBezeichnung(String bezeichnung);
|
// List<AppUser> findByBezeichnung(String bezeichnung);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package de.assecutor.votianlt.zeroconf;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.event.ContextClosedEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes the STOMP WebSocket endpoint via Zeroconf (mDNS/Bonjour) using reflection.
|
||||||
|
* If JmDNS is present on the classpath, it will register the service _stomp._tcp.local.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ZeroconfPublisher {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ZeroconfPublisher.class);
|
||||||
|
|
||||||
|
@Value("${server.port:8080}")
|
||||||
|
private int serverPort;
|
||||||
|
|
||||||
|
// Expose stomp endpoints paths via TXT records
|
||||||
|
@Value("${app.stomp.wsPath:/ws}")
|
||||||
|
private String wsPath;
|
||||||
|
|
||||||
|
@Value("${app.stomp.websocketPath:/websocket}")
|
||||||
|
private String websocketPath;
|
||||||
|
|
||||||
|
@Value("${app.zeroconf.enabled:true}")
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Value("${app.zeroconf.serviceName:votianlt-stomp}")
|
||||||
|
private String serviceName;
|
||||||
|
|
||||||
|
// Controls whether to log a notice if JmDNS is not available
|
||||||
|
@Value("${app.zeroconf.warnWhenMissing:false}")
|
||||||
|
private boolean warnWhenMissing;
|
||||||
|
|
||||||
|
private Object jmdns; // javax.jmdns.JmDNS instance if available
|
||||||
|
|
||||||
|
@EventListener(org.springframework.boot.context.event.ApplicationReadyEvent.class)
|
||||||
|
public void onAppReady() {
|
||||||
|
if (!enabled) return;
|
||||||
|
try {
|
||||||
|
Class<?> jmDNSClass = Class.forName("javax.jmdns.JmDNS");
|
||||||
|
Class<?> serviceInfoClass = Class.forName("javax.jmdns.ServiceInfo");
|
||||||
|
|
||||||
|
InetAddress addr = InetAddress.getLocalHost();
|
||||||
|
Method createMethod = jmDNSClass.getMethod("create", InetAddress.class);
|
||||||
|
jmdns = createMethod.invoke(null, addr);
|
||||||
|
|
||||||
|
String type = "_stomp._tcp.local.";
|
||||||
|
String text = "path=" + wsPath + ",websocket=" + websocketPath + ",protocol=stomp";
|
||||||
|
|
||||||
|
Method createServiceInfo = serviceInfoClass.getMethod("create", String.class, String.class, int.class, String.class);
|
||||||
|
Object serviceInfo = createServiceInfo.invoke(null, type, serviceName, serverPort, text);
|
||||||
|
|
||||||
|
Method registerService = jmDNSClass.getMethod("registerService", serviceInfoClass);
|
||||||
|
registerService.invoke(jmdns, serviceInfo);
|
||||||
|
|
||||||
|
logger.info("STOMP-Service veröffentlicht: {} name={} port={}", type, serviceName, serverPort);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
if (warnWhenMissing) {
|
||||||
|
logger.warn("Hinweis: JmDNS ist nicht vorhanden – Zeroconf ist deaktiviert.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Registrierung fehlgeschlagen: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener(ContextClosedEvent.class)
|
||||||
|
public void onShutdown() {
|
||||||
|
if (jmdns != null) {
|
||||||
|
try {
|
||||||
|
Method unregisterAll = jmdns.getClass().getMethod("unregisterAllServices");
|
||||||
|
unregisterAll.invoke(jmdns);
|
||||||
|
Method close = jmdns.getClass().getMethod("close");
|
||||||
|
close.invoke(jmdns);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user