351 lines
15 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 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 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 transporter = nodemailer.createTransport({
host: prms.mail.sHost,
port: prms.mail.nPort,
secure: prms.mail.nPort == 465,
auth: {
user: prms.mail.sUser,
pass: prms.mail.sPass
}
});
//Параметры отправляемого сообщения
let mailOptions = {
from: prms.mail.sFrom,
to: prms.sTo,
subject: prms.sSubject,
text: prms.sMessage
};
//Отправляем сообщение
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) {
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! КОНТРОЛЬ КОРРЕКТНОСТИ
return `${prms.sSrvRoot}/${prms.sFnURL}${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;
};
//-----------------
// Интерфейс модуля
//-----------------
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;