Привет, программисты 1С. Продолжаем разбираться с сервером взаимодействия 1С. Типовая задача: пользователь запустил длительную операцию, она уходит в фоновое задание на сервере, а на форме нужно показывать прогресс выполнения в реальном времени — а не замороженный интерфейс с песочными часами. Если вы ещё не поднимали сервер взаимодействия, начните с обзорной статьи «Сервер взаимодействия 1С: что это, компоненты и установка», а здесь разберём именно программное взаимодействие сервера и клиента.
Покажу два независимых способа доставить прогресс фонового задания с сервера на клиент: через штатную систему взаимодействия 1С и через WebSocket с внешним ws-сервером. Код ниже — из реальной демо-конфигурации, её можно развернуть у себя и повторить.
Как прогресс попадает с сервера на клиент
Фоновое задание крутится на сервере в отдельном сеансе и напрямую до формы пользователя «дотянуться» не может: клиент уже не ждёт ответа синхронно. Значит, серверу нужен канал, по которому он инициативно шлёт сообщения на клиент, пока задание считает. Оба способа ниже решают ровно это, но каналы разные:
- Система взаимодействия — платформенный механизм 1С поверх WebSocket. Фоновое задание пишет сообщения в скрытое обсуждение, клиент подписан на новые сообщения и двигает индикатор. Сторонней инфраструктуры в коде нет — нужен только зарегистрированный сервер взаимодействия.
- Свой WebSocket — вы поднимаете внешний ws-сервер (например, на Django Channels), 1С подключается к нему Native-компонентой, а форма слушает тот же сокет через поле HTML. Больше кода и инфраструктуры, зато канал общий с веб- и мобильными клиентами.
Способ 1. Через систему взаимодействия 1С
Идея: при открытии формы создаём скрытое обсуждение с ключом, привязанным к пользователю, и подписываемся на новые сообщения в нём. Фоновое задание пишет в это же обсуждение счётчик прогресса, а обработчик на клиенте двигает индикатор.
Клиентская часть формы — создаём описания оповещений, формируем ключ обсуждения и подключаем обработчик новых сообщений:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ПослеВыполненияПодключения = Новый ОписаниеОповещения(
"ВыполнитьПослеОкончания", ЭтаФорма, , "ВыполнитьПриОшибке", ЭтаФорма);
ОбработкаСообщенийСервера = Новый ОписаниеОповещения(
"ОбработкаСообщенийСервера", ЭтаФорма);
ИдентификаторТекущегоПользователя = СистемаВзаимодействия.ИдентификаторТекущегоПользователя();
КлючОбсуждения = "ДемоФоновоеЗадание " + ИдентификаторТекущегоПользователя;
СоздатьОбсуждение(КлючОбсуждения, ИдентификаторТекущегоПользователя);
СистемаВзаимодействия.НачатьПодключениеОбработчикаНовыхСообщений(
ПослеВыполненияПодключения, КлючОбсуждения, ОбработкаСообщенийСервера);
КонецПроцедурыОбсуждение создаём один раз и делаем его невидимым (Отображаемое = Ложь), чтобы оно не мозолило глаза в списке чатов — это чисто транспортный канал под конкретного пользователя:
&НаСервереБезКонтекста
Процедура СоздатьОбсуждение(КлючОбсуждения, ИдентификаторТекущегоПользователя)
Если СистемаВзаимодействия.ПолучитьОбсуждение(КлючОбсуждения) = Неопределено Тогда
Обсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
Обсуждение.Отображаемое = Ложь;
Обсуждение.Ключ = КлючОбсуждения;
Обсуждение.Участники.Добавить(ИдентификаторТекущегоПользователя);
Обсуждение.Записать();
КонецЕсли;
КонецПроцедурыСерверная часть — фоновое задание. В цикле оно кладёт в обсуждение сообщение со структурой прогресса. Обратите внимание: данные едут не строкой, а структурой в поле Сообщение.Данные — на клиенте мы получим её как есть:
Процедура ДемоФоновоеЗадание() Экспорт
ИдентификаторТекущегоПользователя = СистемаВзаимодействия.ИдентификаторТекущегоПользователя();
КлючОбсуждения = "ДемоФоновоеЗадание " + ИдентификаторТекущегоПользователя;
Обсуждение = СистемаВзаимодействия.ПолучитьОбсуждение(КлючОбсуждения);
Для Счетчик = 1 По 10 Цикл
Данные = Новый Структура("Счетчик", Счетчик);
Сообщение = СистемаВзаимодействия.СоздатьСообщение(Обсуждение.Идентификатор);
Сообщение.Данные = Данные;
Сообщение.Записать();
Пауза(); // здесь у вас — реальный шаг длительной операции
КонецЦикла;
КонецПроцедурыЗамыкает всё обработчик новых сообщений на клиенте — вытаскивает счётчик из структуры и двигает индикатор на форме:
&НаКлиенте
Процедура ОбработкаСообщенийСервера(Сообщение, ДополнительныеПараметры) Экспорт
ИндикаторФоновогоЗадания = Сообщение.Данные.Счетчик * 10;
КонецПроцедурыВсё. Никакого своего сервера, JSON и парсинга — платформа доставляет структуру 1С «как есть». Это самый быстрый способ прикрутить живой прогресс-бар, если сервер взаимодействия у вас уже зарегистрирован.
Способ 2. Через WebSocket и внешний ws-сервер
Второй способ пригодится, когда канал реального времени нужен не только внутри 1С, но и общий с веб-клиентами: тот же сокет слушают и толстый клиент, и браузер. Здесь 1С выступает обычным ws-клиентом внешнего сервера (в демо — ws://localhost:8000/ws/server1s/1/, по адресу видно Django Channels).
Серверная часть подключает Native-компоненту ws-клиента и шлёт прогресс уже строкой JSON — внешний сервер про типы 1С ничего не знает:
Процедура ДемоФоновоеЗаданиеWebSocket() Экспорт
ПутьКомпоненты = "ОбщийМакет.КомпонентаWebSocket";
Если НЕ ПодключитьВнешнююКомпоненту(ПутьКомпоненты, "WebSocket", ТипВнешнейКомпоненты.Native) Тогда
ВызватьИсключение "Ошибка подключения внешней компоненты";
КонецЕсли;
Клиент = Новый("AddIn.WebSocket.Client");
АдресСервера = "ws://localhost:8000/ws/server1s/1/"; // вынесите в настройки, не хардкодьте
Попытка
Клиент.Подключиться(АдресСервера);
Исключение
ВызватьИсключение ОписаниеОшибки() + ": " + Клиент.ОписаниеОшибки();
КонецПопытки;
Для Счетчик = 1 По 10 Цикл
ТелоСообщения = ОбъектВJSON(Новый Структура("count", Счетчик));
Клиент.Отправить(ТелоСообщения);
Пауза();
КонецЦикла;
Клиент.Отключиться();
КонецПроцедурыНа форме прогресс-бар живёт в поле HTML: внутри — JS-клиент того же сокета. По входящему сообщению он двигает <progress> и прокидывает значение обратно в 1С через «мост» — клик по скрытой кнопке:
<script>
var interactionVariable = 0;
const socket1S = new WebSocket('ws://localhost:8000/ws/server1s/1/');
socket1S.onmessage = function(event) {
const data = JSON.parse(event.data);
document.getElementById('progressbar').value = data.message['count'] * 10;
interactionVariable = data.message['count'];
interactionButton.click(); // сигналим в 1С
};
</script>А со стороны 1С событие нажатия скрытой кнопки читает значение из окна документа поля HTML — это и есть мост JS → 1С:
&НаКлиенте
Процедура ПолеJSПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
ОкноДокумента = ЭтаФорма.Элементы.ПолеJS.Документ.defaultView;
Если НЕ ОкноДокумента = Неопределено Тогда
НажатыйЭлемент = ДанныеСобытия.Element;
Если НажатыйЭлемент.id = "interactionButton" Тогда
ИндикаторФоновогоЗадания = ОкноДокумента.interactionVariable * 10;
КонецЕсли;
КонецЕсли;
КонецПроцедурыРади этого моста и приходится городить скрытую кнопку: прямого способа «дёрнуть» серверную процедуру 1С из JS в поле HTML нет, поэтому JS меняет переменную и «нажимает» кнопку, а обработчик ПриНажатии уже штатно уходит на сервер.
Какой способ выбрать
Если прогресс нужен только внутри 1С и сервер взаимодействия уже развёрнут — берите способ 1: минимум кода, никакой внешней инфраструктуры, платформа сама тащит структуру. Если канал реального времени нужен общий с веб-частью (сайт, мобильное приложение на том же сокете) или вы и так держите свой backend — способ 2 на WebSocket ложится естественнее, но потребует внешнего ws-сервера и Native-компоненты. Разворачивание самого сервера взаимодействия под оба сценария разобрано в обзорной статье кластера.
Подводные камни
- Пауза в примере — учебная эмуляция. В демо задержка сделана запуском vbs-скрипта со
Sleep(1000)— это платформенно-зависимый костыль под Windows чисто чтобы «растянуть» демо. В бою вместоПауза()у вас реальные шаги операции; ничего эмулировать не нужно. - Адрес ws-сервера не хардкодьте. В примере он строкой в коде — вынесите в константу или регистр сведений, иначе перенос между контурами (dev/prod) превратится в правку кода.
- Система взаимодействия требует регистрации. Обсуждения и push не заработают, пока сервер взаимодействия не зарегистрирован в информационной базе (1С:Диалог или свой сервер).
- WebSocket-способ требует Native-компоненту и живой ws-сервер. Без поднятого внешнего сервера и зарегистрированной компоненты
AddIn.WebSocket.Clientподключение упадёт наПодключиться.
Разобрать оба способа на живом стенде, поднять сервер взаимодействия и связать 1С с внешним Python-бэкендом мы подробно проходим на курсе «Сервер взаимодействия 1С на Python» — там и Native-компонента, и Django Channels, и продовая обвязка.