Создание модулей уведомлений

Материал из ISPWiki
Перейти к: навигация, поиск

Общая информация

BILLmanager позволяет расширять возможности механизма уведомлений сотрудников и клиентов за счет добавления модулей уведомлений (отвечают за тип уведомления, могут использовать различные модули шлюзов уведомлений), а так же модулей шлюзов уведомлений ко встроенным или собственным модулям уведомлений.

По умолчанию BILLmanager поддерживает три типа уведомлений:

  • SMS сообщения - короткие сообщения отправляемые на номера телефонов указанные в профилях пользователей
    • Реализована только отправка сообщений
    • Для отправки используются шлюзы
  • Email сообщения - сообщения на email адреса пользователей (часть уведомлений может быть отправлена только этим способом, так как реализуют специфичный для email уведомлений функционал)
    • Реализована отправка и получение уведомлений
    • Для отправки и получения уведомлений используются шлюзы
  • Сообщения в меню уведомлений - сообщения отображаемые в личном кабинете в разделе "Уведомления", могут быть сформированы на основе отправляемого email сообщения
    • Реализована только отправка сообщений
    • Шлюзы не поддерживаются

Все файлы модулей и шлюзов вызывается с использованием системных вызовов, либо в фоне и могут быть реализованы на любом языке программирования, поддерживающем работу в потоками ввода/вывода

Механизм работы уведомлений

Работы уведомлений в BILLmanager состоит из следующих этапов:

  • Проверка подписки клиента на тип уведомления
  • Генерация XML уведомления нужного типа
  • Передача XML, содержащий в себе шаблон уведомления, XML самого уведомления, а так же данные о провайдере, связанном с уведомленим и пользователе, которому отправляется уведомление, модулю соответствующему типу уведомления

Внутри модуля выполняются следующие действия:

  • Определяется типа шаблона уведомления на основе его структуры, после чего:
    • Для XSLT шаблонов происходит наложение шаблона на XML уведомления
    • Для EJS шаблонов XML переводится в JSON и передается 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 документа следующий
<?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-standard-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++.

Файл имеет следующий формат (на примере интеграции с 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)
  }
}