gifts2017

Обмен данными онлайн через Веб-сервис с использованием обработки "Универсальный обмен данными XML"

Опубликовал Сергей Линков (linkov) в раздел Обмен - Обмен через XML

Доработка позволяет организовать двухсторонний обмен данными в режиме реального времени между любыми конфигурациями (типовые, самописные, обычные формы, управляемые формы). Также предлагается вариант в виде расширения конфигурации (.cfe) для конфигураций стоящих на поддержке без возможности изменения. В обмене может участвовать любое количество баз. Правила обмена подготавливаются с использованием конфигурации "Конвертация данных".

К слову, в БСП 2.3 уже реализован универсальный обмен данными через Веб-сервис, но для его внедрения необходимо включение в конфигурацию всех сопутствующих подсистем БСП, что не всегда представляется возможным. Особенно, если текущая конфигурация на обычных формах (БСП 2.3 ориентировано на управляемые формы).

В представленных алгоритмах были проработаны следующие вопросы:

1. Запуск синхронизации производится в реальном режиме времени при записи (удалении) объекта (документа, справочника), но только если запись выполняется интерактивно. То есть, при пакетном перепроведении документов при восстановлении последовательности или из обработки "Групповая обработка справочников и документов" синхронизация не требуется (предполагается, что движения документов не переносятся, а формируются при проведении).

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

3. Для исключения задержек при записи объектов, синхронизация запускается асинхронно в фоновом задании. Если же требуется организовать отказ от проведения документа в текущей базе в зависимости от успешности проведения этого документа в базе приемнике, имеется возможность предусмотреть запуск синхронизации в основном потоке (по опыту, документ реализация товаров в УПП имеющий в табличной части до 10 строчек синхронизируется за 1-2 сек), в этом случае производится ожидание завершения синхронизации и в зависимости от успешности проведения выставляется отказ и дублируется сообщение о причине неуспешности, переданное из базы приемника.

4. Для универсальности данные передаются в базу приемник в формате XML, сформированными программно с помощью обработки "Универсальный обмен данными XML" по правилам обмена, подготовленными в конфигурации "Конвертация данных". Соответственно, на стороне приемника производится загрузка этой же обработкой. Как правило, во всех типовых конфигурациях данная обработка присутствует, а для самописных можно загрузить из состава .cf данной публикации.

5. Пакет передаваемых данных должен иметь минимальный объем данных, Чтобы не загружать сетевые службы и Веб-сервер. Для этих целей, предусмотрено:

- за один раз производится выгрузка только одного объекта (с подчиненными объектами, выгруженными по ссылкам, если это справочник);

- при разработке правил обмена для всех документов устанавливается режим "Не выгружать объекты свойств источника по ссылкам". В этом случае исключается избыточность передаваемых данных. Если же в реквизите встречается ссылка на элемент объекта, который не включен в состав регистрируемых объектов плана обмена (то есть данный вид объектов самостоятельно не синхронизируется) и эта ссылка не была найдена в базе приемнике (в терминах обработки это фиктивная ссылка), то формируется список фиктивных ссылок, который возвращается в базу источник и рекурсивно инициируется синхронизация для этих объектов.

6. Как следствие из предыдущего пункта, проведение документа в базе приемнике реализовано отдельным запросом после полной передачи всего пакета вместе с рекурсивной синхронизацией фиктивных ссылок по предыдущему пункту. Так как для документов отключена выгрузка объектов свойств источника по ссылкам, то на момент первичной записи документа в нем могут присутствовать фиктивные ссылки, из за которых нормальное проведение документа в этот момент невозможно. 

Реализация

1. Для всех синхронизируемых объектов в форме элемента справочника (форме документа) в обработчике "При открытии" необходимо добавить следующий код:
Процедура ПриОткрытии()
	
	ДополнительныеСвойства.Вставить("СинхронизироватьПриЗаписи");
	
КонецПроцедуры
2. Добавляем подписки на события

ПодпискаОбъектПриЗаписи

ПодпискаОбъектПередУдалением

3. Общий модуль "ЛУ_СинхронизацияКлиентСервер" (Сервер, Клиент Обычное приложение) - обработчики подписок
Процедура ОбъектПриЗаписиСинхронизировать(Источник, Отказ) Экспорт
	
	ПланОбменаРегистрацияИзменений(ПланыОбмена.ЛУ_WebСервис, Источник, Отказ);
	
КонецПроцедуры

Процедура ОбъектПередУдалениемСинхронизировать(Источник, Отказ) Экспорт
	
	ПланОбменаРегистрацияИзменений(ПланыОбмена.ЛУ_WebСервис, Источник, Отказ, Истина);
	
