БСП: добавление нового провайдера SMS

Небольшая статья о том, как добавить нового провайдера SMS на примере конфигурации «Управление торговлей 11», используя библиотеку стандартных подсистем (БСП)

Перед началом добавления нового провайдера услуг нужно убедиться в работоспособности его API. Поможет в этом замечательный инструмент-расширение к браузеру Google Chrome click.

Будем считать, что все тесты прошли успешно. Во-первых, нужно разрешить возможность редактирования общего модуля ОтправкаСМСПереопределяемый и перечисления ПровайдерыСМС:


Во-вторых, добавить в перечисление ПровайдерыСМС название своего провайдера: 


В-третьих, выполнить настройку провайдера SMS, указать логин и пароль в базе данных. Вкладка «Администрирование»:


Далее нужно определить описание взаимодействия с API в общем модуле ОтправкаСМСПереопределяемый. Нужно переопределить 3 пустых процедуры:

// Проверяет правильность сохраненных настроек отправки SMS.
Процедура ПриПроверкеНастроекОтправкиSMS(НастройкиОтправкиSMS, Отказ)

// Отправляет SMS через настроенного поставщика услуги, возвращает идентификатор сообщения.
Процедура ОтправитьSMS(ПараметрыОтправки, Результат)

// Запрашивает статус доставки SMS у поставщика услуг.
Процедура СтатусДоставки(ИдентификаторСообщения, Провайдер, Логин, Пароль, Результат)

Важно, провайдер alphasms использует для коммуникации POST-запросы, на входе и выходе xml-файлы, у других провайдеров может быть все иначе (GET, POST, etc. и другие форматы).


Код процедуры ПриПроверкеНастроекОтправкиSMS у меня получился такой:

Процедура ПриПроверкеНастроекОтправкиSMS(НастройкиОтправкиSMS, Отказ) Экспорт

Перем Провайдер, Логин, Пароль;

Если ТипЗнч(НастройкиОтправкиSMS) = Тип("Структура") Тогда

Если НастройкиОтправкиSMS.Свойство("Логин", Логин)
И  НастройкиОтправкиSMS.Свойство("Пароль", Пароль)
И  НастройкиОтправкиSMS.Свойство("Провайдер", Провайдер)Тогда

Если Логин = Неопределено ИЛИ ПустаяСтрока(Логин) Тогда
Отказ = Истина;
КонецЕсли;

Если Пароль = Неопределено ИЛИ ПустаяСтрока(Пароль) Тогда
Отказ = Истина;
КонецЕсли;

Если Провайдер = Неопределено ИЛИ Провайдер = Перечисления.ПровайдерыSMS.ПустаяСсылка() Тогда
Отказ = Истина;
КонецЕсли;

Иначе
Отказ = Истина;
КонецЕсли;

Иначе
Отказ = Истина;
КонецЕсли;

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

Код отправки SMS у меня базировался на API провайдера:

  • тело POST запроса в кодировке «UTF-8»:
  • примерный ответ сервиса в результате успеха:
  • примерный ответ сервиса в результате ошибки:
// Отправляет SMS через настроенного поставщика услуги, возвращает идентификатор сообщения.
//
// Параметры:
//  ПараметрыОтправки - Структура:
//      * Провайдер         - ПеречислениеСсылка.ПровайдерыSMS - поставщик услуги по отправке SMS.
//      * НомераПолучателей - Массив - массив строк номеров получателей в формате +7ХХХХХХХХХХ.
//      * Текст             - Строка - текст сообщения, максимальная длина у операторов может быть разной.
//      * ИмяОтправителя    - Строка - имя отправителя, которое будет отображаться вместо номера у получателей.
//      * Логин             - Строка - логин для доступа к услуге отправки SMS.
//      * Пароль            - Строка - пароль для доступа к услуге отправки SMS.
//  Результат - Структура - (возвращаемое значение):
//      * ОтправленныеСообщения  - Массив структур:
//      * НомерПолучателя        - Строка - номер получателя из массива НомераПолучателей;
//      * ИдентификаторСообщения - Строка - идентификатор SMS, по которому можно запросить статус отправки.
//      * ОписаниеОшибки         - Строка - пользовательское представление ошибки, если пустая строка, то ошибки нет.
//
Процедура ОтправитьSMS(ПараметрыОтправки, Результат) Экспорт

