ЦИТК-441 - новое расширение "Рассылка E-Mail"

This commit is contained in:
Mikhail Chechnev 2022-07-05 14:59:01 +03:00
parent 0146702e1c
commit 56e3cff024
6 changed files with 359 additions and 207 deletions

View File

@ -13,15 +13,7 @@ const express = require("express"); //WEB-сервер Express
const cors = require("cors"); //Управление заголовками безопасности для WEB-сервера Express const cors = require("cors"); //Управление заголовками безопасности для WEB-сервера Express
const bodyParser = require("body-parser"); //Модуль для Express (разбор тела входящего запроса) const bodyParser = require("body-parser"); //Модуль для Express (разбор тела входящего запроса)
const { ServerError } = require("./server_errors"); //Типовая ошибка const { ServerError } = require("./server_errors"); //Типовая ошибка
const { const { makeErrorText, validateObject, buildURL, getAppSrvFunction, buildOptionsXML, parseOptionsXML, deepMerge } = require("./utils"); //Вспомогательные функции
makeErrorText,
validateObject,
buildURL,
getAppSrvFunction,
buildOptionsXML,
parseOptionsXML,
deepMerge
} = require("./utils"); //Вспомогательные функции
const { NINC_EXEC_CNT_YES, NIS_ORIGINAL_NO, NIS_ORIGINAL_YES } = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД const { NINC_EXEC_CNT_YES, NIS_ORIGINAL_NO, NIS_ORIGINAL_YES } = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД
const objInQueueSchema = require("../models/obj_in_queue"); //Схема валидации сообщений обмена с бработчиком очереди входящих сообщений const objInQueueSchema = require("../models/obj_in_queue"); //Схема валидации сообщений обмена с бработчиком очереди входящих сообщений
const objServiceSchema = require("../models/obj_service"); //Схемы валидации сервиса const objServiceSchema = require("../models/obj_service"); //Схемы валидации сервиса
@ -96,11 +88,7 @@ class InQueue extends EventEmitter {
//Обработка сообщения //Обработка сообщения
async processMessage(prms) { async processMessage(prms) {
//Проверяем структуру переданного объекта для обработки //Проверяем структуру переданного объекта для обработки
let sCheckResult = validateObject( let sCheckResult = validateObject(prms, prmsInQueueSchema.processMessage, "Параметры функции обработки входящего сообщения");
prms,
prmsInQueueSchema.processMessage,
"Параметры функции обработки входящего сообщения"
);
//Если структура объекта в норме //Если структура объекта в норме
if (!sCheckResult) { if (!sCheckResult) {
//Буфер для сообщения очереди //Буфер для сообщения очереди
@ -135,9 +123,10 @@ class InQueue extends EventEmitter {
}); });
//Скажем что пришло новое входящее сообщение //Скажем что пришло новое входящее сообщение
await this.logger.info( await this.logger.info(
`Новое входящее сообщение от ${prms.req.connection.address().address} для функции ${ `Новое входящее сообщение от ${prms.req.connection.address().address} для функции ${prms.function.sCode} (${buildURL({
prms.function.sCode sSrvRoot: prms.service.sSrvRoot,
} (${buildURL({ sSrvRoot: prms.service.sSrvRoot, sFnURL: prms.function.sFnURL })})`, sFnURL: prms.function.sFnURL
})})`,
{ nQueueId: q.nId } { nQueueId: q.nId }
); );
//Выполняем обработчик "До" (если он есть) //Выполняем обработчик "До" (если он есть)
@ -200,13 +189,11 @@ class InQueue extends EventEmitter {
let sOptionsResp = buildOptionsXML({ options: optionsResp }); let sOptionsResp = buildOptionsXML({ options: optionsResp });
q = await this.dbConn.setQueueOptionsResp({ nQueueId: q.nId, sOptionsResp }); q = await this.dbConn.setQueueOptionsResp({ nQueueId: q.nId, sOptionsResp });
} }
//Если пришел флаг ошибочной аутентификации и он положительный - то это ошибка, дальше ничего не делаем //Фиксируем результат исполнения "До" - флаг ошибочной аутентификации - если он поднят, то это ошибка, дальше ничего не делаем
if (!_.isUndefined(resBefore.bUnAuth)) if (!_.isUndefined(resBefore.bUnAuth) && resBefore.bUnAuth === true)
if (resBefore.bUnAuth === true)
throw new ServerError(SERR_UNAUTH, "Нет аутентификации"); throw new ServerError(SERR_UNAUTH, "Нет аутентификации");
//Если пришел флаг прекращения дальнейшей обработки сообщения - то дальше его обработку прекращаем //Фиксируем результат исполнения "До" - флаг прекращения дальнейшей обработки сообщения - если он поднят, то дальше обработку сообщения прекращаем
if (!_.isUndefined(resBefore.bStopPropagation)) if (!_.isUndefined(resBefore.bStopPropagation) && resBefore.bStopPropagation === true) bStopPropagation = true;
if (resBefore.bStopPropagation === true) bStopPropagation = true;
} else { } else {
//Или расскажем об ошибке //Или расскажем об ошибке
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
@ -223,8 +210,7 @@ class InQueue extends EventEmitter {
//Вызов обработчика БД //Вызов обработчика БД
let prcRes = await this.dbConn.execQueueDBPrc({ nQueueId: q.nId }); let prcRes = await this.dbConn.execQueueDBPrc({ nQueueId: q.nId });
//Если результат - ошибка пробрасываем её //Если результат - ошибка пробрасываем её
if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_ERR) if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_ERR) throw new ServerError(SERR_DB_SERVER, prcRes.sMsg);
throw new ServerError(SERR_DB_SERVER, prcRes.sMsg);
//Если результат - ошибка аутентификации, то и её пробрасываем, но с правильным кодом //Если результат - ошибка аутентификации, то и её пробрасываем, но с правильным кодом
if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_UNAUTH) if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_UNAUTH)
throw new ServerError(SERR_UNAUTH, prcRes.sMsg || "Нет аутентификации"); throw new ServerError(SERR_UNAUTH, prcRes.sMsg || "Нет аутентификации");
@ -339,10 +325,7 @@ class InQueue extends EventEmitter {
nExecState: objQueueSchema.NQUEUE_EXEC_STATE_ERR nExecState: objQueueSchema.NQUEUE_EXEC_STATE_ERR
}); });
//Фиксируем ошибку обработки сервером приложений - в протоколе работы сервиса //Фиксируем ошибку обработки сервером приложений - в протоколе работы сервиса
await this.logger.error( await this.logger.error(`Ошибка обработки входящего сообщения ${q.nId} сервером приложений: ${sMessage}`, { nQueueId: q.nId });
`Ошибка обработки входящего сообщения ${q.nId} сервером приложений: ${sMessage}`,
{ nQueueId: q.nId }
);
//Добавим чуть больше информации в тему сообщения //Добавим чуть больше информации в тему сообщения
sSubject = `Ошибка обработки входящего сообщения ${q.nId} сервером приложений для функции "${prms.function.sCode}" сервиса "${prms.service.sCode}"`; sSubject = `Ошибка обработки входящего сообщения ${q.nId} сервером приложений для функции "${prms.function.sCode}" сервиса "${prms.service.sCode}"`;
} else { } else {
@ -371,11 +354,7 @@ class InQueue extends EventEmitter {
//Запуск обработки очереди входящих сообщений //Запуск обработки очереди входящих сообщений
startProcessing(prms) { startProcessing(prms) {
//Проверяем структуру переданного объекта для старта //Проверяем структуру переданного объекта для старта
let sCheckResult = validateObject( let sCheckResult = validateObject(prms, prmsInQueueSchema.startProcessing, "Параметры функции запуска обработки очереди входящих сообщений");
prms,
prmsInQueueSchema.startProcessing,
"Параметры функции запуска обработки очереди входящих сообщений"
);
//Если структура объекта в норме //Если структура объекта в норме
if (!sCheckResult) { if (!sCheckResult) {
//Выставляем флаг работы //Выставляем флаг работы
@ -431,9 +410,7 @@ class InQueue extends EventEmitter {
} }
); );
//...и собственный обработчик ошибок //...и собственный обработчик ошибок
this.webApp.use( this.webApp.use(buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL }), async (err, req, res, next) => {
buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL }),
async (err, req, res, next) => {
//Протоколируем в журнал работы сервера //Протоколируем в журнал работы сервера
await this.logger.error(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)), { await this.logger.error(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)), {
nServiceId: srvs.nId, nServiceId: srvs.nId,
@ -441,8 +418,7 @@ class InQueue extends EventEmitter {
}); });
//Отправим ошибку клиенту //Отправим ошибку клиенту
res.status(500).send(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message))); res.status(500).send(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)));
} });
);
} }
); );
}); });

