351 lines
15 KiB
JavaScript
351 lines
15 KiB
JavaScript
/*
|
||
Сервис интеграции ПП Парус 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;
|