Если ПараметрыОтправки.Провайдер = Перечисления.ПровайдерыSMS.AlfaSMS Тогда

// проверка на заполнение обязательных параметров
Если ПараметрыОтправки.НомераПолучателей.Количество() = 0 Или ПустаяСтрока(ПараметрыОтправки.Текст) Тогда
Результат.ОписаниеОшибки = НСтр("ru = 'Неверные параметры сообщения'");
Возврат;
КонецЕсли;

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

// обработка результата запроса (получение идентификаторов сообщений)
СтруктураОтвета = Новый ЧтениеXML;
СтруктураОтвета.ОткрытьФайл(ИмяФайлаОтвета);
ОписаниеОшибки = "";
ИндексНомера = 0;
Пока СтруктураОтвета.Прочитать() Цикл
Если СтруктураОтвета.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
Если СтруктураОтвета.Имя = "msg" Тогда
ИдентификаторСообщения = "";
НомерПолучателя = "";
Пока СтруктураОтвета.ПрочитатьАтрибут() Цикл
Если СтруктураОтвета.Имя = "sms_id" Тогда
ИдентификаторСообщения = СтруктураОтвета.Значение;
НомерПолучателя = ФорматироватьНомер(ПараметрыОтправки.НомераПолучателей[ИндексНомера]);
ИндексНомера = ИндексНомера + 1;
КонецЕсли;
КонецЦикла;
Если Не ПустаяСтрока(НомерПолучателя) Тогда
ОтправленноеСообщение = Новый Структура("НомерПолучателя,ИдентификаторСообщения",
НомерПолучателя,ИдентификаторСообщения);
Результат.ОтправленныеСообщения.Добавить(ОтправленноеСообщение);
КонецЕсли;
ИначеЕсли СтруктураОтвета.Имя = "error" Тогда
СтруктураОтвета.Прочитать();
ОписаниеОшибки = ОписаниеОшибки + СтруктураОтвета.Значение + Символы.ПС;
КонецЕсли;
КонецЕсли;
КонецЦикла;
СтруктураОтвета.Закрыть();
УдалитьФайлы(ИмяФайлаОтвета);

Результат.ОписаниеОшибки = СокрП(ОписаниеОшибки);

КонецЕсли;

КонецПроцедуры // ОтправитьSMS()

Функция ВыполнитьЗапрос(ПараметрыЗапроса)

Результат = "";

ИмяФайлаЗапроса = СформироватьФайлДляPOSTЗапроса(ПараметрыЗапроса);
ИмяФайлаОтвета = ПолучитьИмяВременногоФайла("xml");

// формирование заголовка
Заголовок = Новый Соответствие;
Заголовок.Вставить("Content-Type", "application/x-www-form-urlencoded");
Заголовок.Вставить("Content-Length", XMLСтрока(РазмерФайла(ИмяФайлаЗапроса)));

// отправка запроса и получение ответа
Попытка
Соединение = Новый HTTPСоединение("alphasms.com.ua", , , , ПолучениеФайловИзИнтернетаКлиентСервер.ПолучитьПрокси("https"), 3);
Соединение.ОтправитьДляОбработки(ИмяФайлаЗапроса, "api/xml.php", ИмяФайлаОтвета, Заголовок);
Результат = ИмяФайлаОтвета;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Отправка SMS'", ОбщегоНазначенияКлиентСервер.КодОсновногоЯзыка()),
УровеньЖурналаРегистрации.Ошибка,
,
,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

УдалитьФайлы(ИмяФайлаЗапроса);

Возврат Результат;

КонецФункции // ВыполнитьЗапрос()

Функция СформироватьФайлДляPOSTЗапроса(ПараметрыЗапроса)

ИмяФайлаЗапроса = ПолучитьИмяВременногоФайла("xml");

ТелоЗапросаXML = Новый ЗаписьXML;
ТелоЗапросаXML.ОткрытьФайл(ИмяФайлаЗапроса, "UTF-8");

