Эта часть появилась после того, как я начал полноценную реализацию. После боевой эксплуатации, появилась потребность изменить структуру обмена данными. Оказалось очень тяжело вводить ТСД в работу. В качестве ПО была выбрана «1С мобильная платформа», т.к. Kotlin давался очень тяжело. В этой части я расскажу о том как был реализован обмен изначально, и предоставлю код на примере одного справочника как на стороне сервера, так и на стороне клиента.

1. Общее описание алгоритма обмена.


На стороне сервера используются планы обмена с автоматической регистрацией изменений. Практика показала, что это наименее затратный способ с точки зрения программирования. На стороне ТСД в каждом справочнике, документе был добавлен реквизит ПризнакОбновления, в регистрах добавлен реквизит с таким же наименованием.

Сам обмен теперь разнесен на две стадии.

  1. Получаем новые данные, фиксируем признак обновления;
  2. Отправляем подтверждение, что данные получены. При положительном ответе, убираем признак обновления;

Такая схема появилась после того, как ТСД начали выходить из зоны действия WiFi. Часто получалась ситуация, что данные пришли, а ответ не отправлен.

Для облегчения ввода ТСД в работу появился справочник с предопределенными значениями, для каждого Узла Обмена(Код ТСД).

2. Как было реализовано до.


Запрос инициировал ТСД. Каждые 15 секунд шел запрос на новые данные, по каждому справочнику, документу, регистру. Данные забираются пачками по 100 элементов. Что накладывало свои ограничения. Если выполнить регистрацию для всего узла, то данные грузились порядка 30 минут. Т.к. в текущей реализации не был предусмотрен механизм оповещения ТСД о наличии еще одной пачки.

Кусок кода 1С на ТСД
Процедура ФоновоеПолучитьНовыеДанные() Экспорт
	
	СетевыеФункцииОКЕИ.ПолучитьНовыеДанныеОКЕИ();
	СетевыеФункцииОКЕИ.ЗафиксироватьНовыеДанныеОКЕИ();

	СетевыеФункцииСклады.ПолучитьНовыеДанныеСклады();
	СетевыеФункцииСклады.ЗафиксироватьНовыеДанныеСклады();
			
	СетевыеФункцииКонтрагенты.ПолучитьНовыеДанныеКонтрагенты();
	СетевыеФункцииКонтрагенты.ЗафиксироватьНовыеДанныеКонтрагенты();
	
	СетевыеФункцииСотрудники.ПолучитьНовыеДанныеСотрудники();
	СетевыеФункцииСотрудники.ЗафиксироватьНовыеДанныеСотрудники();
	
	СетевыеФункцииНоменклатура.ПолучитьНовыеДанныеНоменклатура();
	СетевыеФункцииНоменклатура.ЗафиксироватьНовыеДанныеНоменклатура();
КонецПроцедуры


После того как количество справочников стало больше пяти, вырисовалась печальная картина: Каждое действие на стороне сервера однотипно. Запрос данных разный. Но вот фиксация изменений для справочников одна и та же. Разберем на примере ОКЕИ. Если вкратце, то

  • Подготавливаем соединение, берем все из констант.
  • Подготавливаем запрос. Каждый справочник лежит в своем маршруте. И далее следует команда с параметрами. примерно вот так:
    localhost/ws/okeis/getNew&node=КодТСД.
  • Подготавливаем структуру ответа
  • Получаем ответ и заполняем структуру в зависимости от HTTP кода ответа.
  • При успехе разворачиваем JSON в структуры 1С.
  • Начинаем транзакцию. Фиксируем у справочника изменения. Фиксируем транзакцию
  • Вызываем функцию фиксации изменений на стороне сервера
  • И вот фиксация, как раз, на стороне сервера однотипна

Первоначально под каждый справочник была своя процедура фиксации, теперь это выглядит вот так.

Много 1С кода
На ТСД
	
