Работаем с веб-сервисом 1С из приложения на Android

в 6:33, , рубрики: , web-service, веб-сервис, Разработка под android, метки: , ,

При работе над фронтом для кафе появилась задача обращаться к веб-сервису 1С из приложения, разрабатываемого на Android. Google мне дал несколько ответов на тему как вообще работать с SOAP, используя библиотеку ksoap2-android. Они помогли в передаче простых типов, но когда дело дошло до передачи массива, пришлось немного подумать.

Веб-сервис на стороне 1С

В конфигурации 1С создан веб-сервис с методом WriteSale. Метод принимает несколько параметров, один из которых, items, имеет тип ItemsSold (задан в пакете XDTO конфигурации). Остальные параметры имеют простые типы (string, datetime). Скрин конфигурации:

Работаем с веб сервисом 1С из приложения на Android

Тип ItemsSold имеет единственное свойство Items, для которого установлено свойство «Максимальное количество» в -1, указывая на то, что это массив. Тип этого свойства — ItemSold. Скрин:

Работаем с веб сервисом 1С из приложения на Android

У типа ItemSold все свойства простого типа. Метод WriteSale веб-сервиса имеет следующий код:

Функция WriteSale(id, date, clientCardNumber, discountRate, items, deptId, bonuses, premiumBonuses)
	Текст = "OK";
	Попытка
		Карточка = ПолучитьКартуПоНомеру(clientCardNumber);
		ПроцентСкидки = Число(discountRate);
		Подразделение = НайтиПодразделениеПоПрефиксу(deptId);
		//...
		
		Документ = НайтиДокументПоКодуДате(date, Число(id), Подразделение);
		Если Не ЗначениеЗаполнено(Документ) Тогда
			Объект = Документы.ПолныйЧек.СоздатьДокумент();
			Объект.Дата = date;
			Объект.КафеНомерДокумента = id;
		Иначе
			Объект = Документ.ПолучитьОбъект();
		КонецЕсли;
		
		Объект.ОплатаБонусами = Число(bonuses);
		//...
		Объект.Продажи.Очистить();
		// Здесь идет использование массива
		Для Каждого item Из items.Items Цикл
			Номенклатура = НайтиНоменклатуруПоКоду(item.Code);
			Строка = Объект.Продажи.Добавить();
			Строка.Количество = Число(item.Quantity);
			//...
		КонецЦикла;
		
		Объект.Записать(РежимЗаписиДокумента.Проведение);
		
	Исключение
		Текст = ОписаниеОшибки();
		ЗаписьЖурналаРегистрации("Cafe.WriteSale - исключение: " + Текст, УровеньЖурналаРегистрации.Ошибка);
		ВызватьИсключение;
	КонецПопытки;
	// Возвращаем текст ошибки или "ОК"
	Возврат Текст;
КонецФункции

Клиент на стороне Android

Для обращения к веб-сервису из приложения Android написал следующий код (в соответствии с хорошим примером простого клиента):

protected String call() throws Exception {
            result = null;
            HttpTransportSE httpTransport = new HttpTransportSE(uri);
            httpTransport.debug = true;
            String resultString;

            SoapObject request = new SoapObject(namespace, methodName);
            request.addProperty("id", sale.getId());
            SimpleDateFormat dateFormat = new SimpleDateFormat(
                    "yyyy-MM-dd'T'HH:mm:ss");
            request.addProperty("date", dateFormat.format(sale.getDate()));
            request.addProperty("clientCardNumber", sale.getCardNumber());
            request.addProperty("bonuses", Double.toString(sale.getBonuses()));
            //...

            // see - http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks#Adding_an_array_of_complex_objects_to_the_request
            SoapObject sales = new SoapObject(namespace, "items");
            for (SaleItemInformation item : sale.getSales()) {
                SoapObject itemSoap = new SoapObject(namespace,
                        "Items");
                itemSoap.addProperty("Code", item.getItem().getSourceCode());
                itemSoap.addProperty("Quantity",
                        Double.toString(item.getQuantity()));
                //...
                sales.addSoapObject(itemSoap);
            }
            request.addSoapObject(sales);
            SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
                    SoapEnvelope.VER11);
            // Тоже важный элемент - не выводит типы данных в элементах xml
            envelope.implicitTypes = true;
            envelope.setOutputSoapObject(request);
            try {
                httpTransport.call(soapAction, envelope);
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            }
            resultString = envelope.getResponse().toString();
            return resultString;
        }

Вроде бы код выглядит правильно, формирует красивый xml-запрос:

