Разработчики часто сталкиваются с ситуацией, когда в процессе выполнения тяжелой серверной процедуры или фонового задания требуется оперативно проинформировать пользователя о завершении процесса или возникновении важного события. Стандартный метод ПоказатьОповещениеПользователя, который выводит красивое всплывающее окошко (тот самый «зеленый колокольчик»), доступен исключительно в клиентском контексте &НаКлиенте. Попытка вызвать его напрямую из процедуры с директивой компиляции &НаСервере или &НаСервереБезКонтекста приведет к синтаксической ошибке на этапе компиляции.
В этой статье мы подробно разберем, как обойти это ограничение платформы. Мы проанализируем как классические обходные пути, предложенные практикующими разработчиками (включая использование стандартных регистров сведений), так и современные штатные механизмы, появившиеся в последних версиях платформы 1С:Предприятие 8.3.
Если серверный вызов происходит интерактивно из формы, открытой пользователем, самым простым и логичным решением будет собрать всю необходимую информацию на сервере, передать ее обратно на клиент в виде структуры или массива, а затем на клиенте вызвать метод ПоказатьОповещениеПользователя.
Рассмотрим по шагам, как реализовать эту схему:
Посмотрим на практический пример реализации этого подхода:
&НаКлиенте
Процедура ВыполнитьОперациюНаСервере(Команда)
РезультатыВыполнения = ВыполнитьДействиеНаСервере();
// Обрабатываем полученные данные на клиенте
Для Каждого Оповещение Из РезультатыВыполнения Цикл
ПоказатьОповещениеПользователя(
Оповещение.Событие,
Оповещение.НавигационнаяСсылка,
Оповещение.Текст,
Оповещение.Картинка
);
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ВыполнитьДействиеНаСервере()
СписокОповещений = Новый Массив;
// ... Выполнение сложной бизнес-логики ...
// Если в процессе работы возникло событие, требующее оповещения:
ДанныеОповещения = Новый Структура;
ДанныеОповещения.Вставить("Событие", "Успешная загрузка");
ДанныеОповещения.Вставить("НавигационнаяСсылка", "");
ДанныеОповещения.Вставить("Текст", "Данные контрагентов успешно обновлены.");
ДанныеОповещения.Вставить("Картинка", БиблиотекаКартинок.Информация);
СписокОповещений.Добавить(ДанныеОповещения);
Возврат СписокОповещений;
КонецФункции
Данный способ отлично работает для простых синхронных операций. Однако он совершенно не подходит, если серверный процесс выполняется асинхронно (например, в фоновом задании), либо если нам нужно послать оповещение другому пользователю, сеанс которого активен в данный момент.
Рассмотрим оригинальное решение, которое часто применяется в конфигурациях, построенных на базе Библиотеки стандартных подсистем (БСП). В БСП присутствует стандартный механизм напоминаний, работающий через регистр сведений НапоминанияПользователя.
Суть этого метода заключается в том, что серверный код не пытается вызвать клиентское окно напрямую, а лишь делает запись в специальный регистр сведений, привязывая запись к конкретному пользователю системы. Система БСП на клиенте периодически опрашивает этот регистр и при появлении новых записей выводит форму оповещения.
Проанализируем алгоритм реализации этого обходного пути:
НапоминанияПользователя. В качестве ключевых полей указываем пользователя, время напоминания (текущую дату и время) и текст сообщения.ФормаОповещения регистра НапоминанияПользователя при ее открытии мы перехватываем управление, считываем текст напоминания, вызываем метод ПоказатьОповещениеПользователя и сразу же закрываем саму форму, чтобы она не мозолила глаза пользователю.Этот способ позволяет отправлять оповещения из любого серверного контекста (включая регламентные и фоновые задания) любому активному пользователю базы данных без непосредственной связи клиента и сервера в рамках одного сеанса. Для быстрой организации подобной рассылки можно использовать расширение для срочного оповещения активных пользователей.
Начиная с версии платформы 1С:Предприятие 8.3.26, фирма 1С реализовала долгожданный штатный механизм отправки уведомлений с сервера на клиент в режиме реального времени. Этот механизм называется УведомленияКлиента и работает без создания паразитной нагрузки на базу данных.
Выясним, как устроен этот механизм и как его правильно настроить:
УведомленияКлиента.ПодключитьОбработчик().УведомленияКлиента.ОтправитьУведомление(). Мы можем указать массив конкретных сеансов получателей или отправить уведомление всем активным сеансам.ПоказатьОповещениеПользователя.Рассмотрим пример кода для реализации этого современного подхода. Сначала настроим клиентскую часть в модуле управляемого приложения:
&НаКлиенте
Процедура ПриНачалеРаботыСистемы()
// Подключаем клиентский обработчик для приема серверных уведомлений
УведомленияКлиента.ПодключитьОбработчик("ОбработатьУведомлениеСервера");
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьУведомлениеСервера(ДанныеУведомления) Экспорт
// Проверяем, что пришло именно наше событие
Если ДанныеУведомления.Свойство("ТипСобытия") И ДанныеУведомления.ТипСобытия = "ВсплывающееОповещение" Тогда
ПоказатьОповещениеПользователя(
ДанныеУведомления.Заголовок,
ДанныеУведомления.Ссылка,
ДанныеУведомления.Текст
);
КонецЕсли;
КонецПроцедуры
Теперь посмотрим, как отправить уведомление с сервера:
&НаСервере
Процедура ОтправитьОповещениеПользователю(ЦелевойПользователь, ТекстОповещения)
// Формируем структуру с данными для отображения на клиенте
ПараметрыСообщения = Новый Структура;
ПараметрыСообщения.Вставить("ТипСобытия", "ВсплывающееОповещение");
ПараметрыСообщения.Вставить("Заголовок", "Уведомление от системы");
ПараметрыСообщения.Вставить("Текст", ТекстОповещения);
ПараметрыСообщения.Вставить("Ссылка", "");
// Находим активные сеансы нужного пользователя
МассивСеансов = ПолучитьСеансыПользователя(ЦелевойПользователь);
// Отправляем уведомление на клиенты
УведомленияКлиента.ОтправитьУведомление(МассивСеансов, ПараметрыСообщения);
КонецПроцедуры
Этот способ является наиболее производительным, надежным и архитектурно правильным в современных версиях платформы 1С.
Если в вашей организации развернута и настроена Система взаимодействия (сервер 1С:Диалог), вы можете использовать ее возможности для передачи служебных сигналов между сервером и клиентом. Система взаимодействия работает по протоколу WebSocket, что гарантирует моментальную доставку сообщений.
Разберем по шагам логику этого подхода:
СистемаВзаимодействия.НачатьПодключениеОбработчикНовыхСообщений().ПоказатьОповещениеПользователя.Преимуществом данного подхода является его независимость от версии платформы (главное, чтобы поддерживалась Система взаимодействия) и высокая скорость работы.
Если вы работаете на устаревших версиях платформы (ниже 8.3.26) и у вас не настроена Система взаимодействия, единственным надежным кросс-платформенным способом остается классическая модель «опроса» (polling).
Давайте проанализируем схему реализации данного механизма:
ОчередьОповещенийПользователей) с измерениями Пользователь и ИдентификаторЗаписи, и ресурсами Заголовок, Текст.ПодключитьОбработчикОжидания() с интервалом, например, в 10–15 секунд.ПоказатьОповещениеПользователя.Посмотрим на программную реализацию опроса через обработчик ожидания. Клиентская часть:
&НаКлиенте
Процедура ПриНачалеРаботыСистемы()
// Подключаем проверку очереди оповещений каждые 15 секунд
ПодключитьОбработчикОжидания("ПроверитьОчередьОповещений", 15);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьОчередьОповещений() Экспорт
НовыеОповещения = ПолучитьОповещенияИзОчередиНаСервере();
Для Каждого Оповещение Из НовыеОповещения Цикл
ПоказатьОповещениеПользователя(
Оповещение.Заголовок,
Оповещение.Ссылка,
Оповещение.Текст
);
КонецЦикла;
КонецПроцедуры
Серверная часть для получения и очистки очереди:
&НаСервереБезКонтекста
Функция ПолучитьОповещенияИзОчередиНаСервере()
СписокОповещений = Новый Массив;
ТекущийПользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
// Выбираем все записи из регистра сведений для текущего пользователя
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Очередь.Заголовок КАК Заголовок,
| Очередь.Текст КАК Текст,
| Очередь.Ссылка КАК Ссылка,
| Очередь.Идентификатор КАК Идентификатор
|ИЗ
| РегистрСведений.ОчередьОповещенийПользователей КАК Очередь
|ГДЕ
| Очередь.Пользователь = &Пользователь";
Запрос.УстановитьПараметр("Пользователь", ТекущийПользователь);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ДанныеОповещения = Новый Структура("Заголовок, Текст, Ссылка", Выборка.Заголовок, Выборка.Текст, Выборка.Ссылка);
СписокОповещений.Добавить(ДанныеОповещения);
// Удаляем запись из очереди, чтобы она не вывелась повторно
МенеджерЗаписи = РегистрыСведений.ОчередьОповещенийПользователей.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = ТекущийПользователь;
МенеджерЗаписи.Идентификатор = Выборка.Идентификатор;
МенеджерЗаписи.Удалить();
КонецЦикла;
Возврат СписокОповещений;
КонецФункции
Основным минусом данного метода является постоянная нагрузка на сервер СУБД из-за регулярных запросов от всех активных пользователей. Поэтому интервал опроса не рекомендуется делать слишком маленьким.
Если вы разрабатываете решение в конфигурации, где активно применяется БСП, и ваша серверная задача выполняется в фоновом режиме, рекомендуется использовать штатный механизм длительных операций БСП.
При запуске длительной операции на сервере через программный интерфейс БСП вы можете использовать метод ОбщегоНазначенияКлиентСервер.СообщитьПользователю() для записи сообщений в процессе выполнения фонового задания. БСП сама берет на себя задачу мониторинга состояния фонового задания с клиента с помощью встроенных обработчиков ожидания. После успешного завершения фонового задания клиентский код БСП считывает все накопленные сообщения и выводит их пользователю в удобном виде, избавляя вас от необходимости писать собственные обработчики ожидания и регистры очереди.
Важный совет по юзабилити: Старайтесь не злоупотреблять выводом всплывающих оповещений. Большое количество постоянно всплывающих окон быстро утомляет пользователей, из-за чего они начинают игнорировать важные системные уведомления или искать способы заблокировать и скрыть их на экране.