Процедура ПолучитьНовыеДанныеОКЕИ() Экспорт
HTTPСоединение = СетевыеФункцииВызовСервера.ВернутьHTTPСоединение();
	HTTPЗапрос = СетевыеФункцииВызовСервера.ВернутьHTTPЗапрос("okeis", "getAll?node="+СокрЛП(Константы.КодУзлаОбмена.Получить()));
	
	Результат = HTTPСоединение.Получить(HTTPЗапрос);
	
	СтруктураОтвета = Новый Структура;
	СтруктураОтвета.Вставить("КодОтвета", Результат.КодСостояния);
	СтруктураОтвета.Вставить("Тело", Результат.ПолучитьТелоКакСтроку());

	Если СтруктураОтвета.КодОтвета = 200 тогда
		СтрокаРезультата = СтруктураОтвета.Тело;
		СтруктураРезультата = ОбщегоНазначенияВызовСервера.ВернутьСтруктуруJSON(СтрокаРезультата);
		Если НЕ СтруктураРезультата.Свойство("Ошибка") тогда
			НачатьТранзакцию();
			БылиОшибки = Ложь;
			Для каждого мЭлемент из СтруктураРезультата.okeis Цикл
				Код = мЭлемент.code;
				Наименование = мЭлемент.name;
				НаименованиеПолное = мЭлемент.fullName;
				МеждународноеСокращение = мЭлемент.internationalReduction;
				
				Ссылка = Справочники.ЕдиницыОКЕИ.НайтиПоКоду(Код);
				
				Если ЗначениеЗаполнено(Ссылка) Тогда
					мОбъект = Ссылка.ПолучитьОбъект();
				Иначе
					мОбъект = Справочники.ЕдиницыОКЕИ.СоздатьЭлемент();
				КонецЕсли;
				
				мОбъект.Код = Код;
				мОбъект.Наименование = Наименование;
				мОбъект.НаименованиеПолное = НаименованиеПолное;
				мОбъект.МеждународноеСокращение = МеждународноеСокращение;
				мОбъект.ПризнакОбновления = Истина;
				
				Попытка
					мОбъект.Записать();
				Исключение
					БылиОшибки = Истина;
					Прервать;
				КонецПопытки;
				
			КонецЦикла;
			
			Если НЕ БылиОшибки Тогда
				ЗафиксироватьТранзакцию();
			Иначе
				ОтменитьТранзакцию();
			КонецЕсли;
		КонецЕсли;			
	КонецЕсли;

КонецПроцедуры

На Сервере

Функция ОКЕИget(Запрос)
	
	Если Запрос.ПараметрыURL.Получить("Команда") = "getAll" Тогда
		
		СоздатьЗаписьЖурналаРегистрации(0,"Начало получения ОКЕИ");
		
		КодУзла = Запрос.ПараметрыЗапроса.Получить("node");
		
		Структура = ВернутьСтруктуруОКЕИИзмененныеДляУзла(КодУзла);
		Ответ = ПодготовитьОтветHTTP(200, Структура);
		
		СоздатьЗаписьЖурналаРегистрации(0,"Окончание получения ОКЕИ");
		Возврат Ответ;
	КонецЕсли;

	Ответ = ПодготовитьОтветHTTP(400, Новый Структура("Ошибка,Описание", Истина, "Плохой запрос"));
	Возврат Ответ;

КонецФункции

На ТСД

