Skip to content

Синхронизация между пользователями ​

Изменения, сделанные одним сотрудником, сразу видны у остальных: задачу сдвинули на доске — у коллег доска, список задач, открытая карточка и таймер в шапке обновляются без перезагрузки страницы. То же для карточки проекта, бюджета, файлов, команды, документов, логистики, табелей, платежей и командировок.

Как устроено ​

  1. Сервер. Middleware internal/realtime стоит на группе /api/v1. После каждого успешного изменяющего запроса (POST, PUT, PATCH, DELETE с ответом 2xx) он по шаблону маршрута (c.FullPath()) определяет, что изменилось, и рассылает событие. Сервисы модулей о рассылке не знают; новый маршрут попадает в неё правилом в internal/realtime/rules.go (таблица правил — в тесте realtime_test.go).

  2. Доставка. По WebSocket уведомлений /ws/notifications — всем подключённым сотрудникам, событие entity_changed:

    json
    {"type": "entity_changed", "timestamp": "…", "metadata": {"entity": "task", "action": "updated", "id": 26, "project_id": 6, "part": "", "work": true, "actor_id": 7}}

    В событии только тип, id и проект — никаких данных. Получатель перечитывает данные через API со своими правами, поэтому событие о чужом проекте ничего не раскрывает, кроме того, что объект с таким id изменился.

  3. Фронтенд. useNotifications передаёт entity_changed на локальную шину событий (relayServerEntityChange, useRealtimeEvents). На неё подписаны экраны: onTaskChanged, onProjectChanged, onRealtimeEntity. Изменения в своей вкладке объявляет сам фронт сразу после ответа API; серверное эхо приходит в пределах той же задержки (120–150 мс), и перезагрузки сливаются в одну.

Что рассылается ​

МаршрутыСущностьКто обновляется
/tasks… (кроме чата задачи)task (work — старт, пауза, завершение, статус)доска и обзор проекта, список задач, главная, профиль, открытая карточка, таймер в шапке
/projects/:id, /status, /specs…project, part card / specsкарточка проекта: статус, ворота, деньги
/projects/:id/budget/settings, /finance/cost-items…, …/estimate/applyproject, part budget / estimateвкладка бюджета, деньги в обзоре; смета из ТЗ — ещё задача согласования
/projects/:id/files, /files…, /folders/:id/files, файлы задачиproject, part filesвкладка «Файлы»
/project-team…, /team-requests…, /team-request-items…project, part teamкоманда и заявки на людей
/projects/:id/documents…project, part documentsдокументы проекта
/documents/:id… (статус, черновик счёта: правка, выпуск, удаление)project, parts documents и filesдокументы и файлы проекта
/estimates/:id…, /estimate-sections/:id, /estimate-lines/:idproject, part estimate (+ card при смене статуса)смета
клиент открыл ссылку или решил (/public/estimates/:token…)project, part estimate — из сервиса смет, без пользователясмета
/logistics…, /equipment-requests…, оборудование проектаlogistics_operation, logistics_equipment_request (refresh)логистика, оборудование проекта, бюджет
/timesheets…, /timesheet-corrections…, /time-entries…timesheet (refresh)табели, отметки «часы не запишутся» в задачах
/payments…paymentплатежи и остатки счетов
/trips…tripкомандировки
/absences… (кроме POST /absences/check), /calendar…absence (refresh)«Табель → Отсутствия», виджет «Кто отсутствует», расписание на главной, метки «в отпуске» у имени

Чат и уведомления рассылают свои события сами. Справочники, настройки и права обновляются при открытии страницы.

В сети / не в сети ​

Точка у аватара сотрудника: зелёная — в сети, серая — нет (internal/presence).

  • В сети — у сотрудника открыта RMS (соединение /ws/notifications или /ws/chat) и он что-то делает: клиент на клики, клавиши, прокрутку и касания шлёт по соединению уведомлений {"type":"activity"} — не чаще раза в минуту. heartbeat только держит соединение и в сети не продлевает.
  • Не в сети — 30 минут без активности (проверка раз в минуту), хотя вкладка открыта; первое же действие возвращает в сеть. Закрыл все вкладки — сразу не в сети.
  • Статус пишется в users.presence_status (его же отдают справочник и контакты чата); при старте API всех сбрасывают в «не в сети» — соединений ещё нет.
  • Начальное состояние — GET /api/v1/directory/presence?user_ids=… (до 500), изменения — событие presence_changed всем подключённым: metadata.user_id, metadata.status (online / offline).

Ограничения ​

  • Хаб WebSocket живёт в процессе API: изменения из фонового воркера (notifications-worker) по нему не рассылаются. Учёт «в сети» тоже в памяти этого процесса — при нескольких экземплярах API его нужно вынести в общее хранилище.
  • Пока соединение разорвано, события теряются; после переподключения экраны не перечитываются сами — достаточно открыть раздел заново.
  • Событие получает каждый подключённый сотрудник; при росте числа пользователей получателей можно сузить до участников проекта.

Загружаем документацию…