Вводные
Нужно опубликовать каталог товаров для использования его информационными системами клиентов.
Формат XML, JSON.
В качестве опорной схемы использовал ICML https://help.retailcrm.ru/Developers/ICML, которая является расширением YML https://yandex.ru/support/partnermarket/export/yml.html.
Возможно имело смысл использовать CommerceML, пока с ним не сложилось.
Реализация
- Сформировать схемы выгрузки данных.
- Выгрузить изображения на внешний ресурс сохранив публичные ссылки
- Реализовать выгрузку данных по схемам с требуемыми отборами.
- Реализовать выгрузку схемы.
Формирование схемы данных
Основной формат XML, поэтому использую XDTO пакеты.
<xs:schema xmlns:tns="http://retailcrm.ru/export/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://retailcrm.ru/export/XMLSchema" attributeFormDefault="unqualified" elementFormDefault="qualified">
<xs:element name="yml_catalog">
<xs:complexType>
<xs:sequence>
<xs:element name="shop"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="category">
<xs:sequence>
<xs:element name="name"/>
<xs:element name="picture" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="id" use="required"/>
<xs:attribute name="parentId"/>
</xs:complexType>
<xs:complexType name="offer">
<xs:sequence>
<xs:element name="url"/>
<xs:element name="price"/>
<xs:element name="purchasePrice"/>
<xs:element name="categoryId"/>
<xs:element name="picture" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="name"/>
<xs:element name="xmlId" minOccurs="0"/>
<xs:element name="productName"/>
<xs:element name="param" type="tns:param" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="vendor"/>
<xs:element name="unit"/>
<xs:element name="vatRate" minOccurs="0"/>
<xs:element name="dimensions"/>
<xs:element name="barcode"/>
<xs:element name="productActivity"/>
<xs:element name="weight"/>
</xs:sequence>
<xs:attribute name="id" use="required"/>
<xs:attribute name="productId" use="required"/>
<xs:attribute name="quantity" use="required"/>
</xs:complexType>
<xs:complexType name="param">
<xs:simpleContent>
<xs:restriction>
<xs:attribute name="name" use="required"/>
<xs:attribute name="code" use="required"/>
</xs:restriction>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="shop">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="company" type="xs:string"/>
<xs:element name="categories">
<xs:complexType>
<xs:sequence>
<xs:element name="category" type="tns:category" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="offers">
<xs:complexType>
<xs:sequence>
<xs:element name="offer" type="tns:offer" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="unit">
<xs:attribute name="code" use="required"/>
<xs:attribute name="name" use="required"/>
<xs:attribute name="sym" use="required"/>
</xs:complexType>
</xs:schema>
При использовании расширения они добавляются в него и используется глобальная фабрика.
При использовании внешней обработки создается фабрика из XSD схемы.
Создание фабрики из макета
Макет = ПолучитьМакет("ЭкспортДанных_XSD");
ИмяФайла = ПолучитьИмяВременногоФайла("xsd");
ЗаписьТекста = Новый ЗаписьТекста(ИмяФайла);
ЗаписьТекста.Записать(Макет.ПолучитьТекст());
ЗаписьТекста.Закрыть();
ФайлыXSD = Новый Массив();
ФайлыXSD.Добавить(ИмяФайла);
ИмяСхемы = "http://retailcrm.ru/export/XMLSchema";
МояФабрикаXDTO = СоздатьФабрикуXDTO(ФайлыXSD);
Пакет = МояФабрикаXDTO.Пакеты.Получить(ИмяСхемы);
Организация внешнего ресурса, выгрузка файлов.
Для своих проектов в качестве внешнего хранилища выбрал selectel https://selectel.ru/services/cloud/storage/
- Есть доступ по FTP
- Контейнер можно опубликовать по HTTP с привязкой к домену.
- Относительно небольшая стоимость
Документация по работе с контейнерами selectel https://kb.selectel.ru/23136144.html
Контейнер нужно сделать публичным и получить http ссылку на него (без upload)
Скриншот настройки контейнера
Для того чтобы не перегружать корневую папку при отправке файла вычисляю его md5 хеш и создаю папку из первых трех символов хеша, более подробно описано в статье https://habr.com/ru/post/227855/
Одно из требований хранение изображений для характеристик, через расширение отдельный справочник "ПрикрепленныеФайлы" с требуемой обвязкой не сделать, поэтому в "НоменклатураПрикрепленныеФайлы" добавил реквизит с типом характеристика. Заполняю при загрузке из внешней системы, при выгрузке, offer = характеристика, делаю выборку с учетом этого реквизита.
После отправки файла фиксирую адрес публикации в реквизите элемента спр. "НоменклатураПрикрепленныеФайлы", добавленного через расширение.
Процедура ВыгрузитьФайлыFTP()
...
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
ДвоичныеДанные = РаботаСФайлами.ДвоичныеДанныеФайла(Выборка.Ссылка, Ложь);
Если ДвоичныеДанные = Неопределено Тогда
Продолжить;
КонецЕсли;
ИмяФайлаВременнное = ПолучитьИмяВременногоФайла();
ДвоичныеДанные.Записать(ИмяФайлаВременнное);
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(ДвоичныеДанные);
ХешСумма = Строка(Хеш.ХешСумма);
ХешСумма = НРег(ХешСумма);
ХешСумма = СтрЗаменить(ХешСумма, " ", "");
Каталог = Лев(ХешСумма, 3);
МассивКаталогов = Новый Массив;
МассивКаталогов.Добавить("files");
МассивКаталогов.Добавить(Каталог);
Попытка
ОтправитьНаFTP(ИмяФайлаВременнное, ХешСумма + "." + Выборка.Расширение, МассивКаталогов);
Исключение
Возврат;
КонецПопытки;
АдресНаFTP = ПубличныйАдресFTP + "/files/" + Каталог + "/" + ХешСумма + "." + Выборка.Расширение;
обНоменклатураПрисоединенныеФайлы = Выборка.Ссылка.ПолучитьОбъект();
обНоменклатураПрисоединенныеФайлы.АдресНаFTP = АдресНаFTP;
обНоменклатураПрисоединенныеФайлы.Записать();
КонецЦикла;
КонецПроцедуры
Процедура ОтправитьНаFTP(ЛокальныйПутьКФайлу, ИмяФайла, МассивКаталогов = Неопределено)
Если МассивКаталогов = Неопределено Тогда
МассивКаталогов = Новый Массив;
КонецЕсли;
Если FTPСоединение = Неопределено Тогда
FTPСоединение = Новый FTPСоединение(ИмяСервераFTP, 21, ПользовательFTP, ПарольFTP);
КонецЕсли;
FTPСоединение.УстановитьТекущийКаталог(КорневаяПапкаFTP);
СтрокаКаталог = "";
Для каждого СтрК Из МассивКаталогов Цикл
FTPСоединение.СоздатьКаталог(СтрК);
СтрокаКаталог = СтрокаКаталог + "/" + СтрК;
FTPСоединение.УстановитьТекущийКаталог(КорневаяПапкаFTP + СтрокаКаталог);
КонецЦикла;
FTPСоединение.Записать(ЛокальныйПутьКФайлу, ИмяФайла);
КонецПроцедуры
Выгрузка данных по схемам
Создаем объект из пакета и нормализуем данные из выборок по данным базы.
Код выгрузки данных по схеме
Процедура ВыгрузитьКаталогFTP()
МассивНоменклатуры = ГруппыНоменклатуры.ВыгрузитьКолонку("ГруппаНоменклатуры");
Пакет = ФабрикаXDTO.Пакеты.Получить(ИмяСхемы);
СвойствоXDTO = Пакет.КорневыеСвойства.Получить("yml_catalog");
ОбъектКаталог = ФабрикаXDTO.Создать(СвойствоXDTO.Тип);
ТипМагазин = ФабрикаXDTO.Тип(ИмяСхемы, "shop");
ОбъектМагазин = ФабрикаXDTO.Создать(ТипМагазин);
ОбъектМагазин.name = "shop";
ОбъектМагазин.company = "shop";
ОбъектМагазин.categories = ФабрикаXDTO.Создать(ТипМагазин.Свойства.Получить("categories").Тип);
ОбъектМагазин.offers = ФабрикаXDTO.Создать(ТипМагазин.Свойства.Получить("offers").Тип);
ВыгрузитьКаталогFTP_Категории(ОбъектМагазин, МассивНоменклатуры);
ВыгрузитьКаталогFTP_ТорговыеПредложения(ОбъектМагазин, МассивНоменклатуры);
ОбъектКаталог.shop = ОбъектМагазин;
СохранитьОтправитьНаFTP(ОбъектКаталог, "yml_catalog", "yml_catalog");
КонецПроцедуры
Процедура СохранитьОтправитьНаFTP(ОбъектXDTO, ЛокальноеИмя, ИмяФайла)
ИмяФайлаВременнное = ПолучитьИмяВременногоФайла();
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяФайлаВременнное);
ЗаписьXML.ЗаписатьОбъявлениеXML();
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ОбъектXDTO, ЛокальноеИмя);
ЗаписьXML.Закрыть();
ОтправитьНаFTP(ИмяФайлаВременнное, ИмяФайла + ".xml");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ОткрытьФайл(ИмяФайлаВременнное);
ФабрикаXDTO.ЗаписатьJSON(ЗаписьJSON, ОбъектXDTO);
ЗаписьJSON.Закрыть();
ОтправитьНаFTP(ИмяФайлаВременнное, ИмяФайла + ".json");
КонецПроцедуры
При сериализации и отправки можно добавить архивирование.
Специфика выгрузки списочных типов, СписокXDTO:
Если в объекте нужно несколько повторяющихся элементов, то создается свойство с "Маскимальное количество" = -1 указывается для него тип. При создании из фабрики тип элемента устанавливается автоматически.
Если нужно чтобы список был вложен в элемент, то для элемента тип не указывается и создается для него описание типа, в котором описывается свойство с нужным типом и "Маскимальное количество" = -1. при создании из фабрики его нужно отдельно инициализировать.
ОбъектМагазин.categories = ФабрикаXDTO.Создать(...);
Файлы перед отправкой можно паковать zip для уменьшения объема передаваемой информации.
Выгрузка схемы
Процедура ВыгрузитьСхемуFTP()
НаборСхем = ФабрикаXDTO.ЭкспортСхемыXML(ИмяСхемы);
ИмяФайлаВременнное = ПолучитьИмяВременногоФайла();
Схема = НаборСхем.Получить(0);
Схема.ОбновитьЭлементDOM();
ЗаписьDOM = Новый ЗаписьDOM;
ЗаписьXMLФайл = Новый ЗаписьXML;
ЗаписьXMLФайл.ОткрытьФайл(ИмяФайлаВременнное);
ЗаписьDOM.Записать(Схема.ДокументDOM, ЗаписьXMLФайл);
ЗаписьXMLФайл.Закрыть();
ОтправитьНаFTP(ИмяФайлаВременнное, "ExportSchema.xsd");
КонецПроцедуры
Итог
В результате выполнения кода получаем
- изображения доступные по http, пример ссылки https://192804.selcdn.ru/public/files/007/0070820ecece0a7f17f070cbc940b87c.png
- файл каталога с ссылками на изображения, пример ссылки https://192804.selcdn.ru/public/yml_catalog.xml
- файл схемы данных, пример ссылки https://192804.selcdn.ru/public/ExportSchema.xsd
Из за специфики формирования JSON из XDTO он может получаться больше чем XML, для этого формата нужно сериализовать через "соответствие+массив".
Ссылка на GitHub: https://github.com/malikov-pro/external-storage-1c-ssl/tree/master/epf
UPD_1: Добавил обработку выгрузки файлов, работает только для новых файлов (отсутствие публичной ссылки), изменения не отрабатывает, добавил ссылку на GitHub, папку FTP указывать с ведущим слешем "/"
Благодарю за внимание.