Работаем с сервисом подсказок на формах: dadata.ru




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

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

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

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

30 Comments

  1. xzorkiix
    &НаСервере
    Процедура УстановитьПол(gender)
    
    Если gender = «MALE» Тогда
    Объект.Пол = Перечисления.ПолФизическогоЛица.Мужской;
    ИначеЕсли gender = «FEMALE» Тогда
    объект.Пол = Перечисления.ПолФизическогоЛица.Женский;
    КонецЕсли;
    
    КонецПроцедуры

    Показать

    Здесь достаточно на стороне клиента использовать ПредопределенноеЗначение(«Перечисление.ПолФизическогоЛица.Женский»);

    Reply ↓
  2. xzorkiix
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.УстановитьСтроку(СтрокаXML);
    ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);

    wsСсылка, как таковая и не нужна для разбора ответа сервиса.

    Reply ↓
  3. qwinter

    (2) xzorkiix, получается двойная работа, сначала создай wsСсылку и ее свойства, а затем еще надо присвоить им значения. Кода столько же как при HTTPЗапросе, плюс созданный объект в конфигурации. Вот в упор не вижу преимуществ при их использовании.

    Reply ↓
  4. xzorkiix

    (3) я хотел указать xdto пакет не нужен (оговорился). По крайней мере в рамках описанной задачи. wsСсылка о другом об обмене через протокол SOAP. В вашем случае при разборе ответа нет необходимости расписывать модель сервиса. Достаточно перед обращением к свойствам сервиса проверять их наличие.

    Это если для разбора XML строки вы используете объекты XDTO

    Функция Свойство(ОбъектXDTO, ИмяСвойства, Значение = Неопределено)
    Результат = Не (ОбъектXDTO.Свойства().Получить(ИмяСвойства) = Неопределено);
    Если Результат Тогда
    Значение = ОбъектXDTO[ИмяСвойства];
    КонецЕсли;
    Возварт Результат;
    КонецФункции
    
    // . . .
    
    Предложения = Неопределено;
    Если Свойство(ОбъектXDTO, «suggestions», Предложения) Тогда
    // . . .
    КонецЕсли;
    

    Показать

    Reply ↓
  5. qwinter

    (4) xzorkiix, коментарии как обычно полезнее статьи)))

    Reply ↓
  6. bonv

    1.

    &НаКлиенте
    Функция ПолучитьОбъектXDTO(Текст, КоличествоПодсказок = 10)

    Не делайте это на клиенте, в веб-клиенте работать не будет, в нем нет объекта XDTO. Лучше делать внеконтекстный вызов сервера.

    2.

    Тело = ОтветHTTP.ПолучитьТелоКакСтроку();
    Если Тело = «» Тогда

    Это не совсем корректная проверка, если сервер вернет ошибку, то в теле будет ее описание.

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

    Если ОтветHTTP.КодСостояния = 200 Тогда

    3.

    структурныйТип = ФабрикаXDTO.Тип(«http://www.sample-package.org», «SuggestFioResponse»);
    
    МойXML = Новый ЧтениеXML;
    МойXML.УстановитьСтроку(Тело);
    ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(МойXML, структурныйТип);
    
    Возврат ОбъектXDTO;

    Собственно определять в метаданных и затем использовать объект XDTO это лишнее.

    Используете анонимный объект XDTO.

    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.УстановитьСтроку(Тело);
    ЧтениеXML.ПерейтиКСодержимому();
    ОбъектXDTO = Фабрика.ПрочитатьXML(ЧтениеXML);
    ЧтениеXML.Закрыть();
    
    Возврат ОбъектXDTO;

    4.

    &НаКлиенте
    Процедура НаименованиеАвтоПодбор(Элемент, Текст, ДанныеВыбора, Параметры, Ожидание, СтандартнаяОбработка)

    В этом методе лучше подключать обработчик ожидания, скажем на 0.5 сек и уже в нем заполнять список.

    И опять же лучше отказаться от клиентского кода в пользу вызова внеконтекстного серверного вызова, из которого уже возвращать список.

    Reply ↓
  7. qwinter

    (6) doxflow, спасибо)

    Reply ↓
  8. chmv

    Любопытно, но не более

    Reply ↓
  9. Патриот

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

    Reply ↓
  10. lyalius

    Отличная статья, только пример нерабочий.

    Проблема вот в этих двух строках:

    HTTPЗапрос.Заголовки.Вставить(«Authorization», «ВАШ КОД АВТОРИЗАЦИИ»);

    HTTPЗапрос.УстановитьТелоИзСтроки(«» + Текст + «» + КоличествоПодсказок + «»);

    Правильно так:

    HTTPЗапрос.Заголовки.Вставить(«Authorization», «Token ВАШ КОД АВТОРИЗАЦИИ»);

    HTTPЗапрос.УстановитьТелоИзСтроки(«<req><query>» + Текст + «</query><count>» + КоличествоПодсказок + «</count></req>»);

    Еще момент: если по введенной пользователем строке ничего не будет найдено, то код сломается на строчке

    > Если ТипЗнч(ОбъектXDTO.suggestions) = Тип(«СписокXDTO») Тогда

    потому что свойство suggestions в ответе отсутствует.

    Reply ↓
  11. Патриот

    (10) lyalius, спасибо, так работает

    Reply ↓
  12. mnemchinov

    У меня заработало только так:

    HTTPЗапрос.УстановитьТелоИзСтроки(«<req><query>» + Текст + «» + КоличествоПодсказок + «</query></req>»);

    И еще вместо:

    Если ТипЗнч(ОбъектXDTO) = Тип(«ОбъектXDTO») Тогда
    Если ТипЗнч(ОбъектXDTO.suggestions) = Тип(«СписокXDTO») Тогда

    сделал:

    Если ТипЗнч(ОбъектXDTO) = Тип(«ОбъектXDTO») Тогда
    Если ОбъектXDTO.Свойства().Получить(«suggestions») <> Неопределено Тогда
    Если ТипЗнч(ОбъектXDTO.suggestions) = Тип(«СписокXDTO») Тогда

    и тогда окончательно заработало

    Reply ↓
  13. oslokot

    Всем привет! у этого сервиса, как я понимаю, сменилась версия api и теперь прежняя не работает.

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

    Текст ошибки: {«detail»: «JSON parse error — No JSON object could be decoded»}

    Странно, вроде все как надо сделал.

    HTTPЗапрос = Новый HTTPЗапрос();
    HTTPЗапрос.АдресРесурса = «/api/v2/clean»;
    HTTPЗапрос.Заголовки.Вставить(«Content-Type», «application/json»);
    HTTPЗапрос.Заголовки.Вставить(«charset», «UTF-8»);
    HTTPЗапрос.Заголовки.Вставить(«Authorization», «Token API-ключ»);
    HTTPЗапрос.Заголовки.Вставить(«X-Secret», «Секретный ключ»);
    Стр = «{ «»structure»»: [ «»ADDRESS»» ], «»data»»: [ [ «»Москва, Милютинский 13″» ] ] }»;
    HTTPЗапрос.УстановитьТелоИзСтроки(Стр,КодировкаТекста.UTF8);
    Соединение = Новый HTTPСоединение(«dadata.ru»,,,,Новый ИнтернетПрокси,, Новый ЗащищенноеСоединениеOpenSSL);
    ОтветHTTP = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
    
    Сообщить(ОтветHTTP.КодСостояния);
    Тело = ОтветHTTP.ПолучитьТелоКакСтроку();
    Сообщить(Тело);
    

    Показать

    Может кто уже делал, подскажите пожалуйста где я наколбасил

    Reply ↓
  14. oslokot

    (13) Сам себе и отвечу) Разобрался. Нужно перезаписать файл в кодировке UTF-8 без BOM и отправить серверу.

    В общем неплохой сервис dadata.ru, жаль платный.

    Reply ↓
  15. anton.fly7

    (14) покажите как вы устанавливаете тело НТТРЗапроса?

    я делаю из строки, возвращает такую же ошибку

    ТекстЗапроса = «[ «»NAME»», «»PHONE»» ], «»data»»: [ [ «»» + Парам.Наименование + «»», «»» + Парам.Телефон + «»» ] ]»;
    
    ТекстЗапроса = «{ «»structure»»: » + ТекстЗапроса + » }»;
    
    HTTPЗапрос = Новый HTTPЗапрос();
    HTTPЗапрос.АдресРесурса = «/api/v2/clean»;
    
    HTTPЗапрос.Заголовки.Вставить(«Content-Type», «application/json»);
    HTTPЗапрос.Заголовки.Вставить(«Authorization», «Token » + Token);
    HTTPЗапрос.Заголовки.Вставить(«X-Secret», Secret);
    HTTPЗапрос.Заголовки.Вставить(«charset», «UTF-8»);
    HTTPЗапрос.УстановитьТелоИзСтроки(ТекстЗапроса);
    

    Показать

    Reply ↓
  16. anton.fly7

    (15) сам себе отвечу )

    ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
    ЗТ = Новый ЗаписьТекста(ИмяВременногоФайла, КодировкаТекста.ANSI);
    ЗТ.Закрыть();
    ЗТ = Новый ЗаписьТекста(ИмяВременногоФайла,,, Истина, Символы.ПС);
    ЗТ.Записать(ТекстЗапроса);
    ЗТ.Закрыть();
    
    HTTPЗапрос = Новый HTTPЗапрос();
    HTTPЗапрос.АдресРесурса = «/api/v2/clean»;
    
    HTTPЗапрос.Заголовки.Вставить(«Content-Type», «application/json»);
    HTTPЗапрос.Заголовки.Вставить(«Authorization», «Token » + Token);
    HTTPЗапрос.Заголовки.Вставить(«X-Secret», Secret);
    HTTPЗапрос.Заголовки.Вставить(«charset», «UTF-8»);
    //HTTPЗапрос.УстановитьТелоИзСтроки(ТекстЗапроса, КодировкаТекста.UTF8);
    HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(Новый ДвоичныеДанные(ИмяВременногоФайла));
    

    Показать

    Reply ↓
  17. Zixxx

    А что там по поводу SOAP?

    По адресу: http://confluence.hflabs.ru/pages/viewpage.action?... существуют подсказки для протокола SOAP. Пробовал обратиться к wsdl по адресу: http://dadata.ru/suggestions/api/4_1/ws?wsdl, но в ответ получаю ошибку:

    Error loading [http://dadata.ru/suggestions/api/4_1/ws?wsdl]: org.apache.xmlbeans.XmlException: org.apache.xmlbeans.XmlException: error: does not close tag

    Я так понимаю что это неправильный адрес, но какой тогда правильный?

    Reply ↓
  18. lyalius

    (17) Zixxx, правильный вот этот https://dadata.ru/api/suggest. Та страница, на которую вы сослались — это чтобы вызвать подсказки, которые у вас внутри на сервере установлены.

    Reply ↓
  19. lyalius

    (14) oslokot, спасибо 🙂 Посмотрите, у нас теперь и подсказки по ЕГРЮЛ есть.

    Reply ↓
  20. Zixxx

    (18) lyalius, И где там по ссылке wsdl?

    Reply ↓
  21. oslokot

    (19) lyalius, Да-да, видел.

    Может как-нибудь воспользуюсь

    Пока что использую свою обработку по данным контур.фокус

    Ну а так, конечно молодцы, хорошая работа!

    Желаю вам успешного развития проекта!

    Reply ↓
  22. lyalius

    (20) Zixxx, wsdl доступна тем, кто поставил сервис у себя в корпоративной сети. Вы не ставили и вам она не доступна. Вам нужно пользоваться вот этим сервисом: https://dadata.ru/api/suggest.

    Reply ↓
  23. lyalius

    (21) oslokot, спасибо, очень приятно слышать такие пожелания 🙂

    Reply ↓
  24. olegmedvedev
    HTTPЗапрос = Новый HTTPЗапрос();
    HTTPЗапрос.АдресРесурса = «/api/v2/suggest/address»;
    
    HTTPЗапрос.Заголовки.Вставить(«Content-Type», «application/xml»);
    HTTPЗапрос.Заголовки.Вставить(«Accept», «application/xml»);
    HTTPЗапрос.Заголовки.Вставить(«Authorization», «Token «+Token);
    HTTPЗапрос.УстановитьТелоИзСтроки(«<req><query>» + Текст + «</query></req>»);
    
    Соединение = Новый HTTPСоединение(«dadata.ru»,,,,,, Новый ЗащищенноеСоединениеOpenSSL);
    
    ОтветHTTP = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
    

    Показать

    вот такой код работает сейчас

    Reply ↓
  25. olegmedvedev
    HTTPЗапрос = Новый HTTPЗапрос();
    HTTPЗапрос.АдресРесурса = «/api/4_1/rs/suggest/address»;
    
    HTTPЗапрос.Заголовки.Вставить(«Content-Type», «application/xml»);
    HTTPЗапрос.Заголовки.Вставить(«Accept», «application/xml»);
    HTTPЗапрос.Заголовки.Вставить(«Authorization», «Token «+Token);
    HTTPЗапрос.УстановитьТелоИзСтроки(«<req><query>» + Текст + «</query></req>»);
    
    Соединение = Новый HTTPСоединение(«suggestions.dadata.ru/suggestions»,,,,,, Новый ЗащищенноеСоединениеOpenSSL);
    
    ОтветHTTP = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
    

    Показать

    изменился API dadata

    Reply ↓
  26. vaad80

    народ, подскажите плз, делаю запрос по последней инструкции, апи текущий, как в инструкции, а в ответ приходит код ответа 200 и соответствие

    «Content-Type»:»application/xml; charset=UTF-8″

    «Connection»:»keep-alive»

    «X-Powered-By»:»Undertow/1″

    «Vary»:»Accept-Encoding»

    «Server»:»nginx/1.13.0″

    «Date»:»Wed, 24 May 2017 17:46:12 GMT»

    «Transfer-Encoding»:»chunked»

    Поля suggestions нет(( в чем может быть ошибка?, вызываю &насервере

    код
    Reply ↓
  27. olegmedvedev

    (26) Ответ кода 200 — «Запрос успешно обработан», все верно делаете). Сейчас гляну и может подскажу подробнее.

    Reply ↓
  28. vaad80

    (27)

    Приветствую, со структурой разобрался, не выходило потому что результат не вогнал в тело. Сейчас приходит тегированная структура,<suggest><suggest>

    Всё ок, но это не устраивает, хотелось бы чтобы приходило в json,так как он легче и быстрее парсится 1ской.

    Затык происходит у меня при json на выставке параметра запроса, текста который ищу. Строкой не проходит, возможно надо туда объект json-a впихнуть…

    Может есть рабочий код с json?

    Reply ↓
  29. tonimontana05

    (28)

    HTTPЗапрос = Новый HTTPЗапрос();

    HTTPЗапрос.АдресРесурса = «/api/4_1/rs/suggest/address»;

    HTTPЗапрос.Заголовки.Вставить(«Content-Type», «application/json»);

    HTTPЗапрос.Заголовки.Вставить(«Accept», «application/json»);

    HTTPЗапрос.Заголовки.Вставить(«Authorization», «Token » + «Свой токен»);

    HTTPЗапрос.УстановитьТелоИзСтроки(«{«+»»»query»»» + «:» + «»»» + Текст + «»»» + «,» +»»»count»»» + «:» + «» + КоличествоПодсказок + «» + «}»,

    КодировкаТекста.UTF8,

    ИспользованиеByteOrderMark.НеИспользовать);

    Соединение = Новый HTTPСоединение(«suggestions.dadata.ru/suggestions»,,,,Новый ИнтернетПрокси,, Новый

    ЗащищенноеСоединениеOpenSSL);

    Ответ = Соединение.ОтправитьДляОбработки(HTTPЗапрос);

    ОтветСервера = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);

    Чтение = Новый ЧтениеJSON;

    Чтение.УстановитьСтроку(ОтветСервера);

    Данные = ПрочитатьJSON(Чтение);

    Чтение.Закрыть();

    Reply ↓
  30. JetBrain

    Сервис выдает абсолютно не актуальную информацию, ИП три раза уже закрылось и открылось , а карточка старая из ФНС загрузилась, проверили пол базы из них 50-60 % только актуально у тех кто ни чего не менял в регистрации.

    Reply ↓

Leave a Comment

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