Программные зависимости

express - фреймворк web-приложений для Node.js

ioredis - библиотека для эффективной работы с Redis

fs - библиотека для работы с файловой системой (является частью Node.js)

moment - библиотека для работы с датами и временем

request - библиотека для выполнения HTTP-запросов в Node.js

Функции в коде

Название функции	Назначение
getCalibrationNameAndSerial(calibrationData)	функция возвращает имена приборов и их серийные номера, в случае, если для них есть калибровочные коэффициенты
readAndParseCalibrationFile(file)	асинхронная функция предназначена для чтения и парсинга JSON-файла с данными о калибровке.
getCalibrationSensorNames(calibrationData)	функция возвращает массив с названиями сенсоров для калибровки
getAllRedisKeys()	асинхронная функция для получения всех ключей из Redis
getNecessaryKeys(startDate, endDate, keysArray)	функция выполняет фильтрацию массива ключей (keysArray) на основе заданного временного диапазона
checkDateRangeInRedis(formattedStartDate, formattedEndDate, keysArray)	функция проверяет, находится ли заданный временной диапазон внутри или частично пересекается с временным диапазоном, представленным в массиве keysArray
sortRedisKeys(keysArray)	функция для сортировки массива ключей Redis
sortedAndFilteredKeys(formattedStartDate, formattedEndDate)	асинхронная функция для получения отсортированных и отфильтрованных ключей из Redis
updateDataInRedis(url)	асинхронная функция для добавления данных в Redis
processAndAggregateLastMeasurements(respondParsed, keysArray)	функция для обработки данных (для запросов last_measurements)
processAndAggregateResponsesList(respondParsed, keysArray)	функция для обработки данных (для запросов list)
downloadPage(url)	асинхронная функция выполняет запрос по указанному URL и возвращает ответ
requestAdditionalDataIfMissing(formattedStartDate, formattedEndDate, filteredKeys)	асинхронная функция для дозапроса данных
respondToDataQuery(keys)	функция предназначена для получения значений по заданным ключам
respondToDataQueryParsed(keys)	функция предназначена для получения значений по заданным ключам
applyCalibrationsToData(calibrationData, filteredResponse)	функция выполняет калибровку данных

Дополнительные объяснения по функциям

updateDataInRedis(url)

В программе использована конвейерная обработка данных для сведения к минимуму задержек при множественных операциях. Конвейерная обработка в Redis представляет собой метод повышения эффективности, позволяющий одновременно выполнить несколько команд без ожидания ответа на каждую отдельную команду.

Шаг 1: Получаем ответ от внешнего источника данных

const databaseResponse = await downloadPage(url);

Шаг 2: Получаем массив ключей из ответа

const keys = Object.keys(databaseResponse);

Шаг 3: Объявляем конвейеры для проверки наличия ключей в Redis и добавления данных

const existsPipeline = redis.pipeline();
const updatePipeline = redis.pipeline();

Шаг 4: Добавляем команды проверки наличия в Redis для каждого ключа в конвейер

for (const key of keys) {
const currentDate = databaseResponse[key].Date;
existsPipeline.exists(currentDate);
}

Шаг 5: Запускаем конвейер проверки наличия и ждём результат

const existsResults = await existsPipeline.exec();

Шаг 6: Добавляем данные для отсутствующих ключей

for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const currentDate = databaseResponse[key].Date;
const keyExistsInRedis = existsResults[i][1] === 1;
if (!keyExistsInRedis) {
const newDataArray = JSON.stringify(databaseResponse[key]);
updatePipeline.set(currentDate, newDataArray, 'EX', REDIS_EXPIRY_SECONDS);
}
}

Шаг 7: Запускаем конвейер добавления данных и ждём результат

await updatePipeline.exec();

requestAdditionalDataIfMissing(formattedStartDate, formattedEndDate, filteredKeys)

Шаг №1: Инициализация флага для отслеживания обновлений в Redis.

let flag = false;

Шаг №2: Задаем максимальную разницу по времени между записями в секундах

const secondsThreshold = 360;

Шаг №3: Проверяем и добавляем данные, если разница между первым ключом в Redis и начальной датой больше secondsThreshold

const firstKey = filteredKeys[0];
const formattedFirstKey = moment(firstKey).format(DATE_FORMAT);
const startDifference = moment(formattedFirstKey).diff(moment(formattedStartDate), 'seconds');
if (Math.abs(startDifference) > secondsThreshold) {
flag = true;
const dataUrl = `${URL_DBROBO}${formattedStartDate}${SDATE_QUERY_PARAM}${formattedFirstKey}`;
await updateDataInRedis(dataUrl);
}

Шаг №4: Проверка и добавление данных для промежуточных ключей

for (let i = 0; i < filteredKeys.length - 2; i++) {
const firstDate = filteredKeys[i];
const secondDate = filteredKeys[i + 1];
const formattedFirstDate = moment(firstDate).format(DATE_FORMAT);
const formattedSecondDate = moment(secondDate).format(DATE_FORMAT);
const secondsDifference = moment(formattedSecondDate).diff(moment(formattedFirstDate), 'seconds');
if (secondsDifference > secondsThreshold) {
flag = true;
const dataUrl = `${URL_DBROBO}${formattedFirstDate}${SDATE_QUERY_PARAM}${formattedSecondDate}`;
await updateDataInRedis(dataUrl);
}
}

Шаг №5: Проверяем и добавляем данные, если разница между последним ключом в Redis и конечной датой больше secondsThreshold

const lastKey = filteredKeys[filteredKeys.length - 1];
const formattedLastKey = moment(lastKey).format(DATE_FORMAT);
const endDifference = moment(formattedEndDate).diff(moment(formattedLastKey), 'seconds');
if (Math.abs(endDifference) > secondsThreshold) {
flag = true;
const dataUrl = `${URL_DBROBO}${formattedLastKey}${SDATE_QUERY_PARAM}${formattedEndDate}`;
await updateDataInRedis(dataUrl);
}

Шаг №6: Если флаг установлен в true, возвращение новых отсортированных и отфильтрованных ключей из Redis, иначе возвращаем filteredKeys

return flag ? await sortedAndFilteredKeys(formattedStartDate, formattedEndDate) : filteredKeys;

checkDateRangeInRedis(formattedStartDate, formattedEndDate, keysArray)

Шаг №1: Инициализация переменных, получение начальной и конечной временных меток из массива ключей

const startDate = keysArray[0];
const endDate = keysArray[keysArray.length - 1];

Шаг №2: Расчет временной разницы в минутах между конечной датой массива и заданной начальной датой.

let startDifference = moment(endDate).diff(moment(formattedStartDate), 'minutes');

Шаг №3: Проверка, находится ли заданная начальная дата внутри или частично пересекается с временным диапазоном, представленным в массиве keysArray.

if (startDate <= formattedStartDate && formattedStartDate < endDate && Math.abs(startDifference) >= 5) {
// Если условие выполняется, возвращаем true, указывая на пересечение временных диапазонов.
return true;
}

Шаг №4: Расчет временной разницы в минутах между заданной конечной датой массива и начальной датой.

let endDifference = moment(formattedEndDate).diff(moment(startDate), 'minutes');

Шаг №5: Если ни одно из условий не выполнилось, возвращаем false, указывая на отсутствие пересечения временных диапазонов, иначе true

return formattedStartDate <= startDate && startDate < formattedEndDate && Math.abs(endDifference) >= 5;