356 lines
16 KiB
C#
356 lines
16 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Data;
|
|
using System.Data.OleDb;
|
|
using System.Globalization;
|
|
using System.Net;
|
|
using System.Net.Security;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Text;
|
|
using System.Xml.Linq;
|
|
using Npgsql;
|
|
|
|
internal class Plat_integ
|
|
{
|
|
static string PG_CONN_STRING_PROD = "Server = smart-energia-dev-pgsql.cykff7tj7mik.us-east-1.rds.amazonaws.com; Port = 5432; Database = smartenergiaprod; Username = postgres; Password = VfHml#Z78!%kvvNM; Timeout = 60; CommandTimeout = 60; ApplicationName = new_med_5_min; Connection Lifetime = 120; Minimum Pool Size = 2; Maximum Pool Size = 2;";
|
|
static readonly HttpClient client = 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
|
|
});
|
|
|
|
static async Task Main()
|
|
{
|
|
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
|
|
DateTime inicio = DateTime.Now;
|
|
|
|
string caminhoBD = @"\\srv-dados\documentos\Middle\Informativo Setorial\Modelo Word\BD1_dados cadastrais e faturas.accdb";
|
|
string caminhoLog = $@"\\srv-dados\documentos\Back\Carteira x.x\Codigo\Erros\log_erros_{DateTime.Now:MM_dd_HH_mm}.csv";
|
|
|
|
DateTime now = DateTime.Now;
|
|
//DateTime dataIni = new DateTime(now.Year, now.Month, 1);
|
|
//DateTime dataFim = new DateTime(now.Year, now.Month, now.Day);
|
|
DateTime dataIni = new DateTime(now.Year, 7, 1);
|
|
DateTime dataFim = new DateTime(now.Year, 8, 1);
|
|
|
|
var perfis = Busca_dad_BD(caminhoBD, dataIni);
|
|
|
|
await ProcessarMedicoesAsync(dataIni, dataFim, perfis, caminhoLog);
|
|
|
|
Console.WriteLine($"Concluído. Tempo total: {DateTime.Now - inicio}");
|
|
|
|
Console.ReadKey();
|
|
}
|
|
public static List<perfil> Busca_dad_BD(string caminho_BD, DateTime dataIni)
|
|
{
|
|
var lista = new List<perfil>();
|
|
|
|
string query = $"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 query = "SELECT Cod_5min, Codigo_SCDE 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 query = "SELECT Cod_5min, Codigo_SCDE FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'ALMAVIVA' and Unidade = 'GUARULHOS' AND unidadade_gerenciada ORDER BY PerfilCCEE";
|
|
//string query = "SELECT Cod_5min, Codigo_SCDE FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'bramagran' AND Unidade_gerenciada ORDER BY PerfilCCEE";
|
|
using (var connection = new OleDbConnection($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={caminho_BD};Jet OLEDB:Database Password=gds21"))
|
|
{
|
|
using var cmd = new OleDbCommand(query, connection);
|
|
connection.Open();
|
|
using var reader = cmd.ExecuteReader();
|
|
|
|
while (reader.Read())
|
|
{
|
|
lista.Add(
|
|
new perfil(
|
|
reader["Cod_5min"].ToString(),
|
|
reader["Codigo_SCDE"].ToString(),
|
|
DateTime.Parse(reader["Data_de_Migracao"].ToString())
|
|
)
|
|
);
|
|
}
|
|
}
|
|
return lista;
|
|
}
|
|
|
|
private static async Task ProcessarMedicoesAsync(DateTime dataIni, DateTime dataFim, List<perfil> perfis, string caminhoLog)
|
|
{
|
|
var limiter = new RateLimiter(400, TimeSpan.FromMinutes(1));
|
|
|
|
var errosPersistentes = new ConcurrentBag<string>();
|
|
|
|
await using var dataSource = NpgsqlDataSource.Create(PG_CONN_STRING_PROD);
|
|
|
|
client.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));
|
|
|
|
await Parallel.ForEachAsync(perfis, async (perfil, ct) =>
|
|
{
|
|
try
|
|
{
|
|
Console.WriteLine($"{DateTime.Now}: Iniciado ponto {perfil._Codigo_SCDE}");
|
|
if (perfil._Cod_5min == "0" || perfil._Cod_5min == string.Empty)
|
|
{
|
|
Console.WriteLine($"Pular {perfil._Codigo_SCDE} - (cod 5 min pendente)");
|
|
errosPersistentes.Add($"{perfil._Cod_5min};{perfil._Codigo_SCDE}; cod_5min pendente");
|
|
return;
|
|
}
|
|
string sqlSelect = @"
|
|
SELECT ponto, dia_num, minuto, origem,
|
|
ativa_consumo, ativa_geracao, reativa_consumo, reativa_geracao
|
|
FROM med_5min
|
|
WHERE ponto = @ponto AND dia_num >= @data_ini AND dia_num < @data_fim;
|
|
";
|
|
|
|
var existentes = new Dictionary<(string, double, int), dynamic>();
|
|
|
|
await using (var command = dataSource.CreateCommand(sqlSelect))
|
|
{
|
|
|
|
command.Parameters.AddWithValue("ponto", perfil._Codigo_SCDE + "P");
|
|
command.Parameters.AddWithValue("data_ini", dataIni.ToOADate());
|
|
command.Parameters.AddWithValue("data_fim", dataFim.ToOADate());
|
|
|
|
await using (var reader = await command.ExecuteReaderAsync(ct))
|
|
{
|
|
while (await reader.ReadAsync())
|
|
{
|
|
existentes[(reader.GetString(0), reader.GetDouble(1), reader.GetInt32(2))] = new
|
|
{
|
|
Origem = reader.GetString(3),
|
|
AtivaC = reader.GetDouble(4),
|
|
AtivaG = reader.GetDouble(5),
|
|
ReatC = reader.GetDouble(6),
|
|
ReatG = reader.GetDouble(7)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (DateTime dia in datas)
|
|
{
|
|
int tentativas = 0;
|
|
bool sucesso = false;
|
|
|
|
while (tentativas < 5 && !sucesso)
|
|
{
|
|
try
|
|
{
|
|
string payload = Xml_requisicao(dia, item.Item1, item.Item2, 1);
|
|
var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml");
|
|
|
|
await limiter.WaitAsync(ct);
|
|
using var response = await client.PostAsync(endpoint, conteudo, ct);
|
|
|
|
if ((int)response.StatusCode == 429) // limite da API
|
|
{
|
|
Console.WriteLine("⚠️ Limite de requisições atingido. Aguardando próximo minuto...");
|
|
var now = DateTime.UtcNow;
|
|
var delay = 60000 - (now.Second * 1000 + now.Millisecond);
|
|
await Task.Delay(delay, ct);
|
|
continue; // tentar de novo sem contar como falha
|
|
}
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
string resposta = await response.Content.ReadAsStringAsync(ct);
|
|
await ProcessarXMLAsync(resposta, await dataSource.OpenConnectionAsync());
|
|
|
|
sucesso = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tentativas++;
|
|
if (tentativas == 5)
|
|
{
|
|
errosPersistentes.Add($"{item.Item1};{item.Item2};Erro;{ex.Message.Replace("\n", "-n-")}");
|
|
}
|
|
else
|
|
{
|
|
int backoff = (int)Math.Pow(2.4, tentativas) * 1000; // exponencial
|
|
Console.WriteLine($"Erro na requisição ({ex.Message}), tentativa {tentativas}. Aguardando {backoff / 1000}s...");
|
|
await Task.Delay(backoff, ct);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Console.WriteLine($"{DateTime.Now}: Finalizado ponto {perfil._Codigo_SCDE}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex.ToString());
|
|
}
|
|
|
|
});
|
|
|
|
if (errosPersistentes.Count > 0)
|
|
{
|
|
File.WriteAllLines(caminhoLog, new[] { "Perfil;Ponto;Status;Message" }.Concat(errosPersistentes));
|
|
}
|
|
}
|
|
public static string Xml_requisicao(DateTime data_req, string perfil, string cod_ponto, int pagina)
|
|
{
|
|
string cam_ent, tex_req, sdat_req;
|
|
cam_ent = @"X:\Back\Plataforma de Integração CCEE\RequestPaginate.txt";
|
|
cod_ponto += "P";
|
|
sdat_req = data_req.ToString("yyyy-MM-ddT00:00:00");
|
|
tex_req = File.ReadAllText(cam_ent);
|
|
tex_req = tex_req.Replace("DATAALTERADA", sdat_req);
|
|
tex_req = tex_req.Replace("PONTOMEDICAO", cod_ponto);
|
|
tex_req = tex_req.Replace("CODPERFIL", perfil);
|
|
tex_req = tex_req.Replace("PAGNUM", pagina.ToString());
|
|
return tex_req;
|
|
}
|
|
private static async Task<int> ProcessarXMLAsync(string xml, NpgsqlConnection connection, List<XElement>? acumulador = null, int paginaAtual = 1, int totalPaginas = 1)
|
|
{
|
|
var doc = XDocument.Parse(xml);
|
|
XNamespace ns = "http://xmlns.energia.org.br/BO/v2";
|
|
|
|
int.TryParse(doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "totalPaginas")?.Value, out totalPaginas);
|
|
int.TryParse(doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "numero")?.Value, out paginaAtual);
|
|
|
|
|
|
var medidas = doc.Descendants(ns + "medida")
|
|
.Where(x => (string)x.Element(ns + "tipoEnergia") == "L");
|
|
|
|
acumulador ??= new List<XElement>();
|
|
acumulador.AddRange(medidas);
|
|
|
|
if (paginaAtual < totalPaginas)
|
|
{
|
|
// Requisita próxima página
|
|
string payload = Xml_requisicao(dia, perfil, ponto, paginaAtual + 1);
|
|
|
|
var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml");
|
|
await limiter.WaitAsync(ct);
|
|
using var resp = await client.PostAsync("https://servicos.ccee.org.br/ws/v2/MedidaCincoMinutosBSv2", conteudo);
|
|
|
|
string proxXml = await resp.Content.ReadAsStringAsync();
|
|
|
|
await ProcessarXMLAsync(proxXml, dataSource, dia, perfil, ponto, existentes, limiter, ct, paginaAtual + 1, acumulador, totalPaginas);
|
|
return;
|
|
}
|
|
|
|
var medidasProcessadas = acumulador
|
|
.Select(m =>
|
|
{
|
|
string origem = m.Element(ns + "coletaMedicao")?.Element(ns + "tipo")?.Element(ns + "nome")?.Value ?? "";
|
|
string ponto = m.Element(ns + "medidor")?.Element(ns + "codigo")?.Value ?? "";
|
|
DateTime data = DateTime.Parse(m.Element(ns + "data")?.Value ?? "");
|
|
double diaNum = (data.ToOADate() - data.ToOADate() % 1);
|
|
int minuto = data.Hour * 60 + data.Minute;
|
|
if (minuto == 0) { minuto = 1440; diaNum--; }
|
|
|
|
double.TryParse(m.Element(ns + "energiaAtiva")?.Element(ns + "consumo")?.Element(ns + "valor")?.Value, out double ativa_c);
|
|
double.TryParse(m.Element(ns + "energiaAtiva")?.Element(ns + "geracao")?.Element(ns + "valor")?.Value, out double ativa_g);
|
|
double.TryParse(m.Element(ns + "energiaReativa")?.Element(ns + "consumo")?.Element(ns + "valor")?.Value, out double reat_c);
|
|
double.TryParse(m.Element(ns + "energiaReativa")?.Element(ns + "geracao")?.Element(ns + "valor")?.Value, out double reat_g);
|
|
|
|
return new
|
|
{
|
|
Origem = origem,
|
|
Ponto = ponto,
|
|
DiaNum = diaNum,
|
|
Minuto = minuto,
|
|
AtivaC = ativa_c,
|
|
AtivaG = ativa_g,
|
|
ReatC = reat_c,
|
|
ReatG = reat_g
|
|
};
|
|
})
|
|
.GroupBy(x => new { x.Ponto, x.DiaNum, x.Minuto })
|
|
.Select(g =>
|
|
{
|
|
// Se houver alguma Inspeção Lógica → prioriza
|
|
var logica = g.FirstOrDefault(x => x.Origem == "Inspeção Lógica");
|
|
return logica ?? g.First(); // se não tiver lógica, pega qualquer (coleta diária)
|
|
});
|
|
|
|
foreach (var m in medidasProcessadas)
|
|
{
|
|
// Primeiro tenta UPDATE
|
|
string sqlUpdate = @"
|
|
UPDATE med_5min
|
|
SET origem = @origem,
|
|
ativa_consumo = @ativa_c,
|
|
ativa_geracao = @ativa_g,
|
|
reativa_consumo = @reat_c,
|
|
reativa_geracao = @reat_g
|
|
WHERE ponto = @ponto
|
|
AND dia_num = @dia_num
|
|
AND minuto = @minuto;";
|
|
|
|
using (var cmdUpdate = new NpgsqlCommand(sqlUpdate, connection))
|
|
{
|
|
cmdUpdate.Parameters.AddWithValue("origem", m.Origem);
|
|
cmdUpdate.Parameters.AddWithValue("ativa_c", m.AtivaC);
|
|
cmdUpdate.Parameters.AddWithValue("ativa_g", m.AtivaG);
|
|
cmdUpdate.Parameters.AddWithValue("reat_c", m.ReatC);
|
|
cmdUpdate.Parameters.AddWithValue("reat_g", m.ReatG);
|
|
cmdUpdate.Parameters.AddWithValue("ponto", m.Ponto);
|
|
cmdUpdate.Parameters.AddWithValue("dia_num", m.DiaNum);
|
|
cmdUpdate.Parameters.AddWithValue("minuto", m.Minuto);
|
|
|
|
int rowsAffected = await cmdUpdate.ExecuteNonQueryAsync();
|
|
|
|
if (rowsAffected == 1)
|
|
{
|
|
Console.WriteLine("atualizado");
|
|
}
|
|
|
|
public class RateLimiter
|
|
{
|
|
private readonly int _maxRequests;
|
|
private readonly TimeSpan _interval;
|
|
private int _requestCount;
|
|
private DateTime _windowStart;
|
|
private readonly object _lock = new();
|
|
|
|
public RateLimiter(int maxRequests, TimeSpan interval)
|
|
{
|
|
_maxRequests = maxRequests;
|
|
_interval = interval;
|
|
var now = DateTime.Now;
|
|
_windowStart = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute - (now.Minute % interval.Minutes), 0);
|
|
_requestCount = 0;
|
|
}
|
|
|
|
public async Task WaitAsync(CancellationToken ct)
|
|
{
|
|
while (true)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if ((DateTime.Now - _windowStart) > _interval)
|
|
{
|
|
// reset janela
|
|
_windowStart = DateTime.Now;
|
|
_requestCount = 0;
|
|
}
|
|
|
|
if (_requestCount < _maxRequests)
|
|
{
|
|
_requestCount++;
|
|
return; // pode prosseguir
|
|
}
|
|
}
|
|
|
|
// já bateu o limite → espera até reset
|
|
await Task.Delay(200, ct); // aguarda 200ms e tenta de novo
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class perfil
|
|
{
|
|
public string _Cod_5min { get; set; }
|
|
public string _Codigo_SCDE { get; set; }
|
|
public DateTime _Data_de_Migracao { get; set; }
|
|
|
|
public perfil(string cod_5min, string codigo_scde, DateTime data_de_migracao)
|
|
{
|
|
_Cod_5min = cod_5min;
|
|
_Codigo_SCDE = codigo_scde;
|
|
_Data_de_Migracao = data_de_migracao;
|
|
}
|
|
} |