rewrwer
This commit is contained in:
@@ -18,6 +18,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
registry.enableSimpleBroker("/topic");
|
||||
registry.enableSimpleBroker("/topic", "/queue");
|
||||
registry.setUserDestinationPrefix("/user");
|
||||
}
|
||||
}
|
||||
|
@@ -1,27 +1,54 @@
|
||||
package de.w665.testing.controller;
|
||||
|
||||
import de.w665.testing.model.ChatMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class ChatController {
|
||||
|
||||
@MessageMapping("/chat.sendMessage")
|
||||
@SendTo("/topic/public")
|
||||
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
|
||||
return chatMessage;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
@MessageMapping("/chat/{roomId}/sendMessage")
|
||||
public void sendMessage(@DestinationVariable String roomId, @Payload ChatMessage chatMessage) {
|
||||
messagingTemplate.convertAndSend(String.format("/topic/rooms/%s", roomId), chatMessage);
|
||||
}
|
||||
|
||||
@MessageMapping("/chat.addUser")
|
||||
@SendTo("/topic/public")
|
||||
public ChatMessage addUser(@Payload ChatMessage chatMessage,
|
||||
@MessageMapping("/chat/{roomId}/addUser")
|
||||
public void addUser(@DestinationVariable String roomId, @Payload ChatMessage chatMessage,
|
||||
SimpMessageHeaderAccessor headerAccessor) {
|
||||
// Add username in web socket session
|
||||
String currentRoomId = (String) headerAccessor.getSessionAttributes().put("room_id", roomId);
|
||||
if (currentRoomId != null) {
|
||||
ChatMessage leaveMessage = new ChatMessage();
|
||||
leaveMessage.setType(ChatMessage.MessageType.LEAVE);
|
||||
leaveMessage.setSender(chatMessage.getSender());
|
||||
messagingTemplate.convertAndSend(String.format("/topic/rooms/%s", currentRoomId), leaveMessage);
|
||||
}
|
||||
headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
|
||||
return chatMessage;
|
||||
messagingTemplate.convertAndSend(String.format("/topic/rooms/%s", roomId), chatMessage);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
|
||||
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(event.getMessage());
|
||||
|
||||
String username = (String) headerAccessor.getSessionAttributes().get("username");
|
||||
String roomId = (String) headerAccessor.getSessionAttributes().get("room_id");
|
||||
|
||||
if (username != null && roomId != null) {
|
||||
ChatMessage chatMessage = new ChatMessage();
|
||||
chatMessage.setType(ChatMessage.MessageType.LEAVE);
|
||||
chatMessage.setSender(username);
|
||||
|
||||
messagingTemplate.convertAndSend(String.format("/topic/rooms/%s", roomId), chatMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,34 @@
|
||||
package de.w665.testing.controller;
|
||||
|
||||
import de.w665.testing.model.ChatRoom;
|
||||
import de.w665.testing.service.ChatRoomService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/chat-rooms")
|
||||
@RequiredArgsConstructor
|
||||
public class ChatRoomController {
|
||||
|
||||
private final ChatRoomService chatRoomService;
|
||||
|
||||
@PostMapping
|
||||
public ChatRoom createRoom(@RequestBody CreateRoomRequest request) {
|
||||
return chatRoomService.createChatRoom(request.name(), request.password());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Collection<ChatRoom> getRooms() {
|
||||
return chatRoomService.findAll();
|
||||
}
|
||||
|
||||
@PostMapping("/{roomId}/join")
|
||||
public boolean joinRoom(@PathVariable String roomId, @RequestBody JoinRoomRequest request) {
|
||||
return chatRoomService.joinRoom(roomId, request.password());
|
||||
}
|
||||
|
||||
public record CreateRoomRequest(String name, String password) {}
|
||||
public record JoinRoomRequest(String password) {}
|
||||
}
|
@@ -6,6 +6,12 @@ import lombok.Setter;
|
||||
@Getter
|
||||
@Setter
|
||||
public class ChatMessage {
|
||||
|
||||
public enum MessageType {
|
||||
CHAT, JOIN, LEAVE
|
||||
}
|
||||
|
||||
private MessageType type;
|
||||
private String content;
|
||||
private String sender;
|
||||
}
|
||||
|
32
src/main/java/de/w665/testing/model/ChatRoom.java
Normal file
32
src/main/java/de/w665/testing/model/ChatRoom.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package de.w665.testing.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ChatRoom {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String password;
|
||||
private Set<String> users = new HashSet<>();
|
||||
|
||||
public ChatRoom(String name, String password) {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void addUser(String username) {
|
||||
users.add(username);
|
||||
}
|
||||
|
||||
public void removeUser(String username) {
|
||||
users.remove(username);
|
||||
}
|
||||
}
|
35
src/main/java/de/w665/testing/service/ChatRoomService.java
Normal file
35
src/main/java/de/w665/testing/service/ChatRoomService.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package de.w665.testing.service;
|
||||
|
||||
import de.w665.testing.model.ChatRoom;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class ChatRoomService {
|
||||
|
||||
private final Map<String, ChatRoom> chatRooms = new ConcurrentHashMap<>();
|
||||
|
||||
public ChatRoom createChatRoom(String name, String password) {
|
||||
ChatRoom chatRoom = new ChatRoom(name, password);
|
||||
chatRooms.put(chatRoom.getId(), chatRoom);
|
||||
return chatRoom;
|
||||
}
|
||||
|
||||
public Optional<ChatRoom> findById(String id) {
|
||||
return Optional.ofNullable(chatRooms.get(id));
|
||||
}
|
||||
|
||||
public Collection<ChatRoom> findAll() {
|
||||
return chatRooms.values();
|
||||
}
|
||||
|
||||
public boolean joinRoom(String roomId, String password) {
|
||||
return findById(roomId)
|
||||
.map(room -> room.getPassword() == null || room.getPassword().isEmpty() || room.getPassword().equals(password))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
@@ -4,50 +4,28 @@
|
||||
<title>QChat</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
#username-page, #chat-page {
|
||||
display: none;
|
||||
}
|
||||
#chat-page {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.chat-header {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
#message-area {
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
background-color: white;
|
||||
}
|
||||
.message-form {
|
||||
margin-top: 15px;
|
||||
}
|
||||
body { background-color: #f4f4f4; }
|
||||
#username-page, #room-selection-page, #chat-page { display: none; }
|
||||
.chat-header { background-color: #007bff; color: white; padding: 15px; border-radius: 5px 5px 0 0; }
|
||||
#message-area { height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; background-color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="container mt-4">
|
||||
<!-- Username Page -->
|
||||
<div id="username-page">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h3>Enter Your Name</h3>
|
||||
</div>
|
||||
<div class="card-header text-center"><h3>Enter Your Name</h3></div>
|
||||
<div class="card-body">
|
||||
<form id="username-form">
|
||||
<div class="form-group">
|
||||
<input type="text" id="name" placeholder="Username" class="form-control" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<button type="submit" class="btn btn-primary">Join Chat</button>
|
||||
<button type="submit" class="btn btn-primary">Continue</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -56,13 +34,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Room Selection Page -->
|
||||
<div id="room-selection-page">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3>Chat Rooms</h3></div>
|
||||
<div class="card-body">
|
||||
<h4>Create a New Room</h4>
|
||||
<form id="create-room-form" class="form-inline mb-4">
|
||||
<input type="text" id="room-name" placeholder="Room Name" class="form-control mr-2" required>
|
||||
<input type="password" id="room-password" placeholder="Password (optional)" class="form-control mr-2">
|
||||
<button type="submit" class="btn btn-success">Create</button>
|
||||
</form>
|
||||
<h4>Available Rooms</h4>
|
||||
<ul id="room-list" class="list-group"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Page -->
|
||||
<div id="chat-page">
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<h2>QChat</h2>
|
||||
</div>
|
||||
<div class="chat-header"><h2 id="chat-room-name"></h2></div>
|
||||
<ul id="message-area" class="list-unstyled"></ul>
|
||||
<form id="message-form" name="messageForm" class="message-form">
|
||||
<form id="message-form" class="mt-3">
|
||||
<div class="input-group">
|
||||
<input type="text" id="message" placeholder="Type a message..." class="form-control" autocomplete="off"/>
|
||||
<div class="input-group-append">
|
||||
@@ -78,70 +72,123 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const usernamePage = document.querySelector('#username-page');
|
||||
const chatPage = document.querySelector('#chat-page');
|
||||
const pages = {
|
||||
username: document.querySelector('#username-page'),
|
||||
roomSelection: document.querySelector('#room-selection-page'),
|
||||
chat: document.querySelector('#chat-page')
|
||||
};
|
||||
|
||||
const usernameForm = document.querySelector('#username-form');
|
||||
const createRoomForm = document.querySelector('#create-room-form');
|
||||
const messageForm = document.querySelector('#message-form');
|
||||
|
||||
const messageInput = document.querySelector('#message');
|
||||
const messageArea = document.querySelector('#message-area');
|
||||
const roomList = document.querySelector('#room-list');
|
||||
|
||||
let stompClient = null;
|
||||
let username = null;
|
||||
let currentRoom = null;
|
||||
|
||||
usernamePage.style.display = 'block';
|
||||
function showPage(pageName) {
|
||||
Object.values(pages).forEach(p => p.style.display = 'none');
|
||||
pages[pageName].style.display = 'block';
|
||||
}
|
||||
|
||||
usernameForm.addEventListener('submit', connect, true);
|
||||
messageForm.addEventListener('submit', sendMessage, true);
|
||||
|
||||
function connect(event) {
|
||||
usernameForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
username = document.querySelector('#name').value.trim();
|
||||
|
||||
if (username) {
|
||||
usernamePage.style.display = 'none';
|
||||
chatPage.style.display = 'block';
|
||||
showPage('roomSelection');
|
||||
loadRooms();
|
||||
}
|
||||
}, true);
|
||||
|
||||
createRoomForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const name = document.getElementById('room-name').value.trim();
|
||||
const password = document.getElementById('room-password').value.trim();
|
||||
if (name) {
|
||||
await fetch('/api/chat-rooms', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, password })
|
||||
});
|
||||
document.getElementById('room-name').value = '';
|
||||
document.getElementById('room-password').value = '';
|
||||
loadRooms();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadRooms() {
|
||||
const response = await fetch('/api/chat-rooms');
|
||||
const rooms = await response.json();
|
||||
roomList.innerHTML = '';
|
||||
rooms.forEach(room => {
|
||||
const roomElement = document.createElement('li');
|
||||
roomElement.className = 'list-group-item d-flex justify-content-between align-items-center';
|
||||
roomElement.textContent = room.name;
|
||||
const joinButton = document.createElement('button');
|
||||
joinButton.className = 'btn btn-primary btn-sm';
|
||||
joinButton.textContent = 'Join';
|
||||
joinButton.onclick = () => joinRoom(room);
|
||||
roomElement.appendChild(joinButton);
|
||||
roomList.appendChild(roomElement);
|
||||
});
|
||||
}
|
||||
|
||||
async function joinRoom(room) {
|
||||
let password = '';
|
||||
if (room.password) {
|
||||
password = prompt('This room is password protected. Please enter the password:');
|
||||
if (password === null) return; // User cancelled
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/chat-rooms/${room.id}/join`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password })
|
||||
});
|
||||
|
||||
if (response.ok && await response.json()) {
|
||||
currentRoom = room;
|
||||
showPage('chat');
|
||||
document.getElementById('chat-room-name').textContent = currentRoom.name;
|
||||
connectToChat();
|
||||
} else {
|
||||
alert('Failed to join room. Please check the password and try again.');
|
||||
}
|
||||
}
|
||||
|
||||
function connectToChat() {
|
||||
const socket = new SockJS('/ws');
|
||||
stompClient = Stomp.over(socket);
|
||||
|
||||
stompClient.connect({}, onConnected, onError);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function onConnected() {
|
||||
// Subscribe to the Public Topic
|
||||
stompClient.subscribe('/topic/public', onMessageReceived);
|
||||
|
||||
// Tell your username to the server
|
||||
stompClient.send("/app/chat.addUser",
|
||||
{},
|
||||
JSON.stringify({sender: username, type: 'JOIN'})
|
||||
)
|
||||
stompClient.subscribe(`/topic/rooms/${currentRoom.id}`, onMessageReceived);
|
||||
stompClient.send(`/app/chat/${currentRoom.id}/addUser`, {}, JSON.stringify({ sender: username, type: 'JOIN' }));
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
const connectingElement = document.createElement('li');
|
||||
connectingElement.classList.add('list-group-item', 'list-group-item-danger');
|
||||
connectingElement.textContent = 'Could not connect to WebSocket server. Please refresh this page to try again!';
|
||||
messageArea.appendChild(connectingElement);
|
||||
console.error('WebSocket Error:', error);
|
||||
alert('Could not connect to chat. Please try again.');
|
||||
showPage('roomSelection');
|
||||
}
|
||||
|
||||
function sendMessage(event) {
|
||||
messageForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const messageContent = messageInput.value.trim();
|
||||
if (messageContent && stompClient) {
|
||||
const chatMessage = {
|
||||
sender: username,
|
||||
content: messageInput.value
|
||||
};
|
||||
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
|
||||
const chatMessage = { sender: username, content: messageContent, type: 'CHAT' };
|
||||
stompClient.send(`/app/chat/${currentRoom.id}/sendMessage`, {}, JSON.stringify(chatMessage));
|
||||
messageInput.value = '';
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
}, true);
|
||||
|
||||
function onMessageReceived(payload) {
|
||||
const message = JSON.parse(payload.body);
|
||||
|
||||
const messageElement = document.createElement('li');
|
||||
messageElement.classList.add('list-group-item');
|
||||
|
||||
@@ -158,10 +205,11 @@
|
||||
}
|
||||
|
||||
messageElement.appendChild(document.createTextNode(message.content));
|
||||
|
||||
messageArea.appendChild(messageElement);
|
||||
messageArea.scrollTop = messageArea.scrollHeight;
|
||||
}
|
||||
|
||||
showPage('username');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
Reference in New Issue
Block a user