P8-ExchangeService/core/notifier.js

186 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Сервис интеграции ПП Парус 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);
}
}
//Добавление уведомления в очередь отправки
async addMessage(prms) {
//Проверяем структуру переданного объекта для старта
let sCheckResult = validateObject(
prms,
prmsNotifierSchema.addMessage,
"Параметры функции добавления уведомления в очередь отправки"
);
//Если структура объекта в норме
if (!sCheckResult) {
let tmp = _.cloneDeep(prms);
tmp.bSent = false;
this.messages.push(tmp);
} else {
await this.logger.error(
`Ошибка добавления уведомления в очередь: ${makeErrorText(
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;