elfisa-pharmacy/src/ElectronicPharmacy/Classes/ApiClient.cs

337 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
namespace Электроннаяармация.Classes
{
public class ApiClient
{
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = null
};
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
private string _token;
public ApiClient(string baseUrl = null)
{
_baseUrl = AppConfig.NormalizeApiBaseUrl(baseUrl ?? AppConfig.ApiBaseUrl);
_httpClient = new HttpClient
{
// Крупный прайс тянем одним запросом — даём серверу время посчитать и отдать.
Timeout = TimeSpan.FromSeconds(120)
};
AppDebugLog.Info("ApiClient", $"Создан клиент. BaseUrl={_baseUrl}, Timeout={_httpClient.Timeout.TotalSeconds}s");
}
public string BaseUrl => _baseUrl;
public async Task<string> LoginAsync(string username, string password)
{
var request = new LoginRequest
{
Username = username,
Password = password
};
var path = "/auth/login";
var fullUrl = BuildFullUrl(path);
var requestJson = JsonSerializer.Serialize(request, JsonOptions);
var stopwatch = Stopwatch.StartNew();
AppDebugLog.ApiRequest("POST", fullUrl, requestJson, $"username={username}");
try
{
using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, fullUrl))
{
httpRequest.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(httpRequest);
var responseBody = await response.Content.ReadAsStringAsync();
stopwatch.Stop();
AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);
return ParseLoginResponse(response, responseBody);
}
}
catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
{
stopwatch.Stop();
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, "авторизацию");
}
catch (TaskCanceledException ex)
{
stopwatch.Stop();
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, "авторизацию");
}
catch (Exception ex)
{
stopwatch.Stop();
if (ex is UnauthorizedAccessException || ex is HttpRequestException)
{
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw;
}
AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw new HttpRequestException($"Ошибка при авторизации ({_baseUrl}): {ex.Message}", ex);
}
}
public void SetToken(string token)
{
_token = string.IsNullOrWhiteSpace(token) ? null : token;
AppDebugLog.Info("ApiClient", $"Токен установлен: {AppDebugLog.MaskToken(_token)}");
}
public string Token => _token;
public bool IsAuthenticated => !string.IsNullOrEmpty(_token);
public async Task<PriceSummaryResponse> GetPriceSummaryAsync(string supplierId = null, string regionId = null, string q = null, int? limit = null, int? offset = null)
{
EnsureAuthenticated();
var queryParams = new List<string>();
if (!string.IsNullOrEmpty(supplierId))
{
queryParams.Add($"supplier_id={Uri.EscapeDataString(supplierId)}");
}
if (!string.IsNullOrEmpty(regionId))
{
queryParams.Add($"region_id={Uri.EscapeDataString(regionId)}");
}
if (!string.IsNullOrWhiteSpace(q))
{
queryParams.Add($"q={Uri.EscapeDataString(q)}");
}
if (limit.HasValue)
{
queryParams.Add($"limit={limit.Value}");
}
if (offset.HasValue)
{
queryParams.Add($"offset={offset.Value}");
}
var path = "/api/supplier-prices/summary";
if (queryParams.Count > 0)
{
path += "?" + string.Join("&", queryParams);
}
var responseBody = await SendAuthorizedGetAsync(path, "загрузку прайса");
try
{
var summary = JsonSerializer.Deserialize<PriceSummaryResponse>(responseBody, JsonOptions);
var count = summary?.Summary?.Count ?? 0;
AppDebugLog.Info("ApiClient", $"Прайс получен: {count} позиций");
return summary;
}
catch (Exception ex)
{
AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ прайса", ex);
throw new HttpRequestException($"Некорректный ответ сервера при загрузке прайса: {ex.Message}", ex);
}
}
public async Task<List<InvoiceApiItem>> GetInvoicesAsync()
{
EnsureAuthenticated();
var responseBody = await SendAuthorizedGetAsync("/api/buyer/invoices", "загрузку накладных");
try
{
var items = JsonSerializer.Deserialize<List<InvoiceApiItem>>(responseBody, JsonOptions) ?? new List<InvoiceApiItem>();
AppDebugLog.Info("ApiClient", $"Накладные получены: {items.Count} шт.");
return items;
}
catch (Exception ex)
{
AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ накладных", ex);
throw new HttpRequestException($"Некорректный ответ сервера при загрузке накладных: {ex.Message}", ex);
}
}
private void EnsureAuthenticated()
{
if (!IsAuthenticated)
{
var ex = new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
AppDebugLog.Error("ApiClient", "Запрос без токена авторизации", ex);
throw ex;
}
}
private async Task<string> SendAuthorizedGetAsync(string path, string operationName)
{
var fullUrl = BuildFullUrl(path);
var stopwatch = Stopwatch.StartNew();
AppDebugLog.ApiRequest("GET", fullUrl, extra: $"token={AppDebugLog.MaskToken(_token)}");
try
{
using (var httpRequest = new HttpRequestMessage(HttpMethod.Get, fullUrl))
{
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
var response = await _httpClient.SendAsync(httpRequest);
var responseBody = await response.Content.ReadAsStringAsync();
stopwatch.Stop();
AppDebugLog.ApiResponse("GET", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);
if (response.IsSuccessStatusCode)
{
return responseBody;
}
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_token = null;
var error = TryDeserializeError(responseBody);
var message = $"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}";
AppDebugLog.ApiHttpError("GET", fullUrl, (int)response.StatusCode, responseBody, operationName);
var unauthorized = new UnauthorizedAccessException(message);
AppDebugLog.Error("ApiClient", message, unauthorized);
throw unauthorized;
}
var httpError = TryDeserializeError(responseBody);
var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
AppDebugLog.ApiHttpError("GET", fullUrl, (int)response.StatusCode, responseBody, operationName);
var httpEx = new HttpRequestException(httpMessage);
AppDebugLog.Error("ApiClient", httpMessage, httpEx);
throw httpEx;
}
}
catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
{
stopwatch.Stop();
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, operationName);
}
catch (TaskCanceledException ex)
{
stopwatch.Stop();
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw WrapNetworkError(ex, operationName);
}
catch (UnauthorizedAccessException)
{
throw;
}
catch (HttpRequestException)
{
throw;
}
catch (Exception ex)
{
stopwatch.Stop();
AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
throw;
}
}
private string ParseLoginResponse(HttpResponseMessage response, string responseBody)
{
if (response.IsSuccessStatusCode)
{
try
{
var loginResponse = JsonSerializer.Deserialize<LoginResponse>(responseBody, JsonOptions);
if (loginResponse == null || string.IsNullOrWhiteSpace(loginResponse.Token))
{
var emptyTokenEx = new HttpRequestException($"Сервер {_baseUrl} вернул пустой токен авторизации");
AppDebugLog.Error("ApiClient", "Пустой токен в ответе логина", emptyTokenEx);
throw emptyTokenEx;
}
_token = loginResponse.Token;
AppDebugLog.Info("ApiClient", $"Авторизация успешна. token={AppDebugLog.MaskToken(_token)}");
return _token;
}
catch (HttpRequestException)
{
throw;
}
catch (Exception ex)
{
AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ логина", ex);
throw new HttpRequestException($"Некорректный ответ сервера при авторизации: {ex.Message}", ex);
}
}
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
var error = TryDeserializeError(responseBody);
var message = $"Ошибка авторизации: {error?.Error ?? "Неверные учетные данные"}";
AppDebugLog.ApiHttpError("POST", BuildFullUrl("/auth/login"), (int)response.StatusCode, responseBody, "login");
var unauthorized = new UnauthorizedAccessException(message);
AppDebugLog.Error("ApiClient", message, unauthorized);
throw unauthorized;
}
var httpError = TryDeserializeError(responseBody);
var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
AppDebugLog.ApiHttpError("POST", BuildFullUrl("/auth/login"), (int)response.StatusCode, responseBody, "login");
var httpEx = new HttpRequestException(httpMessage);
AppDebugLog.Error("ApiClient", httpMessage, httpEx);
throw httpEx;
}
private string BuildFullUrl(string path)
{
return new Uri(new Uri(_baseUrl + "/"), path.TrimStart('/')).ToString();
}
private static ErrorResponse TryDeserializeError(string body)
{
if (string.IsNullOrWhiteSpace(body))
{
return null;
}
try
{
return JsonSerializer.Deserialize<ErrorResponse>(body, JsonOptions);
}
catch
{
return null;
}
}
private HttpRequestException WrapNetworkError(Exception ex, string operation)
{
var details = ex.InnerException != null && ex.InnerException.Message != ex.Message
? $" ({ex.InnerException.Message})"
: string.Empty;
return new HttpRequestException(
$"Не удалось выполнить {operation}. Сервер: {_baseUrl}{details}. {ex.Message}",
ex);
}
}
public sealed class InvoiceApiItem
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string SupplierName { get; set; }
public string ConsigneeName { get; set; }
public string InvoiceSum { get; set; }
public string RefuseSum { get; set; }
}
}