1С, oauth2, Google API и Google Merchant. Сбор данных и парсинг

Программирование - Практика программирования

Всем привет. Стала задача, собрать данные с Google Merchants и внести их в 1С, для более детального анализа того, что сейчас показывается, что нет и какие проблемы при этом есть. Если кому это интересно, прошу под кат.

Опыт програмирования на платформе 1С у меня крайне маленький, порядка года, поэтому за какие-то банальные вещи, прощу прощения сразу. Статья будет основана на данной статье, но там не описан процесс парсинга + не описан процесс на HTTPСоединение, что является более универсальным и более удобным. 

Все процедуры производились на клиент-серверном режиме, в конфигурации УТ 10.3 (10.3.13.2) на платформе 1С 8.3 (8.3.8.2322).

Использованные механизмы описаны в ключевых словах.

Еще перед тем, как пойдет код, я хотел бы ответить на вопрос, зачем я это вообще пишу? Во первых, для тех, кто ищет информацию по данным технологиям, во вторых, для того, чтобы получить обратную связь от вас, услышать какие-то советы и как итог - сделать себя лучше =) 

Пара ссылок на основне определения:

  1. oauth2
  2. HTTP
  3. POST/GET запросы
  4. JSON
  5. API

 

Погнали, итак, стоит цель, получить данные с Google Merchant и занести их в 1С.

Сделаем декомпозицию задачи.

  1. Получить данные для авторизации в Google
    1. Сделать уч. запись в Google Cloud Console 
      1. Добавить в разделе API , работу с  Content API for Shopping
      2. Сделать учетную запись с типом "Други типы" - http://jmp.sh/9ikhvbp
      3. Получить ClientId и Secret
  2. Добавить свою учетную запись (которая для Google Cloud Console) в Merchant с обычными правами.
  3. Получить Токен для запросов
  4. Сделать регламент Запроса данных/Обновления токена
  5. При запросе данных парсить JSON и вносить их в свою УС

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

Веселуха начинается с пункта 3. 

Для получения первого токена, пришлос использовать внешнюю обработку с HTML полем  и кнопкой (Если кто знает, как сделать проще, напишите в комментариях)

При нажатии кнопки, появится окно ввода логин/Пароля для учетки Google, вводим :


    client_id = ВАШ_CLIEN_ID_ПОЛУЧЕННЫЙ_В_GOOGLE_CLOUD
	
	ЧастьЗапроса = "response_type=code"+"&";
    ЧастьЗапроса = ЧастьЗапроса + "client_id="+ client_id + "&";
    ЧастьЗапроса = ЧастьЗапроса + "redirect_uri=http://localhost" + "&";
    ЧастьЗапроса = ЧастьЗапроса + "access_type=offline"+"&";
    ЧастьЗапроса = Параметры + "scope=https://www.googleapis.com/auth/content";
    АдресАвторизации = "https://accounts.google.com/o/oauth2/auth" + "?";
    ПолныйАдресАвторизации = АдресАвторизации + ЧастьЗапроса;
    
    ЭлементыФормы.ВАШ_HTTML_ПОЛЕ.Перейти(ПолныйАдресАвторизации);

Для HTML поля, в обработчике "ДокументСформирован", вставим код для отлова Code (Строка, необходимая для обмена на токен)

     Если Сред(ЭлементыФормы.Гугл.Документ.URLUnencoded,1,23) = "http://localhost/?code=" Тогда
		
		Code	=	Сред(ЭлементыФормы.Гугл.Документ.URLUnencoded,24);
		
	 КонецЕсли;

Теперь у нас есть данные, для получения Token и RefreshToken

