Как ограничить доступ к дополнительным реквизитам БСП без снятия с поддержки и безопасно перезаписать 36 000 элементов при 200 активных пользователях?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

В практике разработчика 1С часто встречаются задачи, которые кажутся простыми на первый взгляд, но требуют глубокого понимания архитектуры платформы «1С:Предприятие 8.3» и Библиотеки стандартных подсистем (БСП). Рассмотрим две такие классические задачи, которые часто предлагают на собеседованиях для оценки профпригодности специалиста.

Первая задача проверяет умение работать с типовым механизмом дополнительных свойств БСП в условиях жесткого запрета на снятие объектов с поддержки. Вторая задача направлена на оценку навыков оптимизации высоконагруженных систем: необходимо массово обновить данные, не заблокировав работу 200 активных сеансов пользователей.

Разберем детально архитектурно верные решения для обеих задач.

---

Решение задачи №1: Ограничение доступа к дополнительному реквизиту справочника «Партнеры»

Постановка проблемы: Имеется дополнительный реквизит справочника Партнеры. Требуется закрыть доступ (скрыть или запретить редактирование) к нему для определенных пользователей. При этом сам справочник и его форму элемента снимать с поддержки (разрешать редактирование в основной конфигурации) категорически нельзя.

Поскольку речь идет о дополнительных реквизитах, мы сразу понимаем, что система построена с использованием БСП. Особенность БСП заключается в том, что дополнительные реквизиты не существуют в метаданных справочника как обычные реквизиты — они создаются на управляемой форме программно при ее инициализации. Следовательно, обычная настройка видимости прав на уровне ролей конфигурации здесь не сработает напрямую. Нам нужно перехватить процесс динамического построения формы.

Проанализируем два наиболее технологичных способа решения этой задачи без снятия конфигурации с поддержки.

Вариант А. Использование расширения конфигурации и перехват построения формы (Адаптация формы)

Этот способ основан на использовании механизма расширений. Мы можем заимствовать форму элемента справочника Партнеры в расширение (оставив ее в основной конфигурации нетронутой) и вмешаться в структуру программно создаваемых элементов.

При инициализации формы БСП помещает описание всех динамических реквизитов в служебную таблицу формы Свойства_ОписаниеДополнительныхРеквизитов. Наша задача — найти строку, соответствующую целевому доп. реквизиту, и программно скрыть элемент формы или удалить его.

Рассмотрим по шагам реализацию этого механизма:

  1. Создаем новое расширение конфигурации с назначением Адаптация.
  2. Заимствуем в расширение форму элемента справочника Партнеры.
  3. Создаем для формы обработчик события ПриСозданииНаСервере с аннотацией &После.
  4. Пишем код, который определяет права текущего пользователя и скрывает нужный реквизит.

Посмотрим на пример программной реализации в модуле заимствованной формы:


&После("ПриСозданииНаСервере")
Процедура Расш1_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
    
    // Проверяем, является ли пользователь полноправным. 
    // Если у него ограниченные права, запускаем алгоритм скрытия.
    Если Не Пользователи.ЭтоПолноправныйПользователь() Тогда
        
        // Находим целевое свойство (дополнительный реквизит) по его имени
        ЗапрещенноеСвойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоРеквизиту("Имя", "_ТестовыйСкрываемыйРеквизит");
        
        Если ЗначениеЗаполнено(ЗапрещенноеСвойство) Тогда
            // Ищем описание свойства в служебной таблице БСП на форме
            ПараметрыПоиска = Новый Структура("Свойство", ЗапрещенноеСвойство);
            НайденныеСтроки = ЭтотОбъект.Свойства_ОписаниеДополнительныхРеквизитов.НайтиСтроки(ПараметрыПоиска);
            
            Если НайденныеСтроки.Количество() > 0 Тогда
                СтрокаОписания = НайденныеСтроки[0];
                ИмяЭлементаНаФорме = СтрокаОписания.ИмяРеквизитаЗначение;
                
                // Проверяем наличие элемента управления на форме и скрываем его
                Если ЭтотОбъект.Элементы.Найти(ИмяЭлементаНаФорме) <> Неопределено Тогда
                    ЭтотОбъект.Элементы[ИмяЭлементаНаФорме].Видимость = Ложь;
                КонецЕсли;
                
                // Удаляем строку из описания, чтобы система не пыталась обращаться к ней при дальнейшей работе
                ЭтотОбъект.Свойства_ОписаниеДополнительныхРеквизитов.Удалить(СтрокаОписания);
            КонецЕсли;
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Вариант Б. Переопределение на уровне общих модулей БСП (Более глобальный и надежный подход)

