Бизнес кейс
Получение актуальных курсов валют
Постановка
Системный процесс на получение курсов валют
Решение
Данное решение является переработанным решением https://community.elma365.com/ru/threads/2241/.
Основное отличие в том, что данные по курсам валют обрабатываются за один обход сопоставления строкового представления ответа с регулярным выражением с использованием именованных групп. Также разобран вопрос со временем запуска получения курсов.
Веб-сервис для получения ежедневных данных ЦБР – https://cbr.ru/DailyInfoWebServ/DailyInfo.asmx.
Метод для получения ежедневных курсов валют – https://cbr.ru/DailyInfoWebServ/DailyInfo.asmx?op=GetCursOnDateXML.
Ответом являются курсы валют на заданный день в формате xml документа.
Метод GetCursOnDate не используется, т.к. xml представление DataSet в данном решении является избыточным, но это не критично (т.к. в ответе добавляется «обёртка» таблицы).
GET запрос на https://www.cbr.ru/scripts/XML_daily.asp не используется, т.к. было принято решение взаимодействовать с веб-сервисом DailyInfo.
Используется версия метода с SOAP 1.2, что также не является критичным, но различия имеются (https://www.w3.org/2003/06/soap11-soap12.html). И судя по тэгам xml, в ответе на SOAP 1.2 приходит SOAP 1.1.
Полученный ответ обрабатывается путём сопоставления строкового представления ответа с регулярным выражением (метод matchAll). В регулярном выражении используются Именованные группы для удобства обработки. На выходе – массив совпадений, где для каждого совпадения сформирована группа в виде объекта.
Код:
/**
* Интерфейс для работы с именоваными группами в regex
*
* @remarks
* Для поиска по содержимому тэга ValuteCursOnDate (Курс валюты) в XML Документе
*/
interface ValuteCursOnDate {
/**
* Название валюты
*/
name: string;
/**
* Номинал
*/
nom: number;
/**
* Курс
*/
curs: number;
/**
* ISO Цифровой код валюты
*/
code: string;
/**
* ISO Символьный код валюты
*/
chCode: string;
/**
* Курс за 1 единицу валюты
*/
unitRate: number;
}
/**
* Получить курсы валют на день
*/
async function getDailyInfoXMLSoap12(): Promise<void> {
// GetCursOnDateXML - Получение ежедневных курсов валют (как XMLDocument)
const dailyinfo_response = await fetch(`https://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx`, {
method: 'POST',
headers: {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': 'http://web.cbr.ru/GetCursOnDateXML'
},
body: `<?xml version="1.0" encoding = "utf-8"?>
<soap12:Envelope xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd = "http://www.w3.org/2001/XMLSchema" xmlns:soap12 = "http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetCursOnDateXML xmlns="http://web.cbr.ru/">
<On_date>${new Datetime().format()}</On_date>
</GetCursOnDateXML>
</soap12:Body>
</soap12:Envelope>`
});
if (!dailyinfo_response.ok) {
throw new Error(`dailyinfo_response is not ok at ${getDailyInfoXMLSoap12.name}; Status: ${dailyinfo_response.status}, statusText: ${dailyinfo_response.statusText}.`);
}
const text: any = await dailyinfo_response.text();
if (!text.matchAll) {
throw new Error(`matchAll is not supported at ${getDailyInfoXMLSoap12.name}`);
}
// Match XMLDocument by regex with Named capturing groups
// Поиск совпадений выполняется по тэгу ValuteCursOnDate и его содержимому. Для получения содержимого используются именованные группы
const regex = /<ValuteCursOnDate>(?:.*?)<Vname>(?<name>.*?)<\/Vname>(?:.*?)<Vnom>(?<nom>.*?)<\/Vnom>(?:.*?)<Vcurs>(?<curs>.*?)<\/Vcurs>(?:.*?)<Vcode>(?<code>.*?)<\/Vcode>(?:.*?)<VchCode>(?<chCode>.*?)<\/VchCode>(?:.*?)<VunitRate>(?<unitRate>.*?)<\/VunitRate>(?:.*?)<\/ValuteCursOnDate>/gs;
const match_all_iterator = text.matchAll(regex);
const curs_on_date_table = Context.fields.curs_on_date_table.create();
for (let exec_result of match_all_iterator) {
if (!exec_result.groups) {
throw new Error(`capturing groups is undefined at ${getDailyInfoXMLSoap12.name}`);
}
const groups: ValuteCursOnDate = exec_result.groups;
const row = curs_on_date_table.insert();
row.vchcode = groups.chCode;
row.vcode = groups.code;
row.vcurs = groups.curs;
row.vname = groups.name;
row.vnom = groups.nom;
row.vunitrate = groups.unitRate;
}
// Update table
Context.data.curs_on_date_table = curs_on_date_table;
}
В части сервисного процесса интересен только момент с определением времени запуска. По информации с сайта ЦБР:
В тексте Указания для валют Доллар США, Евро и Китайский юань курс устанавливается на основе данных о средневзвешенном курсе, рассчитанном по сделкам, заключенным в период с 10:00 по 15:30 по мск. Официальные курсы других валют устанавливаются на основе соотношения данных о курсе доллара США по отношению к рублю и официальных курсов доллара США по отношению к соответствующим валютам, установленных и размещенных на официальных сайтах центральными (национальными) банками на 15:30 мск.
По итогу, запуск процесса для достаточно гарантированного получения установленного курса валют на следующий день возможно выполнять после 17:00 мск текущего дня.
Дополнительно возможно добавить проверку на дату установления курса по параметру OnDate в ответе, либо перед выполнением запроса на GetCursOnDateXML проверять последнюю дату публикации курса валют через GetLatestDateTime