Corp/docs/completed/CHAT_CACHING_FIX.md
Игорь Надеин 48631f43fd Реорганизация документации и скриптов проекта
- Создана структура 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 приложения
- Вся документация организована в структурированные папки
2025-12-26 13:49:51 +03:00

7.9 KiB
Raw Blame History

Исправление проблемы кэширования сообщений

Проблема

При отправке сообщения в чате, затем возврате к списку чатов (кнопка "К чатам") и повторном открытии того же чата - отправленное сообщение исчезает.

Причина

Браузер агрессивно кэширует HTML страницы чатов. Когда вы:

  1. Открываете чат → браузер получает HTML с N сообщениями
  2. Отправляете сообщение через WebSocket → сообщение добавляется в DOM И сохраняется в БД
  3. Возвращаетесь к списку чатов
  4. Снова открываете тот же чат → браузер возвращает закэшированный 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: Отправка и проверка сообщения

  1. Откройте любой чат
  2. Отправьте сообщение "Тест 123"
  3. Нажмите "К чатам"
  4. Снова откройте тот же чат
  5. ✅ Сообщение "Тест 123" должно быть видно

Сценарий 2: Множественная отправка

  1. Откройте чат
  2. Отправьте 3 сообщения подряд
  3. Закройте и откройте чат несколько раз
  4. ✅ Все 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 и ChatListView
  • backend/communications/consumers.py - логика создания сообщений (не изменялась)

Дата исправления

30 ноября 2025 г.

Статус

✅ Исправлено и протестировано