Кратко суть задачи: ГАРАНТИРОВАННАЯ отправка СМС-сообщений из уже существующих информационных систем, которые используются у нас на предприятии. Первое, что пришло в голову-это отправка через почту (e-mail to SMS) на ящик вида [номер абонента]@[адрес оператора] большинство сотовых операторов предоставляют подобную услугу (например у МТС: 7913ххххххх@sms.mtslife.ru), хоть и не гарантируют 100% доставку всех сообщений таким образом. После того, как объём сообщений, который стал генерить наш сервер, стал достаточно большим, часть сообщений стали "пропадать" либо приходить с большими задержками. Пришлось искать другое решение.
Идеальный вариант - это работа на прямую c СМС-сервером оператора по протоколу SMPP, но это дополнительная головная боль в виде подписания договора с операторами, которые предоставляют такую возможность для корпоративных клиентов и скорее всего - это дорогая услуга.
В качестве ещё одного варианта был поиск посредников, которые предоставляют подобные услуги. Выбор пал на ресурс http://sms-host.ru (не сочтите за рекламу). Они гарантируют доставку и отслеживание состояния СМС-сообщений через Веб сервис, но ПЛАТНО. Ну да ладно, попросили у них тестовый доступ и я сразу же загорелся идеей сделать CLR-сборку для работы с Веб сервисом.
Адрес Веб-сервиса: https://sms-host.ru/service/smshostws.asmx?WSDL.
Описание классов: http://www.sms-host.ru/docs/.
SumbitSm
Отправляет коллекцию одиночных сообщений
Test
The test form is only available for requests from the local machine.
SOAP 1.1
The following is a sample SOAP 1.1 request and response. The placeholders shown need to be replaced with actual values.
POST /service/smshostws.asmx HTTP/1.1
Host: sms-host.ru
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://sms-host.ru/SumbitSm"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<Authentication xmlns="http://sms-host.ru/">
<User>string</User>
<Password>string</Password>
</Authentication>
</soap:Header>
<soap:Body>
<SumbitSm xmlns="http://sms-host.ru/">
<messageList>
<WsSubmitSm ValidityPeriodSmpp="string" MessageText="string" SenderAddress="string" ReceiverAddress="string" MessageId="guid" />
<WsSubmitSm ValidityPeriodSmpp="string" MessageText="string" SenderAddress="string" ReceiverAddress="string" MessageId="guid" />
</messageList>
</SumbitSm>
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SumbitSmResponse xmlns="http://sms-host.ru/">
<SumbitSmResult>
<WsSubmitSmResp ErrorDescription="string" ErrorCode="int" PartCount="unsignedByte" MessageId="guid" />
<WsSubmitSmResp ErrorDescription="string" ErrorCode="int" PartCount="unsignedByte" MessageId="guid" />
</SumbitSmResult>
</SumbitSmResponse>
</soap:Body>
</soap:Envelope>
QuerySm
Возвращает состояние сообщений, ранее отправленных с помощью метода SumbitSm
Test
The test form is only available for requests from the local machine.
SOAP 1.1
The following is a sample SOAP 1.1 request and response. The placeholders shown need to be replaced with actual values.
POST /service/smshostws.asmx HTTP/1.1
Host: sms-host.ru
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://sms-host.ru/QuerySm"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<Authentication xmlns="http://sms-host.ru/">
<User>string</User>
<Password>string</Password>
</Authentication>
</soap:Header>
<soap:Body>
<QuerySm xmlns="http://sms-host.ru/">
<messageList>
<WsQuerySm MessageId="guid" />
<WsQuerySm MessageId="guid" />
</messageList>
</QuerySm>
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<QuerySmResponse xmlns="http://sms-host.ru/">
<QuerySmResult>
<WsQuerySmResp EndTime="dateTime" BeginTime="dateTime" PartErrorCount="unsignedByte" PartDeliveredCount="unsignedByte" PartSendCount="unsignedByte" StateName="string" StateCode="int" MessageId="guid" />
<WsQuerySmResp EndTime="dateTime" BeginTime="dateTime" PartErrorCount="unsignedByte" PartDeliveredCount="unsignedByte" PartSendCount="unsignedByte" StateName="string" StateCode="int" MessageId="guid" />
</QuerySmResult>
</QuerySmResponse>
</soap:Body>
</soap:Envelope>
Как видно из документации, задача более чем тривиальная.
Приступим:
1) Создадим папку C:\SMS
2) Создаём прокси-сборку, запустив "
C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\wsdl.exe" /o:SMSHost.cs /n:WS https://sms-host.ru/service/smshostws.asmx?WSDL
На выходе у нас получился файл SMSHost.cs
3) Немного изучив Веб-сервис, пишем нашу сборку, с 2мя функциями-отправка смс и получение состояния отправленного сообщения:
using System;
using Microsoft.SqlServer.Server;
using WS;
public class WSClass
{
[SqlFunction]
public static int SendMessage(string User, string Password, string SenderAddress, string ReceiverAddress, Guid MessageID, string ValidityPeriodSmpp, string Message)
{
WS.Authentication auth = new WS.Authentication();
auth.User = User;
auth.Password = Password;
WS.WsSubmitSm s = new WS.WsSubmitSm();
s.SenderAddress = SenderAddress;
s.ReceiverAddress = ReceiverAddress;
s.MessageId = MessageID;
s.ValidityPeriodSmpp = ValidityPeriodSmpp;
s.MessageText = Message;
WS.WsSubmitSm[] ss = new WS.WsSubmitSm[1];
ss[0] = s;
WS.SmsHostWs SendSMS=new WS.SmsHostWs();
SendSMS.AuthenticationValue=auth;
WS.WsSubmitSmResp Resp=new WS.WsSubmitSmResp();
Resp=SendSMS.SumbitSm(ss)[0];
return Resp.ErrorCode;
}
public static string MessageState(string User, string Password, Guid MessageID)
{
WS.Authentication auth = new WS.Authentication();
auth.User = User;
auth.Password = Password;
WS.WsQuerySm q=new WS.WsQuerySm();
q.MessageId=MessageID;
WS.WsQuerySm[] qq = new WS.WsQuerySm[1];
qq[0] = q;
WS.SmsHostWs SendSMS=new WS.SmsHostWs();
SendSMS.AuthenticationValue=auth;
WS.WsQuerySmResp[] Resp = new WS.WsQuerySmResp[1];
Resp[0]=SendSMS.QuerySm(qq)[0];
return Resp[0].StateName;
}
}
Сохраним этот текст в нашей папке в файл MyCLR.cs
4) Компилируем нашу библиотеку:
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CSC" /target:library /out:WSSMSHost.dll *.cs
На выходе файл WSSMSHost.dll
5) Создаем сборку сериализации XML:
"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\sgen.exe" /a:WSSMSHost.dll
На выходе файл WSSMSHost.XmlSerializers.dll
Вроде бы ВСЁ. Дальше регистрируем наши библиотеки и пробуем отправить сообщения/прочитать их состояние.
CREATE ASSEMBLY ClrSMSHost
FROM 'C:\SMS\WSSMSHost.dll'
WITH PERMISSION_SET = UNSAFE;
GO
CREATE ASSEMBLY [ClrSMSHost.XmlSerializers]
FROM 'C:\SMS\WSSMSHost.XmlSerializers.dll'
WITH PERMISSION_SET = SAFE;
GO
Создаём функции:
CREATE FUNCTION [dbo].[SendMessage]
(
@User nvarchar(255),
@Password nvarchar(255),
@SenderAddress nvarchar(255),
@ReceiverAddress nvarchar(255),
@MessageID uniqueidentifier,
@ValidityPeriodSmpp nchar(16),
@Message nvarchar(max)
)
RETURNS [int] WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [ClrSMSHost].[WSClass].[SendMessage]
GO
CREATE FUNCTION [dbo].[MessageState]
(
@User nvarchar(255),
@Password nvarchar(255),
@MessageID uniqueidentifier
)
RETURNS [nvarchar](max) WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [ClrSMSHost].[WSClass].[MessageState]
GO
А теперь покажу как работать с этими функциями:
--Таблица для тестов
create table MyTable (MyID uniqueidentifier, Phone char(11), MyMessage nvarchar(50))
--Вставим всего 2 записи, для демонстрации
insert into MyTable
select NewID(), '7904ххххххх', 'Message1'
union all
select NewID(), '7904ххххххх', 'Message2'
Ну и отправим сообщения:
select MyID, MyMessage, [dbo].[SendMessage] ('MyUser', 'MyPassword', 'SMS-Host.ru',
Phone, MyID, '000002233429000R', MyMessage)
from MyTable
где 'MyUser', 'MyPassword' - это логин и пароль на доступ к Веб сервису, а '000002233429000R' - время жизни сообщения (Время, в течение которого сервер будет пытаться отправить сообщение, если получатель недоступен. Если по истечении времени жизни сообщение все еще не доставлено, попытки доставки прекращаются и сообщение считается недоставленным.
Сообщение может стать недоставленным и до истечения времени жизни, если соответствующие сервисы оператора связи сообщили о невозможности доставки (например, номер получателя не существует). По умолчанию время жизни сообщения 2 суток с момента первой попытки отправки.), Для данного поля допустим только интервальный формат времени, то есть в форме YYMMDDhhmmsstnnR - Формат времени в стандарте SMPP v3.4.
Результат запроса:
- BF23FCC4-00DD-4309-BF5A-AC4CAA22160E Message1 0
- 97063992-182E-4816-B648-35B8AE6FFBAE Message1 0
В ответе на запрос каждый элемент содержит атрибут ErrorCode Коды ошибок подразделяются на следующие категории:
Диапазон значений Категория
- 0 Ошибок нет
- 1-10 Текст сообщения не прошел проверку
- 11-20 Номер получателя не прошел проверку
- 21-30 Номер отправителя не прошел проверку
- 31-40 Ошибки при постановке в очередь на отправку (например, если сообщение с таким идентфикатором уже отправлялось)
Отслеживать состояние сообщений можно запросом:
select MyID, [dbo].[MessageState] ('MyUser', 'MyPassword', MyID)
from MyTable
Результат запроса:
BF23FCC4-00DD-4309-BF5A-AC4CAA22160E Сообщение доставлено
97063992-182E-4816-B648-35B8AE6FFBAE Сообщение доставлено
Таким образом, практически на коленке, реализована задача…надеюсь кому-то этот пост будет полезен. Если у вас возникнут вопросы, то буду рад вам помочь.