<v:Envelope xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d="http://www.w3.org/2001/XMLSchema" xmlns:c="http://schemas.xmlsoap.org/soap/encoding/" xmlns:v="http://schemas.xmlsoap.org/soap/envelope/">
<v:Header />
<v:Body>
	<n0:WriteSale id="o0" c:root="1" xmlns:n0="http://www.xxxxx.ru">
		<date i:type="d:string">Thu May 31 16:13:08 YEKST 2012</date>
		<clientCardNumber i:type="d:string">120</clientCardNumber>
		<discountRate i:type="d:string">5.0</discountRate>
		<id i:type="d:long">11</id>
		<n0:items i:type="n0:items">
			<n0:Items i:type="n0:Items">
				<Code i:type="d:string">3000</Code>
				<Price i:type="d:string">100.0</Price>
				<Quantity i:type="d:string">2.0</Quantity>
				<Sum i:type="d:string">200.0</Sum>
			</n0:Items>
			<n0:Items i:type="n0:Items">
				<Code i:type="d:string">3001</Code>
				<Price i:type="d:string">110.0</Price>
				<Quantity i:type="d:string">1.0</Quantity>
				<Sum i:type="d:string">110.0</Sum>
			</n0:Items>
		</n0:items>
	</n0:WriteSale>
</v:Body>
</v:Envelope>

Но веб-сервис отвечает на него 500-й ошибкой. При этом, обращаясь к другому методу с параметрами простого типа на том же веб-сервисе, мы получаем корректный ответ. Более того, обращаясь из другой базы 1С через WS-ссылка к приведенному выше методу веб-сервиса, мы получаем корректный ответ и выполнение необходимых действий на стороне веб-сервиса. Поэтому пришлось перехватить запрос, формируемый другой базой 1С. Сделать это фидлером не получилось, так как он каким-то образом обрезал само тело запроса с xml и не передавал его веб-сервису. Нормально перехватить запрос удалось только с помощью WireShark. Итак, текст запроса от 1С:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Header/>
	<soap:Body> <m:WriteSale xmlns:m="http://www.xxxxx.ru">
	<m:id xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">1</m:id>
	<m:date xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2</m:date>
	<m:clientCardNumber xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3</m:clientCardNumber>
	<m:discountRate xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">4</m:discountRate>
	<m:items xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
		<m:Items>
			<m:Code>123</m:Code>
			<m:Price>12.2</m:Price>
			<m:Quantity>2</m:Quantity>
			<m:Sum>2</m:Sum>
		</m:Items>
		<m:Items>
			<m:Code>2</m:Code>
			<m:Price>1</m:Price>
			<m:Quantity>2</m:Quantity>
			<m:Sum>2</m:Sum>
		</m:Items>
	</m:items>
</m:WriteSale></soap:Body>
</soap:Envelope>

Несложно заметить, что для вложенных элементов массивов (Code, Price...) библиотека ksoap2-android не проставляет префиксы с пространством имен. Для корневых элементов (id, date...) они также не проставлены, но этот факт 1С в ступор не вводит. А их отсутствие у под-элементов заставляет программу усомниться в корректности входных данных, прочитать она их не может.

Изучив код библиотеки, решил, что наиболее рациональным будет модифицировать метод SoapObject#addProperty(String, Object) следующим образом:

public static class SoapObjectCustom extends SoapObject {

        public SoapObjectCustom(String namespace, String name) {
            super(namespace, name);
        }

        @Override
        public SoapObject addProperty(String name, Object value) {
            PropertyInfo propertyInfo = new PropertyInfo();
            propertyInfo.name = name;
            propertyInfo.type = value == null ? PropertyInfo.OBJECT_CLASS
                    : value.getClass();
            propertyInfo.setValue(value);

            // Добавил эту строку
            propertyInfo.setNamespace(this.namespace);

            return addProperty(propertyInfo);
        }
    }

В исходном коде я заменил объекты SoapObject на SoapObjectCustom в следующих местах:

//...
SoapObjectCustom request = new SoapObjectCustom(namespace, methodName);
//...
SoapObject sales = new SoapObject(namespace, "items");
for (SaleItemInformation item : sale.getSales()) {
    SoapObjectCustom itemSoap = new SoapObjectCustom(namespace,
            "Items");
    //...
}
//...

Заключение

Скорее всего, есть смысл в том, что авторы не включали префиксы пространства имен в свойства элементов. И вполне возможно, что в работе с другими веб-сервисами такие коррективы приведут к некорректному поведению программы. Тем не менее, данный метод работает с веб-сервисами 1С, надеюсь это описание кому-нибудь поможет в работе.

Описанное выше было протестировано с 1С v.8.2.15.294 и Android 12 (3.0).

Автор: ssidelnikov

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js