Push-повідомлення на Android в InterSystems Ensemble на прикладі Штрафів ГИБДД

У багатьох мобільних додатках, які дозволяють дізнаватися штрафи та оплачувати їх, є можливість отримувати інформацію про нові штрафи. Для цього зручно реалізовувати відсилання Push-повідомлень на пристрої клієнтів.
Наше додаток по оплаті штрафів не стало винятком. Серверна частина у нас реалізована на платформі Ensemble, в якій з версії 2015.1 дуже вчасно з'явилася вбудована підтримка push-повідомлень.

Для початку трохи теорії
Push-повідомлення — це один із способів поширення інформації, коли дані надходять від постачальника до користувача на основі встановлених параметрів.

В загальному випадку для мобільних пристроїв процес повідомлення виглядає так:



Для повідомлення користувачів мобільних додатків використовуються сервіси доставки повідомлень, дані з яких отримують пристрою. Причому просто так відправити повідомлення не можна. Користувач повинен бути підписаний на канал push-повідомлень або на повідомлення від конкретного додатка.

Для роботи з push-повідомленнями в Ensemble є наступні сутності:

» EnsLib.PushNotifications.GCM.Operation — бізнес-операція для відсилання push-повідомлень на сервер Google Cloud Messaging Services (GCM). Операція також дозволяє відправляти повідомлення з додатком відразу на кілька пристроїв.

» EnsLib.PushNotifications.APNS.Operation — бізнес-операція, яка відправляє повідомлення на сервер Apple Push Notifications. Для відправки повідомлень в кожне реалізоване додаток знадобиться окремий SSL сертифікат.

» EnsLib.PushNotifications.IdentityManager — бізнес-процес Ensemble. Дозволяє відправляти повідомлення користувачам, не замислюючись про кількість і типи його пристроїв. По суті, Identity Manager містить таблицю, яка ставить у відповідність одному ідентифікатора користувача всі його пристрою. Бізнес-процес Identity мападег'а отримує повідомлення від інших компонентів продукції і перенаправляє їх маршрутизатора, який в свою чергу розсилає всі GCM-повідомлення в GCM-операцію, і кожне APNS-повідомлення в APNS-операцію, зконфігуровану з відповідним SSL сертифікатом.

» EnsLib.PushNotifications.AppService – бізнес-служба, що дозволяє відправляти push-повідомлення, згенеровані поза продукції. По суті, саме повідомлення може генеруватися десь всередині Ensemble незалежно від продукції, служба дозволяє відправляти повідомлення з Ensemble. Докладно всі ці класи описані в розділі документації Ensemble "Configuring and Using Ensemble Push Notifications".

Тепер про те, як процес повідомлень реалізували ми
У нашому випадку повідомлення генеруються спеціально розробленим бізнес-процесом усередині продукції, тому служба нам не знадобилася. Також на даному етапі у нас є тільки Android-додаток, тому APNS-операцією ми теж поки не користувалися. По суті ми використовували самий низькорівневий спосіб надсилання безпосередньо через GCM-операцію. В подальшому, при реалізації iOS-версією програми, зручно буде реалізувати роботу з повідомленнями через Identity Manager, щоб не довелося аналізувати тип і кількість пристроїв. Але зараз розповімо докладніше про GCM.

Для надсилання повідомлень необхідно реалізувати процес всередині продукції і підключити потрібну бізнес-операцію. На поточний момент у нас два окремих процесу відсилання Push-повідомлень, кожен зі своєю логікою: повідомлення за новими штрафами, повідомлення про закінчення дії знижки за штрафу. Про кожному типі розповімо трохи докладніше.

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

  • Створюємо пустий SSL конфігурацію для роботи операції, додаємо її в конфігурацію бізнес-операції (тільки для GCM!).
  • Додаємо в продукцію операцію класу EnsLib.PushNotifications.GCM.Operation, налаштовуємо її параметри:
NotificationProtocol: HTTP
PushServer: http://android.googleapis.com/gcm/send

Налаштування операції в результаті виглядають так:


Нам потрібно зберігати ідентифікатор клієнта, пристрої (ідентифікатори і типи), список документів (водійських посвідчень і свідоцтв про реєстрацію автомобіля). Всю цю інформацію отримуємо запити від клієнта при підписці на повідомлення. Отже, нам потрібні класи:

Client – для зберігання клієнтів, App – для зберігання пристроїв, Doc – для зберігання даних документів:

