Как исключить запуск обработки несколькими пользователями одновременно




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

47 Comments

  1. valvit

    Просто и элегантно!

    Reply
  2. Andris_infostart

    А почему бы не сделать проверку на наличие дублирования перед записью нового объекта?

    Reply
  3. swimdog

    (1) Andris_infostart, конечно, можно сделать проверку

    Reply
  4. molodoi1sneg

    Решение интересное, возьму на заметку.

    Reply
  5. ediks

    (1) Насколько я понимаю, задача ставилась не допустить параллельного запуска этой обработки.

    Определить же в процессе работы обработки, что создается дублирующий объект или «так и должно быть» в общем случае затруднительно. Да к тому же требует дополнительных усилий 🙂

    Reply
  6. swimdog

    (6) 7OH, У меня такой вариант не взлетел. Я его пробовал.

    Reply
  7. Yashazz

    Вот кстати чего мне не хватает. так это «второй стороны» к объекту Блокировка. Нечто вроде расширенного Заблокирован(), чтобы возвращало, кем, когда и откуда.

    Reply
  8. ezhikofff

    можно добавить константу типа булево, и проверять ее перед запуском механизма обработки:)

    Reply
  9. 7OH

    Попытка Исключение ?

    Может лучше

    Если Блокировка.Заблокирован() Тогда
    //сообщить иили возврат;
    КонецЕсли;

    ?

    Reply
  10. swimdog

    (9) ezhikofff, работа через константу подразумевает ручную корректировку при аварийном завершении работы программы с работающей обработкой. Мое решение работает полностью автоматически.

    Reply
  11. ezhikofff

    (10) я не настаиваю, просто предложил.

    Reply
  12. Andris_infostart

    (5) ediks, вариант про блокировку для повторного запуски обработки хорош, но вопрос в топике был следующим:

    «В результате, возникает нежелательное дублирование. Как этого можно избежать?» ©

    Поэтому проверка на дубли, по моему мнению, будет уместнее (в контексте поставленного вопроса), даже если запустят в 2 и более раз эту обработку.

    По теме получается, что вопрос задан»как избежать дублирования?», а по факту приведён пример того, как запретить повторный запуск обработки, которая может наделать дублей.

    Reply
  13. gaglo

    (10) Можно добиться некого полуавтомата, если употребить не константу, а запись некого регистра, при запуске обработки создавать, на выходе удалять, записывать дату (и время) начала блокировки и на старте проверять не просто наличие такой записи, но и дату — скажем, считать вчерашнюю запись уже неактуальной. Для обработок, которые достаточно редко запускаются, должно хватить. (8) Кстати, в регистре можно записать и кто, и откуда, и даже зачем.

    Reply
  14. dusha0020

    (13) gaglo,

    если употребить не константу, а запись некого регистра

    А я бы не стал без нужды плодить сущности. Есть простая проблема и автор предложил простое решение. Если проблема сложнее то и решение нужно более сложное.

    (7) Это странно. Попытка -> Исключение это как-то не очень кошерно. Но все равно спасибо за идею.

    Reply
  15. makas
    Иногда возникают ситуации, когда несколько пользователей одновременно используют обработку, которая создает объекты в базе. Например, производят выгрузку в другие базы через непосредственное подключение. Или с помощью обработки создают новые документы. В результате, возникает нежелательное дублирование.

    У моих бухов такое возникает не иногда, а каждый день. Почти 🙂

    Reply
  16. ediks

    (6) Собственно в помощи все описано. Выделил жирным шрифтом ключевое предложение.

    Синтаксис:

    Заблокирован()

    Возвращаемое значение:

    Тип: Булево.

    Истина — элемент заблокирован; Ложь — в противном случае.

    Описание:

    Определяет, заблокирован ли элемент данным объектом.

    Доступность:

    Сервер, толстый клиент, внешнее соединение.

    Примечание:

    Следует учитывать, что этот метод используется для проверки блокировки объекта базы данных конкретным объектом встроенного языка. Он не может быть использован для проверки, заблокирован ли вообще объект базы данных, например, другими пользователями.

    Показать

    Reply
  17. swimdog

    (11) ezhikofff, константа это первый приходящий на ум вариант 🙂 Я ответил, чтобы показать возможные проблемы данного пути

    Reply
  18. swimdog

    (12) Andris_infostart, согласен, что проверка дублей хороший вариант. Надо смотреть каждый случай. В моем случае проверка приведет к дополнительным одиночным запросам.А с помощью запрета на одновременный запуск обработки я полностью исключу вероятность ошибки.

    Reply
  19. Владbckfd

    Уточните, пож-ста, как вызвать обработку «перед открытием»

    Reply
  20. ediks

    (19) Эта блокировка вызывается непосредственно в форме обработки в событии формы «ПередОткрытием».

    Reply
  21. allegrosoft

    И сколько по времени держится блокировка? Если не ошибаюсь, есть ограничение по времени и оно не большое.

    Reply
  22. VZhulanov

    Лучше создайте отдельный справочник Блокировки (Наименование, КтоЗаблокировал, КогдаЗаблокировал)

    В менеджере справочника надо вставить функцию ЗаблокироватьДействие(Название)

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

    Из этой ссылки можно будет получить инфу Кто и Когда заблокировал это действие.

    И еще одну фунцию Разблокировать(Название)

    В функции передается название действия, которое надо заблокировать.

    Такая реализация позволит реализовать механизм блокировок для неограниченного числа действий/обработок,

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

    Reply
  23. swimdog

    (22) VZhulanov, если блокировать надо много чего, то можно и справочник создать. Если обработка одна, то зачем так заморачиваться?

    Reply
  24. the1

    Не проще использовать существующие объекты конфигурации? Например в типовых есть или справочник, или регистр «Сохраненные настройки». Туда, на мой взгляд, и следует записывать «блокировочную» запись. Это помимо очевидного «ЗначениеВФайл». А корежить типовые — увольте

    Reply
  25. swimdog

    (24) the1,

    1. А причем здесь «ЗначениеВФайл»?

    2. В конфигуратор можно вообще не лезть. Блокировать можно любой объект. Предопределенные значения нужны в основном для распределенных конфигураций. А в локальной базе блокировать можно любой понравившийся объект.

    Reply
  26. the1

    (25) автор предлагает создать предопределенный элемент справочника. Научите делать это без конфигуратора?

    Reply
  27. the1

    (25)

    При нажатии на кнопку «Создать документы»

    ОбработкаВыполняется = ЗначениеИзФайла(<ИмяФайла>);
    Если ОбработкаВыполняется Тогда Предупреждение(«Блокировка!»); Возврат КонецЕсли;
    
    
    ОбработкаВыполняется = истина;
    ЗначениеВФайл(<ИмяФайла>, ОбработкаВыполняется);
    
    ….
    
    ОбработкаВыполняется = ложь;
    ЗначениеВФайл(<ИмяФайла>, ОбработкаВыполняется);

    Показать

    Reply
  28. VZhulanov

    В 1С 7.7. я для таких целей просто использовал внешний файл.

    Тоже не надо лезть в конфигурацию или блокировать существующие объекты.

    Чтобы заблокировать создавал и открывал файл.

    Чтобы разблокировать — удалял файл.

    Если файл не удаляется, значит обработка запущена другим юзером.

    Reply
  29. VZhulanov

    (27)

    а если зависнет чего, то в файла так и останется Истина

    Блокировка хороша тем, что снимется автоматом

    Reply
  30. swimdog

    (26) the1, добавил ответ в текст статьи

    Reply
  31. swimdog

    (21) allegrosoft, У меня блокировка держалась больше часа. Дольше не проверял, но явных ограничений в документации 1С не видел.

    Reply
  32. Ёпрст

    Хорошее решение, почти полный аналог с клюшечным вариантом отлова активных пользователей.

    Все идеи с клюшек в снеговик кочуют помаленьку.. реинкарнация в новом виде.

    🙂

    Reply
  33. ediks

    (26) В 8.3.3 Для объектов конфигурации, которые могут содержать предопределенные данные, реализовано свойство ИмяПредопределенныхДанных. Заполнив это свойство у существующего объекта (или у вновь созданного) Вы получите предопределенный элемент, созданный без конфигуратора.

    Reply
  34. V.Nikonov

    Блокировки одновременного запуска нескольких экземпляров обработки возникают не только при загрузке данных. Есть множество регламентных процедур (в частности, проведение по партиям), множественный запуск которых может приводить к взаимным блокировкам…

    В этом смысле создание справочника «Блокировки» хорошая идея. В справочнике создают запись под каждую Монополизируемую обработку и постоянно обновлять отметку о срабатывании «МонопольнойОбработки». Ведь, простая блокировка не является панацеей от зависания… В случае, если сеанс организовавший блокировку зависнет, то блокировка будет оставаться, а основной сеанс обработки не работать!

    Соответственно, есть смысл при старте не только проверять Факт блокировки, но и анализировать кем и когда заблокировано, а в случае обновления отметки о срабатывании МонопольнойОбработки, можно диагностировать критические зависания первичного сеанса… Соответственно можно вычислить зависшие сеансы и призвать Администратора для решения проблем.

    Reply
  35. hazd

    классная штука, спс.

    Reply
  36. hazd

    согласен с Andris_infostart, проверять перед записью и нет проблем.

    Reply
  37. ronhard

    А надо ли разблокировать выбранный элемент при закрытии обработки, или блокировка при закрытии снимется автоматом?

    Reply
  38. swimdog

    (37) ronhard, Блокировка снимется автоматом

    Reply
  39. Stim213

    Имхо, решение не совсем верное.

    Справочник Номенклатура или любой другой — не предназначены для подобной операции. Это неверное архитектурное решение. Если с вашими базами будут работать другие программисты, они не поймут назначение этого костыля и запросто его удалят.

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

    Без острой необходимости лучше не менять типовые объекты!

    Хочется чего-то нового — добавляйте новые объекты в новых подсистемах.

    Находить по коду/наименованию тоже не совсем верно. Код и наименования объектов могут измениться, элементы могут быть так же удалены.

    Ну и главное — такая разработка не решает основной проблемы — исключения дублирования работы.

    Да, второй пользователь увидит, что обработка кем-то уже запущена. Но он так же увидит, что она не запущена, когда откроет её спустя 10секунд после того, как первый её уже закрыл. И запустит выгрузку в сом-базу, да.

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

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

    А лучше вообще использовать Журнал регистрации, записывать в него время выполнения, результат. И при открытии обработки анализировать и выводить эти записи пользователю, чтобы он видел, что, к примеру, выгрузка была проведена Ивановым 10 секунд назад с положительным результатом.

    Reply
  40. swimdog

    (39) Stim213, ключевое слово «одновременно». Журнал регистрации для этого не нужен.

    Reply
  41. kaging

    (13) gaglo, конечно правильнее использовать регистр сведении, к примеру с одним измерением «имя обработки», при запуске делать запись в регистр, а при закрытии обработки вычищать его. Ну а на случай аварийного завершения программы надо бы при первом запуске программы тоже его чистить.

    Reply
  42. bzmax

    Проверка использования обработки без изменений конфигурации.

    (Для последних версий конфигураций основанных на БСП.)

    (за исключением кода самой обработки)

    1) Включаем в «Общих настройках» флаг «Дополнительные реквизиты и сведения»

    2) Заходим в пункт «Дополнительные сведения»

    см.рис. Начало_настроек_блокировки.png

    3) Добавляем для «Пользователя» новые дополнительные сведения с типом значения булево.

    см.рис. Доп_свойство_блокировка.png

    см.рис. Список_доп_свойств_блокировок.png

    Теперь в нашем распоряжении типовой регистр сведений «ДополнительныеСведения»

    и элементы типового Плана вида харакетристик «ДополнительныеРеквизитыИСведения»

    (в данном случае Блокировка обработки №1 и Блокировка обработки №2)

    В регистр сведений «ДополнительныеСведения» мы независимо можем записывать записи по полям:

    Объект — Пользователь

    Свойство — Элемент плана вида характеристик.

    Значение — Булево


    см.рис. Рег_ДополнительныеСведения.png

    4) Осталось только нужную нам обработку «научить» (дописать код)

    работать с типовым регистром сведений «ДополнительныеСведения»

    На предмет анализа и формирование соответствующих записей.

    И программно в коде обработки назначить ей соответствующий элемент плана видов характеристик.

    (Код писать не буду. и так все «разжевал» :))

    ИТОГ:

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

    И можем не только мониторить на предмет блокировки, но и смотреть кто и какую обработку использует в текущий момент.

    Reply
  43. -fox-

    А почему нельзя воспользоваться механизмом управляемых блокировок?

    Reply
  44. swimdog

    (43) -fox-, блокировок чего? объектов? Так они могут блокироваться как угодно, в том числе с помощью управляемых блокировок. При запуске двух загрузок или двух потоков создания документов может не происходить пересечений на конкретном объекте.

    Reply
  45. swimdog

    Для всех, кого интересует получение блокирующего пользователя, я дополнил статью. Все делается одной строкой кода, без создания регистров сведений 🙂

    Reply
  46. swimdog

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

    Пишите статьи о своих вариантах, приводите ссылки и будьте счастливы 🙂

    Reply
  47. -fox-

    (44)

    Создается спр. (Если будет несколько обработок) или константа и их блокируешь.

    На сколько я понял, у тебя тоже самое, но использован спр. «Номенклатура» и специально для этого созданный элемент «ЭлементДляБлокировкиОбработки».

    ИМХО лучше не засорять спр. заведомо не используемой информацией.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *