From 9dbf65237d0f620c8161ba6a04ed5967a4801b30 Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Mon, 7 Jan 2019 01:42:54 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BE=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B4=D0=B8=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE?= =?UTF-8?q?=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BE=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B5=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/notifier.js | 181 ++++++++++++++++++++++++++++++++++++++++ models/prms_notifier.js | 83 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 core/notifier.js create mode 100644 models/prms_notifier.js diff --git a/core/notifier.js b/core/notifier.js new file mode 100644 index 0000000..d2980b5 --- /dev/null +++ b/core/notifier.js @@ -0,0 +1,181 @@ +/* + Сервис интеграции ПП Парус 8 с WEB API + Модуль ядра: модуль рассылки уведомлений +*/ + +//------------------------------ +// Подключение внешних библиотек +//------------------------------ + +const _ = require("lodash"); //Работа с массивами и коллекциями +const EventEmitter = require("events"); //Обработчик пользовательских событий +const { ServerError } = require("./server_errors"); //Типовая ошибка +const { SERR_OBJECT_BAD_INTERFACE } = require("./constants"); //Общесистемные константы +const { makeErrorText, validateObject, sendMail } = require("./utils"); //Вспомогательные функции +const prmsNotifierSchema = require("../models/prms_notifier"); //Схемы валидации параметров функций класса + +//-------------------------- +// Глобальные идентификаторы +//-------------------------- + +//Типовые события +const SEVT_NOTIFIER_STARTED = "NOTIFIER_STARTED"; //Модуль рассылки уведомлений запущен +const SEVT_NOTIFIER_STOPPED = "NOTIFIER_STOPPED"; //Модуль рассылки уведомлений остановлен + +//Время отложенного старта цикла отправки сообщений (мс) +const NSEND_LOOP_DELAY = 3000; + +//Интервал проверки очереди отправки сообщений (мс) +const NSEND_LOOP_INTERVAL = 3000; + +//------------ +// Тело модуля +//------------ + +//Класс рассылки уведомлений +class Notifier extends EventEmitter { + //Конструктор класса + constructor(prms) { + //Создадим экземпляр родительского класса + super(); + //Проверяем структуру переданного набора параметров для конструктора + let sCheckResult = validateObject(prms, prmsNotifierSchema.Notifier, "Параметры конструктора класса Notifier"); + //Если структура объекта в норме + if (!sCheckResult) { + //Очередь отправляемых уведомлений + this.messages = []; + //Признак функционирования модуля + this.bWorking = false; + //Признак необходимости оповещения об останове + this.bNotifyStopped = false; + //Флаг работы цикла проверки + this.bInSendLoop = false; + //Идентификатор таймера проверки очереди отправки уведомлений + this.nSendLoopTimeOut = null; + //Запомним параметры отправки E-Mail + this.mail = prms.mail; + //Запомним логгер + this.logger = prms.logger; + //Привяжем методы к указателю на себя для использования в обработчиках событий + this.notifySendLoop = this.notifySendLoop.bind(this); + } else { + throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); + } + } + //Добавление уведомления в очередь отправки + addMessage(prms) { + //Проверяем структуру переданного объекта для старта + let sCheckResult = validateObject( + prms, + prmsNotifierSchema.addMessage, + "Параметры функции добавления уведомления в очередь отправки" + ); + //Если структура объекта в норме + if (!sCheckResult) { + let tmp = _.cloneDeep(prms); + tmp.bSent = false; + this.messages.push(tmp); + } else { + throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); + } + } + //Уведомление о запуске модуля + notifyStarted() { + //Оповестим подписчиков о запуске + this.emit(SEVT_NOTIFIER_STARTED); + } + //Уведомление об остановке модуля + notifyStopped() { + //Оповестим подписчиков об останове + this.emit(SEVT_NOTIFIER_STOPPED); + } + //Перезапуск отправки очереди уведомлений + restartSendLoop() { + //Включаем опрос очереди уведомлений только если установлен флаг работы + if (this.bWorking) { + this.nSendLoopTimeOut = setTimeout(this.notifySendLoop, NSEND_LOOP_INTERVAL); + } else { + //Если мы не работаем и просили оповестить об останове (видимо была команда на останов) - сделаем это + if (this.bNotifyStopped) this.notifyStopped(); + } + } + //Отправка уведомлений из очереди + async notifySendLoop() { + //Выставим флаг - цикл опроса активен + this.bInSendLoop = true; + //Обходим уведомления для отправки + for (let i = 0; i < this.messages.length; i++) { + //Работаем только по неотправленным уведомлениям + if (!this.messages[i].bSent) { + try { + //Отправляем + await sendMail({ + mail: this.mail, + sTo: this.messages[i].sTo, + sSubject: this.messages[i].sSubject, + sMessage: this.messages[i].sMessage + }); + //Протоколируем отправку + await this.logger.info( + `Сообщение с темой "${this.messages[i].sSubject}" отпрвлено ${this.messages[i].sTo}` + ); + //Говорим, что отправлено + this.messages[i].bSent = true; + } catch (e) { + await this.logger.error( + `Ошибка отправки сообщения с темой "${this.messages[i].sSubject}" для ${ + this.messages[i].sTo + }: ${makeErrorText(e)}` + ); + } + } + } + //Подчищаем очередь - удалим уже отправленные + _.remove(this.messages, { bSent: true }); + //Выставим флаг - цикл опроса неактивен + this.bInSendLoop = false; + //Перезапускаем опрос + this.restartSendLoop(); + } + //Запуск модуля + startNotifier() { + //Выставляем флаг работы + this.bWorking = true; + //Выставляем флаг необходимости оповещения об останове + this.bNotifyStopped = false; + //Выставляем флаг неактивности (пока) цикла опроса + this.bInSendLoop = false; + //Начинаем слушать очередь исходящих + setTimeout(this.notifySendLoop, NSEND_LOOP_DELAY); + //И оповещаем всех что запустились + this.notifyStarted(); + } + //Остановка модуля + stopNotifier() { + //Выставляем флаг неработы + this.bWorking = false; + //Если сейчас мы не в цикле проверки + if (!this.bInSendLoop) { + //Сбросим его таймер, чтобы он не запустился снова + if (this.nSendLoopTimeOut) { + clearTimeout(this.nSendLoopTimeOut); + this.nSendLoopTimeOut = null; + } + //Выставляем флаг - не надо оповещать об останове + this.bNotifyStopped = false; + //Оповестим об останове + this.notifyStopped(); + } else { + //Выставляем флаг необходимости оповещения об останове (это будет сделано автоматически по завершению цикла проверки) + this.bNotifyStopped = true; + } + } +} + +//----------------- +// Интерфейс модуля +//----------------- + +exports.SEVT_NOTIFIER_STARTED = SEVT_NOTIFIER_STARTED; +exports.SEVT_NOTIFIER_STOPPED = SEVT_NOTIFIER_STOPPED; +exports.Notifier = Notifier; diff --git a/models/prms_notifier.js b/models/prms_notifier.js new file mode 100644 index 0000000..d563c3e --- /dev/null +++ b/models/prms_notifier.js @@ -0,0 +1,83 @@ +/* + Сервис интеграции ПП Парус 8 с WEB API + Модели данных: описатели параметров функций модуля рассылки уведомлений (класс Notifier) +*/ + +//---------------------- +// Подключение библиотек +//---------------------- + +const Schema = require("validate"); //Схемы валидации +const { mail } = require("./obj_config"); //Схемы валидации конфигурации сервера приложений +const { Logger } = require("../core/logger"); //Класс для протоколирования работы +const { validateMailList } = require("./common"); //Общие объекты валидации моделей данных + +//------------- +// Тело модуля +//------------- + +//Валидация списка адресов E-Mail для отправки уведомления +const validateTo = val => { + return validateMailList(val); +}; + +//------------------ +// Интерфейс модуля +//------------------ + +//Схема валидации параметров конструктора +exports.Notifier = new Schema({ + //Параметры отправки E-Mail уведомлений + mail: { + schema: mail, + required: true, + message: { + required: path => `Не указаны параметры отправки E-Mail уведомлений (${path})` + } + }, + //Объект для протоколирования работы + logger: { + type: Logger, + required: true, + message: { + type: path => + `Объект для протоколирования работы (${path}) имеет некорректный тип данных (ожидалось - Logger)`, + required: path => `Не указаны объект для протоколирования работы (${path})` + } + } +}); + +//Схема валидации параметров функции добавления сообщения в очередь отправки +exports.addMessage = new Schema({ + //Список адресов E-Mail для отправки уведомления + sTo: { + type: String, + required: true, + use: { validateTo }, + message: { + type: path => + `Список адресов E-Mail для отправки уведомления (${path}) имеет некорректный тип данных (ожидалось - String)`, + required: path => `Не указан cписок адресов E-Mail для отправки уведомления (${path})`, + validateTo: path => + `Неверный формат списка адресов E-Mail для отправки уведомления (${path}), для указания нескольких адресов следует использовать запятую в качестве разделителя (без пробелов)` + } + }, + //Заголовок сообщения + sSubject: { + type: String, + required: true, + message: { + type: path => `Заголовок сообщения (${path}) имеет некорректный тип данных (ожидалось - String)`, + required: path => `Не указан заголовок сообщения (${path})` + } + }, + //Текст уведомления + sMessage: { + type: String, + required: true, + message: { + type: path => `Текст уведомления (${path}) имеет некорректный тип данных (ожидалось - String)`, + required: path => `Не указан текст уведомления (${path})` + } + } +});