При администрировании информационных баз нередко возникает задача повысить безопасность или оптимизировать использование клиентских лицензий: если пользователь отошел от рабочего места и не совершает никаких действий, требуется заблокировать его интерфейс окном ввода пароля. Разработчики часто пытаются решить эту задачу централизованно через фоновое задание, отслеживающее активность сеансов в кластере серверов. Однако при попытке вызвать платформенный метод ЗаблокироватьРаботуПользователя() возникает логическое затруднение: как направить этот вызов в сеанс конкретного пользователя?
Разберем архитектурные ограничения платформы 1С:Предприятие, выясним причину, по которой нельзя напрямую заблокировать удаленный сеанс с сервера, и детально рассмотрим работающие схемы реализации этой задачи.
Проанализируем работу самого метода. В синтакс-помощнике указано, что процедура ЗаблокироватьРаботуПользователя() предназначена для интерактивной блокировки интерфейса текущего клиентского приложения и вызова стандартного окна авторизации. У метода есть ключевые особенности:
По этой причине фоновое задание, анализирующее кластер серверов, не может напрямую «приказать» клиентскому приложению открыть окно блокировки. Команда на блокировку должна быть получена и выполнена самим клиентским сеансом.
Рассмотрим классический способ взаимодействия между серверным фоновым заданием и клиентскими сеансами, предложенный участниками обсуждения. Схема состоит из трех ключевых этапов:
ЗаблокироватьРаботуПользователя() на клиенте.Разберем по шагам реализацию каждого элемента этой схемы.
Для передачи команды создадим непериодический независимый регистр сведений, например, КомандыБлокировкиСеансов. В структуру регистра включим следующие реквизиты:
СправочникСсылка.Пользователи).Число) — крайне важно хранить именно номер сеанса из кластера, поскольку один и тот же пользователь может открыть несколько независимых баз или сессий. Блокировать нужно только простаивающую сессию.Булево).Посмотрим на пример кода серверной процедуры, которая после анализа активности сеансов делает запись в регистр сведений:
Процедура ЗарегистрироватьНеобходимостьБлокировки(ПользовательСсылка, НомерСеанса) Экспорт
МенеджерЗаписи = РегистрыСведений.КомандыБлокировкиСеансов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = ПользовательСсылка;
МенеджерЗаписи.НомерСеанса = НомерСеанса;
МенеджерЗаписи.Заблокировать = Истина;
МенеджерЗаписи.Записать(Истина);
КонецПроцедуры
На стороне клиента необходимо запустить периодическую проверку. Для этого в модуле управляемого приложения в процедуре ПриНачалеРаботыСистемы подключаем клиентский обработчик ожидания:
Процедура ПриНачалеРаботыСистемы()
// Подключаем проверку каждые 60 секунд
ПодключитьОбработчикОжидания("ПроверитьНеобходимостьБлокировкиСеанса", 60);
КонецПроцедуры
Важный момент: не следует устанавливать слишком частый интервал (например, каждые 2-5 секунд), так как постоянные запросы от десятков пользователей создадут лишнюю паразитную нагрузку на СУБД. Интервала в 60–180 секунд вполне достаточно для отслеживания простоев.
В глобальном клиентском общем модуле или модуле приложения разместим процедуру проверки. Если сервер возвращает признак блокировки, сеанс сначала удаляет команду (чтобы не заблокироваться повторно после ввода пароля), а затем вызывает платформенное окно блокировки:
Процедура ПроверитьНеобходимостьБлокировкиСеанса() Экспорт
Если ТребуетсяБлокировкаНаСервере() Тогда
// Вызываем блокировку текущего клиентского сеанса
ЗаблокироватьРаботуПользователя();
КонецЕсли;
КонецПроцедуры
На сервере проверяем наличие записи по номеру текущего сеанса информационной базы:
Функция ТребуетсяБлокировкаНаСервере() Экспорт
ТекущийНомерСеанса = ПолучитьТекущийСеансИнформационнойБазы().НомерСеанса;
Отбор = Новый Структура;
Отбор.Вставить("НомерСеанса", ТекущийНомерСеанса);
Отбор.Вставить("Заблокировать", Истина);
Результат = РегистрыСведений.КомандыБлокировкиСеансов.Получить(Отбор);
Если Результат.Заблокировать Тогда
// Очищаем команду, чтобы после разблокировки пользователь продолжил работу
МенеджерЗаписи = РегистрыСведений.КомандыБлокировкиСеансов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
МенеджерЗаписи.НомерСеанса = ТекущийНомерСеанса;
МенеджерЗаписи.Удалить();
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
Проанализируем важную техническую проблему, с которой можно столкнуться при анализе параметра «Время последней активности» в консоли администрирования кластера или через COM-соединение:
Кластер серверов фиксирует активность исключительно в момент серверных вызовов. Если пользователь открыл объемный отчет, внимательно изучает строки документа, набирает текст служебной записки или работает с локальными реквизитами формы без обращения к серверу, то с точки зрения кластера такой сеанс простаивает. Если ориентироваться только на время вызовов в кластере, произойдет ложная блокировка реально работающего сотрудника.
В зависимости от конечной цели требования бизнеса можно выполнить более простыми и надежными средствами:
ЗаблокироватьРаботуПользователя() без постоянных опросов базы данных.ЗаблокироватьРаботуПользователя() не поможет, так как заблокированный сеанс продолжает удерживать лицензию. В этом случае правильнее настроить параметры базы в свойствах кластера: «Время засыпания пассивных сеансов» и «Время завершения спящих сеансов», либо принудительно завершать сеансы через утилиту RAS/RAC.