Перейти к содержанию

Провайдер Exchange (EWS)

Документ описывает архитектуру, аутентификацию и режимы синхронизации календарей с Exchange (через протокол EWS). Рассматриваются операции с событиями, push-синхронизация, работа с вложениями, ограничения и сценарии диагностики. Руководство — для администраторов и инженеров, настраивающих интеграцию календарей с Exchange.

1. Архитектура

Провайдер Exchange (EWS) отвечает за чтение и синхронизацию календарных событий через протокол Exchange Web Services. Идентификатор провайдера в системе — ews.

Кэши и время их жизни

Чтобы снизить нагрузку на Exchange, провайдер кэширует часть данных. Время жизни кэша влияет на то, как быстро применяются изменения:

Что кэшируется Время жизни Назначение
Счётчик непрочитанных приглашений 10 мин Бейдж непрочитанных на вкладке «События»
ID папки «Календарь» пользователя до перезапуска Поиск событий
ID папки «Входящие» пользователя до перезапуска Поиск приглашений
Права на просмотр чужих календарей 5 мин (ExchangePermissionsCacheLifeTime) Проверка доступа
Категории и цвета событий до перезапуска Цветовая разметка (24 цвета)

2. Аутентификация

Метод

Используется только Basic Auth — логин, пароль и домен системной учётной записи. OAuth не поддерживается (это ограничение для Exchange Online / Microsoft 365).

Перевоплощение (impersonation)

Соединение перевоплощается в нужного пользователя одним из двух способов:

  • по SMTP-адресу — по умолчанию;
  • по SID — при включённой настройке «Использовать SID для перевоплощения» (UseSIDForImpersonalization = true); значение SID берётся из Active Directory.

Приоритет выбора сервиса: сначала по контроллеру домена пользователя (DomainController), затем — сервис EWS по умолчанию.

Версия протокола

  • Exchange2010_SP1 — по умолчанию;
  • Exchange2013_SP1 — для прямого чтения.

Настройка ExchangeVersion в CustomSettings переопределяет оба значения по умолчанию.

Шифрование паролей

Пароль системной учётной записи хранится в зашифрованном виде.

Режим удаления событий

  • без перевоплощения — событие удаляется безвозвратно (HardDelete);
  • с перевоплощением — переносится в «Удалённые» (MoveToDeletedItems).

3. Режимы синхронизации

Три режима (подробности — в Exchange — диагностика синхронизации пользователя):

Режим DoSyncWithExchange CalendarEwsDirectSync Описание
Disabled false false Нет синхронизации
Sync (событийный) true false Событийная синхронизация
Direct / Online false true Прямое чтение из Exchange

Автоотключение при ошибках

При ошибках Exchange синхронизация пользователя автоматически отключается:

  • ошибки недоступного ящика или адреса: ErrorNonExistentMailbox, ErrorItemNotFound, ErrorFolderNotFound, ErrorInvalidSmtpAddress, ErrorMailRecipientNotFound;
  • при ошибке AccessDenied сбрасываются сохранённые права на папки (EwsFolders / EwsFolderPermissions) и увеличивается счётчик неудачных попыток;
  • при превышении лимита CalendarExchangeRetryLimit режим переключается в Disabled;
  • фоновая задача EnableDoSyncWithExchangeJob может включить синхронизацию обратно.

4. Создание, изменение и удаление событий

Создание

При создании встречи в Exchange:

  • если у встречи есть участники — им рассылаются приглашения, копия сохраняется в календаре организатора; если участников нет — приглашения не отправляются;
  • при включённом перевоплощении встреча создаётся в папке «Календарь» пользователя, иначе — в папке, определённой правами доступа.

Изменение

Существующая встреча перечитывается, обновляется и сохраняется с приоритетом значений из 1Ф. Рассылка обновлений или отмен участникам зависит от наличия участников.

Удаление

Встреча удаляется в режиме, определённом настройкой перевоплощения (см. §2 «Режим удаления событий»).

Чтение и уровни доступа

Объём данных встречи, который видит пользователь, зависит от его прав на папку (FolderPermissionReadAccess):

Уровень доступа Что видно
Только время Время начала и окончания, статус занятости
Время, тема, место + тема и место встречи
Полный доступ Всё, включая тело, участников и вложения

Приватные события (Sensitivity.Private) скрыты от других пользователей. Для повторяющихся событий отдельные экземпляры и исключения читаются через родительскую встречу серии.

