Фоновые задания в файловом режиме 1С 8.x средствами 1С без дополнительных компонент




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

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

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

<?php // Полная загрузка сервисных книжек, создан 2025-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='\

16 Comments

  1. Evg-Lylyk

    На эту же тему http://infostart.ru/public/16486/

    Reply
  2. kosilov

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

    Reply
  3. kosilov

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

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

    Суть вопроса в следующем:

    1. Как сгенерировать сообщение (событие, нотификацию) в 1С, которое передасться через ком соединение вызывающем приложению. Т.е. Чтобы его можно было поймать как в данном стандартном примере:

    msword = New COMObject(«Word.Application»);
    AddHandler msword.DocumentChange, WhenDocumentChange
    Procedure WhenDocumentChange()
    Message(«Document is changed»);
    EndProcedure 

    2. Как другим сопсобом сгенерить сообщение (например Notify — уведомить), чтобы как-то его отловить в другом сообщении.

    Reply
  4. kosilov

    (3) И вот еще как можно передать данные при данном варианте от отработавшего задания:

    1. В глобальном модуле определяем переменную:

    Перем РезультатыФоновойРаботы Экспорт;

    А потом в конце:

    РезультатыФоновойРаботы = новый Соответствие;

    Плюс функция:

    Функция ВернутьРезультатФоновогоЗадания(Ключ) Экспорт
    Рез = РезультатыФоновойРаботы[Ключ];
    РезультатыФоновойРаботы.Удалить(Ключ);
    возврат Рез;
    конецфункции

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

    После выполнения функции перед конецфункции пишем:

    РезультатыФоновойРаботы[Ключ] = <Наш результат работы в виде чего угодно>

    И вуаля:

    Запускаем фоновое задание. Отслеживаем его завершение через стандартный механизм: ФоновыеЗаданияПолучитьФоновыеЗадания или ФоновыеЗадания.НайтиПоУникальномуИдентификатору.

    А затем обращаемся к наему глобальному ком объекту.

    Рез = V8Com.ВернутьРезультатФоновогоЗадания(Ключ);

    В данном случае это будет синхронный вызов и мы получим результат.

    Мне кажется, что это элегантно.

    Reply
  5. Душелов

    Кстати, обычные фоновые задания довольно часто зависают. Решения проблемы так и не нашли — только перезапуск сервиса.

    Reply
  6. kosilov

    (5) Пока не сталкивался. А как виснут? В смысле виснет среда?

    А что 1С по этому поводу говорит?

    Reply
  7. Душелов

    (6) В смысле то, что фоновое задание висит бесконечно со статусом «выполняется» и никак его не удалить, ни через консоль заданий, ни через консоль серверов, что в 8.1, что в 8.2 встречается. Поэтому и пришлось писать свою компоненту для многопоточности.

    Reply
  8. sas2006

    У у меня появился вопрос, получается будет запускаться еще одна копия 1с и следовательно должна быть еще одна свободная лицензия?

    Reply
  9. Kazan

    не понятно какой конкретно оператор запускает механизм выполнения заданий от имени сервера — ВыполнитьОбработкуЗаданий или оновыеЗадания.Выполнить ???

    Reply
  10. krevedgo

    Спасибо автору за тему!

    Очень нужная вещь, но…

    Помогите плиз начинающему

    Что куда вписывать? Платформа 8.2

    Открыл конфигуратор, открыл модуль управляемого приложения

    Сделал так:

    // Конец СтандартныеПодсистемы
    Перем V8Com Экспорт;
    
    Процедура ПередНачаломРаботыСистемы(Отказ)
    
    // СтандартныеПодсистемы
    СтандартныеПодсистемыКлиент.ДействияПередНачаломРаботыСистемы(Отказ);
    // Конец СтандартныеПодсистемы
    
    //выполняем фоновые задания
    если ИмяПользователя() = «Server» тогда
    ПодключитьОбработчикОжидания(«ОбработчикОжидания»,1);
    КонецЕсли;
    //конец выполняем фоновые задания
    
    
    КонецПроцедуры
    
    Процедура ОбработчикОжидания() экспорт
    ВыполнитьОбработкуЗаданий();
    КонецПроцедуры
    
    Процедура ПриНачалеРаботыСистемы()
    
    НеобходимаНачальнаяНастройкаПрограммы = ОбновлениеИнформационнойБазыУТ.НеобходимаНачальнаяНастройкаПрограммы();
    
    // СтандартныеПодсистемы
    СтандартныеПодсистемыКлиент.ДействияПриНачалеРаботыСистемы(Истина);
    // Конец СтандартныеПодсистемы
    
    //Диалог выбора варианта настройки системы
    Если НеобходимаНачальнаяНастройкаПрограммы Тогда
    ОткрытьФормуМодально(«ОбщаяФорма.ВыборВариантаНастройкиПрограммы»);
    КонецЕсли;
    //Конец Диалог выбора варианта настройки системы
    
    //ОткрытиеФормПриНачалеРаботыСистемы
    ОткрытиеФормПриНачалеРаботыСистемыКлиент.ПриНачалеРаботыСистемы();
    //Конец ОткрытиеФормПриНачалеРаботыСистемы
    
    V8Com = Новый COMОбъект(«V81.Application»);
    
    Попытка
    Открытие = V8Com.Connect(СтрокаСоединенияИнформационнойБазы()+ «Usr=»»Server»»;Pwd=»»123″»;»);
    Исключение
    Предупреждение(«Ошибка открытия фоновой копии!!!»);
    Отказ = истина;
    Возврат;
    КонецПопытки;
    
    КонецПроцедуры
    
    

    Показать

    Запускаю отладку и получаю ошибку

    {МодульУправляемогоПриложения(97)}: Ошибка при вызове конструктора (COMОбъект)

    V8Com = Новый COMОбъект(«V81.Application»);

    по причине:

    Недопустимая строка с указанием класса

    Попробовал это — не помогло.

    Подскажите, что я делаю не так?

    Смущает «СтрокаСоединенияИнформационнойБазы» — может тут должно быть что-то более конкретное?

    Требуется выполнять всего одно задание «обмен с сайтом», как-то в УТ10 это работало без дополнительной химии, а вот в УТ11 уже не получается.

    Reply
  11. nikbrik

    (10)

    Что куда вписывать? Платформа 8.2
    V8Com = Новый COMОбъект(«V81.Application»);

    Ответ очевиден:

    V8Com = Новый COMОбъект(«V82.Application»);

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

    Reply
  12. kosilov

    Уже не припомню.

    Но как крайний вариант, сделайте так:

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

    2. На клиентком процессе сделайе регулярный опрос ( каждый 30 сек например) этого регистра (или эл-та который вы использовали).

    3. Если фоновый поток требует завершения пользователей,то в БД выставляется флаг (в этот ваш эл-т или регистр)

    4. Клиентский поток смотрит это значение и если там стоит флаг завершения, то клиентский поток сам завершается по собственному т.с. желанию.

    Если, я конечно правильно понял вашу задачу.

    Reply
  13. nikbrik

    (12)Спасибо за ответ

    Дело в том, что фоновый процесс нужен был, чтобы в файловом варианте не мешать работе пользователя постоянными проверками фоновых заданий. А то что вы предлагаете — значит опять проверять каждые 30 секунд на наличие флага) опять будут подвисания, тогда просто нет смысла в фоновом процессе… Или может я ошибаюсь, и есть способ проверять на наличие флага как-то незаметно?

    P.S. Извините что не могу более ясно выражать мысли)) после работы голова гудит…

    Reply
  14. kosilov

    Я пробовал играться с нотификациями, но что-то не очень у меня это получилось.

    У вас таймаут большой может быть (будет не критично) 30 сек или минута.

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

    Reply
  15. sanfoto

    (0) отказался я от «COM-Объектов» и от ВыполнитьОбработкуЗаданий()

    т.к. нужно было реализовать запуск независящий запуск от действий пользователя — пришлось использовать обработчики в ПриНачалеРаботыСистемы(), и порой на компе на котором это использовалось… плодились несколько COM-Объектов…. все работало и пользователю практически не мешало, но память съедалась(((

    ***************РЕШЕНИЕ — использовать ИСКЛЮЧИТЕЛЬНО «ФоновыеЗадания» а не их инициализацию через «РегламентныеЗадания» ****************

    Процедура ПриНачалеРаботыСистемы()
    //………….Некий код.
    //………….Некий код
    ПодключитьОбработчикОжидания(«ФоновоеЗаданиеТранзакцииКафеЧерезВебСервисВ_УПП_ТекущиеПрод­ажи», 30);
    //………….Некий код
    //………….Некий код
    КонецПроцедуры
    
    Процедура ФоновоеЗаданиеТранзакцииКафеЧерезВебСервисВ_УПП_ТекущиеПрода­жи() Экспорт
    ПараметрыОтбора = Новый Структура(«Наименование, Статус», «ФоновоеЗаданиеТранзакцииКафеЧерезВебСервисВ_УПП_ТекущиеПрод­ажи», СостояниеФоновогоЗадания.Активно);
    СписокФоновыхЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(ПараметрыОтбора);
    
    Если СписокФоновыхЗаданий.Количество()>0 Тогда
    Если СписокФоновыхЗаданий[0].Состояние = СостояниеФоновогоЗадания.Активно Тогда
    Возврат;
    КонецЕсли;
    КонецЕсли;
    //………….Некий код
    //………….Некий код
    КонецПроцедуры

    Показать

    Reply
  16. freeek

    Здравствуйте! Подскажите, пожалуйста. Сталкивался кто-нибудь с такой проблемой, как «потеря пользователя» после выполнения фонового задания. Проверяю так:

    МассивСоединений = ПолучитьСоединенияИнформационнойБазы();

    Для Каждого ТекСоединение Из МассивСоединений Цикл

    значение «ТекСоединение.Пользователь» именно текущего пользователя пустое.

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

    Reply

Leave a Comment

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