418 lines
16 KiB
C#
418 lines
16 KiB
C#
using Microsoft.ClearScript.JavaScript;
|
||
using Microsoft.Extensions.Logging;
|
||
using Newtonsoft.Json;
|
||
using Org.BouncyCastle.Utilities;
|
||
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using YunDa.Server.ISMSTcp.Domain;
|
||
using YunDa.Server.ISMSTcp.Models;
|
||
using YunDa.SOMS.DataTransferObject.ExternalEntities.BeijingYounuo;
|
||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||
|
||
namespace YunDa.Server.ISMSTcp.Services
|
||
{
|
||
/// <summary>
|
||
/// 虚端子处理服务
|
||
/// 负责处理虚端子相关的业务逻辑
|
||
/// </summary>
|
||
public class VirtualTerminalHandler
|
||
{
|
||
private readonly ILogger<VirtualTerminalHandler> _logger;
|
||
private readonly WebApiRequest _webApiRequest;
|
||
private readonly ZzDataCacheContainerInit _zzDataCacheContainerInit;
|
||
|
||
// 🔧 新增:光纤虚点处理相关字段
|
||
private readonly ConcurrentDictionary<string, OpticalFiberDto> _virtualPointToOpticalFiberMapping = new ConcurrentDictionary<string, OpticalFiberDto>();
|
||
|
||
// 初始化状态
|
||
public volatile bool _isOpticalFiberMappingInitialized = false;
|
||
private readonly object _opticalFiberInitLock = new object();
|
||
|
||
|
||
//数据缓冲队列
|
||
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(ZzDataCacheContainerDataType.eVA, 5);
|
||
|
||
|
||
public VirtualTerminalHandler(ILogger<VirtualTerminalHandler> logger, WebApiRequest webApiRequest, ZzDataCacheContainerInit zzDataCacheContainerInit)
|
||
{
|
||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||
_webApiRequest = webApiRequest ?? throw new ArgumentNullException(nameof(webApiRequest));
|
||
_zzDataCacheContainerInit = zzDataCacheContainerInit;
|
||
_zzDataCacheContainerInit.InitVariantBaseinfo(_zzDataCacheContainer);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化光纤虚点映射
|
||
/// </summary>
|
||
/// <param name="maxRetries">最大重试次数</param>
|
||
/// <param name="retryDelayMs">重试间隔(毫秒)</param>
|
||
/// <returns>初始化任务</returns>
|
||
public async Task<bool> InitOpticalFiberMappingAsync(int maxRetries = 3, int retryDelayMs = 2000)
|
||
{
|
||
if (_isOpticalFiberMappingInitialized)
|
||
{
|
||
_logger.LogInformation("光纤虚点映射已经初始化完成");
|
||
return true;
|
||
}
|
||
|
||
lock (_opticalFiberInitLock)
|
||
{
|
||
if (_isOpticalFiberMappingInitialized)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
_logger.LogInformation("开始初始化光纤虚点映射...");
|
||
}
|
||
|
||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("第 {Attempt}/{MaxRetries} 次尝试获取光纤配置数据", attempt, maxRetries);
|
||
|
||
// 获取光纤配置数据
|
||
var opticalFibers = await _webApiRequest.GetOpticalFiberListAsync();
|
||
|
||
if (opticalFibers == null || opticalFibers.Count == 0)
|
||
{
|
||
_logger.LogWarning("第 {Attempt} 次查询光纤配置数据为空", attempt);
|
||
|
||
if (attempt < maxRetries)
|
||
{
|
||
_logger.LogInformation("等待 {DelayMs} 毫秒后重试...", retryDelayMs);
|
||
await Task.Delay(retryDelayMs);
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
_logger.LogError("所有重试都失败,无法获取光纤配置数据");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 创建虚点编码到光纤配置的映射
|
||
int mappingCount = 0;
|
||
foreach (var fiber in opticalFibers.Where(f => f.IsActive && !string.IsNullOrWhiteSpace(f.VirtualPointCode)))
|
||
{
|
||
if (_virtualPointToOpticalFiberMapping.TryAdd(fiber.VirtualPointCode, fiber))
|
||
{
|
||
mappingCount++;
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("虚点编码重复: {VirtualPointCode}", fiber.VirtualPointCode);
|
||
}
|
||
}
|
||
|
||
lock (_opticalFiberInitLock)
|
||
{
|
||
_isOpticalFiberMappingInitialized = true;
|
||
}
|
||
|
||
_logger.LogInformation("光纤虚点映射初始化成功!光纤配置: {OpticalFiberCount} 个,虚点映射: {MappingCount} 个",
|
||
opticalFibers.Count, mappingCount);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "第 {Attempt} 次初始化光纤虚点映射时发生错误", attempt);
|
||
|
||
if (attempt < maxRetries)
|
||
{
|
||
_logger.LogInformation("等待 {DelayMs} 毫秒后重试...", retryDelayMs);
|
||
await Task.Delay(retryDelayMs);
|
||
}
|
||
else
|
||
{
|
||
_logger.LogError("所有重试都失败,初始化光纤虚点映射失败");
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理虚端子数据
|
||
/// </summary>
|
||
/// <param name="virtualTerminalData">虚端子数据对象</param>
|
||
/// <returns>处理任务</returns>
|
||
public async Task ProcessVirtualTerminalDataAsync(VirtualTerminalData virtualTerminalData)
|
||
{
|
||
try
|
||
{
|
||
if (virtualTerminalData == null)
|
||
{
|
||
_logger.LogWarning("虚端子数据为空,跳过处理");
|
||
return;
|
||
}
|
||
|
||
if (!virtualTerminalData.IsValid())
|
||
{
|
||
_logger.LogWarning("虚端子数据无效:{Data}", virtualTerminalData);
|
||
return;
|
||
}
|
||
|
||
_logger.LogInformation("开始处理虚端子数据:{Data}", virtualTerminalData);
|
||
|
||
// 解析时间戳
|
||
if (virtualTerminalData.TryParseDateTime(out DateTime timestamp))
|
||
{
|
||
_logger.LogInformation("虚端子时间戳解析成功:{Timestamp}", timestamp);
|
||
}
|
||
else
|
||
{
|
||
_logger.LogInformation("虚端子时间戳解析失败:{TimeString}", virtualTerminalData.T);
|
||
}
|
||
|
||
// 解析数值
|
||
if (virtualTerminalData.TryParseValue(out double value))
|
||
{
|
||
_logger.LogInformation("虚端子数值解析成功:{Value}", value);
|
||
}
|
||
else
|
||
{
|
||
_logger.LogInformation("虚端子数值为非数字类型:{ValueString}", virtualTerminalData.V);
|
||
}
|
||
|
||
// 根据UA_ID执行不同的处理逻辑
|
||
await ProcessByUAId(virtualTerminalData);
|
||
|
||
//存入缓存
|
||
SaveDataToCache(virtualTerminalData.VA_ID, value, timestamp);
|
||
|
||
_logger.LogInformation("虚端子数据处理完成:{UA_ID}", virtualTerminalData.VA_ID);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理虚端子数据时发生异常:{Data}", virtualTerminalData);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据UA_ID执行不同的处理逻辑
|
||
/// </summary>
|
||
/// <param name="data">虚端子数据</param>
|
||
/// <returns>处理任务</returns>
|
||
private async Task ProcessByUAId(VirtualTerminalData data)
|
||
{
|
||
try
|
||
{
|
||
await ProcessDefaultLogic(data);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "根据UA_ID处理虚端子数据时发生异常:{UA_ID}", data.VA_ID);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 默认处理逻辑 - 处理光纤虚点
|
||
/// </summary>
|
||
/// <param name="data">虚端子数据</param>
|
||
/// <returns>处理任务</returns>
|
||
private async Task ProcessDefaultLogic(VirtualTerminalData data)
|
||
{
|
||
try
|
||
{
|
||
if (!_isOpticalFiberMappingInitialized)
|
||
{
|
||
_logger.LogDebug("光纤虚点映射尚未初始化,跳过处理");
|
||
return;
|
||
}
|
||
|
||
// 检查是否有对应的光纤配置
|
||
if (!_virtualPointToOpticalFiberMapping.TryGetValue(data.VA_ID, out var opticalFiber))
|
||
{
|
||
_logger.LogDebug("未找到虚点编码 {VA_ID} 对应的光纤配置", data.VA_ID);
|
||
return;
|
||
}
|
||
|
||
// 解析虚点值
|
||
if (!data.TryParseValue(out double value))
|
||
{
|
||
_logger.LogWarning("无法解析虚点值: {Value}, 虚点编码: {VA_ID}", data.V, data.VA_ID);
|
||
return;
|
||
}
|
||
|
||
_logger.LogDebug("处理光纤虚点: {VA_ID}, 值: {Value}, 光纤: {TwinId}", data.VA_ID, value, opticalFiber.TwinId);
|
||
|
||
// 根据虚点值处理报警
|
||
//Boolean型变量的值(真,假,不定)2018-04-26从uPro设备
|
||
//TVariantBoolValue = (vbTrue = 0,vbFalse = 1 vbUncertain = 2):
|
||
if (Math.Abs(value) < 0.0001) // 故障状态
|
||
{
|
||
await PushOpticalFiberAlarmAsync(opticalFiber, data);
|
||
}
|
||
else if (Math.Abs(value - 1) < 0.0001) // 正常状态
|
||
{
|
||
await DeleteOpticalFiberAlarmAsync(opticalFiber, data);
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("光纤虚点值异常: {Value}, 预期值为0或1, 虚点编码: {VA_ID}", value, data.VA_ID);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理光纤虚点默认逻辑时发生错误: {VA_ID}", data.VA_ID);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 尝试从JSON字符串解析虚端子数据
|
||
/// </summary>
|
||
/// <param name="json">JSON字符串</param>
|
||
/// <param name="virtualTerminalData">解析后的虚端子数据</param>
|
||
/// <returns>是否解析成功</returns>
|
||
public bool TryParseVirtualTerminalData(string json, out VirtualTerminalData virtualTerminalData)
|
||
{
|
||
virtualTerminalData = null;
|
||
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(json))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
virtualTerminalData = JsonConvert.DeserializeObject<VirtualTerminalData>(json);
|
||
return virtualTerminalData != null && virtualTerminalData.IsValid();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogDebug(ex, "解析虚端子数据失败:{Json}", json);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 推送光纤报警到3D系统
|
||
/// </summary>
|
||
/// <param name="opticalFiber">光纤配置</param>
|
||
/// <param name="virtualTerminalData">虚端子数据</param>
|
||
/// <returns></returns>
|
||
private async Task PushOpticalFiberAlarmAsync(OpticalFiberDto opticalFiber, VirtualTerminalData virtualTerminalData)
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(opticalFiber.TwinId))
|
||
{
|
||
_logger.LogWarning("光纤配置缺少TwinId: {VirtualPointCode}", opticalFiber.VirtualPointCode);
|
||
return;
|
||
}
|
||
|
||
// 解析时间戳
|
||
var timestamp = DateTime.Now;
|
||
if (virtualTerminalData.TryParseDateTime(out DateTime parsedTime))
|
||
{
|
||
timestamp = parsedTime;
|
||
}
|
||
|
||
// 构造报警数据
|
||
var alarmData = new YounuoAlert
|
||
{
|
||
TwinID = opticalFiber.TwinId,
|
||
Severity = 1, // 默认严重级别
|
||
Status = 1, // 活跃报警
|
||
SourceAlertKey = "光纤故障",
|
||
Summary = $"光纤 {opticalFiber.CiCode ?? opticalFiber.TwinId} 故障,虚点编码: {opticalFiber.VirtualPointCode}",
|
||
SourceIdentifier = $"OpticalFiber_{opticalFiber.TwinId}_{opticalFiber.VirtualPointCode}",
|
||
LastOccurrence = timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
|
||
};
|
||
|
||
_logger.LogInformation("推送光纤报警: TwinId={TwinId}, 虚点编码={VirtualPointCode}, 值={Value}",
|
||
opticalFiber.TwinId, opticalFiber.VirtualPointCode, virtualTerminalData.V);
|
||
await _webApiRequest.CallPushAlarmsApiAsync(alarmData);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "推送光纤报警时发生错误: TwinId={TwinId}, 虚点编码={VirtualPointCode}",
|
||
opticalFiber.TwinId, opticalFiber.VirtualPointCode);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除光纤报警
|
||
/// </summary>
|
||
/// <param name="opticalFiber">光纤配置</param>
|
||
/// <param name="virtualTerminalData">虚端子数据</param>
|
||
/// <returns></returns>
|
||
private async Task DeleteOpticalFiberAlarmAsync(OpticalFiberDto opticalFiber, VirtualTerminalData virtualTerminalData)
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(opticalFiber.TwinId))
|
||
{
|
||
_logger.LogWarning("光纤配置缺少TwinId: {VirtualPointCode}", opticalFiber.VirtualPointCode);
|
||
return;
|
||
}
|
||
|
||
_logger.LogInformation("删除光纤报警: TwinId={TwinId}, 虚点编码={VirtualPointCode}, 值={Value}",
|
||
opticalFiber.TwinId, opticalFiber.VirtualPointCode, virtualTerminalData.V);
|
||
|
||
// TODO: 实现实际的API调用到BeijingYounuoApiAppService.DeleteAlarmsByTwinIdAsync
|
||
// 由于架构限制,可能需要通过HTTP API或消息队列来实现
|
||
await _webApiRequest.CallDeleteAlarmsByTwinIdApiAsync(opticalFiber.TwinId);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "删除光纤报警时发生错误: TwinId={TwinId}, 虚点编码={VirtualPointCode}",
|
||
opticalFiber.TwinId, opticalFiber.VirtualPointCode);
|
||
}
|
||
}
|
||
|
||
|
||
//缓存遥信数据到内存
|
||
private void SaveDataToCache(string id, double value, DateTime timestamp)
|
||
{
|
||
try
|
||
{
|
||
string valStr = "不定";
|
||
switch((int)value)
|
||
{
|
||
case 0:
|
||
valStr = "报警";
|
||
break;
|
||
|
||
case 1:
|
||
valStr = "正常";
|
||
break;
|
||
}
|
||
|
||
_zzDataCacheContainer.Write(id, value, timestamp, "", valStr);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "VirtualTerminalHandler: SaveDataToCache 出错");
|
||
}
|
||
|
||
|
||
}
|
||
|
||
//查询数据
|
||
public async Task<List<ZzDataResultModel>> GetDataByIds(List<string> ids, int seconds, int timeWindowType, CancellationToken cancellationToken)
|
||
{
|
||
try
|
||
{
|
||
return await _zzDataCacheContainer.Read(ids, seconds, timeWindowType, cancellationToken);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
_logger.LogError(ex, "VirtualTerminalHandler: GetDataByIds 出错");
|
||
return new List<ZzDataResultModel>();
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
}
|