КонецПроцедуры

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

	// Регистрация изменениий в плане обмена
	ВыборкаУзлов = ПланОбмена.Выбрать();
	Пока ВыборкаУзлов.Следующий() Цикл
		
		ТекущийУзел = ВыборкаУзлов.Ссылка;
		Если ТекущийУзел = ПланОбмена.ЭтотУзел() Тогда
			Продолжить;
		КонецЕсли;
		
		Если НЕ ТекущийУзел.Синхронизировать Тогда 
			Продолжить;
		КонецЕсли;
		
		ОбъектРегистрации = ?(Удаление, Новый УдалениеОбъекта(Источник.Ссылка), Источник.Ссылка);
		ПланыОбмена.ЗарегистрироватьИзменения(ТекущийУзел, ОбъектРегистрации);
		
	КонецЦикла;
	
	ЛУ_СинхронизацияВызовСервера.ПланОбменаСинхронизироватьАсинхронно(ПланОбменаМетаданные.Имя);

КонецПроцедуры
4. План обмена ЛУ_WebСервис

Структура плана обмена

Здесь:

  • Синхронизировать - булево,
  • Сервер, База, Пользователь, Пароль - Строка, 100, переменная
  • ПравилаКонвертации - ХранилищеЗначения (в форме узла реализована загрузка в этот реквизит правил обмена из файла XML)
5. Общий модуль "ЛУ_СинхронизацияВызовСервера" (Сервер, Вызов сервера)
(для наглядности из листингов исключены все конструкции связанные с обработкой исключений, вывода ошибок, и вспомогательные методы)
Процедура ПланОбменаСинхронизировать(ИмяПланаОбмена) Экспорт
	
	ПланОбмена = ПланыОбмена[ИмяПланаОбмена];
	
	ВыборкаУзлов = ПланОбмена.Выбрать();
	Пока ВыборкаУзлов.Следующий() Цикл
		
		ТекущийУзел = ВыборкаУзлов.Ссылка;
		Если ТекущийУзел = ПланОбмена.ЭтотУзел() Тогда
			Продолжить;
		КонецЕсли;
		
		Если НЕ ТекущийУзел.Синхронизировать Тогда 
			Продолжить;
		КонецЕсли;
		
		ИмяСобытияДляЖурнала = "ПланОбмена." + ИмяПланаОбмена + " " + ПланОбмена.ЭтотУзел().Код + " -> " + ТекущийУзел.Код;
		
		ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
		ЗаписьСообщенияXML = Новый ЗаписьXML;
		ЗаписьСообщенияXML.УстановитьСтроку();
		
		Попытка
			ЗаписьСообщения.НачатьЗапись(ЗаписьСообщенияXML, ТекущийУзел);
		Исключение
			// ТекущийУзел заблокирован, пока пропустим, следующая попытка будет по регламентному заданию через 60 сек
			Продолжить;	
		КонецПопытки;
		
		НомерСообщения = ЗаписьСообщения.НомерСообщения;
		МетаданныеДокументы = Метаданные.Документы;
		МассивУдаляемыхОбъектов = Новый Массив;
		МассивОбъектов = Новый Массив;
		
		Выборка = ПланыОбмена.ВыбратьИзменения(ТекущийУзел, НомерСообщения);
		Пока Выборка.Следующий() Цикл
			// В этом цикле только подготовим массив объектов, содержащихся в сообщении плана обмена
			// Чтобы побыстрее освободить ЗаписьСообщения
			// (до вызова метода ЗакончитьЗапись() в плане обмена устанавливается исключительная блокировка)
			Объект = Выборка.Получить();
			Если ТипЗнч(Объект) = Тип("УдалениеОбъекта") Тогда
				МассивУдаляемыхОбъектов.Добавить(Объект);
			ИначеЕсли НЕ МетаданныеДокументы.Содержит(Объект.Ссылка.Метаданные()) Тогда
				МассивОбъектов.Вставить(0, Объект.Ссылка);  // Документы сдвигаем в конец массива
			Иначе
				МассивОбъектов.Добавить(Объект.Ссылка);
			КонецЕсли;
		КонецЦикла;
		
		Если МассивОбъектов.Количество() = 0 и МассивУдаляемыхОбъектов.Количество() = 0 Тогда
			ЗаписьСообщения.ПрерватьЗапись(); // Освобождаем номер сообщения плана обмена
			Продолжить;
		КонецЕсли;
		ЗаписьСообщения.ЗакончитьЗапись(); // Фиксируем исходящее сообщение плана обмена данных
		// Полученный XML сообщения игнорируем. 
		// Так как в базе приемнике фиксация номеров входящих сообщений плана обмена не предусмотрена 
		// (В приемнике вообще может не быть корреспондирующего плана обмена при одностороннем обмене).
		ЗаписьСообщенияXML.Закрыть(); 
		
		// Передача измененных объектов в базу приемник
		Отказ = Ложь;
		ОбработкаОбмена = ИнициализироватьОбработкуОбмена(ТекущийУзел, ИмяСобытияДляЖурнала, Отказ);

		ФайлОбмена = Новый Файл(ОбработкаОбмена.ИмяФайлаОбмена);
		
		// Подключение
		LuExchange = ПолучитьWSПрокси(ТекущийУзел, "LuExchange.1cws", "http://www.LuExchange.ru", "LuExchange");
		
		// Удаляемые объекты // Поиск удаляемого объекта выполняется только по ссылке
		ВыгрузитьУдаляемыеОбъектыВБазуПриемник(ТекущийУзел, МассивУдаляемыхОбъектов, LuExchange, ОбработкаОбмена, ФайлОбмена, ИмяСобытияДляЖурнала, Отказ);
		
		// Измененные объекты
		Для каждого ОбъектСсылка Из МассивОбъектов Цикл
			
			ОшибкаВыгрузкиОбъекта = Ложь;
			СтрокаЗагруженныеДокументы = ""; // Массив загруженных документов в базе приемнике (строка внутренняя) для инициации последующего проведения
			ВыгрузитьОбъектВБазуПриемник(ОбъектСсылка, LuExchange, ОбработкаОбмена, ФайлОбмена, ИмяСобытияДляЖурнала, СтрокаЗагруженныеДокументы, ОшибкаВыгрузкиОбъекта);

			Если ОшибкаВыгрузкиОбъекта Тогда
				Отказ = Истина;
				Продолжить;	
			КонецЕсли;
			
			// Для выгруженных документов необходимо выполнить команду проведения или, если документ был распроведен, удалить движения
			ОшибкаПроведенияДокумента = Ложь;
			ОбработкаПризнакаПроведенияДокумента(СтрокаЗагруженныеДокументы, LuExchange, ИмяСобытияДляЖурнала, ОшибкаПроведенияДокумента);
				
			// Фиксируем успешную синхронизацию в плане обмена для объекта
			ПланыОбмена.УдалитьРегистрациюИзменений(ТекущийУзел, ОбъектСсылка);	
			
		КонецЦикла;
		
		УдалитьФайлы(ОбработкаОбмена.ИмяФайлаОбмена);
		
		Если НЕ Отказ Тогда
			// Фиксируем успешную синхронизацию в плане обмена для всего сообщения
			ПланыОбмена.УдалитьРегистрациюИзменений(ТекущийУзел, НомерСообщения);	
		КонецЕсли;
		
	КонецЦикла;
	