Делаем процедуру Получения Токена 

    Сервер =  "accounts.google.com";
	Ресурс = "/o/oauth2/token";
	КодДоступа 		= 	Code;
	client_id		=	ВАШ_CLIENT_ID
	client_secret 	=	ВАШ_SECRET

	СтрокаЗапроса = "client_id=" + client_id + "&";
	СтрокаЗапроса = СтрокаЗапроса + "client_secret=" + client_secret + "&";
	СтрокаЗапроса = СтрокаЗапроса + "grant_type=authorization_code" + "&";
	СтрокаЗапроса = СтрокаЗапроса + "code=" + КодДоступа + "&";
	СтрокаЗапроса = СтрокаЗапроса + "redirect_uri=http://localhost";

	Соединение = Новый HTTPСоединение(Сервер,443,,,,,Новый ЗащищенноеСоединениеOpenSSL);

	Заголовки  = Новый Соответствие;
	Заголовки.Вставить("Content-Type","application/x-www-form-urlencoded");
	
	ЗапросХТТП = Новый HTTPЗапрос(Ресурс,Заголовки);
	ЗапросХТТП.УстановитьТелоИзСтроки(СтрокаЗапроса);

	Ответ = Соединение.ВызватьHTTPМетод("POST",ЗапросХТТП);
	
    Если Ответ.КодСостояния <> 200 Тогда Возврат КонецЕсли;

	Строка = Ответ.ПолучитьТелоКакСтроку();
	
    Чтение = Новый ЧтениеJSON();
	Чтение.УстановитьСтроку(Строка);

	Фабрика = ФабрикаXDTO.ПрочитатьJSON(Чтение);
	
    Чтение.Закрыть();
	
    Токен           = Фабрика.access_token;
	ТокенОбновления = Фабрика.refresh_token;

Тут конечно стоило бы написать кучу инфы про ФабрикуXDTO , но ее на просторах Инфостарта столько, что уже просто стыдно, что-то писать далее. Но если не читали, то очень советую прочитать цикл статей  , очень подробно и интересно расписали.

Но если вкратце, то фабрика преобразует наш XML/JSON в объект, с которым можно работать, через точку, что крайне удобно и не надо парсить документ старыми методами, перебором узлов, с проверкой на закрытие-открытие.

После получения токена, сделаем сразу еще одну процедуру, обновления такового, так как он имеет срок жизни.

 

    Сервер =  "accounts.google.com";
	Ресурс = "/o/oauth2/token";
	КодДоступа 		= 	Code;
	client_id		=	ВАШ_CLIEN_ID
	client_secret 	=	ВАШ_SECRET

	СтрокаЗапроса = "client_id="  + client_id + "&";
	СтрокаЗапроса = СтрокаЗапроса + "client_secret=" + client_secret + "&";
	СтрокаЗапроса = СтрокаЗапроса + "grant_type=refresh_token" + "&";
	СтрокаЗапроса = СтрокаЗапроса + "refresh_token=" + ТокенОбновления;	

	Соединение = Новый HTTPСоединение(Сервер,443,,,,,Новый ЗащищенноеСоединениеOpenSSL);

	Заголовки  = Новый Соответствие;
	Заголовки.Вставить("Content-Type","application/x-www-form-urlencoded");
	
	ЗапросХТТП = Новый HTTPЗапрос(Ресурс,Заголовки);
	ЗапросХТТП.УстановитьТелоИзСтроки(СтрокаЗапроса);

	Ответ = Соединение.ВызватьHTTPМетод("POST",ЗапросХТТП);
	Если Ответ.КодСостояния <> 200 Тогда Возврат КонецЕсли;

	Строка = Ответ.ПолучитьТелоКакСтроку();
	Чтение = Новый ЧтениеJSON();
	Чтение.УстановитьСтроку(Строка);

	Фабрика = ФабрикаXDTO.ПрочитатьJSON(Чтение);
	Чтение.Закрыть();
	Токен = Фабрика.access_token;

 

Теперь, когда мы имеем токен и Refresh токен, можем смело делать запросы.. и тут я столкнулся с бедой... в документации тупо не указано, как его делать, кроме строки

Google сделал, конечно, хороший  инструмент, но в нем просто не написана правильная локига создания запроса, поэтому в дело идут инструменты разработчика. 

 

Для примера, получение списка товарных позиций, из документации выглядит вот так

GET https://www.googleapis.com/content/v2/ИДЕНТИФИКАТОР_МАГАЗИНА_В_MERCHANT/products

Но если вы его сделаете, получите просто ошибку ... 

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

https://www.googleapis.com/content/v2/ID_МАГАЗИНА_В_МЕРЧАНТ/productstatuses?maxResults=250&key=ВАШ_CLIENT_ID

 + Заголовок с токен

Теперь код процедуры, с рекурсией =) 

Почему так, потому что гугл, отдаем всего 250 позиций, за один запрос. Так как у нас 10 000 тороговых предложений, то мне надо примерно 41 раз сделать вызов метода ...

Поэтому, рекурсия =) 

Сам вызов процедуры

ПолучениеФайлаСМерчанта(АдресРесурса,ТокенСтраницы)

