Что было за удалённым объектом и кто его удалил: поиск по GUID, навигационной и битой ссылке «Объект не найден» по всей базе и журналу регистрации
База данных - Журнал регистрации
Файлы
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний
Оформите подписку на компанию для решения рабочих задач
Оформить подписку и скачать решение со скидкойВы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
В ошибке проведения написано «Банк = <Объект не найден> (20:91aaaa28…)». В выгрузке обмена — голый GUID. В трассе SQL — 0x91AAAA28…. Что это был за объект, куда он делся и кто его удалил? Платформа умеет переходить только по навигационной ссылке, где тип уже записан: по голому GUID объект так не открыть, а про удалённый она не скажет ничего.
Обработка «Поиск по GUID» отвечает на все три вопроса. В любом вставленном тексте она сама находит идентификаторы всех видов и по каждому говорит, что это за объект и есть ли он в базе. Если объекта уже нет, она находит его в журнале регистрации: как он назывался, что это был за справочник или документ, кто и когда его удалил. Тип знать не нужно. Форма управляемая, работает только на методах платформы, поэтому открывается и в конфигурации без БСП. В базу обработка ничего не пишет, нужны права администратора.
Как это выглядит
Сверху — текст, откуда угодно и сколько угодно строк. Под ним — строка на каждый найденный идентификатор: что это за объект и в каком он состоянии. Под таблицей — история выбранного объекта из журнала регистрации.
Битая ссылка из ошибки и 0x… из трассы SQL — один и тот же банк: удалён, видно, кто и когда, и как он назывался до удаления.Строку из трассы SQL или технологического журнала можно вставить как есть:
WHERE T1._IDRRef = 0x91AAAA284181B39211F1BB82A3872A60
Обработка сама узнаёт в 0x… ссылку на объект, переводит её в обычный GUID и находит объект — на кадре выше это «Банк Восток (закрыт)». В строке «Выбранный» под таблицей она подписывает, в каком виде идентификатор был в тексте: «двоичный из SQL».
Двойной щелчок открывает живой объект. Его навигационная ссылка — в строке «Выбранный», её можно выделить и скопировать. Для документа обработка пишет, проведён он или нет, для любого объекта — помечен ли он на удаление.