КонецПроцедуры
Процедура ВыгрузитьОбъектВБазуПриемник(ОбъектСсылка, LuExchange, ОбработкаОбмена, ФайлОбмена, ИмяСобытияДляЖурнала, СтрокаЗагруженныеДокументы="", Отказ)
	
	// Получаем ДанныеXML
	ВыгруженоОбъектов = 0; 
	ДанныеXML = ПолучитьДанныеXMLПоПравиламКонвертации(ОбработкаОбмена, ОбъектСсылка, ВыгруженоОбъектов, ИмяСобытияДляЖурнала);
	
	// ExecuteExchange
	ТекстОшибки = "";
	ОшибкаВыгрузки = Ложь;
	СтрокаФиктивныеСсылки = "";
	Данные = Новый ХранилищеЗначения(ДанныеXML, Новый СжатиеДанных(9));
	ЗагруженоОбъектов = LuExchange.ExecuteExchange(Данные, ИмяСобытияДляЖурнала, СтрокаФиктивныеСсылки, СтрокаЗагруженныеДокументы, ТекстОшибки);

	// Фиктивные ссылки (выгружаем объекты по ссылкам)
	ОбработкаФиктивныхСсылок(СтрокаФиктивныеСсылки, LuExchange, ОбработкаОбмена, ФайлОбмена, ИмяСобытияДляЖурнала, ОшибкаВыгрузки);
	
