gifts2017

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

Опубликовал Петр Базелюк (pbazeliuk) в раздел Обмен - 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" Тогда
                    СтруктураОтвета.Прочитать();
                    ОписаниеОшибки = ОписаниеОшибки + СтруктураОтвета.Значение + Символы.ПС;
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
        СтруктураОтвета.Закрыть();
        УдалитьФайлы(ИмяФайлаОтвета);

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

    КонецЕсли;

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

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

    ИмяФайлаЗапроса = СформироватьФайлДля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.Закрыть();

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

КонецФункции
Функция ФорматироватьНомер(Номер)
    Результат = "";
    ДопустимыеСимволы = "+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-сообщений, так же, нужно задать расписание предопределенного регламентного задания:

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

Статья в нашем блоге avtomat.biz

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Антон Стеклов (asved.ru) 03.12.14 09:53
Я правильно понимаю, что подсистема не поддерживает многопоточную отправку и многопоточный контроль статуса?
2. Петр Базелюк (pbazeliuk) 03.12.14 10:41
(1) asved.ru, 1C не поддерживает многопоточного программирования. Частный случай запуск нескольких фоновых заданий.
3. Антон Стеклов (asved.ru) 03.12.14 20:31
(2) pbazeliuk, Вы сами себе противоречите :)

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

В силу того, что (я надеюсь) Вы разбирали работу подсистемы, я и спрашиваю, поддерживается ли в подсистеме многопоточная отправка.
4. Петр Базелюк (pbazeliuk) 03.12.14 21:04
(3) asved.ru, нет не поддерживается, отправка последовательная. Механизм фоновых заданий, не считаю, полноценным решением для полноценного многопоточного программирования.