Модуль обработки очереди уведомлений о работе сервера приложений

This commit is contained in:
Mikhail Chechnev 2019-01-07 01:42:54 +03:00
parent af5f93380f
commit 9dbf65237d
2 changed files with 264 additions and 0 deletions

181
core/notifier.js Normal file
View File

@ -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;

83
models/prms_notifier.js Normal file
View File

@ -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})`
}
}
});