КонецПроцедуры
Процедура ОбработкаФиктивныхСсылок(СтрокаФиктивныеСсылки, LuExchange, ОбработкаОбмена, ФайлОбмена, ИмяСобытияДляЖурнала, Отказ)
	
	Если ПустаяСтрока(СтрокаФиктивныеСсылки) Тогда
		Возврат;
	КонецЕсли;
	
	ФиктивныеСсылки = ЗначениеИзСтрокиВнутр(СтрокаФиктивныеСсылки);
	Для каждого ФиктивнаяСсылка Из ФиктивныеСсылки Цикл
		ФиктивнаяСсылка = СтрЗаменить(ФиктивнаяСсылка, ".", Символы.ПС);
		ВидПриемника = СтрПолучитьСтроку(ФиктивнаяСсылка, 1);
		ТипПриемника = СтрПолучитьСтроку(ФиктивнаяСсылка, 2); 
		УИД = СтрПолучитьСтроку(ФиктивнаяСсылка, 3);
		
		// Здесь тип объекта соответствует метаданным базы приемника, 
		// для обработки необходимо привести к соответствующему типу текущей базы
		// Найдем корреспондирующий тип по правилам обмена
		ПравилоКонвертации = ОбработкаОбмена.ТаблицаПравилКонвертации.Найти(ВидПриемника+"Ссылка."+ТипПриемника, "Приемник");
		Если ПравилоКонвертации = Неопределено Тогда
			Продолжить;
		КонецЕсли;
		
		TypeOf = СтрЗаменить(ПравилоКонвертации.ТипИсточника, ".", Символы.ПС);
		ВидИсточника = СтрПолучитьСтроку(TypeOf, 1);
		ТипИсточника = СтрПолучитьСтроку(TypeOf, 2);
		
		Если ВидИсточника = "ДокументСсылка" Тогда
			МенеджерОбъекта = Документы[ТипИсточника];
		ИначеЕсли ВидИсточника = "СправочникСсылка" Тогда
			МенеджерОбъекта = Справочники[ТипИсточника];
		Иначе
			Отказ = Истина;
			Продолжить;	
		КонецЕсли;
		
		ОбъектСсылка = МенеджерОбъекта.ПолучитьСсылку(Новый УникальныйИдентификатор(УИД));
		
		НайденныйОбъект = ОбъектСсылка.ПолучитьОбъект();
		Если НайденныйОбъект = Неопределено Тогда
			Отказ = Истина;
			Продолжить;	
		КонецЕсли;
		
		ВыгрузитьОбъектВБазуПриемник(ОбъектСсылка, LuExchange, ОбработкаОбмена, ФайлОбмена, ИмяСобытияДляЖурнала,, Отказ);
		
	КонецЦикла;

КонецПроцедуры
Процедура ОбработкаПризнакаПроведенияДокумента(СтрокаЗагруженныеДокументы, LuExchange, ИмяСобытияДляЖурнала, Отказ)
	
	Если ПустаяСтрока(СтрокаЗагруженныеДокументы) Тогда
		Возврат;
	КонецЕсли;
	
	// PostingDocuments
	ТекстОшибки = "";
	Отказ = LuExchange.PostingDocuments(СтрокаЗагруженныеДокументы, ТекстОшибки);

КонецПроцедуры
Функция ИнициализироватьОбработкуОбмена(ТекущийУзел, ИмяСобытияДляЖурнала, Отказ)
	
	// Инициализация
	ОбработкаОбмена = Обработки.УниверсальныйОбменДаннымиXML.Создать();
	ОбработкаОбмена.РежимОбмена = "Выгрузка";
	ОбработкаОбмена.ПостроительОтчета = Новый ПостроительОтчета;
	ОбработкаОбмена.НеВыводитьНикакихИнформационныхСообщенийПользователю = Истина;
	
	// Загрузка правил
	ПравилаОбмена = ТекущийУзел.ПравилаКонвертации.Получить();
	ОбработкаОбмена.ЗагрузитьПравилаОбмена(ПравилаОбмена, "Строка");
	ОбработкаОбмена.ИмяФайлаПравилОбмена = "Неопределено"; // Обходим глюк обработки, иначе она считает, что правила не загружены
	
	// ИмяФайлаОбмена	
	ОбработкаОбмена.ИмяФайлаОбмена = ПолучитьИмяВременногоФайла("xml");
	
	// Для всех правил конвертации справочников и документов рекомендуется по возможности устанавливать в ИСТИНА следующие свойства: 
	//	- Искать объект приемника по внутреннему идентификатору объекта источника;
	//	- При переносе объкта по ссылке НЕ создавать новый объект, а только переносить ссылку;
	// Для правил конвертации документов дополнительно рекомендуется устанавливать в ИСТИНА следующее свойство: 
	//	- Не выгружать объекты свойств источника по ссылкам;
	// В этом случае файл данных XML для выгружаемого объекта не будет содержать объекты выгружаемые по ссылкам. 
	// Это существенно уменьшит размер передаваемого файла и ускорит процесс синхронизации.
	// Выгрузка этих объектов при необходимости будет инициироваться автоматически.
	
	Возврат ОбработкаОбмена;
	