Как это сделано
Идентификаторы в тексте
Один и тот же GUID встречается в двух видах. В выгрузках и XML — как есть, с дефисами. В навигационной ссылке, в битой ссылке «<Объект не найден>» и в поле _IDRRef трассы SQL — 32 знака подряд, и части в них переставлены. Обработка ищет оба вида регулярными выражениями:
// Соседний шестнадцатеричный знак означает, что это кусок длинного числа, а не идентификатор
СДефисами = "(?<![0-9a-f])[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?![0-9a-f])";
Подряд = "(?<![0-9a-f])(?:0x)?[0-9a-f]{32}(?![0-9a-f])";
Для Каждого Результат Из СтрНайтиВсеПоРегулярномуВыражению(Текст, СДефисами, Истина) Цикл
ДобавитьВхождение(Вхождения, Результат.НачальнаяПозиция, НРег(Результат.Значение), "GUID", Результат.Значение);
КонецЦикла;
СтрНайтиВсеПоРегулярномуВыражению работает только на сервере, в веб-клиенте её нет, поэтому обработка разбирает текст на сервере. Просмотр назад и вперёд в шаблоне отсекает куски длинных чисел: 40 шестнадцатеричных знаков подряд обработка за идентификатор не принимает. Пустой GUID из одних нулей она тоже пропускает — это пустая ссылка, а не объект.
У 32 знаков подряд нам нужно вернуть частям обычный порядок. Платформа хранит их задом наперёд — четвёртая, пятая, третья, вторая, первая:
// 91aaaa284181b39211f1bb785de17b8c U94; 5de17b8c-bb78-11f1-91aa-aa284181b392
Функция GUIDИзХранимого(Хранимый)
Возврат Сред(Хранимый, 25, 8) + "-" + Сред(Хранимый, 21, 4) + "-" + Сред(Хранимый, 17, 4)
+ "-" + Лев(Хранимый, 4) + "-" + Сред(Хранимый, 5, 12);
КонецФункции
Порядок одинаков у справочников и у документов — тот же, что в ссылке от ПолучитьНавигационнуюСсылку. Одинаковые GUID обработка сводит в одну строку, даже если в тексте они записаны по-разному.
Объект в базе, когда тип неизвестен
ПолучитьСсылку даёт ссылку по GUID, но только у менеджера конкретного типа. Обработка берёт ссылку каждого ссылочного типа конфигурации: справочников, документов, планов видов характеристик, счетов, видов расчёта, обмена, бизнес-процессов и задач — в УНФ это больше тысячи таблиц. Проверить их можно одним запросом с ОБЪЕДИНИТЬ ВСЕ, и так делают готовые функции на форумах. Но на больших конфигурациях такой запрос падает: в комментариях к ним жалуются на «query too complex» и лимит колонок PostgreSQL. Поэтому я собрал объединение кусками по 100 таблиц, и в каждой таблице обработка ищет сразу все GUID из текста:
Для Номер = 0 По Таблицы.ВГраница() Цикл
Ссылки = Новый Массив;
Для Каждого Уид Из Уиды Цикл
Ссылки.Добавить(Таблицы[Номер].Менеджер.ПолучитьСсылку(Уид));
КонецЦикла;
ИмяПараметра = "Ссылки" + XMLСтрока(Номер);
Запрос.УстановитьПараметр(ИмяПараметра, Ссылки);
Проведен = ?(Таблицы[Номер].Проводится, "Т.Проведен", "ЛОЖЬ");
Части.Добавить("ВЫБРАТЬ Т.Ссылка КАК Ссылка, ПРЕДСТАВЛЕНИЕ(Т.Ссылка) КАК Представление,
| Т.ПометкаУдаления КАК ПометкаУдаления, " + Проведен + " КАК Проведен
|ИЗ " + Таблицы[Номер].ОбъектМД.ПолноеИмя() + " КАК Т
|ГДЕ Т.Ссылка В (&" + ИмяПараметра + ")");
КонецЦикла;
Запрос.Текст = СтрСоединить(Части, Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС);
Имя параметра обработка строит через XMLСтрока, а не сложением строки с числом. Сложение строки с числом ставит пробел между разрядами: "Ссылки" + 1000 даёт «Ссылки1 000», и такой запрос не разбирается.
Таблицу, которую меняет сломанное расширение, платформа иногда не читает: запрос бросает «Работа с таблицей невозможна», и вместе с ней падает весь кусок. Тогда обработка проверяет этот кусок по одной таблице. Непрочитанную она называет над таблицей результатов, ошибку пишет в журнал регистрации, а остальные таблицы всё равно проверяет — так на втором кадре.
Кто удалил
Если объекта в базе нет, о нём знает журнал регистрации. Отбор ВыгрузитьЖурналРегистрации по полю Данные принимает не только одну ссылку, но и массив, поэтому тип и здесь знать не нужно: по ссылке каждого типа на каждый GUID — и один вызов.
Тут нас ждёт подвох со скоростью. Без отбора по событию журнал проходит каждую ссылку из массива отдельно, а ссылок здесь — тысяча на каждый GUID. С отбором по событию «Удаление» он берёт только удаления, которых мало, и находит те же ссылки сразу. Поэтому я разбил поиск на два шага. Сначала обработка ищет среди удалений, по всем типам сразу:
Удаления = Новый ТаблицаЗначений;
ВыгрузитьЖурналРегистрации(Удаления, Новый Структура("Данные,Событие", ВсеТипы, "_$Data$_.Delete"), "Данные");
Для Каждого Удаление Из Удаления Цикл
НайденныеСсылки.Вставить(Строка(Удаление.Данные.УникальныйИдентификатор()), Удаление.Данные);
КонецЦикла;
В поле Данные записи об удалении лежит ссылка того самого типа — битая, «<Объект не найден>», но с верным типом. Значит, после первого шага тип удалённого объекта известен. Вторым шагом обработка берёт всю историю — создание, изменения, проведение, удаление — уже по одной ссылке на GUID. Туда же попадают и живые объекты, найденные в базе.
Как назывался удалённый объект, обработка берёт из ПредставлениеДанных. Журнал хранит представление на момент события, поэтому видно и прежнее имя, и то, под которым объект удалили.
Время в журнале
Журнал пишет время сервера, а не пользователя. Если сервер живёт по UTC, а пользователь — по Москве, удаление минутной давности стоит в журнале на три часа раньше. Отбор журнала по дате тоже ждёт время сервера. Для показа обработка пересчитывает дату в пояс сеанса:
// Журнал пишет время сервера, пользователю — время его сеанса
Сдвиг = Окр((ТекущаяДатаСеанса() - ТекущаяДата()) / 900) * 900;
Две даты обработка берёт в разные мгновения, и они расходятся на доли секунды, поэтому сдвиг она округляет до четверти часа — часовые пояса кратны ей.
На чём проверено
Я проверял на платформе 1С:Предприятие 8.5 (8.5.1.1522) и «Управлении нашей фирмой» 3.0 (3.0.14.143). В веб-клиенте, на серверной базе. Без БСП — на своей конфигурации, на той же платформе.
На 8.3 я не проверял. Может, откроется, а может, и нет.
Состав поставки
PoiskPoGUID.epf— внешняя обработка «Поиск по GUID», код открыт.
Проверено на следующих конфигурациях и релизах:
- Управление нашей фирмой, редакция 3.0, релизы 3.0.14.143
Вступайте в нашу телеграмм-группу Инфостарт