127 lines
2.9 KiB
Go
127 lines
2.9 KiB
Go
package ws
|
||
|
||
import (
|
||
"log"
|
||
"sync"
|
||
"tailly_back_v2/internal/domain"
|
||
"time"
|
||
)
|
||
|
||
type Client struct {
|
||
UserID int
|
||
Send chan *domain.Message
|
||
LastSeen time.Time
|
||
CloseChan chan bool
|
||
}
|
||
|
||
type Hub struct {
|
||
clients map[int]*Client
|
||
register chan *Client
|
||
unregister chan *Client
|
||
broadcast chan *domain.Message
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
func NewHub() *Hub {
|
||
hub := &Hub{
|
||
clients: make(map[int]*Client),
|
||
register: make(chan *Client),
|
||
unregister: make(chan *Client),
|
||
broadcast: make(chan *domain.Message, 100),
|
||
}
|
||
go hub.cleanupInactiveClients()
|
||
return hub
|
||
}
|
||
|
||
// Добавляем периодическую очистку неактивных клиентов
|
||
func (h *Hub) cleanupInactiveClients() {
|
||
ticker := time.NewTicker(5 * time.Minute)
|
||
defer ticker.Stop()
|
||
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
h.mu.Lock()
|
||
for userID, client := range h.clients {
|
||
if time.Since(client.LastSeen) > 10*time.Minute {
|
||
log.Printf("Cleaning up inactive client: %d", userID)
|
||
close(client.Send)
|
||
delete(h.clients, userID)
|
||
}
|
||
}
|
||
h.mu.Unlock()
|
||
}
|
||
}
|
||
}
|
||
|
||
func (h *Hub) RegisterClient(client *Client) {
|
||
client.LastSeen = time.Now()
|
||
h.register <- client
|
||
}
|
||
|
||
func (h *Hub) UnregisterClient(client *Client) {
|
||
h.unregister <- client
|
||
}
|
||
|
||
func (h *Hub) Broadcast(message *domain.Message) {
|
||
if message == nil || message.SenderID == 0 {
|
||
log.Println("Attempt to broadcast invalid message")
|
||
return
|
||
}
|
||
|
||
if message.ReceiverID == 0 {
|
||
log.Printf("Message %d has no receiver", message.ID)
|
||
return
|
||
}
|
||
|
||
h.broadcast <- message
|
||
}
|
||
|
||
func (h *Hub) Run() {
|
||
for {
|
||
select {
|
||
case client := <-h.register:
|
||
h.mu.Lock()
|
||
// Обновляем LastSeen при регистрации
|
||
client.LastSeen = time.Now()
|
||
|
||
// Закрываем предыдущее соединение если есть
|
||
if existing, ok := h.clients[client.UserID]; ok {
|
||
select {
|
||
case existing.CloseChan <- true: // Уведомляем о закрытии
|
||
default:
|
||
}
|
||
close(existing.Send)
|
||
}
|
||
h.clients[client.UserID] = client
|
||
h.mu.Unlock()
|
||
|
||
case client := <-h.unregister:
|
||
h.mu.Lock()
|
||
if c, ok := h.clients[client.UserID]; ok && c == client {
|
||
close(c.Send)
|
||
delete(h.clients, client.UserID)
|
||
}
|
||
h.mu.Unlock()
|
||
|
||
case message := <-h.broadcast:
|
||
h.mu.RLock()
|
||
// Отправляем всем клиентам, кто участвует в этом чате
|
||
for _, client := range h.clients {
|
||
if client.UserID == message.SenderID || client.UserID == message.ReceiverID {
|
||
client.LastSeen = time.Now() // Обновляем время активности
|
||
|
||
select {
|
||
case client.Send <- message:
|
||
// Сообщение успешно отправлено
|
||
default:
|
||
log.Printf("Client %d channel busy, skipping message", client.UserID)
|
||
// Не закрываем соединение, просто пропускаем сообщение
|
||
}
|
||
}
|
||
}
|
||
h.mu.RUnlock()
|
||
}
|
||
}
|
||
}
|