КонецФункции
Функция ПолучитьДанныеXMLПоПравиламКонвертации(ОбработкаОбмена, ОбъектСсылка, ВыгруженоОбъектов, ИмяСобытияДляЖурнала)
	
	МетаИмя = ОбъектСсылка.Метаданные().Имя;
	
	// Сначала все отключаем
	Для Каждого Строка из ОбработкаОбмена.ТаблицаПравилВыгрузки.Строки Цикл
		Строка.Включить = 0;
		ОбработкаОбмена.УстановитьПометкиПодчиненных(Строка, "Включить");
	КонецЦикла;
	
	// Включаем нужное правило
	СтрПравил = ОбработкаОбмена.ТаблицаПравилВыгрузки.Строки.Найти(МетаИмя, "Имя", Истина);
	
	Если СтрПравил = Неопределено Тогда
		Возврат Неопределено;
	КонецЕсли; 
	
	Если НЕ ЗначениеЗаполнено(СтрПравил.ИмяОбъектаДляЗапроса) Тогда
		Возврат Неопределено;
	КонецЕсли; 
	
	СтрПравил.Включить = 1;
	ОбработкаОбмена.УстановитьПометкиРодителей(СтрПравил, "Включить");
	
	// Отбор
	ИмяОтбора = СтрЗаменить(СтрПравил.ИмяОбъектаДляЗапроса, ".", "_");
	ПостроительОтчета = Новый ПостроительОтчета("ВЫБРАТЬ ПЕРВЫЕ 1 _.* ИЗ " + СтрПравил.ИмяОбъектаДляЗапроса + " КАК _ {ГДЕ _.Ссылка.* КАК " + ИмяОтбора + "}");
	
	Отбор = ПостроительОтчета.Отбор.Добавить(ИмяОтбора);
	Отбор.ВидСравнения = ВидСравнения.Равно;
	Отбор.Установить(ОбъектСсылка); 
	
	СтрПравил.ИспользоватьОтбор = Истина;
	СтрПравил.НастройкиПостроителя = ПостроительОтчета.ПолучитьНастройки();
	
	// Выгрузка
	ОбработкаОбмена.ВыполнитьВыгрузку();
	ВыгруженоОбъектов = ОбработкаОбмена.мСчетчикВыгруженныхОбъектов;
	ЧтениеТекста = Новый ЧтениеТекста;
	ЧтениеТекста.Открыть(ОбработкаОбмена.ИмяФайлаОбмена, КодировкаТекста.UTF8);
	СтрокаXML = ЧтениеТекста.Прочитать();
	ЧтениеТекста.Закрыть();
	
	Возврат СтрокаXML;
	
КонецФункции // ПолучитьДанныеXMLПоПравиламКонвертации()
Функция ПолучитьWSПрокси(Параметры, ИмяФайлаПубликации, URIПространстваИменСервиса, ИмяСервиса)
	
	Определения = Новый WSОпределения("http://"+Параметры.Сервер+"/"+Параметры.База+"/ws/"+ИмяФайлаПубликации+"?wsdl", Параметры.Пользователь, Параметры.Пароль);
	WSПрокси = Новый WSПрокси(Определения, URIПространстваИменСервиса, ИмяСервиса, ИмяСервиса+"Soap");
	WSПрокси.Пользователь = Параметры.Пользователь;
	WSПрокси.Пароль = Параметры.Пароль;
	Возврат WSПрокси;
	
КонецФункции
6. Настройка Веб-сервиса для базы приемника

Настройка Веб-сервиса для базы приемника

7. Модуль Веб-сервиса

