Приложение на ТСД и связь с 1С: Предприятие 8.3 через HTTP-Сервис. Часть 2 (Реализация API на стороне 1С) +5


1. Выбор способа обмена. Описание API.

2. Реализация API на стороне 1С.


3. Приложение на ТСД и связь с 1С: Предприятие 8.3 через HTTP-Сервис. Часть 3(BroadcastReceiver. Получаем данные)

Пособий как создать HTTP-сервис в интернете предостаточно. Поэтому сразу опишу реализацию. Сервис у нас состоит из трех шаблонов URL:

  • Справочники — /v1/catalogs/{Справочник}/{Действие}
  • Документы — /v1/documents/{Документ}/{Действие}
  • Сервисы — /v1/services/{Действие}


Для того чтобы не раздувать статью, рассмотрим пример со справочником «Номенклатура». 1С нам не позволяет иметь разные пути для HTTP-методов По этому шаблон URL будет (/v1/catalogs/wares/{Действие}, для которого определены два HTTP метода GET, POST. В других шаблонах также будем использовать GET, POST.
  • /v1 — позволит нам в будущем поддерживать несколько вариантов API. Мы сможем добавить шаблон URL Справочники_v2 и соответственно путь начать с /v2
  • /catalogs/wares/catalogs объединяет все справочники. Это статичная часть пути. wares соответствует справочнику «Номенклатура». 1С поместит «wares» в ПараметрыURL. Тут надо отметить, что пути мы придумываем сами. И как их потом обрабатывать в 1С думаем тоже сами
  • {Действие} — сюда мы будем передавать что же мы хотим получить от 1С. На пример getByParam или getAll


С точки зрения Retrofit baseUrl будет выглядеть примерно так "http://192.168.0.1/unf/hs/inntsd/v1/" где:

  • 192.168.0.1 — Сервер где опубликован HTTP-сервис
  • /unf — имя базы данных 1С
  • /hs — статическая часть для HTTP-сервисов
  • /inntsd — корневой URL для всего сервиса. Обзываем как хотим


Рассмотрим процедуру получения запроса.

Функция СправочникиGet(Запрос)
//Разбираем параметры запроса. Заполнение структуры выполняется одинаково и для Get и для //Post  
	СтруктураЗапросаСправочник = ВернутьПараметрыЗапросаСправочник(Запрос, "GET");
	
//middleware. Проверим что пришли все необходимые данные. 
	РезультатПроверки = ПроверитьСтруктуруЗапроса(СтруктураЗапросаСправочник);
	
	Если РезультатПроверки Тогда
		СтруктураОтвета	= ОбработатьЗапросСправочник(СтруктураЗапросаСправочник);

Функция ВернутьПараметрыЗапросаСправочник(Запрос, МетодЗапроса) Экспорт
	
	Справочник		= ПолучитьИмяСправочника(Запрос); // получим переменную {Справочник}
	Действие			= ПолучитьДействиеСправочник(Запрос); // получим переменную {Действие}
	ПараметрыСтроки	= ПолучитьПараметрыИзСтроки(Запрос);  // получим список  из Параметров URL. В функции одна строка Возврат Запрос.ПараметрыURL
	ПараметрыТела	=ПолучитьПараметрыИзТела(Запрос); // Советую взглянуть на эту функцию поближе. Поможет понять почему в Firefox работает, а Google Chrome нет
	
	СтруктураЗапроса = Новый Структура;
	СтруктураЗапроса.Вставить("МетодЗапроса", МетодЗапроса);
	СтруктураЗапроса.Вставить("Действие", Действие);
	СтруктураЗапроса.Вставить("Справочник", Справочник);
	СтруктураЗапроса.Вставить("ПараметрыСтроки", ПараметрыСтроки);
	СтруктураЗапроса.Вставить("ПараметрыТела",	ПараметрыТела);
	
	Возврат СтруктураЗапроса;
	
КонецФункции

Функция ПолучитьПараметрыИзТела(Запрос)
	
	ТипСообщения = Запрос.Заголовки.Получить("Content-Type");
	
	СтруктураДанных = Новый Структура;
    СтруктураДанных.Вставить("ОшибкаЧтения", Ложь);

      //Разные браузеры по разному отдают "Content-Type","content-type", "Content-type"
	Если ТипСообщения = Неопределено тогда
		ТипСообщения = Запрос.Заголовки.Получить("content-type");
	КонецЕсли;

	Если ТипСообщения = Неопределено тогда
		ТипСообщения = Запрос.Заголовки.Получить("Сontent-type");
	КонецЕсли;
	
	Если ТипСообщения = Неопределено Тогда
		СтруктураДанных.Вставить("ОшибкаЧтения", Истина);
 		Возврат СтруктураДанных;
	КонецЕсли;
	
	Если Найти(Нрег(ТипСообщения), "multipart/form-data") > 0 тогда
		СтруктураДанных = ЗаполнитьСтруктуруДополнительныхПараметровИзТела(Запрос);
	ИначеЕсли Найти(Нрег(ТипСообщения), "application/json") > 0 тогда
		СтруктураДанных = ЗаполнитьСтруктуруДополнительныхПараметровJSON(Запрос);
	Иначе
		СтруктураДанных.Вставить("ОшибкаЧтения", Истина);
	КонецЕсли;
	
	Возврат СтруктураДанных;
	
КонецФункции

//С новыми методами для работы с JSON работать стало одно удовольствие. Даже с датами проблем нет.
Функция ЗаполнитьСтруктуруДополнительныхПараметровJSON(Запрос);
	ЧтениеJSON = Новый ЧтениеJSON;
	
	Данные = Запрос.ПолучитьТелоКакСтроку();
	
	ЧтениеJSON.УстановитьСтроку(Данные);
	Попытка
		СтруктураДанных = ПрочитатьJSON(ЧтениеJSON,,"sampleDate",ФорматДатыJSON.ISO);
		СтруктураДанных.Вставить("ОшибкаЧтения", Ложь);
	Исключение
		СтруктураДанных = Новый Структура;
		СтруктураДанных.Вставить("ОшибкаЧтения", Истина);
	КонецПопытки;	
	
	ЧтениеJSON.Закрыть();
	
	Возврат СтруктураДанных;
	
КонецФункции


В данном примере есть много вредных советов. Например ИмяСправочника, Действие необходимо переделать в перечисление или в новый справочник с соответствиями. Но как это элегантно реализовать в дополнении я еще не решил.

Разберем запрос getByParam. Полный адрес: /v1/catalogs/wares/getByParam?prop=byCode&comparison=similarly&searchString=239

Выясним с каким справочником мы работаем
Функция ОбработатьЗапросСправочник(СтруктураЗапроса)
	Если СтруктураЗапроса.Справочник = "Номенклатура" Тогда
		 СтруктураОтвета = ОбработатьЗапросНоменклатура(СтруктураЗапроса);
	ИначеЕсли СтруктураЗапроса.Справочник = "Склады" Тогда
		 СтруктураОтвета = ОбработатьЗапросСклады(СтруктураЗапроса)
	Иначе
		 СтруктураОтвета = ПодготовитьСтруктуруОтвета(100, "Не найден обработчик справочника " +  СтруктураЗапроса.Справочник);
	КонецЕсли;
	 
	Возврат СтруктураОтвета; 
КонецФункции


Получаем метод запроса. И проверяем что к методу прилагаются все параметры. Здесь надо отметить глубокую проверку я не делаю. Так как пишу и для 1С, и для Android.
Функция ОбработатьЗапросНоменклатура(СтруктураЗапроса)
	Если СтруктураЗапроса.Действие = "ПолучитьПоПараметрам" Тогда
		РеквизитСравнения = СтруктураЗапроса.ПараметрыСтроки.Получить("prop");
		ТипСравнения = СтруктураЗапроса.ПараметрыСтроки.Получить("comparison");
		СтрокаПоиска = СтруктураЗапроса.ПараметрыСтроки.Получить("searchString");
		
		
		Если (РеквизитСравнения = Неопределено) или (ТипСравнения = Неопределено) или (СтрокаПоиска = Неопределено) Тогда
			СтруктураОтвета = ПодготовитьСтруктуруОтвета(103, "Не заданы поля Реквизит сравнения и/или Тип сравнения или нет строки поиска");
			Возврат СтруктураОтвета;
		КонецЕсли;
		
		Данные  = НоменклатураВернутьЭлементыПоПараметрам(СтрокаПоиска, РеквизитСравнения, ТипСравнения);
		СтруктураОтвета = ПодготовитьСтруктуруОтвета(0, "");
		СтруктураОтвета.Вставить("payload", Данные);
		
	ИначеЕсли СтруктураЗапроса.Действие = "ПолучитьВсеЭлементы" Тогда 	
		Данные = НоменклатураВернутьВсеЭлементы();
		СтруктураОтвета = ПодготовитьСтруктуруОтвета(0, "");
		СтруктураОтвета.Вставить("payload", Данные);
			
	Иначе
		СтруктураОтвета = ПодготовитьСтруктуруОтвета("102", "Не наден обработчик для действия " + СтруктураЗапроса.Действие);
	КонецЕсли;
	
	Возврат СтруктураОтвета;
	
КонецФункции


Если все хорошо. Подготавливаем данные для ответа.
Функция НоменклатураВернутьЭлементыПоПараметрам(СтрокаПоиска, Реквизит, ТипСравнения)
			
	Запрос = Новый Запрос;
	
	ТекстЗапроса = 
	"ВЫБРАТЬ РАЗРЕШЕННЫЕ
	|	Номенклатура.Код КАК Код,
	|	Номенклатура.Артикул КАК Артикул,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.НаименованиеПолное КАК НаименованиеПолное,
	|	Номенклатура.ЕдиницаИзмерения.Наименование КАК ЕдиницаИзмеренияНаименование
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	НЕ Номенклатура.ПометкаУдаления
	|	И {Условие}";

		
	стрУсловие = "";
	
	Если НРег(Реквизит) = НРег("byCode") тогда
		стрУсловие = "Номенклатура.Код";
	ИначеЕсли НРег(Реквизит) = НРег("byArticle") тогда
		стрУсловие = "Номенклатура.Артикул";
	ИначеЕсли НРег(Реквизит) = НРег("byName") тогда
		стрУсловие = "Номенклатура.Наименование";
	КонецЕсли;
	
	Если НРег(ТипСравнения) = НРег("equally") тогда
		стрУсловие = стрУсловие +  " = &Параметр";
	ИначеЕсли НРег(ТипСравнения) = НРег("similarly") тогда
		стрУсловие = стрУсловие + " ПОДОБНО &Параметр";
	КонецЕсли;
	
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "{Условие}", стрУсловие);
		
		
	Запрос.Текст = ТекстЗапроса;
	
	Если НРег(ТипСравнения) = НРег("similarly") тогда
		СтрокаПоиска = "%" + СтрокаПоиска + "%";
	КонецЕсли;
	
	Запрос.УстановитьПараметр("Параметр", СтрокаПоиска); 	
		
	ТЗ = Запрос.Выполнить().Выгрузить();
		
	КолВоЭлементов = ТЗ.Количество();
	
	МассивЭлементов = Новый Массив;
	
	Для каждого СтрокаТЗ из ТЗ Цикл
		МассивЭлементов.Добавить(Новый Структура("code,article, name, fullName, unit", 
												СтрокаТЗ.Код, 
												СтрокаТЗ.Артикул,
												СтрокаТЗ.Наименование,
												СтрокаТЗ.НаименованиеПолное,
												СтрокаТЗ.ЕдиницаИзмеренияНаименование));			
	КонецЦикла;
	
	СтруктураДанных = Новый Структура;
	СтруктураДанных.Вставить("quantity", КолВоЭлементов);
	СтруктураДанных.Вставить("wares", МассивЭлементов);
	Возврат СтруктураДанных;
	