Class penalties.Data.Doc Extends %Persistent
{
///тип документа (СТС або ВУ)
Property type As %String;
///ідентифікатор документа
Property value As %String;
}
Class penalties.Data.App Extends %Persistent
{
///тип пристрою (GCM або APNS)
Property Type As %String;
///ідентифікатор пристрою 
Property ID As %String(MAXLEN = 2048);
}
Class penalties.Data.Client Extends %Persistent
{
/// поштова адреса клієнта з Google Play Services, використовуємо як ідентифікатор
Property Email As %String;
///список пристроїв клієнта
Property AppList As list Of penalties.Data.App;
///перелік документів, на які підписався клієнт
Property Docs As list Of penalties.Data.Doc;
}

Для розсилки повідомлень про нові штрафи нам треба розуміти, які штрафи клієнту відправляти, а які він уже бачив, при вході в додаток. Для цього у нас є клас NotificationFlow, в якому ми відзначаємо, що клієнт вже отримував інформацію про штраф.

Class penalties.Data.NotificationFlow Extends %Persistent
{
///ідентифікатор клієнта (в нашому випадку email)
Property Client As %String;
///ідентифікатор штрафу
Property Penalty As %String;
/// ознака відправлення
Property Sent As %Boolean;
}

Для зручності сприйняття нижче при згадці класів опустимо імена пакетів. За змістом класів зрозуміло, як буде виглядати процес за новими штрафами: для кожного клієнта проходимо за списком документів, робимо з ним запит штрафів в ГІС ГМП (Державна інформаційна система про державних і муніципальних платежі), перевіряємо отримані штрафи на наявність у NotificationFlow, якщо знайдено – видаляємо зі списку, в результаті формуємо список штрафів, про які треба повідомити клієнта, пробегаемся за списком пристроїв клієнта і відправляємо на кожне з них push повідомлення.

Верхній рівень:


де clientkey – властивість контексту, значенням за замовчуванням якого є ідентифікатор першого по порядку клієнта має підписку, що зберігається в класі Client.

Підпроцес виглядає так:


Заглянемо всередину блоків foreach:


Після цього блоку foreach маємо готовий запит EnsLib.PushNotifications.NotificationRequest, який залишилося додати текст повідомлення. Це робимо в блоці foreach за Doc'ам.


І невеликий шматок коду, який заповнює дані запиту:

