Как вызвать клиента с сервера в 1С при получении внешнего события телефонии

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработка интеграции 1С с телефонией (например, Asterisk, Zebra Telecom, Mango Office и др.) часто ставит перед программистом сложную архитектурную задачу. Когда АТС присылает вебхук на HTTP-сервис 1С о входящем звонке, это событие обрабатывается на стороне сервера. Но как моментально «вытолкнуть» это событие на клиентское рабочее место конкретного менеджера, чтобы у него открылась карточка клиента или появилось всплывающее окно? При этом крайне важно не перегрузить сервер постоянными запросами.

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

В чем опасность частого опроса базы данных (Polling)

Самое очевидное и простое в реализации решение, к которому часто прибегают начинающие разработчики, — это создание периодического регистра сведений (например, «ОчередьСобытийТелефонии») и запуск на клиенте обработчика ожидания через метод ПодключитьОбработчикОжидания с интервалом в 1–2 секунды. Каждые две секунды каждый активный сеанс пользователя делает запрос к СУБД, чтобы проверить наличие новых записей.

Разберем, почему в реальной эксплуатации этот подход приводит к деградации производительности:

  1. Повышенная нагрузка на СУБД. Если в базе одновременно работают 50–100 пользователей, опрашивающих регистр каждые 2 секунды, СУБД вынуждена обрабатывать до 50 тяжелых запросов в секунду холостого хода. Это приводит к росту загрузки процессора сервера БД и росту очередей дисковой активности.
  2. Транзакционные блокировки. Если в процессе чтения данных клиент пытается пометить запись как прочитанную (или удалить ее), возникают частые операции записи. Удаление строк из СУБД в высоконагруженных таблицах — одна из самых ресурсоемких операций, приводящая к фрагментации индексов и взаимным блокировкам (deadlocks).
  3. Раздувание TempDB и кэша СУБД. Постоянные микрозапросы вымывают полезные данные из кэша планов запросов СУБД, заставляя сервер тратить ресурсы на непроизводительные операции.

Ниже мы подробно разберем альтернативные, профессиональные способы решения этой задачи.

Решение 1. Использование встроенного «Менеджера уведомлений клиента» (начиная с платформы 8.3.26)

В современных версиях платформы «1С:Предприятие» (начиная с релиза 8.3.26) фирма «1С» добавила долгожданный нативный механизм отправки push-уведомлений с сервера на клиент без использования Системы взаимодействия. Этот механизм работает через встроенный оптимизированный транспорт платформы и гарантирует доставку сообщений.

Рассмотрим по шагам, как запрограммировать этот механизм.

Шаг 1. Подписка на клиенте. При запуске клиентского приложения (например, в модуле управляемого приложения) мы регистрируем обработчик входящих уведомлений по определенному ключу события:


&НаКлиенте
Процедура ПриНачалеРаботыСистемы()
    // Подписываемся на события телефонии
    УведомленияКлиента.ПодключитьОбработчик("СобытиеТелефонии", Новый ОписаниеОповещения("ОбработатьВходящийЗвонок", ЭтотОбъект));
КонецПроцедуры

&НаКлиенте
Процедура ОбработатьВходящийЗвонок(ДанныеУведомления, ДополнительныеПараметры) Экспорт
    // ДанныеУведомления содержат структуру, переданную с сервера
    Если ДанныеУведомления.Свойство("НомерТелефона") Тогда
        ПоказатьОповещениеПользователя("Входящий звонок", , "Звонит: " + ДанныеУведомления.НомерТелефона);
        // Здесь можно открыть карточку клиента
    КонецЕсли;
EndProcedure

Шаг 2. Отправка события с сервера. Когда наш HTTP-сервис принимает вебхук от АТС, он определяет, какому пользователю адресован звонок, находит его активные сеансы и отправляет push-уведомление:


&НаСервере
Процедура ОтправитьУведомлениеПользователю(ИмяПользователя, НомерТелефона)
    
    // Получаем список активных сеансов нужного пользователя
    Адресаты = Новый Массив;
    СеансыИнформационнойБазы = ПолучитьСеансыИнформационнойБазы();
    
    Для Каждого Сеанс Из СеансыИнформационнойБазы Цикл
        Если Сеанс.Пользователь.Имя = ИмяПользователя Тогда
            Адресаты.Добавить(Сеанс.НомерСеанса);
        КонецЕсли;
    КонецЦикла;
    
    // Если сеансы найдены, отправляем данные напрямую в сеанс клиента
    Если Адресаты.Количество() > 0 Тогда
        ПараметрыУведомления = Новый Структура;
        ПараметрыУведомления.Вставить("НомерТелефона", НомерТелефона);
        
        УведомленияКлиента.ОтправитьУведомление("СобытиеТелефонии", ПараметрыУведомления, Адресаты);
    КонецЕсли;
    
