Refatora ProcessarMedicoesUseCase e ajusta dependências

Refatora o uso de HttpClient, removendo controle explícito de concorrência com SemaphoreSlim e RateLimiter, e adiciona suporte a certificados SSL com o método CreateHttpClient. Simplifica assinaturas de métodos e inicialização de dependências.

Ajusta a lógica de comparação de medições para maior precisão com Math.Round. Atualiza consultas SQL em AccessRepository e PostgresRepository para melhorar legibilidade e remover filtros específicos.

Altera o intervalo de datas no Program.cs e remove configurações de proxy e validação de certificado no HttpClient. Remove código legado e comentários desnecessários, melhorando a organização geral do código.
This commit is contained in:
Adriano Serighelli 2025-10-28 13:42:58 -03:00
parent 0db548b273
commit 9a91cc456f
4 changed files with 46 additions and 67 deletions

View File

@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Globalization;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml.Linq;
using Domain;
@ -11,19 +12,13 @@ namespace Application
{
private readonly IPostgresRepository _postgresRepository;
private readonly IAccessRepository _accessRepository;
private readonly HttpClient _httpClient;
private readonly RateLimiter _rateLimiter;
public ProcessarMedicoesUseCase(
IPostgresRepository postgresRepository,
IAccessRepository accessRepository,
HttpClient httpClient,
RateLimiter rateLimiter)
IAccessRepository accessRepository)
{
_postgresRepository = postgresRepository;
_accessRepository = accessRepository;
_httpClient = httpClient;
_rateLimiter = rateLimiter;
}
public async Task ExecuteAsync(DateTime dataIni, DateTime dataFim, string caminhoLog, CancellationToken ct)
@ -32,15 +27,9 @@ namespace Application
var operacoesLog = new ConcurrentBag<string>();
var perfis = (await _accessRepository.ObterPerfisAsync(ct)).ToList();
_httpClient.DefaultRequestHeaders.Add("SOAPAction", "listarMedidaCincoMinutos");
var endpoint = new Uri("https://servicos.ccee.org.br/ws/v2/MedidaCincoMinutosBSv2");
var datas = Enumerable.Range(0, (dataFim - dataIni).Days).Select(i => dataIni.AddDays(i));
// Controle explícito de concorrência HTTP (assíncrona)
// Ajuste conforme latência observada (ex.: 32, 48, 64).
var maxConcurrentRequests = 10;
using var httpSemaphore = new SemaphoreSlim(maxConcurrentRequests);
await Parallel.ForEachAsync(perfis, async (perfil, ctPerfil) =>
{
Console.WriteLine($"{DateTime.Now}: Iniciado ponto {perfil.CodigoSCDE}");
@ -58,7 +47,7 @@ namespace Application
{
try
{
await ProcessarDiaAsync(perfil, dia, existentes, endpoint, errosPersistentes, operacoesLog, ctDia, httpSemaphore);
await ProcessarDiaAsync(perfil, dia, existentes, endpoint, errosPersistentes, operacoesLog, ctDia);
}
catch (Exception ex)
{
@ -110,8 +99,7 @@ namespace Application
Uri endpoint,
ConcurrentBag<string> errosPersistentes,
ConcurrentBag<string> operacoesLog,
CancellationToken ct,
SemaphoreSlim httpSemaphore)
CancellationToken ct)
{
if (perfil.DataDeMigracao > dia)
{
@ -129,21 +117,14 @@ namespace Application
string payload = Xml_requisicao(dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, 1);
var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml");
// SEGURAR O SEMÁFORO APENAS NA CHAMADA HTTP
await _rateLimiter.WaitAsync(ct);
HttpResponseMessage response;
string resposta;
await httpSemaphore.WaitAsync(ct);
try
using (var client = CreateHttpClient())
{
response = await _httpClient.PostAsync(endpoint, conteudo, ct);
response = await client.PostAsync(endpoint, conteudo, ct);
resposta = await response.Content.ReadAsStringAsync();
}
finally
{
httpSemaphore.Release();
}
if ((int)response.StatusCode >= 400)
{
@ -171,7 +152,7 @@ namespace Application
}
}
await ProcessarXMLAsync(resposta, dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, existentes, ct, endpoint, httpSemaphore, 1, null, 1, operacoesLog);
await ProcessarXMLAsync(resposta, dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, existentes, ct, endpoint, 1, null, 1, operacoesLog);
sucesso = true;
}
catch (Exception ex)
@ -199,7 +180,6 @@ namespace Application
IDictionary<(string, double, int), Medicao> existentes,
CancellationToken ct,
Uri endpoint,
SemaphoreSlim httpSemaphore,
int paginaAtual = 1,
List<XElement>? acumulador = null,
int totalPaginas = 1,
@ -223,21 +203,15 @@ namespace Application
string payload = Xml_requisicao(dia, perfil, ponto, paginaAtual + 1);
var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml");
await _rateLimiter.WaitAsync(ct);
string proxXml;
await httpSemaphore.WaitAsync(ct);
try
using (var client = CreateHttpClient())
{
using var resp = await _httpClient.PostAsync(endpoint, conteudo, ct);
using var resp = await client.PostAsync(endpoint, conteudo, ct);
proxXml = await resp.Content.ReadAsStringAsync();
}
finally
{
httpSemaphore.Release();
}
await ProcessarXMLAsync(proxXml, dia, perfil, ponto, existentes, ct, endpoint, httpSemaphore, paginaAtual + 1, acumulador, totalPaginas, operacoesLog);
await ProcessarXMLAsync(proxXml, dia, perfil, ponto, existentes, ct, endpoint, paginaAtual + 1, acumulador, totalPaginas, operacoesLog);
return;
}
@ -364,10 +338,10 @@ namespace Application
else
{
if (existente.Origem != m.Origem ||
existente.AtivaConsumo != m.AtivaConsumo ||
existente.AtivaGeracao != m.AtivaGeracao ||
existente.ReativaConsumo != m.ReativaConsumo ||
existente.ReativaGeracao != m.ReativaGeracao)
Math.Round(existente.AtivaConsumo ?? 0, 10) != Math.Round(m.AtivaConsumo ?? 0, 10) ||
Math.Round(existente.AtivaGeracao ?? 0, 10) != Math.Round(m.AtivaGeracao ?? 0, 10) ||
Math.Round(existente.ReativaConsumo ?? 0, 10) != Math.Round(m.ReativaConsumo ?? 0, 10) ||
Math.Round(existente.ReativaGeracao ?? 0, 10) != Math.Round(m.ReativaGeracao ?? 0, 10))
{
alterados.Add(m);
}
@ -407,5 +381,18 @@ namespace Application
tex_req = tex_req.Replace("PAGNUM", pagina.ToString());
return tex_req;
}
private static HttpClient CreateHttpClient()
{
// Configura o HttpClientHandler para ignorar erros SSL
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
// Adiciona o certificado SSL
handler.ClientCertificates.Add(new X509Certificate2(@"X:\Back\APP Smart\Certificado\cert_ssl.pfx", "appsmart"));
return new HttpClient(handler);
}
}
}

