Формат EnterpriseData
20.10.2017
Внимание! Данный функционал доступен в "Библиотеке стандартных подсистем", начиная с версии 2.3.1.62.
Для обмена данными через формат EnterpriseData у конфигураций, использующих "Библиотеку стандартных подсистем", есть два веб-сервиса:
Т.к. объем передаваемых через веб-сервисы данных ограничен, данные передаются в виде архивов формата ZIP. Если размер архива слишком велик для передачи через веб-сервис – его разбивают на части и передают по частям. Максимальный размер передаваемого через веб-сервис за сообщения зависит от ряда факторов (от пропускной способности сети, от настроек веб-сервера, от объема свободной памяти на передающей и приемной стороне и т.д.). Опыт показывает, что лучше не передавать через веб-сервис данные размером более нескольких мегабайт.
Собственно задача обмена данными включает в себя две подзадачи:
Большинство методов обоих веб-сервисов имеют выходной параметр – строку ErrorMessage. В случае если внутри конфигурации произошла ошибка, связанная с бизнес-логикой – в эту строку будет записана информация об этой ошибке. Если ошибок в процессе работы метода не было – в строку ErrorMessage будет помещена пустая строка. Если же в процессе работы метода возникла системная ошибка (например, на стороне конфигурации не удалось разархивировать полученный архив) – веб-метод сгенерирует исключение (exception).
Большинство методов обоих веб-сервисов возвращают строки, но в текущей версии возвращаемые строки всегда пустые (кроме EnterpriseDataUpload.PutDataActionResult – он возвращает статус обработки данных на стороне конфигурации – “Active”, “Completed” либо “Failed”).
Ниже на примерах мы рассмотрим, как использовать эти веб-сервисы для обмена данными с конфигурациями из языков C# и Java.
На стороне конфигурации должны быть развернуты веб-сервисы EnterpriseDataUpload и EnterpriseDataExchange соответствующих версий (в данном случае была использована версия 1.0.1.1). При открытии этих двух URL-адресов в браузере (нужно подставить правильное для вашей инсталляции «1С:Предприятия» имя веб-сервера и публикации):
http://<веб-сервер>/<веб-приложение>/ws/EnterpriseDataExchange_1_0_1_1?wsdl
http://<веб-сервер>/<веб-приложение>/ws/EnterpriseDataUpload_1_0_1_1?wsdl
должны выводиться WSDL-описания сервисов:
В примерах использовалась Visual Studio 2012. В ней было создано консольное приложение на C#, в него импортированы веб-сервисы:
Использовалась среда разработки Eclipse 4.4.2. Для генерации кода по WSDL файлов веб-сервисов применялась утилита wsdl2java из фреймворка Apache CXF 2.7.16.
Объекты, предназначенные к обмену, «упакованы» в сообщение (Message), структуру в формате XML. Корневой элемент сообщения называется Message и содержит два дочерних элемента:
Объект может содержать в себе ссылки на другие объекты (например, документ «Акт выполненных работ» может содержать в себе одну или несколько ссылок на номенклатуру). В этом случае, если мы импортируем данные в информационную базу, все объекты, на которые мы ссылаемся из «родительского» объекта, должны либо уже существовать в системе, либо их описание должно содержаться в том же XML файле.
Если нам необходимо удалить какой-то объект, в коллекцию Body надо добавить элемент типа «УдалениеОбъекта», и в этом элементе сослаться на удаляемый объект (см. описание типа «УдалениеОбъекта» в схеме EnterpriseData_X_Y_Z.xsd).
С помощью формата EnterpriseData нам доступны операции создания, обновления и удаления объектов. На данный момент для корректной загрузки данных в типовые решения все объекты должны содержать заполненный элемент «Ссылка» из элемента «Ключевые свойства» (GUID в форме строки). Это первичный ключ объекта. Конфигурации ведут себя следующим образом:
<Message xmlns:msg="http://www.1c.ru/SSL/Exchange/Message" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<msg:Header> <msg:Format>http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.0</msg:Format>
<msg:CreationDate>2015-04-09T11:57:48</msg:CreationDate>
<msg:AvailableVersion>1.0</msg:AvailableVersion> </msg:Header> <Body xmlns="http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.0"> <Справочник.Контрагенты> <КлючевыеСвойства> <Ссылка>15ee9feb-696e-11e4-a6fc-5404a67fc69d</Ссылка> <НаименованиеПолное>Леннон Джон</НаименованиеПолное> <ЮридическоеФизическоеЛицо>ЮридическоеЛицо</ЮридическоеФизическоеЛицо> </КлючевыеСвойства> <Наименование>Леннон Джон</Наименование> <ОбособленноеПодразделение>false</ОбособленноеПодразделение> </Справочник.Контрагенты> <УдалениеОбъекта> <СсылкаНаОбъект> <СсылкаНаОбъект> <КонтрагентыГруппаСсылка>15ee9feb-696e-11e4-a6fc-5404a67fc69d</КонтрагентыГруппаСсылка> </СсылкаНаОбъект> </СсылкаНаОбъект> </УдалениеОбъекта> </Body> </Message>
В секции <Header> - ссылка на формат сообщения, дата создания сообщения и версия формата.
В секции <Body> - описание нового контрагента (тип «Справочник.Контрагенты») с полным наименованием «Леннон Джон» и описание удаления объекта по ключу. Ключ в объекте <УдалениеОбъекта> (элемент <СсылкаНаОбъект>) – тот же, что и у нового контрагента. В результате обработки этого сообщения конфигурация создаст нового контрагента и тут же удалит его.
EnterpriseDataUpload – интерфейс исключительно для импорта данных в формате EnterpriseData в конфигурацию из сторонних приложений. Условия задачи: у нас есть XML файл с данными в формате EnterpriseData, надо передать его в конфигурацию и убедиться в том, что на стороне конфигурации данные успешно получены.
Алгоритм работы следующий:
Итак, у нас есть архив с данными в формате EnterpriseData (один или несколько файлов). Не будем описывать, как в программе создать архив и разбить его на несколько файлов, чтобы сэкономить время – желающие смогут легко найти примеры подобного кода, используя поисковые сервера и соответствующие запросы.
Реализуем функцию, которая принимает такие входные параметры:
Путь до архивированного файла с данными. Если архив умещается в одном файле – это имя файла с полным путем, включая расширение (например, “C:\Exchange\data.zip”). Если же архив разбит на несколько частей, то это будет имя любого из файлов без расширения и точки (например, “C:\Exchange\data”). Предполагается, что это последовательность файлов с расширениями “.001”, “.002” и т.д.
Реализация функции (в виде статического метода) в листинге ниже. Диагностическая информация выводится в консоль.
Копировать в буфер обменаprivate static void PutData2WebService(string serviceURL, string user, string password, string fileName)
{
EnterpriseDataUpload.EnterpriseDataUpload_1_0_1_1 edDataUpload = new EnterpriseDataUpload.EnterpriseDataUpload_1_0_1_1();
edDataUpload.Url = serviceURL;
edDataUpload.Credentials = new System.Net.NetworkCredential(user, password);
string result = edDataUpload.Ping();
System.Console.WriteLine("Invoking ping " + result);
string errorMessage;
bool b = edDataUpload.TestConnection(out errorMessage);
if (b)
{
//если передано непосредственно имя ZIP-архива - читаем одним куском
bool singleFile = fileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase);
string fileId = Guid.NewGuid().ToString();
if (singleFile)
{
byte[] partData = System.IO.File.ReadAllBytes(fileName);
//передаем 0 в качестве номера части файла
result = edDataUpload.PutFilePart(fileId, 0, partData, out errorMessage);
if (errorMessage != string.Empty)
{
System.Console.WriteLine("PutFilePart error: " + errorMessage);
return;
}
}
else
{
//счетчик частей файла начинается с единицы
int partNumber = 1;
//считаем, что нам передали имя последовательности файлов (*.001, *.002 и т.д.) БЕЗ расширения и точки на конце
string filePath = fileName + "." + (partNumber).ToString("d3");
while (System.IO.File.Exists(filePath))
{
System.Console.WriteLine("Reading from: " + filePath);
byte[] partData = System.IO.File.ReadAllBytes(filePath);
string putFilePartResult = edDataUpload.PutFilePart(fileId, partNumber, partData, out errorMessage);
if (errorMessage != string.Empty)
{
System.Console.WriteLine("PutFilePart error: " + errorMessage);
return;
}
filePath = fileName + "." + (++partNumber).ToString("d3");
}
}
string operationId;
result = edDataUpload.PutData(fileId, out operationId, out errorMessage);
if (errorMessage != string.Empty)
{
System.Console.WriteLine("PutData error: " + errorMessage);
return;
}
result = edDataUpload.PutDataActionResult(operationId, out errorMessage);
if (errorMessage != string.Empty)
{
System.Console.WriteLine("PutDataActionResult error: " + errorMessage);
return;
}
while (result == "Active")
{
//ждем в цикле 5 секунд - ожидаем окончания обработки данных на стороне конфигурации и спрашиваем снова
System.Console.WriteLine("PutDataActionResult: " + result);
System.Threading.Thread.Sleep(5000);
result = edDataUpload.PutDataActionResult(operationId, out errorMessage);
}
System.Console.WriteLine("PutDataActionResult: " + result);
System.Console.WriteLine("PutData2WebService finished");
}
else
{
System.Console.WriteLine("TestConnection failed: "+ errorMessage);
}
}
Вызов метода:
PutData2WebService("http://server/1c_app/ws/EnterpriseDataUpload_1_0_1_1",
"Administrator",
"pass@word",
@"c:\exchange\data.zip");
Принципиально ничем не отличается от реализации на C#.
Копировать в буфер обменаprivate static void putData2WebService(String serviceURL, String user, String password, String fileName)
{
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password.toCharArray());
}
});
URL url = null;
try {
url = new URL(serviceURL);
} catch (MalformedURLException e) {
e.printStackTrace();
}
EnterpriseDataUpload1011 ss;
ss = new EnterpriseDataUpload1011(url,
EnterpriseDataUpload1011.SERVICE);
EnterpriseDataUpload1011PortType port = ss.getEnterpriseDataUpload1011Soap12();
System.out.println("Invoking ping...");
port.ping();
System.out.println("Ping Ok");
Holder<String> outString = new Holder<String>();
Boolean testConnectionOk = port.testConnection(outString);
if (testConnectionOk)
{
//если передано непосредственно имя ZIP-архива - читаем одним куском
Boolean singleFile = fileName.toLowerCase().endsWith(".zip");
String fileId = UUID.randomUUID().toString();
String strResult;
if (singleFile)
{
Path path = Paths.get(fileName);
byte[] data;
try
{
data = Files.readAllBytes(path);
}
catch (Exception e)
{
System.err.println("Exception when open file " + path);
return;
}
//передаем 0 в качестве номера части файла
strResult = port.putFilePart(fileId, 0, data, outString);
if (!outString.value.isEmpty())
{
System.err.println("PutFilePart error: " + outString);
return;
}
}
else
{
//счетчик частей файла начинается с единицы
int partNumber = 1;
//считаем, что нам передали имя последовательности файлов (*.001, *.002 и т.д.) БЕЗ расширения и точки на конце
Path filePath = Paths.get(fileName + "." + String.format("%03d", partNumber));
while (Files.exists(filePath))
{
System.out.println("reading from " + filePath);
byte[] data;
try
{
data = Files.readAllBytes(filePath);
}
catch (Exception e)
{
System.err.println("Exception when open file " + filePath);
return;
}
strResult = port.putFilePart(fileId, partNumber, data, outString);
if (!outString.value.isEmpty())
{
System.err.println("PutFilePart error: " + outString.value);
return;
}
filePath = Paths.get(fileName + "." + String.format("%03d", ++partNumber));
}
}
Holder<String> operationId = new Holder<String>();
strResult = port.putData(fileId, operationId, outString);
if (!outString.value.isEmpty())
{
System.err.println("PutData error: " + outString.value);
return;
}
strResult = port.putDataActionResult(operationId.value, outString);
if (!outString.value.isEmpty())
{
System.err.println("putDataActionResult error: " + outString.value);
return;
}
else
{
System.out.println("putDataActionResult: " + strResult);
while ("Active".equals(strResult))
{
//ждем в цикле 5 секунд - ожидаем окончания обработки данных на стороне конфигурации и спрашиваем снова
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
//ignore
}
strResult = port.putDataActionResult(operationId.value, outString);
if (!outString.value.isEmpty())
{
System.err.println("putDataActionResult error: " + outString.value);
return;
}
System.out.println("putDataActionResult: " + strResult);
}
}
}
else
{
System.err.println("Connection failed, reason: " + outString.value);
}
}