Метод с адаптацией конкретной формы элемента имеет один недостаток: дополнительный реквизит может выводиться не только в форме элемента справочника Партнеры, но и в формах списков, отчетах или групповых обработках. Чтобы решить задачу централизованно, лучше переопределить поведение механизмов БСП на уровне общего модуля.

В БСП за вывод дополнительных реквизитов отвечает общий модуль УправлениеСвойствамиПереопределяемый. Заимствуем этот модуль в наше расширение и отредактируем процедуру ЗаполнитьНаборыСвойствОбъекта или аналогичную (в зависимости от версии БСП, например, ПриПолученииСвойствОбъекта).

В этом обработчике мы можем напрямую отфильтровать список свойств, передаваемых форме. Если у пользователя нет прав на этот реквизит, мы просто удаляем его из возвращаемого списка. В результате реквизит гарантированно исчезнет со всех экранных форм системы для данного пользователя.

---

Решение задачи №2: Безопасная перезапись 36 000 элементов справочника

Постановка проблемы: Необходимо вызвать метод Записать() для 36 000 элементов справочника. В базе параллельно работают 200 пользователей. Требуется написать обработку, которая минимально осложнит работу пользователей.

Основная сложность этой задачи кроется в конфликтах блокировок. Если мы запустим простой цикл по выборке всех элементов и начнем их последовательно записывать, мы столкнемся с двумя критическими проблемами:

  1. Блокировки таблиц в СУБД: Слишком длинная транзакция или непрерывный поток мелких транзакций вызовет ожидания на блокировках у активных пользователей. Пользователи начнут получать ошибки времени ожидания (Timeouts).
  2. Ошибки изменения версии объекта: Если пользователь открыл карточку элемента и начал редактировать ее, а в этот момент наша обработка перезаписала объект в фоне, то при попытке сохранить карточку пользователь получит критическую ошибку: «Данные были изменены или удалены другим пользователем». Вся работа пользователя за последние несколько минут будет потеряна, что вызовет сильное недовольство.

Разберем правила построения высококлассной обработки для массовой перезаписи.

Правило 1. Использование фонового задания

Ни в коем случае нельзя запускать такую обработку непосредственно в клиентском сеансе. Она должна выполняться на сервере асинхронно через фоновое задание. Это исключит зависание интерфейса администратора и снизит сетевой трафик между клиентом и сервером.

Правило 2. Разделение данных на пакеты (Chunking) с технологическими паузами

Нельзя выполнять всю запись в одной транзакции. Запись нужно производить небольшими порциями (например, по 100–200 элементов). После записи каждой порции необходимо делать искусственную паузу (например, api-таймаут или метод ПриостановитьВыполнение() на 0.5–1 секунду). Это позволит СУБД обработать накопившуюся очередь транзакций от реальных пользователей.

Правило 3. Пессимистическая блокировка для пропуска активных объектов

Перед тем как получить объект для изменения и записи, мы должны попытаться его заблокировать методом Заблокировать(). Если метод вызывает исключение, это означает, что объект прямо сейчас редактируется пользователем. Чтобы не мешать пользователю и не портить его транзакцию, наша обработка должна **пропустить** этот элемент, поместив его ссылку во временную таблицу (или массив) «отложенных» элементов, к которым можно будет вернуться позже (например, в конце цикла или вообще ночью).

Правило 4. Отключение лишней бизнес-логики (`ОбменДанными.Загрузка = Истина`)

