Разработка регламентных заданий в 1С — стандартная задача для автоматизации рутинных процессов. Однако довольно часто программисты сталкиваются с архитектурным барьером: регламентное задание выполняется в фоновом режиме на сервере, в то время как уведомление о результатах его работы необходимо показать активным пользователям на клиенте. Прямой вызов клиентских методов вроде ПоказатьОповещениеПользователя() или Предупреждение() из серверного кода регламентного задания невозможен и вызывает ошибку компиляции.
В этой статье мы подробно проанализируем ситуацию, разберем по шагам несколько эффективных архитектурных подходов, которые позволяют решить эту проблему «малой кровью», не перегружая базу данных созданием сотен индивидуальных записей.
Самым современным, быстрым и технологичным решением является использование встроенного механизма платформы — Системы взаимодействия. Этот механизм работает по протоколу WebSockets и позволяет серверу мгновенно отправлять сообщения на клиентские приложения без постоянных запросов к базе данных (технология push-уведомлений).
Выясним, как настроить этот процесс. Мы создадим служебное обсуждение, на которое будут подписаны все пользователи. При завершении работы регламентного задания сервер будет отправлять сообщение в это обсуждение, а на клиенте сработает обработчик, выводящий стандартное оповещение.
Рассмотрим пример кода для отправки сообщения из регламентного задания на сервере:
Процедура ОтправитьОповещениеЧерезСистемуВзаимодействия(ТекстСообщения) Экспорт
// Проверяем, доступна ли система взаимодействия в текущей базе
Если Не СистемаВзаимодействия.Доступна() Тогда
Возврат;
КонецЕсли;
// Попробуем найти существующее служебное обсуждение
ВыборкаОбсуждений = СистемаВзаимодействия.ПолучитьОбсуждения();
СлужебноеОбсуждение = Неопределено;
Для Каждого Обсуждение Из ВыборкаОбсуждений Цикл
Если Обсуждение.Ключ = "СистемныеОповещенияРегламентныхЗаданий" Тогда
СлужебноеОбсуждение = Обсуждение;
Прервать;
КонецЕсли;
КонецЦикла;
// Если обсуждение не найдено, создаем его
Если СлужебноеОбсуждение = Неопределено Тогда
СлужебноеОбсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
СлужебноеОбсуждение.Ключ = "СистемныеОповещенияРегламентныхЗаданий";
СлужебноеОбсуждение.Заголовок = "Системные уведомления";
СлужебноеОбсуждение.Групповое = Истина;
// Добавляем всех пользователей (или оставляем пустым, если обсуждение публичное)
СлужебноеОбсуждение.Записать();
КонецЕсли;
// Создаем и отправляем новое сообщение
НовоеСообщение = СистемаВзаимодействия.СоздатьСообщение(СлужебноеОбсуждение.Идентификатор);
НовоеСообщение.Текст = ТекстСообщения;
НовоеСообщение.Записать();
КонецПроцедуры
Теперь настроим клиентскую часть. В модуле управляемого приложения при старте системы нам необходимо подключить обработчик новых сообщений, который будет перехватывать сообщения из нашего служебного обсуждения и показывать их пользователю.
Процедура ПриНачалеРаботыСистемы()
// Подключаем обработчик новых сообщений Системы взаимодействия
Если СистемаВзаимодействия.Доступна() Тогда
ОписаниеОповещения = Новый ОписаниеОповещения("ОбработатьНовоеСообщениеСистемыВзаимодействия", ЭтотОбъект);
СистемаВзаимодействия.НачатьПодключениеОбработчикаНовыхСообщений(ОписаниеОповещения);
КонецЕсли;
КонецПроцедуры
Процедура ОбработатьНовоеСообщениеСистемыВзаимодействия(Сообщение, ДополнительныеПараметры) Экспорт
// Проверяем, что сообщение пришло именно из нашего служебного обсуждения
Обсуждение = СистемаВзаимодействия.ПолучитьОбсуждение(Сообщение.ИдентификаторОбсуждения);
Если Обсуждение.Ключ = "СистемныеОповещенияРегламентныхЗаданий" Тогда
// Выводим красивое всплывающее окно на клиенте
ПоказатьОповещениеПользователя("Уведомление от системы", , Сообщение.Текст, БиблиотекаКартинок.Информация);
КонецЕсли;
КонецПроцедуры
Преимущества этого подхода: мгновенная доставка сообщений, минимальная нагрузка на сервер СУБД, отсутствие периодических тяжелых запросов со стороны клиента.
Если Система взаимодействия не настроена или не может быть использована, стандартным выходом является хранение информации в базе данных. Но вместо того чтобы плодить сотни записей для каждого пользователя (как это делает типовой механизм напоминаний), мы применим оптимизированную схему всего с одной строкой в специальном регистре сведений.
Создадим непериодический регистр сведений ОповещенияРегламентныхЗаданий со следующей структурой:
Идентификатор (тип УникальныйИдентификатор или строка GUID)ТекстСообщения (тип Строка)ДатаСоздания (тип Дата)При выполнении регламентного задания сервер просто перезаписывает эту единственную запись в регистре, присваивая ей новый уникальный идентификатор. Разберем код записи на сервере:
Процедура ЗаписатьНовоеОповещениеДляВсех(ТекстУведомления) Экспорт
МенеджерЗаписи = РегистрыСведений.ОповещенияРегламентныхЗаданий.СоздатьМенеджерЗаписи();
// Поскольку измерений нет, запись всегда будет одна и та же
МенеджерЗаписи.Идентификатор = Новый УникальныйИдентификатор();
МенеджерЗаписи.ТекстСообщения = ТекстУведомления;
МенеджерЗаписи.ДатаСоздания = ТекущаяДатаСеанса();
МенеджерЗаписи.Записать(Истина);
КонецПроцедуры
На стороне клиента мы запускаем периодический опрос этого регистра с помощью обработчика ожидания. Чтобы не выводить одно и то же сообщение повторно, мы будем сохранять идентификатор последнего показанного оповещения в глобальной переменной или параметре сеанса.
Перем ПоследнийИдентификаторОповещения Экспорт;
Процедура ПриНачалеРаботыСистемы()
// Инициализируем переменную при старте системы
ПоследнийИдентификаторОповещения = Неопределено;
// Подключаем обработчик ожидания, например, раз в 60 секунд
ПодключитьОбработчикОжидания("ПроверитьНовыеСистемныеОповещения", 60);
КонецПроцедуры
Процедура ПроверитьНовыеСистемныеОповещения() Экспорт
// Читаем единственную запись из регистра сведений
ДанныеОповещения = ПолучитьПоследнееОповещениеИзРегистра();
Если ДанныеОповещения <> Неопределено Тогда
// Если идентификатор изменился, значит появилось новое сообщение
Если ДанныеОповещения.Идентификатор <> ПоследнийИдентификаторОповещения Тогда
// Если это не первый запуск (чтобы не спамить старыми сообщениями при входе)
Если ПоследнийИдентификаторОповещения <> Неопределено Тогда
ПоказатьОповещениеПользователя("Системное сообщение", , ДанныеОповещения.ТекстСообщения);
КонецЕсли;
ПоследнийИдентификаторОповещения = ДанныеОповещения.Идентификатор;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьПоследнееОповещениеИзРегистра()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОповещенияРегламентныхЗаданий.Идентификатор КАК Идентификатор,
| ОповещенияРегламентныхЗаданий.ТекстСообщения КАК ТекстСообщения
|ИЗ
| РегистрСведений.ОповещенияРегламентныхЗаданий КАК ОповещенияРегламентныхЗаданий";
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Новый Структура("Идентификатор, ТекстСообщения", Выборка.Идентификатор, Выборка.ТекстСообщения);
КонецЕсли;
Возврат Неопределено;
КонецФункции
Преимущества этого подхода: чрезвычайно прост в реализации, база данных не раздувается, так как в регистре всегда хранится ровно одна запись, а легкий запрос по индексу раз в минуту абсолютно незаметен для производительности СУБД.
Если регламентное задание запускается интерактивно пользователем или если клиентское приложение ожидает его завершения в фоновом режиме, правильным решением будет использование подсистемы «Длительные операции» из Библиотеки стандартных подсистем (БСП).
В процессе выполнения фонового задания на сервере мы можем использовать стандартный механизм отправки сообщений пользователю:
// Внутри серверного кода фонового задания
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Регламентный расчет успешно завершен!");
На стороне клиента при использовании функций БСП для ожидания фонового задания (например, ДлительныеОперацииКлиент.ОжидатьСовместноеВыполнениеПроцедуры()) все накопленные на сервере сообщения будут автоматически переданы на клиент и выведены в стандартное окно сообщений пользователю по окончании процесса.
Когда пользователям важно получать оперативные уведомления, даже если у них закрыто клиентское приложение 1С, отличным архитектурным решением становится интеграция с внешними мессенджерами. Регламентное задание может отправлять HTTP-запросы напрямую к API мессенджера (например, Telegram-боту).
Рассмотрим, как реализовать отправку уведомления в групповой чат компании через Telegram API:
Процедура ОтправитьУведомлениеВTelegram(ТекстСообщения) Экспорт
ТокенБота = "123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ"; // Замените на токен вашего бота
ИдентификаторЧата = "-100123456789"; // Идентификатор группы или канала
// Формируем текст запроса, кодируя спецсимволы в URL-формат
СтрокаЗапроса = "bot" + ТокенБота + "/sendMessage?chat_id=" + ИдентификаторЧата + "&text=" + КодироватьСтроку(ТекстСообщения, СпособКодированияСтроки.КодировкаURL);
Попытка
HTTPСоединение = Новый HTTPСоединение("api.telegram.org", 443, , , , 10, Новый ЗащищенноеСоединениеOpenSSL());
HTTPЗапрос = Новый HTTPЗапрос(СтрокаЗапроса);
HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
Если HTTPОтвет.КодСостояния <> 200 Тогда
ЗаписьЖурналаРегистрации("УведомленияРегламентныхЗаданий", УровеньЖурналаРегистрации.Предупреждение, , , "Ошибка отправки в Telegram: " + HTTPОтвет.ПолучитьТелоКакСтроку());
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("УведомленияРегламентныхЗаданий", УровеньЖурналаРегистрации.Ошибка, , , "Системная ошибка отправки в Telegram: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Преимущества этого подхода: мобильность получателей, отсутствие нагрузки на базу данных 1С и клиентские сеансы, гарантированная доставка сообщений вне зависимости от активности пользователя в программе.
Мы проанализировали различные варианты решения задачи вывода оповещений из фонового режима. Для современных конфигураций на платформе 8.3 лучшим выбором является Система взаимодействия, обеспечивающая мгновенный результат. Если же инфраструктура не позволяет ее использовать — схема с оптимизированным регистром сведений из одной записи станет надежным и нетребовательным к ресурсам решением.