Как запустить клиентский общий модуль или вывести уведомление из серверного кода (HTTP-сервиса, фонового задания) в 1С?

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

Разработчики часто сталкиваются с архитектурным ограничением платформы «1С:Предприятие»: невозможно напрямую вызвать клиентский код из серверного контекста. Эта проблема встает особенно остро при интеграции различных внешних систем, таких как SIP-телефония, чат-боты или системы мониторинга. Типичный сценарий: на сервер 1С через HTTP-сервис поступает информация о входящем звонке. Сервер обрабатывает запрос, сопоставляет номер с контрагентом, но теперь нам необходимо мгновенно показать конкретному пользователю интерактивное всплывающее окно (Notification) с кнопкой открытия карточки клиента. Напрямую обратиться к клиентскому общему модулю из HTTP-сервиса или фонового задания нельзя, так как они выполняются в изолированных сеансах на сервере, не имеющих графического интерфейса.

В этой статье мы подробно разберем пять практических способов решения этой задачи: от классических архитектурных обходов до самых современных механизмов платформы 1С.

---

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

Начиная с версии платформы 8.3.26, фирма «1С» добавила долгожданный нативный механизм сквозной отправки push-сообщений с сервера на конкретные клиентские сеансы — объект МенеджерУведомленийКлиента. Этот способ является наиболее приоритетным и производительным, так как он работает на уровне платформы без постоянных запросов к базе данных.

Рассмотрим по шагам, как настроить эту схему:

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

// В модуле управляемого приложения или специализированном клиентском общем модуле
Процедура ПриНачалеРаботыСистемы()
    
    // Подключаем обработчик уведомлений
    ИмяОбработчика = "ОбработкаВходящегоЗвонкаТелефонии";
    УведомленияКлиента.ПодключитьОбработчик(ИмяОбработчика, "ОбщийМодульТелефонияКлиент.ПринятьЗвонокССервера");
    
КонецПроцедуры
  1. Отправка уведомления с сервера (из HTTP-сервиса). Когда HTTP-сервис принимает звонок, он определяет, какому пользователю адресован вызов, находит его активный сеанс и отправляет push-уведомление напрямую:

// Код внутри метода HTTP-сервиса или фонового задания
Процедура ОтправитьСигналОЗвонкеНаКлиент(ИдентификаторПользователя, ДанныеЗвонка) export
    
    // Получаем массив активных сеансов нужного пользователя
    ПараметрыОтбора = Новый Структура("ИмяПользователя", ИдентификаторПользователя);
    СеансыПользователя = ПолучитьСеансыИнформационнойБазы(ПараметрыОтбора);
    
    МассивНомеровСеансов = Новый Массив;
    Для Каждого Сеанс Из СеансыПользователя Цикл
        МассивНомеровСеансов.Добавить(Сеанс.НомерСеанса);
    КонецЦикла;
    
    Если МассивНомеровСеансов.Количество() > 0 Тогда
        // Отправляем уведомление целенаправленно в сеансы пользователя
        УведомленияКлиента.ОтправитьУведомление(
            "ОбработкаВходящегоЗвонкаТелефонии", 
            ДанныеЗвонка, 
            МассивНомеровСеансов
        );
    КонецЕсли;
    
КонецПроцедуры
  1. Прием и обработка на клиенте. В общем модуле ОбщийМодульТелефонияКлиент (у которого в свойствах стоит флаг «Клиент») пишем процедуру обработки:

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

Процедура ОбработатьНажатиеНаУведомление(ДополнительныеПараметры) Экспорт
    
    СсылкаНаКонтрагента = ДополнительныеПараметры;
    Если ЗначениеЗаполнено(СсылкаНаКонтрагента) Тогда
        // Открываем форму карточки контрагента на клиенте
        ПараметрыФормы = Новый Структура("Ключ", СсылкаНаКонтрагента);
        ОткрытьФорму("Справочник.Контрагенты.ФормаОбъекта", ПараметрыФормы);
    КонецЕсли;
    
КонецПроцедуры

---

Способ 2. Использование подсистемы БСП «Напоминания пользователя»

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

Проанализируем алгоритм работы через БСП:

  1. При поступлении запроса в HTTP-сервис мы программно создаем запись в регистре сведений НапоминанияПользователя.
  2. В качестве времени напоминания указываем текущую дату и время (или даже на 1 секунду в прошлом, чтобы оно сработало мгновенно).
  3. Указываем конкретного пользователя-получателя.

Пример кода записи напоминания на сервере:


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

Преимущество метода: Клиентская часть БСП сама регулярно считывает этот регистр с помощью встроенного фонового микро-запроса и выведет штатное окно напоминаний 1С, откуда пользователь в один клик сможет открыть карточку звонка или клиента. Нам не пришлось писать ни одной строчки клиентского кода опроса!

---

Способ 3. Система взаимодействия (1С:Диалог) через служебные обсуждения

Механизм Системы взаимодействия основан на протоколе WebSocket, что обеспечивает мгновенную передачу сообщений (Push-технология) без задержек и практически с нулевой нагрузкой на СУБД.

Выясним, как построить архитектуру получения уведомлений через этот инструмент:

  1. Создание скрытого обсуждения. При первом запуске сеанса пользователя для него создается персональное служебное (неотображаемое) обсуждение.
  2. Подписка на сообщения на клиенте. На стороне клиента мы подписываемся на новые сообщения в этом обсуждении:

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

