В журнале регистрации мы видим только вход/выход из программы и операции по изменению данных. Если пользователь просматривает отчеты, документы, то мы особо ничего не увидим, только вход и выход. Как понять, работает пользователь в программе или нет?
Основная идея
Если абстрагироваться от конкретных функций, выполняемых пользователем, то внешне работа пользователя заключается в открытии окон, перемещении курсора, вводе значений в поля формы. Таким образом, если список открытых окон в программе не меняется, а активное поле остается тем же самым, то значит пользователь не работает с программой, даже если программа открыта. Другой способ отслеживать активность пользователя описан здесь, он заключается в проверке движений мышью/нажатий клавиатуры.
Получить список окон можно с помощью функции ПолучитьОкна(), доступной на клиенте. Каждое окно содержит список форм. Проверить активность формы можно с помощью функции формы ВводДоступен(). Текущий элемент формы можно получить у свойства формы ТекущийЭлемент.
Регистр сведений "Активность пользователей"
Чтобы понять, изменилось ли что-нибудь за некоторый интервал времени, нужно хранить предыдущее состояние окон программы. Очевидно, что хранить весь список окон и форм особого смысла нет, поэтому будем по строке, полученной склеиванием заголовков окон и форм, вычислять хеш-функцию, и записывать значение хеша в регистре сведений.
Тогда структура регистра АктивностьПользователей будет выглядеть следующим образом:
Измерения:
- Пользователь (тип "СправочникСсылка.Пользователи")
- НомерСеанса (тип "Число")
Ресурсы:
- Хеш (тип "Число")
Реквизиты:
- Комментарий (тип "Строка")
- Ссылка (тип "ЛюбаяСсылка")
Периодический (в пределах секунды), режим записи независимый.
Комментарий и ссылка позволяют записать с каким окном и с каким объектом базы данных работал пользователь. Номер сеанса нужен для того, чтобы исключить ситуацию, когда один пользователь запустил два окна программы и "ушел". По номеру сеанса можно их различать и смотреть активность по каждому сеансу.
Как регистрировать активность пользователя?
Чтобы зарегистрировать активность пользователя программы, нужно периодически сканировать список открытых окон и форм в программе. Для этого нужно подключить обработчик ожидания. Это можно сделать из модуля управляемого приложения.
Модификация модуля управляемого приложения
В процедуре ПриНачалеРаботыСистемы() модуля управляемого приложения нужно сделать вызов функции из глобального клиентского модуля. Например, так:
Процедура ПриНачалеРаботыСистемы()
// СтандартныеПодсистемы
СтандартныеПодсистемыКлиент.ПриНачалеРаботыСистемы();
// Конец СтандартныеПодсистемы
// ПодключаемоеОборудование
МенеджерОборудованияКлиент.ПриНачалеРаботыСистемы();
// Конец ПодключаемоеОборудование
// Постовалов, 01.08.2018
ВключитьРегистрациюАктивностиПользователя();
// ---
КонецПроцедуры
Общий модуль АктивностьПользователейГлобальный
Функцию ВключитьРегистрациюАктивностиПользователя() создаем в глобальном клиентском общем модуле (это означает, что у общего модуля должны стоять флажки "глобальный" и "клиент").
Процедура ВключитьРегистрациюАктивностиПользователя() Экспорт
АктивностьПользователейСервер.РегистрацияАктивностиПользователя(0,,"#Начало работы");//1
ПодключитьОбработчикОжидания("ПроверитьАктивностьПользователя", 60);//2
КонецПроцедуры
Прокомментируем эту функцию.
// 1. Первой строкой этой процедуры регистрируем начало нового сеанса пользователя с помощью функции РегистрацияАктивностиПользователя() серверного общего модуля АктивностьПользователейСервер. Эта функция будет описана далее.
//2. Во второй строке подключается обработчик ожидания, который будет вызывать функцию ПроверитьАктивностьПользователя() каждую минуту. Эта функция располагается в этом же модуле и выглядит следующим образом:
Процедура ПроверитьАктивностьПользователя() Экспорт
Окна = ПолучитьОкна();
ТекстСообщения = "";
Ссылка = Неопределено;
АктивнаяФорма = "";
Для Каждого Окно Из Окна Цикл
ТекстСообщения = ТекстСообщения + "["+Окно.Заголовок+"] ";
Формы = Окно.Содержимое;
Для Каждого Ф Из Формы Цикл
Если Ф.ВводДоступен() Тогда
// Это активная форма
Ссылка = ПолучитьЗначениеСвойства(ПолучитьЗначениеСвойства(Ф,"Объект"),"Ссылка");//3
Активность="!";
АктивнаяФорма = Ф.Заголовок;
Иначе
Активность="";
КонецЕсли;
ТекстСообщения = ТекстСообщения
+ "("+Ф.Заголовок+": "+ПолучитьЗначениеСвойства(Ф.ТекущийЭлемент,"Имя")+Активность+")";
КонецЦикла;
КонецЦикла;
ЗавершитьСеанс = АктивностьПользователейСервер.РегистрацияАктивностиПользователя(
Хэш(ТекстСообщения),Ссылка,АктивнаяФорма); //4
Если ЗавершитьСеанс Тогда // 5
ЗавершитьРаботуСистемы(Истина);
КонецЕсли;
КонецПроцедуры
// 3. Чтобы понять, с каким объектом работает пользователь, хотелось бы получить ссылку на объект. Как правило (но, к сожалению, не обязательно), главный реквизит формы справочника или документа называется "Объект". Так как мы не знаем точно, является ли форма формой документа или справочника, то получение значения этого реквизита сделаем через вспомогательную функцию ПолучитьЗначениеСвойства(). С другой стороны, может оказаться, что форма имеет главный реквизит с названием "Объект", но у него нет реквизита Ссылка.
Функция ПолучитьЗначениеСвойства(Переменная, Свойство)
Если Переменная<>Неопределено
И ТипЗнч(Переменная)<>Тип("Null")
И ТипЗнч(Переменная)<>Тип("Строка")
И ТипЗнч(Переменная)<>Тип("Число")
И ТипЗнч(Переменная)<>Тип("Дата")
И ТипЗнч(Переменная)<>Тип("Булево")
И ТипЗнч(Переменная)<>Тип("ДекорацияФормы")
Тогда
Стр = Новый Структура(Свойство, Неопределено);
ЗаполнитьЗначенияСвойств(Стр, Переменная);
Возврат Стр[Свойство];
КонецЕсли;
Возврат Неопределено;
КонецФункции
// 4. Хэш-функцию от строки вычисляем по следующему алгоритму:
//////////////////////////////////////////////////////////////////////
//СтрокаХэш - исходный текст
//hash- начальное значение hash
// М - множитель (влияет накачество хэш и производительность)
// TABLE_SIZE - размер получаемого ключа, как Максимальная величина + 1
Функция Хэш(СтрокаХэш, hash=0, M = 31, TABLE_SIZE = 18446744073709551616)
//TABLE_SIZE = 18446744073709551615; 64 бита
//M = 31; Умножитель
ДлинаСтроки = СтрДлина(СтрокаХэш);
Для к=1 по ДлинаСтроки цикл
hash = M * hash + КодСимвола(Сред(СтрокаХэш,к,1));
конеццикла;
возврат hash%TABLE_SIZE;
КонецФункции
//5. Если функция РегистрацияАктивностиПользователя() вернула Истина, то завершаем сеанс работы пользователя.
Общий модуль АктивностьПользователейСервер
Теперь перейдем к записи в регистр. Функция РегистрацияАктивностиПользователя располагается в серверном общем модуле АктивностьПользователейСервер.
Функция РегистрацияАктивностиПользователя(Хеш,Ссылка=Неопределено,Комментарий = "") Экспорт
ТекДата = ТекущаяДата();
НомерСеанса = НомерСеансаИнформационнойБазы();
ЗавершитьСеанс = Ложь;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| АктивностьПользователейСрезПоследних.Период,
| АктивностьПользователейСрезПоследних.Пользователь,
| АктивностьПользователейСрезПоследних.Хеш,
| АктивностьПользователейСрезПоследних.Комментарий,
| АктивностьПользователейСрезПоследних.Ссылка
|ИЗ
| РегистрСведений.АктивностьПользователей.СрезПоследних(
| &ТекДата,
| Пользователь = &ТекущийПользователь
| И НомерСеанса = &НомерСеанса) КАК АктивностьПользователейСрезПоследних";
Запрос.УстановитьПараметр("ТекДата", ТекДата);
Запрос.УстановитьПараметр("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);
Запрос.УстановитьПараметр("НомерСеанса",НомерСеанса);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Следующий() Тогда
Простой = 0;
Если НачалоДня(ТекДата)=НачалоДня(Выборка.Период) Тогда
Простой = ТекДата-Выборка.Период; // в секундах
КонецЕсли;
// Проверяем простой
Если Простой > 3600 Тогда
ЗавершитьСеанс=Истина;
КонецЕсли;
Если Выборка.Хеш = Хеш Тогда
// Активности не было...
Возврат ЗавершитьСеанс;
КонецЕсли;
КонецЕсли;
Рег = РегистрыСведений.ДОРН_АктивностьПользователей.СоздатьМенеджерЗаписи();
Рег.Период = ТекДата;
Рег.Пользователь = ПараметрыСеанса.ТекущийПользователь;
Рег.НомерСеанса = НомерСеанса;
Рег.Хеш = Хеш;
Рег.Комментарий = Комментарий;
Рег.Ссылка=Ссылка;
Рег.Записать(Истина);
Возврат ЗавершитьСеанс;
КонецФункции
В функции сначала с помощью запроса получаем текущее состояние активности пользователя. Если хеш не изменился, то вычисляем время простоя. Если время простоя превышает максимально возможное (в примере задано 3600 секунд), то выполняется завершение работы системы. Если значение хеш-функции изменилось, тогда записываем в регистр сведений новое значение хеша.
Насколько мониторинг замедляет работу пользователя?
По проведенным замерам времени проверка активности занимает менее 1 секунды, поэтому визуально для пользователя существенного замедления работы не происходит.
Результаты
Активность пользователей можно анализировать или непосредственно, просматривая записи регистра (рис.1), или написать специальные отчеты. Например, я немного модифицировал стандартную обработку "Активные пользователи" (рис. 2), добавив туда колонку "Статус" и написал отчет "Распределение работы пользователей" (рис. 3).
Рис. 1.
Рис. 2.
Рис. 3.