Разработчики часто сталкиваются с архитектурным ограничением платформы «1С:Предприятие»: невозможно напрямую вызвать клиентский код из серверного контекста. Эта проблема встает особенно остро при интеграции различных внешних систем, таких как SIP-телефония, чат-боты или системы мониторинга. Типичный сценарий: на сервер 1С через HTTP-сервис поступает информация о входящем звонке. Сервер обрабатывает запрос, сопоставляет номер с контрагентом, но теперь нам необходимо мгновенно показать конкретному пользователю интерактивное всплывающее окно (Notification) с кнопкой открытия карточки клиента. Напрямую обратиться к клиентскому общему модулю из HTTP-сервиса или фонового задания нельзя, так как они выполняются в изолированных сеансах на сервере, не имеющих графического интерфейса.
В этой статье мы подробно разберем пять практических способов решения этой задачи: от классических архитектурных обходов до самых современных механизмов платформы 1С.
---
Начиная с версии платформы 8.3.26, фирма «1С» добавила долгожданный нативный механизм сквозной отправки push-сообщений с сервера на конкретные клиентские сеансы — объект МенеджерУведомленийКлиента. Этот способ является наиболее приоритетным и производительным, так как он работает на уровне платформы без постоянных запросов к базе данных.
Рассмотрим по шагам, как настроить эту схему:
// В модуле управляемого приложения или специализированном клиентском общем модуле
Процедура ПриНачалеРаботыСистемы()
// Подключаем обработчик уведомлений
ИмяОбработчика = "ОбработкаВходящегоЗвонкаТелефонии";
УведомленияКлиента.ПодключитьОбработчик(ИмяОбработчика, "ОбщийМодульТелефонияКлиент.ПринятьЗвонокССервера");
КонецПроцедуры
// Код внутри метода HTTP-сервиса или фонового задания
Процедура ОтправитьСигналОЗвонкеНаКлиент(ИдентификаторПользователя, ДанныеЗвонка) export
// Получаем массив активных сеансов нужного пользователя
ПараметрыОтбора = Новый Структура("ИмяПользователя", ИдентификаторПользователя);
СеансыПользователя = ПолучитьСеансыИнформационнойБазы(ПараметрыОтбора);
МассивНомеровСеансов = Новый Массив;
Для Каждого Сеанс Из СеансыПользователя Цикл
МассивНомеровСеансов.Добавить(Сеанс.НомерСеанса);
КонецЦикла;
Если МассивНомеровСеансов.Количество() > 0 Тогда
// Отправляем уведомление целенаправленно в сеансы пользователя
УведомленияКлиента.ОтправитьУведомление(
"ОбработкаВходящегоЗвонкаТелефонии",
ДанныеЗвонка,
МассивНомеровСеансов
);
КонецЕсли;
КонецПроцедуры
ОбщийМодульТелефонияКлиент (у которого в свойствах стоит флаг «Клиент») пишем процедуру обработки:
// Общий модуль: ОбщийМодульТелефонияКлиент (Клиент)
Процедура ПринятьЗвонокССервера(ДанныеЗвонка) Экспорт
// ДанныеЗвонка — это переданная с сервера структура
НомерТелефона = ДанныеЗвонка.Номер;
ИмяКонтрагента = ДанныеЗвонка.КонтрагентИмя;
СсылкаНаКонтрагента = ДанныеЗвонка.КонтрагентСсылка;
// Выводим красивое интерактивное оповещение платформы
ОписаниеОповещения = Новый ОписаниеОповещения("ОбработатьНажатиеНаУведомление", ЭтотОбъект, СсылкаНаКонтрагента);
ПоказатьОповещениеПользователю(
"Входящий звонок!",
ОписаниеОповещения,
"Звонит: " + ИмяКонтрагента + " (" + НомерТелефона + ")",
БиблиотекаКартинок.Телефон
);
КонецПроцедуры
Процедура ОбработатьНажатиеНаУведомление(ДополнительныеПараметры) Экспорт
СсылкаНаКонтрагента = ДополнительныеПараметры;
Если ЗначениеЗаполнено(СсылкаНаКонтрагента) Тогда
// Открываем форму карточки контрагента на клиенте
ПараметрыФормы = Новый Структура("Ключ", СсылкаНаКонтрагента);
ОткрытьФорму("Справочник.Контрагенты.ФормаОбъекта", ПараметрыФормы);
КонецЕсли;
КонецПроцедуры
---
Если ваша конфигурация разработана на базе БСП (Библиотеки стандартных подсистем), то вам не нужно разрабатывать велосипед для организации периодического опроса. В БСП уже встроена оптимизированная служба напоминаний, работающая через специальный регистр сведений.
Проанализируем алгоритм работы через БСП:
НапоминанияПользователя.Пример кода записи напоминания на сервере:
// Серверный общий модуль, вызываемый из HTTP-сервиса
Процедура СоздатьНапоминаниеОЗвонке(ПользовательСсылка, ДанныеЗвонка) Экспорт
// БСП предоставляет готовый программный интерфейс для работы с напоминаниями
ЗаписьНапоминания = РегистрыСведений.НапоминанияПользователя.СоздатьМенеджерЗаписи();
ЗаписьНапоминания.Пользователь = ПользовательСсылка;
ЗаписьНапоминания.ВремяСобытия = ТекущаяДатаСеанса();
ЗаписьНапоминания.Идентификатор = Новый УникальныйИдентификатор();
// В качестве источника можно передать документ "Событие" или непосредственно ссылку на Контрагента
ЗаписьНапоминания.Источник = ДанныеЗвонка.КонтрагентСсылка;
ЗаписьНапоминания.Содержание = "Входящий звонок с номера: " + ДанныеЗвонка.Номер;
// Указываем способ и время напоминания
ЗаписьНапоминания.СпособУстановкиВремени = Перечисления.СпособыУстановкиВремениНапоминания.ВУказанноеВремя;
ЗаписьНапоминания.ВремяНапоминания = ТекущаяДатаСеанса();
ЗаписьНапоминания.Записать();
КонецПроцедуры
Преимущество метода: Клиентская часть БСП сама регулярно считывает этот регистр с помощью встроенного фонового микро-запроса и выведет штатное окно напоминаний 1С, откуда пользователь в один клик сможет открыть карточку звонка или клиента. Нам не пришлось писать ни одной строчки клиентского кода опроса!
---
Механизм Системы взаимодействия основан на протоколе WebSocket, что обеспечивает мгновенную передачу сообщений (Push-технология) без задержек и практически с нулевой нагрузкой на СУБД.
Выясним, как построить архитектуру получения уведомлений через этот инструмент:
// На клиенте при старте системы
Процедура ПодключитьСлужебныйКаналТелефонии(ИдентификаторОбсуждения) Экспорт
ОписаниеОповещения = Новый ОписаниеОповещения("ПриПолученииСлужебногоСообщения", ЭтотОбъект);
СистемаВзаимодействия.НачатьПодключениеОбработчикаНовыхСообщений(
ИдентификаторОбсуждения,
ОписаниеОповещения
);
КонецПроцедуры
Процедура ПриПолученииСлужебногоСообщения(Сообщение, ДополнительныеПараметры) Экспорт
// Парсим текст сообщения (например, JSON с данными звонка)
ДанныеЗвонка = ПолучитьСтруктуруИзJSON(Сообщение.Текст);
// Запускаем нужный клиентский модуль для показа формы звонка
ОбщийМодульТелефонияКлиент.ПоказатьОкноЗвонка(ДанныеЗвонка);
КонецПроцедуры
// На сервере в HTTP-сервисе
НовоеСообщение = СистемаВзаимодействия.СоздатьСообщение(ИдентификаторОбсуждения);
НовоеСообщение.Текст = СформироватьJSONДанныхЗвонка(ДанныеЗвонка);
НовоеСообщение.Записать();
Как только сервер выполнит метод Записать(), на клиентском ПК мгновенно сработает WebSocket-триггер, который запустит процедуру ПриПолученииСлужебногоСообщения на клиенте без создания лишних транзакций в базе данных.
---
Если вы работаете на более ранних версиях платформы (до 8.3.26) и в вашей базе не настроена Система взаимодействия, единственным надежным кроссплатформенным решением «из коробки» является схема с периодическим опросом (Polling).
Рассмотрим эту схему подробнее:
ОчередьСобытийТелефонии со следующей структурой:
Пользователь (СправочникСсылка.Пользователи), ИдентификаторСобытия (УникальныйИдентификатор).ДанныеСобытия (Строка неограниченной длины, куда будем сериализовать JSON).ДатаСоздания (ДатаВремя).
// Запись события в очередь на сервере (в HTTP-сервисе)
МенеджерЗаписи = РегистрыСведений.ОчередьСобытийТелефонии.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = ОпределитьОтветственногоОператора(ДанныеЗвонка.ВходящийНомер);
МенеджерЗаписи.ИдентификаторСобытия = Новый УникальныйИдентификатор();
МенеджерЗаписи.ДанныеСобытия = ЗаписатьОбъектВJSONСтроку(ДанныеЗвонка);
МенеджерЗаписи.ДатаСоздания = ТекущаяДатаСеанса();
МенеджерЗаписи.Записать();
ПодключитьОбработчикОжидания. Оптимальный интервал для телефонии — 1–2 секунды:
// В модуле управляемого приложения на клиенте
Процедура ПриНачалеРаботыСистемы()
// Подключаем проверку очереди звонков каждые 2 секунды
ПодключитьОбработчикОжидания("ПроверитьВходящиеЗвонкиИзОчереди", 2, Ложь);
КонецПроцедуры
Процедура ПроверитьВходящиеЗвонкиИзОчереди() Экспорт
// Запрашиваем у сервера наличие новых записей для текущего пользователя
МассивЗвонков = ОбщийМодульТелефонияСервер.ПолучитьИУдалитьСобытияИзОчереди(ПараметрыСеанса.ТекущийПользователь);
Для Каждого Звонок Из МассивЗвонков Цикл
// Обрабатываем звонок на клиенте
ОбщийМодульТелефонияКлиент.ПоказатьВсплывающееОкно(Звонок);
КонецЦикла;
КонецПроцедуры
// В серверном модуле
Функция ПолучитьИУдалитьСобытияИзОчереди(ТекущийПользователь) Экспорт
Результат = Новый Массив;
// Используем максимально быстрый запрос по первому измерению индекса (Пользователь)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Очередь.ИдентификаторСобытия КАК ИдентификаторСобытия,
| Очередь.ДанныеСобытия КАК ДанныеСобытия
|ИЗ
| РегистрСведений.ОчередьСобытийТелефонии КАК Очередь
|ГДЕ
| Очередь.Пользователь = &ТекущийПользователь";
Запрос.УстановитьПараметр("ТекущийПользователь", ТекущийПользователь);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Десериализуем данные
СтруктураДанных = ДесериализоватьJSONСтроку(Выборка.ДанныеСобытия);
Результат.Добавить(СтруктураДанных);
// Сразу же удаляем запись, чтобы не прочитать её повторно
МЗ = РегистрыСведений.ОчередьСобытийТелефонии.СоздатьМенеджерЗаписи();
МЗ.Пользователь = ТекущийПользователь;
МЗ.ИдентификаторСобытия = Выборка.ИдентификаторСобытия;
МЗ.Удалить();
КонецЦикла;
Возврат Результат;
КонецФункции
---
Этот метод часто применяется крупными интеграторами телефонии. Он позволяет добиться мгновенного отклика (real-time) на старых версиях платформы 1С без нагрузки на базу данных.
Проанализируем суть технологии:
http://localhost:15023) и начинает слушать входящие HTTP-запросы.http://192.168.1.45:15023/incoming_call?phone=79998887766.
// В модуле управляемого приложения или формы
Процедура ВнешнееСобытие(Источник, Событие, Данные)
Если Источник = "ЛокальныйСлушательТелефонии" И Событие = "ВходящийЗвонок" Тогда
// Этот метод гарантированно выполняется "НаКлиенте"
НомерТелефона = Данные;
ОбщийМодульТелефонияКлиент.ПоказатьКарточкуЗвонка(НомерТелефона);
КонецЕсли;
КонецПроцедуры
Преимущества: Мгновенная доставка (доли миллисекунды), нулевой расход ресурсов сервера на фоновые опросы БД. Единственная сложность — необходимость администрирования локальных сетевых портов на рабочих станциях пользователей.
---
Подведем итоги совместного анализа архитектурных подходов: