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 _instances = new List(); private static readonly object _registryLock = new object(); /// /// 当前已注册的所有 EnovaDataReporter 实例(线程安全快照) /// public static IReadOnlyList Instances { get { lock (_registryLock) { return _instances.ToList(); } } } /// /// 设备编码:用于在多设备场景下按 deviceCode 过滤分发 /// 留空表示该实例接收所有上报数据 /// 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; /// /// 收到下位机 POST 上报数据时触发 /// public event EventHandler? ChannelDataReceived; public EnovaDataReporter(HttpClient httpClient) { _httpClient = httpClient ?? new HttpClient(); // 自动注册到静态实例表,便于 Controller 反向找到本实例 lock (_registryLock) { _instances.Add(this); } } /// /// 从静态注册表中注销当前实例(设备销毁/释放时调用) /// public virtual void Unregister() { lock (_registryLock) { _instances.Remove(this); } } public async Task ReportChannelStateAsync(List 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(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}" }; } } /// /// 处理 Controller 转发过来的下位机上报数据,并触发事件 /// public virtual ApiResponse HandleIncomingChannelData(List 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}" }; } } /// /// 由 Controller 调用:将下位机上报的数据广播到所有匹配的实例 /// 当数据中含 DeviceCode 时,按 DeviceCode 精确匹配;否则广播给所有实例 /// public static ApiResponse Dispatch(List 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(); 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) }; } } }