Использование Web-сервисов для синхронизации баз данных в режиме online 1С8.2 (8.1) .




Принцип обмена данными из 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='\

9 Comments

  1. Il

    тоже интересная альтернатива.

    Reply
  2. DitriX

    (0) вот недавно сделал аналогичную тему, только синхронизировал 3 интренет магазина с базой.

    Делается это все немного не так 🙂

    Во первых, у вас указано в теме:

    Что нам потребуется для того чтобы задача возникла и могла быть решена.

    1. Сервер 1с, с установленной web компонентой;

    2. веб-сервер (например IIS 6.0);

    3. Две или более конфигурации учетных систем.

    Но таким образом — вы не решите проблему нескольких баз, так как GUID у вас только один указывается, тот что в реквизите.

    Т.е. в вашем случае — количество реквизитов должно быть равно 3 * (Количество баз — 1).

    Кроме этого, к чему вы используете вот это:

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

    Когда это делается Подпиской на событие?

    И почему в две функции?

    Учтите, если вы в момент записи делаете такое, то в случае, когда вторая база не доступна, то соап будет несколько секунд висеть.

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

    Вобщем идея у вас правильная, но подход не тот 🙂

    Если кому то будет интересно, то могу написать статью, о том, как это делать правильно 🙂

    Reply
  3. allert73

    (2) DitriX,

    На во первых. дополнительных реквизитов GUID достаточно одного набора, при условии что одна из баз должна считаться первичной, а в остальные базы реквизит GUID будет записываться как уникальный идентификатор первичной базы. В этом случае синхронизация вызываемая не из первичной базы, должна быть обращена в первую очередь к базе первичной с параметром GUID=»», и в случае создания нового элемента синхронизация должна возвращать GUID этого элемента. у мена ответ вгялядит примерно так. ‘Возврат «ОК «+СокрЛП(ТекДокОбъект.Ссылка.УникальныйИдентификатор());’. Вызов синхронизаций во вторичные базы будет осуществляться с уже заполненным реквизитом GUID.

    На во вторых. в случае записи элемента справочника действительно можно было бы обойтись одним ПриЗаписи().

    в ПередЗаписью() возникает необходимость для случая документа. дело в том что документ может записываться как проведенным так и не проведенным. Бывает так что во время проведения документа он немного меняется в частности признак проведен у него становится Истиной. Я использую перед записью так.

    Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

    РежимЗаписиДок = РежимЗаписи;

    Если не ДокументОснование.Склад.Пустая() тогда

    Склад = ДокументОснование.Склад;

    КонецЕсли;

    #Если Клиент тогда

    Если передавать тогда

    Синхронизирован = Ложь;

    КонецЕсли;

    #КонецЕсли

    КонецПроцедуры

    Затем в процедурах ОбработкаПроведения() и ПриЗаписи() добавляю проверку.

    Процедура ПриЗаписи(Отказ)

    Если РежимЗаписиДок<>РежимЗаписиДокумента.Проведение тогда

    отказ = не Синхронизировать();

    КонецЕсли;

    КонецПроцедуры

    Действительно из примера со справочником этот можно было бы исключить.

    Reply
  4. allert73

    (2)а вообще спасибо за конструктивную критику 🙂

    Reply
  5. DitriX

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

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

    Из вашей схемы я не совсем уяснил, как вы передаете значения остальных реквизитов?

    Так же как и единицы измерения?

    Так же вопрос — почему вы выбрали GUID? Почему не по коду какому-то например? Видь их сравнивать намного сложнее, если что то пошло не так 🙂

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

    Если колПопыток=100 тогда

    Я так понял вы пытаетесь 100 раз достучаться до сервера, как это влияет на производительность? В случае если второй сервер не доступен?

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

    Вы правы, не хватает опыта, можете скачать архив с моей публикации http://infostart.ru/public/128699/, там по полочкам разложено — как создать веб-сервис и какие камни вас ожидают.

    Reply
  6. allert73

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

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

    но вот кстати процедура передачи Единицы измерения в примере есть.

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

    Почему ГУИД а не код, дело в том, что по определенным причинам, код и номер(для объекта документа) являются величинами переменными, в то время как УникальныйИдентифкатор() величина постоянная.

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

    Процедура ПриЗаписи(Отказ)

    Если РежимЗаписиДок<>РежимЗаписиДокумента.Проведение тогда

    отказ = не Синхронизировать();

    КонецЕсли;

    КонецПроцедуры

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

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

    Насчет Сотни Раз. Фиговое решение согласен. Однажды на практике как то вышло так что запрос к веб-серверу обрабатывался с третьей попытки. Вообще давно пора от этого куска избавиться.

    Reply
  7. allert73

    (5) DitriX, Извиняюсь, забыл указать получателя в комментарии. текст ответа в комментарии 6.

    Reply
  8. almas

    Спасибо за публикацию.

    Reply
  9. lutic19

    Все очень даже хорошо, единственное думаю что:

    1) эта строчка явно лишняя:

    //заполним реквизиты

    Если ТоварСсылка.Пустая() тогда

    ТоварСсылка = Справочники.Товары.НайтиПоКоду(СтруктураРеквизитов.Код);

    КонецЕсли;

    зачем искать по коду, когда синхронизируешь по GUIDтовара? Если будут номенклатуры одинаковые по кодам — это не есть good.

    2) в цикле подключаться к веб-сервису, который может быть не доступен, тоже не есть good.

    Reply

Leave a Comment

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