АдресРесурса = Это тот же адрес, только с доп параметром pageToken=, с этим параметром, гугл отдаст следующую страницу, с новыми позициями.

    Путь   = Я_ФАЙЛЫ_СОХРАНИЛ_НА_ДИСК
	Сервер = "www.googleapis.com";
	Запрос =  АдресРесурса;
	Соединение = Новый HTTPСоединение(Сервер,443,,,,,Новый ЗащищенноеСоединениеOpenSSL);
	Заголовки  = Новый Соответствие;
	Заголовки.Вставить("Authorization","Bearer " + Токен);
	ЗапросХТТП = Новый HTTPЗапрос;
	ЗапросХТТП.АдресРесурса	=	Запрос;
	ЗапросХТТП.Заголовки = Заголовки;
	
	Соединение.ВызватьHTTPМетод("GET",ЗапросХТТП);
	Ответ = Соединение.ВызватьHTTPМетод("GET",ЗапросХТТП);
	Файл = Ответ.ПолучитьТелоКакДвоичныеДанные();
	Файл.Записать(Путь + "json_" + ТокенСтраницы + ".json");
	
	Чтение = Новый ЧтениеJSON;
	Чтение.ОткрытьФайл(Путь + "json_" + ТокенСтраницы + ".json");
	
	Фабрика = ФабрикаXDTO.ПрочитатьJSON(Чтение);
	Чтение.Закрыть();
	Если ОбъектXDTOСодержитСвойство(Фабрика,"nextPageToken") Тогда
		Если Фабрика.nextPageToken <> "" Тогда
			ПолучениеФайлаСМерчанта("/content/v2/ID_МАГАЗИНА/productstatuses?maxResults=250&pageToken="+Фабрика.nextPageToken+"&key=897236016313-ceggltgsseaoke0bfb48sivrr3dj766k.apps.googleusercontent.com",Фабрика.nextPageToken);
		КонецЕсли;
	КонецЕсли;

 

Вот теперь у нас уже есть все для внесения данных в 1С, можно файлы было не сохранять, а сразу парсить и вносить в тот же регистр сведений, к примеру. Но я пока тестил, сделал внесение в табличную часть.

МассивФайлов = НайтиФайлы(Путь,"*.json");
	
	Если МассивФайлов.Количество() = 0 Тогда
		Сообщить("Файлов не найдено");
		Возврат;
	КонецЕсли;
	
	ТабличноеПоле8.Очистить();
	Для Каждого Файл из МассивФайлов Цикл
		Чтение = Новый ЧтениеJSON;
		Чтение.ОткрытьФайл(Файл.ПолноеИмя);
		Фабрика = ФабрикаXDTO.ПрочитатьJSON(Чтение);
		Чтение.Закрыть();
		
		Для каждого Товар из КоллекцияXDTO(Фабрика.resources.resources) Цикл
			
			стр = ТабличноеПоле8.Добавить();
			Стр.Номенклатура 	= 	Справочники.Номенклатура.НайтиПоРеквизиту("Артикул",Сред(Товар.ProductID,14));
			Стр.Артикул 		=	Стр.Номенклатура.Артикул;
			Стр.ЦеноваяГруппа	=	Стр.Номенклатура.ЦеноваяГруппа;
			Стр.Категория		=	Стр.Номенклатура.МЮС_КаталогНоменклатуры;
			Стр.Ответственный	=	Стр.Номенклатура.МЮС_КаталогНоменклатуры.Ответственный;
			Стр.Ссылка 			=	Товар.Link;
			Стр.ProductID		=	Товар.ProductID;
			Стр.Displayed       =   Товар.destinationStatuses.destinationStatuses[0].approvalStatus;
			Стр.Shoping		    =   Товар.destinationStatuses.destinationStatuses[1].approvalStatus;
			
			
			Если ОбъектXDTOСодержитСвойство(Товар,"dataQualityIssues") тогда
				Для каждого Строка из КоллекцияXDTO(Товар.dataQualityIssues.dataQualityIssues) Цикл
				 					
					Стр.IDerror 	= 	?(ОбъектXDTOСодержитСвойство(Строка,"id")			,Строка.id		,"");
					Стр.severity 	= 	?(ОбъектXDTOСодержитСвойство(Строка,"severity")		,Строка.severity	,"");
					Стр.timestamp 	= 	?(ОбъектXDTOСодержитСвойство(Строка,"timestamp")	,Строка.timestamp	,"");
					Стр.Location	=	?(ОбъектXDTOСодержитСвойство(Строка,"Location")		,Строка.Location	,"");
					
				КонецЦикла;
			КонецЕсли;
			
		
		  КонецЦикла;
		УдалитьФайлы(Файл.ПолноеИмя);
		
	КонецЦикла;
	
	ЭлементыФормы.ТабличноеПоле8.СоздатьКолонки();

 

