v0.0.17.3 Переработан websocket, добавлена обработка ping/pong
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
madipo2611 2025-08-11 15:49:16 +03:00
parent 039c8447a7
commit db667f224b
4 changed files with 46 additions and 13 deletions

View File

@ -159,17 +159,22 @@ func (r *mutationResolver) SendMessage(ctx context.Context, receiverID int, cont
return nil, errors.New("не авторизован") return nil, errors.New("не авторизован")
} }
// Проверяем, что не отправляем сообщение себе
if senderID == receiverID {
return nil, errors.New("cannot send message to yourself")
}
chat, err := r.Services.Chat.GetOrCreateChat(ctx, senderID, receiverID) chat, err := r.Services.Chat.GetOrCreateChat(ctx, senderID, receiverID)
if err != nil { if err != nil {
return nil, fmt.Errorf("ошибка создания чата: %v", err) return nil, fmt.Errorf("ошибка создания чата: %v", err)
} }
return r.Services.Chat.SendMessage(ctx, senderID, chat.ID, content) message, err := r.Services.Chat.SendMessage(ctx, senderID, chat.ID, content)
if err != nil {
return nil, err
}
// Рассылаем сообщение через WebSocket
if r.Services.ChatHub != nil {
r.Services.ChatHub.Broadcast(message)
}
return message, nil
} }
// MarkAsRead - помечает сообщение как прочитанное // MarkAsRead - помечает сообщение как прочитанное

View File

@ -9,6 +9,7 @@ import (
"tailly_back_v2/internal/service" "tailly_back_v2/internal/service"
"tailly_back_v2/internal/ws" "tailly_back_v2/internal/ws"
"tailly_back_v2/pkg/auth" "tailly_back_v2/pkg/auth"
"time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -105,13 +106,17 @@ func (h *ChatHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
} }
func (h *ChatHandler) readPump(ctx context.Context, conn *websocket.Conn, client *ws.Client, userID int) { func (h *ChatHandler) readPump(ctx context.Context, conn *websocket.Conn, client *ws.Client, userID int) {
defer func() { ticker := time.NewTicker(30 * time.Second)
h.hub.UnregisterClient(client) defer ticker.Stop()
conn.Close()
}()
for { for {
select { select {
case <-ticker.C:
// Отправляем ping
if err := conn.WriteJSON(map[string]string{"type": "ping"}); err != nil {
log.Printf("Ping error: %v", err)
return
}
case <-ctx.Done(): case <-ctx.Done():
return return
default: default:
@ -125,11 +130,15 @@ func (h *ChatHandler) readPump(ctx context.Context, conn *websocket.Conn, client
if err := conn.ReadJSON(&msg); err != nil { if err := conn.ReadJSON(&msg); err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket read error: %v", err) log.Printf("WebSocket error: %v", err)
} }
return return
} }
if msg.Type == "pong" {
continue
}
// Обработка ping/pong // Обработка ping/pong
if msg.Type == "ping" { if msg.Type == "ping" {
conn.WriteJSON(map[string]string{"type": "pong"}) conn.WriteJSON(map[string]string{"type": "pong"})

View File

@ -39,30 +39,38 @@ func (s *chatService) SendMessage(ctx context.Context, senderID, chatID int, con
return nil, err return nil, err
} }
// Проверяем, что отправитель является участником чата
if senderID != chat.User1ID && senderID != chat.User2ID { if senderID != chat.User1ID && senderID != chat.User2ID {
return nil, errors.New("user is not a participant of this chat") return nil, errors.New("user is not a participant of this chat")
} }
// Определяем получателя
receiverID := chat.User1ID receiverID := chat.User1ID
if senderID == chat.User1ID { if senderID == chat.User1ID {
receiverID = chat.User2ID receiverID = chat.User2ID
} }
// Создаем сообщение
message := &domain.Message{ message := &domain.Message{
ChatID: chatID, ChatID: chatID,
SenderID: senderID, SenderID: senderID,
ReceiverID: receiverID, ReceiverID: receiverID, // Гарантируем что receiverID всегда установлен
Content: content, Content: content,
Status: "sent", Status: "sent",
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
// Сохраняем в БД
if err := s.chatRepo.SaveMessage(ctx, message); err != nil { if err := s.chatRepo.SaveMessage(ctx, message); err != nil {
return nil, err return nil, err
} }
// Отправляем сообщение через WebSocket // Отправляем через WebSocket только один раз
if s.hub != nil { if s.hub != nil {
// Добавляем проверку перед рассылкой
if message.ReceiverID == 0 {
return nil, errors.New("receiver ID is required")
}
s.hub.Broadcast(message) s.hub.Broadcast(message)
} }

View File

@ -1,6 +1,7 @@
package ws package ws
import ( import (
"log"
"sync" "sync"
"tailly_back_v2/internal/domain" "tailly_back_v2/internal/domain"
) )
@ -36,6 +37,16 @@ func (h *Hub) UnregisterClient(client *Client) {
} }
func (h *Hub) Broadcast(message *domain.Message) { 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 h.broadcast <- message
} }