12 KiB
12 KiB
Миграция на единое WebSocket соединение
Что изменилось
До (старая архитектура - 2 WebSocket):
Пользователь → 2 WebSocket соединения:
├─ ws://host/ws/chats/ (глобальный - для списка чатов и бейджа)
└─ ws://host/ws/chat/123/ (конкретный чат - только если открыт)
Проблемы:
- Двойная подписка на группы
chat_{id} - Дублирование событий
- Больше нагрузка на сервер
- Сложность управления
После (новая архитектура - 1 WebSocket):
Пользователь → 1 WebSocket соединение:
└─ ws://host/ws/ (универсальное для пользователя)
├─ Чаты: список + активный чат
├─ Уведомления (звонки, события, задачи)
├─ Бейджи в sidebar
├─ Онлайн-статус
└─ Другие real-time события
Преимущества:
- ✅ Одно соединение вместо двух
- ✅ Нет дублирования событий
- ✅ Меньше нагрузка на сервер (50% соединений)
- ✅ Проще отладка
- ✅ Быстрее переключение между чатами
- ✅ Универсальное для всех типов обновлений
- ✅ Масштабируемость (легко добавить новые типы событий)
Изменения в файлах
Backend
1. communications/user_consumer.py (НОВЫЙ)
Универсальный Consumer для всех real-time операций пользователя.
Назначение:
- 🔵 Чаты: подписка на все чаты, отслеживание активного
- 🔔 Уведомления: (будущее) звонки, события, задачи
- 🔢 Бейджи: обновление счетчиков в sidebar
- 🟢 Статусы: (будущее) онлайн/офлайн пользователей
- 📅 События: (будущее) календарь, документы и др.
Ключевые особенности:
- Подписывается на все чаты пользователя при подключении
- Отслеживает активный чат (
active_chat_id) - Отправляет полные данные для активного чата
- Отправляет компактные данные для обновления списка
- Управляется через действия:
open_chat,close_chat
2. communications/routing.py (ОБНОВЛЕН)
# Универсальный WebSocket endpoint
re_path(r"^ws/$", UserConsumer.as_asgi(), name="ws_user")
Frontend
3. static/js/components/userWebSocket.js (НОВЫЙ)
Универсальный клиентский модуль для WebSocket пользователя.
API:
const api = {
// Управление чатом
openChat(chatId, loadHistory),
closeChat(chatId),
// Отправка сообщений
sendMessage(content),
// Реакции
addReaction(messageId, emoji),
removeReaction(messageId, emoji),
// Редактирование
editMessage(messageId, content),
deleteMessage(messageId),
// Отметка прочитанного
markRead(chatId),
// Голосование
votePoll(pollId, optionIds),
// Состояние
getActiveChatId(),
isConnected()
};
4. templates/base.html (ОБНОВЛЕН)
- Заменен
initChatWebSocketнаinitUnifiedChatWebSocket - Удален отдельный глобальный WebSocket для списка чатов
- Обновлен
initSidebarBadgeдля работы с событиями из единого WS
Протокол WebSocket
Клиент → Сервер (действия)
// Открыть чат
{
action: "open_chat",
chat_id: 123,
load_history: true
}
// Закрыть чат
{
action: "close_chat",
chat_id: 123
}
// Отправить сообщение
{
action: "send_message",
content: "Привет!"
}
// Добавить реакцию
{
action: "add_reaction",
message_id: 456,
emoji: "👍"
}
// Индикатор печати
{
action: "typing"
}
// Отметить прочитанным
{
action: "mark_read",
chat_id: 123
}
Сервер → Клиент (события)
// Новое сообщение (полные данные для активного чата)
{
type: "new_message",
chat_id: 123,
message: { id, content, author_id, ... }
}
// Обновление списка (компактные данные для всех чатов)
{
type: "list_update",
chat_id: 123,
message: { id, content, author_id, created_ts }
}
// Реакция добавлена
{
type: "reaction_added",
chat_id: 123,
message_id: 456,
emoji: "👍",
user_id: 789
}
// Пользователь печатает
{
type: "typing_start",
chat_id: 123,
user_id: 789,
user_name: "Иван"
}
// Ping для keepalive
{
type: "ping",
timestamp: "2025-12-01T12:00:00Z"
}
Логика работы
При загрузке страницы:
-
Любая страница (авторизованный пользователь):
- Создается единое WebSocket соединение:
ws://host/ws/user/ - Сервер подписывает на все чаты пользователя
- Начинается прослушивание событий
- Создается единое WebSocket соединение:
-
Страница чата (/chats/123/):
- WebSocket уже открыт
- Клиент отправляет
open_chatсchat_id: 123 - Сервер устанавливает
active_chat_id = 123 - Сервер отправляет начальную историю (если запрошена)
- Клиент начинает получать полные данные сообщений
-
Переход к другому чату:
- Клиент отправляет
close_chatдля текущего - Клиент отправляет
open_chatдля нового - WebSocket остается открытым!
- Клиент отправляет
При получении нового сообщения:
На сервере (UnifiedChatConsumer.chat_message):
chat_id = event['chat_id']
# Если это активный чат - полные данные
if chat_id == self.active_chat_id:
send_json({"type": "new_message", "message": full_data})
# Для списка - компактные данные
send_json({"type": "list_update", "message": compact_data})
На клиенте:
// new_message - добавляем в DOM активного чата
handleNewMessage(data) {
const msgEl = createMessageElement(data.message);
scrollEl.appendChild(msgEl);
}
// list_update - обновляем карточку чата + бейдж
handleListUpdate(data) {
updateChatCard(data.chat_id, data.message);
badgeManager.incrementChat(data.chat_id);
}
Обратная совместимость
Для плавной миграции:
- Старые endpoints закомментированы, но не удалены
- API совместимо:
window.chatWebSocketApiиwindow.chatWebSocketработают - События совместимы:
chat:ws-ready,chat:list-updateи др.
План полной миграции
Этап 1: ✅ Создание (выполнено)
- Создан
UnifiedChatConsumer - Создан
unifiedChatWebSocket.js - Обновлен routing
- Обновлен base.html
Этап 2: Тестирование
- Проверить работу в разных браузерах
- Проверить переключение между чатами
- Проверить обновление бейджа
- Проверить индикатор "печатает..."
- Проверить реакции и редактирование
Этап 3: Очистка
- Удалить
ChatConsumerиChatListConsumer - Удалить
chatWebSocket.js - Удалить старые endpoints из routing
- Обновить документацию
Тестирование
Проверка соединения:
// В консоли браузера
console.log(window.chatWebSocketApi);
console.log(window.chatWebSocketApi.isConnected()); // true
// Открыть чат
window.chatWebSocketApi.openChat(123, true);
// Отправить сообщение
window.chatWebSocketApi.sendMessage("Тест");
Проверка в Network DevTools:
- Открыть DevTools → Network → WS
- Найти соединение
ws/user/ - Проверить входящие/исходящие сообщения
- Убедиться, что только одно WebSocket соединение активно
Производительность
До:
1 пользователь = 2 WebSocket (если открыт чат) или 1 (если только список)
100 пользователей с открытыми чатами = 200 WebSocket соединений
После:
1 пользователь = 1 WebSocket (всегда)
100 пользователей = 100 WebSocket соединений
Экономия: 50% соединений!
Известные ограничения
- История сообщений: Загружается только при
load_history: true - Переподключение: Автоматическое переподключение до 5 попыток
- Keepalive: Ping каждые 20 секунд (можно настроить)
Troubleshooting
Проблема: WebSocket не подключается
Решение:
// Проверить URL
console.log(location.host); // должен быть правильный хост
// Проверить авторизацию
console.log(window.currentUserId); // должен быть ID пользователя
Проблема: Сообщения не появляются
Решение:
// Проверить активный чат
console.log(window.chatWebSocketApi.getActiveChatId()); // должен быть chat_id
// Переоткрыть чат
const chatId = parseInt(document.getElementById('chatScroll').dataset.chatId);
window.chatWebSocketApi.closeChat(chatId);
window.chatWebSocketApi.openChat(chatId, true);
Проблема: Бейдж не обновляется
Решение: Проверить, что события диспатчатся:
window.addEventListener('chat:list-update', (e) => {
console.log('List update:', e.detail);
});
Дополнительные улучшения (будущее)
- Redis для typing status - кэширование статуса печати
- Compression - сжатие WebSocket сообщений
- Binary protocol - для вложений
- Presence - онлайн статус пользователей
- Read receipts - уведомления о прочтении
- Message queue - очередь для offline сообщений
Статус: READY FOR TESTING Дата: 1 декабря 2025 г. Автор: IgorNadein