Функция ExecuteExchange(DataXML, NameOfEvent, FictitiousObjects, UploadedDocuments, ErrorMessage)       

	УстановитьПривилегированныйРежим(Истина);
	
	Попытка
		
		ОбработкаОбмена = Обработки.УниверсальныйОбменДаннымиXML.Создать();
		ОбработкаОбмена.РежимОбмена = "Загрузка";
		ОбработкаОбмена.НеВыводитьНикакихИнформационныхСообщенийПользователю = Истина;
		ОбработкаОбмена.ЗагружатьДанныеВРежимеОбмена = Истина;
		ОбработкаОбмена.ЗаписыватьВИнформационнуюБазуТолькоИзмененныеОбъекты = Истина;
		ОбработкаОбмена.ОптимизированнаяЗаписьОбъектов = Истина;
		ОбработкаОбмена.ЗаписыватьРегистрыНаборамиЗаписей = Истина;
	
		РаботаВозможна = ОбработкаОбмена.ВыполнитьДействияПередЧтениемДанных(DataXML.Получить());
		Если НЕ РаботаВозможна Тогда
			ErrorMessage = "ExecuteExchange(1). ОбработкаОбмена.ВыполнитьДействияПередЧтениемДанных - работа не возможна";
			Возврат 0;
		КонецЕсли;	

		ОбработкаОбмена.ПроизвестиЧтениеДанных(ErrorMessage);
		Если НЕ ПустаяСтрока(ErrorMessage) Тогда
			Возврат 0;
		КонецЕсли;

		ОбработкаОбмена.ВыполнитьДействияПослеЗавершенияЧтенияДанных(); 

		// Подготовим для возврата в базу источник массивы Фиктивных ссылок и массив выгруженных документов
		МассивФиктивныхСсылок = Новый Массив;
		МассивЗагруженныхДокументов = Новый Массив;
		МетаданныеДокументы = Метаданные.Документы;
		Для каждого КлючИЗначение Из ОбработкаОбмена.ЗагруженныеОбъекты Цикл
			ОбъектСсылка = КлючИЗначение.Значение.СсылкаНаОбъект;
			ОбъектМетаданные = ОбъектСсылка.Метаданные();
			Если КлючИЗначение.Значение.СсылкаФиктивная Тогда  
				// Фиктивная ссылка - объект не найденный в базе приемнике и не выгруженный по ссылкам
				МассивФиктивныхСсылок.Добавить(ОбъектМетаданные.ПолноеИмя() + "." + XMLСтрока(ОбъектСсылка.Ссылка));
			ИначеЕсли МетаданныеДокументы.Содержит(ОбъектМетаданные) Тогда
				МассивЗагруженныхДокументов.Добавить(ОбъектСсылка.Ссылка);
			КонецЕсли;
		КонецЦикла;
		Если МассивФиктивныхСсылок.Количество() > 0 Тогда
			FictitiousObjects = ЗначениеВСтрокуВнутр(МассивФиктивныхСсылок);
		КонецЕсли;
		Если МассивЗагруженныхДокументов.Количество() > 0 Тогда
			UploadedDocuments = ЗначениеВСтрокуВнутр(МассивЗагруженныхДокументов);
		КонецЕсли;

	Исключение
		ErrorMessage = "ExecuteExchange(2). " + ОписаниеОшибки();
		Возврат 0;
	КонецПопытки;
	
	Возврат ОбработкаОбмена.мСчетчикЗагруженныхОбъектов;	
	
КонецФункции

Функция DeleteObject(TypeOf, UUID, ErrorMessage)
	
	УстановитьПривилегированныйРежим(Истина);
	Попытка
		
		TypeOf = СтрЗаменить(TypeOf, ".", Символы.ПС);
		ВидОбъекта = СтрПолучитьСтроку(TypeOf, 1);
		ТипОбъекта = СтрПолучитьСтроку(TypeOf, 2);

		Если ВидОбъекта = "ДокументСсылка" Тогда
			МенеджерОбъекта = Документы[ТипОбъекта];
		ИначеЕсли ВидОбъекта = "СправочникСсылка" Тогда
			МенеджерОбъекта = Справочники[ТипОбъекта];
		Иначе
			ErrorMessage = "DeleteObject(1). Ошибка получения менеджера объекта " + ВидОбъекта + "." + ТипОбъекта + "!";
			Возврат Истина;	
		КонецЕсли;
		
		ОбъектСсылка = МенеджерОбъекта.ПолучитьСсылку(Новый УникальныйИдентификатор(UUID));
		НайденныйОбъект = ОбъектСсылка.ПолучитьОбъект();
		Если НЕ НайденныйОбъект = Неопределено Тогда
			НайденныйОбъект.Удалить(); 
			// В базе приемнике удаление происходит без проверки ссылочной целостности
			// В некоторых схемах целесообразнее отключать данный функционал
			// а удаление выполнять соответствующей обработкой
			// ("ПометкаУдаления" синхронизируется обычным образом в ExecuteExchange)
		КонецЕсли;
			
	Исключение
		ErrorMessage = "DeleteObject(2). " + ОписаниеОшибки();
		Возврат Истина;
	КонецПопытки;
	
	Возврат Ложь;	
	
КонецФункции