ClassMethod getPenaltyforNotify(client As penalties.Data.Client, penaltyResponse As penalties.Operations.Response.getPenaltiesResponse, notificationRequest As EnsLib.PushNotifications.NotificationRequest)
{
set json="",count=0
set key="" for 
{
set value=penaltyResponse.penalties.GetNext(.key)
quit:key=""
set find=0
set res=##class(%ResultSet).%New("%DynamicQuery:SQL")
set exec="SELECT * FROM penalties_Data.NotificationFlow WHERE (Penalty = ?) AND (Client = ?)"
set status=res.Prepare(exec)
set status=res.Execute(value.billNumber,client.Email)
if $$$ISERR(status) do res.%Close() kill res continue
while res.Next()
{
if res.Data("Sent") set find=1
}
do res.%Close() kill res
if find {do penaltyResponse.penalties.RemoveAt(key), penaltyResponse.penalties.GetPrevious(.key)}
else {
set count=count+1
do notificationRequest.Data.SetAt("single","pushType")
for prop="billNumber","billDate","validUntil","amount","addInfo","driverLicence","regCert"
{
set json=$property(value,prop)
set json=$tr(json,"""","")
if json="" continue
do notificationRequest.Data.SetAt(json,prop)

}
set json=""
set notObj=##class(penalties.Data.NotificationFlow).%New()
set notObj.Client=client.Email
set notObj.Penalty=value.billNumber
set notObj.Sent=1
do notObj.%Save()
}
}
if count>1 {
set keyn="" for {
do notificationRequest.Data.GetNext(.keyn)
quit:keyn=""
do notificationRequest.Data.RemoveAt(keyn)
}
do notificationRequest.Data.SetAt("multiple","pushType")
do notificationRequest.Data.SetAt(count,"penaltiesCount")
}
}

Процес по знижкам на оплату штрафів реалізований дещо інакше. На верхньому рівні:


Відбір штрафів зі знижкою виконується наступним кодом:

ClassMethod getSaleforNotify()
{
//на всякий випадок почистимо тимчасову глобаль
kill ^mtempArray
set res=##class(%ResultSet).%New("%DynamicQuery:SQL")
//пошукаємо все ще не оплачені штрафи зі знижкою
set exec="SELECT * FROM penalties_Data.Penalty WHERE status!=2 AND addInfo LIKE '%Знижка%'"
set status=res.Prepare(exec)
set status=res.Execute()
if $$$ISERR(status) do res.%Close() kill res quit
while res.Next()
{
set discDate=$piece(res.Data("addInfo"),"Знижка 50% при оплаті до: ",2)
set discDate=$extract(discDate,1,10)
set date=$zdh(discDate,3)
set dayscount=date-$p($h,",")
//відправляти будемо за 5,2,1 та 0 днів
if '$lf($lb(5,2,1,0),dayscount) continue
set doc=$s(res.Data("regCert")'="":"sts||"_Res.Data("regCert"),1:"vu||"_Res.Data("driverLicence"))
set clRes=##class(%ResultSet).%New("%DynamicQuery:SQL")
//пошукаємо клієнтів, підписаних на документ
set clExec="SELECT * FROM penalties_Data.Client WHERE (Docs [ ?)"
set clStatus=clRes.Prepare(clExec)
set clStatus=clRes.Execute(doc)
if $$$ISERR(clStatus) do clRes.%Close() kill clRes quit
while clRes.Next()
{
//складемо зручний список, по якому потім будемо бігати
set ^mtempArray($job,clRes.Data("Email"),res.Data("billNumber"))=res.Data("billDate")
}
do clRes.Close()
}
do res.Close()
}

На виході маємо глобал з розбивкою штрафів по клієнтам. Тепер треба пробігтися по цьому глобалу і відправити кожного клієнта його штраф, попередньо переконавшись, що він ще не оплачений де-небудь ще:


Провалюємося в цикл по штрафах:


Власне різниця між процесами в наступному: в першому випадку обов'язково пробегаемся по всім нашим клієнтам, у другому відбираємо тільки клієнтів, у яких є штрафи певного виду; в першому випадку для декількох штрафів шолом одне повідомлення із загальною кількістю (бувають клієнти, які за день встигають нахапати багато штрафів), у другому випадку по кожній пропозиції окремо.
В процесі налагодження зіткнулися з невеликою особливістю наших повідомлень, з-за якої деякі системні методи нам довелося змінити. Одним з параметрів нашого повідомлення ми передаємо номер штрафу, який в загальному вигляді виглядає приблизно так «12345678901234567890». Системні класи операції з відправлення повідомлень перетворюють такі рядки в кількості, а GCM сервіс, на жаль, отримавши таке велике число дивується і повертає «Bad Request».

Тому переопределили системний клас операції, в ньому викликаємо свій метод ConvertArrayToJSON, всередині якого викликаємо ..Quote з другим параметром рівним 0, тобто не перетворюємо рядки, які складаються тільки з цифр числа, а залишаємо їх рядками:

Method ConvertArrayToJSON(ByRef pArray) As %String
{
#dim tOutput As %String = ""
#dim tSubscript As %String = ""
For {
Set tSubscript = $ORDER(pArray(tSubscript))
Quit:tSubscript=""
Set:tOutput'="" tOutput = tOutput _ ","
Set tOutput = tOutput _ ..Quote(tSubscript) _ ": "
If $GET(pArray(tSubscript))'="" {
#dim tValue = pArray(tSubscript)
If $LISTVALID(tValue) {
#dim tIndex As %Integer
// $LIST .. aka an array
// NOTE: This only handles an array of values scalar
Set tOutput = tOutput _ "[ "
For tIndex = 1:1:$LISTLENGTH(tValue) {
Set:tIndex>1 tOutput = tOutput _ ", "
Set tOutput = tOutput _ ..Quote($LISTGET(tValue,tIndex),0)
}
Set tOutput = tOutput _ " ]"
} Else {
// Simple string
Set tOutput = tOutput _ ..Quote(tValue,1)
}
} Else {
// Child elements
#dim tTemp
Kill tTemp
Merge tTemp = pArray(tSubscript)
Set tOutput = tOutput _ ..ConvertArrayToJSON(.tTemp)
}
}
Set tOutput = "{" _ tOutput _ "}"
Quit tOutput
}

Інших проблем у процесі реалізації знайдено не було. Отже, основні речі, які треба зробити для відправлення повідомлень:

  • додати потрібну операцію
  • вибудувати процес, що заповнює наступні властивості запиту: AppIdentifier — Server API Key, отриманий при реєстрації сервісу в GCM, Identifiers — список ідентифікаторів пристроїв, до яких звертаємося, Service — тип пристрою, до якого звертаємося (у нашому випадку «GCM»), Data — дані запиту (пам'ятаємо, що масив будується за принципом " ключ-значення).
Власне, все. За рахунок використання готових компонентів Ensemble реалізація процесу займає пару годин, включаючи налагодження і тестування.

На виході маємо задоволених клієнтів, вчасно дізнаються про нові штрафи і вчасно людей, які згадували про знижки.


Найближчим часом запускаємо IOS-додаток з відповідною реалізацією Push-повідомлень, про це напишемо окрему статтю.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.