Процедура ПриПолученииСлужебногоСообщения(Сообщение, ДополнительныеПараметры) Экспорт
    
    // Парсим текст сообщения (например, JSON с данными звонка)
    ДанныеЗвонка = ПолучитьСтруктуруИзJSON(Сообщение.Текст);
    
    // Запускаем нужный клиентский модуль для показа формы звонка
    ОбщийМодульТелефонияКлиент.ПоказатьОкноЗвонка(ДанныеЗвонка);
    
КонецПроцедуры
  1. Отправка сообщения с сервера. В обработчике HTTP-сервиса мы просто программно пишем текстовое сообщение (в формате JSON или XML) в это обсуждение:

// На сервере в HTTP-сервисе
НовоеСообщение = СистемаВзаимодействия.СоздатьСообщение(ИдентификаторОбсуждения);
НовоеСообщение.Текст = СформироватьJSONДанныхЗвонка(ДанныеЗвонка);
НовоеСообщение.Записать();

Как только сервер выполнит метод Записать(), на клиентском ПК мгновенно сработает WebSocket-триггер, который запустит процедуру ПриПолученииСлужебногоСообщения на клиенте без создания лишних транзакций в базе данных.

---

Способ 4. Классический периодический опрос через регистр сведений и обработчик ожидания

Если вы работаете на более ранних версиях платформы (до 8.3.26) и в вашей базе не настроена Система взаимодействия, единственным надежным кроссплатформенным решением «из коробки» является схема с периодическим опросом (Polling).

Рассмотрим эту схему подробнее:

  1. Создаем регистр сведений ОчередьСобытийТелефонии со следующей структурой:
    • Измерения: Пользователь (СправочникСсылка.Пользователи), ИдентификаторСобытия (УникальныйИдентификатор).
    • Ресурсы: ДанныеСобытия (Строка неограниченной длины, куда будем сериализовать JSON).
    • Реквизиты: ДатаСоздания (ДатаВремя).
  2. При входящем вызове серверная часть HTTP-сервиса создает запись в этом регистре, указывая пользователя, которому назначен звонок:

// Запись события в очередь на сервере (в HTTP-сервисе)
МенеджерЗаписи = РегистрыСведений.ОчередьСобытийТелефонии.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = ОпределитьОтветственногоОператора(ДанныеЗвонка.ВходящийНомер);
МенеджерЗаписи.ИдентификаторСобытия = Новый УникальныйИдентификатор();
МенеджерЗаписи.ДанныеСобытия = ЗаписатьОбъектВJSONСтроку(ДанныеЗвонка);
МенеджерЗаписи.ДатаСоздания = ТекущаяДатаСеанса();
МенеджерЗаписи.Записать();
  1. На клиенте настраиваем постоянный опрос этого регистра с помощью метода ПодключитьОбработчикОжидания. Оптимальный интервал для телефонии — 1–2 секунды:

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

Процедура ПроверитьВходящиеЗвонкиИзОчереди() Экспорт
    
    // Запрашиваем у сервера наличие новых записей для текущего пользователя
    МассивЗвонков = ОбщийМодульТелефонияСервер.ПолучитьИУдалитьСобытияИзОчереди(ПараметрыСеанса.ТекущийПользователь);
    
    Для Каждого Звонок Из МассивЗвонков Цикл
        // Обрабатываем звонок на клиенте
        ОбщийМодульТелефонияКлиент.ПоказатьВсплывающееОкно(Звонок);
    КонецЦикла;
    
КонецПроцедуры
  1. Важное правило оптимизации на сервере! Чтобы база данных не испытывала колоссальных нагрузок при опросе регистра сотней пользователей каждые 2 секунды, серверная процедура чтения должна работать максимально быстро и сразу удалять прочитанные события из очереди:

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

---

Способ 5. Технология обратного вызова через локальную внешнюю компоненту (Reverse Push)

Этот метод часто применяется крупными интеграторами телефонии. Он позволяет добиться мгновенного отклика (real-time) на старых версиях платформы 1С без нагрузки на базу данных.

Проанализируем суть технологии:

  1. При запуске 1С на компьютере пользователя загружается специализированная локальная внешняя компонента (написанная, например, на C++ или C# по технологии Native API).
  2. Эта компонента открывает на компьютере пользователя локальный порт (например, http://localhost:15023) и начинает слушать входящие HTTP-запросы.
  3. Когда на сервер 1С (в общий HTTP-сервис) поступает информация о звонке, сервер по активным сеансам определяет IP-адрес компьютера нужного пользователя.
  4. Сервер отправляет короткий HTTP-запрос со своей стороны напрямую на этот IP-адрес: http://192.168.1.45:15023/incoming_call?phone=79998887766.
  5. Внешняя компонента, работающая в контексте клиентского сеанса 1С на данном ПК, ловит этот запрос и генерирует стандартное внешнее событие в платформу 1С:

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

Преимущества: Мгновенная доставка (доли миллисекунды), нулевой расход ресурсов сервера на фоновые опросы БД. Единственная сложность — необходимость администрирования локальных сетевых портов на рабочих станциях пользователей.

---

Резюме и рекомендации по выбору решения

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

← На главную