diff --git a/.github/workflows/dotnet8-desktop.yml b/.github/workflows/dotnet8-desktop.yml new file mode 100644 index 0000000..2c36ef4 --- /dev/null +++ b/.github/workflows/dotnet8-desktop.yml @@ -0,0 +1,35 @@ +# This workflow will build a .NET8 Desktop project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET8 Desktop + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore + + - name: Test + run: dotnet test --no-build --verbosity normal diff --git a/FeedsAPI/ConfigManager.cs b/FeedsAPI/ConfigManager.cs index 9e44bc3..15600bb 100644 --- a/FeedsAPI/ConfigManager.cs +++ b/FeedsAPI/ConfigManager.cs @@ -35,7 +35,10 @@ public static Config Read() { try { - string appsettings = Path.ChangeExtension(Environment.ProcessPath!, ".config.json"); + // Works in Windows, fails in Linux (/usr/lib/dotnet...) + // string appsettings = Path.ChangeExtension(Environment.ProcessPath!, ".config.json"); + + string appsettings = Path.Combine(AppContext.BaseDirectory, "FeedsAPI.config.json"); //TODO what? if (File.Exists(appsettings)) { diff --git a/FeedsAPI/FeedsAPI.csproj b/FeedsAPI/FeedsAPI.csproj index 0c9693e..85a6e22 100644 --- a/FeedsAPI/FeedsAPI.csproj +++ b/FeedsAPI/FeedsAPI.csproj @@ -6,7 +6,7 @@ FeedsApi enable enable - 8.2024.712 + 8.2024.726 diev 2022-2024 Dmitrii Evdokimov Получение фидов из FinCERT (АСОИ ФинЦЕРТ) Банка России. Обновление референсного проекта до TLS с сертификатами. diff --git a/FeedsAPI/FincertAPI.cs b/FeedsAPI/FincertAPI.cs index 74d1caa..6436fd5 100644 --- a/FeedsAPI/FincertAPI.cs +++ b/FeedsAPI/FincertAPI.cs @@ -150,7 +150,7 @@ public async Task LogoutFromASOIAsync() { try { - var response = await _tlsClient.PostAsJsonAsync(_api + "account/logout", string.Empty); + using var response = await _tlsClient.PostAsJsonAsync(_api + "account/logout", string.Empty); response.EnsureSuccessStatusCode(); if (response.StatusCode == HttpStatusCode.OK) @@ -204,7 +204,7 @@ public async Task LogoutFromASOIAsync() for (int i = 0; i < 3; i++) // Сколько делаем попыток получить данные { - var response = await _tlsClient.GetAsync(_api + $"antifraud/feeds/{feed}"); + using var response = await _tlsClient.GetAsync(_api + $"antifraud/feeds/{feed}"); response.EnsureSuccessStatusCode(); if (response.StatusCode == HttpStatusCode.OK) // Ответ получен @@ -260,7 +260,7 @@ public async Task LogoutFromASOIAsync() for (int i = 0; i < 3; i++) // Сколько делаем попыток получить данные { - var response = await _tlsClient.GetAsync(_api + $"antifraud/feeds/{feed}/download"); + using var response = await _tlsClient.GetAsync(_api + $"antifraud/feeds/{feed}/download"); response.EnsureSuccessStatusCode(); if (response.StatusCode == HttpStatusCode.OK) // Ответ получен diff --git a/FeedsAPI/README.md b/FeedsAPI/README.md index 3ed8de8..e64872c 100644 --- a/FeedsAPI/README.md +++ b/FeedsAPI/README.md @@ -1,6 +1,7 @@ # FeedsAPI [![Build status](https://ci.appveyor.com/api/projects/status/hpsbfj3qds34i4yb?svg=true)](https://ci.appveyor.com/project/diev/fincert-client) +[![.NET8 Desktop](https://github.com/diev/FinCERT-Client/actions/workflows/dotnet8-desktop.yml/badge.svg)](https://github.com/diev/FinCERT-Client/actions/workflows/dotnet8-desktop.yml) [![GitHub Release](https://img.shields.io/github/release/diev/FinCERT-Client.svg)](https://github.com/diev/FinCERT-Client/releases/latest) Получение по API фидов из FinCERT (АСОИ ФинЦЕРТ) Банка России. @@ -21,8 +22,8 @@ read-only. ## Config / Конфигурация -При первом запуске и отсутствии файла конфигурации `.config.json`, он -создается рядом с программой с параметрами по умолчанию. +При первом запуске и отсутствии файла конфигурации `FeedsAPI.config.json`, +он создается рядом с программой с параметрами по умолчанию. Никакие другие конфиги, переменные среды окружения и т.п. не используются. Важно заполнить вашими данными значения параметров: @@ -35,6 +36,9 @@ read-only. Если указываете файловые пути, то по правилам JSON надо удваивать `\\` в Windows и использовать `/` в Linux. +По окончании корректировки надо переключить параметр NewConfig = `true` +в `false` или удалить эту строчку полностью. + ## Exit codes / Коды возврата * 0 - успешно; @@ -44,21 +48,30 @@ read-only. ## Requirements / Требования * .NET 6-7-8 (Windows или Linux) -* КриптоПро для подключения с сертификатом TLS +* КриптоПро CSP для установки соединения TLS * Сертификат TLS клиента и цепочка доверия * Логин и пароль -Вариант Linux пока не тестировался, Stunnel программе не требуется. +## Linux + +Вариант Linux протестирован в WSL без установки КриптоПро. Пример сборки проекта под Linux (укажите нужную версию .NET) из папки с файлом FeedsAPI.csproj: dotnet publish -r linux-x64 -f net6.0 --self-contained -Запуск: +Запуск из папки с файлами программы: dotnet FeedsAPI.dll +## Breaking Changes / Важные изменения + +Выяснилось, что механизм получения файла настроек, работавший в Windows +(одноименный и рядом с exe), в Linux дает неправильное размещение файла. +Пока пришлось жестко прописать имя файла в коде. Далее придется менять +эту схему именования, крайне удобную ранее. + ## Versioning / Порядок версий Номер версии программы указывается по нарастающему принципу и строится @@ -80,5 +93,7 @@ read-only. ## License / Лицензия -Licensed under the [Apache License, Version 2.0](LICENSE). +Licensed under the [Apache License, Version 2.0](LICENSE). Вы можете использовать эти материалы под свою ответственность. + +[![Telegram](https://img.shields.io/badge/t.me-dievdo-blue?logo=telegram)](https://t.me/dievdo) diff --git a/FinCERT-Client/API/Bulletins.cs b/FinCERT-Client/API/Bulletins.cs index 738b5a7..1be277f 100644 --- a/FinCERT-Client/API/Bulletins.cs +++ b/FinCERT-Client/API/Bulletins.cs @@ -80,7 +80,8 @@ internal static class Bulletins public static async Task GetBulletinIdsAsync(int limit = 100, long offset = 0) { string url = $"bulletins?limit={limit}&offset={offset}"; - var response = await TlsClient.GetAsync(url); + using var response = await TlsClient.GetAsync(url); + response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync() ?? throw new BulletinsException( "Список бюллетеней не получен."); @@ -95,7 +96,8 @@ public static async Task GetBulletinIdsAsync(int limit = 100, long public static async Task GetBulletinIdsAsync(string path, int limit = 100, long offset = 0) { string url = $"bulletins?limit={limit}&offset={offset}"; - var response = await TlsClient.GetAsync(url); + using var response = await TlsClient.GetAsync(url); + response.EnsureSuccessStatusCode(); using var output = File.Create(path); await response.Content.CopyToAsync(output); } @@ -109,7 +111,8 @@ public static async Task GetBulletinInfosAsync(string[] ids) { string url = "bulletins/list"; var content = new BulletinList(ids); - var response = await TlsClient.PostAsJsonAsync(url, content); + using var response = await TlsClient.PostAsJsonAsync(url, content); + response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync() ?? throw new BulletinsException( "Информация по бюллетеням не получена."); @@ -124,7 +127,8 @@ public static async Task GetBulletinInfosAsync(string[] ids, string path) { string url = "bulletins/list"; var content = new BulletinList(ids); - var response = await TlsClient.PostAsJsonAsync(url, content); + using var response = await TlsClient.PostAsJsonAsync(url, content); + response.EnsureSuccessStatusCode(); using var output = File.Create(path); await response.Content.CopyToAsync(output); } @@ -137,7 +141,8 @@ public static async Task GetBulletinInfosAsync(string[] ids, string path) public static async Task GetBulletinAttachInfoAsync(string id) { string url = $"bulletins/{id}"; - var response = await TlsClient.GetAsync(url); + using var response = await TlsClient.GetAsync(url); + response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync() ?? throw new BulletinsException( $"Информация по бюллетеню '{id}' не получена."); @@ -151,7 +156,8 @@ public static async Task GetBulletinAttachInfoAsync(string i public static async Task GetBulletinAttachInfoAsync(string id, string path) { string url = $"bulletins/{id}"; - var response = await TlsClient.GetAsync(url); + using var response = await TlsClient.GetAsync(url); + response.EnsureSuccessStatusCode(); using var output = File.Create(path); await response.Content.CopyToAsync(output); } @@ -164,7 +170,8 @@ public static async Task GetBulletinAttachInfoAsync(string id, string path) public static async Task DownloadAttachmentAsync(string id, string path) { string url = $"attachments/{id}/download"; - var response = await TlsClient.GetAsync(url); + using var response = await TlsClient.GetAsync(url); + response.EnsureSuccessStatusCode(); using var output = File.Create(path); await response.Content.CopyToAsync(output); } diff --git a/FinCERT-Client/API/Feeds.cs b/FinCERT-Client/API/Feeds.cs index 175a18b..8bc9a55 100644 --- a/FinCERT-Client/API/Feeds.cs +++ b/FinCERT-Client/API/Feeds.cs @@ -39,7 +39,8 @@ internal static class Feeds public static async Task GetFeedsStatusAsync(FeedType feed) { string url = $"antifraud/feeds/{feed}"; - var response = await TlsClient.GetAsync(url); + using var response = await TlsClient.GetAsync(url); + response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync() ?? throw new FeedsException("Статус фидов не получен."); } @@ -54,7 +55,8 @@ public static async Task DownloadFeedsAsync(FeedType feed, string path) Directory.CreateDirectory(path); string url = $"antifraud/feeds/{feed}/download"; - var response = await TlsClient.GetAsync(url); + using var response = await TlsClient.GetAsync(url); + response.EnsureSuccessStatusCode(); string name = feed switch { diff --git a/FinCERT-Client/FinCERT-Client.csproj b/FinCERT-Client/FinCERT-Client.csproj index 7a2c0c0..ccb6a23 100644 --- a/FinCERT-Client/FinCERT-Client.csproj +++ b/FinCERT-Client/FinCERT-Client.csproj @@ -6,7 +6,7 @@ FincertClient enable enable - 8.2024.712 + 8.2024.726 diev 2022-2024 Dmitrii Evdokimov Получение фидов и бюллетеней из FinCERT (АСОИ ФинЦЕРТ) Банка России. diff --git a/FinCERT-Client/Managers/ConfigManager.cs b/FinCERT-Client/Managers/ConfigManager.cs index eaa745b..ce85b85 100644 --- a/FinCERT-Client/Managers/ConfigManager.cs +++ b/FinCERT-Client/Managers/ConfigManager.cs @@ -33,16 +33,23 @@ private static JsonSerializerOptions GetJsonOptions() public static Config Read() { - string appsettings = Path.ChangeExtension(Environment.ProcessPath!, ".config.json"); + // Works in Windows, fails in Linux (/usr/lib/dotnet...) + // string appsettings = Path.ChangeExtension(Environment.ProcessPath!, ".config.json"); + + string appsettings = Path.Combine(AppContext.BaseDirectory, "FinCERT-Client.config.json"); //TODO what? if (File.Exists(appsettings)) { using var read = File.OpenRead(appsettings); var config = JsonSerializer.Deserialize(read); - if (config is null || config.NewConfig) + if (config is null) + throw new NewConfigException( + @$"Файл настроек ""{appsettings}"" испорчен - удалите его."); + + if (config.NewConfig) throw new NewConfigException( - ); + @$"Откорректируйте файл настроек ""{appsettings}"" и отключите NewConfig."); return config; } @@ -52,22 +59,20 @@ public static Config Read() JsonSerializer.Serialize(write, newConfig, GetJsonOptions()); throw new NewConfigException( - @$"Создан новый файл настроек ""{appsettings}""."); + @$"Создан новый файл настроек ""{appsettings}"" - откорректируйте его."); } } internal class NewConfigException : Exception { - const string message = @"Необходимо откорректировать новый конфиг ""{0}""."; - public NewConfigException() : base() { } - public NewConfigException(string config) - : base(string.Format(message, config)) { } + public NewConfigException(string message) + : base(message) { } - public NewConfigException(string config, Exception inner) - : base(string.Format(message, config), inner) { } + public NewConfigException(string message, Exception inner) + : base(message, inner) { } } public class ConfigException : Exception diff --git a/README.md b/README.md index 6d002ed..b0be97e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # FinCERT-Client [![Build status](https://ci.appveyor.com/api/projects/status/hpsbfj3qds34i4yb?svg=true)](https://ci.appveyor.com/project/diev/fincert-client) +[![.NET8 Desktop](https://github.com/diev/FinCERT-Client/actions/workflows/dotnet8-desktop.yml/badge.svg)](https://github.com/diev/FinCERT-Client/actions/workflows/dotnet8-desktop.yml) [![GitHub Release](https://img.shields.io/github/release/diev/FinCERT-Client.svg)](https://github.com/diev/FinCERT-Client/releases/latest) Получение по API фидов и бюллетеней из FinCERT (АСОИ ФинЦЕРТ) Банка России. @@ -49,14 +50,10 @@ feeds_20240703-03.zip), то его содержимое будет распак ## Config / Конфигурация -При первом запуске и отсутствии файла конфигурации `.config.json`, он -создается рядом с программой с параметрами по умолчанию. +При первом запуске и отсутствии файла конфигурации `FinCERT-Client.config.json`, +он создается рядом с программой с параметрами по умолчанию. Никакие другие конфиги, переменные среды окружения и т.п. не используются. -В этом файле есть параметр `NewConfig` (true) - программа будет ждать -корректировки созданного нового файла конфигурации при каждом запуске, -пока этот параметр не будет удален или переключен в false. - Важно заполнить вашими данными значения параметров: * `MyThumbprint` - отпечаток сертификата клиента, зарегистрированного на @@ -72,6 +69,9 @@ feeds_20240703-03.zip), то его содержимое будет распак Если указываете файловые пути, то по правилам JSON надо удваивать `\\` в Windows и использовать `/` в Linux. +По окончании корректировки надо переключить параметр NewConfig = `true` +в `false` или удалить эту строчку полностью. + ## Parameters / Опциональные параметры командной строки * `-checklist` - сформировать в папке `BulletinsDownloads\CheckList` @@ -117,21 +117,32 @@ feeds_20240703-03.zip), то его содержимое будет распак ## Requirements / Требования * .NET 6-7-8 (Windows или Linux) -* КриптоПро для подключения с сертификатом TLS +* КриптоПро CSP для установки соединения TLS * Сертификат TLS клиента и цепочка доверия * Логин и пароль -Вариант Linux пока не тестировался, Stunnel программе не требуется. +*Stunnel* программе не требуется - она сама поднимает соединение TLS. + +## Linux + +Вариант Linux протестирован в WSL без установки КриптоПро. Пример сборки проекта под Linux (укажите нужную версию .NET) из папки с файлом FinCERT-Client.csproj: - dotnet publish -r linux-x64 -f net6.0 --self-contained + dotnet publish -r linux-x64 -f net8.0 --self-contained -Запуск: +Запуск из папки с файлами программы: dotnet FinCERT-Client.dll +## Breaking Changes / Важные изменения + +Выяснилось, что механизм получения файла настроек, работавший в Windows +(одноименный и рядом с exe), в Linux дает неправильное размещение файла. +Пока пришлось жестко прописать имя файла в коде. Далее придется менять +эту схему именования, крайне удобную ранее. + ## Versioning / Порядок версий Номер версии программы указывается по нарастающему принципу и строится @@ -153,5 +164,7 @@ feeds_20240703-03.zip), то его содержимое будет распак ## License / Лицензия -Licensed under the [Apache License, Version 2.0](LICENSE). +Licensed under the [Apache License, Version 2.0](LICENSE). Вы можете использовать эти материалы под свою ответственность. + +[![Telegram](https://img.shields.io/badge/t.me-dievdo-blue?logo=telegram)](https://t.me/dievdo) diff --git a/build.cmd b/build.cmd index bbc824f..e146273 100644 --- a/build.cmd +++ b/build.cmd @@ -28,6 +28,10 @@ call :bin %1 %option% %prj% net6.0 x64 call :bin %1 %option% %prj% net7.0 x64 call :bin %1 %option% %prj% net8.0 x64 +rem Linux +rem set option=4 +rem call :bin %1 %option% %prj% net8.0 x64 + call :version_txt %1 %prj% > bin\version.txt set pack=%1-v%version%.zip @@ -47,10 +51,10 @@ rem %3 - project.csproj rem %4 - net rem %5 - x86/x64 echo === Build %1 %4 %5 === -if /%2/==/1/ dotnet publish %3 -o bin\%4\%5 -f %4 -r win-%5 -if /%2/==/2/ dotnet publish %3 -o bin\%4\%5 -f %4 -r win-%5 -p:PublishSingleFile=true --no-self-contained -if /%2/==/3/ dotnet publish %3 -o bin\%4\%5 -f %4 -r win-%5 -p:PublishSingleFile=true -if /%2/==/4/ dotnet publish %3 -o bin\%4\%5 -f %4 -r linux-%5 --self-contained +if /%2/==/1/ dotnet publish %3 -o bin\%4.%5 -f %4 -r win-%5 +if /%2/==/2/ dotnet publish %3 -o bin\%4.%5 -f %4 -r win-%5 -p:PublishSingleFile=true --no-self-contained +if /%2/==/3/ dotnet publish %3 -o bin\%4.%5 -f %4 -r win-%5 -p:PublishSingleFile=true +if /%2/==/4/ dotnet publish %3 -o bin\%4.linux-%5 -f %4 -r linux-%5 --self-contained goto :eof :init @@ -94,7 +98,8 @@ echo Requires .NET [Desktop] Runtime to run echo Download from https://dotnet.microsoft.com/download echo. echo Run once to create %1.config.json -echo and correct it +echo correct it and switch NewConfig to false +echo (or delete this row with NewConfig) echo. echo https://github.com/diev/%repo% echo https://gitverse.ru/diev/%repo%