View File

@ -19,11 +19,11 @@ namespace Infrastructure
using var connection = new OleDbConnection(_connectionString);
await connection.OpenAsync(ct);
//string sql = $"SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 AND Unidade_gerenciada ORDER BY cod_smart_unidade";
string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and (Cliente = 'RMC ALIMENTOS' OR Cliente = 'FERREIRA SUPERMERCADO' OR Cliente = 'VANGUARDA ALIMENTOS') AND Unidade_gerenciada ORDER BY PerfilCCEE";
string sql = $"SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 AND Unidade_gerenciada ORDER BY cod_smart_unidade";
//string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and (Cliente = 'RMC ALIMENTOS' OR Cliente = 'FERREIRA SUPERMERCADO' OR Cliente = 'VANGUARDA ALIMENTOS') AND Unidade_gerenciada ORDER BY PerfilCCEE";
//string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'ABEVÊ' and Unidade = 'ABV LOJA 29 - COXIM' AND Unidade_gerenciada ORDER BY PerfilCCEE";
//string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'RMC ALIMENTOS' AND Unidade_gerenciada ORDER BY PerfilCCEE";
//string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Codigo_SCDE = '3016021620'";
//string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Codigo_SCDE = 'MTTMAUENTR101'";
using var command = new OleDbCommand(sql, connection);
using var reader = await command.ExecuteReaderAsync(ct);

View File

@ -86,18 +86,18 @@ namespace Infrastructure
// Gerar a query dinâmica com os valores em formato de tabela temporária
var query = $@"
UPDATE med_5min
SET origem = nv.origem,
ativa_consumo = CAST(nv.ativa_consumo AS numeric),
ativa_geracao = CAST(nv.ativa_geracao AS numeric),
reativa_consumo = CAST(nv.reativa_consumo AS numeric),
reativa_geracao = CAST(nv.reativa_geracao AS numeric)
FROM (VALUES
{string.Join(",", valores)}
) AS nv (ponto, dia_num, minuto, origem, ativa_consumo, ativa_geracao, reativa_consumo, reativa_geracao)
WHERE med_5min.ponto = nv.ponto
AND med_5min.dia_num = nv.dia_num
AND med_5min.minuto = nv.minuto;";
UPDATE med_5min
SET origem = nv.origem,
ativa_consumo = CAST(nv.ativa_consumo AS numeric),
ativa_geracao = CAST(nv.ativa_geracao AS numeric),
reativa_consumo = CAST(nv.reativa_consumo AS numeric),
reativa_geracao = CAST(nv.reativa_geracao AS numeric)
FROM (VALUES
{string.Join(",", valores)}
) AS nv (ponto, dia_num, minuto, origem, ativa_consumo, ativa_geracao, reativa_consumo, reativa_geracao)
WHERE med_5min.ponto = nv.ponto
AND med_5min.dia_num = nv.dia_num
AND med_5min.minuto = nv.minuto;";
// Criação do comando NpgsqlBatchCommand
var cmd = new NpgsqlBatchCommand(query);

View File

@ -16,21 +16,13 @@ class Program
//DateTime dataIni = new DateTime(inicio.Year, inicio.Month, 1);
//DateTime dataFim = new DateTime(inicio.Year, inicio.Month, inicio.Day);
DateTime dataIni = new DateTime(inicio.Year, 10, 01);
DateTime dataFim = new DateTime(inicio.Year, 10, 23);
DateTime dataFim = new DateTime(inicio.Year, 10, 28);
// Configuração de dependências (pode usar um container DI depois)
var postgresRepo = new PostgresRepository(PG_CONN_STRING_PROD);
var accessRepo = new AccessRepository(ACCESS_CONN_STRING);
var httpClient = new HttpClient(new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Automatic,
Proxy = new WebProxy("127.0.0.1", 8888),
UseProxy = true,
ServerCertificateCustomValidationCallback = (HttpRequestMessage req, X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) => true
});
var rateLimiter = new RateLimiter(12, TimeSpan.FromSeconds(1));
var useCase = new ProcessarMedicoesUseCase(postgresRepo, accessRepo, httpClient, rateLimiter);
var useCase = new ProcessarMedicoesUseCase(postgresRepo, accessRepo);
await useCase.ExecuteAsync(dataIni, dataFim, caminhoLog, CancellationToken.None);