View File

@ -12,15 +12,7 @@ const _ = require("lodash"); //Работа с массивами и объек
const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const lg = require("./logger"); //Протоколирование работы const lg = require("./logger"); //Протоколирование работы
const db = require("./db_connector"); //Взаимодействие с БД const db = require("./db_connector"); //Взаимодействие с БД
const { const { makeErrorText, validateObject, getAppSrvFunction, buildURL, parseOptionsXML, buildOptionsXML, deepMerge } = require("./utils"); //Вспомогательные функции
makeErrorText,
validateObject,
getAppSrvFunction,
buildURL,
parseOptionsXML,
buildOptionsXML,
deepMerge
} = require("./utils"); //Вспомогательные функции
const { ServerError } = require("./server_errors"); //Типовая ошибка const { ServerError } = require("./server_errors"); //Типовая ошибка
const objOutQueueProcessorSchema = require("../models/obj_out_queue_processor"); //Схема валидации сообщений обмена с бработчиком очереди исходящих сообщений const objOutQueueProcessorSchema = require("../models/obj_out_queue_processor"); //Схема валидации сообщений обмена с бработчиком очереди исходящих сообщений
const prmsOutQueueProcessorSchema = require("../models/prms_out_queue_processor"); //Схема валидации параметров функций модуля const prmsOutQueueProcessorSchema = require("../models/prms_out_queue_processor"); //Схема валидации параметров функций модуля
@ -35,12 +27,7 @@ const {
SERR_WEB_SERVER, SERR_WEB_SERVER,
SERR_UNAUTH SERR_UNAUTH
} = require("./constants"); //Глобальные константы } = require("./constants"); //Глобальные константы
const { const { NINC_EXEC_CNT_YES, NINC_EXEC_CNT_NO, NIS_ORIGINAL_NO, NIS_ORIGINAL_YES } = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД
NINC_EXEC_CNT_YES,
NINC_EXEC_CNT_NO,
NIS_ORIGINAL_NO,
NIS_ORIGINAL_YES
} = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД
//-------------------------- //--------------------------
// Глобальные идентификаторы // Глобальные идентификаторы
@ -112,8 +99,7 @@ const appProcess = async prms => {
//Проверяем аутентификацию //Проверяем аутентификацию
if ( if (
prms.function.nAuthOnly == objServiceFnSchema.NAUTH_ONLY_NO || prms.function.nAuthOnly == objServiceFnSchema.NAUTH_ONLY_NO ||
(prms.function.nAuthOnly == objServiceFnSchema.NAUTH_ONLY_YES && (prms.function.nAuthOnly == objServiceFnSchema.NAUTH_ONLY_YES && isServiceAuth == objServiceSchema.NIS_AUTH_YES)
isServiceAuth == objServiceSchema.NIS_AUTH_YES)
) { ) {
//Фиксируем начало исполнения сервером приложений - в статусе сообщения //Фиксируем начало исполнения сервером приложений - в статусе сообщения
res = await dbConn.setQueueState({ res = await dbConn.setQueueState({
@ -122,9 +108,9 @@ const appProcess = async prms => {
}); });
//Фиксируем начало исполнения сервером приложений - в протоколе работы сервиса //Фиксируем начало исполнения сервером приложений - в протоколе работы сервиса
await logger.info( await logger.info(
`Обрабатываю исходящее сообщение сервером приложений: ${prms.queue.nId}, ${prms.queue.sInDate}, ${ `Обрабатываю исходящее сообщение сервером приложений: ${prms.queue.nId}, ${prms.queue.sInDate}, ${prms.queue.sServiceFnCode}, ${
prms.queue.sServiceFnCode prms.queue.sExecState
}, ${prms.queue.sExecState}, попытка исполнения - ${prms.queue.nExecCnt + 1}`, }, попытка исполнения - ${prms.queue.nExecCnt + 1}`,
{ nQueueId: prms.queue.nId } { nQueueId: prms.queue.nId }
); );
//Считаем тело сообщения //Считаем тело сообщения
@ -141,6 +127,8 @@ const appProcess = async prms => {
let options = { method: prms.function.sFnPrmsType, encoding: null }; let options = { method: prms.function.sFnPrmsType, encoding: null };
//Инициализируем параметры ответа сервера //Инициализируем параметры ответа сервера
let optionsResp = {}; let optionsResp = {};
//Флаг прекращения обработки сообщения
let bStopPropagation = false;
//Определимся с URL и телом сообщения в зависимости от способа передачи параметров //Определимся с URL и телом сообщения в зависимости от способа передачи параметров
if (prms.function.nFnPrmsType == objServiceFnSchema.NFN_PRMS_TYPE_POST) { if (prms.function.nFnPrmsType == objServiceFnSchema.NFN_PRMS_TYPE_POST) {
options.url = buildURL({ sSrvRoot: prms.service.sSrvRoot, sFnURL: prms.function.sFnURL }); options.url = buildURL({ sSrvRoot: prms.service.sSrvRoot, sFnURL: prms.function.sFnURL });
@ -176,6 +164,7 @@ const appProcess = async prms => {
try { try {
let resBeforePrms = _.cloneDeep(prms); let resBeforePrms = _.cloneDeep(prms);
resBeforePrms.options = _.cloneDeep(options); resBeforePrms.options = _.cloneDeep(options);
resBeforePrms.dbConn = dbConn;
resBefore = await fnBefore(resBeforePrms); resBefore = await fnBefore(resBeforePrms);
} catch (e) { } catch (e) {
throw new ServerError(SERR_APP_SERVER_BEFORE, e.message); throw new ServerError(SERR_APP_SERVER_BEFORE, e.message);
@ -225,12 +214,16 @@ const appProcess = async prms => {
}); });
bCtxIsSet = true; bCtxIsSet = true;
} }
//Применим ответ "До" - флаг прекращения дальнейшей обработки сообщения - если он поднят, то дальше обработку сообщения прекращаем
if (!_.isUndefined(resBefore.bStopPropagation) && resBefore.bStopPropagation === true) bStopPropagation = true;
} else { } else {
//Или расскажем об ошибке в структуре ответа //Или расскажем об ошибке в структуре ответа
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
} }
} }
} }
//Если флаг прекращения обработки сообщения не установлен
if (bStopPropagation === false) {
//Фиксируем отправку сообщения в протоколе работы сервиса //Фиксируем отправку сообщения в протоколе работы сервиса
await logger.info(`Отправляю исходящее сообщение ${prms.queue.nId} на URL: ${options.url}`, { await logger.info(`Отправляю исходящее сообщение ${prms.queue.nId} на URL: ${options.url}`, {
nQueueId: prms.queue.nId nQueueId: prms.queue.nId
@ -265,10 +258,9 @@ const appProcess = async prms => {
let sOptionsResp = buildOptionsXML({ options: optionsResp }); let sOptionsResp = buildOptionsXML({ options: optionsResp });
await dbConn.setQueueOptionsResp({ nQueueId: prms.queue.nId, sOptionsResp }); await dbConn.setQueueOptionsResp({ nQueueId: prms.queue.nId, sOptionsResp });
} catch (e) { } catch (e) {
await logger.warn( await logger.warn(`Не удалось сохранить заголовок ответа удалённого сервера: ${makeErrorText(e)}`, {
`Не удалось сохранить заголовок ответа удалённого сервера: ${makeErrorText(e)}`, nQueueId: prms.queue.nId
{ nQueueId: prms.queue.nId } });
);
} }
} catch (e) { } catch (e) {
//Прекращаем исполнение если были ошибки //Прекращаем исполнение если были ошибки
@ -332,17 +324,14 @@ const appProcess = async prms => {
} }
} }
//Если это функция начала сеанса, и нет обработчика на стороне БД и контекст не был установлен до сих пор - то положим в него то, что нам ответил сервер //Если это функция начала сеанса, и нет обработчика на стороне БД и контекст не был установлен до сих пор - то положим в него то, что нам ответил сервер
if ( if (prms.function.nFnType == objServiceFnSchema.NFN_TYPE_LOGIN && !prms.function.sPrcResp && !bCtxIsSet) {
prms.function.nFnType == objServiceFnSchema.NFN_TYPE_LOGIN &&
!prms.function.sPrcResp &&
!bCtxIsSet
) {
await dbConn.setServiceContext({ nServiceId: prms.service.nId, sCtx: serverResp }); await dbConn.setServiceContext({ nServiceId: prms.service.nId, sCtx: serverResp });
} }
//Если это функция окончания сеанса, и нет обработчика на стороне БД - то сбросим контекст здесь //Если это функция окончания сеанса, и нет обработчика на стороне БД - то сбросим контекст здесь
if (prms.function.nFnType == objServiceFnSchema.NFN_TYPE_LOGOUT && !prms.function.sPrcResp) { if (prms.function.nFnType == objServiceFnSchema.NFN_TYPE_LOGOUT && !prms.function.sPrcResp) {
await dbConn.clearServiceContext({ nServiceId: prms.service.nId }); await dbConn.clearServiceContext({ nServiceId: prms.service.nId });
} }
}
//Фиксируем успешное исполнение сервером приложений - в статусе сообщения //Фиксируем успешное исполнение сервером приложений - в статусе сообщения
res = await dbConn.setQueueState({ res = await dbConn.setQueueState({
nQueueId: prms.queue.nId, nQueueId: prms.queue.nId,
@ -383,10 +372,9 @@ const appProcess = async prms => {
}); });
} }
//Фиксируем ошибку обработки сервером приложений - в протоколе работы сервиса //Фиксируем ошибку обработки сервером приложений - в протоколе работы сервиса
await logger.error( await logger.error(`Ошибка обработки исходящего сообщения ${prms.queue.nId} сервером приложений: ${makeErrorText(e)}`, {
`Ошибка обработки исходящего сообщения ${prms.queue.nId} сервером приложений: ${makeErrorText(e)}`, nQueueId: prms.queue.nId
{ nQueueId: prms.queue.nId } });
);
} }
} else { } else {
//Фатальная ошибка обработки - некорректный объект параметров //Фатальная ошибка обработки - некорректный объект параметров
@ -401,11 +389,7 @@ const dbProcess = async prms => {
//Результат обработки - объект Queue (обработанное сообщение) или ServerError (ошибка обработки) //Результат обработки - объект Queue (обработанное сообщение) или ServerError (ошибка обработки)
let res = null; let res = null;
//Проверяем структуру переданного объекта для старта //Проверяем структуру переданного объекта для старта
let sCheckResult = validateObject( let sCheckResult = validateObject(prms, prmsOutQueueProcessorSchema.dbProcess, "Параметры функции запуска обработки ообщения сервером БД");
prms,
prmsOutQueueProcessorSchema.dbProcess,
"Параметры функции запуска обработки ообщения сервером БД"
);
//Если структура объекта в норме //Если структура объекта в норме
if (!sCheckResult) { if (!sCheckResult) {
//Обрабатываем //Обрабатываем
@ -417,9 +401,9 @@ const dbProcess = async prms => {
}); });
//Фиксируем начало исполнения сервером БД - в протоколе работы сервиса //Фиксируем начало исполнения сервером БД - в протоколе работы сервиса
await logger.info( await logger.info(
`Обрабатываю исходящее сообщение сервером БД: ${prms.queue.nId}, ${prms.queue.sInDate}, ${ `Обрабатываю исходящее сообщение сервером БД: ${prms.queue.nId}, ${prms.queue.sInDate}, ${prms.queue.sServiceFnCode}, ${
prms.queue.sServiceFnCode prms.queue.sExecState
}, ${prms.queue.sExecState}, попытка исполнения - ${prms.queue.nExecCnt + 1}`, }, попытка исполнения - ${prms.queue.nExecCnt + 1}`,
{ nQueueId: prms.queue.nId } { nQueueId: prms.queue.nId }
); );
//Если обработчик со стороны БД указан //Если обработчик со стороны БД указан
@ -427,11 +411,9 @@ const dbProcess = async prms => {
//Вызываем его //Вызываем его
let prcRes = await dbConn.execQueueDBPrc({ nQueueId: prms.queue.nId }); let prcRes = await dbConn.execQueueDBPrc({ nQueueId: prms.queue.nId });
//Если результат - ошибка пробрасываем её //Если результат - ошибка пробрасываем её
if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_ERR) if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_ERR) throw new ServerError(SERR_DB_SERVER, prcRes.sMsg);
throw new ServerError(SERR_DB_SERVER, prcRes.sMsg);
//Если результат - ошибка аутентификации, то и её пробрасываем, но с правильным кодом //Если результат - ошибка аутентификации, то и её пробрасываем, но с правильным кодом
if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_UNAUTH) if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_UNAUTH) throw new ServerError(SERR_UNAUTH, prcRes.sMsg || "Нет аутентификации");
throw new ServerError(SERR_UNAUTH, prcRes.sMsg || "Нет аутентификации");
} }
//Фиксируем успешное исполнение (полное - дальше обработки нет) - в статусе сообщения //Фиксируем успешное исполнение (полное - дальше обработки нет) - в статусе сообщения
res = await dbConn.setQueueState({ res = await dbConn.setQueueState({
@ -466,10 +448,9 @@ const dbProcess = async prms => {
}); });
} }
//Фиксируем ошибку обработки сервером БД - в протоколе работы сервиса //Фиксируем ошибку обработки сервером БД - в протоколе работы сервиса
await logger.error( await logger.error(`Ошибка обработки исходящего сообщения ${prms.queue.nId} сервером БД: ${makeErrorText(e)}`, {
`Ошибка обработки исходящего сообщения ${prms.queue.nId} сервером БД: ${makeErrorText(e)}`, nQueueId: prms.queue.nId
{ nQueueId: prms.queue.nId } });
);
} }
} else { } else {
//Фатальная ошибка обработки - некорректный объект параметров //Фатальная ошибка обработки - некорректный объект параметров
@ -482,11 +463,7 @@ const dbProcess = async prms => {
//Обработка задачи //Обработка задачи
const processTask = async prms => { const processTask = async prms => {
//Проверяем параметры //Проверяем параметры
let sCheckResult = validateObject( let sCheckResult = validateObject(prms, prmsOutQueueProcessorSchema.processTask, "Параметры функции обработки задачи");
prms,
prmsOutQueueProcessorSchema.processTask,
"Параметры функции обработки задачи"
);
//Если параметры в норме //Если параметры в норме
if (!sCheckResult) { if (!sCheckResult) {
let q = null; let q = null;
@ -629,11 +606,7 @@ process.on("uncaughtException", e => {
//Приём сообщений //Приём сообщений
process.on("message", task => { process.on("message", task => {
//Проверяем структуру переданного сообщения //Проверяем структуру переданного сообщения
let sCheckResult = validateObject( let sCheckResult = validateObject(task, objOutQueueProcessorSchema.OutQueueProcessorTask, "Задача обработчика очереди исходящих сообщений");
task,
objOutQueueProcessorSchema.OutQueueProcessorTask,
"Задача обработчика очереди исходящих сообщений"
);
//Если структура объекта в норме //Если структура объекта в норме
if (!sCheckResult) { if (!sCheckResult) {
//Запускаем обработку //Запускаем обработку

View File

@ -197,7 +197,9 @@ const sendMail = prms => {
from: prms.mail.sFrom, from: prms.mail.sFrom,
to: prms.sTo, to: prms.sTo,
subject: prms.sSubject, subject: prms.sSubject,
text: prms.sMessage text: prms.sMessage,
html: prms.sHTML,
attachments: prms.attachments
}; };
//Отправляем сообщение //Отправляем сообщение
transporter.sendMail(mailOptions, (error, info) => { transporter.sendMail(mailOptions, (error, info) => {

View File

@ -145,6 +145,16 @@ exports.OutQueueProcessorFnBefore = new Schema({
type: path => `Дата истечения контекста (${path}) имеет некорректный тип данных (ожидалось - Date)`, type: path => `Дата истечения контекста (${path}) имеет некорректный тип данных (ожидалось - Date)`,
required: path => `Не указана дата истечения контекста (${path})` required: path => `Не указана дата истечения контекста (${path})`
} }
},
//Флаг прекращения дальнейшей обработки сообщения
bStopPropagation: {
type: Boolean,
required: false,
message: {
type: path =>
`Флаг прекращения дальнейшей обработки сообщения (${path}) имеет некорректный тип данных (ожидалось - Boolean)`,
required: path => `Не указан флаг прекращения дальнейшей обработки сообщения (${path})`
}
} }
}); });

View File

@ -40,8 +40,7 @@ exports.sendMail = new Schema({
required: true, required: true,
use: { validateTo }, use: { validateTo },
message: { message: {
type: path => type: path => `Список адресов E-Mail для отправки уведомления (${path}) имеет некорректный тип данных (ожидалось - String)`,
`Список адресов E-Mail для отправки уведомления (${path}) имеет некорректный тип данных (ожидалось - String)`,
required: path => `Не указан cписок адресов E-Mail для отправки уведомления (${path})`, required: path => `Не указан cписок адресов E-Mail для отправки уведомления (${path})`,
validateTo: path => validateTo: path =>
`Неверный формат списка адресов E-Mail для отправки уведомления (${path}), для указания нескольких адресов следует использовать запятую в качестве разделителя (без пробелов)` `Неверный формат списка адресов E-Mail для отправки уведомления (${path}), для указания нескольких адресов следует использовать запятую в качестве разделителя (без пробелов)`
@ -50,7 +49,7 @@ exports.sendMail = new Schema({
//Заголовок сообщения //Заголовок сообщения
sSubject: { sSubject: {
type: String, type: String,
required: true, required: false,
message: { message: {
type: path => `Заголовок сообщения (${path}) имеет некорректный тип данных (ожидалось - String)`, type: path => `Заголовок сообщения (${path}) имеет некорректный тип данных (ожидалось - String)`,
required: path => `Не указан заголовок сообщения (${path})` required: path => `Не указан заголовок сообщения (${path})`
@ -59,11 +58,29 @@ exports.sendMail = new Schema({
//Текст уведомления //Текст уведомления
sMessage: { sMessage: {
type: String, type: String,
required: true, required: false,
message: { message: {
type: path => `Текст уведомления (${path}) имеет некорректный тип данных (ожидалось - String)`, type: path => `Текст уведомления (${path}) имеет некорректный тип данных (ожидалось - String)`,
required: path => `Не указан текст уведомления (${path})` required: path => `Не указан текст уведомления (${path})`
} }
},
//HTML текст сообщения
sHTML: {
type: String,
required: false,
message: {
type: path => `HTML текст сообщения (${path}) имеет некорректный тип данных (ожидалось - String)`,
required: path => `Не указан HTML текст сообщения (${path})`
}
},
//Вложения сообщения
attachments: {
type: Array,
required: false,
message: {
type: path => `Список вложений сообщения (${path}) имеет некорректный тип данных (ожидалось - Array)`,
required: path => `Не указан список вложений сообщения (${path})`
}
} }
}); });
@ -141,8 +158,7 @@ exports.buildOptionsXML = new Schema({
type: Object, type: Object,
required: true, required: true,
message: { message: {
type: path => type: path => `Объект параметров сообщения/ответа (${path}) имеет некорректный тип данных (ожидалось - Object)`,
`Объект параметров сообщения/ответа (${path}) имеет некорректный тип данных (ожидалось - Object)`,
required: path => `Не указан объект параметров сообщения/ответа (${path})` required: path => `Не указан объект параметров сообщения/ответа (${path})`
} }
} }

175
modules/send_mail.js Normal file
View File

@ -0,0 +1,175 @@
/*
Сервис интеграции ПП Парус 8 с WEB API
Дополнительный модуль: Рассылка E-Mail (MAIL)
*/
//------------------------------
// Подключение внешних библиотек
//------------------------------
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
const cfg = require("./../config"); //Настройки сервера приложений
const { makeErrorText, sendMail } = require("./../core/utils"); //Вспомогательные функции
const oracledb = require("oracledb"); //Работа с СУБД Oracle
//---------------------
// Глобальные константы
//---------------------
//Статусы отправки
const NSTATUS_ERR = 2;
const NSTATUS_DONE = 3;
//------------
// Тело модуля
//------------
//Чтение данных из курсора
const readCursorData = cursor => {
return new Promise((resolve, reject) => {
let queryStream = cursor.toQueryStream();
let rows = [];
queryStream.on("data", row => {
rows.push(row);
});
queryStream.on("error", err => {
reject(new Error(err.message));
});
queryStream.on("close", () => {
resolve(rows);
});
});
};
//Установка статуса отправки
const setSendMsg = async prms => {
let pooledConnection;
try {
pooledConnection = await prms.connection.getConnection();
await pooledConnection.execute(
"begin PKG_EXS_EXT_MAIL.EXSEXTMAIL_SET_STATUS(NRN => :NRN, SERR_TEXT => :SERR_TEXT, NSTATUS => :NSTATUS); end;",
{ NRN: prms.nRn, SERR_TEXT: prms.sErrMsg, NSTATUS: prms.nStatus },
{ autoCommit: true }
);
} catch (e) {
throw new Error(e.message);
} finally {
if (pooledConnection) {
try {
await pooledConnection.close();
} catch (e) {
throw new Error(e.message);
}
}
}
};
//Считывание записей прикладываемых документов
const getMailAttach = async prms => {
let pooledConnection;
try {
pooledConnection = await prms.connection.getConnection();
let res = await pooledConnection.execute(
"begin PKG_EXS_EXT_MAIL.GET_ATTACH(NIDENT => :NIDENT, RCDOCUMENTS => :RCDOCUMENTS); end;",
{
NIDENT: prms.nIdent,
RCDOCUMENTS: { type: oracledb.CURSOR, dir: oracledb.BIND_OUT }
},
{ outFormat: oracledb.OBJECT, autoCommit: true }
);
let rows = await readCursorData(res.outBinds.RCDOCUMENTS);
let rowsRes = [];
//Если результат запроса не пустой
if (rows.length !== 0) {
//Переводим BLOB в BUFFER и формируем формат аттача
for (let i = 0; i < rows.length; i++) {
let rowContent = await rows[i].BDATA.getData();
rowsRes.push({
filename: rows[i].FILENAME,
content: rowContent
});
}
}
return rowsRes;
} catch (e) {
throw new Error(e.message);
} finally {
if (pooledConnection) {
try {
await pooledConnection.close();
} catch (e) {
throw new Error(e.message);
}
}
}
};
//Разбор XML
const parseXML = xmlDoc => {
return new Promise((resolve, reject) => {
xml2js.parseString(xmlDoc, { explicitArray: false, mergeAttrs: true }, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
//Обработчик "До" для исходящего сообщения
const before = async prms => {
//Инициализируем переменные
let res = "OK";
let parseRes = null;
//Разбираем параметры отправки
try {
//Формируем объект на основании XML
parseRes = await parseXML(prms.queue.blMsg.toString());
//Если есть присоединенные файлы - добавляем их
if (parseRes.mail.ident) {
parseRes.mail.attachments = await getMailAttach({ connection: prms.dbConn.connection, nIdent: parseRes.mail.ident });
}
//Если указан текст в обычном формате
if (parseRes.mail.text) {
parseRes.mail.text = Buffer.from(parseRes.mail.text, "base64").toString("utf-8");
}
//Если указан текст в формате HTML
if (parseRes.mail.html) {
parseRes.mail.html = Buffer.from(parseRes.mail.html, "base64").toString("utf-8");
}
} catch (e) {
parseRes = prms.queue.blMsg.toString();
res = `Ошибка разбора параметров отправки: ${makeErrorText(e)}`;
}
if (res === "OK") {
try {
await sendMail({
mail: cfg.mail,
sTo: parseRes.mail.to,
sSubject: parseRes.mail.title,
sMessage: parseRes.mail.text,
sHTML: parseRes.mail.html,
attachments: parseRes.mail.attachments
});
} catch (e) {
res = `Ошибка отправки E-Mail сообщения: ${makeErrorText(e)}`;
}
}
//Если имеется рег. номер записи очереди отправки E-mail - обновляем информацию о текущем сообщении
if (parseRes.mail.nExsextmailId) {
if (res === "OK") {
await setSendMsg({ connection: prms.dbConn.connection, nRn: parseRes.mail.nExsextmailId, sErrMsg: "", nStatus: NSTATUS_DONE });
} else {
await setSendMsg({ connection: prms.dbConn.connection, nRn: parseRes.mail.nExsextmailId, sErrMsg: res, nStatus: NSTATUS_ERR });
}
}
//Возвращаем результат и флаг того, что дальше отрабатывать это сообщение не надо
return {
blMsg: Buffer.from(JSON.stringify({ message: parseRes, state: res })),
bStopPropagation: true
};
};
//-----------------
// Интерфейс модуля
//-----------------
exports.before = before;