Процедура ЗафиксироватьНовыеДанныеОКЕИ() Экспорт
	
	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
	               |	ЕдиницыОКЕИ.Ссылка КАК Ссылка
	               |ИЗ
	               |	Справочник.ЕдиницыОКЕИ КАК ЕдиницыОКЕИ
	               |ГДЕ
	               |	ЕдиницыОКЕИ.ПризнакОбновления";
	
	Результат = Запрос.Выполнить();
	Выборка = Результат.Выбрать();
	
	МассивКодов = Новый Массив;
	
	Пока Выборка.Следующий() Цикл
		МассивКодов.Добавить(Новый Структура("code", Выборка.Ссылка.Код));
	КонецЦикла;
	
	мСтруктура = Новый Структура;
	мСтруктура.Вставить("okeis", МассивКодов);
	
	
	HTTPСоединение = СетевыеФункцииВызовСервера.ВернутьHTTPСоединение();
	HTTPЗапрос = СетевыеФункцииВызовСервера.ВернутьHTTPЗапрос("okeis", "dataObtained?node="+СокрЛП(Константы.КодУзлаОбмена.Получить()));
	
	СтрокаЗапроса = ОбщегоНазначенияВызовСервера.ПолучитьСтрокуТелаОтвета(мСтруктура);
	
	HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапроса);
	
	Ответ = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
	
	Если Ответ.КодСостояния = 200 тогда
		НачатьТранзакцию();
		ЕстьОшибки = Ложь;
				
		Для Каждого мКод из МассивКодов Цикл 
			мОбъект = Справочники.ЕдиницыОКЕИ.НайтиПоКоду(мКод.code).ПолучитьОбъект();
			мОбъект.ПризнакОбновления = Ложь;
			Попытка
				мОбъект.Записать();
			Исключение
				ЕстьОшибки = Истина;
				Прервать;
			КонецПопытки;
				
		КонецЦикла;
		
		Если Не ЕстьОшибки Тогда
			ЗафиксироватьТранзакцию();
		Иначе
			ОтменитьТранзакцию();
		КонецЕсли;
		
	КонецЕсли;
	
КонецПроцедуры

На Сервере

Функция ОКЕИpost(Запрос)
	
	Команда = Запрос.ПараметрыURL.Получить("Команда");

	Если Команда = "dataObtained" Тогда
		
		КодУзла = Запрос.ПараметрыЗапроса.Получить("node");
		ИмяСтруктуры = Запрос.ПараметрыЗапроса.Получить("nameStructure");

		Данные = Запрос.ПолучитьТелоКакСтроку();
		СтруктураЭлементов = ВернутьСтруктуруИзJSON(Данные);
		
		Успех =  УдалитьРегистрациюСправочникиИзмененныеДляУзла("КлассификаторЕдиницИзмерения", КодУзла, СтруктураЭлементов, ИмяСтруктуры);
		
		Если Успех Тогда
			Ответ = ПодготовитьОтветHTTP(200, Новый Структура("code", 0));
		Иначе
			Ответ = ПодготовитьОтветHTTP(400, Новый Структура("code", 100));
		КонецЕсли;
		
		Возврат Ответ;
	КонецЕсли;
	 
	Ответ = ПодготовитьОтветHTTP(400, Новый Структура("Ошибка,Описание", Истина, "Плохой запрос"));
	Возврат Ответ;
КонецФункции

Тут надо выделить вот эту функцию УдалитьРегистрациюСправочникиИзмененныеДляУзла

Функция УдалитьРегистрациюСправочникиИзмененныеДляУзла(ИмяСправочника, КодУзла, СтруктураЭлементов, ИмяСтруктуры)
	
	Узел = ПолучитьУзелОбменаПоКоду(КодУзла);
	
	НачатьТранзакцию();
	ЕстьОшибки = Ложь;
	Для каждого мЭлемент из СтруктураЭлементов[ИмяСтруктуры] Цикл
		
		Ссылка = Справочники[ИмяСправочника].НайтиПоКоду(мЭлемент.code);
		
		Если ЗначениеЗаполнено(Ссылка) тогда
			Попытка
				ПланыОбмена.УдалитьРегистрациюИзменений(Узел,Ссылка);
			Исключение
				ЕстьОшибки = Истина;
				Прервать;
			КонецПопытки;	
		КонецЕсли;
	КонецЦикла;
	
	Если ЕстьОшибки Тогда
		ОтменитьТранзакцию();
		Возврат Ложь;
	Иначе
		ЗафиксироватьТранзакцию();
		Возврат Истина;
	КонецЕсли;
	
	
КонецФункции


В этой статье рассмотрена реализация в лоб. Не делайте так, если только не требуется быстро собрать прототип, и проверить на боевых условиях.

Если статья понравится, в следующей расскажу как реализовано сейчас.