КонецПроцедуры

Этот способ является наиболее предпочтительным и производительным, так как не требует стороннего софта или платных лицензий и работает «из коробки» на уровне платформы.

Решение 2. Система взаимодействия и бесплатный облачный сервис «1С:Диалог»

Если ваша платформа ниже версии 8.3.26, но выше 8.3.10, отличным решением будет использование Системы взаимодействия. Многие разработчики ошибочно полагают, что для нее требуется покупка дорогостоящей лицензии КОРП или развертывание собственного сервера взаимодействия. На самом деле для небольших баз можно совершенно бесплатно использовать облачный сервис 1С:Диалог.

Идея решения заключается в программном создании скрытого (служебного) обсуждения, сообщения в котором перехватываются кодом 1С на клиенте без отображения стандартного интерфейса чата.

Проанализируем программную реализацию этой схемы:


// На стороне клиента при начале работы системы инициализируем подписку
&НаКлиенте
Процедура ИнициализироватьСистемуВзаимодействия()
    // Подключаем обработчик новых сообщений для служебного обсуждения
    СистемаВзаимодействия.ПодключитьОбработчикНовыхСообщений(
        Новый ОписаниеОповещения("ПриПолученииСообщенияСистемыВзаимодействия", ЭтотОбъект)
    );
КонецПроцедуры

&НаКлиенте
Процедура ПриПолученииСообщенияСистемыВзаимодействия(Сообщение, ДополнительныеПараметры) Экспорт
    // Проверяем, что это служебное сообщение нашей телефонии
    Если Сообщение.Обсуждение = ПараметрыСеанса.ИдентификаторСлужебногоОбсужденияТелефонии Тогда
        // Извлекаем переданные сервером данные из свойств сообщения
        НомерТелефона = Сообщение.Свойства.Получить("НомерТелефона");
        ПоказатьОповещениеПользователя("Входящий звонок через Систему взаимодействия",, НомерТелефона);
    КонецЕсли;
КонецПроцедуры

На стороне сервера при получении вебхука HTTP-сервисом выполняется генерация нового сообщения в указанное обсуждение:


&НаСервере
Процедура ОтправитьСобытиеВСистемуВзаимодействия(ИДОбсуждения, НомерТелефона)
    
    НовоеСообщение = СистемаВзаимодействия.СоздатьСообщение(ИДОбсуждения);
    НовоеСообщение.Текст = "Служебное событие: Входящий звонок";
    
    // Передаем структурированные данные во внутренних свойствах сообщения
    НовоеСообщение.Свойства.Вставить("НомерТелефона", НомерТелефона);
    НовоеСообщение.Свойства.Вставить("ТипСобытия", "ВходящийЗвонок");
    
    НовоеСообщение.Записать();
    
КонецПроцедуры

Преимуществом Системы взаимодействия является то, что отправка и доставка сообщений оптимизированы на сетевом уровне. Сообщение доставляется клиенту за миллисекунды по постоянному WebSocket-соединению, которое платформа 1С поддерживает автоматически.

Решение 3. Использование внешних компонент (ВК) и механизма внешних событий

Если требуется полная автономность без привязки к облачным сервисам 1С и на старых версиях платформ, стандартным и проверенным решением является разработка или покупка готовой внешней компоненты (ВК), работающей по технологии Native API.

Схема работы выглядит следующим образом:

  1. На клиентских компьютерах в сеансе 1С загружается внешняя компонента. Она запускает фоновый поток, который слушает определенный TCP/IP порт или удерживает WebSocket-соединение с сервером телефонии (или вашим промежуточным сервером-маршрутизатором вебхуков).
  2. Когда на сервер телефонии поступает звонок, он шлет пакет на порт запущенной внешней компоненты конкретного пользователя.
  3. Внешняя компонента при получении пакета генерирует стандартное событие платформы, которое перехватывается в предопределенной процедуре управляемой формы или модуля приложения ОбработкаВнешнегоСобытия.

Разберем пример обработки такого события в модуле управляемого приложения или в форме рабочего стола:


&НаКлиенте
Процедура ПриОткрытииФормыРабочегоСтола()
    // Подключаем внешнюю компоненту
    ИмяФайлаВК = "ОбщийМакет.КомпонентаТелефонии";
    Если ПодключитьВнешнююКомпоненту(ИмяФайлаВК, "TelephonyInteg", ТипВнешнейКомпоненты.Native) Тогда
        ОбъектВК = Новый("AddIn.TelephonyInteg.IntegClass");
        // Запускаем прослушивание порта или устанавливаем коннект
        ОбъектВК.УстановитьСоединение(ПараметрыСеанса.АдресСервераСобытий, ПараметрыСеанса.ПортСобытий);
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ВнешнееСобытие(Источник, Событие, Данные)
    // Метод вызывается платформой при генерации события компонентой
    Если Источник = "TelephonyInteg" И Событие = "IncomingCall" Тогда
        // Обрабатываем данные звонка
        СтруктураДанных = ДесериализоватьJSON(Данные);
        ПоказатьОповещениеПользователя("Входящий звонок (ВК)",, СтруктураДанных.Номер);
    КонецЕсли;
