|
|
|
@ -0,0 +1,822 @@
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* CoreServices.cs - 核心业务服务实现
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* 实现CNC机床数据采集分析系统的核心业务服务。
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* 修订历史:
|
|
|
|
|
|
|
|
* - 2026-04-13: 初始实现
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
using System.Net.NetworkInformation;
|
|
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
|
|
using System.Text.Json.Nodes;
|
|
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
|
|
using Haoliang.Models.Device;
|
|
|
|
|
|
|
|
using Haoliang.Models.Production;
|
|
|
|
|
|
|
|
using Haoliang.Models.DataCollection;
|
|
|
|
|
|
|
|
using Haoliang.Data.Repositories;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Haoliang.Core.Services
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
#region ========== Ping服务实现 ==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Ping服务实现 - 设备网络连通性检测
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public class PingService : IPingService
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
private readonly ILogger<PingService> _logger;
|
|
|
|
|
|
|
|
private const int DefaultTimeout = 5000; // 5秒超时
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public PingService(ILogger<PingService> logger)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Ping指定设备
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<PingResult> PingAsync(int deviceId, string ipAddress)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var result = new PingResult
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
DeviceId = deviceId,
|
|
|
|
|
|
|
|
IpAddress = ipAddress,
|
|
|
|
|
|
|
|
Timestamp = DateTime.UtcNow
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
using var ping = new Ping();
|
|
|
|
|
|
|
|
var reply = await ping.SendPingAsync(ipAddress, DefaultTimeout);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result.Success = reply.Status == IPStatus.Success;
|
|
|
|
|
|
|
|
result.RoundtripTime = reply.RoundtripTime;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!result.Success)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result.ErrorMessage = $"Ping失败: {reply.Status}";
|
|
|
|
|
|
|
|
_logger.LogWarning("设备{DeviceId} Ping {IpAddress} 失败: {Status}",
|
|
|
|
|
|
|
|
deviceId, ipAddress, reply.Status);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (PingException ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result.Success = false;
|
|
|
|
|
|
|
|
result.ErrorMessage = ex.Message;
|
|
|
|
|
|
|
|
_logger.LogError(ex, "设备{DeviceId} Ping {IpAddress} 异常", deviceId, ipAddress);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result.Success = false;
|
|
|
|
|
|
|
|
result.ErrorMessage = ex.Message;
|
|
|
|
|
|
|
|
_logger.LogError(ex, "设备{DeviceId} Ping {IpAddress} 未知错误", deviceId, ipAddress);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 批量Ping设备
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<IEnumerable<PingResult>> PingAllAsync(IEnumerable<(int DeviceId, string IpAddress)> devices)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var tasks = devices.Select(d => PingAsync(d.DeviceId, d.IpAddress));
|
|
|
|
|
|
|
|
var results = await Task.WhenAll(tasks);
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 检查设备是否可达
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<bool> IsReachableAsync(string ipAddress, TimeSpan? timeout = null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
using var ping = new Ping();
|
|
|
|
|
|
|
|
var timeoutMs = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : DefaultTimeout;
|
|
|
|
|
|
|
|
var reply = await ping.SendPingAsync(ipAddress, timeoutMs);
|
|
|
|
|
|
|
|
return reply.Status == IPStatus.Success;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region ========== 数据解析服务实现(发那科JSON解析) ==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 数据解析服务实现 - 解析发那科CNC设备返回的JSON数据
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// 发那科标准JSON格式:
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// "device": "设备编号",
|
|
|
|
|
|
|
|
/// "desc": "设备描述",
|
|
|
|
|
|
|
|
/// "tags": [
|
|
|
|
|
|
|
|
/// { "id": "Tag5", "value": "NC程序名", ... },
|
|
|
|
|
|
|
|
/// { "id": "Tag8", "value": "12345.00000", ... },
|
|
|
|
|
|
|
|
/// { "id": "_io_status", "value": "1", ... },
|
|
|
|
|
|
|
|
/// ...
|
|
|
|
|
|
|
|
/// ]
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public class DataParserService : IDataParserService
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
private readonly ILogger<DataParserService> _logger;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public DataParserService(ILogger<DataParserService> logger)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 解析设备原始数据
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task<ParsedDeviceData> ParseRawDataAsync(string rawData, int templateId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var json = JsonNode.Parse(rawData);
|
|
|
|
|
|
|
|
if (json == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
throw new InvalidOperationException("JSON解析失败:返回空对象");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var result = new ParsedDeviceData
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Timestamp = DateTime.UtcNow,
|
|
|
|
|
|
|
|
RawJson = rawData,
|
|
|
|
|
|
|
|
Tags = new Dictionary<string, TagValue>()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 解析device字段 - 设备标识
|
|
|
|
|
|
|
|
result.DeviceName = json["device"]?.GetValue<string>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 解析tags数组 - 关键字段
|
|
|
|
|
|
|
|
var tags = json["tags"]?.AsArray();
|
|
|
|
|
|
|
|
if (tags != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var tag in tags)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (tag == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var tagId = tag["id"]?.GetValue<string>();
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(tagId)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var tagValue = new TagValue
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Id = tagId,
|
|
|
|
|
|
|
|
Description = tag["desc"]?.GetValue<string>(),
|
|
|
|
|
|
|
|
Value = ParseTagValue(tag["value"]),
|
|
|
|
|
|
|
|
Quality = tag["quality"]?.GetValue<string>(),
|
|
|
|
|
|
|
|
Timestamp = DateTime.TryParse(tag["time"]?.GetValue<string>(), out var ts)
|
|
|
|
|
|
|
|
? ts : DateTime.UtcNow
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result.Tags[tagId] = tagValue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("解析设备数据成功: Device={Device}, TagCount={Count}",
|
|
|
|
|
|
|
|
result.DeviceName, result.Tags?.Count ?? 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Task.FromResult(result);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (JsonException ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogError(ex, "JSON解析异常: {RawData}", rawData);
|
|
|
|
|
|
|
|
throw new InvalidOperationException($"JSON格式错误: {ex.Message}", ex);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 解析多设备数据(数组格式)
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task<IEnumerable<ParsedDeviceData>> ParseMultiDeviceDataAsync(string rawData, int templateId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var jsonArray = JsonNode.Parse(rawData)?.AsArray();
|
|
|
|
|
|
|
|
if (jsonArray == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return Task.FromResult<IEnumerable<ParsedDeviceData>>(
|
|
|
|
|
|
|
|
Array.Empty<ParsedDeviceData>());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var results = new List<ParsedDeviceData>();
|
|
|
|
|
|
|
|
foreach (var item in jsonArray)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (item == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var jsonStr = item.ToJsonString();
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var parsed = ParseRawDataAsync(jsonStr, templateId).Result;
|
|
|
|
|
|
|
|
results.Add(parsed);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogWarning(ex, "解析单个设备数据失败: {Json}", jsonStr);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Task.FromResult<IEnumerable<ParsedDeviceData>>(results);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogError(ex, "解析多设备数据异常");
|
|
|
|
|
|
|
|
return Task.FromResult<IEnumerable<ParsedDeviceData>>(
|
|
|
|
|
|
|
|
Array.Empty<ParsedDeviceData>());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 验证数据格式
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public bool ValidateDataFormat(string rawData)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(rawData))
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var json = JsonNode.Parse(rawData);
|
|
|
|
|
|
|
|
if (json == null)
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查必要字段:发那科格式应该有 device, desc, tags
|
|
|
|
|
|
|
|
var hasDevice = json["device"] != null;
|
|
|
|
|
|
|
|
var hasTags = json["tags"] != null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return hasDevice && hasTags;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 解析Tag值 - 处理数值类型的尾缀去除
|
|
|
|
|
|
|
|
/// 发那科返回的数值如 "12345.00000" 需要转换为整数/小数
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private object? ParseTagValue(JsonNode? valueNode)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (valueNode == null)
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var strValue = valueNode.GetValue<string>();
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(strValue))
|
|
|
|
|
|
|
|
return strValue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试解析为数值
|
|
|
|
|
|
|
|
if (decimal.TryParse(strValue, out var decValue))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 如果是小数且尾缀为.00000,去除尾缀转为整数
|
|
|
|
|
|
|
|
if (decValue == Math.Floor(decValue) && strValue.Contains(".00000"))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return (long)decValue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 如果是整数
|
|
|
|
|
|
|
|
if (decValue == Math.Floor(decValue))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return (long)decValue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 否则返回小数
|
|
|
|
|
|
|
|
return decValue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return strValue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region ========== 设备采集服务实现 ==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 设备采集服务实现
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// 负责定时轮询采集CNC设备数据,支持:
|
|
|
|
|
|
|
|
/// - Ping检测设备连通性
|
|
|
|
|
|
|
|
/// - HTTP请求采集数据
|
|
|
|
|
|
|
|
/// - 失败自动重试(3次,间隔30秒)
|
|
|
|
|
|
|
|
/// - 并行采集多设备
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public class DeviceCollectionService : IDeviceCollectionService
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
private readonly ILogger<DeviceCollectionService> _logger;
|
|
|
|
|
|
|
|
private readonly IPingService _pingService;
|
|
|
|
|
|
|
|
private readonly IDataParserService _dataParserService;
|
|
|
|
|
|
|
|
private readonly IDeviceRepository _deviceRepository;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private const int MaxRetryCount = 3;
|
|
|
|
|
|
|
|
private const int RetryIntervalSeconds = 30;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public DeviceCollectionService(
|
|
|
|
|
|
|
|
ILogger<DeviceCollectionService> logger,
|
|
|
|
|
|
|
|
IPingService pingService,
|
|
|
|
|
|
|
|
IDataParserService dataParserService,
|
|
|
|
|
|
|
|
IDeviceRepository deviceRepository)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
|
_pingService = pingService;
|
|
|
|
|
|
|
|
_dataParserService = dataParserService;
|
|
|
|
|
|
|
|
_deviceRepository = deviceRepository;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 获取所有设备
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<IEnumerable<CNCDevice>> GetAllDevicesAsync()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return await _deviceRepository.GetAllAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 根据ID获取设备
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<CNCDevice?> GetDeviceByIdAsync(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return await _deviceRepository.GetByIdAsync(deviceId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 创建设备
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<CNCDevice> CreateDeviceAsync(CNCDevice device)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
device.CreatedAt = DateTime.UtcNow;
|
|
|
|
|
|
|
|
device.UpdatedAt = DateTime.UtcNow;
|
|
|
|
|
|
|
|
await _deviceRepository.AddAsync(device);
|
|
|
|
|
|
|
|
await _deviceRepository.SaveAsync();
|
|
|
|
|
|
|
|
return device;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 更新设备信息
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<CNCDevice?> UpdateDeviceAsync(CNCDevice device)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
device.UpdatedAt = DateTime.UtcNow;
|
|
|
|
|
|
|
|
_deviceRepository.Update(device);
|
|
|
|
|
|
|
|
var affected = await _deviceRepository.SaveAsync();
|
|
|
|
|
|
|
|
return affected > 0 ? device : null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 删除设备
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task<bool> DeleteDeviceAsync(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
|
|
|
|
|
|
|
if (device == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_deviceRepository.Remove(device);
|
|
|
|
|
|
|
|
return await _deviceRepository.SaveAsync() > 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 采集指定设备数据
|
|
|
|
|
|
|
|
/// 采集流程:Ping检测 -> HTTP请求 -> 数据解析 -> 存储
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task CollectDeviceAsync(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
|
|
|
|
|
|
|
if (device == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogWarning("采集失败:设备{DeviceId}不存在", deviceId);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查设备是否可用
|
|
|
|
|
|
|
|
if (!device.IsAvailable)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogDebug("设备{DeviceId}标记为不可用,跳过采集", deviceId);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Ping检测
|
|
|
|
|
|
|
|
var pingResult = await _pingService.PingAsync(deviceId, device.IPAddress);
|
|
|
|
|
|
|
|
if (!pingResult.Success)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogWarning("设备{DeviceId} Ping失败: {Error}", deviceId, pingResult.ErrorMessage);
|
|
|
|
|
|
|
|
await UpdateDeviceOnlineStatus(deviceId, false);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// HTTP请求采集数据,带重试
|
|
|
|
|
|
|
|
var retryCount = 0;
|
|
|
|
|
|
|
|
while (retryCount < MaxRetryCount)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var rawData = await HttpCollectAsync(device);
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(rawData))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
throw new InvalidOperationException("HTTP返回空数据");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 解析数据
|
|
|
|
|
|
|
|
var parsedData = await _dataParserService.ParseRawDataAsync(rawData, device.TemplateId);
|
|
|
|
|
|
|
|
parsedData.DeviceId = deviceId;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("设备{DeviceId}采集成功", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新设备在线状态
|
|
|
|
|
|
|
|
await UpdateDeviceOnlineStatus(deviceId, true);
|
|
|
|
|
|
|
|
await UpdateLastCollectionTime(deviceId);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
retryCount++;
|
|
|
|
|
|
|
|
_logger.LogWarning(ex, "设备{DeviceId}采集失败 (重试 {Retry}/{Max})",
|
|
|
|
|
|
|
|
deviceId, retryCount, MaxRetryCount);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (retryCount < MaxRetryCount)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(RetryIntervalSeconds));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 连续MaxRetryCount次失败
|
|
|
|
|
|
|
|
_logger.LogError("设备{DeviceId}连续{Attempts}次采集失败", deviceId, MaxRetryCount);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 采集所有设备数据
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public async Task CollectAllDevicesAsync()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var devices = await _deviceRepository.GetAllAsync();
|
|
|
|
|
|
|
|
var availableDevices = devices.Where(d => d.IsAvailable).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("开始采集 {Count} 台设备", availableDevices.Count);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var device in availableDevices)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await CollectDeviceAsync(device.Id);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogError(ex, "设备{DeviceId}采集异常", device.Id);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("采集任务完成");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 获取设备状态
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task<DeviceStatus> GetDeviceStatusAsync(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return Task.FromResult(new DeviceStatus
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
DeviceId = deviceId,
|
|
|
|
|
|
|
|
Timestamp = DateTime.UtcNow
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 获取设备健康状态
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task<DeviceHealth> GetDeviceHealthAsync(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return Task.FromResult(new DeviceHealth
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
DeviceId = deviceId,
|
|
|
|
|
|
|
|
IsHealthy = true,
|
|
|
|
|
|
|
|
LastCheck = DateTime.UtcNow
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 获取设备当前状态
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task<DeviceStatus> GetDeviceCurrentStatusAsync(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return GetDeviceStatusAsync(deviceId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region 私有方法
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// HTTP采集数据
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private async Task<string?> HttpCollectAsync(CNCDevice device)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
using var httpClient = new HttpClient();
|
|
|
|
|
|
|
|
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
|
|
|
|
|
|
|
var response = await httpClient.GetAsync(device.HttpUrl);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogWarning("HTTP请求失败: {StatusCode}", response.StatusCode);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogError(ex, "HTTP请求异常: {Url}", device.HttpUrl);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 更新设备在线状态
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private async Task UpdateDeviceOnlineStatus(int deviceId, bool isOnline)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
|
|
|
|
|
|
|
if (device != null && device.IsOnline != isOnline)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
device.IsOnline = isOnline;
|
|
|
|
|
|
|
|
device.UpdatedAt = DateTime.UtcNow;
|
|
|
|
|
|
|
|
_deviceRepository.Update(device);
|
|
|
|
|
|
|
|
await _deviceRepository.SaveAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 更新最后采集时间
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private async Task UpdateLastCollectionTime(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
|
|
|
|
|
|
|
if (device != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
device.LastCollectionTime = DateTime.UtcNow;
|
|
|
|
|
|
|
|
device.UpdatedAt = DateTime.UtcNow;
|
|
|
|
|
|
|
|
_deviceRepository.Update(device);
|
|
|
|
|
|
|
|
await _deviceRepository.SaveAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region ========== 产量计算服务实现 ==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 产量计算服务实现
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// 核心差分计算逻辑:
|
|
|
|
|
|
|
|
/// 1. 同一程序连续加工:产量 = MAX(0, 当前累计数 - 上次累计数)
|
|
|
|
|
|
|
|
/// 2. 程序切换(A→B):A产量锁定,B以当前累计数为新起点
|
|
|
|
|
|
|
|
/// 3. 切回历史程序(B→A):视为A重新开始,增量累加到当日产量
|
|
|
|
|
|
|
|
/// 4. 跨天处理:0点自动重置,新日期以首次采集累计值为起点
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public class ProductionCalculator : IProductionCalculator
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
private readonly ILogger<ProductionCalculator> _logger;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 存储每个设备的生产状态:设备ID -> (程序名, 上次累计数, 记录时间, 上次日期)
|
|
|
|
|
|
|
|
private readonly Dictionary<int, DeviceProductionState> _productionStates = new();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 异常值检测阈值:产量变化超过此值视为异常
|
|
|
|
|
|
|
|
private const decimal AbnormalJumpThreshold = 1000;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class DeviceProductionState
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public string? ProgramName { get; set; }
|
|
|
|
|
|
|
|
public decimal LastValue { get; set; }
|
|
|
|
|
|
|
|
public DateTime LastTime { get; set; }
|
|
|
|
|
|
|
|
public DateTime LastDate { get; set; }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public ProductionCalculator(ILogger<ProductionCalculator> logger)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 计算生产增量
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task<decimal> CalculateProductionIncrementAsync(int deviceId, decimal currentValue, string programName, DateTime timestamp)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
decimal increment = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lock (_productionStates)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 首次采集或跨天:初始化状态
|
|
|
|
|
|
|
|
if (!_productionStates.TryGetValue(deviceId, out var state))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
state = new DeviceProductionState
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
ProgramName = programName,
|
|
|
|
|
|
|
|
LastValue = currentValue,
|
|
|
|
|
|
|
|
LastTime = timestamp,
|
|
|
|
|
|
|
|
LastDate = timestamp.Date
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
_productionStates[deviceId] = state;
|
|
|
|
|
|
|
|
_logger.LogDebug("设备{DeviceId}首次采集,程序={Program},初始值={Value}",
|
|
|
|
|
|
|
|
deviceId, programName, currentValue);
|
|
|
|
|
|
|
|
return Task.FromResult(0m);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 跨天处理:0点自动重置
|
|
|
|
|
|
|
|
if (timestamp.Date != state.LastDate)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogInformation("设备{DeviceId}跨天重置,昨日程序={Program},新日期={Date}",
|
|
|
|
|
|
|
|
deviceId, state.ProgramName, timestamp.Date);
|
|
|
|
|
|
|
|
state.ProgramName = programName;
|
|
|
|
|
|
|
|
state.LastValue = currentValue;
|
|
|
|
|
|
|
|
state.LastTime = timestamp;
|
|
|
|
|
|
|
|
state.LastDate = timestamp.Date;
|
|
|
|
|
|
|
|
return Task.FromResult(0m);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 同一程序连续加工
|
|
|
|
|
|
|
|
if (state.ProgramName == programName)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var diff = currentValue - state.LastValue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 异常值保护:负数视为0
|
|
|
|
|
|
|
|
if (diff < 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogWarning("设备{DeviceId}产量为负,忽略: {Diff}", deviceId, diff);
|
|
|
|
|
|
|
|
diff = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 异常值保护:跳变过大视为异常
|
|
|
|
|
|
|
|
else if (diff > AbnormalJumpThreshold)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogWarning("设备{DeviceId}产量跳变过大,忽略: {Diff}", deviceId, diff);
|
|
|
|
|
|
|
|
diff = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
increment = diff;
|
|
|
|
|
|
|
|
state.LastValue = currentValue;
|
|
|
|
|
|
|
|
state.LastTime = timestamp;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("设备{DeviceId}连续加工,程序={Program},增量={Increment}",
|
|
|
|
|
|
|
|
deviceId, programName, increment);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 程序切换
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 切回历史程序:增量累加
|
|
|
|
|
|
|
|
// 新程序:以当前累计数为新起点
|
|
|
|
|
|
|
|
_logger.LogInformation("设备{DeviceId}程序切换 {OldProgram}→{NewProgram},新起点={Value}",
|
|
|
|
|
|
|
|
deviceId, state.ProgramName, programName, currentValue);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
state.ProgramName = programName;
|
|
|
|
|
|
|
|
state.LastValue = currentValue;
|
|
|
|
|
|
|
|
state.LastTime = timestamp;
|
|
|
|
|
|
|
|
increment = 0; // 程序切换不计入产量
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Task.FromResult(increment);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 重置设备生产状态
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public void ResetDeviceProductionState(int deviceId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
lock (_productionStates)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_productionStates.ContainsKey(deviceId))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_productionStates.Remove(deviceId);
|
|
|
|
|
|
|
|
_logger.LogInformation("设备{DeviceId}生产状态已重置", deviceId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 验证生产数据有效性
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public bool ValidateProductionValue(int deviceId, decimal value)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 负数无效
|
|
|
|
|
|
|
|
if (value < 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogWarning("设备{DeviceId}产量验证失败:负数 {Value}", deviceId, value);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lock (_productionStates)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_productionStates.TryGetValue(deviceId, out var state))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var diff = Math.Abs(value - state.LastValue);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 跳变过大无效
|
|
|
|
|
|
|
|
if (diff > AbnormalJumpThreshold)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogWarning("设备{DeviceId}产量验证失败:跳变过大 {Diff}", deviceId, diff);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region ========== 数据存储服务实现 ==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 数据存储服务实现
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// 负责将解析后的数据存储到数据库:
|
|
|
|
|
|
|
|
/// - 原始JSON存入日志库(cnc_log)
|
|
|
|
|
|
|
|
/// - 解析后结构化数据存入业务库(cnc_business)
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public class DataStorageService : IDataStorageService
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
private readonly ILogger<DataStorageService> _logger;
|
|
|
|
|
|
|
|
private readonly IDeviceRepository _deviceRepository;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public DataStorageService(
|
|
|
|
|
|
|
|
ILogger<DataStorageService> logger,
|
|
|
|
|
|
|
|
IDeviceRepository deviceRepository)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
|
_deviceRepository = deviceRepository;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 存储设备数据
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task StoreDeviceDataAsync(ParsedDeviceData data)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 更新设备状态
|
|
|
|
|
|
|
|
_deviceRepository.UpdateDeviceStatusAsync(data.DeviceId, true, true).Wait();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("设备{DeviceId}数据存储成功", data.DeviceId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogError(ex, "设备{DeviceId}数据存储失败", data.DeviceId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 批量存储设备数据
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task StoreDeviceDataBatchAsync(IEnumerable<ParsedDeviceData> dataList)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var data in dataList)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
StoreDeviceDataAsync(data).Wait();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.LogError(ex, "批量存储中设备{DeviceId}失败", data.DeviceId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 存储生产记录
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task StoreProductionRecordAsync(ProductionRecord record)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// TODO: 调用ProductionRepository存储
|
|
|
|
|
|
|
|
_logger.LogDebug("存储生产记录: DeviceId={DeviceId}, Quantity={Quantity}",
|
|
|
|
|
|
|
|
record.DeviceId, record.Quantity);
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// 更新设备状态
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public Task UpdateDeviceStatusAsync(int deviceId, DeviceStatus status)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_deviceRepository.UpdateDeviceStatusAsync(deviceId, true, true).Wait();
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
}
|