Функция PostingDocuments(UploadedDocuments, ErrorMessage)
	
	УстановитьПривилегированныйРежим(Истина);
	Отказ = Ложь;
	Попытка
		
		МассивВыгруженныхДокументов = ЗначениеИзСтрокиВнутр(UploadedDocuments);
		Для каждого ДокументСсылка Из МассивВыгруженныхДокументов Цикл
			
			ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
			Если ДокументОбъект = Неопределено Тогда
				Отказ = Истина;
				ErrorMessage = ErrorMessage + Символы.ПС + "PostingDocuments(1). Объект не найден: " + ДокументСсылка;
				Продолжить;
			КонецЕсли;
			
			// Проведение
			Если ДокументОбъект.Проведен Тогда
				Попытка
					ПолучитьСообщенияПользователю(Истина); // Очистим все предыдущие сообщения на момент проведения
					ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
				Исключение
					Сообщения = ПолучитьСообщенияПользователю(Истина); // Вернем в базу источник ошибки проведения
					ТекстСообщения = "";
					Для каждого Сообщение Из Сообщения Цикл
						ТекстСообщения = ТекстСообщения + Символы.ПС + Сообщение.Текст;
					КонецЦикла;
					ErrorMessage = ErrorMessage + Символы.ПС + "PostingDocuments(2). " + ДокументСсылка + Символы.ПС + ОписаниеОшибки() + ТекстСообщения;
					Отказ = Истина;
					// Сбросим признак проведения
					ДокументОбъект.Проведен = Ложь;
					ДокументОбъект.ОбменДанными.Загрузка = Истина;
					ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
				КонецПопытки;
			КонецЕсли;
			
			// Если снят с проведения, очистим движения
			Если НЕ ДокументОбъект.Проведен Тогда
				Для каждого НаборДвижений Из ДокументОбъект.Движения Цикл
					НаборДвижений.Прочитать();
					Если НаборДвижений.Количество() > 0 Тогда
						НаборДвижений.Очистить();
						НаборДвижений.Записать();
					КонецЕсли;
				КонецЦикла;
			КонецЕсли;
			
		КонецЦикла;
		
	Исключение
		ErrorMessage = "PostingDocuments(3). " + ОписаниеОшибки();
		Отказ = Истина;
	КонецПопытки;
	
	Возврат Отказ;	
	
КонецФункции
8. Рекомендации
При  разработке правил конвертации очень сложно отлаживать правила в боевой схеме, поэтому для сложных конвертаций рекомендую сначало воспользоваться средствами отладки встроенными в обработку "Универсальный обмен данными XML", в этом случае имеется возможность подключить алгоритмы из правил в виде модулей внешней обработки как при выгрузке, так и при загрузке данных. И только после успешной выгрузки - загрузки в интерактивном режиме через промежуточный файл - пробовать синхронизацию через Веб.
9. Опыт внедрения

Данная схема успешно реализована в реальных действующих системах:

  • связка УТ 10.3 и УПП 1.3. (УТ нетиповая, доработана под производство с вводом данных через тач-панели на конвейерной линии)
  • связка Самописная конфигурация и БП 3.0 (В самописной конфигурации ведется учет технологических операций на фабрике птицы)
10. Состав вложенных файлов
  • ОбменДаннымиОнлайнЧерезВебСервис.cf - содержит все описанные объекты для синхронизации (Источник + Приемник). Обычные формы. Клиент-серверный вариант. В файловом варианте тоже все будет работать, кроме регламентного задания. В этом случае необходимо предусмотреть периодический запуск синхронизации с помощью метода ПодключитьОбработчикОжидания(). При сравнении, объединении необходимо отметить по подсистемам файла: подсистема "ЛУ_СинхронизацияЧерезВебСервис".
  • ОбменДаннымиОнлайнЧерезВебСервисУФ.cf - то же, но для управляемых форм (в процессе, будет выложен чуть позже)
  • ДемоБазы.zip - содержит выгрузки из двух баз (PR1.dt и PR2.dt) для демонстрации готовой настроенной схемы синхронизации. Базы содержат простенькие документы и справочники. Имена метаданных синхронизируемых объектов различаются. Правила конвертации загружены в базу. Необходимо восстановить базы из .dt. Выполнить публикацию на Веб-сервере под именами PR1 и PR2 с установленной галочкой Публиковать Веб-сервисы и LuExchange. В режиме Предприятия в настройках соответствующих узлов планов обмена необходимо прописать имя Веб-сервера на котором была выполнена публикация баз.
  • ОбменДаннымиОнлайнЧерезВебСервис.cfe - вариант доработки в виде расширения конфигурации для баз находящихся на поддержке без возможности редактирования. Ввиду ограниченного функционала расширений, не содержит объекты: План обмена и Регламентное задание. То есть синхронизация здесь реализована без квитирования успешности доставки данных, но полностью совместима с протоколом обмена основного .cf

11. Инструкция по внедрению (ОбменДаннымиОнлайнЧерезВебСервис.cf)

Скачиваем файл ОбменДаннымиОнлайнЧерезВебСервис.cf

