Compare commits
5 Commits
13d5f843ce
...
8a4fceebc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a4fceebc0 | ||
|
|
0adda965c2 | ||
|
|
c30d957a0e | ||
|
|
1fad43581a | ||
| 8864302771 |
@ -95,9 +95,9 @@ namespace YunDa.Server.ISMSTcp.Configuration
|
||||
public string SecondaryCircuitInspectionPlanUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionPlan/GetList";
|
||||
|
||||
/// <summary>
|
||||
/// 二次回路获取事件的列表
|
||||
/// 二次回路获取事件巡检的配置数据
|
||||
/// </summary>
|
||||
public string GetCircuitEventDrivenConfigUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitEventDrivenConfig/FindDatasNoPageList";
|
||||
public string GetCircuitEventDrivenConfigUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitEventDrivenConfig/GetList";
|
||||
|
||||
/// <summary>
|
||||
/// 获取孪生体与遥测数据绑定关系 URI
|
||||
@ -129,6 +129,12 @@ namespace YunDa.Server.ISMSTcp.Configuration
|
||||
/// </summary>
|
||||
public string GetInspectionResultDataUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/OriginalInspectionStoreResult/GetInspectionResultData";
|
||||
|
||||
/// 获取网关数据URI
|
||||
public string GetGatewayInfoDataUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetGatewayInfoData";
|
||||
|
||||
/// 获取虚点信息URI
|
||||
public string GetVirtualPointInfoDataUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetVirtualPointInfoData";
|
||||
|
||||
/// <summary>
|
||||
/// 查询网线配置
|
||||
/// </summary>
|
||||
@ -147,7 +153,19 @@ namespace YunDa.Server.ISMSTcp.Configuration
|
||||
/// <summary>
|
||||
/// 保存巡检计划执行结果
|
||||
/// </summary>
|
||||
public string SaveSecondaryCircuitInspectionPlanResultUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/OpticalCable/GetList";
|
||||
public string SaveSecondaryCircuitInspectionPlanResultUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionResult/Create";
|
||||
|
||||
//调用Ai
|
||||
public string GetAIAnalysisUri => "http://192.168.81.25:8002/chat/ai";
|
||||
|
||||
//巡检计划执行结果AI诊断更新
|
||||
public string UpdateReportWithAIAnalysisUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionResult/UpdateReportWithAIAnalysis";
|
||||
|
||||
//获取虚点信息
|
||||
public string GetVariantBaseinfoUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetVariantBaseinfo";
|
||||
|
||||
//获取网关信息
|
||||
public string GetGateWayBaseInfoUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetGateWayBaseInfo";
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -35,6 +35,8 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
private readonly ITcpClient _tcpClient;
|
||||
private readonly TelemeteringHandle _telemeteringHandle;
|
||||
private readonly TelesignalisationHandle _telesignalisationHandle;
|
||||
private readonly GwErrorRatioService _gwErrorRatioService;
|
||||
private readonly VirtualTerminalHandler _virtualTerminalHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
@ -54,7 +56,9 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
WebApiRequest webApiRequest,
|
||||
ITcpClient tcpClient,
|
||||
TelemeteringHandle telemeteringHandle,
|
||||
TelesignalisationHandle telesignalisationHandle)
|
||||
TelesignalisationHandle telesignalisationHandle,
|
||||
GwErrorRatioService gwErrorRatioService,
|
||||
VirtualTerminalHandler virtualTerminalHandler)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_queryService = queryService ?? throw new ArgumentNullException(nameof(queryService));
|
||||
@ -64,6 +68,8 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
_tcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient));
|
||||
_telemeteringHandle = telemeteringHandle ?? throw new ArgumentNullException(nameof(telemeteringHandle));
|
||||
_telesignalisationHandle = telesignalisationHandle ?? throw new ArgumentNullException(nameof(telesignalisationHandle));
|
||||
_gwErrorRatioService = gwErrorRatioService ?? throw new ArgumentNullException(nameof(gwErrorRatioService));
|
||||
_virtualTerminalHandler = virtualTerminalHandler ?? throw new ArgumentNullException(nameof(virtualTerminalHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -268,7 +274,7 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
/// </remarks>
|
||||
[HttpPost("CallYCByDataId")]
|
||||
public async Task<IActionResult> CallYCByDataId(
|
||||
[FromBody] CallYCByDataIdRequest request,
|
||||
[FromBody] ZzDataRequestModel request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var id = request.Id;
|
||||
@ -278,6 +284,8 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
|
||||
string cmd = string.Format("CallYCByDataID|{0}", string.Join("#", id));
|
||||
|
||||
System.Console.WriteLine($"发送遥测命令:{cmd}");
|
||||
|
||||
try
|
||||
{
|
||||
// 参数验证
|
||||
@ -297,26 +305,35 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
DateTime cmdTime = DateTime.Now;
|
||||
|
||||
int sucessCount = 0;
|
||||
for (int i = 0; i < times; i++)
|
||||
|
||||
if(request.TimeWindowType == 1 || request.TimeWindowType == 2)
|
||||
{
|
||||
var sendResult = await SendTcpMessageAsync(cmd, cancellationToken);
|
||||
if (sendResult.Success)
|
||||
for (int i = 0; i < times; i++)
|
||||
{
|
||||
sucessCount++;
|
||||
var sendResult = await SendTcpMessageAsync(cmd, cancellationToken);
|
||||
if (sendResult.Success)
|
||||
{
|
||||
sucessCount++;
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
sucessCount = 10;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (times-sucessCount<3)
|
||||
if (times - sucessCount < 3)
|
||||
{
|
||||
List<YCResultData> ycDataList = null;
|
||||
List<ZzDataResultModel>? ycDataList = null;
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
while(sw.ElapsedMilliseconds < 10*1000)
|
||||
|
||||
while (sw.ElapsedMilliseconds < 10 * 1000)
|
||||
{
|
||||
ycDataList = await _telemeteringHandle.GetYCDataByDataIds(id, cmdTime, sucessCount);
|
||||
ycDataList = await _telemeteringHandle.GetYCDataByDataIds(id, times, request.TimeWindowType, cancellationToken, cmdTime);
|
||||
|
||||
if (ycDataList.Count == id.Count)
|
||||
break;
|
||||
@ -398,7 +415,7 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
/// </remarks>
|
||||
[HttpPost("CallYXByDataId")]
|
||||
public async Task<IActionResult> CallYXByDataId(
|
||||
[FromBody] CallYCByDataIdRequest request,
|
||||
[FromBody] ZzDataRequestModel request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ids = request.Id;
|
||||
@ -419,7 +436,7 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
var dataList = await _telesignalisationHandle.GetYXDataByDataIds(ids, times, cancellationToken);
|
||||
var dataList = await _telesignalisationHandle.GetYXDataByDataIds(ids, times, request.TimeWindowType, cancellationToken);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
@ -439,10 +456,98 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据网关Id获取网关通信状态
|
||||
[HttpPost("CallGatewayByDataId")]
|
||||
public async Task<IActionResult> CallGatewayByDataId(
|
||||
[FromBody] ZzDataRequestModel request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ids = request.Id;
|
||||
var times = request.Times;
|
||||
|
||||
ids.RemoveAll(string.IsNullOrWhiteSpace);
|
||||
|
||||
try
|
||||
{
|
||||
// 参数验证
|
||||
if (ids.Count == 0)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
success = false,
|
||||
message = "未提供网关ID",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var dataList = await _gwErrorRatioService.GetDataByIds(ids, times, cancellationToken);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = "获取网关通信状态成功",
|
||||
data = dataList,
|
||||
count = dataList.Count,
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取网关通信状态失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据Id获取虚点数据
|
||||
[HttpPost("CallVAByDataId")]
|
||||
public async Task<IActionResult> CallVAByDataId(
|
||||
[FromBody] ZzDataRequestModel request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ids = request.Id;
|
||||
var times = request.Times;
|
||||
|
||||
ids.RemoveAll(string.IsNullOrWhiteSpace);
|
||||
|
||||
try
|
||||
{
|
||||
// 参数验证
|
||||
if (ids.Count == 0)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
success = false,
|
||||
message = "未提供ID",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var dataList = await _virtualTerminalHandler.GetDataByIds(ids, times, request.TimeWindowType, cancellationToken);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = "获取虚点数据成功",
|
||||
data = dataList,
|
||||
count = dataList.Count,
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取虚点数据失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -917,87 +1022,87 @@ namespace YunDa.Server.ISMSTcp.Controllers
|
||||
return WebSocketGroups.ALL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取召唤的遥测数据
|
||||
/// </summary>
|
||||
/// <param name="dataIds">数据ID字符串,格式:DataID1#DataID2#DataID3#......</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>遥测数据列表</returns>
|
||||
/// <remarks>
|
||||
/// 示例请求:GET /api/GetCollectedYCData?dataIds=001#002#003
|
||||
/// </remarks>
|
||||
[HttpGet("GetCollectedYCData")]
|
||||
public async Task<IActionResult> GetCollectedYCData(
|
||||
[FromQuery] string dataIds,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 参数验证
|
||||
if (string.IsNullOrWhiteSpace(dataIds))
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
success = false,
|
||||
message = "参数 dataIds 不能为空",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
///// <summary>
|
||||
///// 获取召唤的遥测数据
|
||||
///// </summary>
|
||||
///// <param name="dataIds">数据ID字符串,格式:DataID1#DataID2#DataID3#......</param>
|
||||
///// <param name="cancellationToken">取消令牌</param>
|
||||
///// <returns>遥测数据列表</returns>
|
||||
///// <remarks>
|
||||
///// 示例请求:GET /api/GetCollectedYCData?dataIds=001#002#003
|
||||
///// </remarks>
|
||||
//[HttpGet("GetCollectedYCData")]
|
||||
//public async Task<IActionResult> GetCollectedYCData(
|
||||
// [FromQuery] string dataIds,
|
||||
// CancellationToken cancellationToken = default)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// // 参数验证
|
||||
// if (string.IsNullOrWhiteSpace(dataIds))
|
||||
// {
|
||||
// return BadRequest(new
|
||||
// {
|
||||
// success = false,
|
||||
// message = "参数 dataIds 不能为空",
|
||||
// timestamp = DateTime.Now
|
||||
// });
|
||||
// }
|
||||
|
||||
_logger.LogInformation("收到获取遥测数据请求 - dataIds: {DataIds}", dataIds);
|
||||
// _logger.LogInformation("收到获取遥测数据请求 - dataIds: {DataIds}", dataIds);
|
||||
|
||||
// 解析DataId列表
|
||||
var dataIdList = dataIds.Split('#', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(id => id.Trim())
|
||||
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||
.ToList();
|
||||
// // 解析DataId列表
|
||||
// var dataIdList = dataIds.Split('#', StringSplitOptions.RemoveEmptyEntries)
|
||||
// .Select(id => id.Trim())
|
||||
// .Where(id => !string.IsNullOrWhiteSpace(id))
|
||||
// .ToList();
|
||||
|
||||
if (dataIdList.Count == 0)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
success = false,
|
||||
message = "未提供有效的数据ID",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
// if (dataIdList.Count == 0)
|
||||
// {
|
||||
// return BadRequest(new
|
||||
// {
|
||||
// success = false,
|
||||
// message = "未提供有效的数据ID",
|
||||
// timestamp = DateTime.Now
|
||||
// });
|
||||
// }
|
||||
|
||||
// 从TelemeteringHandle获取数据
|
||||
var ycDataList = _telemeteringHandle.GetYCDataByDataIds(dataIdList);
|
||||
// // 从TelemeteringHandle获取数据
|
||||
// var ycDataList = _telemeteringHandle.GetYCDataByDataIds(dataIdList);
|
||||
|
||||
_logger.LogInformation("获取遥测数据成功 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
|
||||
dataIdList.Count, ycDataList.Count);
|
||||
// _logger.LogInformation("获取遥测数据成功 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
|
||||
// dataIdList.Count, ycDataList.Count);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = "获取遥测数据成功",
|
||||
data = ycDataList,
|
||||
count = ycDataList.Count,
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning("获取遥测数据请求被取消 - dataIds: {DataIds}", dataIds);
|
||||
return StatusCode(504, new
|
||||
{
|
||||
success = false,
|
||||
message = "请求超时",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取遥测数据时发生异常 - dataIds: {DataIds}", dataIds);
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"服务器内部错误: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
// return Ok(new
|
||||
// {
|
||||
// success = true,
|
||||
// message = "获取遥测数据成功",
|
||||
// data = ycDataList,
|
||||
// count = ycDataList.Count,
|
||||
// timestamp = DateTime.Now
|
||||
// });
|
||||
// }
|
||||
// catch (OperationCanceledException)
|
||||
// {
|
||||
// _logger.LogWarning("获取遥测数据请求被取消 - dataIds: {DataIds}", dataIds);
|
||||
// return StatusCode(504, new
|
||||
// {
|
||||
// success = false,
|
||||
// message = "请求超时",
|
||||
// timestamp = DateTime.Now
|
||||
// });
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _logger.LogError(ex, "获取遥测数据时发生异常 - dataIds: {DataIds}", dataIds);
|
||||
// return StatusCode(500, new
|
||||
// {
|
||||
// success = false,
|
||||
// message = $"服务器内部错误: {ex.Message}",
|
||||
// timestamp = DateTime.Now
|
||||
// });
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -86,6 +86,12 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
/// </summary>
|
||||
string GetInspectionResultDataUri { get; }
|
||||
|
||||
//查询网关数据
|
||||
string GetGatewayInfoDataUri { get; }
|
||||
|
||||
//查询虚点信息
|
||||
string GetVirtualPointInfoDataUri { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询网线配置
|
||||
/// </summary>
|
||||
@ -106,6 +112,19 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
/// </summary>
|
||||
string SaveSecondaryCircuitInspectionPlanResultUri { get; }
|
||||
|
||||
|
||||
//调用Ai
|
||||
string GetAIAnalysisUri { get; }
|
||||
|
||||
//巡检计划执行结果AI诊断更新
|
||||
string UpdateReportWithAIAnalysisUri { get; }
|
||||
|
||||
//获取虚点信息
|
||||
string GetVariantBaseinfoUri { get; }
|
||||
|
||||
//获取网关信息
|
||||
string GetGateWayBaseInfoUri { get; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -207,6 +226,13 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
public string GetInspectionResultDataUri => _config.GetInspectionResultDataUri;
|
||||
|
||||
|
||||
//查询网关数据
|
||||
public string GetGatewayInfoDataUri => _config.GetGatewayInfoDataUri;
|
||||
|
||||
//查询虚点信息
|
||||
public string GetVirtualPointInfoDataUri => _config.GetVirtualPointInfoDataUri;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 查询网线配置
|
||||
/// </summary>
|
||||
@ -227,5 +253,17 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
/// </summary>
|
||||
public string SaveSecondaryCircuitInspectionPlanResultUri => _config.SaveSecondaryCircuitInspectionPlanResultUri;
|
||||
|
||||
//调用Ai
|
||||
public string GetAIAnalysisUri => _config.GetAIAnalysisUri;
|
||||
|
||||
//巡检计划执行结果AI诊断更新
|
||||
public string UpdateReportWithAIAnalysisUri => _config.UpdateReportWithAIAnalysisUri;
|
||||
|
||||
//获取虚点信息
|
||||
public string GetVariantBaseinfoUri => _config.GetVariantBaseinfoUri;
|
||||
|
||||
//获取网关信息
|
||||
public string GetGateWayBaseInfoUri => _config.GetGateWayBaseInfoUri;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,12 +648,12 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
/// 获取二次回路巡检计划
|
||||
/// </summary>
|
||||
/// <returns>二次回路巡检计划列表</returns>
|
||||
public async Task<List<SecondaryCircuitInspectionPlanOutput>> GetSecondaryCircuitInspectionPlanListAsync()
|
||||
public async Task<List<SecondaryCircuitInspectionPlanOutput>> GetSecondaryCircuitInspectionPlanListAsync(int start)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SecondaryCircuitInspectionPlanUri, new object());
|
||||
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SecondaryCircuitInspectionPlanUri, new { maxResultCount = 10, skipCount = start, includeInspectionItems = true });
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
@ -672,14 +672,14 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取事件的列表
|
||||
/// 获取时间巡检的配置数据
|
||||
/// </summary>
|
||||
public async Task<List<SecondaryCircuitEventDrivenConfigOutput>> GetCircuitEventDrivenConfigAsync()
|
||||
public async Task<List<SecondaryCircuitEventDrivenConfigOutput>> GetCircuitEventDrivenConfigAsync(int start)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetCircuitEventDrivenConfigUri, new object());
|
||||
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetCircuitEventDrivenConfigUri, new { maxResultCount = 10, skipCount = start, includeInspectionItems = true });
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
@ -751,7 +751,7 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
/// <summary>
|
||||
/// 查询遥测
|
||||
/// </summary>
|
||||
public async Task<List<YCResultData>?> GetTelemetryInfoData(string id)
|
||||
public async Task<List<ZzDataResultModel>?> GetTelemetryInfoData(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -761,7 +761,7 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
if (response != null)
|
||||
{
|
||||
|
||||
var result = ExtractDataFromAbpResponse<List<YCResultData>>(response);
|
||||
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -777,7 +777,7 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
/// <summary>
|
||||
/// 查询遥信
|
||||
/// </summary>
|
||||
public async Task<List<YXResultData>?> GetTeleSignalData(string id)
|
||||
public async Task<List<ZzDataResultModel>?> GetTeleSignalData(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -787,7 +787,7 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
if (response != null)
|
||||
{
|
||||
|
||||
var result = ExtractDataFromAbpResponse<List<YXResultData>>(response);
|
||||
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -827,7 +827,7 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询定值
|
||||
/// 查询预置位的巡检结果
|
||||
/// </summary>
|
||||
public async Task<List<InspectionResultData>?> GetInspectionResultData(string id)
|
||||
{
|
||||
@ -852,6 +852,58 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询网关数据
|
||||
/// </summary>
|
||||
public async Task<List<ZzDataResultModel>?> GetGatewayInfoData(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>($"{_apiEndpoints.GetGatewayInfoDataUri}?id={id}"));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
|
||||
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error Call GetTeleSignalData Api");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询虚点信息
|
||||
/// </summary>
|
||||
public async Task<List<ZzDataResultModel>?> GetVirtualPointInfoData(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>($"{_apiEndpoints.GetVirtualPointInfoDataUri}?id={id}"));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
|
||||
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error Call GetTeleSignalData Api");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网线配置
|
||||
/// </summary>
|
||||
@ -911,8 +963,7 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetOpticalCableConfigUri, new object()));
|
||||
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetOpticalCableConfigUri, new { includeInspectionItems = true }));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
@ -933,12 +984,53 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
/// <summary>
|
||||
/// 保存巡检计划执行结果
|
||||
/// </summary>
|
||||
public async Task<bool> SaveSecondaryCircuitInspectionPlanResultAsync()
|
||||
public async Task<string> SaveSecondaryCircuitInspectionPlanResultAsync(SecondaryCircuitInspectionResultSaveModel data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SaveSecondaryCircuitInspectionPlanResultUri, new object()));
|
||||
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SaveSecondaryCircuitInspectionPlanResultUri, data));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
var result = ExtractDataFromAbpResponse<string>(response);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
//调用Ai
|
||||
public async Task<string> GetAIAnalysis(object data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
ThingWebClientHelper httpClient = new ThingWebClientHelper();
|
||||
var result = await httpClient.PostJsonAsync(_apiEndpoints.GetAIAnalysisUri, data);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
//巡检计划执行结果AI诊断更新
|
||||
public async Task<bool> UpdateReportWithAIAnalysisAsync(SecondaryCircuitInspectionAiSaveModel data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.UpdateReportWithAIAnalysisUri, data));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
@ -952,5 +1044,53 @@ namespace YunDa.Server.ISMSTcp.Domain
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//获取虚点信息
|
||||
public async Task<List<ZzDataHistoryModel>> GetVariantBaseinfoAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>(_apiEndpoints.GetVariantBaseinfoUri));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
|
||||
var result = ExtractDataFromAbpResponse<List<ZzDataHistoryModel>>(response);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//获取网关信息
|
||||
public async Task<List<ZzDataHistoryModel>> GetGateWayBaseInfoAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>(_apiEndpoints.GetGateWayBaseInfoUri));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
|
||||
var result = ExtractDataFromAbpResponse<List<ZzDataHistoryModel>>(response);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +129,9 @@ namespace YunDa.Server.ISMSTcp.Extensions
|
||||
services.AddSingleton<SecondaryCircuitInspectionPlanService>();
|
||||
services.AddSingleton<ThingService>();
|
||||
services.AddSingleton<ThingApiService>();
|
||||
services.AddSingleton<GwErrorRatioService>();
|
||||
services.AddSingleton<ZzTcpService>();
|
||||
services.AddSingleton<ZzDataCacheContainerInit>();
|
||||
|
||||
// 日志过滤器配置
|
||||
services.Configure<YunDa.Server.ISMSTcp.Filters.Configuration.LogFilterConfiguration>(
|
||||
|
||||
@ -20,50 +20,4 @@ namespace YunDa.Server.ISMSTcp.Models
|
||||
public string T { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public class CallYCByDataIdRequest
|
||||
{
|
||||
public List<string> Id { get; set; }
|
||||
public int Times { get; set; }
|
||||
}
|
||||
|
||||
public class YCResultValue
|
||||
{
|
||||
[JsonPropertyName("Value")]
|
||||
public decimal Value { get; set; }
|
||||
|
||||
[JsonPropertyName("TimeStamp")]
|
||||
public string TimeStamp { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class YCResultData
|
||||
{
|
||||
[JsonPropertyName("Id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[JsonPropertyName("Name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("DispatcherAddress")]
|
||||
public int? DispatcherAddress { get; set; } = null;
|
||||
|
||||
|
||||
[JsonPropertyName("Data")]
|
||||
public List<YCResultValue> Data { get; set; } = new List<YCResultValue>();
|
||||
|
||||
|
||||
public void ParseValue(List<YCData> datas)
|
||||
{
|
||||
Data.Clear();
|
||||
|
||||
foreach (var data in datas)
|
||||
{
|
||||
Data.Add(new YCResultValue() { Value = data.V, TimeStamp = data.T });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,72 +20,72 @@ namespace YunDa.Server.ISMSTcp.Models
|
||||
public string T { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class YXCacheData
|
||||
{
|
||||
[JsonPropertyName("Id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
//public class YXCacheData
|
||||
//{
|
||||
// [JsonPropertyName("Id")]
|
||||
// public string Id { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("Value")]
|
||||
public int Value { get; set; }
|
||||
// [JsonPropertyName("Value")]
|
||||
// public int Value { get; set; }
|
||||
|
||||
[JsonPropertyName("ValueStr")]
|
||||
public string ValueStr { get; set; } = string.Empty;
|
||||
// [JsonPropertyName("ValueStr")]
|
||||
// public string ValueStr { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("TimeStamp")]
|
||||
public string TimeStamp { get; set; } = string.Empty;
|
||||
}
|
||||
// [JsonPropertyName("TimeStamp")]
|
||||
// public string TimeStamp { get; set; } = string.Empty;
|
||||
//}
|
||||
|
||||
public class YXResultData
|
||||
{
|
||||
[JsonPropertyName("Id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
//public class YXResultData
|
||||
//{
|
||||
// [JsonPropertyName("Id")]
|
||||
// public string Id { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[JsonPropertyName("Name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
// [JsonPropertyName("Name")]
|
||||
// public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("DispatcherAddress")]
|
||||
public int? DispatcherAddress { get; set; } = null;
|
||||
// [JsonPropertyName("DispatcherAddress")]
|
||||
// public int? DispatcherAddress { get; set; } = null;
|
||||
|
||||
|
||||
[JsonPropertyName("Data")]
|
||||
public List<YXCacheData> Data { get; set; } = new List<YXCacheData>();
|
||||
// [JsonPropertyName("Data")]
|
||||
// public List<YXCacheData> Data { get; set; } = new List<YXCacheData>();
|
||||
|
||||
[JsonPropertyName("Value")]
|
||||
public int Value { get; set; }
|
||||
// [JsonPropertyName("Value")]
|
||||
// public int Value { get; set; }
|
||||
|
||||
[JsonPropertyName("ValueStr")]
|
||||
public string ValueStr { get; set; } = string.Empty;
|
||||
// [JsonPropertyName("ValueStr")]
|
||||
// public string ValueStr { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("TimeStamp")]
|
||||
public string TimeStamp { get; set; } = string.Empty;
|
||||
// [JsonPropertyName("TimeStamp")]
|
||||
// public string TimeStamp { get; set; } = string.Empty;
|
||||
|
||||
public void ParseValue(List<YXCacheData> datas)
|
||||
{
|
||||
Data.Clear();
|
||||
// public void ParseValue(List<YXCacheData> datas)
|
||||
// {
|
||||
// Data.Clear();
|
||||
|
||||
foreach (var data in datas)
|
||||
{
|
||||
Data.Add(data);
|
||||
}
|
||||
// foreach (var data in datas)
|
||||
// {
|
||||
// Data.Add(data);
|
||||
// }
|
||||
|
||||
if (Data.Count > 0)
|
||||
{
|
||||
Value = Data.Last().Value;
|
||||
ValueStr = Data.Last().ValueStr;
|
||||
TimeStamp = Data.Last().TimeStamp;
|
||||
}
|
||||
}
|
||||
// if (Data.Count > 0)
|
||||
// {
|
||||
// Value = Data.Last().Value;
|
||||
// ValueStr = Data.Last().ValueStr;
|
||||
// TimeStamp = Data.Last().TimeStamp;
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
//}
|
||||
|
||||
|
||||
public class YXResultValue
|
||||
{
|
||||
[JsonPropertyName("Value")]
|
||||
public decimal Value { get; set; }
|
||||
//public class YXResultValue
|
||||
//{
|
||||
// [JsonPropertyName("Value")]
|
||||
// public decimal Value { get; set; }
|
||||
|
||||
[JsonPropertyName("TimeStamp")]
|
||||
public string TimeStamp { get; set; } = string.Empty;
|
||||
}
|
||||
// [JsonPropertyName("TimeStamp")]
|
||||
// public string TimeStamp { get; set; } = string.Empty;
|
||||
//}
|
||||
}
|
||||
|
||||
@ -18,15 +18,22 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
private readonly ITcpClient _tcpClient;
|
||||
private readonly ISMSServerConfiguration _config;
|
||||
private Timer? _connectionCheckTimer;
|
||||
private readonly ZzDataCacheContainerInit _zzDataCacheContainerInit;
|
||||
|
||||
public AutoConnectionService(
|
||||
ILogger<AutoConnectionService> logger,
|
||||
ITcpClient tcpClient,
|
||||
IOptions<ISMSServerConfiguration> config)
|
||||
IOptions<ISMSServerConfiguration> config,
|
||||
ZzDataCacheContainerInit zzDataCacheContainerInit)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_tcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient));
|
||||
_config = config?.Value ?? throw new ArgumentNullException(nameof(config));
|
||||
|
||||
|
||||
_zzDataCacheContainerInit = zzDataCacheContainerInit ?? throw new ArgumentNullException(nameof(zzDataCacheContainerInit));
|
||||
|
||||
zzDataCacheContainerInit.InitTcpService(_tcpClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -57,6 +57,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
private readonly IRedisRepository<ProtectionDeviceCommInfoOutput, string> _protectionDeviceCommInfoRedis;
|
||||
private readonly IApiEndpoints _apiEndpoints;
|
||||
private readonly WebApiRequest _webApiRequest;
|
||||
private readonly GwErrorRatioService _gwErrorRatioService;
|
||||
|
||||
//孪生体服务
|
||||
private readonly ThingService _thingService;
|
||||
@ -92,7 +93,8 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
IApiEndpoints apiEndpoints,
|
||||
WebApiRequest webApiRequest,
|
||||
SecondaryCircuitInspectionPlanService secondaryCircuitInspectionPlanService,
|
||||
ThingService thingService)
|
||||
ThingService thingService,
|
||||
GwErrorRatioService gwErrorRatioService)
|
||||
{
|
||||
_logger = logger;
|
||||
_messageParser = messageParser;
|
||||
@ -111,6 +113,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
_webApiRequest = webApiRequest ?? throw new ArgumentNullException(nameof(webApiRequest));
|
||||
_secondaryCircuitInspectionPlanService = secondaryCircuitInspectionPlanService ?? throw new ArgumentNullException(nameof(secondaryCircuitInspectionPlanService));
|
||||
_thingService = thingService ?? throw new ArgumentNullException(nameof(thingService));
|
||||
_gwErrorRatioService = gwErrorRatioService ?? throw new ArgumentNullException(nameof(gwErrorRatioService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -158,6 +161,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
"WAVEDATA" => await ProcessWaveDataMessageAsync(contentToken),
|
||||
"故障报告" => await ProcessFaultRptMessageAsync(contentToken),
|
||||
"ALLDATA" => await ProcessAllDataMessageAsync(contentToken),
|
||||
"GWERRORRATIO" => await ProcessGwErrorRatioMessageAsync(contentToken),
|
||||
|
||||
_ => ProcessUnknownMessageType(messageType, originalJson)
|
||||
};
|
||||
@ -254,19 +258,23 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
if (contentToken is JArray contentArray && contentArray.Count > 0)
|
||||
{
|
||||
|
||||
//2025-10-25 所有值加1
|
||||
foreach (var item in contentArray)
|
||||
{
|
||||
if (item["V"] != null && item["V"].Type == JTokenType.String)
|
||||
{
|
||||
string val = item["V"].Value<string>();
|
||||
if(int.TryParse(val, out int value))
|
||||
{
|
||||
item["V"] = (value + 1).ToString();
|
||||
}
|
||||
//2025-10-25 所有值加1 send CallYXByDevice|B001208
|
||||
//foreach (var item in contentArray)
|
||||
//{
|
||||
// if (item["YX_ID"].Value<string>() == "YXB001103058")
|
||||
// {
|
||||
// int kk = 0;
|
||||
// }
|
||||
// if (item["V"] != null && item["V"].Type == JTokenType.String)
|
||||
// {
|
||||
// string val = item["V"].Value<string>();
|
||||
// if(int.TryParse(val, out int value))
|
||||
// {
|
||||
// //item["V"] = (value + 1).ToString();
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
// }
|
||||
//}
|
||||
|
||||
List<TelesignalisationModel> telesignalisationModels = new List<TelesignalisationModel>();
|
||||
|
||||
@ -299,7 +307,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
//将状态推送到孪生体
|
||||
_thingService.UpdateThingYXStatus(contentArray.ToObject<List<YXData>>(), telesignalisationModels);
|
||||
_thingService.UpdateThingYXStatus(telesignalisationModels);
|
||||
}
|
||||
|
||||
else
|
||||
@ -992,6 +1000,16 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理网关通信数据消息,数据每小时发一次
|
||||
/// </summary>
|
||||
/// <param name="contentToken">消息内容</param>
|
||||
/// <returns>处理结果</returns>
|
||||
private async Task<Models.ProcessResult> ProcessGwErrorRatioMessageAsync(JToken contentToken)
|
||||
{
|
||||
return await _gwErrorRatioService.TranselateData(contentToken);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
|
||||
@ -0,0 +1,134 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using Quartz;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using YunDa.Server.ISMSTcp.Models;
|
||||
using YunDa.SOMS.DataTransferObject.MaintenanceAndOperations.SecondaryEquipment;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
public class GwErrorRatioService
|
||||
{
|
||||
private readonly ILogger<AlarmService> _logger;
|
||||
private readonly ZzDataCacheContainerInit _zzDataCacheContainerInit;
|
||||
|
||||
//数据缓冲队列
|
||||
|
||||
private ZzDataCacheContainer _cacheContainer = new ZzDataCacheContainer(2 * 60 + 1);//保留2小时的数据,多1分钟,兼容发送送数据发送延迟情况(每1小时发送一次)
|
||||
|
||||
public GwErrorRatioService(ILogger<AlarmService> logger, ZzDataCacheContainerInit zzDataCacheContainerInit)
|
||||
{
|
||||
_logger = logger;
|
||||
_zzDataCacheContainerInit = zzDataCacheContainerInit;
|
||||
_zzDataCacheContainerInit.InitGateWayBaseInfo(_cacheContainer);
|
||||
}
|
||||
|
||||
//解析数据
|
||||
public async Task<Models.ProcessResult> TranselateData(JToken contentToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 输入验证
|
||||
if (contentToken == null)
|
||||
{
|
||||
_logger.LogWarning("GwErrorRatio message content is null");
|
||||
return Models.ProcessResult.Error("GwErrorRatio message content is null");
|
||||
}
|
||||
|
||||
// 批量处理网关通信数据
|
||||
List<GwErrorRatioDataModel> gatewayDatas;
|
||||
|
||||
gatewayDatas = contentToken.ToObject<List<GwErrorRatioDataModel>>();
|
||||
|
||||
if (gatewayDatas == null || gatewayDatas.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("GwErrorRatio message contains no valid alert data");
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessData(gatewayDatas);
|
||||
}
|
||||
|
||||
|
||||
return Models.ProcessResult.Success(contentToken, "GWERRORRATIO");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing GwErrorRatio message");
|
||||
return Models.ProcessResult.Error($"GwErrorRatio processing error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
//处理数据
|
||||
private void ProcessData(List<GwErrorRatioDataModel> gatewayDatas)
|
||||
{
|
||||
foreach (var data in gatewayDatas)
|
||||
{
|
||||
data.DataTimeStamp = DateTime.Now;
|
||||
SaveDataToCache(data);
|
||||
}
|
||||
}
|
||||
|
||||
//缓存遥信数据到内存
|
||||
private void SaveDataToCache(GwErrorRatioDataModel data)
|
||||
{
|
||||
_cacheContainer.Write(data.GatewayID, data.ErrorRatio, data.DataTimeStamp, "", data.ErrorRatio.ToString("P2"));
|
||||
}
|
||||
|
||||
//查询数据
|
||||
public async Task<List<ZzDataResultModel>> GetDataByIds(List<string> ids, int seconds, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (seconds < 60 * 60)
|
||||
{
|
||||
seconds = 60 * 60 + 60; //多60秒,兼容发送送数据发送延迟情况(每1小时发送一次)
|
||||
}
|
||||
else if (seconds < 2 * 60 * 60)
|
||||
{
|
||||
seconds = 2 * 60 * 60 + 60;
|
||||
}
|
||||
|
||||
return await _cacheContainer.Read(ids, seconds, 0, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
_logger.LogError(ex, "GwErrorRatioService: GetDataByIds 出错");
|
||||
return new List<ZzDataResultModel>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class GwErrorRatioDataModel
|
||||
{
|
||||
public string GatewayID { get; set; } = string.Empty; //网关Id
|
||||
|
||||
public double ErrorRatio { get; set; } //每小时的错误率
|
||||
|
||||
public DateTime DataTimeStamp { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class ErrorRatioDataByIdRequestModel
|
||||
{
|
||||
public List<string> Id { get; set; }
|
||||
public int Times { get; set; }
|
||||
public int TimeWindowType { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -1,18 +1,26 @@
|
||||
using Jint;
|
||||
using DotNetty.Common.Utilities;
|
||||
using Jint;
|
||||
using Jint.Native.Object;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Nito.AsyncEx;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
@ -22,6 +30,7 @@ using YunDa.Server.ISMSTcp.Models;
|
||||
using YunDa.SOMS.DataTransferObject.DataMonitoring.SecondaryCircuitInspection;
|
||||
using YunDa.SOMS.DataTransferObject.DataMonitoring.SecondaryCircuitInspection.Configurations;
|
||||
using YunDa.SOMS.DataTransferObject.MaintenanceAndOperations.SecondaryEquipment;
|
||||
using YunDa.SOMS.Entities.DataMonitoring.SecondaryCircuitInspection;
|
||||
|
||||
namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
@ -46,6 +55,19 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
|
||||
public class SecondaryCircuitInspectionItemOutputEx
|
||||
{
|
||||
public int TriggerType { get; set; }
|
||||
|
||||
public SecondaryCircuitInspectionItemOutput Item { get; set; }
|
||||
|
||||
public SecondaryCircuitInspectionItemOutputEx(int triggerType, SecondaryCircuitInspectionItemOutput item)
|
||||
{
|
||||
TriggerType = triggerType;
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceDzData
|
||||
{
|
||||
public List<DzInfo> UserDz { get; set; }
|
||||
@ -66,26 +88,89 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
//巡检结果
|
||||
public class SecondaryCircuitInspectionResultModel
|
||||
|
||||
|
||||
public class SecondaryCircuitInspectionStoreDataModel
|
||||
{
|
||||
//遥测数据
|
||||
public List<YCResultData> TelemetryInfoData { get; set; }
|
||||
public List<ZzDataResultModel> TelemetryData { get; set; }
|
||||
|
||||
//遥信数据
|
||||
public List<YXResultData> TeleSignalData { get; set; }
|
||||
public List<ZzDataResultModel> TeleSignalData { get; set; }
|
||||
|
||||
//定值
|
||||
public List<DeviceDzData> DeviceDzData { get; set; }
|
||||
public List<DeviceDzData> SettingData { get; set; }
|
||||
|
||||
//预置位的巡检结果
|
||||
public List<InspectionResultData> InspectionResultData { get; set; }
|
||||
//预置位
|
||||
public List<InspectionResultData> PresetData { get; set; }
|
||||
|
||||
//虚点数据
|
||||
public List<ZzDataResultModel> VariantData { get; set; }
|
||||
|
||||
//网关数据
|
||||
public List<ZzDataResultModel> GatewayData { get; set; }
|
||||
}
|
||||
//巡检结果
|
||||
public class SecondaryCircuitInspectionJsDataModel
|
||||
{
|
||||
public int TimeWindowSeconds { get; set; }
|
||||
public int TimeWindowCount { get; set; }
|
||||
public TimeWindowTypeEnum TimeWindowType { get; set; }
|
||||
|
||||
public SecondaryCircuitInspectionStoreDataModel StoreData { get; set; } = new SecondaryCircuitInspectionStoreDataModel();
|
||||
public List<SecondaryCircuitInspectionTelemetryConfigOutput> TelemetryConfigs { get; set; }
|
||||
public List<SecondaryCircuitInspectionTelesignalConfigOutput> TelesignalConfigs { get; set; }
|
||||
public List<SecondaryCircuitInspectionCameraPresetOutput> CameraPresets { get; set; }
|
||||
public List<SecondaryCircuitInspectionDeviceConfigOutput> DeviceConfigs { get; set; }
|
||||
public List<SecondaryCircuitInspectionVariantOutput> VariantConfigs { get; set; }
|
||||
public List<SecondaryCircuitInspectionGatewayOutput> GatewayConfigs { get; set; }
|
||||
}
|
||||
|
||||
|
||||
//巡检结果上传Model
|
||||
public class SecondaryCircuitInspectionResultSaveModel
|
||||
{
|
||||
public string SecondaryCircuitInspectionItemId { get; set; } = string.Empty;
|
||||
public string InspectionPlanId { get; set; } = string.Empty;
|
||||
|
||||
public DateTime ExecutionTime { get; set; } = DateTime.Now;
|
||||
|
||||
public long ExecutionDurationMs { get; set; }
|
||||
|
||||
public int TriggerType { get; set; } //1计划,2事件
|
||||
|
||||
public string TriggerEventId { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty; //"异常|正常|故障",
|
||||
public string InspectionResult { get; set; } = string.Empty; //巡检结果 inspectionResult不能超过15个字符
|
||||
public string CalculationProcess { get; set; } = string.Empty; //计算过程
|
||||
public string VerificationResult { get; set; } = string.Empty; //校验结果
|
||||
public SecondaryCircuitInspectionJsDataModel Data { get; set; }
|
||||
public string Remark { get; set; } = string.Empty; //无用
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
public class SecondaryCircuitInspectionAiParamModel
|
||||
{
|
||||
public string InspectionResultId { get; set; } = string.Empty;
|
||||
public string PlanName { get; set; } = string.Empty;
|
||||
public SecondaryCircuitInspectionResultSaveModel SaveModel { get; set; }
|
||||
}
|
||||
|
||||
public class SecondaryCircuitInspectionAiSaveModel
|
||||
{
|
||||
public string InspectionResultId { get; set; } = string.Empty;
|
||||
public string AiAnalysisResult { get; set; } = string.Empty; // "AI结果状态:失败|成功",
|
||||
public string ResultHandleDescription { get; set; } = string.Empty; //"AI返回的处理结果"
|
||||
}
|
||||
|
||||
//二次回路巡检计划
|
||||
public class SecondaryCircuitInspectionPlanService
|
||||
{
|
||||
private readonly bool _isDebug = false;
|
||||
|
||||
private readonly ILogger<SecondaryCircuitInspectionPlanService> _logger;
|
||||
private readonly IApiEndpoints _apiEndpoints;
|
||||
private readonly WebApiRequest _webApiRequest;
|
||||
@ -95,13 +180,15 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
|
||||
private Queue<SecondaryCircuitInspectionPlanStateModel> _planList;
|
||||
private readonly object _planLock = new object();
|
||||
private readonly AsyncLock _planLock = new AsyncLock();
|
||||
private int _planCheckDay = 0;
|
||||
private readonly Channel<SecondaryCircuitInspectionItemOutput> _singlePlanChannel;
|
||||
|
||||
private readonly Channel<SecondaryCircuitInspectionItemOutputEx> _singlePlanChannel; //巡检计划
|
||||
private readonly Channel<SecondaryCircuitInspectionAiParamModel> _aiChannel; //Ai调用
|
||||
|
||||
//巡检事件
|
||||
private Queue<SecondaryCircuitEventDrivenConfigOutput> _eventPlanList;
|
||||
private readonly object _eventPlanLock = new object();
|
||||
private readonly AsyncLock _eventPlanLock = new AsyncLock();
|
||||
private bool _getEventPlanListOk = false;
|
||||
|
||||
public SecondaryCircuitInspectionPlanService(
|
||||
@ -119,17 +206,21 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
_planList = new Queue<SecondaryCircuitInspectionPlanStateModel>();
|
||||
|
||||
_singlePlanChannel = Channel.CreateUnbounded<SecondaryCircuitInspectionItemOutput>();
|
||||
_singlePlanChannel = Channel.CreateUnbounded<SecondaryCircuitInspectionItemOutputEx>();
|
||||
|
||||
_aiChannel = Channel.CreateUnbounded<SecondaryCircuitInspectionAiParamModel>();
|
||||
|
||||
_eventPlanList = new Queue<SecondaryCircuitEventDrivenConfigOutput>();
|
||||
|
||||
//var kk = webApiRequest.GetTeleSignalData("08de219e-3524-41db-8d7d-86b547bac16a");
|
||||
|
||||
|
||||
StartAsync();
|
||||
|
||||
}
|
||||
|
||||
private async Task StartAsync()
|
||||
{
|
||||
|
||||
//更新计划
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
@ -184,7 +275,8 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
});
|
||||
|
||||
//执行计划(两个线程同时执行)
|
||||
int threadNumber = 3;
|
||||
int threadNumber = _isDebug ? 1 : 3;
|
||||
|
||||
for (int i = 0; i < threadNumber; ++i)
|
||||
{
|
||||
_ = Task.Run(async () => {
|
||||
@ -203,6 +295,25 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
});
|
||||
}
|
||||
|
||||
//调用Ai获取诊断结果
|
||||
for (int i = 0; i < threadNumber; ++i)
|
||||
{
|
||||
_ = Task.Run(async () => {
|
||||
|
||||
var rand = new Random(Guid.NewGuid().GetHashCode());
|
||||
|
||||
await foreach (var item in _aiChannel.Reader.ReadAllAsync())
|
||||
{
|
||||
// 让每个线程在执行之间有不同的节奏
|
||||
await Task.Delay(rand.Next(0, 300));
|
||||
|
||||
await CallAiAndSave(item);
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -211,40 +322,60 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await _webApiRequest.GetSecondaryCircuitInspectionPlanListAsync();
|
||||
if (list != null)
|
||||
using (await _planLock.LockAsync())
|
||||
{
|
||||
lock (_planLock)
|
||||
var oldPlan = _planList.ToArray();
|
||||
|
||||
_planList.Clear();
|
||||
|
||||
int start = 0;
|
||||
while (start < 1000)
|
||||
{
|
||||
var oldPlan = _planList.ToArray();
|
||||
|
||||
_planList.Clear();
|
||||
|
||||
List<SecondaryCircuitInspectionPlanStateModel> planlist = new List<SecondaryCircuitInspectionPlanStateModel>();
|
||||
|
||||
foreach (var item in list)
|
||||
var list = await _webApiRequest.GetSecondaryCircuitInspectionPlanListAsync(start);
|
||||
if (list != null)
|
||||
{
|
||||
planlist.Add(new SecondaryCircuitInspectionPlanStateModel() { Plan = item });
|
||||
}
|
||||
|
||||
foreach (var item in planlist)
|
||||
{
|
||||
var plan = oldPlan.FirstOrDefault(x => x.Plan.Id == item.Plan.Id && x.Plan.ScheduledTimeString == item.Plan.ScheduledTimeString);
|
||||
if (plan != null)
|
||||
List<SecondaryCircuitInspectionPlanStateModel> planlist = new List<SecondaryCircuitInspectionPlanStateModel>();
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
item.ExecuteTime = plan.ExecuteTime;
|
||||
planlist.Add(new SecondaryCircuitInspectionPlanStateModel() { Plan = item });
|
||||
}
|
||||
|
||||
_planList.Enqueue(item);
|
||||
}
|
||||
foreach (var item in planlist)
|
||||
{
|
||||
var plan = oldPlan.FirstOrDefault(x => x.Plan.Id == item.Plan.Id && x.Plan.ScheduledTimeString == item.Plan.ScheduledTimeString);
|
||||
if (plan != null)
|
||||
{
|
||||
item.ExecuteTime = plan.ExecuteTime;
|
||||
}
|
||||
|
||||
//获取到绑定信息,可以更新全部状态了
|
||||
if (planlist.Count > 0)
|
||||
_updatedPlanOk = true;
|
||||
_planList.Enqueue(item);
|
||||
}
|
||||
|
||||
if (list.Count < 10)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
start += 10;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//获取到绑定信息,可以更新全部状态了
|
||||
if (_planList.Count > 0)
|
||||
_updatedPlanOk = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -259,21 +390,38 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await _webApiRequest.GetCircuitEventDrivenConfigAsync();
|
||||
if (list != null)
|
||||
|
||||
using (await _eventPlanLock.LockAsync())
|
||||
{
|
||||
lock (_eventPlanLock)
|
||||
_eventPlanList.Clear();
|
||||
|
||||
int start = 0;
|
||||
while (start < 1000)
|
||||
{
|
||||
_eventPlanList.Clear();
|
||||
var list = await _webApiRequest.GetCircuitEventDrivenConfigAsync(start);
|
||||
if (list != null)
|
||||
{
|
||||
foreach (var item in list)
|
||||
_eventPlanList.Enqueue(item);
|
||||
|
||||
foreach (var item in list)
|
||||
_eventPlanList.Enqueue(item);
|
||||
|
||||
|
||||
//获取到绑定信息,可以更新全部状态了
|
||||
if (_eventPlanList.Count > 0)
|
||||
_getEventPlanListOk = true;
|
||||
if (list.Count < 10)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
start += 10;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//获取到绑定信息,可以更新全部状态了
|
||||
if (_eventPlanList.Count > 0)
|
||||
_getEventPlanListOk = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -295,7 +443,8 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
try
|
||||
{
|
||||
SecondaryCircuitInspectionPlanStateModel[] planList;
|
||||
lock (_planLock)
|
||||
|
||||
using (await _planLock.LockAsync())
|
||||
{
|
||||
planList = _planList.ToArray();
|
||||
}
|
||||
@ -305,7 +454,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
if (now.Day != _planCheckDay)
|
||||
{//翻天,清空已执行标记
|
||||
lock (_planLock)
|
||||
using (await _planLock.LockAsync())
|
||||
{
|
||||
foreach (var item in _planList)
|
||||
item.ExecuteTime = DateTime.MinValue;
|
||||
@ -322,9 +471,9 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
if (item.Plan.ScheduledWeekDaysList.IndexOf(week) != -1)
|
||||
{
|
||||
await PushPlanToChannel(item.Plan);
|
||||
await PushPlanToChannel(1, item.Plan);
|
||||
|
||||
lock (_planLock)
|
||||
using (await _planLock.LockAsync())
|
||||
{
|
||||
item.ExecuteTime = DateTime.Now;
|
||||
}
|
||||
@ -345,7 +494,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
//将需要执行的计划写到队列里
|
||||
private async Task<bool> PushPlanToChannel(SecondaryCircuitInspectionPlanOutput plan)
|
||||
private async Task<bool> PushPlanToChannel(int triggerType, SecondaryCircuitInspectionPlanOutput plan)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -358,7 +507,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
if(item.IsActive && !string.IsNullOrWhiteSpace(item.CalculationExpression) && !string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
//写到队列里,由队列控制执行频率
|
||||
await _singlePlanChannel.Writer.WriteAsync(item);
|
||||
await _singlePlanChannel.Writer.WriteAsync(new SecondaryCircuitInspectionItemOutputEx(triggerType, item));
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,43 +522,345 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
//执行计划
|
||||
private async Task ExecutePlan(SecondaryCircuitInspectionItemOutput item)
|
||||
private async Task ExecutePlan(SecondaryCircuitInspectionItemOutputEx itemEx)
|
||||
{
|
||||
|
||||
string func2 = "";
|
||||
string data2 = "";
|
||||
try
|
||||
{
|
||||
var item = itemEx.Item;
|
||||
|
||||
string id = item.Id.ToString();
|
||||
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
|
||||
SecondaryCircuitInspectionJsDataModel jsData = new SecondaryCircuitInspectionJsDataModel();
|
||||
|
||||
jsData.TimeWindowSeconds = item.TimeWindowSeconds;
|
||||
jsData.TimeWindowCount = item.TimeWindowCount;
|
||||
jsData.TimeWindowType = item.TimeWindowType;
|
||||
|
||||
jsData.TelemetryConfigs = item.TelemetryConfigs;
|
||||
jsData.TelesignalConfigs = item.TelesignalConfigs;
|
||||
jsData.CameraPresets = item.CameraPresets;
|
||||
jsData.DeviceConfigs = item.DeviceConfigs;
|
||||
jsData.VariantConfigs = item.VariantConfigs;
|
||||
jsData.GatewayConfigs = item.GatewayConfigs;
|
||||
|
||||
|
||||
var engine = new Jint.Engine();
|
||||
|
||||
SecondaryCircuitInspectionResultModel resultModel = new SecondaryCircuitInspectionResultModel();
|
||||
|
||||
var task1 = GetSourceDataAsync<List<YCResultData>>(id, 1);
|
||||
var task2 = GetSourceDataAsync<List<YXResultData>>(id, 2);
|
||||
var task3 = GetSourceDataAsync<List<DeviceDzData>>(id, 3);
|
||||
var task4 = GetSourceDataAsync<List<InspectionResultData>>(id, 4);
|
||||
|
||||
// 等待全部完成
|
||||
await Task.WhenAll(task1, task2, task3, task4);
|
||||
|
||||
resultModel.TelemetryInfoData = await task1;
|
||||
resultModel.TeleSignalData = await task2;
|
||||
resultModel.DeviceDzData = await task3;
|
||||
resultModel.InspectionResultData = await task4;
|
||||
|
||||
engine.Execute(item.CalculationExpression);
|
||||
|
||||
var result = engine.Invoke("calculate", resultModel).AsObject();
|
||||
func2 = item.CalculationExpression;
|
||||
|
||||
ObjectInstance? saveObj = null;
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
for (int i = 0; i < item.TimeWindowCount; i++)
|
||||
{
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
Task<List<ZzDataResultModel>>? t1 = null;
|
||||
Task<List<ZzDataResultModel>>? t2 = null;
|
||||
Task<List<DeviceDzData>>? t3 = null;
|
||||
Task<List<InspectionResultData>>? t4 = null;
|
||||
Task<List<ZzDataResultModel>>? t5 = null;
|
||||
Task<List<ZzDataResultModel>>? t6 = null;
|
||||
|
||||
if (item.TelemetryConfigCount > 0)
|
||||
{
|
||||
t1 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 1);
|
||||
tasks.Add(t1);
|
||||
}
|
||||
|
||||
if (item.TelesignalConfigCount > 0)
|
||||
{
|
||||
t2 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 2);
|
||||
tasks.Add(t2);
|
||||
}
|
||||
|
||||
if (item.DeviceConfigCount > 0)
|
||||
{
|
||||
t3 = GetSourceDataAsync<List<DeviceDzData>>(id, 3);
|
||||
tasks.Add(t3);
|
||||
}
|
||||
|
||||
if (item.CameraPresetCount > 0)
|
||||
{
|
||||
t4 = GetSourceDataAsync<List<InspectionResultData>>(id, 4);
|
||||
tasks.Add(t4);
|
||||
}
|
||||
|
||||
if (item.GatewayConfigCount > 0)
|
||||
{
|
||||
t5 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 5);
|
||||
tasks.Add(t5);
|
||||
}
|
||||
|
||||
if (item.VariantConfigCount > 0)
|
||||
{
|
||||
t6 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 6);
|
||||
tasks.Add(t6);
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (t1 != null) jsData.StoreData.TelemetryData = await t1;
|
||||
if (t2 != null) jsData.StoreData.TeleSignalData = await t2;
|
||||
if (t3 != null) jsData.StoreData.SettingData = await t3;
|
||||
if (t4 != null) jsData.StoreData.PresetData = await t4;
|
||||
if (t5 != null) jsData.StoreData.GatewayData = await t5;
|
||||
if (t6 != null) jsData.StoreData.VariantData = await t6;
|
||||
|
||||
if(jsData.StoreData.TelemetryData == null && jsData.StoreData.TeleSignalData == null && jsData.StoreData.SettingData == null &&
|
||||
jsData.StoreData.PresetData == null && jsData.StoreData.GatewayData == null && jsData.StoreData.VariantData == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var json = JsonConvert.SerializeObject(jsData,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
|
||||
var js = $"JSON.parse('{json.Replace("'", "\\'")}')";
|
||||
|
||||
data2 = json;
|
||||
|
||||
var jsObj = engine.Evaluate(js).ToObject();
|
||||
|
||||
var jsResult = engine.Invoke("calculate", jsObj).AsObject();
|
||||
|
||||
if (jsResult != null && jsResult.HasProperty("status"))
|
||||
{
|
||||
saveObj = jsResult;
|
||||
|
||||
var status = jsResult.Get("status").ToString();
|
||||
if (status == "正常" || string.IsNullOrWhiteSpace(status))
|
||||
{//只要正常,就不用再检测了
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
saveObj = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
|
||||
//保存结果
|
||||
if (saveObj != null)
|
||||
{
|
||||
SecondaryCircuitInspectionResultSaveModel saveData = new SecondaryCircuitInspectionResultSaveModel();
|
||||
|
||||
if(saveObj.HasProperty("status") && saveObj.HasProperty("inspectionResult") && saveObj.HasProperty("calculationProcess") && saveObj.HasProperty("verificationResult"))
|
||||
{
|
||||
saveData.SecondaryCircuitInspectionItemId = item.Id.ToString();
|
||||
saveData.ExecutionDurationMs = sw.ElapsedMilliseconds;
|
||||
saveData.TriggerType = itemEx.TriggerType;
|
||||
if (saveData.TriggerType == 1)
|
||||
saveData.InspectionPlanId = id;
|
||||
else
|
||||
saveData.TriggerEventId = id;
|
||||
|
||||
saveData.Status = saveObj.Get("status").ToString();
|
||||
saveData.InspectionResult = saveObj.Get("inspectionResult").ToString();
|
||||
saveData.CalculationProcess = saveObj.Get("calculationProcess").ToString();
|
||||
saveData.VerificationResult = saveObj.Get("verificationResult").ToString();
|
||||
|
||||
saveData.Data = jsData;
|
||||
|
||||
var inspectionResultId = await _webApiRequest.SaveSecondaryCircuitInspectionPlanResultAsync(saveData);
|
||||
|
||||
//获取Ai诊断结果并保存,不正常时才诊断
|
||||
if (!string.IsNullOrWhiteSpace(inspectionResultId) && saveData.Status != "正常")
|
||||
{
|
||||
SecondaryCircuitInspectionAiParamModel aiParamModel = new SecondaryCircuitInspectionAiParamModel();
|
||||
aiParamModel.InspectionResultId = inspectionResultId;
|
||||
aiParamModel.PlanName = $"{itemEx.Item.Name} {itemEx.Item.Description}";
|
||||
aiParamModel.SaveModel = saveData;
|
||||
|
||||
_aiChannel.Writer.WriteAsync(aiParamModel);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"SecondaryCircuitInspectionPlanService 执行巡检项失败:{item.Id.ToString()}");
|
||||
_logger.LogError(ex, $"SecondaryCircuitInspectionPlanService 执行巡检项失败:{itemEx.Item.Id.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CallAiAndSave(SecondaryCircuitInspectionAiParamModel aiParamModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var aiParam = new
|
||||
{
|
||||
user_message = GetAiParamString(aiParamModel),
|
||||
history = new List<string[]>()
|
||||
};
|
||||
|
||||
var aiResultRaw = await _webApiRequest.GetAIAnalysis(aiParam);
|
||||
|
||||
bool isSuccess = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(aiResultRaw))
|
||||
{
|
||||
string aiResult = ParseAiResult(aiResultRaw);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(aiResult))
|
||||
{
|
||||
SecondaryCircuitInspectionAiSaveModel data = new SecondaryCircuitInspectionAiSaveModel();
|
||||
data.InspectionResultId = aiParamModel.InspectionResultId;
|
||||
data.AiAnalysisResult = "成功";
|
||||
data.ResultHandleDescription = aiResult;
|
||||
|
||||
await _webApiRequest.UpdateReportWithAIAnalysisAsync(data);
|
||||
|
||||
isSuccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isSuccess)
|
||||
{
|
||||
SecondaryCircuitInspectionAiSaveModel data = new SecondaryCircuitInspectionAiSaveModel();
|
||||
data.InspectionResultId = aiParamModel.InspectionResultId;
|
||||
data.AiAnalysisResult = "失败";
|
||||
data.ResultHandleDescription = "";
|
||||
|
||||
await _webApiRequest.UpdateReportWithAIAnalysisAsync(data);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"SecondaryCircuitInspectionPlanService CallAiAndSave出错");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetAiParamString(SecondaryCircuitInspectionAiParamModel aiParamModel)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("你是一个电力系统专家,请根据以下变电站二次巡检结果数据生成故障排除建议:");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("检修的建议要避免停止变电所的正常运行,要以宽松的方式,检修工作必须遵循“安全第一、预防为主”的核心原则,实施全过程精益化管理");
|
||||
sb.AppendLine("检修前,需制定详尽的计划与方案,确保人员资质合格、物资工具齐备,并严格执行工作票制度,实现安全隔离。检修中,必须强化现场安全管控,设专人监护,并严格按标准工艺施工,做好关键数据记录与过程质检。检修后,要严格执行试验验收程序,确保设备性能达标,彻底清理现场,并完善检修报告与设备台账,形成闭环管理");
|
||||
sb.AppendLine("整个流程旨在通过规范化、标准化的作业,确保人身、设备与电网安全,提升设备可靠性,保障电力稳定供应");
|
||||
sb.AppendLine(",减少思考过程");
|
||||
sb.AppendLine();
|
||||
|
||||
// 巡检结果
|
||||
sb.AppendLine($"巡检内容为:");
|
||||
sb.AppendLine(aiParamModel.PlanName);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("巡检结果为:");
|
||||
sb.AppendLine($"巡检状态: {aiParamModel.SaveModel.Status}");
|
||||
sb.AppendLine($"计算过程: {aiParamModel.SaveModel.CalculationProcess}");
|
||||
sb.AppendLine($"巡检结果: {aiParamModel.SaveModel.InspectionResult}");
|
||||
sb.AppendLine($"校验结果: {aiParamModel.SaveModel.VerificationResult}");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("请按以下格式生成排除建议,直接建议内容,每条建议前加序号,如1、,每条建议间加换行符");
|
||||
|
||||
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GetAiParamString2(SecondaryCircuitInspectionAiParamModel aiParamModel)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("你是一个电力系统专家,请根据以下变电站二次巡检结果数据生成检修策略:");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.Append("检修的开始时间不得晚于当前时间 ")
|
||||
.AppendLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
sb.AppendLine("检修的建议要避免停止变电所的正常运行,要以宽松的方式,检修工作必须遵循“安全第一、预防为主”的核心原则,实施全过程精益化管理");
|
||||
sb.AppendLine("检修前,需制定详尽的计划与方案,确保人员资质合格、物资工具齐备,并严格执行工作票制度,实现安全隔离。检修中,必须强化现场安全管控,设专人监护,并严格按标准工艺施工,做好关键数据记录与过程质检。检修后,要严格执行试验验收程序,确保设备性能达标,彻底清理现场,并完善检修报告与设备台账,形成闭环管理");
|
||||
sb.AppendLine("整个流程旨在通过规范化、标准化的作业,确保人身、设备与电网安全,提升设备可靠性,保障电力稳定供应");
|
||||
sb.AppendLine(",减少思考过程");
|
||||
sb.AppendLine();
|
||||
|
||||
// 巡检结果
|
||||
sb.AppendLine("巡检结果为:");
|
||||
sb.AppendLine($"巡检状态: {aiParamModel.SaveModel.Status}");
|
||||
sb.AppendLine($"计算过程: {aiParamModel.SaveModel.CalculationProcess}");
|
||||
sb.AppendLine($"巡检结果: {aiParamModel.SaveModel.InspectionResult}");
|
||||
sb.AppendLine($"校验结果: {aiParamModel.SaveModel.VerificationResult}");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("请按以下JSON格式生成策略:");
|
||||
|
||||
sb.AppendLine(@"{
|
||||
""level"": ""紧急程度"",
|
||||
""focusAreas"": [""重点检修区域1"", ""重点检修区域2""],
|
||||
""steps"": [
|
||||
{""duration"": ""时间"", ""content"": ""步骤描述""}
|
||||
],
|
||||
""resources"": [""资源需求1"", ""资源需求2""],
|
||||
""schedule"": {
|
||||
""startDate"": ""开始时间"",
|
||||
""optimalWindow"": ""最佳窗口"",
|
||||
""endDate"": ""结束时间""
|
||||
},
|
||||
""risks"": [
|
||||
{""title"": ""风险标题"", ""severity"": ""风险等级"", ""mitigation"": ""缓解措施""}
|
||||
]
|
||||
}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string ParseAiResult(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
return string.Empty;
|
||||
|
||||
var result = new StringBuilder();
|
||||
|
||||
foreach (string line in raw.Split('\n'))
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(trimmed))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(trimmed);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (root.TryGetProperty("type", out var typeProp) &&
|
||||
typeProp.GetString() == "content" &&
|
||||
root.TryGetProperty("content", out var contentProp))
|
||||
{
|
||||
result.Append(contentProp.GetString());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 跳过非 JSON 行
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
//获取计划数据
|
||||
@ -437,6 +888,14 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
result = await _webApiRequest.GetInspectionResultData(id);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
result = await _webApiRequest.GetGatewayInfoData(id);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
result = await _webApiRequest.GetVirtualPointInfoData(id);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Invalid type");
|
||||
}
|
||||
@ -468,7 +927,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
try
|
||||
{
|
||||
SecondaryCircuitEventDrivenConfigOutput[] planList;
|
||||
lock (_planLock)
|
||||
using (await _eventPlanLock.LockAsync())
|
||||
{
|
||||
planList = _eventPlanList.ToArray();
|
||||
}
|
||||
@ -485,26 +944,54 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
var engine = new Jint.Engine();
|
||||
|
||||
SecondaryCircuitInspectionResultModel resultModel = new SecondaryCircuitInspectionResultModel();
|
||||
SecondaryCircuitInspectionJsDataModel jsData = new SecondaryCircuitInspectionJsDataModel();
|
||||
|
||||
var task1 = GetSourceDataAsync<List<YCResultData>>(id, 1);
|
||||
var task2 = GetSourceDataAsync<List<YXResultData>>(id, 2);
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// 等待全部完成
|
||||
await Task.WhenAll(task1, task2);
|
||||
Task<List<ZzDataResultModel>>? t1 = null;
|
||||
Task<List<ZzDataResultModel>>? t2 = null;
|
||||
|
||||
resultModel.TelemetryInfoData = await task1;
|
||||
resultModel.TeleSignalData = await task2;
|
||||
if (item.TelemetryConfigs.Count > 0)
|
||||
{
|
||||
t1 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 1);
|
||||
tasks.Add(t1);
|
||||
}
|
||||
|
||||
if (item.TelesignalConfigs.Count > 0)
|
||||
{
|
||||
t2 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 2);
|
||||
tasks.Add(t2);
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (t1 != null) jsData.StoreData.TelemetryData = await t1;
|
||||
if (t2 != null) jsData.StoreData.TeleSignalData = await t2;
|
||||
|
||||
engine.Execute(item.TriggerExpression);
|
||||
|
||||
var canCheck = engine.Invoke("calculate", resultModel).AsBoolean();
|
||||
bool canCheck = true;
|
||||
|
||||
if(!_isDebug)
|
||||
{
|
||||
canCheck = engine.Invoke("calculate", jsData).AsBoolean();
|
||||
}
|
||||
|
||||
if(canCheck)
|
||||
{
|
||||
await Task.Delay(item.MandatoryWaitSeconds * 1000);
|
||||
|
||||
CheckPlanFormIds(item.SecondaryCircuitInspectionEventItems);
|
||||
//CheckPlanFormIds(item.SecondaryCircuitInspectionEventItems);
|
||||
|
||||
//执行巡检
|
||||
foreach(var planItem in item.SecondaryCircuitInspectionEventItems)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(planItem.CalculationExpression) && !string.IsNullOrWhiteSpace(planItem.Id.ToString()))
|
||||
{
|
||||
//写到队列里,由队列控制执行频率
|
||||
await _singlePlanChannel.Writer.WriteAsync(new SecondaryCircuitInspectionItemOutputEx(2, planItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -519,45 +1006,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
}
|
||||
|
||||
//检测巡检计划中的计划
|
||||
private async Task CheckPlanFormIds(List<SecondaryCircuitInspectionItemOutput> planIds)
|
||||
{
|
||||
if (_planList == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
SecondaryCircuitInspectionPlanStateModel[] planList;
|
||||
lock (_planLock)
|
||||
{
|
||||
planList = _planList.ToArray();
|
||||
}
|
||||
|
||||
foreach (var plan in planList)
|
||||
{
|
||||
if(plan.Plan.IsActive)
|
||||
{
|
||||
foreach (var item in plan.Plan.InspectionItems)
|
||||
{
|
||||
if(planIds.Find( e => e.Id == item.Id) != null && item.IsActive)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(item.CalculationExpression) && !string.IsNullOrWhiteSpace(item.Id.ToString()))
|
||||
{
|
||||
//写到队列里,由队列控制执行频率
|
||||
await _singlePlanChannel.Writer.WriteAsync(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "SecondaryCircuitInspectionPlanService - CheckPlanFormIds :发生错误");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
////获取遥测数据
|
||||
//public async Task<List<YCResultData>> CallYCByDataIdAsync(CallYCByDataIdRequest request, CancellationToken cancellationToken = default)
|
||||
|
||||
@ -5,6 +5,7 @@ using StackExchange.Redis;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@ -62,10 +63,11 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
// 🔧 新增:遥测数据召唤存储相关字段
|
||||
// 存储召唤的遥测数据:DataId -> List<TimestampedYCData>
|
||||
private readonly ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>> _ycDataStorage = new ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>>();
|
||||
private readonly Timer _cleanupTimer;
|
||||
private const int CLEANUP_INTERVAL_MS = 60000; // 1分钟清理一次
|
||||
private const int DATA_EXPIRATION_MINUTES = 5; // 数据保留5分钟
|
||||
//private readonly ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>> _ycDataStorage = new ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>>();
|
||||
//private readonly Timer _cleanupTimer;
|
||||
//private const int CLEANUP_INTERVAL_MS = 60000; // 1分钟清理一次
|
||||
//private const int DATA_EXPIRATION_MINUTES = 5; // 数据保留5分钟
|
||||
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(3); // 数据保留3分钟
|
||||
|
||||
// 初始化状态
|
||||
public volatile bool _isInitialized = false;
|
||||
@ -98,7 +100,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
_batchPushTimer = new Timer(ProcessBatchPush, null, BATCH_PUSH_INTERVAL_MS, BATCH_PUSH_INTERVAL_MS);
|
||||
|
||||
// 🔧 新增:初始化数据清理定时器
|
||||
_cleanupTimer = new Timer(CleanupExpiredData, null, CLEANUP_INTERVAL_MS, CLEANUP_INTERVAL_MS);
|
||||
//_cleanupTimer = new Timer(CleanupExpiredData, null, CLEANUP_INTERVAL_MS, CLEANUP_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -178,6 +180,10 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
if (_ycIdToHashKeysMapping[model.ismsbaseYCId].Contains(haskey))
|
||||
{
|
||||
mappingCount++;
|
||||
|
||||
//初始化
|
||||
_zzDataCacheContainer.Write(model.ismsbaseYCId, model.ResultValue, model.ResultTime, model.Name, $"{model.ResultValue} {model.Unit}", model.DispatcherAddress);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -370,9 +376,6 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
// 🔧 新增:存储遥测数据到内存,用于数据召唤功能
|
||||
StoreYCDataForRetrieval(ycDataModel);
|
||||
|
||||
// 使用映射字典查找对应的haskey列表
|
||||
if (!_ycIdToHashKeysMapping.TryGetValue(ycDataModel.YC_ID, out List<string> haskeys))
|
||||
{
|
||||
@ -417,6 +420,14 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
// 保存到Redis
|
||||
await _telemeteringModelListRedis.HashSetUpdateOneAsync(redisKey, haskey, telemeteringModel);
|
||||
|
||||
//if(ycDataModel.YC_ID == "YCB001101001")
|
||||
//{
|
||||
// Debug.WriteLine("YCB001101001 : ", ycDataModel.V.ToString(), telemeteringModel.ResultValue.ToString(), telemeteringModel.Coefficient.ToString());
|
||||
//}
|
||||
|
||||
//保存到缓存
|
||||
_zzDataCacheContainer.Write(ycDataModel.YC_ID, (float)ycDataModel.V, telemeteringModel.ResultTime, telemeteringModel.Name, $"{(float)ycDataModel.V} {telemeteringModel.Unit}", telemeteringModel.DispatcherAddress);
|
||||
|
||||
// 检查设备是否处于检修状态
|
||||
equipmentId = telemeteringModel.EquipmentInfoId.Value;
|
||||
var isInMaintenance = await IsEquipmentInMaintenanceAsync(equipmentId);
|
||||
@ -641,241 +652,28 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 🔧 新增:存储遥测数据用于召唤检索
|
||||
/// </summary>
|
||||
/// <param name="ycData">遥测数据</param>
|
||||
private void StoreYCDataForRetrieval(YCData ycData)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ycData == null || string.IsNullOrEmpty(ycData.YC_ID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var timestampedData = new TimestampedYCData
|
||||
{
|
||||
Data = ycData,
|
||||
ReceivedTime = DateTime.Now
|
||||
};
|
||||
|
||||
// 获取或创建该DataId的存储列表
|
||||
var dataList = _ycDataStorage.GetOrAdd(ycData.YC_ID, _ => new ConcurrentBag<TimestampedYCData>());
|
||||
dataList.Add(timestampedData);
|
||||
|
||||
_logger.LogDebug("存储遥测数据 - YC_ID: {YcId}, Value: {Value}, Time: {Time}",
|
||||
ycData.YC_ID, ycData.V, ycData.T);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "存储遥测数据失败 - YC_ID: {YcId}", ycData?.YC_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 🔧 新增:根据DataId列表获取召唤的遥测数据
|
||||
/// </summary>
|
||||
/// <param name="dataIds">DataId列表</param>
|
||||
/// <returns>遥测数据列表</returns>
|
||||
public List<YCData> GetYCDataByDataIds(List<string> dataIds)
|
||||
public async Task<List<ZzDataResultModel>> GetYCDataByDataIds(List<string> ids, int seconds, int timeWindowType, CancellationToken cancellationToken, DateTime now = default)
|
||||
{
|
||||
var result = new List<YCData>();
|
||||
|
||||
try
|
||||
if (!_isInitialized)
|
||||
{
|
||||
if (dataIds == null || dataIds.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("DataId列表为空,无法获取遥测数据");
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var dataId in dataIds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dataId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_ycDataStorage.TryGetValue(dataId, out var dataList))
|
||||
{
|
||||
|
||||
// 只返回未过期的数据(5分钟内)
|
||||
var validData = dataList
|
||||
.Where(d => (DateTime.Now - d.ReceivedTime).TotalMinutes <= DATA_EXPIRATION_MINUTES)
|
||||
.Select(d => d.Data)
|
||||
.ToList();
|
||||
|
||||
result.AddRange(validData);
|
||||
|
||||
_logger.LogDebug("获取遥测数据 - DataId: {DataId}, Count: {Count}", dataId, validData.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("未找到遥测数据 - DataId: {DataId}", dataId);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("获取遥测数据完成 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
|
||||
dataIds.Count, result.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取遥测数据失败");
|
||||
await InitAsync();
|
||||
}
|
||||
|
||||
var result = await _zzDataCacheContainer.Read(ids, seconds, timeWindowType, cancellationToken, now);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 🔧 新增:根据DataId列表获取召唤的遥测数据
|
||||
/// </summary>
|
||||
/// <param name="dataIds">DataId列表</param>
|
||||
/// <returns>遥测数据列表</returns>
|
||||
public async Task<List<YCResultData>> GetYCDataByDataIds(List<string> dataIds, DateTime cmdTime, int times)
|
||||
{
|
||||
var result = new List<YCResultData>();
|
||||
|
||||
try
|
||||
{
|
||||
if (dataIds == null || dataIds.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("DataId列表为空,无法获取遥测数据");
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var dataId in dataIds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dataId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_ycDataStorage.TryGetValue(dataId, out var dataList))
|
||||
{
|
||||
|
||||
var validData = dataList
|
||||
.Where(d => d.ReceivedTime >= cmdTime)
|
||||
.Select(d => d.Data)
|
||||
.ToList();
|
||||
|
||||
if (validData.Count >= times)
|
||||
{
|
||||
var item = new YCResultData();
|
||||
|
||||
item.Id = dataId;
|
||||
item.ParseValue(validData[^times..]);
|
||||
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
_logger.LogDebug("获取遥测数据 - DataId: {DataId}, Count: {Count}", dataId, validData.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("未找到遥测数据 - DataId: {DataId}", dataId);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("获取遥测数据完成 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
|
||||
dataIds.Count, result.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取遥测数据失败");
|
||||
}
|
||||
|
||||
if(result.Count == dataIds.Count)
|
||||
{
|
||||
string redisKey = $"{_telemeteringModelListRediskey}_Zongzi";
|
||||
|
||||
foreach (var item in result)
|
||||
{
|
||||
if (!_ycIdToHashKeysMapping.TryGetValue(item.Id, out List<string> haskeys))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(haskeys.Count >= 0)
|
||||
{
|
||||
string haskey = haskeys.First();
|
||||
|
||||
var telemeteringModel = await _telemeteringModelListRedis.HashSetGetOneAsync(redisKey, haskey);
|
||||
if (telemeteringModel == null)
|
||||
{
|
||||
_logger.LogWarning("Redis中未找到遥测数据: RedisKey={RedisKey}, HasKey={HasKey}", redisKey, haskey);
|
||||
continue; // 继续处理下一个haskey
|
||||
}
|
||||
|
||||
item.Name = telemeteringModel.Name;
|
||||
item.DispatcherAddress = telemeteringModel.DispatcherAddress;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 🔧 新增:清理过期的遥测数据(定时器回调)
|
||||
/// </summary>
|
||||
/// <param name="state">定时器状态</param>
|
||||
private void CleanupExpiredData(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var expiredKeys = new List<string>();
|
||||
var totalCleaned = 0;
|
||||
|
||||
foreach (var kvp in _ycDataStorage)
|
||||
{
|
||||
var dataId = kvp.Key;
|
||||
var dataList = kvp.Value;
|
||||
|
||||
// 过滤出未过期的数据
|
||||
var validData = dataList
|
||||
.Where(d => (now - d.ReceivedTime).TotalMinutes <= DATA_EXPIRATION_MINUTES)
|
||||
.ToList();
|
||||
|
||||
var cleanedCount = dataList.Count - validData.Count;
|
||||
totalCleaned += cleanedCount;
|
||||
|
||||
if (validData.Count == 0)
|
||||
{
|
||||
// 如果没有有效数据,标记该键为待删除
|
||||
expiredKeys.Add(dataId);
|
||||
}
|
||||
else if (cleanedCount > 0)
|
||||
{
|
||||
// 如果有部分数据过期,重新创建数据列表
|
||||
var newBag = new ConcurrentBag<TimestampedYCData>();
|
||||
foreach (var data in validData)
|
||||
{
|
||||
newBag.Add(data);
|
||||
}
|
||||
_ycDataStorage.TryUpdate(dataId, newBag, dataList);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除没有有效数据的键
|
||||
foreach (var key in expiredKeys)
|
||||
{
|
||||
_ycDataStorage.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
if (totalCleaned > 0 || expiredKeys.Count > 0)
|
||||
{
|
||||
_logger.LogInformation("清理过期遥测数据完成 - 清理数据数: {CleanedCount}, 删除键数: {RemovedKeys}",
|
||||
totalCleaned, expiredKeys.Count);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "清理过期遥测数据时发生错误");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 🔧 新增:释放资源
|
||||
@ -883,7 +681,6 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
public void Dispose()
|
||||
{
|
||||
_batchPushTimer?.Dispose();
|
||||
_cleanupTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,9 +53,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
private readonly object _expressionInitLock = new object();
|
||||
|
||||
//遥信数据缓冲队列
|
||||
private static readonly ConcurrentQueue<(DateTime time, YXCacheData yxData)> _yxDataBuffer = new ConcurrentQueue<(DateTime time, YXCacheData yxData)>();
|
||||
private const int YXDATA_BUFFER_SECONDS = 60; //只保留60秒的数据
|
||||
private static int _yxDataRecvCount = 0; //每100次清除一下超时数据,用于计数
|
||||
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(30); //只保留5分钟的数据
|
||||
|
||||
|
||||
|
||||
@ -145,6 +143,9 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
if (_yxIdToHashKeyMapping.TryAdd(model.ismsbaseYXId, haskey))
|
||||
{
|
||||
mappingCount++;
|
||||
|
||||
//初始化
|
||||
_zzDataCacheContainer.Write(model.ismsbaseYXId, model.ResultValue, model.ResultTime, model.Name, model.ResultValueStr, model.DispatcherAddress);
|
||||
}
|
||||
|
||||
}
|
||||
@ -349,12 +350,25 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
// 解析YXData结构
|
||||
var yxDataModel = yxItem.ToObject<YXData>();
|
||||
|
||||
if (yxDataModel == null)
|
||||
{
|
||||
_logger.LogWarning("无法解析遥信数据项: {Item}", yxItem?.ToString());
|
||||
return null;
|
||||
}
|
||||
|
||||
//开关状态(合、分、不定),与具体的规约(101、104)无关 TSwitchstate = (swon = 0 swOff = 1,swUncertainty = 2);
|
||||
switch (yxDataModel.V)
|
||||
{
|
||||
case 0:
|
||||
yxDataModel.V = 2;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
yxDataModel.V = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// 使用映射字典查找对应的haskey
|
||||
if (!_yxIdToHashKeyMapping.TryGetValue(yxDataModel.YX_ID, out string haskey))
|
||||
{
|
||||
@ -401,16 +415,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
_telesignalisationModelListRedis.PublishAsync(channel, telesignalisationModel);
|
||||
|
||||
//缓存遥信数据到内存
|
||||
YXCacheData yXCacheData = new YXCacheData
|
||||
{
|
||||
Id = yxDataModel.YX_ID,
|
||||
Value = yxDataModel.V,
|
||||
ValueStr = telesignalisationModel.ResultValueStr,
|
||||
TimeStamp = resultTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
};
|
||||
|
||||
SaveYxDataToCache(resultTime, yXCacheData);
|
||||
|
||||
_zzDataCacheContainer.Write(yxDataModel.YX_ID, yxDataModel.V, resultTime, telesignalisationModel.Name, telesignalisationModel.ResultValueStr, telesignalisationModel.DispatcherAddress);
|
||||
|
||||
// 🔧 优化:如果跳过转发,仅更新Redis后直接返回
|
||||
if (skipForwarding)
|
||||
@ -458,115 +463,40 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//缓存遥信数据到内存
|
||||
private void SaveYxDataToCache(DateTime dateTime, YXCacheData yxData)
|
||||
{
|
||||
_yxDataBuffer.Enqueue((dateTime, yxData));
|
||||
|
||||
// 每100次接收清理一次过期数据
|
||||
if (Interlocked.Increment(ref _yxDataRecvCount) % 100 == 0)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
while (_yxDataBuffer.TryPeek(out var oldest) &&
|
||||
(now - oldest.time).TotalSeconds > YXDATA_BUFFER_SECONDS)
|
||||
{
|
||||
_yxDataBuffer.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<YXResultData>> GetYXDataByDataIds(List<string> ids, int times, CancellationToken cancellationToken)
|
||||
public async Task<List<ZzDataResultModel>> GetYXDataByDataIds(List<string> ids, int times, int timeWindowType, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
var resultDict = new Dictionary<string, List<YXCacheData>>(); // key = YX_ID
|
||||
|
||||
// 初始化每个ID的结果列表
|
||||
foreach (var id in ids)
|
||||
resultDict[id] = new List<YXCacheData>();
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
// 异步监听任务
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while ((DateTime.Now - startTime).TotalSeconds < times)
|
||||
{
|
||||
var matched = _yxDataBuffer
|
||||
.Where(x => x.time >= startTime && ids.Contains(x.yxData.Id))
|
||||
.ToList();
|
||||
|
||||
// 按 ID 分类
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var datas = matched.Where(x => x.yxData.Id == id)
|
||||
.Select(x => x.yxData)
|
||||
.ToList();
|
||||
|
||||
lock (resultDict[id])
|
||||
{
|
||||
resultDict[id].Clear();
|
||||
resultDict[id].AddRange(datas);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(500, cancellationToken);
|
||||
}
|
||||
|
||||
tcs.TrySetResult();
|
||||
}, cancellationToken);
|
||||
|
||||
await Task.WhenAny(tcs.Task, Task.Delay(times * 1000, cancellationToken));
|
||||
|
||||
// 构建最终返回数据
|
||||
var finalResult = new List<YXResultData>();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var yxResult = new YXResultData
|
||||
{
|
||||
Id = id,
|
||||
};
|
||||
|
||||
lock (resultDict[id])
|
||||
{
|
||||
yxResult.ParseValue(resultDict[id]);
|
||||
}
|
||||
|
||||
finalResult.Add(yxResult);
|
||||
}
|
||||
|
||||
//设置Name和DispatcherAddress,并且检查数据是否为空,如果为空,从Redis里取最新值
|
||||
|
||||
//检查数据是否为空,如果为空,从Redis里取最新值
|
||||
if(!_isInitialized)
|
||||
{
|
||||
await InitAsync();
|
||||
}
|
||||
|
||||
var finalResult = await _zzDataCacheContainer.Read(ids, times, timeWindowType, cancellationToken);
|
||||
|
||||
|
||||
string redisKey = $"{_telesignalisationModelListRediskey}_Zongzi";
|
||||
foreach (var item in finalResult)
|
||||
{
|
||||
if (!_yxIdToHashKeyMapping.TryGetValue(item.Id, out string haskey))
|
||||
if(item.TimeStamp == DateTime.MinValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!_yxIdToHashKeyMapping.TryGetValue(item.Id, out string haskey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var telesignalisationModel = await _telesignalisationModelListRedis.HashSetGetOneAsync(redisKey, haskey);
|
||||
if (telesignalisationModel == null)
|
||||
{
|
||||
_logger.LogWarning("Redis中未找到遥信数据: RedisKey={RedisKey}, HasKey={HasKey}", redisKey, haskey);
|
||||
continue; // 继续处理下一个haskey
|
||||
}
|
||||
var telesignalisationModel = await _telesignalisationModelListRedis.HashSetGetOneAsync(redisKey, haskey);
|
||||
if (telesignalisationModel == null)
|
||||
{
|
||||
_logger.LogWarning("Redis中未找到遥信数据: RedisKey={RedisKey}, HasKey={HasKey}", redisKey, haskey);
|
||||
continue; // 继续处理下一个haskey
|
||||
}
|
||||
|
||||
item.Name = telesignalisationModel.Name;
|
||||
item.DispatcherAddress = telesignalisationModel.DispatcherAddress;
|
||||
|
||||
if (item.Data.Count == 0)
|
||||
{
|
||||
item.Value = telesignalisationModel.ResultValue;
|
||||
item.ValueStr = telesignalisationModel.ResultValueStr;
|
||||
item.TimeStamp = telesignalisationModel.ResultTime.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
item.TimeStamp = telesignalisationModel.ResultTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return finalResult;
|
||||
|
||||
@ -287,11 +287,12 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
/******************配置信息*****************/
|
||||
|
||||
//更新孪生体与遥测数据绑定关系
|
||||
//更新孪生体与遥信数据绑定关系
|
||||
private async Task UpdateDeviceBindingConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
//db ex_younuo_twin_data_binding
|
||||
List<ThingDeviceBindingModel> list = await _webApiRequest.GetThingDeviceBindingConfigAsync();
|
||||
|
||||
if (list != null)
|
||||
@ -432,7 +433,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
/******************设备状态(遥信)*****************/
|
||||
|
||||
//遥信状态有变化时,更新设备状态
|
||||
private async Task UpdateDeviceChangeStatus(List<YXData> yXDatas)
|
||||
private async Task UpdateDeviceChangeStatus(List<TelesignalisationModel> yXDatas)
|
||||
{
|
||||
if (yXDatas == null)
|
||||
return;
|
||||
@ -447,14 +448,17 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
foreach (var item in yXDatas)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(item.YX_ID))
|
||||
if (!string.IsNullOrWhiteSpace(item.ismsbaseYXId))
|
||||
{
|
||||
if (_deviceBindingConfigs.TryGetValue(item.YX_ID, out var bindingItem))
|
||||
if (_deviceBindingConfigs.TryGetValue(item.ismsbaseYXId, out var bindingItem))
|
||||
{
|
||||
if(bindingItem.IsActive)
|
||||
{
|
||||
var metric = bindingItem.MetricList.Find(e => e.Val == item.T);
|
||||
var val = bindingItem.ValList.Find(e => e.Val == item.T);
|
||||
|
||||
var yxValue = item.ResultValue.ToString();
|
||||
|
||||
var metric = bindingItem.MetricList.Find(e => e.Val == yxValue);
|
||||
var val = bindingItem.ValList.Find(e => e.Val == yxValue);
|
||||
|
||||
if (metric != null && val != null)
|
||||
{
|
||||
@ -501,6 +505,8 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//实时更新遥信全体设备状态
|
||||
public async Task UpdateYXDeviceAllStatus()
|
||||
{
|
||||
@ -526,10 +532,10 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
if(bindingItem.IsActive)
|
||||
{
|
||||
int yxValue = item.ResultValue;
|
||||
var yxValue = item.ResultValue.ToString();
|
||||
|
||||
var metric = bindingItem.MetricList.Find(e => e.Val == yxValue.ToString());
|
||||
var val = bindingItem.ValList.Find(e => e.Val == yxValue.ToString());
|
||||
var metric = bindingItem.MetricList.Find(e => e.Val == yxValue);
|
||||
var val = bindingItem.ValList.Find(e => e.Val == yxValue);
|
||||
|
||||
if (metric != null && val != null)
|
||||
{
|
||||
@ -539,7 +545,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
else
|
||||
{
|
||||
int k = 0;
|
||||
//int k = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -598,10 +604,10 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
//遥信数据有变化时,将状态和性能数据推送到孪生体
|
||||
public async Task UpdateThingYXStatus(List<YXData> yXDatas, List<TelesignalisationModel> telesignalisationModels)
|
||||
public async Task UpdateThingYXStatus(List<TelesignalisationModel> telesignalisationModels)
|
||||
{
|
||||
//更新孪生体
|
||||
await UpdateDeviceChangeStatus(yXDatas);
|
||||
await UpdateDeviceChangeStatus(telesignalisationModels);
|
||||
|
||||
//推送性能数据
|
||||
await UpdateYXSimDatasStatus(telesignalisationModels);
|
||||
@ -929,20 +935,9 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
// 根据虚点值处理报警
|
||||
// Value = 0 表示正常状态,Value = 1 表示故障状态
|
||||
bool isAlarm = false;
|
||||
if (Math.Abs(value - 1) < 0.0001) // 故障状态
|
||||
{
|
||||
isAlarm = true;
|
||||
}
|
||||
else if (Math.Abs(value) < 0.0001) // 正常状态
|
||||
{
|
||||
isAlarm = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//Boolean型变量的值(真,假,不定)2018-04-26从uPro设备
|
||||
//TVariantBoolValue = (vbTrue = 0,vbFalse = 1 vbUncertain = 2):
|
||||
bool isAlarm = Math.Abs(value) < 0.0001;
|
||||
|
||||
TingAlarmPushDataModel alertData = new TingAlarmPushDataModel();
|
||||
|
||||
@ -952,7 +947,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
isAlarm,
|
||||
$"{configItem.P1DeviceName}{configItem.P2DeviceName}",
|
||||
"光纤断线",
|
||||
item.T
|
||||
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
);
|
||||
|
||||
alertDatas.Add(alertData);
|
||||
@ -1109,6 +1104,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
configs = _opticalCableConfig.ToArray();
|
||||
}
|
||||
|
||||
//虚点
|
||||
if (virtualTerminalDatas != null)
|
||||
{
|
||||
foreach (var item in virtualTerminalDatas)
|
||||
@ -1126,20 +1122,10 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
// 根据虚点值处理报警
|
||||
// Value = 0 表示正常状态,Value = 1 表示故障状态
|
||||
bool isAlarm = false;
|
||||
if (Math.Abs(value - 1) < 0.0001) // 故障状态
|
||||
{
|
||||
isAlarm = true;
|
||||
}
|
||||
else if (Math.Abs(value) < 0.0001) // 正常状态
|
||||
{
|
||||
isAlarm = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// 根据虚点值处理报警
|
||||
//Boolean型变量的值(真,假,不定)2018-04-26从uPro设备
|
||||
//TVariantBoolValue = (vbTrue = 0,vbFalse = 1 vbUncertain = 2):
|
||||
bool isAlarm = Math.Abs(value) < 0.0001;
|
||||
|
||||
TingAlarmPushDataModel alertData = new TingAlarmPushDataModel();
|
||||
|
||||
@ -1149,7 +1135,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
isAlarm,
|
||||
$"{configItem.P1CabinetName}{configItem.P2CabinetName}",
|
||||
"光缆断线",
|
||||
item.T
|
||||
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
);
|
||||
|
||||
alertDatas.Add(alertData);
|
||||
@ -1160,7 +1146,6 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (telesignalisationModels != null)
|
||||
{
|
||||
foreach (var item in telesignalisationModels)
|
||||
@ -1226,6 +1211,8 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
|
||||
private string _apiToken = string.Empty;
|
||||
private DateTime _apiTokenTime = DateTime.MinValue;
|
||||
private readonly SemaphoreSlim _apiTokenSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
|
||||
public ThingApiService(
|
||||
ILogger<ThingApiService> logger)
|
||||
@ -1242,30 +1229,57 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
//获取Token
|
||||
private async Task<string> GetTokenAsync()
|
||||
{
|
||||
await _apiTokenSemaphore.WaitAsync();
|
||||
|
||||
string src = string.Empty;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_apiToken) || (DateTime.Now - _apiTokenTime).TotalHours > 6)
|
||||
{//每6小时更新一次token
|
||||
var http = new ThingWebClientHelper();
|
||||
int retryCnt = 3;
|
||||
while (retryCnt-- > 0)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_apiToken) || (DateTime.Now - _apiTokenTime).TotalHours > 6)
|
||||
{//每6小时更新一次token
|
||||
var http = new ThingWebClientHelper();
|
||||
|
||||
var json = await http.PostJsonAsync($"{_apiServerUrl}:1662/thing/provider/rest/getToken", new { loginCode = _loginCode, loginKey = _loginKey });
|
||||
var json = await http.PostJsonAsync($"{_apiServerUrl}:1662/thing/provider/rest/getToken", new { loginCode = _loginCode, loginKey = _loginKey });
|
||||
|
||||
JObject obj = JObject.Parse(json);
|
||||
src = json ?? "";
|
||||
|
||||
_apiToken = obj["data"]?["token"]?.ToString();
|
||||
JObject obj = JObject.Parse(json);
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(_apiToken))
|
||||
_apiTokenTime = DateTime.Now;
|
||||
_apiToken = obj["data"]?["token"]?.ToString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_apiToken))
|
||||
{
|
||||
_apiTokenTime = DateTime.Now;
|
||||
System.Console.WriteLine("ThingApiService - 获取token成功");
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_apiToken))
|
||||
{
|
||||
await Task.Delay(6000); //此接 口每5秒只可以调用1次
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
_apiTokenSemaphore.Release();
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(_apiToken))
|
||||
_logger.LogError("ThingApiService - 获取token失败");
|
||||
if (string.IsNullOrWhiteSpace(_apiToken))
|
||||
_logger.LogError("ThingApiService - 连续3次获取token失败,返回内容:" + src);
|
||||
|
||||
return _apiToken;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.ClearScript.JavaScript;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using YunDa.Server.ISMSTcp.Models;
|
||||
using YunDa.Server.ISMSTcp.Domain;
|
||||
using YunDa.SOMS.DataTransferObject.ExternalEntities.BeijingYounuo;
|
||||
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
|
||||
{
|
||||
@ -19,6 +23,7 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
private readonly ILogger<VirtualTerminalHandler> _logger;
|
||||
private readonly WebApiRequest _webApiRequest;
|
||||
private readonly ZzDataCacheContainerInit _zzDataCacheContainerInit;
|
||||
|
||||
// 🔧 新增:光纤虚点处理相关字段
|
||||
private readonly ConcurrentDictionary<string, OpticalFiberDto> _virtualPointToOpticalFiberMapping = new ConcurrentDictionary<string, OpticalFiberDto>();
|
||||
@ -27,10 +32,17 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
public volatile bool _isOpticalFiberMappingInitialized = false;
|
||||
private readonly object _opticalFiberInitLock = new object();
|
||||
|
||||
public VirtualTerminalHandler(ILogger<VirtualTerminalHandler> logger, WebApiRequest webApiRequest)
|
||||
|
||||
//数据缓冲队列
|
||||
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(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>
|
||||
@ -172,6 +184,9 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
// 根据UA_ID执行不同的处理逻辑
|
||||
await ProcessByUAId(virtualTerminalData);
|
||||
|
||||
//存入缓存
|
||||
SaveDataToCache(virtualTerminalData.VA_ID, value, timestamp);
|
||||
|
||||
_logger.LogInformation("虚端子数据处理完成:{UA_ID}", virtualTerminalData.VA_ID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -229,12 +244,13 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
_logger.LogDebug("处理光纤虚点: {VA_ID}, 值: {Value}, 光纤: {TwinId}", data.VA_ID, value, opticalFiber.TwinId);
|
||||
|
||||
// 根据虚点值处理报警
|
||||
// Value = 0 表示正常状态,Value = 1 表示故障状态
|
||||
if (Math.Abs(value - 1) < 0.0001) // 故障状态
|
||||
//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) < 0.0001) // 正常状态
|
||||
else if (Math.Abs(value - 1) < 0.0001) // 正常状态
|
||||
{
|
||||
await DeleteOpticalFiberAlarmAsync(opticalFiber, data);
|
||||
}
|
||||
@ -352,5 +368,38 @@ namespace YunDa.Server.ISMSTcp.Services
|
||||
opticalFiber.TwinId, opticalFiber.VirtualPointCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//缓存遥信数据到内存
|
||||
private void SaveDataToCache(string id, double value, DateTime timestamp)
|
||||
{
|
||||
try
|
||||
{
|
||||
_zzDataCacheContainer.Write(id, value, timestamp, "");
|
||||
}
|
||||
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>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,303 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using YunDa.Server.ISMSTcp.Domain;
|
||||
using YunDa.Server.ISMSTcp.Interfaces;
|
||||
|
||||
namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
public class ZzDataPoint
|
||||
{
|
||||
public DateTime TimeStamp { get; set; }
|
||||
public double Value { get; }
|
||||
public string ValueStr { get; }
|
||||
|
||||
public ZzDataPoint(DateTime time, double value, string valueStr)
|
||||
{
|
||||
TimeStamp = time;
|
||||
Value = value;
|
||||
ValueStr = valueStr;
|
||||
}
|
||||
}
|
||||
|
||||
public class ZzData
|
||||
{
|
||||
public TimeSpan Retention { get; set; }
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int? DispatcherAddress { get; set; } = null;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
public double Value { get; set; }
|
||||
public string ValueStr { get; set; } = string.Empty;
|
||||
public DateTime TimeStamp { get; set; }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private readonly ConcurrentQueue<(DateTime time, ZzDataPoint point)> _queue = new ConcurrentQueue<(DateTime time, ZzDataPoint point)>();
|
||||
|
||||
|
||||
|
||||
public ZzData(int minutes, string id, string name, int dispatcherAddress)
|
||||
{
|
||||
Retention = TimeSpan.FromMinutes(minutes);
|
||||
Id = id;
|
||||
Name = name;
|
||||
DispatcherAddress = dispatcherAddress;
|
||||
}
|
||||
|
||||
|
||||
public void AddData(ZzDataPoint point)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
point.TimeStamp = now; //2025-11-18 用当前时间(临时修改)
|
||||
|
||||
_queue.Enqueue((now, point));
|
||||
|
||||
TimeStamp = point.TimeStamp;
|
||||
Value = point.Value;
|
||||
ValueStr = point.ValueStr;
|
||||
|
||||
CleanupOldData();
|
||||
}
|
||||
|
||||
private void CleanupOldData()
|
||||
{
|
||||
DateTime limit = DateTime.Now - Retention;
|
||||
while (_queue.TryPeek(out var dp) && dp.time < limit)
|
||||
{
|
||||
_queue.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ZzDataPoint> GetData(DateTime start, DateTime end)
|
||||
{
|
||||
return _queue.Where(dp => dp.point.TimeStamp >= start && dp.point.TimeStamp <= end)
|
||||
.Select(x => x.point)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ZzDataRequestModel
|
||||
{
|
||||
public List<string> Id { get; set; }
|
||||
public int Times { get; set; }
|
||||
public int TimeWindowType { get; set; }
|
||||
}
|
||||
|
||||
public class ZzDataResultModel
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int? DispatcherAddress { get; set; } = null;
|
||||
public double Value { get; set; }
|
||||
public string ValueStr { get; set; } = string.Empty;
|
||||
public DateTime TimeStamp { get; set; } = DateTime.MinValue;
|
||||
|
||||
public List<ZzDataPoint> Data { get; set; } = new List<ZzDataPoint>();
|
||||
|
||||
public ZzDataResultModel()
|
||||
{
|
||||
|
||||
}
|
||||
public ZzDataResultModel(ZzData data)
|
||||
{
|
||||
Id = data.Id;
|
||||
Name = data.Name;
|
||||
Value = data.Value;
|
||||
ValueStr = data.ValueStr;
|
||||
TimeStamp = data.TimeStamp;
|
||||
DispatcherAddress = data.DispatcherAddress;
|
||||
}
|
||||
}
|
||||
|
||||
public class ZzDataHistoryModel
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
public class ZzDataCacheContainer
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ZzData> _datas = new ConcurrentDictionary<string, ZzData>();
|
||||
|
||||
private readonly int _cleanupMinutes = 5;
|
||||
|
||||
public ZzDataCacheContainer(int cleanupMinutes)
|
||||
{
|
||||
_cleanupMinutes = cleanupMinutes;
|
||||
}
|
||||
|
||||
public void Write(string id, double val, DateTime time, string name)
|
||||
{
|
||||
var data = _datas.GetOrAdd(id, _ => new ZzData(_cleanupMinutes, id, name, 0));
|
||||
|
||||
data.AddData(new ZzDataPoint(time, val, ""));
|
||||
}
|
||||
|
||||
public void Write(string id, double val, DateTime time, string name, string valStr)
|
||||
{
|
||||
var data = _datas.GetOrAdd(id, _ => new ZzData(_cleanupMinutes, id, name, 0));
|
||||
|
||||
data.AddData(new ZzDataPoint(time, val, valStr));
|
||||
}
|
||||
|
||||
public void Write(string id, double val, DateTime time, string name, string valStr, int dispatcherAddress)
|
||||
{
|
||||
//if (id == "YCB001101001")
|
||||
//{
|
||||
// int kk = 0;
|
||||
//}
|
||||
var data = _datas.GetOrAdd(id, _ => new ZzData(_cleanupMinutes, id, name, dispatcherAddress));
|
||||
|
||||
data.AddData(new ZzDataPoint(time, val, valStr));
|
||||
}
|
||||
|
||||
public Dictionary<string, ZzDataResultModel> Read(List<string> ids, DateTime start, DateTime end)
|
||||
{
|
||||
var result = new Dictionary<string, ZzDataResultModel>();
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
||||
//if (id == "YCB001101001")
|
||||
//{
|
||||
// int kk = 0;
|
||||
//}
|
||||
|
||||
if (_datas.TryGetValue(id, out var channel))
|
||||
{
|
||||
ZzDataResultModel data = new ZzDataResultModel(channel);
|
||||
data.Data = channel.GetData(start, end);
|
||||
|
||||
if(data.Data.Count == 0 && data.TimeStamp != DateTime.MinValue)
|
||||
{
|
||||
ZzDataPoint zzDataPoint = new ZzDataPoint(DateTime.Now, data.Value, data.ValueStr);
|
||||
|
||||
data.Data.Add(zzDataPoint);
|
||||
}
|
||||
|
||||
result[id] = data;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<ZzDataResultModel>> Read(List<string> ids, int seconds, int timeWindowType, CancellationToken cancellationToken, DateTime now = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(now == default)
|
||||
now = DateTime.Now;
|
||||
|
||||
//寻找匹配的值
|
||||
Dictionary<string, ZzDataResultModel> matched1 = new Dictionary<string, ZzDataResultModel>();
|
||||
Dictionary<string, ZzDataResultModel> matched2 = new Dictionary<string, ZzDataResultModel>();
|
||||
|
||||
if (timeWindowType == 0 || timeWindowType == 2)
|
||||
{
|
||||
matched1 = Read(ids, now.AddSeconds(-seconds), now);
|
||||
}
|
||||
|
||||
if (timeWindowType == 1 || timeWindowType == 2)
|
||||
{
|
||||
int span = seconds*1000 - (int)(DateTime.Now - now).TotalMilliseconds;
|
||||
|
||||
if(span > 0)
|
||||
await Task.Delay(span, cancellationToken);
|
||||
matched2 = Read(ids, now, DateTime.Now);
|
||||
}
|
||||
|
||||
foreach (var kv in matched2)
|
||||
{
|
||||
if (!matched1.TryAdd(kv.Key, kv.Value))
|
||||
{
|
||||
matched1[kv.Key].Data.AddRange(kv.Value.Data);
|
||||
}
|
||||
}
|
||||
|
||||
return matched1.Values.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new List<ZzDataResultModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ZzDataCacheContainerInit
|
||||
{
|
||||
private readonly ILogger<SecondaryCircuitInspectionPlanService> _logger;
|
||||
private readonly WebApiRequest _webApiRequest;
|
||||
private readonly ZzTcpService _zTcpService = null;
|
||||
|
||||
public ZzDataCacheContainerInit(ILogger<SecondaryCircuitInspectionPlanService> logger, WebApiRequest webApiRequest, ZzTcpService zTcpService)
|
||||
{
|
||||
_logger = logger;
|
||||
_webApiRequest = webApiRequest;
|
||||
_zTcpService = zTcpService;
|
||||
}
|
||||
|
||||
|
||||
public void InitTcpService(ITcpClient tcpClient)
|
||||
{
|
||||
_zTcpService.Init(tcpClient);
|
||||
}
|
||||
|
||||
//初始化虚点信息
|
||||
public async Task InitVariantBaseinfo(ZzDataCacheContainer cache)
|
||||
{
|
||||
//从数据库获取
|
||||
var list = await _webApiRequest.GetVariantBaseinfoAsync();
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
cache.Write(item.Id, 2, DateTime.Now, item.Name, "不定");
|
||||
}
|
||||
}
|
||||
|
||||
//发送命令从综自获取
|
||||
if(_zTcpService != null)
|
||||
{
|
||||
string cmd = "CallVAByStat|B001";
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var sendResult = await _zTcpService.SendTcpMessageAsync(cmd, CancellationToken.None);
|
||||
if (sendResult.Success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//初始化网关信息
|
||||
public async Task InitGateWayBaseInfo(ZzDataCacheContainer cache)
|
||||
{
|
||||
var list = await _webApiRequest.GetGateWayBaseInfoAsync();
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
cache.Write(item.Id, 0, DateTime.Now, item.Name, "0.00 %");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using YunDa.Server.ISMSTcp.Controllers;
|
||||
using YunDa.Server.ISMSTcp.Interfaces;
|
||||
using YunDa.SOMS.ExternalInteraction.DataTransferObject.InspectionEquipment;
|
||||
|
||||
namespace YunDa.Server.ISMSTcp.Services
|
||||
{
|
||||
public class ZzTcpService
|
||||
{
|
||||
private readonly ILogger<QueryController> _logger;
|
||||
private ITcpClient _tcpClient = null;
|
||||
|
||||
public ZzTcpService(ILogger<QueryController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public void Init(ITcpClient tcpClient)
|
||||
{
|
||||
_tcpClient = tcpClient;
|
||||
}
|
||||
public async Task<(bool Success, string ErrorMessage)> SendTcpMessageAsync(string message, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
// 使用QueryService的TCP客户端发送消息
|
||||
// 注意:这里直接通过ITcpClient发送,不需要等待响应
|
||||
var tcpClient = GetTcpClientFromQueryService();
|
||||
if (tcpClient == null)
|
||||
{
|
||||
return (false, "TCP客户端未初始化");
|
||||
}
|
||||
|
||||
if (!tcpClient.IsConnected)
|
||||
{
|
||||
return (false, "TCP连接未建立");
|
||||
}
|
||||
|
||||
var sendResult = await tcpClient.SendMessageAsync(message, cancellationToken);
|
||||
|
||||
if (!sendResult)
|
||||
{
|
||||
return (false, "TCP消息发送失败");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "发送TCP消息时发生异常");
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从QueryService获取TCP客户端
|
||||
/// </summary>
|
||||
/// <returns>TCP客户端实例</returns>
|
||||
private ITcpClient GetTcpClientFromQueryService()
|
||||
{
|
||||
return _tcpClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,10 @@ namespace YunDa.Server.ISMSTcp
|
||||
|
||||
// 添加控制器
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson(); // 使用Newtonsoft.Json
|
||||
.AddNewtonsoftJson(options => // 使用Newtonsoft.Json
|
||||
{
|
||||
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||
});
|
||||
|
||||
// 添加CORS支持
|
||||
services.AddCors(options =>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user