БСП: добавление нового провайдера 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 не будет опубликован. Обязательные поля помечены *