Problem:
- After wizard form submission, need to wait for claim data from n8n
- Claim data comes via Redis channel claim:plan:{session_token}
- Need to display confirmation form with claim data
Solution:
1. Backend: Added SSE endpoint /api/v1/claim-plan/{session_token}
- Subscribes to Redis channel claim:plan:{session_token}
- Streams claim data from n8n to frontend
- Handles timeouts and errors gracefully
2. Frontend: Added subscription to claim:plan channel
- StepWizardPlan: After form submission, subscribes to SSE
- Waits for claim_plan_ready event
- Shows loading message while waiting
- On success: saves claimPlanData and shows confirmation step
3. New component: StepClaimConfirmation
- Displays claim confirmation form in iframe
- Receives claimPlanData from parent
- Generates HTML form (placeholder - should call n8n for real HTML)
- Handles confirmation/cancellation via postMessage
4. ClaimForm: Added conditional step for confirmation
- Shows StepClaimConfirmation when showClaimConfirmation=true
- Step appears after StepWizardPlan
- Only visible when claimPlanData is available
Flow:
1. User fills wizard form → submits
2. Form data sent to n8n via /api/v1/claims/wizard
3. Frontend subscribes to SSE /api/v1/claim-plan/{session_token}
4. n8n processes data → publishes to Redis claim:plan:{session_token}
5. Backend receives → streams to frontend via SSE
6. Frontend receives → shows StepClaimConfirmation
7. User confirms → proceeds to next step
Files:
- backend/app/api/events.py: Added stream_claim_plan endpoint
- frontend/src/components/form/StepWizardPlan.tsx: Added subscribeToClaimPlan
- frontend/src/components/form/StepClaimConfirmation.tsx: New component
- frontend/src/pages/ClaimForm.tsx: Added confirmation step to steps array
347 lines
12 KiB
Markdown
347 lines
12 KiB
Markdown
# Лог сессии: 2025-11-20 - Session Persistence & Draft Management
|
||
|
||
## Дата: 20 ноября 2025
|
||
|
||
---
|
||
|
||
## 🎯 Основные задачи
|
||
|
||
1. ✅ Реализация сохранения сессии в Redis
|
||
2. ✅ Восстановление сессии из localStorage после перезагрузки страницы
|
||
3. ✅ Исправление передачи `unified_id` и `claim_id` в n8n при отправке визарда
|
||
4. ✅ Исправление приоритета `session_id` при загрузке черновика
|
||
|
||
---
|
||
|
||
## 📝 Выполненные изменения
|
||
|
||
### 1. Backend - Session API (`/api/v1/session`)
|
||
|
||
**Файл:** `ticket_form/backend/app/api/session.py`
|
||
**Создан новый роутер** для управления сессиями в Redis:
|
||
|
||
- `POST /api/v1/session/create` - создание сессии с TTL 24 часа
|
||
- `POST /api/v1/session/verify` - проверка валидности сессии
|
||
- `POST /api/v1/session/logout` - удаление сессии
|
||
|
||
**Данные сессии:**
|
||
```python
|
||
{
|
||
"session_token": "sess_...",
|
||
"unified_id": "usr_...",
|
||
"phone": "79262306381",
|
||
"contact_id": "320096",
|
||
"verified_at": "2025-11-20T14:54:01.279Z",
|
||
"expires_at": "2025-11-21T14:54:01.279Z"
|
||
}
|
||
```
|
||
|
||
**Файл:** `ticket_form/backend/app/main.py`
|
||
- Добавлен импорт `session` роутера
|
||
- Подключен роутер: `app.include_router(session.router)`
|
||
|
||
---
|
||
|
||
### 2. Frontend - Session Management
|
||
|
||
**Файл:** `ticket_form/frontend/src/components/form/Step1Phone.tsx`
|
||
**Версия:** v2.0 - 2025-11-20 14:40
|
||
|
||
**Изменения:**
|
||
- После успешной SMS-верификации вызывается `POST /api/v1/session/create`
|
||
- `session_token` сохраняется в `localStorage`
|
||
- Добавлены подробные debug логи для отладки сессии
|
||
|
||
**Код:**
|
||
```typescript
|
||
// После получения unified_id от n8n
|
||
const sessionPayload = {
|
||
session_token: finalSessionId,
|
||
unified_id: unifiedIdToPass,
|
||
phone: formData.phone!,
|
||
contact_id: result.contact_id,
|
||
};
|
||
|
||
const sessionResponse = await fetch('/api/v1/session/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(sessionPayload),
|
||
});
|
||
|
||
if (sessionResponse.ok) {
|
||
localStorage.setItem('session_token', finalSessionId);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Frontend - Session Restoration
|
||
|
||
**Файл:** `ticket_form/frontend/src/pages/ClaimForm.tsx`
|
||
**Версия:** v3.8 - 2025-11-20 15:10
|
||
|
||
**Изменения:**
|
||
|
||
#### A. Проверка сессии при загрузке компонента:
|
||
```typescript
|
||
useEffect(() => {
|
||
const sessionToken = localStorage.getItem('session_token');
|
||
if (!sessionToken) return;
|
||
|
||
// Проверяем валидность сессии
|
||
fetch('/api/v1/session/verify', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ session_token: sessionToken }),
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success && data.valid) {
|
||
// Восстанавливаем данные сессии
|
||
updateFormData({
|
||
unified_id: data.unified_id,
|
||
phone: data.phone,
|
||
contact_id: data.contact_id,
|
||
});
|
||
setIsPhoneVerified(true);
|
||
checkDrafts(data.unified_id, data.phone, formData.session_id);
|
||
} else {
|
||
localStorage.removeItem('session_token');
|
||
}
|
||
});
|
||
}, []);
|
||
```
|
||
|
||
#### B. Кнопка "Выход":
|
||
```typescript
|
||
const handleExitToList = () => {
|
||
const sessionToken = localStorage.getItem('session_token');
|
||
if (sessionToken) {
|
||
fetch('/api/v1/session/logout', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ session_token: sessionToken }),
|
||
});
|
||
localStorage.removeItem('session_token');
|
||
}
|
||
|
||
// Сброс формы
|
||
updateFormData({
|
||
unified_id: undefined,
|
||
phone: '',
|
||
contact_id: '',
|
||
});
|
||
setIsPhoneVerified(false);
|
||
setCurrentStep(0);
|
||
};
|
||
```
|
||
|
||
#### C. Исправление приоритета `session_id` при загрузке черновика:
|
||
|
||
**До:**
|
||
```typescript
|
||
session_id: claim.session_token || sessionIdRef.current, // ❌ Старый из черновика
|
||
```
|
||
|
||
**После:**
|
||
```typescript
|
||
session_id: sessionIdRef.current || formData.session_id, // ✅ Текущий актуальный
|
||
```
|
||
|
||
**Причина:** При загрузке черновика старый `session_id` из БД перезаписывал новый, полученный от n8n после SMS-верификации.
|
||
|
||
---
|
||
|
||
### 4. Frontend - Wizard Payload Fix
|
||
|
||
**Файл:** `ticket_form/frontend/src/components/form/StepWizardPlan.tsx`
|
||
**Версия:** v1.4 - 2025-11-20 15:00
|
||
|
||
**Проблема:** При отправке визарда в n8n не передавались `unified_id` и `claim_id`.
|
||
|
||
**Исправление:**
|
||
```typescript
|
||
// Добавляем unified_id и claim_id (если есть)
|
||
if (formData.unified_id) formPayload.append('unified_id', formData.unified_id);
|
||
if (formData.claim_id) formPayload.append('claim_id', formData.claim_id);
|
||
```
|
||
|
||
**Debug лог:**
|
||
```typescript
|
||
console.log('📤 Отправка в n8n:', {
|
||
session_id: formData.session_id,
|
||
unified_id: formData.unified_id,
|
||
claim_id: formData.claim_id,
|
||
contact_id: formData.contact_id,
|
||
phone: formData.phone,
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### 5. Docker Volumes для Hot Module Replacement
|
||
|
||
**Файл:** `ticket_form/docker-compose.yml`
|
||
|
||
**Добавлен volume для фронтенда:**
|
||
```yaml
|
||
ticket_form_frontend:
|
||
volumes:
|
||
- ./frontend/src:/app/src:ro
|
||
```
|
||
|
||
**Цель:** Включить live reload (HMR) при изменении файлов фронтенда без пересборки контейнера.
|
||
|
||
---
|
||
|
||
## 🔄 Workflow изменений
|
||
|
||
### Полный цикл работы с сессией:
|
||
|
||
1. **Пользователь вводит телефон и SMS-код**
|
||
- → Step1Phone вызывает n8n для верификации
|
||
- → n8n возвращает `unified_id`, `contact_id`, `session_id`
|
||
- → Step1Phone создаёт сессию в Redis через `POST /api/v1/session/create`
|
||
- → `session_token` сохраняется в `localStorage`
|
||
|
||
2. **Пользователь закрывает/обновляет страницу**
|
||
- → ClaimForm при загрузке проверяет `localStorage`
|
||
- → Вызывается `POST /api/v1/session/verify`
|
||
- → Если сессия валидна, восстанавливаются `unified_id`, `phone`, `contact_id`
|
||
- → Автоматически загружаются черновики
|
||
|
||
3. **Пользователь продолжает черновик**
|
||
- → При загрузке черновика используется ТЕКУЩИЙ `session_id` (не старый из БД)
|
||
- → При отправке визарда передаются `unified_id`, `claim_id`, актуальный `session_id`
|
||
|
||
4. **Пользователь нажимает "Выход"**
|
||
- → Вызывается `POST /api/v1/session/logout`
|
||
- → Сессия удаляется из Redis
|
||
- → `session_token` удаляется из `localStorage`
|
||
- → Редирект на Step1Phone
|
||
|
||
---
|
||
|
||
## 🐛 Исправленные проблемы
|
||
|
||
### Проблема #1: Session token not found in localStorage
|
||
**Причина:** Backend эндпоинт `/api/v1/session/create` не был подключен.
|
||
**Решение:** Добавлен импорт и подключение роутера в `main.py`.
|
||
|
||
### Проблема #2: unified_id не передавался в n8n
|
||
**Причина:** В `StepWizardPlan.tsx` не было строки `formPayload.append('unified_id', ...)`.
|
||
**Решение:** Добавлена передача `unified_id` и `claim_id` в FormData.
|
||
|
||
### Проблема #3: Старый session_id перезаписывал новый
|
||
**Причина:** При загрузке черновика приоритет был у `claim.session_token` из БД.
|
||
**Решение:** Изменён приоритет на `sessionIdRef.current` (текущая сессия).
|
||
|
||
### Проблема #4: Frontend не обновлялся без пересборки
|
||
**Причина:** Docker контейнер не монтировал исходники фронтенда.
|
||
**Решение:** Добавлен volume `./frontend/src:/app/src:ro` для HMR.
|
||
|
||
---
|
||
|
||
## 📊 Результаты
|
||
|
||
### Payload в n8n после исправлений:
|
||
|
||
```json
|
||
{
|
||
"stage": "wizard",
|
||
"form_id": "ticket_form",
|
||
"session_id": "sess_e6e3f447-8770-47af-ae87-8c022c686d9f", ✅ Актуальный от n8n
|
||
"unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c", ✅ Добавлен
|
||
"claim_id": "19572ab7-cad5-4f8d-a622-4617487c07ce", ✅ Добавлен
|
||
"contact_id": "320096",
|
||
"phone": "79262306381",
|
||
"wizard_plan": "{...}",
|
||
"wizard_answers": "{...}",
|
||
"wizard_skipped_documents": "[]"
|
||
}
|
||
```
|
||
|
||
### Сессия в Redis (TTL 24 часа):
|
||
|
||
```
|
||
Key: crm:session:sess_e6e3f447-8770-47af-ae87-8c022c686d9f
|
||
Value: {
|
||
"session_token": "sess_e6e3f447-8770-47af-ae87-8c022c686d9f",
|
||
"unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c",
|
||
"phone": "79262306381",
|
||
"contact_id": "320096",
|
||
"verified_at": "2025-11-20T14:54:01.279Z",
|
||
"expires_at": "2025-11-21T14:54:01.279Z"
|
||
}
|
||
TTL: 86400 секунд
|
||
```
|
||
|
||
---
|
||
|
||
## 📦 Изменённые файлы
|
||
|
||
1. ✅ `ticket_form/backend/app/api/session.py` (создан)
|
||
2. ✅ `ticket_form/backend/app/main.py` (добавлен импорт session)
|
||
3. ✅ `ticket_form/frontend/src/components/form/Step1Phone.tsx` (v2.0)
|
||
4. ✅ `ticket_form/frontend/src/pages/ClaimForm.tsx` (v3.8)
|
||
5. ✅ `ticket_form/frontend/src/components/form/StepWizardPlan.tsx` (v1.4)
|
||
6. ✅ `ticket_form/docker-compose.yml` (добавлен volume)
|
||
|
||
---
|
||
|
||
## 🧪 Тестирование
|
||
|
||
### Сценарий 1: Новая сессия
|
||
- ✅ Ввод телефона и SMS-кода
|
||
- ✅ Создание сессии в Redis
|
||
- ✅ Сохранение session_token в localStorage
|
||
- ✅ Отображение черновиков (если есть)
|
||
|
||
### Сценарий 2: Восстановление сессии
|
||
- ✅ Ctrl+F5 (hard refresh)
|
||
- ✅ Автоматическая верификация сессии
|
||
- ✅ Восстановление unified_id, phone, contact_id
|
||
- ✅ Автоматическое отображение черновиков
|
||
|
||
### Сценарий 3: Продолжение черновика
|
||
- ✅ Выбор черновика из списка
|
||
- ✅ Загрузка данных черновика
|
||
- ✅ Сохранение актуального session_id (не старого из БД)
|
||
- ✅ Отправка в n8n с unified_id, claim_id, session_id
|
||
|
||
### Сценарий 4: Выход
|
||
- ✅ Нажатие кнопки "🚪 Выход"
|
||
- ✅ Удаление сессии из Redis
|
||
- ✅ Удаление session_token из localStorage
|
||
- ✅ Редирект на Step1Phone
|
||
|
||
---
|
||
|
||
## 🎉 Итоги
|
||
|
||
Реализован полноценный механизм управления сессиями:
|
||
- Персистентность через Redis (TTL 24 часа)
|
||
- Восстановление после перезагрузки страницы
|
||
- Корректная передача идентификаторов в n8n
|
||
- Безопасный выход с очисткой данных
|
||
|
||
Все изменения протестированы и готовы к продакшену! 🚀
|
||
|
||
---
|
||
|
||
## 📝 Следующие шаги (опционально)
|
||
|
||
1. Добавить обновление TTL сессии при активности пользователя
|
||
2. Реализовать уведомление о скором истечении сессии (за 5 минут)
|
||
3. Добавить мониторинг активных сессий в админке
|
||
4. Реализовать "запомнить меня" с увеличенным TTL (7 дней)
|
||
|
||
---
|
||
|
||
**Автор:** AI Assistant
|
||
**Дата:** 2025-11-20
|
||
**Статус:** ✅ Завершено
|
||
|
||
|
||
|