Отказ от работы с временными файлами при работе с двоичными данными или Потоки как простая замена ADODB.Stream и временным файлам




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

31 Comments

  1. VmvLer

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

    Я хочу спросить хрен редки не слаще ли, т.е. тесты на общую производительность есть?

    Reply
  2. vardeg

    Полноценные тесты не стояла задача делать.

    Вот умозаключения по этому поводу не подкрепленные практическими тестами:

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

    А вот при работе с ADODB.Stream — выигрыш очевиден. У нас нет необходимости при прочих равных (я имею ввиду и так наличие временных структур в памяти) иметь еще один COM объект и обращаться к его методам. Все делается штатными средствами.

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

    Reply
  3. vitkhv

    Исправьте плиз:

    Получение данных из SQL базы с varbinaty varbinary типом данных.

    Reply
  4. PerlAmutor

    Исправьте плиз:

    Итак, код функции получения фала файла без использования ADODB.Stream и временных файлов.

    работаем с наобором набором байтов

    десереализация десериализация

    мощного инустремнты инструмента работы
    Reply
  5. PerlAmutor
    Upd: Исправил орфографические ошибки. Спасибо всем, кто обратил внимание.

    Тогда вот еще =)

    десерализации десериализации

    десереализации десериализации

    десереализацией десериализацией

    интерисует интересует

    десятиричном десятеричном

    инструмента работы с двоичными данным данными

    P.S.: я не Grammar Nazi =)

    Reply
  6. RailMen

    Отличная статья, задрали временные файлы.

    Reply
  7. Silenser

    Спасибо за идею.

    Reply
  8. Tangram

    Интересно, буду использовать. Ну и в заголовке поправь версию платформы пжлста (3.8.9).

    Reply
  9. ivanov660

    «+» за акцент внимания на новых фитчах от 1С.

    Reply
  10. kiruha

    Иногда файлы xml имеют размеры > 1 Гб

    Этот Поток будет скидывать данные во временный файл в случае нехватки памяти

    или рухнет 1С ?

    Reply
  11. vardeg

    (10) Если честно — Вы меня немного озадачили и заставили задуматься.

    Хотелось бы посмотреть на работу с такими xml файлами через механизм временных файлов, ну или вообще на работу с ними.

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

    Однако мысли на этот счет следующие — мы имеем набор инструментов, и сами определяем в какой ситуации каким набором инструментов.

    Поэтому в качестве ответа:

    В отличии от коснтруктора ДвоичныхДанных на основе файла мы с потоками можем работать порционно — делать последовательные записи и чтения Потока, для этого существует полный набор методов объекта ЧтениеДанных, включая приятный метод ПрочитатьСтроку, который в потоке ДД определяет по указанному нами разделителю строку. Аналогично в обратную сторону.Если мы знаем, что XML будет таких размеров, а у нас возникает необходимость получить его из двоичных данных, то я бы создал механизм поточного чтения и обработки порций информации. Тем более что десериализация XML так же поддерживает механизм последовательного чтения — у объекта ЧтениеXML есть соответствующие методы.

    Теоретически используя подход чтения и обработки таких файлов «по кускам», а правильнее поточного (масло масленое конечно, но все же) мы можем обойти ограничения по памяти, которое вывалит нам ошибку при попытке работы в объектной модели.

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

    Reply
  12. PerlAmutor

    Жалко что 1С умеет работать со своими коллекциями (Массив, Структура, ТаблицаЗначений и т.д.) только на уровне оперативной памяти.

    Вы можете сериализовать ТаблицуЗначений в файл и потом загрузить обратно. А представьте что будет, если сериализовать ТаблицуЗначений в файл на сервере где >100Гб ОЗУ, а потом попробовать загрузить этот файл уже на клиенте (толстый клиент, обычное приложение) ?

    Такие операции как построение индекса, сортировка или свертывание ТЗ хотелось бы иметь возможность проводить на диске.

    Чтобы можно было делать отборы по ТЗ находящейся на диске, подгружать порционно в окно пользователя (pagination), а не держать сотни мегабайт в ОЗУ, где все данные и не нужны.

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

    Reply
  13. CyberCerber

    По поводу оптимизации могу сказать, что операцию, описанную во второй части статьи, пришлось проделать пару месяцев назад в одном проекте, т.к. объект ADODB.Stream время от времени отваливался на сервере. Код, в принципе, почти полностью совпадает с вашим.

    Только в итоге остановился на варианте, что сначала в попытке выполняется по ADODB.Stream, а только в исключении встроенный ЗакрытьИПолучитьДвоичныеДанные, т.к. на данных объемом в несколько мегабайт тормоза у встроенного заметные. Наверное, дело в побайтовой записи в цикле.

    Reply
  14. Altair777

    (5) Ну, и это поправьте

    Точнее нас интерисует как происходит

    А еще не хватает несколько десятков запятых. Попробуйте воспользоваться MS Word

    Reply
  15. vardeg

    (13) Собственно подтверждение домыслов из моего ответа (2) . Как уже говорил — вполне логично разработчикам платформы предусмотреть метод формирования буфера двоичных данных по массиву. Если это будет реализовано, то это даст ощутимый прирост скорости.

    Reply
  16. kiruha

    (11)

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

    В более предметной — читаются или пишутся файлы обмена со сторонними системами xml — очень большого размера .

    Обсуждений много и на инфостарте и на мисте , например http://www.forum.mista.ru/topic.php?id=489798 https://infostart.ru/public/15464/

    Reply
  17. vardeg

    (16) я в принципе Вас понял. Я смоделирую ситуацию и попробую создать инструментарий для таких загрузок из сторонних систем.

    Reply
  18. Arxxximed

    По первому пункту вроде добавлен

    ДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтроки(ПараметрСтрока,КодировкаТекста.ANSI);
    ПараметрСтрока = ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанные,КодировкаТекста.ANSI);
    
    

    Как то все намного изящнее… Но это в 8.3.10 я вижу. Не знаю как в 8.3.9

    Reply
  19. vardeg

    (18) да, в 8.3.10 функционал еще более расширен. так как с самой платформой еще не работал — упустил из виду эти функцию. спасибо — изучим.

    Reply
  20. palsergeich

    Вместо

    ЗаписьXML = Новый ЗаписьXML;
    ЗаписьXML.ОткрытьФайл(ИмяВременногоФайла);
    ЗаписьXML.ЗаписатьОбъявлениеXML();
    ЗаписатьXML(ЗаписьXML, Источник, НазначениеТипаXML.Явное);
    ЗаписьXML.Закрыть(); 

    и обратного есть же шикарные методы — XMLСтрока и XMLЗначение …

    Функция ПолучитьФайлАДО_Stream(Value)
    Файл = Неопределено;
    Stream = Новый COMОбъект(«ADODB.Stream»);
    Stream.Type = 1;
    Stream.Open();
    Stream.Write(Value);
    ИмяФайла = ПолучитьИмяВременногоФайла();
    Stream.SaveToFile(ИмяФайла);
    Stream.Close();
    Файл = Новый ДвоичныеДанные(ИмяФайла);
    УдалитьФайлы(ИмяФайла);
    Возврат Файл;
    КонецФункции

    Показать

    Нет никаких гарантий, что завтра сервер переедет на тот же linux и придется все участки с COM лопатить. Что мешает сделать сразу универсально с помошью хранимых процедур….

    Reply
  21. binex

    Спасибо за разбор! Сегодня уже применил. Недавно ковырял в этом направлении, да забросил.

    Reply
  22. vardeg

    (20)

    1 — речь в статье не возможностях сериализатора. Уверен там есть изящные и удобные методы работы. Речь идет о преобразовании данных при работе с ДД.

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

    Reply
  23. LexSeIch

    Спасибо за интересную статью. Добавил себе в копилку.

    Reply
  24. vlengin

    За информацию по новым объектам — спасибо!

    Ну а от ADODB.Stream (и «промежуточного» файла) легко избавиться другим способом: конвертировать Binary-Base64 (Base64-Binary) средствами MS SQL, ну а на стороне 1С использовать Base64Значение(Base64Строка), т.е. «обмен данными» с MS SQL через строку Base64

    Reply
  25. Silenser

    Попробовал прямой метод считывания побайтово данных из потока и запихивания в COMSafeArray. С производительностью, как и ожидалось, беда. На файлах 2-10 Кб — все проходит незаметно, а вот на файлах в несколько мегабайт все печально.

    Reply
  26. Silenser

    Какой-то глюк, вот вариант с временными файлами

    Reply
  27. Crush

    Спасибо!

    Хорошее применение потокам.

    Вот бы еще gzip через них распаковывать:)

    Reply
  28. Crush

    Побайтовая запись только подвела. Неадекватно долго перебирается массив. Пробовал и с массивом и с COMsafeArray. Запись попробовал и в буфер и в ЗаписьДанных.

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

    Reply
  29. Crush

    Еее!!! Справился с тормознутым побитовыми операциями.

    Для решения моей задачи нужно было ComSafeArray конвертить в ДвоичныеДанные. И вот чё получилось:

    Функция ПолучитьДвоичныеДанныеИзCOMSafeArray(COMSafeArray)
    дом    = Новый COMОбъект(«Msxml2.DOMDocument»);
    элДом   = дом.createElement(«tmp»);
    элДом.datatype = «bin.base64»;
    элДом.nodeTypedvalue = COMSafeArray;
    дд  = ПолучитьДвоичныеДанныеИзBase64Строки(элДом.Text);
    Возврат дд;
    КонецФункции

    Кстати, может кто-то знает, WinHttp.WinHttpRequest.5.1 в ResponseBody отдаёт массив, а сам WinHttp умеет в base64 сразу конвертировать тело ответа? Собственно под эту задачу и искал решение

    Reply
  30. PLAstic

    В анонсе, вероятно, подразумевалась платформа 8.3.9.

    Reply
  31. endym

    Автор, исправь шапку «В платформе начиная с версии 3.8.9» 😉

    Reply

Leave a Comment

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