Corp/CHAT_CACHING_FIX.md

211 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Исправление проблемы кэширования сообщений
## Проблема
При отправке сообщения в чате, затем возврате к списку чатов (кнопка "К чатам") и повторном открытии того же чата - **отправленное сообщение исчезает**.
### Причина
Браузер агрессивно кэширует HTML страницы чатов. Когда вы:
1. Открываете чат → браузер получает HTML с N сообщениями
2. Отправляете сообщение через WebSocket → сообщение добавляется в DOM И сохраняется в БД
3. Возвращаетесь к списку чатов
4. Снова открываете тот же чат → **браузер возвращает закэшированный HTML** (без нового сообщения)
### Диагностика
Сообщение **правильно сохраняется в БД**:
```python
# 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 (страница чата)
```python
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 (список чатов)
```python
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 кэша при создании сообщения
```python
# В consumers.py после создания сообщения
from django.core.cache import cache
cache.delete(f'chat_detail_{chat.id}')
```
**Минус**: Нужно кэшировать view с `@cache_page`, что усложняет код
### 2. ❌ Версионирование URL с timestamp
```javascript
// При возврате к чату добавлять ?v=timestamp
location.href = `/communications/chats/${chatId}/?v=${Date.now()}`;
```
**Минус**: Загрязняет history браузера, не работает с back button
### 3. ❌ Service Worker для управления кэшем
```javascript
// Отлавливать запросы к /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 запрос
❌ **Нет оффлайн режима** - без кэша страница не загрузится без интернета
### Оптимизация (если потребуется)
Можно использовать **частичное кэширование**:
```python
response['Cache-Control'] = 'max-age=10, must-revalidate' # Кэш на 10 сек
```
Или **ETag** для условных запросов:
```python
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 г.
## Статус
✅ **Исправлено и протестировано**