Маппинг полей

Соответствие полей события 1Ф и Exchange при создании и изменении:

Поле 1Forma Поле Exchange Примечание
Subject Subject
Location Location
TextBody Body (HTML) Несмотря на имя «TextBody», в Exchange передаётся HTML с блоком forma-id-{taskId}
Start/End Start/End С коррекцией таймзон
IsAllDayEvent IsAllDayEvent
IsPrivate Sensitivity Private / Normal
FreeBusyStatus LegacyFreeBusyStatus Free / Busy / Tentative / OOF
RequiredAttendees RequiredAttendees Обязательные участники
OptionalAttendees OptionalAttendees Необязательные участники
Recurrence Recurrence Повторяемость
Attachments Attachments Вложения

Связь события с задачей (LinkedTaskId)

С версии 2.267 блок <div class="forma-link"> в тело встречи больше не вставляется. Приглашения участникам формируются отдельным письмом (см. ниже).

Для ранее созданных встреч связь с задачей по-прежнему извлекается из тела по шаблонам forma-id-(\d+) или Linked task id: \d+.

Письмо-уведомление участникам при создании встречи

При создании встречи в Exchange 1Форма отправляет участникам письмо по шаблону:

Шаблон ID события (TCEvents) Условие Тело письма
MeetingCreated 54 встреча не связана с задачей Текст пользователя
MeetingCreatedWithLinkedTask 55 встреча связана с задачей Текст + блок «Перейти к задаче»

Шаблоны настраиваются в Администрирование → Почта → Шаблоны писем. Поставляются как системные (SystemTemplate = 1, IsActive = 1).

5. Push-синхронизация (Streaming Subscriptions)

Где работает

Push-подписки на изменения в Exchange работают только на сервере заданий (JobServer). Если JobServer не запущен — push-уведомлений не будет (события подтянутся только при следующем чтении календаря).

Как определяется список EWS-сервисов для подписки

Перед подпиской пользователей система собирает список используемых EWS-сервисов двумя путями.

Путь 1 — по доменам AD-профилей. Если EWS-сервис связан с профилем синхронизации Active Directory, он определяется по контроллеру домена (DomainController) из AD-учётных данных профиля. Если ни один EWS-сервис не привязан к AD-профилю — этот путь не даёт ни одного сервиса.

Путь 2 — резервный, через Settings.DefaultEwsServiceId. Если в таблице Settings поле DefaultEwsServiceId пустое (NULL) — резервный сервис не добавляется.

Сценарий «тихого отказа»

Если оба пути вернули 0 сервисов — список пуст, подписки не создаются и не активируются. При этом в журнал ничего не пишется (нет записей в AutomationScriptsLog, Type = 10). Симптом: событие в Outlook создано, но комментарий в задачу не приходит, а лог пуст.

Диагностика:

-- 1. Проверить DefaultEwsServiceId
SELECT DefaultEwsServiceId FROM Settings;

-- 2. Проверить, что EWS-сервисы вообще заведены
SELECT Id, ServiceId, EwsUrl, EwsLogin FROM EwsServiceSettings;

-- 3. Проверить привязку AD-профилей к EWS (наполнение кэша доменов)
SELECT sp.Id, sp.IsActive, sp.ServiceId, ads.Id AS ADSettingsId
FROM SynchronizationProfiles sp
LEFT JOIN SynchronizationProfilesADSettings ads ON ads.SynchronizationProfileId = sp.Id;

Если DefaultEwsServiceId = NULL и AD-профилей нет — подписки не стартуют. Исправление:

UPDATE Settings SET DefaultEwsServiceId = <Id>  -- Id из EwsServiceSettings

После обновления требуется перезапуск приложения.

Подписки и принципы работы

Потоковые подписки на изменения работают по следующим принципам:

  1. При старте система подписывается на всех неуволенных пользователей с включённой синхронизацией (CalendarEwsDirectSync или DoSyncWithExchange).
  2. На каждый почтовый адрес создаётся отдельная подписка на потоковые уведомления.
  3. Отслеживаемые события календаря: Created, Deleted, Modified, Moved, Copied, FreeBusyChanged.
  4. События папки «Входящие»: только NewMail (новое письмо).
  5. Время жизни потокового соединения — ExchangeConnectionLifetime (по умолчанию 1 мин).
  6. При разрыве соединение восстанавливается автоматически.

Как уведомление доходит до пользователя