Тут используется 2 функции, одна проверяет на наличие свойства, вторая проверяет тип объекта XDTO, список он, или единичный

 

Код обеих, авторство не укажу, найдены на просторах Инфостарта.

Функция   ОбъектXDTOСодержитСвойство(ОбъектXDTO, Свойство)
	
	ЕстьСвойство = ОбъектXDTO.Свойства().Получить(Свойство) <> Неопределено
		И ОбъектXDTO[Свойство] <> Неопределено;
		
	Возврат ЕстьСвойство;
	
КонецФункции
Функция   КоллекцияXDTO(Элемент)
    Если ТипЗнч(Элемент)=Тип("ОбъектXDTO") Тогда
        МассивXDTO=Новый Массив;
        МассивXDTO.Добавить(Элемент);
        Возврат МассивXDTO;
    КонецЕсли;
    Возврат Элемент;
КонецФункции

 

В целом все ...

Если вы дочитали это, то уже спасибо =)

Если дадите какой-то хороший совет, буду прям очень рад.

См. также

Комментарии
1. Петр Вел (shmellevich) 110 20.09.17 12:17 Сейчас в теме
Статья хороша +!

А чем не угодил "ПрочитатьJSON(ЧтениеJSON, Истина)" - на выходе будет Соответствие. и безопасность получения свойств проще, и не нужны доп. функции ))

и как себя поведет фабрика если ключом окажется не строка, а например число?

{
   "key1":"value1",
   100:{"part1":50, 
            "part2":30, 
            "part3":20}
}


я когда на подобный джисон попал, долго мучался, было тяжело отойти от уже отлаженного механизма работы с XDTO объектами, все сломать и построить заново )).
user604633_slevin64celevra; +1 Ответить
2. Денис Мельников (Mi11er) 16 20.09.17 17:37 Сейчас в теме
(1) 0_о комментарий к статье... надо будет выпить сегодня.

Чем не угодил, не знаю, привык читать уже через фабрику, можно конечно и через чтение сделать, просто формат который выходил, был протестирован именно на фабрике.

+ у меня не так много опыта в этой теме и когда делаю такие парсеры, делаю не как то универсально, а именно заточенный под этот сервис.
4. Петр Базелюк (pbazeliuk) 1362 03.10.17 00:46 Сейчас в теме
(1) ПрочитатьJSON, ЗаписатьJSON очень медленныe. Здесь упоминается про это https://infostart.ru/public/640996/, опытным путем удалось подтвердить слова автора.
5. Петр Вел (shmellevich) 110 03.10.17 15:54 Сейчас в теме
(4) Согласно скрина из статьи видно, что фабрика пишет медленнее, чем даже обычная ЗаписатьJSON в 2 раза.
Или я не правильно понимаю?
6. Петр Базелюк (pbazeliuk) 1362 03.10.17 16:07 Сейчас в теме
(5) Про фабрику не знаю, там описано только:
3. ПрочитатьJSON, ЗаписатьJSON - самые медленные
2. Запись в поток - сейчас использую вот этот метод, он почти в 2 раза быстрее, чем предыдущий
1. Запись без контроля - этот пока нет времени реализовать.

По поводу фабрики не знаю, необходимо тестировать.
7. Петр Цап (Inkasor) 25 09.10.17 01:31 Сейчас в теме
(6)Привет :)

ПрочитатьJSON, ЗаписатьJSON - самые медленные

Дело в том, что 1С как раз по умолчанию инициализирует одну фабрику для быстрой обработки в рамках глобального контекста. Это быстро и удобно для, наверное, 99% задач, стоящих обычно перед коллегами :) Это и есть ПрочитатьJSON, ЗаписатьJSON как они есть.
Мы работаем только на потоке, причем с заранее подготовленными данными, поэтому пишем без контроля, что ещё очень сильно ускоряет обработку.
3. huse 34 22.09.17 21:17 Сейчас в теме
"Добавить свою учетную запись (которая для Google Cloud Console) в Merchant с обычными правами."

Подскажите как сделать эту операцию для Firebase?

PS content на firebase заменил, но проект запрашивает разрешение и не редиректит. А при нажатии Разрешить пытается открыть страницу с адресом res://ieframe.dll/dnserrordiagoff_webOC.htm#https://accounts.google.com/o/oauth2/approval?as=.......... после чего говорит что не может ее отобразить
Оставьте свое сообщение