КонецПроцедуры

Этот подход гарантирует мгновенную реакцию интерфейса, однако накладывает дополнительные требования к сетевой инфраструктуре (необходимо обеспечить сетевую доступность портов между сервером событий и клиентскими машинами).

Решение 4. Поле HTML-документа и внешние WebSocket/SSE-серверы

Весьма элегантным решением, которое часто применяется в веб-клиенте 1С или при невозможности использовать внешние компоненты, является интеграция через поле HTML-документа на форме.

Посмотрим, как выглядит архитектура этого решения по шагам:

  1. На форму рабочего стола пользователя добавляется скрытый или явный элемент управления с типом ПолеHTMLДокумента.
  2. В это поле загружается простая HTML-страница с JavaScript-скриптом.
  3. JavaScript-скрипт при инициализации открывает постоянное соединение по протоколу WebSocket или Server-Sent Events (SSE) с внешним сервером уведомлений (например, написанным на Node.js, Go или готовым решением вроде Centrifugo).
  4. Когда вебхук от АТС поступает на ваш HTTP-сервис 1С, сервер 1С пересылает это событие по API на ваш внешний сервер WebSocket/SSE.
  5. Сервер WebSocket мгновенно транслирует событие в браузерный скрипт, запущенный в поле HTML-документа 1С.
  6. Скрипт принимает данные и генерирует событие в 1С с помощью стандартной функции интеграции: window.external.MessageTo1C().

Разберем пример JS-кода внутри HTML-поля:


<script>
    const socket = new WebSocket('ws://ваш-сервер-вебсокетов:8080');
    
    socket.onmessage = function(event) {
        const eventData = JSON.parse(event.data);
        if (eventData.type === 'IncomingCall') {
            // Передаем данные обратно в платформу 1С
            window.external.MessageTo1C('IncomingCall', eventData.phoneNumber);
        }
    };
</script>

На стороне 1С в свойствах элемента ПолеHTMLДокумента мы настраиваем событие ПриНажатии или специализированный обработчик сообщений (в зависимости от версии платформы):


&НаКлиенте
Процедура ПолеHTMLДокументаПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
    // Обрабатываем событие, переданное через window.external.MessageTo1C()
    Если ДанныеСобытия.ИмяСобытия = "IncomingCall" Тогда
        НомерТелефона = ДанныеСобытия.Параметр;
        ПоказатьОповещениеПользователя("Новый вызов (WebSocket)",, НомерТелефона);
    КонецЕсли;
КонецПроцедуры

Решение 5. Оптимизированный опрос базы данных (если другие варианты невозможны)

Если вы ограничены рамками старой платформы, у вас нет возможности поднимать сторонние сервисы, использовать Систему взаимодействия или внешние компоненты, вам придется использовать опрос регистра сведений. Однако этот процесс можно и нужно оптимизировать, чтобы снизить нагрузку на СУБД на 95%.

Рассмотрим правила оптимизации такого опроса:

Посмотрим на пример правильного, максимально легкого запроса для обработчика ожидания:


&НаСервереБезКонтекста
Функция ПолучитьНовыеСобытияТелефонии(ИмяПользователя) Экспорт
    
    РезультатСобытий = Новый Массив;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТелефонияСобытия.НомерТелефона КАК НомерТелефона,
        |   ТелефонияСобытия.Период КАК Период
        |ИЗ
        |   РегистрСведений.ТелефонияСобытия КАК ТелефонияСобытия
        |ГДЕ
        |   ТелефонияСобытия.Пользователь = &Пользователь
        |   И ТелефонияСобытия.Период >= &ГраницаВремени";
    
    // Опрашиваем события только за последние 5 секунд
    Запрос.УстановитьПараметр("Пользователь", ИмяПользователя);
    Запрос.УстановитьПараметр("ГраницаВремени", ТекущаяДата() - 5);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        РезультатСобытий.Добавить(Новый Структура("НомерТелефона", Выборка.НомерТелефона));
    КонецЦикла;
    
    Возврат РезультатСобытий;
    
КонецФункции

Такой запрос выполняется СУБД мгновенно, используя индекс по измерениям Пользователь + Период, и практически не создает дисковой нагрузки, работая целиком в оперативной памяти.

Резюме и рекомендации

Мы проанализировали различные варианты решения задачи вызова клиента с сервера при обработке вебхуков телефонии. Подводя итог, дадим следующие архитектурные рекомендации:

← На главную