КонецФункции


И наконец объединяем все вместе. Помещаем это в JSON и отправляем ответ.
СтруктураОтвета = ПодготовитьСтруктуруОтвета(0, "");
СтруктураОтвета.Вставить("payload", Данные);
Возврат СтруктураОтвета;

Функция ПодготовитьОтвет(КодОтвета, СтруктураОтвета) Экспорт
	
	ТелоОтвета = ПолучитьСтрокуТелаОтвета(СтруктураОтвета);
	
	HTTPОтвет = Новый HTTPСервисОтвет(КодОтвета);
	HTTPОтвет.Заголовки["Content-Type"] = "application/json; charset=utf-8"; //сообщаем что это json UTF-8
	// ToDo CORS
	//HTTPОтвет.Заголовки["Access-Control-Allow-Origin"] = Оригин;
	//HTTPОтвет.Заголовки["Access-Control-Allow-Credentials"] = "true";
	HTTPОтвет.УстановитьТелоИзСтроки(ТелоОтвета);
	
	Возврат HTTPОтвет;
	
КонецФункции

Функция ПолучитьСтрокуТелаОтвета(СтруктураТела)
	
	ПараметрыJSON = Новый ПараметрыЗаписиJSON(, Символы.Таб,,,,,,,);
	
	ТелоОтвета = новый ЗаписьJSON;
	ТелоОтвета.ПроверятьСтруктуру = Ложь;
	ТелоОтвета.УстановитьСтроку(ПараметрыJSON);
	
	НастройкиСериализации = Новый НастройкиСериализацииJSON;
	НастройкиСериализации.ВариантЗаписиДаты = ВариантЗаписиДатыJSON.ЛокальнаяДата;
	НастройкиСериализации.ФорматСериализацииДаты = ФорматДатыJSON.ISO;
	
	ЗаписатьJSON(ТелоОтвета, СтруктураТела, НастройкиСериализации);
	
	СтрокаТелаОтвета = ТелоОтвета.Закрыть();

	Возврат СтрокаТелаОтвета;
	
КонецФункции


И отдаем это все на клиента.
	
Ответ = ПодготовитьОтвет(200, СтруктураОтвета)
Возврат Ответ;


Вне зависимости что мы получили от 1С. Всегда стараемся ответить правильно.
Функция ПодготовитьСтруктуруОтвета(КодОшибки, ТекстОшибки)
	СтруктураОтвета = Новый Структура;
	СтруктураРезультата = Новый Структура();
	СтруктураРезультата.Вставить("code", КодОшибки);
	СтруктураРезультата.Вставить("description", ТекстОшибки);
	СтруктураОтвета.Вставить("result", СтруктураРезультата);
	СтруктураОтвета.Вставить("payload", Новый Структура);
	
	Возврат СтруктураОтвета;
КонецФункции


На этом все. Все остальные методы реализованы похожим способом. Задавайте вопросы.С радостью отвечу.




К сожалению, не доступен сервер mySQL