Если задача состоит именно в технической перезаписи (например, для пересчета индексов или обновления реквизитов), обязательно устанавливаем свойство ОбменДанными.Загрузка = Истина. Это действие отключает выполнение тяжелых обработчиков ПередЗаписью, ПриЗаписи, проверку заполнения реквизитов и регистрацию изменений на узлах планов обмена. Запись выполнится в разы быстрее, минимизируя время удержания транзакционных блокировок в СУБД.

Пример реализации безопасного алгоритма перезаписи

Проанализируем пример кода серверной процедуры массовой перезаписи:


Процедура ВыполнитьБезопаснуюПерезапись() Экспорт
    
    // Получаем массив всех ссылок, которые нужно обработать.
    // Используем запрос без иерархии и лишних полей для максимального быстродействия.
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура.Ссылка КАК Ссылка
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   НЕ Номенклатура.ЭтоГруппа";
        
    ТаблицаСсылок = Запрос.Выполнить().Выгрузить();
    МассивСсылок = ТаблицаСсылок.ВыгрузитьКолонку("Ссылка");
    
    ОтложенныеСсылки = Новый Массив;
    РазмерПорции = 100; // Оптимальный размер транзакции
    Индекс = 0;
    
    НачатьТранзакцию();
    
    Для Каждого Ссылка Из МассивСсылок Цикл
        
        // Попытка установить пессимистическую блокировку на объект.
        // Если объект открыт пользователем на редактирование - метод вызовет исключение.
        Попытка
            // Блокируем элемент, чтобы другие фоновые процессы его не трогали
            Блокировка = Новый БлокировкаДанных;
            ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
            ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
            ЭлементБлокировки.УстановитьЗначение("Ссылка", Ссылка);
            Блокировка.Заблокировать();
            
            // Получаем объект для записи
            ОбъектСпр = Ссылка.ПолучитьОбъект();
            Если ОбъектСпр <> Неопределено Тогда
                
                // Устанавливаем режим загрузки для отключения подписок и проверок
                ОбъектСпр.ОбменДанными.Загрузка = Истина;
                
                // Дополнительно можно выполнить проверку объектной пессимистической блокировки
                ОбъектСпр.Заблокировать();
                ОбъектСпр.Записать();
                ОбъектСпр.Разблокировать();
                
            КонецЕсли;
            
        Исключение
            // Сюда мы попадаем, если объект занят пользователем.
            // Ничего страшного, просто откладываем его на потом.
            ОтложенныеСсылки.Добавить(Ссылка);
            Продолжить;
        КонецПопытки;
        
        Индекс = Индекс + 1;
        
        // Каждые N элементов фиксируем транзакцию и даем серверу "передохнуть"
        Если Индекс % РазмерПорции = 0 Тогда
            ЗафиксироватьТранзакцию();
            
            // Технологическая пауза на 1 секунду, чтобы разгрузить СУБД 
            // и дать пользователям возможность выполнить свои операции.
            СигнальныйОбъект = Новый Файл("НесуществующийФайл");
            // Простой и безопасный метод задержки выполнения на сервере
            Попытка
                СигнальныйОбъект.Существует(); 
                // Используем платформенный метод ожидания, если доступен, или фоновое задание
                ПриостановитьВыполнение(1); 
            Исключение
            КонецПопытки;
            
            НачатьТранзакцию();
        КонецЕсли;
        
    КонецЦикла;
    
    Если ТранзакцияАктивна() Тогда
        ЗафиксироватьТранзакцию();
    КонецЕсли;
    
    // Записываем информацию об отложенных элементах в лог.
    // К ним можно будет вернуться в ночное время, когда пользователей в базе не будет.
    Если ОтложенныеСсылки.Количество() > 0 Тогда
        ЗаписьЖурналаРегистрации("МассоваяПерезапись.ПропущенныеОбъекты",
            УровеньЖурналаРегистрации.Предупреждение,
            ,
            ,
            "Не удалось записать объектов: " + ОтложенныеСсылки.Количество() + ". Они были заблокированы пользователями.");
    КонецЕсли;
    
КонецПроцедуры

---

Подведение итогов

Проанализировав обе задачи, мы можем сделать важные выводы о качестве проектирования систем в среде «1С:Предприятие»:

← На главную