Files
IOT/DeviceCommand/Base/EnovaDataReporter.cs
2026-06-10 10:53:10 +08:00

216 lines
7.8 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.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Model.Model;
using Newtonsoft.Json;
namespace DeviceCommand.Base
{
public class EnovaDataReporter : IEnovaDataReporter
{
private readonly HttpClient _httpClient;
// ========= 静态注册表:用于 EnovaDataController 反向查找设备实例并分发数据 =========
private static readonly List<EnovaDataReporter> _instances = new List<EnovaDataReporter>();
private static readonly object _registryLock = new object();
/// <summary>
/// 当前已注册的所有 EnovaDataReporter 实例(线程安全快照)
/// </summary>
public static IReadOnlyList<EnovaDataReporter> Instances
{
get
{
lock (_registryLock)
{
return _instances.ToList();
}
}
}
/// <summary>
/// 设备编码:用于在多设备场景下按 deviceCode 过滤分发
/// 留空表示该实例接收所有上报数据
/// </summary>
public string DeviceCode { get; set; } = string.Empty;
// 显式实现/自动属性,方便外部随时更新配置
public string TargetUrl { get; set; } = "http://127.0.0.1:8080/api/channel/state";
public int TimeoutMilliseconds { get; set; } = 5000;
/// <summary>
/// 收到下位机 POST 上报数据时触发
/// </summary>
public event EventHandler<EnovaChannelDataReceivedEventArgs>? ChannelDataReceived;
public EnovaDataReporter(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
// 自动注册到静态实例表,便于 Controller 反向找到本实例
lock (_registryLock)
{
_instances.Add(this);
}
}
/// <summary>
/// 从静态注册表中注销当前实例(设备销毁/释放时调用)
/// </summary>
public virtual void Unregister()
{
lock (_registryLock)
{
_instances.Remove(this);
}
}
public async Task<ApiResponse> ReportChannelStateAsync(List<EnovaChannelData> dataList, CancellationToken ct = default)
{
if (dataList == null || dataList.Count == 0)
{
return new ApiResponse { Success = false, ErrorInfo = "上报数据集合为空" };
}
if (string.IsNullOrWhiteSpace(TargetUrl))
{
return new ApiResponse { Success = false, ErrorInfo = "目标上报 URL 未配置" };
}
try
{
// 1. 序列化为标准 JSON 字符串
string jsonPayload = JsonConvert.SerializeObject(dataList);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
// 2. 绑定联动超时控制
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
if (TimeoutMilliseconds > 0)
{
cts.CancelAfter(TimeoutMilliseconds);
}
HttpResponseMessage response = await _httpClient.PostAsync(TargetUrl, content, cts.Token);
// 4. 解析返回值
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ApiResponse>(responseContent);
return result ?? new ApiResponse { Success = true }; // 防止对方返回空Body
}
else
{
return new ApiResponse
{
Success = false,
ErrorInfo = $"服务器响应错误代码: {(int)response.StatusCode} {response.ReasonPhrase}"
};
}
}
catch (Exception ex)
{
// 完美承接你上位机原有的异常日志记录器逻辑
// Logger.LoggerHelper.ErrorWithNotify($"Enova3 数据上传失败: {ex.Message}");
return new ApiResponse { Success = false, ErrorInfo = $"网络异常: {ex.Message}" };
}
}
/// <summary>
/// 处理 Controller 转发过来的下位机上报数据,并触发事件
/// </summary>
public virtual ApiResponse HandleIncomingChannelData(List<EnovaChannelData> dataList)
{
if (dataList == null || dataList.Count == 0)
{
return new ApiResponse { Success = false, ErrorInfo = "接收到的数据为空" };
}
try
{
// 触发事件,让派生类(如 CTS3或外部订阅者处理具体业务
ChannelDataReceived?.Invoke(this, new EnovaChannelDataReceivedEventArgs(dataList));
return new ApiResponse { Success = true, ErrorInfo = string.Empty };
}
catch (Exception ex)
{
return new ApiResponse { Success = false, ErrorInfo = $"处理上报数据时异常: {ex.Message}" };
}
}
/// <summary>
/// 由 Controller 调用:将下位机上报的数据广播到所有匹配的实例
/// 当数据中含 DeviceCode 时,按 DeviceCode 精确匹配;否则广播给所有实例
/// </summary>
public static ApiResponse Dispatch(List<EnovaChannelData> dataList)
{
if (dataList == null || dataList.Count == 0)
{
return new ApiResponse { Success = false, ErrorInfo = "接收到的数据为空" };
}
EnovaDataReporter[] snapshot;
lock (_registryLock)
{
snapshot = _instances.ToArray();
}
if (snapshot.Length == 0)
{
return new ApiResponse { Success = false, ErrorInfo = "无可用的设备实例接收数据" };
}
// 按 DeviceCode 分组分发:同一批数据可能来自多个 deviceCode
var groups = dataList
.GroupBy(d => d?.DeviceCode ?? string.Empty)
.ToList();
var errors = new List<string>();
int successCount = 0;
foreach (var group in groups)
{
string deviceCode = group.Key;
var items = group.ToList();
// 选取目标1) 设置了相同 DeviceCode 的实例2) 没设置 DeviceCode 的实例(通用接收者)
var targets = snapshot
.Where(r => string.Equals(r.DeviceCode, deviceCode, StringComparison.OrdinalIgnoreCase)
|| string.IsNullOrEmpty(r.DeviceCode))
.ToList();
if (targets.Count == 0)
{
errors.Add($"DeviceCode={deviceCode} 无匹配的设备实例");
continue;
}
foreach (var target in targets)
{
var resp = target.HandleIncomingChannelData(items);
if (resp != null && resp.Success)
{
successCount++;
}
else
{
errors.Add(resp?.ErrorInfo ?? "未知错误");
}
}
}
return new ApiResponse
{
Success = errors.Count == 0,
ErrorInfo = errors.Count == 0 ? string.Empty : string.Join("", errors)
};
}
}
}