- Создана структура docs/ с категориями: completed, guides, architecture, diagnostic, in-progress, reports - Перемещено 47+ MD файлов из корня проекта в docs/ - Удалено 6 устаревших/дублирующихся файлов - Создана структура backend/scripts/ для тестов и утилит - Перемещено 35 Python скриптов в backend/scripts/ - Создана структура backend/docs/ для технической документации - Перемещено 12 MD файлов backend в backend/docs/ - Перемещены LDAP тестовые данные в backend/tests/fixtures/ - Добавлены README файлы для навигации - Добавлены правила в .github/copilot-instructions.md для размещения документации Результат: - Корень проекта: только README.md - Корень backend/: только manage.py и Django приложения - Вся документация организована в структурированные папки
7.9 KiB
Исправление проблемы кэширования сообщений
Проблема
При отправке сообщения в чате, затем возврате к списку чатов (кнопка "К чатам") и повторном открытии того же чата - отправленное сообщение исчезает.
Причина
Браузер агрессивно кэширует HTML страницы чатов. Когда вы:
- Открываете чат → браузер получает HTML с N сообщениями
- Отправляете сообщение через WebSocket → сообщение добавляется в DOM И сохраняется в БД
- Возвращаетесь к списку чатов
- Снова открываете тот же чат → браузер возвращает закэшированный HTML (без нового сообщения)
Диагностика
Сообщение правильно сохраняется в БД:
# communications/consumers.py
@database_sync_to_async
def _create_message(self, chat: Chat, user, text: str, reply_to_id=None) -> Message:
msg = Message.objects.create( # ✅ Создается в БД
chat=chat,
author=user,
content=text[:2000],
reply_to_id=reply_to_id
)
return msg
Но Django view возвращает полный HTML со всеми сообщениями из БД, а браузер его кэширует.
Решение
Добавлены HTTP заголовки для запрета кэширования в двух view:
1. ChatDetailView (страница чата)
class ChatDetailView(LoginRequiredMixin, DetailView, FormView):
# ...
def dispatch(self, request, *args, **kwargs):
"""Добавляем заголовки для отключения кэширования"""
response = super().dispatch(request, *args, **kwargs)
# Запрещаем кэширование страницы чата в браузере
response['Cache-Control'] = (
'no-cache, no-store, must-revalidate, max-age=0'
)
response['Pragma'] = 'no-cache'
response['Expires'] = '0'
return response
2. ChatListView (список чатов)
class ChatListView(LoginRequiredMixin, ListView):
# ...
def dispatch(self, request, *args, **kwargs):
"""Добавляем заголовки для отключения кэширования"""
response = super().dispatch(request, *args, **kwargs)
# Запрещаем кэширование списка чатов
response['Cache-Control'] = (
'no-cache, no-store, must-revalidate, max-age=0'
)
response['Pragma'] = 'no-cache'
response['Expires'] = '0'
return response
HTTP заголовки
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
- no-cache - браузер должен проверить с сервером перед использованием кэша
- no-store - полностью запрещает сохранение в кэш
- must-revalidate - кэш должен быть проверен после истечения
- max-age=0 - кэш истекает немедленно
Pragma: no-cache
- Обратная совместимость с HTTP/1.0
Expires: 0
- Дата истечения кэша (в прошлом)
Результат
✅ До: Браузер кэшировал HTML чата → при повторном открытии показывал старую версию
✅ После: Браузер запрашивает свежий HTML каждый раз → всегда показывает актуальные сообщения из БД
Тестирование
Сценарий 1: Отправка и проверка сообщения
- Откройте любой чат
- Отправьте сообщение "Тест 123"
- Нажмите "К чатам"
- Снова откройте тот же чат
- ✅ Сообщение "Тест 123" должно быть видно
Сценарий 2: Множественная отправка
- Откройте чат
- Отправьте 3 сообщения подряд
- Закройте и откройте чат несколько раз
- ✅ Все 3 сообщения должны оставаться видимыми
Сценарий 3: Проверка в разных браузерах
- ✅ Chrome
- ✅ Firefox
- ✅ Edge
- ✅ Safari
Проверка HTTP заголовков (DevTools)
Откройте DevTools → Network → выберите запрос к /communications/chats/{id}/
Response Headers должны содержать:
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
Pragma: no-cache
Expires: 0
Альтернативные решения (не использованы)
1. ❌ Инвалидация Django кэша при создании сообщения
# В consumers.py после создания сообщения
from django.core.cache import cache
cache.delete(f'chat_detail_{chat.id}')
Минус: Нужно кэшировать view с @cache_page, что усложняет код
2. ❌ Версионирование URL с timestamp
// При возврате к чату добавлять ?v=timestamp
location.href = `/communications/chats/${chatId}/?v=${Date.now()}`;
Минус: Загрязняет history браузера, не работает с back button
3. ❌ Service Worker для управления кэшем
// Отлавливать запросы к /communications/chats/ и всегда делать network-first
self.addEventListener('fetch', event => {
if (event.request.url.includes('/communications/chats/')) {
event.respondWith(fetch(event.request));
}
});
Минус: Требует регистрации Service Worker, сложнее в поддержке
4. ✅ HTTP заголовки Cache-Control (выбрано)
Плюсы:
- Простое решение
- Работает во всех браузерах
- Не требует изменений в JS
- Соответствует HTTP стандарту
Влияние на производительность
Потенциальные проблемы
❌ Больше запросов к серверу - каждое открытие чата = новый HTTP запрос
❌ Нет оффлайн режима - без кэша страница не загрузится без интернета
Оптимизация (если потребуется)
Можно использовать частичное кэширование:
response['Cache-Control'] = 'max-age=10, must-revalidate' # Кэш на 10 сек
Или ETag для условных запросов:
from django.views.decorators.http import condition
@condition(etag_func=lambda r, *a, **k: f"chat-{kwargs['pk']}-{Chat.objects.get(pk=kwargs['pk']).updated_at.timestamp()}")
class ChatDetailView(...):
...
Но для текущей задачи полное отключение кэша - оптимальное решение.
Связанные файлы
backend/communications/views.py- добавлены методыdispatch()в ChatDetailView и ChatListViewbackend/communications/consumers.py- логика создания сообщений (не изменялась)
Дата исправления
30 ноября 2025 г.
Статус
✅ Исправлено и протестировано