Add "Temps-Reel"
parent
a07089ce2c
commit
d97ef2f87d
1 changed files with 285 additions and 0 deletions
285
Temps-Reel.-.md
Normal file
285
Temps-Reel.-.md
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
# ⚡ Architecture Temps Réel
|
||||
|
||||
> WebSocket via Socket.io : rooms, événements, reconnexion et synchronisation.
|
||||
|
||||
---
|
||||
|
||||
## Pourquoi le Temps Réel ?
|
||||
|
||||
QueueMed repose sur la **mise à jour instantanée** de 3 interfaces simultanément :
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ Médecin appelle le suivant │
|
||||
│ │ │
|
||||
│ │ < 100ms │
|
||||
│ │ │
|
||||
│ ├──────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ 📱 Patient #13 │ │ 📺 Écran salle │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ "C'est votre │ │ ┌────────────┐ │ │
|
||||
│ │ tour !" │ │ │ #13 │ │ │
|
||||
│ │ │ │ │ APPELÉ │ │ │
|
||||
│ └──────────────────┘ │ └────────────┘ │ │
|
||||
│ └──────────────────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ 📱 Patient #14 │ │ 📱 Patient #15 │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ Position: 1er │ │ Position: 2ème │ │
|
||||
│ │ (était 2ème) │ │ (était 3ème) │ │
|
||||
│ └──────────────────┘ └──────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Socket.io
|
||||
|
||||
### Initialisation
|
||||
|
||||
Le serveur Socket.io est intégré au serveur Express principal :
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ server/_core/index.ts │
|
||||
│ │
|
||||
│ const httpServer = createServer(app); │
|
||||
│ const io = new Server(httpServer, { │
|
||||
│ cors: { origin: "*" }, │
|
||||
│ transports: ["websocket", "polling"] │
|
||||
│ }); │
|
||||
│ │
|
||||
│ // Exposé globalement pour accès depuis les procédures tRPC │
|
||||
│ (global as any).__socketIo = io; │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Accès depuis les Procédures
|
||||
|
||||
```typescript
|
||||
// Dans routers.ts
|
||||
function getIo(): Server {
|
||||
return (global as any).__socketIo;
|
||||
}
|
||||
|
||||
// Utilisation dans une procédure
|
||||
const io = getIo();
|
||||
io.to(`clinic:${clinicId}`).emit("queue:updated", data);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Système de Rooms
|
||||
|
||||
Socket.io utilise des **rooms** pour isoler les communications par cabinet et par patient :
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ROOMS SOCKET.IO │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ clinic:{clinicId} │ │
|
||||
│ │ │ │
|
||||
│ │ Membres : Médecin + Écran d'affichage │ │
|
||||
│ │ Événements reçus : queue:updated, patient:called │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ patient:{patientToken} │ │
|
||||
│ │ │ │
|
||||
│ │ Membres : 1 patient (navigateur unique) │ │
|
||||
│ │ Événements reçus : position:updated, turn:called │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ display:{clinicId} │ │
|
||||
│ │ │ │
|
||||
│ │ Membres : Écran(s) d'affichage du cabinet │ │
|
||||
│ │ Événements reçus : display:update, display:called │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Connexion Client
|
||||
|
||||
```
|
||||
Patient ouvre /queue/{token}
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ socket.emit("join:patient", { │
|
||||
│ patientToken: "abc123", │
|
||||
│ clinicId: 5 │
|
||||
│ }); │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Serveur : │
|
||||
│ socket.join(`patient:abc123`); │
|
||||
│ socket.join(`clinic:5`); │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Événements WebSocket
|
||||
|
||||
### Événements Émis par le Serveur
|
||||
|
||||
| Événement | Room cible | Payload | Déclencheur |
|
||||
|:----------|:-----------|:--------|:------------|
|
||||
| `queue:updated` | `clinic:{id}` | `{ entries, stats }` | Tout changement de file |
|
||||
| `patient:called` | `patient:{token}` | `{ ticketNumber, message }` | Médecin appelle |
|
||||
| `position:updated` | `patient:{token}` | `{ position, estimatedWait }` | Recalcul positions |
|
||||
| `display:update` | `display:{id}` | `{ calledNumber, waiting, next }` | Changement d'état |
|
||||
| `display:called` | `display:{id}` | `{ ticketNumber, animation }` | Nouveau patient appelé |
|
||||
| `queue:closed` | `clinic:{id}` | `{ message }` | File fermée |
|
||||
| `queue:opened` | `clinic:{id}` | `{ message }` | File ouverte |
|
||||
|
||||
### Événements Reçus par le Serveur
|
||||
|
||||
| Événement | Émetteur | Payload | Action |
|
||||
|:----------|:---------|:--------|:-------|
|
||||
| `join:patient` | Patient | `{ patientToken, clinicId }` | Rejoint les rooms |
|
||||
| `join:clinic` | Médecin | `{ clinicId }` | Rejoint room clinic |
|
||||
| `join:display` | Écran | `{ clinicId }` | Rejoint room display |
|
||||
| `disconnect` | Tous | — | Nettoyage rooms |
|
||||
|
||||
---
|
||||
|
||||
## Flux de Données Complet
|
||||
|
||||
### Appel du Suivant
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 1. Médecin → tRPC mutation queue.callNext({ clinicId: 5 }) │
|
||||
│ │
|
||||
│ 2. Serveur : │
|
||||
│ ├── UPDATE queue_entries SET status='called' WHERE id=42 │
|
||||
│ ├── UPDATE queue_entries SET position=position-1 (tous après) │
|
||||
│ ├── INSERT analytics_events (patient_called) │
|
||||
│ │ │
|
||||
│ ├── io.to("patient:abc123").emit("patient:called", { │
|
||||
│ │ ticketNumber: 13, message: "C'est votre tour !" │
|
||||
│ │ }) │
|
||||
│ │ │
|
||||
│ ├── io.to("display:5").emit("display:called", { │
|
||||
│ │ ticketNumber: 13, animation: "pulse" │
|
||||
│ │ }) │
|
||||
│ │ │
|
||||
│ ├── io.to("clinic:5").emit("queue:updated", { │
|
||||
│ │ entries: [...], stats: { waiting: 4, called: 1 } │
|
||||
│ │ }) │
|
||||
│ │ │
|
||||
│ └── Pour chaque patient restant : │
|
||||
│ io.to("patient:{token}").emit("position:updated", { │
|
||||
│ position: newPos, estimatedWait: newWait │
|
||||
│ }) │
|
||||
│ │
|
||||
│ 3. Résultat : Toutes les interfaces se mettent à jour < 100ms │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Patient Rejoint la File
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 1. Patient → tRPC mutation queue.join({ token, name? }) │
|
||||
│ │
|
||||
│ 2. Serveur : │
|
||||
│ ├── Valide le token QR (non expiré, file ouverte) │
|
||||
│ ├── INSERT queue_entries (nouveau patient) │
|
||||
│ ├── INSERT analytics_events (patient_joined) │
|
||||
│ │ │
|
||||
│ ├── io.to("clinic:5").emit("queue:updated", { ... }) │
|
||||
│ ├── io.to("display:5").emit("display:update", { ... }) │
|
||||
│ │ │
|
||||
│ └── Retourne { ticketNumber, position, estimatedWait } │
|
||||
│ │
|
||||
│ 3. Patient : socket.emit("join:patient", { patientToken, clinicId})│
|
||||
│ → Rejoint la room pour recevoir les mises à jour │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reconnexion Automatique
|
||||
|
||||
### Côté Client
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ Socket.io gère automatiquement la reconnexion : │
|
||||
│ │
|
||||
│ Connexion ──── Perte réseau ──── Tentative 1 (1s) ──── Échec │
|
||||
│ │ │
|
||||
│ Tentative 2 (2s) ──── Échec │
|
||||
│ │ │
|
||||
│ Tentative 3 (4s) ──── ✅ Reconnecté│
|
||||
│ │ │
|
||||
│ Re-join rooms │
|
||||
│ Sync état actuel │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Indicateur de Connexion
|
||||
|
||||
L'écran d'affichage montre un indicateur visuel :
|
||||
|
||||
| État | Indicateur | Description |
|
||||
|:-----|:-----------|:------------|
|
||||
| 🟢 Connecté | Pastille verte | WebSocket actif |
|
||||
| 🟡 Reconnexion | Pastille orange clignotante | Tentative en cours |
|
||||
| 🔴 Déconnecté | Pastille rouge | Connexion perdue |
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
| Métrique | Valeur | Description |
|
||||
|:---------|:-------|:------------|
|
||||
| Latence | < 100ms | Temps entre action et mise à jour |
|
||||
| Connexions simultanées | ~1000 | Par instance serveur |
|
||||
| Taille message | ~200 bytes | Payload JSON compressé |
|
||||
| Reconnexion | < 5s | Délai max de reconnexion |
|
||||
| Transports | WebSocket + Polling | Fallback automatique |
|
||||
|
||||
---
|
||||
|
||||
## Scalabilité
|
||||
|
||||
Pour un déploiement multi-instances, Socket.io supporte un adapter Redis :
|
||||
|
||||
```
|
||||
Instance 1 ──┐
|
||||
│
|
||||
Instance 2 ──┼──── Redis Pub/Sub ──── Synchronisation rooms
|
||||
│
|
||||
Instance 3 ──┘
|
||||
```
|
||||
|
||||
> **Note v1.3 :** Le déploiement actuel est mono-instance. L'adapter Redis sera ajouté en v2.0 si nécessaire.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**[[Home]] | [[Architecture-Technique]] | [[Systeme-Anti-Triche]] | [[Guide-Patient]]**
|
||||
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue