Сообщение с сервера на клиент 1С 8.3: четыре рабочие схемы

· 9 мин

Содержание

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

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

Почему сервер 1С не может сам вызвать клиента

Платформа 1С:Предприятие 8.3 построена по схеме «запрос — ответ». Клиент вызывает сервер, сервер выполняет код и возвращает результат. Обратного канала, по которому сервер инициирует вызов процедуры на клиенте, во встроенном языке нет.

Отсюда три следствия, на которых спотыкаются чаще всего:

  • 🚫 ПоказатьОповещениеПользователя(), Оповестить(), ПоказатьПредупреждение() — методы клиента. В модуле с директивой &НаСервере или в серверном общем модуле их нет.
  • 🚫 Сообщить() на сервере не «отправляет» текст сразу. Сообщение копится и уходит клиенту вместе с ответом на текущий вызов.
  • 🚫 У регламентного задания нет клиента вообще. Всё, что оно «сообщает», видно только через свойства самого задания.

Обходных путей два. Первый — клиент периодически спрашивает сервер «есть что-то для меня?». Второй — использовать механизм, у которого собственный канал доставки: система взаимодействия. В свежих версиях БСП есть подсистема серверных оповещений, которая прячет эти детали за готовым программным интерфейсом. Если ваша конфигурация на ней, начните с неё и посмотрите описание её общего модуля.

Сообщение из серверного вызова: самый простой случай

Когда пользователь нажал кнопку в форме, а обработка выполняется на сервере, проблемы нет. Есть два варианта.

Вариант 1. СообщениеПользователю на сервере

&НаСервере
Процедура ПровестиПроверкуНаСервере()
	Ошибок = 3;
	Сообщение = Новый СообщениеПользователю;
	Сообщение.Текст = СтрШаблон("Найдено ошибок: %1", Формат(Ошибок, "ЧГ=0"));
	Сообщение.Сообщить();
КонецПроцедуры

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

Вариант 2. Вернуть текст и показать всплывающее окно

Окно сообщений внизу формы пользователь часто не замечает. Всплывающее оповещение в углу экрана заметнее:

&НаКлиенте
Процедура Проверить(Команда)
	Текст = ПроверитьНаСервере();
	ПоказатьОповещениеПользователя("Проверка завершена", , Текст, ,
		СтатусОповещенияПользователя.Важное);
КонецПроцедуры

&НаСервере
Функция ПроверитьНаСервере()
	// ... полезная работа ...
	Возврат "Расхождений нет";
КонецФункции

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

Длительные операции: сообщения из фонового задания

Если обработка идёт минуты, её выносят в фоновое задание, чтобы форма не «висела». Внутри задания можно вызывать Сообщение.Сообщить() как обычно, но текст никуда не выводится: он сохраняется в самом задании. Клиент забирает его через серверный вызов.

Запуск задания

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

Получение сообщений на клиенте

&НаСервереБезКонтекста
Функция СообщенияЗадания(ИдентификаторЗадания)
	Результат = Новый Массив;
	Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
	Если Задание = Неопределено Тогда
		Возврат Результат;
	КонецЕсли;
	Для Каждого Сообщ Из Задание.ПолучитьСообщенияПользователю(Истина) Цикл
		Результат.Добавить(Сообщ.Текст);
	КонецЦикла;
	Возврат Результат;
КонецФункции

Параметр Истина удаляет полученные сообщения из задания, иначе при каждом опросе придут все сообщения с самого начала. Объекты СообщениеПользователю на клиент не передаются, поэтому функция возвращает массив строк.

Опрашивать задание удобно обработчиком ожидания формы: ПодключитьОбработчикОжидания("ПроверитьЗадание", 2, Ложь). Когда задание закончилось, обработчик отключают через ОтключитьОбработчикОжидания("ПроверитьЗадание").

В конфигурациях на БСП всё это уже сделано в подсистеме «Длительные операции»: она запускает задание, крутит индикатор, показывает прогресс и выводит сообщения. Писать опрос вручную стоит, только если БСП нет.

Очередь уведомлений в регистре сведений

Это универсальная схема для случая, когда событие возникло на сервере без участия адресата. Например, регламентное задание загрузило выписку и нашло неопознанные платежи, а бухгалтеру надо об этом сказать.

Идея простая: сервер пишет запись в регистр, клиент раз в минуту забирает «свои» записи и показывает их.

Шаг 1. Создайте регистр сведений

Непериодический регистр ОчередьОповещений, режим записи «Независимый»:

ПолеВидТип
ИмяПользователяИзмерениеСтрока (100)
ИдентификаторИзмерениеСтрока (36)
ДатаРесурсДата и время
ТекстРесурсСтрока (неограниченная)

Имя пользователя как строка делает схему независимой от БСП. Если справочник «Пользователи» есть, измерение можно сделать ссылкой на него.

Шаг 2. Серверная процедура отправки

Общий модуль ОповещенияСервер, флаг «Сервер»:

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

Вызывать её можно откуда угодно на сервере: из обработчика проведения, из регламентного задания, из обмена.

Шаг 3. Серверная функция получения

Общий модуль ОповещенияВызовСервера, флаги «Сервер» и «Вызов сервера»:

Функция ЗабратьОповещения() Экспорт
	УстановитьПривилегированныйРежим(Истина);
	ИмяТекущего = ПользователиИнформационнойБазы.ТекущийПользователь().Имя;
	Результат = Новый Массив;

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

	Пока Выборка.Следующий() Цикл
		Результат.Добавить(Выборка.Текст);
		Набор = РегистрыСведений.ОчередьОповещений.СоздатьНаборЗаписей();
		Набор.Отбор.ИмяПользователя.Установить(ИмяТекущего);
		Набор.Отбор.Идентификатор.Установить(Выборка.Идентификатор);
		Набор.Записать(); // пустой набор с отбором удаляет запись
	КонецЦикла;

	Возврат Результат;
КонецФункции

Шаг 4. Опрос на клиенте

В глобальном клиентском общем модуле (флаги «Клиент» и «Глобальный»):

Процедура ПроверитьОповещенияСервера() Экспорт
	Тексты = ОповещенияВызовСервера.ЗабратьОповещения();
	Для Каждого Текст Из Тексты Цикл
		ПоказатьОповещениеПользователя("Уведомление", , Текст, ,
			СтатусОповещенияПользователя.Важное);
	КонецЦикла;
КонецПроцедуры

В модуле управляемого приложения, в обработчике ПриНачалеРаботыСистемы:

ПодключитьОбработчикОжидания("ПроверитьОповещенияСервера", 60);

Каждый опрос — это серверный вызов. 200 пользователей с интервалом 10 секунд дают 20 вызовов в секунду только на проверку пустой очереди. Интервал 60 секунд снижает нагрузку до 3–4 вызовов в секунду и для бизнес-уведомлений обычно достаточен.

В типовой конфигурации не правьте модуль приложения напрямую: подключите обработчик из расширения, иначе изменения придётся переносить при каждом обновлении.

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

Когда адресатов десятки или сотни, отправку не делают в интерактивном вызове: пользователь будет ждать, пока запишутся все записи. Рассылку выносят в фоновое или регламентное задание.

Пример процедуры, которая разошлёт текст всем, кто сейчас работает в базе:

Процедура РазослатьАктивным(Текст) Экспорт
	Отправлено = Новый Соответствие;
	Для Каждого Сеанс Из ПолучитьСеансыИнформационнойБазы() Цикл
		Если Сеанс.Пользователь = Неопределено Тогда
			Продолжить;
		КонецЕсли;
		Имя = Сеанс.Пользователь.Имя;
		Если Отправлено[Имя] = Неопределено Тогда
			ОповещенияСервер.ДобавитьОповещение(Имя, Текст);
			Отправлено.Вставить(Имя, Истина);
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

Запуск с клиента через серверный вызов:

ФоновыеЗадания.Выполнить("ОповещенияСервер.РазослатьАктивным", ПараметрыЗадания, , "Рассылка оповещений");

Соответствие Отправлено защищает от дублей: у одного пользователя бывает несколько сеансов (тонкий клиент, веб-клиент, фоновые задания под его именем). У сеансов фоновых заданий без пользователя свойство Пользователь пустое, их пропускаем.

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

Система взаимодействия: доставка без опроса

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

Порядок работы:

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

Ограничения схемы:

  • 🌐 Нужен доступ к серверу системы взаимодействия: облачному сервису фирмы 1С или своему серверу. В закрытом контуре без интернета это отдельный проект.
  • 👤 Автором сообщения станет пользователь, под которым выполняется код. Для регламентного задания заведите технического пользователя, чтобы уведомления приходили от понятного отправителя.
  • 📵 Если пользователь отключил обсуждения в своём клиенте, сообщение он увидит, только когда откроет обсуждение.

Точные параметры методов зависят от версии платформы, поэтому перед написанием кода сверьтесь с синтакс-помощником в Конфигураторе.

Какой способ выбрать: сравнение

СпособКогда подходитСкорость доставкиНагрузкаОграничения
Сообщение в серверном вызовеПользователь сам запустил действиеСразу по окончании вызоваНетТолько инициатору, только в рамках вызова
Сообщения фонового заданияДолгая обработка, запущенная пользователемИнтервал опроса заданияМалаяТолько тому, кто запустил задание
Очередь в регистре + опросСобытие на сервере, любые адресатыИнтервал опроса (обычно 60 с)Растёт с числом пользователейНужно доработать конфигурацию
Система взаимодействияНужна мгновенная доставкаПочти сразуНа стороне сервиса обсужденийРегистрация базы, доступ к серверу взаимодействия
Кто инициирует оповещение?

Отладка и частые ошибки

Процедура или функция с указанным именем не определена (ПоказатьОповещениеПользователя). Метод вызван в серверном контексте. Перенесите вызов в процедуру &НаКлиенте, а с сервера возвращайте только текст.

Обработчик ожидания не срабатывает. Процедура должна быть экспортной и находиться в том же модуле, что и вызов ПодключитьОбработчикОжидания(), либо в глобальном клиентском общем модуле. Если процедура лежит в обычном клиентском модуле без флага «Глобальный», платформа её не найдёт.

Сообщения из регламентного задания «пропадают». У регламентного задания нет клиента. Сообщить() внутри него пишет текст в само задание, его видно разве что в консоли заданий. Используйте очередь или систему взаимодействия.

Уведомление приходит с опозданием или не приходит вовсе. Проверьте, не пишется ли запись в регистр внутри транзакции, которая потом откатывается. Запись становится видна клиенту только после фиксации транзакции. Если документ не провёлся, уведомление о нём пропадёт вместе с откатом — это правильное поведение.

Одно уведомление показывается много раз. Записи не удаляются после чтения: забыли Набор.Записать() с отбором или у пользователя нет прав на изменение регистра. Привилегированный режим в функции получения решает вопрос с правами.

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

Для пошаговой отладки фонового задания откройте в Конфигураторе Отладка → Подключение, нажмите «Автоматическое подключение» и отметьте фоновые задания. Сервер 1С при этом должен быть запущен с ключом -debug, иначе точки останова внутри задания не сработают.

Частые вопросы

Можно ли из серверного кода открыть форму у пользователя?
Нет. Открыть форму может только клиентский код. С сервера передают данные, а клиент по ним решает, что открыть, например по навигационной ссылке из текста уведомления.
Работает ли ПоказатьОповещениеПользователя в веб-клиенте?
Да, оповещение показывается в окне браузера. Опрос обработчиком ожидания в веб-клиенте тоже работает, пока вкладка открыта.
Как сделать уведомление кликабельным?
Передайте вторым параметром ПоказатьОповещениеПользователя() навигационную ссылку на объект, полученную на сервере через ПолучитьНавигационнуюСсылку(). Щелчок по оповещению откроет объект.
Можно ли оповестить пользователя, который сейчас не в базе?
Схема с регистром это позволяет: запись дождётся входа, и первый опрос покажет её. Сообщение в обсуждении тоже сохранится и будет видно при следующем входе.
Чем Оповестить() отличается от ПоказатьОповещениеПользователя()?
Оповестить() передаёт событие между открытыми формами одного клиента, пользователь его не видит. ПоказатьОповещениеПользователя() выводит окно на экран. Оба метода только клиентские.