Download PDF
Download page Создание модулей уведомлений.
Создание модулей уведомлений
BILLmanager позволяет расширять возможности механизма уведомлений сотрудников и клиентов за счет добавления модулей уведомлений (отвечают за тип уведомления, могут использовать различные модули шлюзов уведомлений), а так же модулей шлюзов уведомлений ко встроенным или собственным модулям уведомлений.
По умолчанию BILLmanager поддерживает три типа уведомлений:
- SMS сообщения (ntsms) — короткие сообщения отправляемые на номера телефонов указанные в профилях пользователей
- Реализована только отправка сообщений
- Для отправки используются шлюзы
- Email сообщения (ntemail) — сообщения на email адреса пользователей (часть уведомлений может быть отправлена только этим способом, так как реализуют специфичный для email уведомлений функционал)
- Реализована отправка и получение уведомлений
- Для отправки и получения уведомлений используются шлюзы
- Сообщения в меню уведомлений (ntinternal) — сообщения отображаемые в личном кабинете в разделе "Уведомления", могут быть сформированы на основе отправляемого email сообщения
- Реализована только отправка сообщений
- Шлюзы не поддерживаются
Все файлы модулей и шлюзов вызывается с использованием системных вызовов, либо в фоне и могут быть реализованы на любом языке программирования, поддерживающем работу в потоками ввода/вывода
Механизм работы уведомлений
Работа уведомлений в BILLmanager состоит из следующих этапов:
- Проверка подписки клиента на тип уведомления.
- Генерация XML уведомления нужного типа.
- Передача XML, содержащего в себе шаблон уведомления, XML самого уведомления, а так же данные о провайдере, связанном с уведомлением и пользователе, которому отправляется уведомление, модулю соответствующему типу уведомления.
Внутри модуля выполняются следующие действия:
- Определяется тип шаблона уведомления на основе его структуры, после чего:
- Для XSLT шаблонов происходит наложение шаблона на XML уведомления
- Для EJS шаблонов XML переводится в JSON и передается EJS шаблонизатору вместе с шаблоном
- При необходимости в результирующем тексте производится замена макросов на необходимые значения.
- При отсутствии поддержки шлюзов уведомлений производится отправка уведомления непосредственно средствами модуля.
- При наличии поддержки шлюзов, на основе переданного идентификатора провайдера, а так же типа модуля уведомлений выбирается шлюз для отправки уведомления.
- Параметры шлюзы, необходимые контактные данные, текст сообщения и его заголовок передаются модулю шлюза для отправки.
Чтобы в BILLmanager для типа уведомления отображался шаблон:
Поместите в директорию etc/notice/<тип_уведомления>/ xml файл, содержащий имя шаблона, которое будет отображаться в интерфейса BILLmanager.
Пример XML файла
<?xml version="1.0" encoding="UTF-8"?> <doc> <template notice="имя_файла_ejs"> <locale>notice_name</locale> <locale_ru>Имя шаблона</locale_ru> </template> </doc>
XML- <locale>notice_name</locale> — название шаблона интерфейсе на английском;
- <locale_ru>имя_шаблона</locale_ru> — название шаблона в интерфейсе на русском и т.д.
Имя xml файла должно быть составлено по шаблону: billmgr_mod_<имя_модуля>_notice
Поместите в директорию etc/notice/<тип_уведомления>/ EJS файл, содержащий шаблон уведомления.
Имя EJS файла должно быть составлено по шаблону: <имя_модуля>_<код_страны>. Например, example_ru.ejs.
Не указывайте код страны, чтобы платформа считала шаблон английским.<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <style type="text/css"> b { color: #333; } .sP {font-family:Arial,Helvetica,sans-serif; font-size:12px;} .sA {color:blue;} .sH2 {font-family:Arial,Helvetica,sans-serif; font-size:13px; font-weight:bold;} .sP {font-family:Arial,Helvetica,sans-serif; font-size:12px;} .sLI {font-family:Arial,Helvetica,sans-serif; font-size:12px;} .sUL {margin-top: 0px; padding-left: 30px;} #subject {font-family:Arial,Helvetica,sans-serif; font-size:15px; margin-top:6px; margin-left:10px; margin-right:10px; margin-bottom:5px;} #content {margin: 8px 9px;} </style> </head> <body bgcolor="#ffffff"> <div> <table width="90%" align="center" cellpadding="0" cellspacing="0" border="0"> <tr><td bgcolor="#e5e5e5"> <div id="subject"><b><%= project.name %>: Product Auto Renew</b></div> <div id="summary">We want to inform you that the following services were renewed automatically ...</div> </td></tr> <tr><td> <div id="content"> <p class="sP">Dear <%= user.realname %>!</p> <p class="sP">This notice is being sent to you to inform you of the following products renewal:</p> <ul class="sUL"> <% for (var i = 0; i < elem.length; ++i) { %> <li class="sLI"> <b><%= elem[i].item.name %></b> <%= elem[i].item.domain_puny_decode && elem[i].item.domain_puny_decode != '' ? '-' + elem[i].item.domain_puny_decode : '' %>.</p> </li> <% } %> </ul> <p class="sP"><br/>Kind regards!<br/><%= project.name %> team</p> </div> </td></tr> </table> </div> </body> </html>
XML
Архитектура модулей
Для работы с BILLmanager каждый модуль уведомления или шлюза должен уметь обрабатывать определенный набор команд описанный ниже. В случае отсутствия поддержки какой-либо команды, либо выводе модулем данных в не поддерживаемом формате приведет к невозможности BILLmanager взаимодействовать с модулем
Архитектура модуля уведомлений
Модуль устанавливается в каталог /usr/local/mgr5/notify/ и должен уметь обрабатывать следующие команды:
- --command process — обработка очереди уведомлений на отправку. Очередь уведомлений хранится в таблице notifytask со следующей структурой:
- id — идентификатор уведомления. Генерируется автоматически
- modulename — имя модуля типа уведомлений. Стандартные значения ntemail, ntsms, ntinternal
- filename — имя файла с данными уведомления
- priority — приоритет отправки уведомления. При обработке очереди уведомлений рекомендуется выбирать из базы данных уведомления небольшими порциями отсортированными по убыванию приоритета. В этом случае, при проведении объемных рассылок, важные уведомления будут доставлены в срок
- error_count — количество попыток отправки уведомления завершившихся ошибкой
- forcedonothing — флаг отправки уведомления игнорируя файл billmgr.DoNothing, создаваемый во время переноса данных из другого биллинга
- err_info — текст сообщения об ошибке отправки уведомления
- createdate — дата постановки уведомления в очередь
- --command getmessage --gate gate_id, где gate_id — код шлюза, для которого производится обработка получения сообщения. В качестве параметра --gate, может быть передано значение all, в этом случае необходимо обработать получения сообщений всеми шлюзами типа уведомления
- --command features — запрос параметров модуля уведомлений. В ответ модуль должен вывести в стандартный поток вывода XML описание поддерживаемого функционала. Формат XML документа следующий
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<features>
<feature name="html"/>
<feature name="sms"/>
<feature name="call"/>
</features>
<contact_type>тип контакта</contact_type>
</doc>
Порядок обработки очереди, а так же действий с полученными сообщениями определяется разработчиком. Так, например можно реализовать следующие сценарии:
- Непрерывна работа модуля с периодической проверкой появления новых сообщений в очереди
- Добавления входящих сообщений в запросы клиентов
- Управление услугами через входящие сообщения
- Запрос и вывод информация управляющими командами
Реализация модуля с использованием заголовочных файлов BILLmanager предполагает обязательную реализацию методов
- virtual mgr_xml::Xml Features() const = 0; — возврат XML описания поддерживаемых возможностей. Вывод данных в поток вывода будет произведен автоматически
- virtual bool UserNotify(const string& filename) const = 0; — отправка пользователю уведомления описанной в файле с именем переданном в параметре
- virtual void GetMessage(string gate_id = 0) const = 0; — обработка получения входящих сообщения для шлюза с кодом gate_id
Дополнительно может быть переопределен метод
- virtual int ProcessQueue() const; — обработка очереди уведомлений, которые необходимо отправить пользователям. UserNotify вызывает как раз при работе этого метода класса и может быть определен пустым в случае реализации всей необходимой логики в ProcessQueue
Архитектура модуля шлюза
Модуль устанавливается в каталог /usr/local/mgr5/gate/ и должен уметь обрабатывать следующие команды:
- --command features — запрос параметров модуля шлюза. В ответ модуль должен вывести в стандартный поток вывода XML описание поддерживаемого функционала. Формат XML документа следующий
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<features>
<feature name="outgoing"/>
<feature name="ingoing"/>
<feature name="formtune"/>
<feature name="check_connection"/>
</features>
<notify_module>тип модуля уведомлений</notify_module>
</doc>
- --command formtune — модификация формы настроек параметров шлюза. На вход модулю передается XML описание формы параметров шлюза, на выход модуль должен вернуть модифицированную XML описания формы настроек
- --command check_connection — проверка подключения к шлюзу с указанными параметрами. На вход модулю передается XML описание формы параметров шлюза, с добавлением введенных на форме данных, на выход модуль должен вернуть XML описание формы настроек (XML может быть изменена при необходимости)
- --command outgoing и --command ingoing — не вызываются BILLmanager напрямую в общем случае, исключением является отправка СМС сообщений, в этом случае реализация --command outgoing обязательна в определенном формате. В остальном случае могут быть описаны любые другие команды, которые будут вызываться модулями уведомлений. Ниже описана работа стандартным модулей с этими командами:
- --command outgoing — отправка уведомления. На вход модулю передается XML описание сообщения следующего вида:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<gateway> - параметры шлюза
<param>value</param>
<param>value</param>
...
<param>value</param>
<xmlparams>Параметры подключения шлюза в виде XML</xmlparams>
</gateway>
<message>текст сообщения</message>
<user> - параметры пользователя, которому отправляется уведомление
<param>value</param>
<param>value</param>
...
<param>value</param>
</user>
<project> - параметры провайдера
<param>value</param>
<param>value</param>
...
<param>value</param>
</project>
</doc>
На выход модуль должен вернуть пустую XML или XML описания ошибки
- --command ingoing (используется для получения почты) — на вход модулю передается XML с параметрами шлюза, как описано выше, на выход нужно вернуть XML со списком полученных сообщений вида
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<messages>
<message>исходный текст сообщения</message>
...
<message>исходный текст сообщения</message>
</messages>
</doc>
Реализация модуля с использованием заголовочных файлов BILLmanager предполагает обязательную реализацию методов virtual mgr_xml::Xml Features() const = 0; — возвращается XML описание поддерживаемых возможностей virtual mgr_xml::Xml Ingoing(mgr_xml::Xml& input) const = 0; — на вход получает XML с параметрами шлюза (параметры могут быть так же получены методом GateParam), возвращает список сообщений в описанном формате virtual void Outgoing(mgr_xml::Xml& input) const = 0; — на вход получает XML с параметрами шлюза и сообщением для отправки (параметры могут быть так же получены методом GateParam)
Примеры модулей
C++ (с использованием библиотек BILLmanager в пакете разработчика)
Использование заголовочных файлов BILLmanager для разработки собственных модулей обработчиков доступно с версии BILLmanager 5.58.0. Кроме приведенного упрощенного примера, можно изучить примеры представленные в пакете разработчика BILLmanager — billmanager-[Редакция BILLmanager]-devel, например:
yum install billmanager-devel
или
yum install billmanager-corporate-devel
yum install billmanager-devel — для BILLmanager;
yum install billmanager-corporate-devel — для BILLmanager Corporate.
После этого примеры можно найти в директории:
/usr/local/mgr5/src/examples
C++ (с использованием библиотек BILLmanager)
Реализация модуля уведомлений и шлюза для отправки и получения XMPP сообщений представлена по ссылке
https://github.com/ISPsystemLLC/jabber
Пример реализован на C++, с использования заголовочных файлов COREmanager и BILLmanager, а так же библиотеки Gloox. Пример состоит из:
- Модуль уведомлений ntjabber — основной файл ntjabber.cpp. Отвечает за тип уведомлений, позволяет добавлять шаблоны уведомлений и создавать рассылки нужного типа
- Модуль шлюза для подключения к Jabber серверу gwjabber — основной файл gwjabber.cpp. Отвечает за отправку уведомлений на Jabber контакт пользователя, а так же обрабатывает входящие сообщения
- XML файлы, описывающие необходимые сообщения и параметры подключения с серверу:
- billmgr_mod_ntjabber.xml — добавляет на форму редактирования пользователей поле Jabber контакта (отображение и сохранение значения происходит автоматически согласно механизму описанному по ссылке), добавляет описание типа уведомлений
- billmgr_mod_gwjabber.xml — описывает параметры подключения к jabber-серверу, а так же описывает наименование модуля подключения
- Описание поля базы данных jabber — описывает дополнительное поле для таблицы user базы данных BILLmanager
- Логотип шлюза billmanager-plugin-gwjabber.png — добавляет отображение логотипа XMPP на форму выбора модуля шлюза
- Файл описания сборки Makefile
Другие языки программирования
XML модулей
XML описание модуля сохраняется в файл /usr/local/mgr5/etc/xml/billmgr_mod_ntxxx.xml, для модуля уведомлений и в файл /usr/local/mgr5/etc/xml/billmgr_mod_gwxxx.xml для модулей шлюзов, где xxx — уникальное имя модуля. В данном примере рассматривается только XML для модуля шлюза, для XML файла модуля уведомлений смотрите пример на C++.
Файл имеет следующий формат (на примере интеграции с ePochta SMS)
<?xml version="1.0" encoding="UTF-8"?>
<mgrdata>
<plugin name="gwepochta"> <!-- описание плагина для отображения в BILLmanager -->
<group>gateway</group> <!-- привязка плагина к разделу шлюзов сообщений -->
<author>BILLmanager team</author> <!-- автор модуля -->
</plugin>
<metadata name="gateway.gwepochta"> <!-- описание настроек модуля -->
<form>
<field name="login">
<input type="text" name="login" required="yes" identifier="yes"/>
</field>
<field name="password">
<input type="password" name="password" required="yes"/>
</field>
<field name="sender">
<input type="text" name="sender" required="yes"/>
</field>
</form>
</metadata>
<lang name="ru">
<messages name="plugin"> <!-- сообщения для описания плагина -->
<msg name="desc_short_gwepochta">ePochta SMS</msg>
<msg name="desc_full_gwepochta">ePochta SMS</msg>
<msg name="price_gwepochta">Бесплатно</msg>
</messages>
<messages name="gateway.gwepochta"> <!-- сообщения для формы настроек модуля -->
<msg name="login">Логин</msg>
<msg name="password">Пароль</msg>
<msg name="sender">Отправитель</msg>
<msg name="hint_login">Логин в личный кабинет ePochta SMS</msg>
<msg name="hint_password">Пароль от личного кабинета</msg>
<msg name="hint_sender">Подпись отправителя сообщения</msg>
</messages>
<messages name="gateway_include"> <!-- наименование модуля для отображения в различных разделах BILLmanager -->
<msg name="module_gwepochta">Сервер ePochta SMS</msg>
<msg name="gwepochta">ePochta SMS</msg>
<msg name="desc_gwepochta">ePochta SMS</msg>
</messages>
</lang>
<lang name="en"> <!-- английская локализация сообщений -->
<messages name="plugin">
<msg name="desc_short_gwepochta">ePochta SMS</msg>
<msg name="desc_full_gwepochta">Server ePochta SMS</msg>
<msg name="price_gwepochta">Free</msg>
</messages>
</lang>
</mgrdata>
Go
package main
import "bytes"
import "log"
import "encoding/xml"
import "flag"
import "fmt"
import "os"
import "io/ioutil"
import "net/http"
func request(operation, username, password, phone, message, sender string) (string, string) {
type SMS struct {
XMLName xml.Name `xml:"SMS"`
Operation string `xml:"operations>operation"`
Username string `xml:"authentification>username"`
Password string `xml:"authentification>password"`
Message string `xml:"message>text"`
Sender string `xml:"message>sender"`
Number string `xml:"numbers>number"`
}
v := &SMS{
Operation: operation,
Username: username,
Password: password,
Message: message,
Sender: sender,
Number: phone,
}
output, err := xml.MarshalIndent(v, " ", " ")
log.Print("REQUEST: " + string(output))
if err != nil {
return "", ""
}
resp, err := http.Post("http://api.myatompark.com/members/sms/xml.php", "image/jpeg", bytes.NewBuffer(output))
if err != nil {
return "", ""
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
log.Print("RESPONSE: " + string(body))
type Response struct {
XMLName xml.Name `xml:"RESPONSE"`
Status int `xml:"status"`
}
r := Response{Status: 1}
resperr := xml.Unmarshal(body, &r)
if resperr != nil {
log.Printf("error: %v", resperr)
return "", ""
}
if r.Status != 0 {
return string(body), "error"
}
return string(body), ""
}
func main() {
f, _ := os.OpenFile("var/gwepochta.log", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
defer f.Close()
log.SetOutput(f)
command_ptr := flag.String("command", "features", "gateway command")
flag.Parse()
if *command_ptr == "features" {
type Feature struct {
XMLName xml.Name `xml:"feature"`
Name string `xml:"name,attr"`
}
type Features struct {
XMLName xml.Name `xml:"doc"`
Features []Feature `xml:"features>feature"`
Module string `xml:"notify_module"`
}
v := &Features{
Module: "ntsms",
Features: []Feature {
{Name: "formtune"},
{Name: "check_connection"},
{Name: "outgoing"},
},
}
output, err := xml.MarshalIndent(v, " ", " ")
if err != nil {
fmt.Println("error: %v\n", err)
}
os.Stdout.Write(output)
} else if *command_ptr == "formtune" {
bytes, _ := ioutil.ReadAll(os.Stdin)
os.Stdout.Write(bytes)
} else if *command_ptr == "check_connection" {
type Doc struct {
XMLName xml.Name `xml:"doc"`
XMLparams string `xml:"xmlparams"`
Login string `xml:"login"`
Password string `xml:"password"`
}
bytes, _ := ioutil.ReadAll(os.Stdin)
v := Doc{XMLparams: "none", Login: "none", Password: "none"}
err := xml.Unmarshal(bytes, &v)
if err != nil {
log.Printf("error: %v", err)
return
}
paramerr := xml.Unmarshal([]byte(v.XMLparams), &v)
if paramerr != nil {
log.Printf("error: %v", paramerr)
return
}
_, error := request("BALANCE", v.Login, v.Password, "", "", "")
if error != "" {
type Error struct {
Type string `xml:"type,attr"`
}
type Doc struct {
XMLName xml.Name `xml:"doc"`
ErrorType Error `xml:"error"`
}
t := &Error {
Type: error,
}
v := &Doc{
ErrorType: *t,
}
output, _ := xml.MarshalIndent(v, " ", " ")
os.Stdout.Write(output)
return
}
os.Stdout.Write(bytes)
} else if *command_ptr == "outgoing" {
bytes, _ := ioutil.ReadAll(os.Stdin)
log.Print(string(bytes))
type Doc struct {
XMLName xml.Name `xml:"doc"`
XMLparams string `xml:"gateway>xmlparams"`
Login string `xml:"login"`
Password string `xml:"password"`
Sender string `xml:"sender"`
Message string `xml:"message"`
Phone string `xml:"user>phone"`
}
v := Doc{XMLparams: "none", Login: "none", Password: "none", Message: "none", Phone: "none"}
err := xml.Unmarshal(bytes, &v)
log.Print(v.XMLparams)
if err != nil {
log.Printf("error: %v", err)
return
}
xml.Unmarshal([]byte(v.XMLparams), &v)
request("SEND", v.Login, v.Password, v.Phone, v.Message, v.Sender)
}
}