Выполняем команду: Конфигурация - Сравнить, объединить с конфигурацией из файла...
После вывода результатов сравнения конфигураций выполняем команду:
Действия - Отметить по подсистемам файла - отмечаем подсистему "ЛУ_СинхронизацияЧерезВебСервис".
Далее - Выполнить.
Обновляем конфигурацию базы данных.
То же повторяем для корреспондирующей базы.
Публикуем обе базы на Веб-сервере (Как? - это отдельная тема, поиск в помощь)
При публикации отмечаем галочками Публиковать Веб-сервисы и LuExchange.
Настраиваем соответствующие узлы Планов обменов обеих баз
В первой базе создаем узел с настройками подключения ко второй базе и наоборот.
Здесь:
  • Сервер - имя веб-сервера, на котором выполнена публикация баз
  • База - имя, под которым выполнена публикация корреспондирующей базы на веб-сервере, как правило совпадает с именем базы на сервере 1С Предприятие
  • Логин - имя пользователя, которого необходимо создать в обеих базах с ролью WebСервисLuExchange (подключение веб-сервиса будет происходить под этим пользователем)
  • Пароль
  • Правила конвертации - сюда необходимо загрузить правила конвертации, созданные в конфигурации "Конвертация данных"
Создание правил конвертации - тоже отдельная большая тема. (Если не в курсе, можно поштудировать инет)
Если Вы владеете этим мощным и универсальным инструментом, то для идентичных корреспондирующих баз, правила конвертации сможете создать полностью автоматически за несколько минут.
Открываем состав плана обмена ЛУ_WebСервис и добавляем все объекты, для которых в правилах конвертации прописали ПВД (Правила выгрузки данных).
Для всех объектов состава запрещаем авторегистрацию.
При разработке правил конвертации придерживаемся следующих рекомендаций:
  • Для ПВД поддерживается только способ выборки: "Стандартная выборка"
  • Для всех ПКО справочников и документов рекомендуется устанавливать в ИСТИНА следующие свойства:
    • Искать объект приемника по внутреннему идентификатору объекта источника;
    • При переносе объкта по ссылке НЕ создавать новый объект, а только переносить ссылку;
  • Для ПКО документов дополнительно рекомендуется устанавливать в ИСТИНА следующее свойство:
    • Не выгружать объекты свойств источника по ссылкам;

В этом случае файл данных XML для выгружаемого объекта не будет содержать объекты выгружаемые по ссылкам. Это существенно уменьшит размер передаваемых данных и ускорит процесс синхронизации. Выгрузка этих объектов при необходимости будет инициироваться автоматически.

Если возникнут затруднения в настройке планов обменов
можно скачать вложение "ДемоБазы.zip" и посмотреть как это выполнено в них.
12. Инструкция по внедрению Расширения конфигурации (ОбменДаннымиОнлайнЧерезВебСервис.cfe)
(в процессе, будет чуть позже)
Если понравилось и желаешь порекомендовать другим коллегам, ставь плюс!

Скачать файлы

Наименование Файл Версия Размер Кол. Скачив.
ОбменДаннымиОнлайнЧерезВебСервис.cf
.cf 221,56Kb
06.11.16
11
.cf 1 221,56Kb 11 Скачать
ДемоБазы.zip
.zip 582,91Kb
06.11.16
7
.zip 1 582,91Kb 7 Скачать
ОбменДаннымиОнлайнЧерезВебСервисУФ.cfe (расширение конфигурации, управляемые формы)
.cfe 203,46Kb
06.11.16
3
.cfe 1.01 203,46Kb 3 Скачать

См. также

Подписаться Добавить вознаграждение

Комментарии

1. борян петров (TODD22) 31.10.16 14:04
А какой объём данных можно передать за одно подключение?
У меня бывает пакеты по 30-40 Мб. На плохих каналах связи не всегда через ftp удаётся передать такой объём.
2. Сергей Линков (linkov) 31.10.16 14:47
(1) TODD22,

А какой объём данных можно передать за одно подключение?
У меня бывает пакеты по 30-40 Мб. На плохих каналах связи не всегда через ftp удаётся передать такой объём.

В представленной публикации алгоритм реализован именно для минимизации размера передаваемых данных через Веб-сервис.
Для этого:
- В одном пакете передается только один объект (элемент справочника или документ)
- Для документов в правилах конвертации отключена выгрузка объектов по ссылкам. Эта настройка существенно уменьшает размер передаваемых данных, исключая их избыточность. В случае необходимости, если подчиненный объект все же отсутствует в базе приемнике, то автоматически рекурсивно запускается его выгрузка в новых пакетах.
В итоге максимальный размер одного пакета в реальной схеме при синхронизации УПП 1.3 - УТ 10.3 для документа Поступление товаров и услуг (в среднем 10-20 строчек в табличной части) не превышает 0.5 Мб.

При стандартной установке для IIS максимальный размер обрабатываемого сообщения ~30 МБ

Если в Вашей схеме вес одного пакета зашкаливает за 30 Мб., то тогда Вам в помощь эта публикация: Передача больших пакетов через веб-сервисы
3. Артур Аюханов (artbear) 02.11.16 20:13
4. Рашид Ас (rash7wolf) 05.11.16 20:08
Приобрел оба файла. не жалею. все работает. спасибо ,нужная вещь . советую
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа