P8-ExchangeService/core/service_available_controller.js

238 lines
14 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 rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const EventEmitter = require("events"); //Обработчик пользовательских событий
const { ServerError } = require("./server_errors"); //Типовая ошибка
const { SERR_SERVICE_UNAVAILABLE, SERR_OBJECT_BAD_INTERFACE } = require("./constants"); //Общесистемные константы
const { makeErrorText, validateObject, sendMail } = require("./utils"); //Вспомогательные функции
const prmsServiceAvailableControllerSchema = require("../models/prms_service_available_controller"); //Схемы валидации параметров функций класса
const objServiceSchema = require("../models/obj_service"); //Схемы валидации сервисов
//--------------------------
// Глобальные идентификаторы
//--------------------------
//Типовые события
const SEVT_SERVICE_AVAILABLE_CONTROLLER_STARTED = "SERVICE_AVAILABLE_CONTROLLER_STARTED"; //Контроллер доступности сервисов запущен
const SEVT_SERVICE_AVAILABLE_CONTROLLER_STOPPED = "SERVICE_AVAILABLE_CONTROLLER_STOPPED"; //Контроллер доступности сервисов остановлен
//Время отложенного старта цикла контроля (мс)
const NDETECTING_LOOP_DELAY = 3000;
//Интервал проверки доступности сервисов (мс)
const NDETECTING_LOOP_INTERVAL = 60000;
//------------
// Тело модуля
//------------
//Класс контроллера доступности сервисов
class ServiceAvailableController extends EventEmitter {
//Конструктор класса
constructor(prms) {
//Создадим экземпляр родительского класса
super();
//Проверяем структуру переданного объекта для подключения
let sCheckResult = validateObject(
prms,
prmsServiceAvailableControllerSchema.ServiceAvailableController,
"Параметры конструктора класса ServiceAvailableController"
);
//Если структура объекта в норме
if (!sCheckResult) {
//Список обслуживаемых сервисов
this.services = null;
//Признак функционирования контроллера
this.bWorking = false;
//Признак необходимости оповещения об останове
this.bNotifyStopped = false;
//Флаг работы цикла проверки
this.bInDetectingLoop = false;
//Идентификатор таймера проверки доступности сервисов
this.nDetectingLoopTimeOut = null;
//Запомним параметры отправки E-Mail
this.mail = prms.mail;
//Запомним логгер
this.logger = prms.logger;
//Привяжем методы к указателю на себя для использования в обработчиках событий
this.serviceDetectingLoop = this.serviceDetectingLoop.bind(this);
} else {
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
}
}
//Уведомление об запуске контроллера
notifyStarted() {
//Оповестим подписчиков о запуске
this.emit(SEVT_SERVICE_AVAILABLE_CONTROLLER_STARTED);
}
//Уведомление об остановке контроллера
notifyStopped() {
//Оповестим подписчиков об останове
this.emit(SEVT_SERVICE_AVAILABLE_CONTROLLER_STOPPED);
}
//Перезапуск опроса списка сервисов
async restartDetectingLoop() {
//Включаем опрос сервисов только если установлен флаг работы
if (this.bWorking) {
this.nDetectingLoopTimeOut = await setTimeout(async () => {
await this.serviceDetectingLoop();
}, NDETECTING_LOOP_INTERVAL);
} else {
//Если мы не работаем и просили оповстить об останове (видимо была команда на останов) - сделаем это
if (this.bNotifyStopped) this.notifyStopped();
}
}
//Опрос очереди исходящих сообщений
async serviceDetectingLoop() {
//Если есть сервисы для опроса
if (this.services && Array.isArray(this.services) && this.services.length > 0) {
//Выставим флаг - цикл опроса кативен
this.bInDetectingLoop = true;
try {
//Обходим список сервисов для проверки
for (let i = 0; i < this.services.length; i++) {
//Если сервис надо проверять на доступность и это сервис для отправки исходящих сообщений
if (
this.services[i].nUnavlblNtfSign == objServiceSchema.NUNAVLBL_NTF_SIGN_YES &&
this.services[i].nSrvType == objServiceSchema.NSRV_TYPE_SEND
) {
try {
//Отправляем проверочный запрос
await rqp({ url: this.services[i].sSrvRoot });
//Запрос прошел - фиксируем дату доступности и сбрасываем дату недоступности
this.services[i].dAvailable = new Date();
this.services[i].dUnAvailable = null;
} catch (e) {
//Зафиксируем дату и время недоступности
this.services[i].dUnAvailable = new Date();
//Сформируем текст ошибки в зависимости от того, что случилось
let sError = "Неожиданная ошибка удалённого сервиса";
if (e.error) {
sError = `Ошибка передачи данных: ${e.error.code}`;
}
if (e.response) {
sError = `Ошибка работы удалённого сервиса: ${e.response.statusCode} - ${
e.response.statusMessage
}`;
}
//Фиксируем ошибку проверки в протоколе
await this.logger.warn(
`При проверке доступности сервиса ${this.services[i].sCode}: ${makeErrorText(
new ServerError(SERR_SERVICE_UNAVAILABLE, sError)
)} (адрес - ${this.services[i].sSrvRoot})`,
{ nServiceId: this.services[i].nId }
);
}
//Если есть даты - будем проверять
if (this.services[i].dUnAvailable && this.services[i].dAvailable) {
//Выясним как долго он уже недоступен (в минутах)
let nDiffMs = this.services[i].dUnAvailable - this.services[i].dAvailable;
let nDiffMins = Math.round(((nDiffMs % 86400000) % 3600000) / 60000);
//Если простой больше указанного в настройках - будем оповещать по почте
if (nDiffMins >= this.services[i].nUnavlblNtfTime) {
let sSubject = `Удалённый сервис ${this.services[i].sCode} неотвечает на запросы`;
let sMessage = `Сервис недоступен более ${
this.services[i].nUnavlblNtfTime
} мин. (${nDiffMins} мин. с момента запуска сервера приложений).\nАдрес сервиса: ${
this.services[i].sSrvRoot
}`;
await this.logger.error(sMessage, { nServiceId: this.services[i].nId });
try {
await sendMail({
mail: this.mail,
sTo: this.services[i].sUnavlblNtfMail,
sSubject,
sMessage
});
} catch (e) {
await this.logger.error(makeErrorText(e), {
nServiceId: this.services[i].nId
});
}
}
}
}
}
} catch (e) {
//Фиксируем ошибку в протоколе работы сервера приложений
await this.logger.error(makeErrorText(e));
}
//Выставим флаг - цикл опроса неактивен
this.bInDetectingLoop = false;
//Перезапускаем опрос
await this.restartDetectingLoop();
} else {
//Выставим флаг - цикл опроса неактивен
this.bInDetectingLoop = false;
//Опрашивать нечего - ждём и перезапускаем цикл опроса
await this.restartDetectingLoop();
}
}
//Запуск контроллера
startController(prms) {
//Проверяем структуру переданного объекта для старта
let sCheckResult = validateObject(
prms,
prmsServiceAvailableControllerSchema.startController,
"Параметры функции запуска контроллера доступности сервисов"
);
//Если структура объекта в норме
if (!sCheckResult) {
//Выставляем флаг работы
this.bWorking = true;
//Выставляем флаг необходимости оповещения об останове
this.bNotifyStopped = false;
//Выставляем флаг неактивности (пока) цикла опроса
this.bInDetectingLoop = false;
//запоминаем список обслуживаемых сервисов и инициализируем даты доступности и недоступности
this.services = _.cloneDeep(prms.services);
this.services.forEach(s => {
s.dUnAvailable = null;
s.dAvailable = new Date();
});
//Начинаем слушать очередь исходящих
setTimeout(this.serviceDetectingLoop, NDETECTING_LOOP_DELAY);
//И оповещаем всех что запустились
this.notifyStarted();
} else {
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
}
}
//Остановка контроллера
stopController() {
//Выставляем флаг неработы
this.bWorking = false;
//Если сейчас мы не в цикле проверки
if (!this.bInDetectingLoop) {
//Сбросим его таймер, чтобы он не запустился снова
if (this.nDetectingLoopTimeOut) {
clearTimeout(this.nDetectingLoopTimeOut);
this.nDetectingLoopTimeOut = null;
}
//Выставляем флаг - не надо оповещать об останове
this.bNotifyStopped = false;
//Оповестим об останове
this.notifyStopped();
} else {
//Выставляем флаг необходимости оповещения об останове (это будет сделано автоматически по завершению цикла проверки)
this.bNotifyStopped = true;
}
}
}
//-----------------
// Интерфейс модуля
//-----------------
exports.SEVT_SERVICE_AVAILABLE_CONTROLLER_STARTED = SEVT_SERVICE_AVAILABLE_CONTROLLER_STARTED;
exports.SEVT_SERVICE_AVAILABLE_CONTROLLER_STOPPED = SEVT_SERVICE_AVAILABLE_CONTROLLER_STOPPED;
exports.ServiceAvailableController = ServiceAvailableController;