ТелоЗапросаXML.ЗаписатьОбъявлениеXML();
ТелоЗапросаXML.ЗаписатьНачалоЭлемента("package");
ТелоЗапросаXML.ЗаписатьАтрибут("login", ПараметрыЗапроса.Логин);
ТелоЗапросаXML.ЗаписатьАтрибут("password", ПараметрыЗапроса.Пароль);
Если ПараметрыЗапроса.Свойство("ИдентификаторСообщения") Тогда

ТелоЗапросаXML.ЗаписатьНачалоЭлемента("status");
ТелоЗапросаXML.ЗаписатьНачалоЭлемента("msg");
ТелоЗапросаXML.ЗаписатьАтрибут("sms_id", ПараметрыЗапроса.ИдентификаторСообщения);
ТелоЗапросаXML.ЗаписатьКонецЭлемента();
ТелоЗапросаXML.ЗаписатьКонецЭлемента();

Иначе

ТелоЗапросаXML.ЗаписатьНачалоЭлемента("message");
Для каждого НомераПолучателя Из ПараметрыЗапроса.НомераПолучателей Цикл
ТелоЗапросаXML.ЗаписатьНачалоЭлемента("msg");
ТелоЗапросаXML.ЗаписатьАтрибут("recipient", ФорматироватьНомер(НомераПолучателя));
ТелоЗапросаXML.ЗаписатьАтрибут("sender", "KTC-ua.com");
ТелоЗапросаXML.ЗаписатьАтрибут("type", "0");
ТелоЗапросаXML.ЗаписатьТекст(ПараметрыЗапроса.Текст);
ТелоЗапросаXML.ЗаписатьКонецЭлемента();
КонецЦикла;
ТелоЗапросаXML.ЗаписатьКонецЭлемента();

КонецЕсли;
ТелоЗапросаXML.ЗаписатьКонецЭлемента();

ТелоЗапросаXML.Закрыть();

Возврат ИмяФайлаЗапроса;

КонецФункции // СформироватьФайлДляPOSTЗапроса()

Функция ФорматироватьНомер(Номер)
Результат = "";
ДопустимыеСимволы = "+1234567890";
Для Позиция = 1 По СтрДлина(Номер) Цикл
Символ = Сред(Номер,Позиция,1);
Если Найти(ДопустимыеСимволы, Символ) > 0 Тогда
Результат = Результат + Символ;
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции // ФорматироватьНомер()

Функция РазмерФайла(ИмяФайла)
Файл = Новый Файл(ИмяФайла);
Возврат Файл.Размер();
КонецФункции // РазмерФайла()

После переопределения 2-х функций уже должна работать отправка sms. Чтобы выполнялась отправка sms конфигурацией, нужно настроить расписание предопределенного регламентного задания: 

А вот и результат:


Осталось переопределить последнюю процедуру СтатусДоставки, она нужна для обновления статуса sms-сообщения в базе. API провайдера:

  • тело POST запроса в кодировке «UTF-8»: 
  • примерный ответ сервиса в результате успеха: 
// Запрашивает статус доставки SMS у поставщика услуг.
//
// Параметры:
//  ИдентификаторСообщения - Строка - идентификатор, присвоенный SMS при отправке;
//  Логин                  - Строка - логин для доступа к услуге отправки SMS.
//  Пароль                 - Строка - пароль для доступа к услуге отправки SMS.
//  Результат              - Строка - (возвращаемое значение) статус доставки,
//                                см. описание функции ОтправкаSMS.СтатусДоставки.
Процедура СтатусДоставки(ИдентификаторСообщения, Провайдер, Логин, Пароль, Результат) Экспорт

Если Провайдер = Перечисления.ПровайдерыSMS.AlfaSMS Тогда

// подготовка параметров запроса
ПараметрыЗапроса = Новый Структура;
ПараметрыЗапроса.Вставить("Логин", Логин);
ПараметрыЗапроса.Вставить("Пароль", Пароль);
ПараметрыЗапроса.Вставить("ИдентификаторСообщения", ИдентификаторСообщения);

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