Exchange присылает уведомление об изменении события. 1Форма определяет роль пользователя (организатор или участник) и тип изменения, синхронизирует событие в задачу 1Ф и отправляет пользователю push-уведомление (SignalR) — браузер перечитывает агенду. Параллельно об изменении узнают внутренние подписчики системы.

Параллельность

Подписки обрабатываются параллельно; число одновременно обрабатываемых задаётся настройкой ExchangeSemaphoreCount (по умолчанию 50).

Как обрабатывается изменение события

Полученное от Exchange уведомление обрабатывается по шагам:

  1. Уведомления фильтруются (события уровня папки отбрасываются).
  2. Группируются по событию (перемещение порождает несколько уведомлений).
  3. Определяется главный тип изменения по приоритету: перемещение → изменение → создание → удаление.
  4. Загружается минимальный набор полей события для проверки применимости.
  5. Проверки: является ли событие мастером серии, не входит ли в исключённые категории, не слишком ли оно старое.
  6. Дальнейшая обработка зависит от роли пользователя:
Роль Создание Изменение Перемещение
Организатор событие создаётся в 1Ф событие обновляется в 1Ф создание или отмена (зависит от направления переноса)
Участник событие создаётся в 1Ф событие обновляется в 1Ф отметка об отклонении встречи

Что НЕ покрывают push-подписки

Часть изменений push-подписки не доставляют в реальном времени:

Ограничение Описание
Нет push «новое приглашение» Подписка на «Входящие» ловит только новое письмо (NewMail). Приглашение на встречу в «Входящих» не порождает отдельного календарного уведомления. Пользователь узнаёт о приглашении только при открытии вкладки «События» или из почтового клиента
FreeBusyChanged Подписка есть, обработчик не задействован — изменение, по-видимому, игнорируется
Copied Подписка есть, отдельной обработки нет
Ответы (принятие/отклонение) Организатор видит обновлённый статус в календаре, но отдельного уведомления «пользователь X ответил» нет

6. Приглашения на встречи (вкладка «События»)

Список приглашений

Чтение приглашений из папки «Входящие» работает только при двух условиях:

  • включён доступ к папке «Входящие» (CalendarInboxAccessEnabled = true);
  • пользователь запрашивает свои собственные приглашения (чужие «Входящие» недоступны).

Ищутся элементы папки «Входящие» по типу (ItemClass):

ItemClass Тип Описание
IPM.Schedule.Meeting.Request Приглашение Приглашение на встречу
IPM.Schedule.Meeting.Canceled Отмена Отмена встречи
IPM.Schedule.Meeting.Resp.* Ответ Ответ участника (с предложенным временем)

Сортировка — по дате получения, от новых к старым.

Счётчик непрочитанных

  • значение кэшируется на 10 минут;
  • учитываются непрочитанные приглашения и отмены встреч за последние 14 дней;
  • область поиска — папка «Входящие».

Сброс счётчика

При прочтении приглашений кэш очищается, а счётчик в интерфейсе обновляется через SignalR.

7. Вложения и обработка HTML

Добавление и обновление вложений

  1. Текущие вложения встречи сравниваются с переданными.
  2. Отсутствующие удаляются.
  3. Новые загружаются и прикрепляются к встрече.
  4. Расширения проверяются на список запрещённых (настройка запрещённых расширений файлов).
  5. Поддерживается прикрепление файлов из хранилища 1Формы.

Получение вложений

Доступно только при полном уровне доступа к встрече. Несколько вложений отдаются одним ZIP-архивом.

Обработка HTML-тела

Из тела встречи удаляется служебный блок-контейнер 1Ф и устаревший маркер Linked task id: <номер>, извлекается содержимое <body>.

8. Настройки CustomSettings

Пул и таймауты

Ключ Default Назначение
ExchangeConnectionPoolSize 100 Размер семафора конкурентности
CalendarRequestTimeoutInMilliseconds (EwsRequestsTimeout) Таймаут на запрос списка
Таймаут семафора 2 сек (фиксированное значение) Ошибка ожидания при превышении
Таймаут сервиса 30 мин (фиксированное значение)

Streaming subscriptions

Ключ Default Назначение
EnableEwsSubscriptions true Вкл/выкл push-подписки
EnableEwsEmailSubscriptions true Подписки на Inbox
EnableEwsCalendarInboxAccess true Доступ к папке Inbox
ExchangeConnectionLifetime 1 мин Время жизни потокового соединения
ExchangeSemaphoreCount 50 Параллельные подписки
ExchangeSemaphoreWait 0 Таймаут семафора подписок

Поведение

Ключ Default Назначение
ExchangePermissionsCacheLifeTime 5 мин TTL кэша прав
EnableEwsSetDirectSyncDisabledWhenEwsErrorsOccurs true Автоотключение при ошибках
WriteEwsRequestDurationToAutomationLog false Логирование длительности
ExchangeSubscriptionsToLog false Подробное логирование подписок

9. Уровни доступа к данным события и таймзоны

Какие поля события читаются

В зависимости от прав пользователя из Exchange читается разный набор полей события:

Уровень Когда применяется Какие поля
Только время Нет прав или включён показ только занятости Время начала/окончания, таймзона, признак «весь день», статус занятости
Время + тема и место Частичный доступ + тема, место
Список (краткий) Полный доступ, режим списка Все поля, кроме тела и участников
Полный Открытие одного события Всё: тело, участники, повторяемость, вложения

Ограничение выборки: не более 5000 событий за один запрос.

События «весь день» и таймзоны

Даты событий «весь день» корректируются с учётом таймзоны пользователя, создателя и Exchange-сервиса. Это частый источник ошибок в сценариях с разными часовыми поясами.

10. Ограничения и известные проблемы

Известные ограничения провайдера Exchange и связанные с ними проблемы:

# Проблема Детали
1 Нет OAuth Только Basic Auth и перевоплощение. Ограничение для Exchange Online / Microsoft 365
2 Ограничение конкурентности Соединения не пулируются; одновременная конкурентность ограничена (100), таймаут ожидания — 2 сек
3 Занятость сервера Exchange При ошибке «сервер занят» (ServerBusyException) повторных попыток нет: ошибка логируется, возвращается пустой результат
4 Медленное чтение мастера серии Получение родительской встречи повторяющейся серии — медленная операция; в списках отключено, выполняется только при открытии события
5 Приглашения только для владельца Список приглашений доступен только для своих «Входящих»; чужие недоступны
6 Лимит выборки 5000 событий Фиксированное значение; при превышении выборка обрезается
7 Push-подписки только на JobServer Если сервер заданий не запущен — push-уведомлений нет
8 Пустая вкладка «События» из-за кэша папки Известная ошибка: при старте приложения кэш ID папки «Входящие» может заполниться идентификатором папки «Календарь», и приглашения ищутся не в той папке → вкладка пуста. После истечения кэша (24 ч) может заработать само
9 Нет push о новом приглашении Подписка на «Входящие» ловит только новое письмо. Вкладка «События» обновляется только по запросу — уведомления «вам пришло приглашение» в реальном времени нет

11. Диагностика

Типичные симптомы

Симптом Где смотреть
Пустая вкладка «События» 1) Проблема №8: кэш подменяет папку «Входящие» на «Календарь». 2) Настройка EnableEwsCalendarInboxAccess. 3) Запрашиваются ли свои собственные приглашения. 4) Если /api/mail/find-messages работает — соединение с EWS в порядке, проблема в кэше
Нет push-обновлений Проверить EnableEwsSubscriptions и что запущен сервер заданий (JobServer). Смотреть логи синхронизации Exchange
Ошибка ожидания семафора Нагрузка более 100 параллельных запросов. Увеличить ExchangeConnectionPoolSize
Автоотключение синхронизации SyncWithExchangeFailedAttempts превысил CalendarExchangeRetryLimit. Сбросить счётчик в профиле пользователя
Приватные события не видны Приватные события (Sensitivity.Private) скрыты намеренно; зависит от уровня доступа к папке

Диагностический SQL

-- Режим синхронизации пользователя
select u.UserID, u.UserName, u.Email, u.SID,
       u.CalendarEwsDirectSync, u.DoSyncWithExchange,
       u.SyncWithExchangeFailedAttempts, u.DomainController
from Users u
where u.UserID = @userId;

-- Кэш непрочитанных сообщений
select c.[Key], c.Value, c.UpdatedDate
from UsersMessageCountCache c
where c.[Key] = cast(@userId as varchar(50));

-- EWS-права пользователя на чужие папки
select f.UserID, f.FolderOwnerUserID, f.FolderId,
       p.ReadAccess, p.PermissionLevel
from EwsFolders f
join EwsFolderPermissions p on f.ID = p.EwsFolderID
where f.UserID = @userId;

Связанные документы