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-файла<doc> <template notice="имя_файла_ejs"> <locale>notice_name</locale> <locale_ru>Имя шаблона</locale_ru> </template> </doc>
ПоясненияИмя XML-файла должно быть составлено по шаблону:
billmgr_mod_<имя_модуля>_notice
-
Поместите в директорию etc/notice/<тип_уведомления>/ EJS файл, содержащий шаблон уведомления.
Имя EJS файла должно быть составлено по шаблону:<имя_модуля>_<код_страны>
. Например,example_ru.ejs
.
Не указывайте код страны, чтобы платформа считала шаблон английским.Пример EJS файла
Архитектура модулей
Для работы с 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-документа следующий:
<doc>
<features>
<feature name="html"/>
<feature name="sms"/>
<feature name="call"/>
</features>https://docs.ispsystem.net/pages/editpage.action?pageId=211032476#
<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-документа следующий:
<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-описание сообщения следующего вида:
<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>https://docs.ispsystem.net/pages/editpage.action?pageId=211032476#
</project>
</doc>
На выход модуль должен вернуть пустую XML или XML-описания ошибки
-
- --command ingoing (используется для получения почты) — на вход модулю передается XML с параметрами шлюза, как описано выше, на выход нужно вернуть XML со списком полученных сообщений вида:
<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
После этого примеры можно найти в директории:
/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++.
<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)
}
}