// обработка результата запроса
SMSSTS_CODE = "";
ТекущийSMS_ID = "";
СтруктураОтвета = Новый ЧтениеXML;
СтруктураОтвета.ОткрытьФайл(ИмяФайлаОтвета);
Пока СтруктураОтвета.Прочитать() Цикл
Если СтруктураОтвета.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
Если СтруктураОтвета.Имя = "msg" Тогда
Пока СтруктураОтвета.ПрочитатьАтрибут() Цикл
Если СтруктураОтвета.Имя = "sms_id" Тогда
ТекущийSMS_ID = СтруктураОтвета.Значение;
КонецЕсли;
КонецЦикла;
СтруктураОтвета.Прочитать();
SMSSTS_CODE = СтруктураОтвета.Значение;
КонецЕсли;
КонецЕсли;
КонецЦикла;
СтруктураОтвета.Закрыть();
УдалитьФайлы(ИмяФайлаОтвета);

Результат = СтатусДоставкиSMS(SMSSTS_CODE);

КонецЕсли;

КонецПроцедуры // СтатусДоставки()

Функция СтатусДоставкиSMS(СтатусСтрокой)

СоответствиеСтатусов = Новый Соответствие;
СоответствиеСтатусов.Вставить("", "НеОтправлялось");
СоответствиеСтатусов.Вставить("100", "НеОтправлялось"); // SCHEDULED
СоответствиеСтатусов.Вставить("101", "Отправляется");   // ENROUTE
СоответствиеСтатусов.Вставить("102", "Доставлено");     // DELIVERED
СоответствиеСтатусов.Вставить("103", "НеДоставлено");   // EXPIRED
СоответствиеСтатусов.Вставить("104", "НеДоставлено");   // DELETED
СоответствиеСтатусов.Вставить("105", "НеДоставлено");   // UNDELIVERABLE
СоответствиеСтатусов.Вставить("106", "Отправлено");     // ACCEPTED
СоответствиеСтатусов.Вставить("107", "НеОпознаноПровайдером"); // UNKNOWN
СоответствиеСтатусов.Вставить("108", "НеОпознаноПровайдером"); // REJECTED
СоответствиеСтатусов.Вставить("109", "НеОпознаноПровайдером"); // DISCARDED
СоответствиеСтатусов.Вставить("110", "Отправляется");   // SENDING
СоответствиеСтатусов.Вставить("111", "НеДоставлено");   // NOT_SUPPORTED
СоответствиеСтатусов.Вставить("112", "НеДоставлено");   // WRONG_ALPHANAME
СоответствиеСтатусов.Вставить("113", "НеДоставлено");   // WRONG_ALPHANAME_RETURNED

Результат = СоответствиеСтатусов[НРег(СтатусСтрокой)];
Возврат ?(Результат = Неопределено, "Ошибка", Результат);

КонецФункции // СтатусДоставкиSMS()

Для обновления статусов sms-сообщений, так же, нужно задать расписание предопределенного регламентного задания:

А вот и результат:

Статья в личном блоге pbazeliuk.com

5 Comments

  1. asved.ru

    Я правильно понимаю, что подсистема не поддерживает многопоточную отправку и многопоточный контроль статуса?

    Reply
  2. pbazeliuk

    (1) asved.ru, 1C не поддерживает многопоточного программирования. Частный случай запуск нескольких фоновых заданий.

    Reply
  3. asved.ru

    (2) Вы сами себе противоречите 🙂

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

    В силу того, что (я надеюсь) Вы разбирали работу подсистемы, я и спрашиваю, поддерживается ли в подсистеме многопоточная отправка.

    Reply
  4. pbazeliuk

    (3) asved.ru, нет не поддерживается, отправка последовательная. Механизм фоновых заданий, не считаю, полноценным решением для полноценного многопоточного программирования.

    Reply
  5. nickperel

    (4)

    Хорошая работа.

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

    Можно ОтправитьSMS обернуть в ВыполнитьВФоне, рассылка пойдет в задании. Но ее, рассылку, делать надо. В коде чего-то с налету не нашел.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *