431 lines
20 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 fs = require("fs"); //Работа с файлами
const _ = require("lodash"); //Работа с массивами и объектами
const os = require("os"); //Средства операционной системы
const xml2js = require("xml2js"); //Конвертация XML в JSON
const Schema = require("validate"); //Схемы валидации
const nodemailer = require("nodemailer"); //Отправка E-Mail сообщений
const {
SERR_UNEXPECTED,
SMODULES_PATH_MODULES,
SERR_OBJECT_BAD_INTERFACE,
SERR_MODULES_NO_MODULE_SPECIFIED,
SERR_MODULES_BAD_INTERFACE,
SERR_MAIL_FAILED
} = require("./constants"); //Глобавльные константы системы
const { ServerError } = require("./server_errors"); //Ошибка сервера
const prmsUtilsSchema = require("../models/prms_utils"); //Схемы валидации параметров функций
const { SPROTOCOL_HTTP, SPROTOCOL_KAFKA } = require("../models/obj_service"); //Схемы валидации сервиса
//------------
// Тело модуля
//------------
//Валидация объекта
const validateObject = (obj, schema, sObjName) => {
//Объявим результат
let sRes = "";
//Если пришла верная схема
if (schema instanceof Schema) {
//И есть что проверять
if (obj) {
//Сделаем это
const objTmp = _.cloneDeep(obj);
const errors = schema.validate(objTmp, { strip: false });
//Если есть ошибки
if (errors && Array.isArray(errors)) {
if (errors.length > 0) {
//Сформируем из них сообщение об ошибке валидации
let a = errors.map(e => {
return e.message;
});
sRes = `Объект${sObjName ? ` "${sObjName}" ` : " "}имеет некорректный формат: ${_.uniq(a).join("; ")}`;
}
} else {
//Валидатор вернул не то, что мы ожидали
sRes = "Неожиданный ответ валидатора";
}
} else {
//Нам не передали объект на проверку
sRes = `Объект${sObjName ? ` "${sObjName}" ` : " "}не указан`;
}
} else {
//Пришла не схема валидации а непонятно что
sRes = "Ошибочный формат схемы валидации";
}
//Вернем результат
return sRes;
};
//Формирование полного пути к подключаемому модулю
const makeModuleFullPath = sModuleName => {
//Если имя модуля передано
if (sModuleName) {
//Объединим его с шаблоном пути до библиотеки модулей
return `${SMODULES_PATH_MODULES}/${sModuleName}`;
} else {
//Нет имени модуля - нет полного пути
return "";
}
};
//Формирование текста ошибки
const makeErrorText = e => {
//Сообщение об ошибке по умолчанию
let sErr = `${SERR_UNEXPECTED}: ${e.message}`;
//Если это простая строка
if (e instanceof String || typeof e === "string") sErr = `${SERR_UNEXPECTED}: ${e}`;
//Если это наше внутреннее сообщение, с кодом, то сделаем ошибку более информативной
if (e instanceof ServerError) sErr = `${e.sCode}: ${e.sMessage}`;
//Вернем ответ
return sErr;
};
//Считывание наименования модуля-обработчика сервера приложений (ожидаемый формат - <МОДУЛЬ>.js/<ФУНКЦИЯ>)
const getAppSrvModuleName = sAppSrv => {
//Если есть что разбирать
if (sAppSrv) {
//И если это строка
if (sAppSrv instanceof String || typeof sAppSrv === "string") {
//Проверим наличие разделителя между именем модуля и функции
if (sAppSrv.indexOf("/") === -1) {
//Нет разделителя - нечего вернуть
return null;
} else {
//Вернём всё, что левее разделителя
return sAppSrv.substring(0, sAppSrv.indexOf("/"));
}
} else {
//Пришла не строка
return null;
}
} else {
//Ничего не пришло
return null;
}
};
//Считывание наименования функции модуля-обработчика сервера приложений (ожидаемый формат - <МОДУЛЬ>.js/<ФУНКЦИЯ>)
const getAppSrvFunctionName = sAppSrv => {
//Если есть что разбирать
if (sAppSrv) {
//И если это строка
if (sAppSrv instanceof String || typeof sAppSrv === "string") {
//Проверим наличие разделителя между именем модуля и функции
if (sAppSrv.indexOf("/") === -1) {
//Нет разделителя - нечего вернуть
return null;
} else {
//Вернём всё, что правее разделителя
return sAppSrv.substring(sAppSrv.indexOf("/") + 1, sAppSrv.length);
}
} else {
//Пришла не строка
return null;
}
} else {
//Ничего не пришло
return null;
}
};
//Получение функции обработчика
const getAppSrvFunction = sAppSrv => {
//Объявим формат (для сообщений об ошибках)
const sFormat = "(ожидаемый формат: <МОДУЛЬ>/<ФУНКЦИЯ>)";
//Проверим, что есть что разбирать
if (!sAppSrv) throw new ServerError(SERR_MODULES_NO_MODULE_SPECIFIED, `Не указаны модуль и функция обработчика ${sFormat}`);
//Разбираем
try {
//Разбираем на модуль и функцию
let moduleName = getAppSrvModuleName(sAppSrv);
let funcName = getAppSrvFunctionName(sAppSrv);
//Проверим, что есть и то и другое
if (!moduleName) throw Error(`Обработчик ${sAppSrv} не указывает на модуль ${sFormat}`);
if (!funcName) throw Error(`Обработчик ${sAppSrv} не указывает на функцию ${sFormat}`);
//Подключаем модуль
let mdl = null;
try {
mdl = require(makeModuleFullPath(moduleName));
} catch (e) {
throw Error(
`Не удалось подключить модуль ${moduleName}, проверье что он существует и не имеет синтаксических ошибок. Ошибка подключения: ${e.message}`
);
}
//Проверяем, что в нём есть эта функция
if (!mdl[funcName]) throw Error(`Функция ${funcName} не определена в модуле ${moduleName}`);
//Проверяем, что функция асинхронна и если это так - возвращаем её
if ({}.toString.call(mdl[funcName]) === "[object AsyncFunction]") return mdl[funcName];
else throw Error(`Функция ${funcName} модуля ${moduleName} должна быть асинхронной`);
} catch (e) {
throw new ServerError(SERR_MODULES_BAD_INTERFACE, e.message);
}
};
//Отправка E-Mail уведомления
const sendMail = prms => {
return new Promise((resolve, reject) => {
//Проверяем структуру переданного объекта для отправки E-Mail уведомления
let sCheckResult = validateObject(prms, prmsUtilsSchema.sendMail, "Параметры функции отправки E-Mail уведомления");
//Если структура объекта в норме
if (!sCheckResult) {
//Формируем параметры для подключения к SMTP
let transpOptions = {
host: prms.mail.sHost,
port: prms.mail.nPort,
secure: prms.mail.bSecure,
tls: {
rejectUnauthorized: prms.mail.bRejectUnauthorized
}
};
//Если есть информация о пользователе - добавляем
if (prms.mail.sUser) {
transpOptions.auth = {
user: prms.mail.sUser,
pass: prms.mail.sPass
};
}
//Настраиваем подключение к SMTP-серверу
let transporter = nodemailer.createTransport(transpOptions);
//Параметры отправляемого сообщения
let mailOptions = {
from: prms.mail.sFrom,
to: prms.sTo,
subject: prms.sSubject,
text: prms.sMessage,
html: prms.sHTML,
attachments: prms.attachments
};
//Отправляем сообщение
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
reject(new ServerError(SERR_MAIL_FAILED, `${error.code}: ${error}`));
} else {
if (info.rejected && Array.isArray(info.rejected) && info.rejected.length > 0) {
reject(new ServerError(SERR_MAIL_FAILED, `Сообщение не доствлено адресатам: ${info.rejected.join(", ")}`));
} else {
resolve(info);
}
}
});
} else {
reject(new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult));
}
});
};
//Сборка URL по адресу сервиса и функции сервиса
const buildURL = prms => {
//Проверяем структуру переданного объекта для сборки URL
let sCheckResult = validateObject(prms, prmsUtilsSchema.buildURL, "Параметры функции формирования URL");
//Если структура объекта в норме
if (!sCheckResult) {
//Формируем URL с учетом лишних "/"
return `${prms.sSrvRoot.replace(/\/+$/, "")}/${prms.sFnURL.replace(/^\/+/, "")}${prms.sQuery ? `?${prms.sQuery}` : ""}`;
} else {
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
}
};
//Получение списка IP-адресов хоста сервера
const getIPs = () => {
let ips = [];
//получим список сетевых интерфейсов
const ifaces = os.networkInterfaces();
//обходим сетевые интерфейсы
Object.keys(ifaces).forEach(ifname => {
ifaces[ifname].forEach(iface => {
//пропускаем локальный адрес и не IPv4 адреса
if ("IPv4" !== iface.family || iface.internal !== false) return;
//добавим адрес к резульату
ips.push(iface.address);
});
});
//вернем ответ
return ips;
};
//Разбор XML (обёртка для async/await)
const parseXML = prms => {
return new Promise((resolve, reject) => {
//Проверяем структуру переданного объекта для парсинша
let sCheckResult = validateObject(prms, prmsUtilsSchema.parseXML, "Параметры функции разбора XML");
//Если структура объекта в норме
if (!sCheckResult) {
xml2js.parseString(prms.sXML, prms.options, (err, result) => {
if (err) reject(err);
else resolve(result);
});
} else {
reject(new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult));
}
});
};
//Разбор параметров сообщения/ответа (XML > JSON)
const parseOptionsXML = async prms => {
//Проверяем структуру переданных параметров
let sCheckResult = validateObject(prms, prmsUtilsSchema.parseOptionsXML, "Параметры функции разбора XML параметров сообщения/ответа");
//Если структура объекта в норме
if (!sCheckResult) {
try {
parseRes = await parseXML({
sXML: prms.sOptions,
options: {
explicitArray: false,
mergeAttrs: true,
valueProcessors: [xml2js.processors.parseNumbers, xml2js.processors.parseBooleans]
}
});
return parseRes.root;
} catch (e) {
throw new Error("Ошибка рабора XML с параметрами сообщения/ответа: " + e);
}
} else {
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
}
};
//Сборка параметров сообщения/ответа (JSON > XML)
const buildOptionsXML = prms => {
//Проверяем структуру переданных параметров
let sCheckResult = validateObject(prms, prmsUtilsSchema.buildOptionsXML, "Параметры функции сборки XML параметров сообщения/ответа");
//Если структура объекта в норме
if (!sCheckResult) {
try {
let builder = new xml2js.Builder();
return builder.buildObject({ root: prms.options });
} catch (e) {
throw new Error("Ошибка сборки XML с параметрами сообщения/ответа: " + e);
}
} else {
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
}
};
//Получение текущего времени в строковом формате
const getNowString = () => {
//Создадим объект даты
const dNow = new Date();
//Возьмём его строковое представление
const sNow = dNow.toLocaleString();
//Вернем результат
return sNow;
};
//Глубокое слияние объектов
const deepMerge = (...args) => {
let res = {};
for (let i = 0; i < args.length; i++) _.merge(res, args[i]);
return res;
};
//Глубокое копирование объекта
const deepCopyObject = obj => JSON.parse(JSON.stringify(obj));
//Проверка на undefined
const isUndefined = value => value === undefined;
//Считывание параметров подключения для сервиса обмена (при service === "" считывание подключения "По умолчанию", settingsArray - массив объектов [{sService: "", ...},...])
const getConnectionSettings = (service, settingsArray) => {
//Считываем параметры и возвращаем
return settingsArray.find(connection => {
return connection.sService === service;
});
};
//Считывание параметров подключения к Kafka для сервиса обмена (kafka - массив объектов [{sService: "", ...},...])
const getKafkaConnectionSettings = (service, kafka) => {
//Считываем подключение с указанным сервисом обмена
let kafkaConnection = getConnectionSettings(service, kafka);
//Если нет подключения с указанным сервисом обмена
if (!kafkaConnection) {
//Считываем "По умолчанию"
kafkaConnection = getConnectionSettings("", kafka);
}
//Вернем результат
return kafkaConnection;
};
//Считывание параметров подключения к MQTT для сервиса обмена (mqtt - массив объектов [{sService: "", ...},...])
const getMQTTConnectionSettings = (service, mqtt) => {
//Считываем подключение с указанным сервисом обмена
let mqttConnection = getConnectionSettings(service, mqtt);
//Если нет подключения с указанным сервисом обмена
if (!mqttConnection) {
//Считываем "По умолчанию"
mqttConnection = getConnectionSettings("", mqtt);
}
//Вернем результат
return mqttConnection;
};
//Получение брокера Kafka по адресу сервиса обмена (прим. kafka://server.ru -> server.ru, https://server.ru => undefined)
const getKafkaBroker = sURL => {
//Если протокол URL - Kafka
if (getURLProtocol(sURL) === SPROTOCOL_KAFKA) {
//Возвращаем брокера
return sURL.slice(8);
}
//Возвращаем undefined
return;
};
//Получение авторизации для Kafka
const getKafkaAuth = (sUser, sPass, kafka) => {
//Если аутентификация по SSL-сертификату
if (kafka.bAuthSSL) {
//Возвращаем авторизацию в формате SSL
return {
ssl: {
rejectUnauthorized: kafka.ssl.bRejectUnauthorized,
ca: kafka.ssl.sPathCa ? [fs.readFileSync(kafka.ssl.sPathCa, "utf-8")] : [],
key: kafka.ssl.sPathKey ? fs.readFileSync(kafka.ssl.sPathKey, "utf-8") : "",
cert: kafka.ssl.sPathCert ? fs.readFileSync(kafka.ssl.sPathCert, "utf-8") : ""
}
};
}
//Возвращаем авторизацию по пользователю, если необходимо
return sUser ? { ssl: true, sasl: { mechanism: "plain", username: sUser, password: sPass } } : null;
};
//Получение протокола адреса (прим. mqtt://server.ru -> mqtt, https://server.ru => https, ...)
const getURLProtocol = sURL => {
//Если начинается с "/" - HTTP, иначе получаем из URL
return sURL.substring(0, 1) === "/" ? SPROTOCOL_HTTP : new URL(sURL).protocol.slice(0, -1);
};
//-----------------
// Интерфейс модуля
//-----------------
exports.validateObject = validateObject;
exports.makeModuleFullPath = makeModuleFullPath;
exports.makeErrorText = makeErrorText;
exports.getAppSrvModuleName = getAppSrvModuleName;
exports.getAppSrvFunctionName = getAppSrvFunctionName;
exports.getAppSrvFunction = getAppSrvFunction;
exports.sendMail = sendMail;
exports.buildURL = buildURL;
exports.getIPs = getIPs;
exports.parseXML = parseXML;
exports.parseOptionsXML = parseOptionsXML;
exports.buildOptionsXML = buildOptionsXML;
exports.getNowString = getNowString;
exports.deepMerge = deepMerge;
exports.deepCopyObject = deepCopyObject;
exports.isUndefined = isUndefined;
exports.getKafkaConnectionSettings = getKafkaConnectionSettings;
exports.getMQTTConnectionSettings = getMQTTConnectionSettings;
exports.getKafkaBroker = getKafkaBroker;
exports.getKafkaAuth = getKafkaAuth;
exports.getURLProtocol = getURLProtocol;