Расчет контрольной суммы строки по алгоритму CRC32 методами встроенного языка платформы "1С:Предприятие" (версии ранее 8.3)




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

20 Comments

  1. ildarovich

    Нет ли ошибки в названии? Дело в том, что в 8.3 есть встроенная функция для решения этой задачи.

    Второй вопрос: какой именно CRC32 вычисляли? — Там есть стандартный IEEE, CRC32С, CRC32Q.

    Сравнивали результаты с полученными независимо другими методами?

    Reply
  2. premierex

    (1) В названии ошибки нет. Как раз в названии явно указано, что алгоритм предназначен для платформ 1С:Предприятие версий старше 8.3 (8.2, 8.1, 8.0, я так полагаю и на 7.7 можно алгоритм реализовать).

    Какой именно вариант алгоритма применялся я уже и не помню, давно всё это писалось (лет 5 назад), а вот опубликовать решил только сейчас. Предполагаю, что стандартный.

    Результаты проверял в онлайн калькуляторе NoName и написанной тогда мной на С++ программе, реализующей этот же алгоритм.

    Reply
  3. premierex

    (1) Вот нашел исходные коды этого алгоритма на С++ http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c. Это алгоритм CRC-32-IEEE 802.3.

    Reply
  4. ildarovich

    (2) мне кажется, что вы путаете слова «старше» и «старее». Версия 8.3 старше 8.2. Версия 8.2 старее 8.3. Или я ошибаюсь?

    См.:Нумерация версий программного обеспечения

    Reply
  5. premierex

    (4) ildarovich, Вы, к сожалению, ошибаетесь. Слова «старше» и «старее» — синонимы. Приведу простой пример: если сравнить годы выпуска платформ 8.0 и 8.3, то по «возрасту» 8.0 окажется значительно старше (ну, или «старее» — кому как нравится). Сравните Ваш возраст и возраст Ваших родителей. Кто из вас старше?

    Reply
  6. ildarovich

    (5) это «наивное» объяснение. И неправильное.

    По ссылке в Википедии статью прочитали? Старшая версия — это более зрелая версия, вбирающая, как правило, в себя часть кода более ранней версии.

    Reply
  7. premierex

    (6) ildarovich, всё это — полемика по определению старшинства версий. Конечно, на первый взгляд кажется, что версия платформы 1С:Предприятие 8.3 «старше», чем версия 8.2. Но, в посте (4) я уже указал причину, по которой версия 8.3 является младшей — дату её выпуска. Материалы из «Википедии» — ни о чём. Ведь там за основу взят номер релиза (подрелиза) и т. д., а я имел в виду именно старшие по «возрасту» платформы 1С:Предприятия. Если Вам кажется, что название публикации может ввести пользователей InfoStart’а в заблуждение, я совсем не против изменить наименование публикации (если есть желание, можете подсказать, как «грамотно» с Вашей точки зрения, её именовать). Наверное, так и сделаю к моменту выхода еженедельного обзора. Хотя, если вдуматься в название публикации, можно даже удалить её из раздела публичного доступа. Ведь, по-вашему, в названии явная ошибка — не существует на данный момент платформы 1С:Предприятия «старше» версии 8.3. Предлагаю полемику о старшинстве версий ПО закончить. Если есть замечания по опубликованному функционалу — пишите. Критику воспринимаю адекватно.

    Reply
  8. ildarovich

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

    1) кодовую таблицу utf-8 — windows 1251 можно не хранить в макете, а быстро и просто получить вот такой функцией:

    Функция НаборСимволов(КодировкаТекста = «windows-1251», Ответ = «»)
    Для КодСимвола = 1 По 255 Цикл
    Ответ = Ответ + Символ(КодСимвола)
    КонецЦикла;
    ТекстовыйДокумент = Новый ТекстовыйДокумент;
    ТекстовыйДокумент.УстановитьТекст(Ответ);
    ПутьКФайлу = ПолучитьИмяВременногоФайла();
    ТекстовыйДокумент.Записать(ПутьКФайлу, «ISO-8859-1», «»);
    ТекстовыйДокумент.Прочитать(ПутьКФайлу, КодировкаТекста, «»);
    Ответ = ТекстовыйДокумент.ПолучитьТекст();
    Возврат Ответ
    КонецФункции

    Показать

    В строке символы utf-8, их позиция — это код windows-1251. Но, вообще говоря, непонятно, к чему это преобразования — почему не брать все байты utf-8?

    2) При всем богатстве операций с числами в языке 1С есть ли необходимость использовать строку для представления и работы с двоичными числами? Сдвиг вправо — это деление на 256. Логическое «И» с FF это нахождение остатка от деления на 256. «ХОR» можно сделать побайтно (по-полубайтно) по таблицам (два байта — вход, один — выход). Тем более, что таблицы для предпросчета восьми сдвигов входного символа вы все же используете. Инверсия — это тот же XOR с FFFFFFFF. Таблицы лучше загнать заранее в массив. Насчет XOR можно еще подумать — как его смоделировать.

    3) Таблицы в макете — очень не гибко. Лучше из все же рассчитывать и хранить, например, в параметрах обработки. Это не так уж долго.

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

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

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

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

    Reply
  9. premierex

    (8) ildarovich, по поводу Ваших заметок:

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

    Текст = НаборСимволов(, «АБВГД»);
    Для НомерСимвола = 1 По СтрДлина(Текст) Цикл
    Символ = Сред(Текст, НомерСимвола, 1);
    Сообщить(Символ + » = » + Строка(КодСимвола(Символ)));
    КонецЦикла;
    

    И результат:

    ? = 63

    ? = 63

    ? = 63

    ? = 63

    ? = 63

    = 1

    = 2

    = 3

    = 4

    = 5

    = 6

    = 7

    = 8

    = 9

    = 10

    ……..

    А = 1 040

    Б = 1 041

    В = 1 042

    Г = 1 043

    Д = 1 044

    Е = 1 045

    Ж = 1 046

    З = 1 047

    И = 1 048

    ………

    Поэтому-то я и загнал таблицу кодов в макет (макет — из соображений не загромождать текст модуля).

    2. Не спорю логика есть — операции с числами, несомненно производятся быстрее, но на момент написания этих алгоритмов, пришло такое вот решение. К тому же для выполнения «XOR» все равно понадобилось бы двоичное представление числа.

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

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

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

    Заняло 21 сек. Много, конечно, но мне и не требовалось обрабатывать символные массивы таких размеров. Требовалось обрабатывать строки длиной порядка 30-40 символов, а это уже 1 сек. на выполнение, что в общем-то для моей задачи оказалось приемлемым.

    Хотя, я, конечно, всё равно в последующем заменил функцию расчета CRC32 на аналогичную функцию внешней компоненты. А изложенную выше оставил, так сказать, для истории :).

    Reply
  10. premierex

    (8) ildarovich, забыл задать встречный вопрос к п.2: с операцией «AND» всё понятно, Вы привели пример, а как же остальные операции рассчитывать?

    Инверсия — это тот же XOR с FFFFFFFF

    Но Вы же не привели пример, как рассчитать XOR используя только числовые показатели и встроенные функции для работы с числами….

    Reply
  11. premierex

    (8) ildarovich, не внимательно я прочитал Ваш ответ в п.1. и, соответственно, сделал неправильные выводы в посте 9.

    Действительно, в возвращаемой функцией строке номер символа является его кодом в таблице ASCII. Ну, как говорится: «Век живи — век учись». Спасибо за подсказку.

    Reply
  12. ildarovich

    (10)

    1) — чуть позже

    21 сек. — это очень, очень много. Получается 3 хэша в минуту. Тогда как в статье Простая и быстрая хэш функция (hash) средствами 1С (оптимизированный вариант) предлагается решение, позволяющее рассчитать для такой строки 21000 хэшей в минуту! Притом 64-разрядных! Пусть не CRC32.

    У меня в запросе (!!!) получалось (для CRC32) порядка 1000 хэшей минуту (сейчас не ручаюсь, нужно перепроверить). Я счел этот показатель ниже всякой критики и перешел на арифметический хэш, где в запросе получается примерно 12000 хэшей в минуту, а для коротких строк из цифр (всякие коды справочников) еще намного больше.

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

    1) хранить текущее состояния регистра сдвига в «разреженном» числе. При этом на каждый бит регистра будет приходится три бита числа. Это нужно для того, чтобы заменить XOR обычным сложением, чтобы не мешали межразрядные переносы.

    2) предрассчитать таблицу ОС (и загнать в массив 2#k8SjZc9Dxk24) отображения младшего байта регистра (растянутого на три байта) в сумму, которые нужно добавлять к регистру через обратные связи. Хотя массив будет занимать 2#k8SjZc9Dxk24, заполненным там будет всего 5#k8SjZc9Dxk8 ~ 100000 элементов.

    3) предрассчитать отображения символа в «разреженный» байт.

    Алгоритм будет таким:

    Для каждого символа:

    1) Добавляем разреженный байт символа к регистру;

    2) Выделяем младший разреженный байт через %;

    3) Вычитаем его из регистра и делим регистр на 2#k8SjZc9Dxk24, чтобы получить сдвиг на 8 реальных бит (24 разреженных);

    4) Находим добавляемую суммы по разреженному байту из таблицы ОС;

    5) Добавляем эту сумму к регистру

    Записал просто, чтобы не забыть. Основная идея в том, что XOR — это четность, а четность суммы — это младший бит. В регистре делается всего четые сложения «за оборот», поэтому сумма не выйдет за «100» (двоичный код 4) и для ее хранения будет достаточно трех бит.

    Reply
  13. premierex

    (12) ildarovich, 21 сек. — это очень, очень много.

    Конечно много! Я и сам утверждаю это в посте (9).

    Ваши публикации, указанные в ссылках поста я прочитал. Очень познавательно! Надо будет вернуться и поставить «плюс».

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

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

    Reply
  14. ildarovich

    (13) это нужно рассматривать как хороший учебный пример.

    Во-первых, код очень хорошо структурирован и замечательно оформлен, снабжен множеством нужных при отладке проверок.

    Во-вторых, реализован один из возможных быстрых алгоритмов (при реализации на другом языке), где в отладчике можно проследить по шагам его работу, чтобы разобраться в выполняемых преобразованиях (символ за такт — это не символ за восемь тактов, как при решении задачи «в лоб»).

    Так что тщательно сделанная работа бесполезной не бывает.

    Reply
  15. premierex

    Спасибо! Надеюсь, кому-нибудь пригодится.

    Reply
  16. kylux

    Ув. Максим! Вы если ставите флажек, насчет поддержки «Все платформы», то уж выкладывайте обработку в версии и 8.0! А так получился лохотрон, мне каким образом Ваш макет использовать в версии 8.1?

    Reply
  17. premierex

    (16) Ув. kylux. Вот уж просто не подумал, что кто-то ещё использует платформы версий 8.0 или 8.1. Когда я эту обработку писал, уже 8.2 вовсю использовали. Ну, файл-то Вы не скачивали, значит и не потеряли ничего )))

    Reply
  18. premierex

    (16) Прошу прощения за неточность: файл Вы скачивали (раньше подпись была у комментария «файл скачал», сейчас её убрали к сожалению, вот и получилось недоразумение). Если хотите, могу купить что-нибудь из Ваших наработок, чтобы вернуть Вам потраченные $m.

    Reply
  19. driveboy

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

    Reply
  20. premierex

    (19) Очень интересно, как же вы контрольную сумму пустой строки вычисляете?

    Reply

Leave a Comment

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