travail sur websocket
This commit is contained in:
parent
f6e1b6b488
commit
4177bf8c52
16 changed files with 741 additions and 23 deletions
|
|
@ -92,3 +92,46 @@
|
|||
{
|
||||
id=$id
|
||||
}
|
||||
|
||||
# Websocket (http://localhost:8080/ws)
|
||||
- Vider le channel
|
||||
- Insérer une donnée dans le channel
|
||||
- Modifier une donnée dans le channel
|
||||
- Supprimer une donnée du channel
|
||||
|
||||
## MATCHES
|
||||
## matches (MatchRestController, "/matches")
|
||||
- Lister tous les matches -> dans le channel Websocket
|
||||
- Lister tous les matches à partir d'une certaine date -> dans le channel Websocket
|
||||
- Lister les matches en cours (état : en cours) -> **GET "/matches/active"** -> List<MatchBean>
|
||||
- Lister les matches terminés (état : terminé) -> **GET "/matches/over"** -> List<MatchBean>
|
||||
- Lister les matches non commencés (état : non commencé) -> **GET "/matches/not-started"** -> List<MatchBean>
|
||||
- Afficher un match par id -> **GET "/matches/id=&id"** -> MatchBean
|
||||
- Mettre à jour les score d'un match récupéré par id -> **POST "/matches/update-match"**
|
||||
{
|
||||
id=$id,
|
||||
score1=$score1,
|
||||
score2=$score2
|
||||
}
|
||||
-> MatchBean
|
||||
- Ajouter un match -> **POST "/matches/add-match"**
|
||||
{
|
||||
date=$date,
|
||||
country=$country
|
||||
city=$city,
|
||||
weapon=$weapon,
|
||||
refereeID=$refereeID,
|
||||
player1ID=$player1ID,
|
||||
player2ID=$player2ID,
|
||||
|
||||
}
|
||||
-> MatchBean
|
||||
- Supprimer un match (supprimer élément de MatchRepository en récupérant l'id) -> **POST "/matches/delete-match"**
|
||||
{
|
||||
id=$id
|
||||
}
|
||||
|
||||
## REFEREES
|
||||
|
||||
|
||||
## PLAYERS
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
corriger les services - ~
|
||||
tester les restcontrollers via des requêtes http - ~
|
||||
créer la bdd avec toutes les tables (repositories)
|
||||
tester le websocket
|
||||
corriger les services - V
|
||||
tester les restcontrollers via des requêtes http - V
|
||||
créer la bdd avec toutes les tables (repositories) - V
|
||||
tester le websocket -
|
||||
prévoir l'utilisation en admin uniquement (spring security)
|
||||
faire le jackarta de l'api
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package fr.teamflash.fencerjudgeback.config
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer
|
||||
|
||||
const val CHANNEL_NAME: String = "/ws/topic"
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
open class WebSocketConfig : WebSocketMessageBrokerConfigurer {
|
||||
|
||||
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
|
||||
// Enable a simple memory-based message broker to send messages to clients
|
||||
// Prefix for messages FROM server TO client
|
||||
registry.enableSimpleBroker(CHANNEL_NAME)
|
||||
|
||||
// Prefix for messages FROM client TO server
|
||||
registry.setApplicationDestinationPrefixes("/ws")
|
||||
}
|
||||
|
||||
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
|
||||
// Register the "/ws" endpoint, enabling SockJS fallback options
|
||||
registry.addEndpoint("/ws")
|
||||
.setAllowedOriginPatterns("*") // Allow connections from any origin (adjust for production)
|
||||
.withSockJS()
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,9 @@ data class MatchBean(
|
|||
val player2ID:Long?=null,
|
||||
val refereeID: Long?=null,
|
||||
@field:Min(value = 0, message = "Score must be at least 0")
|
||||
val score1:Int=0,
|
||||
var score1:Int=0,
|
||||
@field:Min(value = 0, message = "Score must be at least 0")
|
||||
val score2:Int=0,
|
||||
var score2:Int=0,
|
||||
val date: String?=null,
|
||||
val state:String?=null
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ class MatchService(
|
|||
// Ajouter un match (admin)
|
||||
fun createMatch(newMatch: MatchBean): MatchBean {
|
||||
println("MatchService.createMatch : $newMatch")
|
||||
val savedMatch = matchRepository.save(newMatch)
|
||||
// Broadcast the new match via WebSocket
|
||||
// matchWebSocketController?.broadcastMatchUpdate(savedMatch, MatchUpdateMessage.UpdateType.MATCH_START)
|
||||
matchWebSocketController?.broadcastMatchUpdate(savedMatch, fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage.UpdateType.MATCH_START)
|
||||
|
||||
return matchRepository.save(newMatch)
|
||||
return savedMatch
|
||||
}
|
||||
|
||||
// Modifier un match (admin)
|
||||
|
|
@ -54,7 +55,9 @@ class MatchService(
|
|||
}
|
||||
|
||||
val updatedMatch = newMatch.copy(id = id)
|
||||
matchRepository.save(updatedMatch)
|
||||
val savedMatch = matchRepository.save(updatedMatch)
|
||||
// Broadcast the updated match via WebSocket
|
||||
matchWebSocketController?.broadcastMatchUpdate(savedMatch, fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage.UpdateType.SCORE_UPDATE)
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -62,10 +65,15 @@ class MatchService(
|
|||
fun deleteMatchById(id: Long?) : Int? {
|
||||
println("MatchService.deleteMatchById : $id")
|
||||
|
||||
if (getById(id) == null) {
|
||||
val match = getById(id)
|
||||
if (match == null) {
|
||||
println("MatchService.deleteMatchById : Match not found")
|
||||
return -1
|
||||
}
|
||||
|
||||
// Broadcast the match deletion via WebSocket before deleting
|
||||
matchWebSocketController?.broadcastMatchUpdate(match, fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage.UpdateType.MATCH_CANCEL)
|
||||
|
||||
matchRepository.deleteById(id!!)
|
||||
return 1
|
||||
}
|
||||
|
|
@ -83,4 +91,8 @@ class MatchService(
|
|||
return matchRepository.findAll()
|
||||
.filter { it.country == country }
|
||||
}
|
||||
|
||||
fun addMatch(match:MatchBean) {
|
||||
matchRepository.save(match)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,17 @@ package fr.teamflash.fencerjudgeback.services
|
|||
|
||||
import fr.teamflash.fencerjudgeback.entities.PlayerBean
|
||||
import fr.teamflash.fencerjudgeback.repositories.PlayerRepository
|
||||
import fr.teamflash.fencerjudgeback.websocket.controllers.PlayerWebSocketController
|
||||
import fr.teamflash.fencerjudgeback.websocket.models.PlayerUpdateMessage
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class PlayerService(@Autowired private val playerRepository: PlayerRepository) {
|
||||
class PlayerService(
|
||||
@Autowired private val playerRepository: PlayerRepository,
|
||||
@Lazy private val playerWebSocketController: PlayerWebSocketController? = null
|
||||
) {
|
||||
// Obtenir tous les joueurs (public)
|
||||
fun getAll(): List<PlayerBean> {
|
||||
println("PlayerService.getAll")
|
||||
|
|
@ -43,9 +49,12 @@ class PlayerService(@Autowired private val playerRepository: PlayerRepository) {
|
|||
}
|
||||
|
||||
// Ajouter un joueur (admin)
|
||||
fun createPlayer(referee: PlayerBean) : PlayerBean {
|
||||
println("PlayerService.createPlayer : $referee")
|
||||
return playerRepository.save(referee)
|
||||
fun createPlayer(player: PlayerBean) : PlayerBean {
|
||||
println("PlayerService.createPlayer : $player")
|
||||
val savedPlayer = playerRepository.save(player)
|
||||
// Broadcast the new player via WebSocket
|
||||
playerWebSocketController?.broadcastPlayerUpdate(savedPlayer, PlayerUpdateMessage.UpdateType.PLAYER_CREATE)
|
||||
return savedPlayer
|
||||
}
|
||||
|
||||
// Modifier un joueur (admin)
|
||||
|
|
@ -57,7 +66,9 @@ class PlayerService(@Autowired private val playerRepository: PlayerRepository) {
|
|||
}
|
||||
|
||||
val updatedPlayer = newPlayer.copy(id = id)
|
||||
playerRepository.save(updatedPlayer)
|
||||
val savedPlayer = playerRepository.save(updatedPlayer)
|
||||
// Broadcast the updated player via WebSocket
|
||||
playerWebSocketController?.broadcastPlayerUpdate(savedPlayer, PlayerUpdateMessage.UpdateType.PLAYER_UPDATE)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
|
@ -66,12 +77,20 @@ class PlayerService(@Autowired private val playerRepository: PlayerRepository) {
|
|||
fun deletePlayerById(id:Long?): Int {
|
||||
println("PlayerService.deletePlayer : $id")
|
||||
|
||||
if (getById(id) == null) {
|
||||
val player = getById(id)
|
||||
if (player == null) {
|
||||
println("PlayerService.deletePlayer : Player not found")
|
||||
return -1
|
||||
}
|
||||
|
||||
// Broadcast the player deletion via WebSocket before deleting
|
||||
playerWebSocketController?.broadcastPlayerUpdate(player, PlayerUpdateMessage.UpdateType.PLAYER_DELETE)
|
||||
|
||||
playerRepository.deleteById(id!!)
|
||||
return 1
|
||||
}
|
||||
|
||||
fun addPlayer(player:PlayerBean) {
|
||||
playerRepository.save(player)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,17 @@ package fr.teamflash.fencerjudgeback.services
|
|||
|
||||
import fr.teamflash.fencerjudgeback.entities.RefereeBean
|
||||
import fr.teamflash.fencerjudgeback.repositories.RefereeRepository
|
||||
import fr.teamflash.fencerjudgeback.websocket.controllers.RefereeWebSocketController
|
||||
import fr.teamflash.fencerjudgeback.websocket.models.RefereeUpdateMessage
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class RefereeService(@Autowired private val refereeRepository: RefereeRepository) {
|
||||
class RefereeService(
|
||||
@Autowired private val refereeRepository: RefereeRepository,
|
||||
@Lazy private val refereeWebSocketController: RefereeWebSocketController? = null
|
||||
) {
|
||||
// Obtenir tous les arbitres (public)
|
||||
fun getAll(): List<RefereeBean> {
|
||||
println("RefereeService.getReferees")
|
||||
|
|
@ -45,7 +51,10 @@ class RefereeService(@Autowired private val refereeRepository: RefereeRepository
|
|||
// Ajouter un arbitre (admin)
|
||||
fun createReferee(referee: RefereeBean) : RefereeBean {
|
||||
println("RefereeService.createReferee : $referee")
|
||||
return refereeRepository.save(referee)
|
||||
val savedReferee = refereeRepository.save(referee)
|
||||
// Broadcast the new referee via WebSocket
|
||||
refereeWebSocketController?.broadcastRefereeUpdate(savedReferee, RefereeUpdateMessage.UpdateType.REFEREE_CREATE)
|
||||
return savedReferee
|
||||
}
|
||||
|
||||
// Modifier un arbitre (admin)
|
||||
|
|
@ -55,8 +64,10 @@ class RefereeService(@Autowired private val refereeRepository: RefereeRepository
|
|||
return -1
|
||||
}
|
||||
|
||||
val updatedMatch = newReferee.copy(id = id)
|
||||
refereeRepository.save(updatedMatch)
|
||||
val updatedReferee = newReferee.copy(id = id)
|
||||
val savedReferee = refereeRepository.save(updatedReferee)
|
||||
// Broadcast the updated referee via WebSocket
|
||||
refereeWebSocketController?.broadcastRefereeUpdate(savedReferee, RefereeUpdateMessage.UpdateType.REFEREE_UPDATE)
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -64,11 +75,15 @@ class RefereeService(@Autowired private val refereeRepository: RefereeRepository
|
|||
fun deleteRefereeById(id:Long): Int {
|
||||
println("RefereeService.deleteReferee : $id")
|
||||
|
||||
if (getById(id) == null) {
|
||||
val referee = getById(id)
|
||||
if (referee == null) {
|
||||
println("RefereeService.deleteReferee : Referee not found")
|
||||
return -1
|
||||
}
|
||||
|
||||
// Broadcast the referee deletion via WebSocket before deleting
|
||||
refereeWebSocketController?.broadcastRefereeUpdate(referee, RefereeUpdateMessage.UpdateType.REFEREE_DELETE)
|
||||
|
||||
refereeRepository.deleteById(id)
|
||||
return 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test WebSocket Match</title>
|
||||
<style>
|
||||
#messageArea {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 1px solid #ddd;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
font-family: monospace;
|
||||
}
|
||||
input, button {
|
||||
padding: 8px;
|
||||
margin: 4px;
|
||||
}
|
||||
.input-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Test WebSocket Match</h2>
|
||||
|
||||
<div id="messageArea">Connexion...</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" id="matchId" placeholder="Match ID">
|
||||
<input type="number" id="player1Id" placeholder="Joueur 1 ID">
|
||||
<input type="number" id="player2Id" placeholder="Joueur 2 ID">
|
||||
<input type="number" id="refereeId" placeholder="Arbitre ID">
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" id="score1" placeholder="Score Joueur 1">
|
||||
<input type="number" id="score2" placeholder="Score Joueur 2">
|
||||
<input type="datetime-local" id="matchDate">
|
||||
</div>
|
||||
|
||||
<button onclick="sendMatch()">Envoyer Match</button>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/sockjs-client/dist/sockjs.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
|
||||
|
||||
<script>
|
||||
const stompClient = Stomp.over(new SockJS('/ws'));
|
||||
const channel = "/topic/match.updates";
|
||||
|
||||
stompClient.connect({}, function () {
|
||||
stompClient.subscribe(channel, function (message) {
|
||||
const msg = JSON.parse(message.body);
|
||||
console.log("Match reçu :", msg);
|
||||
displayMessage(msg);
|
||||
});
|
||||
document.getElementById('messageArea').textContent = 'Connecté au WebSocket';
|
||||
}, function (error) {
|
||||
document.getElementById('messageArea').textContent = 'Erreur WebSocket : ' + error;
|
||||
});
|
||||
|
||||
function sendMatch() {
|
||||
const match = {
|
||||
matchId: parseInt(document.getElementById("matchId").value),
|
||||
player1Id: parseInt(document.getElementById("player1Id").value),
|
||||
player2Id: parseInt(document.getElementById("player2Id").value),
|
||||
refereeId: parseInt(document.getElementById("refereeId").value),
|
||||
score1: parseInt(document.getElementById("score1").value),
|
||||
score2: parseInt(document.getElementById("score2").value),
|
||||
date: document.getElementById("matchDate").value
|
||||
};
|
||||
|
||||
stompClient.send("/app/match.update", {}, JSON.stringify(match));
|
||||
}
|
||||
|
||||
function displayMessage(match) {
|
||||
const area = document.getElementById("messageArea");
|
||||
const div = document.createElement("div");
|
||||
div.textContent = `Match ${match.matchId}: ${match.player1Id} (${match.score1}) vs ${match.player2Id} (${match.score2})`;
|
||||
area.appendChild(div);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# WebSocket Implementation for FencerJudge
|
||||
|
||||
This document explains how to use the WebSocket functionality in the FencerJudge application for real-time match updates.
|
||||
|
||||
## Overview
|
||||
|
||||
The WebSocket implementation allows for real-time updates of match information, including:
|
||||
- Match creation
|
||||
- Score updates
|
||||
- Match completion
|
||||
- Match cancellation
|
||||
|
||||
## WebSocket Endpoints
|
||||
|
||||
### Connection Endpoint
|
||||
```
|
||||
ws://localhost:8080/ws
|
||||
```
|
||||
|
||||
The WebSocket endpoint supports SockJS for fallback in browsers that don't support WebSockets natively.
|
||||
|
||||
## Message Topics
|
||||
|
||||
### Subscribe to Match Updates
|
||||
To receive real-time match updates, subscribe to:
|
||||
```
|
||||
/topic/match.updates
|
||||
```
|
||||
|
||||
### Send Match Updates
|
||||
To send match updates from the client to the server:
|
||||
```
|
||||
/app/match.update
|
||||
```
|
||||
|
||||
## Message Format
|
||||
|
||||
Match update messages use the following JSON format:
|
||||
|
||||
```json
|
||||
{
|
||||
"matchId": 1,
|
||||
"player1Id": 101,
|
||||
"player2Id": 102,
|
||||
"refereeId": 201,
|
||||
"score1": 5,
|
||||
"score2": 3,
|
||||
"date": "2023-06-01",
|
||||
"type": "SCORE_UPDATE"
|
||||
}
|
||||
```
|
||||
|
||||
The `type` field can have the following values:
|
||||
- `SCORE_UPDATE`: When a match score is updated
|
||||
- `MATCH_START`: When a new match is created
|
||||
- `MATCH_END`: When a match is completed
|
||||
- `MATCH_CANCEL`: When a match is cancelled
|
||||
|
||||
## Integration with REST API
|
||||
|
||||
The WebSocket functionality is integrated with the REST API. Any changes made through the REST endpoints will automatically trigger WebSocket messages to all connected clients.
|
||||
|
||||
REST endpoints:
|
||||
- `GET /api/matches`: Get all matches
|
||||
- `GET /api/matches/{id}`: Get a match by ID
|
||||
- `POST /api/matches`: Create a new match (triggers MATCH_START WebSocket message)
|
||||
- `PUT /api/matches/{id}`: Update a match (triggers SCORE_UPDATE WebSocket message)
|
||||
- `PATCH /api/matches/{id}/score`: Update just the score (triggers SCORE_UPDATE WebSocket message)
|
||||
- `DELETE /api/matches/{id}`: Delete a match (triggers MATCH_CANCEL WebSocket message)
|
||||
|
||||
## Client-Side Example (JavaScript)
|
||||
|
||||
```javascript
|
||||
// Using SockJS and STOMP client
|
||||
import SockJS from 'sockjs-client';
|
||||
import { Client } from '@stomp/stompjs';
|
||||
|
||||
// Create a new STOMP client
|
||||
const socket = new SockJS('http://localhost:8080/ws');
|
||||
const stompClient = new Client({
|
||||
webSocketFactory: () => socket,
|
||||
debug: (str) => console.log(str)
|
||||
});
|
||||
|
||||
// Connect to the WebSocket server
|
||||
stompClient.activate();
|
||||
|
||||
// Subscribe to match updates
|
||||
stompClient.onConnect = (frame) => {
|
||||
console.log('Connected to WebSocket');
|
||||
|
||||
// Subscribe to match updates
|
||||
stompClient.subscribe('/topic/match.updates', (message) => {
|
||||
const matchUpdate = JSON.parse(message.body);
|
||||
console.log('Received match update:', matchUpdate);
|
||||
|
||||
// Handle the update in your application
|
||||
// e.g., update UI, play sound, etc.
|
||||
});
|
||||
};
|
||||
|
||||
// Send a match update (e.g., when a referee updates a score)
|
||||
function sendMatchUpdate(matchId, score1, score2) {
|
||||
if (stompClient.connected) {
|
||||
stompClient.publish({
|
||||
destination: '/app/match.update',
|
||||
body: JSON.stringify({
|
||||
matchId: matchId,
|
||||
score1: score1,
|
||||
score2: score2,
|
||||
type: 'SCORE_UPDATE'
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
The WebSocket endpoint is currently configured to allow connections from any origin (`*`). For production environments, this should be restricted to specific allowed origins.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package fr.teamflash.fencerjudgeback.websocket
|
||||
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
|
||||
data class Data(val id: String, val value: Any)
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/ws")
|
||||
class WebSocketController(private val messagingTemplate: SimpMessagingTemplate) {
|
||||
|
||||
// Insérer une donnée dans le channel
|
||||
@MessageMapping("/insert")
|
||||
fun insertData(data: Data) {
|
||||
// Logique d'insertion
|
||||
messagingTemplate.convertAndSend("/topic/data", data)
|
||||
}
|
||||
|
||||
// Modifier une donnée existente dans le channel
|
||||
@MessageMapping("/modify")
|
||||
fun modifyData(data: Data) {
|
||||
// Logique de modification
|
||||
messagingTemplate.convertAndSend("/topic/data", data)
|
||||
}
|
||||
|
||||
// Supprimer une donnée existante dans le channel
|
||||
@MessageMapping("/delete")
|
||||
fun deleteData(id: String) {
|
||||
// Logique de suppression
|
||||
messagingTemplate.convertAndSend("/topic/data/delete", id)
|
||||
}
|
||||
|
||||
// Vider le channel
|
||||
@MessageMapping("/clear")
|
||||
fun clearChannel() {
|
||||
// Logique pour vider le channel
|
||||
messagingTemplate.convertAndSend("/topic/data/clear", null)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package fr.teamflash.fencerjudgeback.websocket.controllers
|
||||
|
||||
import fr.teamflash.fencerjudgeback.config.CHANNEL_NAME
|
||||
import fr.teamflash.fencerjudgeback.entities.MatchBean
|
||||
import fr.teamflash.fencerjudgeback.services.MatchService
|
||||
import fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage
|
||||
import org.springframework.context.event.EventListener
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.messaging.handler.annotation.SendTo
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.socket.messaging.SessionSubscribeEvent
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/ws")
|
||||
class MatchWebSocketController(
|
||||
private val matchService: MatchService,
|
||||
private val messagingTemplate: SimpMessagingTemplate
|
||||
) {
|
||||
|
||||
private val messageHistory = ArrayList<MatchBean>()
|
||||
|
||||
@MessageMapping("/matches")
|
||||
fun receiveMessage(message: MatchBean) {
|
||||
println("/ws/matches $message")
|
||||
messageHistory.add(message)
|
||||
|
||||
// Envoyer la liste des messages sur le channel
|
||||
//Si la variable est dans le même package il faut enlever WebSocketConfig.
|
||||
messagingTemplate.convertAndSend(CHANNEL_NAME, messageHistory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle match update requests from clients
|
||||
* Client sends to: /app/match.update
|
||||
* Server broadcasts to: /topic/match.updates
|
||||
*/
|
||||
@MessageMapping("/match.update")
|
||||
@SendTo("/topic/match.updates")
|
||||
fun handleMatchUpdate(matchUpdateMessage: MatchUpdateMessage): MatchUpdateMessage {
|
||||
// Create a MatchBean from the update message
|
||||
val matchBean = MatchBean(
|
||||
id = matchUpdateMessage.matchId,
|
||||
player1ID = matchUpdateMessage.player1Id,
|
||||
player2ID = matchUpdateMessage.player2Id,
|
||||
refereeID = matchUpdateMessage.refereeId,
|
||||
score1 = matchUpdateMessage.score1,
|
||||
score2 = matchUpdateMessage.score2,
|
||||
date = matchUpdateMessage.date
|
||||
)
|
||||
|
||||
// Update the match in the database
|
||||
matchService.updateMatch(matchBean.id, matchBean)
|
||||
|
||||
// Return the update message to be broadcast to all subscribers
|
||||
return matchUpdateMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a match update to all connected clients
|
||||
* This method can be called from other services
|
||||
*/
|
||||
fun broadcastMatchUpdate(matchBean: MatchBean, type: MatchUpdateMessage.UpdateType = MatchUpdateMessage.UpdateType.SCORE_UPDATE) {
|
||||
val updateMessage = MatchUpdateMessage.fromMatchBean(matchBean, type)
|
||||
messagingTemplate.convertAndSend("/topic/match.updates", updateMessage)
|
||||
}
|
||||
|
||||
//A mettre dans le controller
|
||||
@EventListener
|
||||
fun handleWebSocketSubscribeListener(event: SessionSubscribeEvent) {
|
||||
val headerAccessor = StompHeaderAccessor.wrap(event.message)
|
||||
if (CHANNEL_NAME == headerAccessor.destination) {
|
||||
messagingTemplate.convertAndSend(CHANNEL_NAME, messageHistory)
|
||||
}
|
||||
}
|
||||
|
||||
fun addPoint(match:MatchBean, playerId:Long) {
|
||||
when (playerId) {
|
||||
match.player1ID -> match.score1 += 1
|
||||
match.player2ID -> match.score2 += 1
|
||||
}
|
||||
matchService.updateMatch(match.id, match)
|
||||
broadcastMatchUpdate(match)
|
||||
}
|
||||
|
||||
fun minusPoint(match:MatchBean, playerId:Long) {
|
||||
when (playerId) {
|
||||
match.player1ID -> if (match.score1 > 0) match.score1 -= 1
|
||||
match.player2ID -> if (match.score2 > 0) match.score2 -= 1
|
||||
}
|
||||
matchService.updateMatch(match.id, match)
|
||||
broadcastMatchUpdate(match)
|
||||
}
|
||||
|
||||
fun addMatchtoMainList(match:MatchBean) {
|
||||
matchService.addMatch(match)
|
||||
broadcastMatchUpdate(match, MatchUpdateMessage.UpdateType.NEW_MATCH)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package fr.teamflash.fencerjudgeback.websocket.controllers
|
||||
|
||||
import fr.teamflash.fencerjudgeback.config.CHANNEL_NAME
|
||||
import fr.teamflash.fencerjudgeback.entities.PlayerBean
|
||||
import fr.teamflash.fencerjudgeback.services.PlayerService
|
||||
import fr.teamflash.fencerjudgeback.websocket.models.PlayerUpdateMessage
|
||||
import org.springframework.context.event.EventListener
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.messaging.handler.annotation.SendTo
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.socket.messaging.SessionSubscribeEvent
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/ws")
|
||||
class PlayerWebSocketController(
|
||||
private val playerService: PlayerService,
|
||||
private val messagingTemplate: SimpMessagingTemplate
|
||||
) {
|
||||
|
||||
private val messageHistory = ArrayList<PlayerBean>()
|
||||
|
||||
@MessageMapping("/player.update")
|
||||
@SendTo("/topic/player.updates")
|
||||
fun handlePlayerUpdate(playerUpdateMessage: PlayerUpdateMessage): PlayerUpdateMessage {
|
||||
// Create a PlayerBean from the update message
|
||||
val playerBean = PlayerBean(
|
||||
id = playerUpdateMessage.playerId,
|
||||
name = playerUpdateMessage.name,
|
||||
firstName = playerUpdateMessage.firstName,
|
||||
club = playerUpdateMessage.club
|
||||
)
|
||||
|
||||
// Update the player in the database
|
||||
playerService.updatePlayer(playerBean.id ?: 0, playerBean)
|
||||
|
||||
// Return the update message to be broadcast to all subscribers
|
||||
return playerUpdateMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a player update to all connected clients
|
||||
* This method can be called from other services
|
||||
*/
|
||||
fun broadcastPlayerUpdate(playerBean: PlayerBean, type: PlayerUpdateMessage.UpdateType = PlayerUpdateMessage.UpdateType.PLAYER_UPDATE) {
|
||||
val updateMessage = PlayerUpdateMessage.fromPlayerBean(playerBean, type)
|
||||
messagingTemplate.convertAndSend("/topic/player.updates", updateMessage)
|
||||
}
|
||||
|
||||
@EventListener
|
||||
fun handleWebSocketSubscribeListener(event: SessionSubscribeEvent) {
|
||||
val headerAccessor = StompHeaderAccessor.wrap(event.message)
|
||||
if ("/topic/player.updates" == headerAccessor.destination) {
|
||||
// When a client subscribes, send them the current players
|
||||
val players = playerService.getAll()
|
||||
players.forEach { player ->
|
||||
messagingTemplate.convertAndSend(
|
||||
"/topic/player.updates",
|
||||
PlayerUpdateMessage.fromPlayerBean(player, PlayerUpdateMessage.UpdateType.PLAYER_UPDATE)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addPlayerToMainList(playerBean: PlayerBean) {
|
||||
playerService.addPlayer(playerBean)
|
||||
broadcastPlayerUpdate(playerBean, PlayerUpdateMessage.UpdateType.PLAYER_CREATE)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package fr.teamflash.fencerjudgeback.websocket.controllers
|
||||
|
||||
import fr.teamflash.fencerjudgeback.config.CHANNEL_NAME
|
||||
import fr.teamflash.fencerjudgeback.entities.RefereeBean
|
||||
import fr.teamflash.fencerjudgeback.services.RefereeService
|
||||
import fr.teamflash.fencerjudgeback.websocket.models.RefereeUpdateMessage
|
||||
import org.springframework.context.event.EventListener
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.messaging.handler.annotation.SendTo
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.socket.messaging.SessionSubscribeEvent
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/ws")
|
||||
class RefereeWebSocketController(
|
||||
private val refereeService: RefereeService,
|
||||
private val messagingTemplate: SimpMessagingTemplate
|
||||
) {
|
||||
|
||||
private val messageHistory = ArrayList<RefereeBean>()
|
||||
|
||||
@MessageMapping("/referee.update")
|
||||
@SendTo("/topic/referee.updates")
|
||||
fun handleRefereeUpdate(refereeUpdateMessage: RefereeUpdateMessage): RefereeUpdateMessage {
|
||||
// Create a RefereeBean from the update message
|
||||
val refereeBean = RefereeBean(
|
||||
id = refereeUpdateMessage.refereeId,
|
||||
name = refereeUpdateMessage.name,
|
||||
firstName = refereeUpdateMessage.firstName,
|
||||
qualification = refereeUpdateMessage.qualification
|
||||
)
|
||||
|
||||
// Update the referee in the database
|
||||
refereeService.updateReferee(refereeBean.id ?: 0, refereeBean)
|
||||
|
||||
// Return the update message to be broadcast to all subscribers
|
||||
return refereeUpdateMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a referee update to all connected clients
|
||||
* This method can be called from other services
|
||||
*/
|
||||
fun broadcastRefereeUpdate(refereeBean: RefereeBean, type: RefereeUpdateMessage.UpdateType = RefereeUpdateMessage.UpdateType.REFEREE_UPDATE) {
|
||||
val updateMessage = RefereeUpdateMessage.fromRefereeBean(refereeBean, type)
|
||||
messagingTemplate.convertAndSend("/topic/referee.updates", updateMessage)
|
||||
}
|
||||
|
||||
@EventListener
|
||||
fun handleWebSocketSubscribeListener(event: SessionSubscribeEvent) {
|
||||
val headerAccessor = StompHeaderAccessor.wrap(event.message)
|
||||
if ("/topic/referee.updates" == headerAccessor.destination) {
|
||||
// When a client subscribes, send them the current referees
|
||||
val referees = refereeService.getAll()
|
||||
referees.forEach { referee ->
|
||||
messagingTemplate.convertAndSend(
|
||||
"/topic/referee.updates",
|
||||
RefereeUpdateMessage.fromRefereeBean(referee, RefereeUpdateMessage.UpdateType.REFEREE_UPDATE)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package fr.teamflash.fencerjudgeback.websocket.models
|
||||
|
||||
import fr.teamflash.fencerjudgeback.entities.MatchBean
|
||||
|
||||
/**
|
||||
* Message model for match updates sent through WebSocket
|
||||
*/
|
||||
data class MatchUpdateMessage(
|
||||
val matchId: Long,
|
||||
val player1Id: Long?,
|
||||
val player2Id: Long?,
|
||||
val refereeId: Long?,
|
||||
val score1: Int,
|
||||
val score2: Int,
|
||||
val date: String?,
|
||||
val type: UpdateType = UpdateType.SCORE_UPDATE
|
||||
) {
|
||||
enum class UpdateType {
|
||||
SCORE_UPDATE,
|
||||
MATCH_START,
|
||||
MATCH_END,
|
||||
MATCH_CANCEL,
|
||||
NEW_MATCH,
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a MatchUpdateMessage from a MatchBean
|
||||
*/
|
||||
fun fromMatchBean(matchBean: MatchBean, type: UpdateType = UpdateType.SCORE_UPDATE): MatchUpdateMessage {
|
||||
return MatchUpdateMessage(
|
||||
matchId = matchBean.id ?: 0,
|
||||
player1Id = matchBean.player1ID,
|
||||
player2Id = matchBean.player2ID,
|
||||
refereeId = matchBean.refereeID,
|
||||
score1 = matchBean.score1,
|
||||
score2 = matchBean.score2,
|
||||
date = matchBean.date,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package fr.teamflash.fencerjudgeback.websocket.models
|
||||
|
||||
import fr.teamflash.fencerjudgeback.entities.PlayerBean
|
||||
|
||||
/**
|
||||
* Message model for player updates sent through WebSocket
|
||||
*/
|
||||
data class PlayerUpdateMessage(
|
||||
val playerId: Long,
|
||||
val name: String?,
|
||||
val firstName: String?,
|
||||
val club: String?,
|
||||
val type: UpdateType = UpdateType.PLAYER_UPDATE
|
||||
) {
|
||||
enum class UpdateType {
|
||||
PLAYER_UPDATE,
|
||||
PLAYER_CREATE,
|
||||
PLAYER_DELETE
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a PlayerUpdateMessage from a PlayerBean
|
||||
*/
|
||||
fun fromPlayerBean(playerBean: PlayerBean, type: UpdateType = UpdateType.PLAYER_UPDATE): PlayerUpdateMessage {
|
||||
return PlayerUpdateMessage(
|
||||
playerId = playerBean.id ?: 0,
|
||||
name = playerBean.name,
|
||||
firstName = playerBean.firstName,
|
||||
club = playerBean.club,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package fr.teamflash.fencerjudgeback.websocket.models
|
||||
|
||||
import fr.teamflash.fencerjudgeback.entities.RefereeBean
|
||||
|
||||
/**
|
||||
* Message model for referee updates sent through WebSocket
|
||||
*/
|
||||
data class RefereeUpdateMessage(
|
||||
val refereeId: Long,
|
||||
val name: String?,
|
||||
val firstName: String?,
|
||||
val qualification: String?,
|
||||
val type: UpdateType = UpdateType.REFEREE_UPDATE
|
||||
) {
|
||||
enum class UpdateType {
|
||||
REFEREE_UPDATE,
|
||||
REFEREE_CREATE,
|
||||
REFEREE_DELETE
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a RefereeUpdateMessage from a RefereeBean
|
||||
*/
|
||||
fun fromRefereeBean(refereeBean: RefereeBean, type: UpdateType = UpdateType.REFEREE_UPDATE): RefereeUpdateMessage {
|
||||
return RefereeUpdateMessage(
|
||||
refereeId = refereeBean.id ?: 0,
|
||||
name = refereeBean.name,
|
||||
firstName = refereeBean.firstName,
|
||||
qualification = refereeBean.qualification,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue