Сообщение с сервера на клиент 1С 8.3: четыре рабочие схемы
Содержание
Сервер 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С или своему серверу. В закрытом контуре без интернета это отдельный проект.
- 👤 Автором сообщения станет пользователь, под которым выполняется код. Для регламентного задания заведите технического пользователя, чтобы уведомления приходили от понятного отправителя.
- 📵 Если пользователь отключил обсуждения в своём клиенте, сообщение он увидит, только когда откроет обсуждение.
Точные параметры методов зависят от версии платформы, поэтому перед написанием кода сверьтесь с синтакс-помощником в Конфигураторе.
Какой способ выбрать: сравнение
| Способ | Когда подходит | Скорость доставки | Нагрузка | Ограничения |
|---|---|---|---|---|
| Сообщение в серверном вызове | Пользователь сам запустил действие | Сразу по окончании вызова | Нет | Только инициатору, только в рамках вызова |
| Сообщения фонового задания | Долгая обработка, запущенная пользователем | Интервал опроса задания | Малая | Только тому, кто запустил задание |
| Очередь в регистре + опрос | Событие на сервере, любые адресаты | Интервал опроса (обычно 60 с) | Растёт с числом пользователей | Нужно доработать конфигурацию |
| Система взаимодействия | Нужна мгновенная доставка | Почти сразу | На стороне сервиса обсуждений | Регистрация базы, доступ к серверу взаимодействия |
ПоказатьОповещениеПользователя() на клиенте.ПолучитьСообщенияПользователю(Истина) по таймеру формы. При наличии БСП — подсистема «Длительные операции».Отладка и частые ошибки
Процедура или функция с указанным именем не определена (ПоказатьОповещениеПользователя). Метод вызван в серверном контексте. Перенесите вызов в процедуру &НаКлиенте, а с сервера возвращайте только текст.
Обработчик ожидания не срабатывает. Процедура должна быть экспортной и находиться в том же модуле, что и вызов ПодключитьОбработчикОжидания(), либо в глобальном клиентском общем модуле. Если процедура лежит в обычном клиентском модуле без флага «Глобальный», платформа её не найдёт.
Сообщения из регламентного задания «пропадают». У регламентного задания нет клиента. Сообщить() внутри него пишет текст в само задание, его видно разве что в консоли заданий. Используйте очередь или систему взаимодействия.
Уведомление приходит с опозданием или не приходит вовсе. Проверьте, не пишется ли запись в регистр внутри транзакции, которая потом откатывается. Запись становится видна клиенту только после фиксации транзакции. Если документ не провёлся, уведомление о нём пропадёт вместе с откатом — это правильное поведение.
Одно уведомление показывается много раз. Записи не удаляются после чтения: забыли Набор.Записать() с отбором или у пользователя нет прав на изменение регистра. Привилегированный режим в функции получения решает вопрос с правами.
Из фонового задания каждый раз приходят все сообщения заново. В ПолучитьСообщенияПользователю() не передан параметр Истина.
Для пошаговой отладки фонового задания откройте в Конфигураторе Отладка → Подключение, нажмите «Автоматическое подключение» и отметьте фоновые задания. Сервер 1С при этом должен быть запущен с ключом -debug, иначе точки останова внутри задания не сработают.
Частые вопросы
Можно ли из серверного кода открыть форму у пользователя?
Работает ли ПоказатьОповещениеПользователя в веб-клиенте?
Как сделать уведомление кликабельным?
ПоказатьОповещениеПользователя() навигационную ссылку на объект, полученную на сервере через ПолучитьНавигационнуюСсылку(). Щелчок по оповещению откроет объект.Можно ли оповестить пользователя, который сейчас не в базе?
Чем Оповестить() отличается от ПоказатьОповещениеПользователя()?
Оповестить() передаёт событие между открытыми формами одного клиента, пользователь его не видит. ПоказатьОповещениеПользователя() выводит окно на экран. Оба метода только клиентские.