Database changes: - Add unified_id, contact_id, phone columns to clpr_claims table - Create indexes for fast lookup by these fields - Migrate existing data from payload to new columns - SQL migration: docs/SQL_ALTER_CLPR_CLAIMS_ADD_FIELDS.sql SQL improvements: - Simplify claimsave query: remove complex claim_lookup logic - Use UPSERT (INSERT ON CONFLICT) with known claim_id - Always return claim (fix NULL issue) - Store unified_id, contact_id, phone directly in table columns - SQL: docs/SQL_CLAIMSAVE_UPSERT_SIMPLE.sql Workflow enhancements: - Add branch for form submissions WITHOUT files - Create 6 new nodes: extract, prepare, save, redis, respond - Separate flow for has_files=false in IF node - Instructions: docs/N8N_FORM_GET_NO_FILES_INSTRUCTIONS.md - Node config: docs/N8N_FORM_GET_NO_FILES_BRANCH.json Migration stats: - Total claims: 81 - With unified_id: 77 - Migrated from payload: 2 Next steps: 1. Add 6 nodes to n8n workflow form_get (ID: 8ZVMTsuH7Cmw7snw) 2. Connect TRUE branch of IF node to extract_webhook_data_no_files 3. Test form submission without files 4. Verify PostgreSQL save and Redis event
12 KiB
Лог сессии: 2025-11-20 - Session Persistence & Draft Management
Дата: 20 ноября 2025
🎯 Основные задачи
- ✅ Реализация сохранения сессии в Redis
- ✅ Восстановление сессии из localStorage после перезагрузки страницы
- ✅ Исправление передачи
unified_idиclaim_idв n8n при отправке визарда - ✅ Исправление приоритета
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- удаление сессии
Данные сессии:
{
"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 логи для отладки сессии
Код:
// После получения 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. Проверка сессии при загрузке компонента:
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. Кнопка "Выход":
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 при загрузке черновика:
До:
session_id: claim.session_token || sessionIdRef.current, // ❌ Старый из черновика
После:
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.
Исправление:
// Добавляем 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 лог:
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 для фронтенда:
ticket_form_frontend:
volumes:
- ./frontend/src:/app/src:ro
Цель: Включить live reload (HMR) при изменении файлов фронтенда без пересборки контейнера.
🔄 Workflow изменений
Полный цикл работы с сессией:
-
Пользователь вводит телефон и SMS-код
- → Step1Phone вызывает n8n для верификации
- → n8n возвращает
unified_id,contact_id,session_id - → Step1Phone создаёт сессию в Redis через
POST /api/v1/session/create - →
session_tokenсохраняется вlocalStorage
-
Пользователь закрывает/обновляет страницу
- → ClaimForm при загрузке проверяет
localStorage - → Вызывается
POST /api/v1/session/verify - → Если сессия валидна, восстанавливаются
unified_id,phone,contact_id - → Автоматически загружаются черновики
- → ClaimForm при загрузке проверяет
-
Пользователь продолжает черновик
- → При загрузке черновика используется ТЕКУЩИЙ
session_id(не старый из БД) - → При отправке визарда передаются
unified_id,claim_id, актуальныйsession_id
- → При загрузке черновика используется ТЕКУЩИЙ
-
Пользователь нажимает "Выход"
- → Вызывается
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 после исправлений:
{
"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 секунд
📦 Изменённые файлы
- ✅
ticket_form/backend/app/api/session.py(создан) - ✅
ticket_form/backend/app/main.py(добавлен импорт session) - ✅
ticket_form/frontend/src/components/form/Step1Phone.tsx(v2.0) - ✅
ticket_form/frontend/src/pages/ClaimForm.tsx(v3.8) - ✅
ticket_form/frontend/src/components/form/StepWizardPlan.tsx(v1.4) - ✅
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
- Безопасный выход с очисткой данных
Все изменения протестированы и готовы к продакшену! 🚀
📝 Следующие шаги (опционально)
- Добавить обновление TTL сессии при активности пользователя
- Реализовать уведомление о скором истечении сессии (за 5 минут)
- Добавить мониторинг активных сессий в админке
- Реализовать "запомнить меня" с увеличенным TTL (7 дней)
Автор: AI Assistant
Дата: 2025-11-20
Статус: ✅ Завершено