You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

707 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Haoliang.Models.Device;
using Haoliang.Models.DataCollection;
using Haoliang.Data.Repositories;
using Haoliang.Core.Services;
namespace Haoliang.Core.Services
{
public class DeviceCollectionService : IDeviceCollectionService
{
private readonly IDeviceRepository _deviceRepository;
private readonly ICollectionTaskRepository _taskRepository;
private readonly ICollectionResultRepository _resultRepository;
private readonly ICollectionLogRepository _logRepository;
private readonly IPingService _pingService;
private readonly IDataParserService _dataParserService;
private readonly IDataStorageService _dataStorageService;
private readonly IRetryService _retryService;
private readonly HttpClient _httpClient;
public DeviceCollectionService(
IDeviceRepository deviceRepository,
ICollectionTaskRepository taskRepository,
ICollectionResultRepository resultRepository,
ICollectionLogRepository logRepository,
IPingService pingService,
IDataParserService dataParserService,
IDataStorageService dataStorageService,
IRetryService retryService)
{
_deviceRepository = deviceRepository;
_taskRepository = taskRepository;
_resultRepository = resultRepository;
_logRepository = logRepository;
_pingService = pingService;
_dataParserService = dataParserService;
_dataStorageService = dataStorageService;
_retryService = retryService;
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}
public async Task CollectAllDevicesAsync()
{
var devices = await _deviceRepository.GetAllAsync();
var onlineDevices = devices.Where(d => d.IsOnline && d.IsAvailable).ToList();
foreach (var device in onlineDevices)
{
try
{
await CollectDeviceAsync(device.Id);
}
catch (Exception ex)
{
await LogCollectionAsync(device.Id, LogLevel.Error,
$"Failed to collect from device {device.DeviceCode}: {ex.Message}");
}
}
}
public async Task CollectDeviceAsync(int deviceId)
{
var device = await _deviceRepository.GetByIdAsync(deviceId);
if (device == null)
throw new CollectionException(deviceId, "", "Device not found", CollectionErrorType.Unknown);
if (!device.IsOnline || !device.IsAvailable)
{
throw new CollectionException(deviceId, device.DeviceCode,
"Device is not online or not available", CollectionErrorType.DeviceOffline);
}
var taskId = await CreateCollectionTaskAsync(device);
try
{
await _retryService.ExecuteWithRetryAsync(async () =>
{
var result = await CollectDeviceDataAsync(deviceId);
await ProcessCollectedDataAsync(result);
await MarkTaskCompletedAsync(taskId, true);
return result;
}, maxRetries: 3, delayMs: 30000);
}
catch (Exception ex)
{
await MarkTaskCompletedAsync(taskId, false, ex.Message);
throw;
}
}
public async Task<DeviceCurrentStatus> CollectDeviceDataAsync(int deviceId)
{
var device = await _deviceRepository.GetByIdAsync(deviceId);
if (device == null)
throw new CollectionException(deviceId, "", "Device not found", CollectionErrorType.Unknown);
try
{
await _pingService.PingAsync(device.IPAddress);
var rawJson = await GetDeviceDataAsync(device.HttpUrl);
var result = await _dataParserService.ParseDeviceDataAsync(rawJson, deviceId);
return result;
}
catch (Exception ex)
{
throw new CollectionException(deviceId, device.DeviceCode,
ex.Message, CollectionErrorType.NetworkError);
}
}
public async Task<bool> PingDeviceAsync(string ipAddress)
{
return await _pingService.PingAsync(ipAddress);
}
public async Task<string> GetDeviceDataAsync(string httpUrl)
{
try
{
var response = await _httpClient.GetAsync(httpUrl);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return content;
}
catch (HttpRequestException ex)
{
throw new Exception($"HTTP request failed: {ex.Message}", ex);
}
catch (Exception ex)
{
throw new Exception($"Failed to get device data: {ex.Message}", ex);
}
}
public async Task ProcessCollectedDataAsync(CollectionResult result)
{
if (result.IsSuccess && result.ParsedData != null)
{
await _dataStorageService.SaveDeviceStatusAsync(result.ParsedData);
if (await _dataParserService.ValidateDeviceDataAsync(result.ParsedData))
{
await UpdateDeviceStatusAsync(result.ParsedData);
}
else
{
await LogCollectionAsync(result.DeviceId, LogLevel.Warning,
"Device data validation failed", JsonSerializer.Serialize(result.ParsedData));
}
}
else
{
await _dataStorageService.LogCollectionAsync(result.DeviceId, LogLevel.Error,
"Collection failed", result.ErrorMessage);
}
}
public async Task<IEnumerable<CollectionResult>> GetCollectionHistoryAsync(int deviceId, DateTime startDate, DateTime endDate)
{
return await _resultRepository.GetResultsByDateRangeAsync(startDate, endDate)
.Where(r => r.DeviceId == deviceId).ToListAsync();
}
public async Task<CollectionStatistics> GetCollectionStatisticsAsync(DateTime date)
{
var startOfDay = date.Date;
var endOfDay = startOfDay.AddDays(1);
var results = await _resultRepository.GetResultsByDateRangeAsync(startOfDay, endOfDay);
var stats = new CollectionStatistics
{
Date = date,
TotalAttempts = results.Count(),
SuccessCount = results.Count(r => r.IsSuccess),
FailedCount = results.Count(r => !r.IsSuccess),
SuccessRate = results.Any() ? (decimal)results.Count(r => r.IsSuccess) / results.Count() * 100 : 0,
DeviceCount = results.Select(r => r.DeviceId).Distinct().Count(),
OnlineDeviceCount = await _deviceRepository.CountOnlineDevicesAsync(),
TotalDataSize = results.Sum(r => r.DataSize ?? 0)
};
if (stats.SuccessCount > 0)
{
var successfulResults = results.Where(r => r.IsSuccess).ToList();
stats.AverageResponseTime = TimeSpan.FromTicks(
(long)successfulResults.Average(r => r.ResponseTime ?? 0));
}
return stats;
}
public async Task<CollectionHealth> GetCollectionHealthAsync()
{
var stats = await GetCollectionStatisticsAsync(DateTime.Now);
var onlineDeviceCount = await _deviceRepository.CountOnlineDevicesAsync();
var availableDeviceCount = await _deviceRepository.CountAvailableDevicesAsync();
var activeTasks = await _taskRepository.GetRunningTasksAsync();
var failedTasks = await _taskRepository.GetFailedTasksAsync();
var lastSuccessful = await _resultRepository.GetResultsByDateRangeAsync(
DateTime.Now.AddDays(-1), DateTime.Now)
.Where(r => r.IsSuccess).FirstOrDefault();
var lastFailed = await _resultRepository.GetResultsByDateRangeAsync(
DateTime.Now.AddDays(-1), DateTime.Now)
.Where(r => !r.IsSuccess).FirstOrDefault();
return new CollectionHealth
{
CheckTime = DateTime.Now,
TotalDevices = onlineDeviceCount,
OnlineDevices = availableDeviceCount,
ActiveCollectionTasks = activeTasks.Count(),
FailedTasks = failedTasks.Count(),
SuccessRate = stats.SuccessRate,
AverageResponseTime = stats.AverageResponseTime,
TotalCollectedData = stats.TotalDataSize,
LastSuccessfulCollection = lastSuccessful?.CollectionTime ?? DateTime.MinValue,
LastFailedCollection = lastFailed?.CollectionTime ?? DateTime.MinValue
};
}
public async Task RestartFailedCollectionsAsync()
{
var failedTasks = await _taskRepository.GetFailedTasksAsync();
foreach (var task in failedTasks)
{
try
{
await CollectDeviceAsync(task.DeviceId);
}
catch (Exception ex)
{
await LogCollectionAsync(task.DeviceId, LogLevel.Error,
$"Failed to restart collection for device {task.DeviceId}: {ex.Message}");
}
}
}
public async Task<bool> TestConnectionAsync(int deviceId)
{
try
{
var device = await _deviceRepository.GetByIdAsync(deviceId);
if (device == null)
return false;
var isOnline = await _pingService.PingAsync(device.IPAddress);
if (!isOnline)
return false;
var data = await GetDeviceDataAsync(device.HttpUrl);
return !string.IsNullOrEmpty(data);
}
catch
{
return false;
}
}
private async Task<int> CreateCollectionTaskAsync(CNCDevice device)
{
var task = new CollectionTask
{
DeviceId = device.Id,
TaskName = $"Collection_{device.DeviceCode}_{DateTime.Now:yyyyMMddHHmmss}",
Status = "Pending",
ScheduledTime = DateTime.Now,
CreatedAt = DateTime.Now
};
await _taskRepository.AddAsync(task);
await _taskRepository.SaveAsync();
return task.Id;
}
private async Task MarkTaskCompletedAsync(int taskId, bool isSuccess, string errorMessage = null)
{
await _taskRepository.MarkTaskCompletedAsync(taskId, isSuccess, errorMessage);
}
private async Task UpdateDeviceStatusAsync(DeviceCurrentStatus status)
{
var device = await _deviceRepository.GetByIdAsync(status.DeviceId);
if (device != null)
{
device.IsOnline = true;
device.LastCollectionTime = status.RecordTime;
await _deviceRepository.SaveAsync();
}
}
private async Task LogCollectionAsync(int deviceId, LogLevel logLevel, string message, string data = null)
{
await _dataStorageService.LogCollectionAsync(deviceId, logLevel, message, data);
}
}
public class PingService : IPingService
{
public async Task<bool> PingAsync(string ipAddress)
{
try
{
using (var client = new System.Net.NetworkInformation.Ping())
{
var reply = await client.SendPingAsync(ipAddress, 3000);
return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
}
}
catch
{
return false;
}
}
public async Task<int> GetPingTimeAsync(string ipAddress)
{
try
{
using (var client = new System.Net.NetworkInformation.Ping())
{
var reply = await client.SendPingAsync(ipAddress, 3000);
return reply.Status == System.Net.NetworkInformation.IPStatus.Success ?
reply.RoundtripTime : -1;
}
}
catch
{
return -1;
}
}
public async Task<bool> IsDeviceOnlineAsync(string ipAddress)
{
return await PingAsync(ipAddress);
}
public async Task<PingResult> GetPingResultAsync(string ipAddress)
{
try
{
using (var client = new System.Net.NetworkInformation.Ping())
{
var reply = await client.SendPingAsync(ipAddress, 3000);
return new PingResult
{
IsSuccess = reply.Status == System.Net.NetworkInformation.IPStatus.Success,
PingTime = reply.RoundtripTime,
PingTime = DateTime.Now
};
}
}
catch (Exception ex)
{
return new PingResult
{
IsSuccess = false,
ErrorMessage = ex.Message,
PingTime = DateTime.Now
};
}
}
}
public class DataParserService : IDataParserService
{
public async Task<DeviceCurrentStatus> ParseDeviceDataAsync(string rawJson, int deviceId)
{
try
{
var document = JsonDocument.Parse(rawJson);
var root = document.RootElement;
var device = new DeviceCurrentStatus
{
DeviceId = deviceId,
RecordTime = DateTime.Now,
Tags = new List<TagData>()
};
if (root.TryGetProperty("device", out var deviceElement))
{
device.DeviceCode = deviceElement.GetString();
}
if (root.TryGetProperty("desc", out var descElement))
{
device.DeviceName = descElement.GetString();
}
if (root.TryGetProperty("tags", out var tagsElement))
{
foreach (var tagElement in tagsElement.EnumerateArray())
{
var tag = new TagData
{
Id = tagElement.GetProperty("id").GetString(),
Desc = tagElement.GetProperty("desc").GetString(),
Quality = tagElement.GetProperty("quality").GetString(),
Time = DateTime.Parse(tagElement.GetProperty("time").GetString())
};
if (tagElement.TryGetProperty("value", out var valueElement))
{
tag.Value = ParseTagValue(valueElement);
}
device.Tags.Add(tag);
}
}
ExtractDeviceStatus(device);
return device;
}
catch (Exception ex)
{
throw new Exception($"Failed to parse device data: {ex.Message}", ex);
}
}
private void ExtractDeviceStatus(DeviceCurrentStatus device)
{
var ioStatus = device.Tags?.FirstOrDefault(t => t.Id == "_io_status");
var tag9 = device.Tags?.FirstOrDefault(t => t.Id == "Tag9");
var tag26 = device.Tags?.FirstOrDefault(t => t.Id == "Tag26");
device.Status = "Unknown";
device.IsRunning = false;
if (ioStatus?.Value?.ToString() == "1" || tag9?.Value?.ToString() == "1" || tag26?.Value?.ToString() == "1")
{
device.Status = "Running";
device.IsRunning = true;
}
else
{
device.Status = "Stopped";
device.IsRunning = false;
}
var ncProgram = device.Tags?.FirstOrDefault(t => t.Id == "Tag5");
device.NCProgram = ncProgram?.Value?.ToString();
var cumulativeCount = device.Tags?.FirstOrDefault(t => t.Id == "Tag8");
if (cumulativeCount?.Value != null)
{
if (int.TryParse(cumulativeCount.Value.ToString(), out int count))
{
device.CumulativeCount = count;
}
}
var operatingMode = device.Tags?.FirstOrDefault(t => t.Id == "Tag11");
device.OperatingMode = operatingMode?.Value?.ToString();
}
private object ParseTagValue(JsonElement valueElement)
{
if (valueElement.ValueKind == JsonValueKind.String)
{
return valueElement.GetString();
}
else if (valueElement.ValueKind == JsonValueKind.Number)
{
if (valueElement.TryGetInt32(out int intValue))
return intValue;
else if (valueElement.TryGetDecimal(out decimal decimalValue))
return decimalValue;
else
return valueElement.GetDouble();
}
else if (valueElement.ValueKind == JsonValueKind.True || valueElement.ValueKind == JsonValueKind.False)
{
return valueElement.GetBoolean();
}
else
{
return valueElement.ToString();
}
}
public async Task<TagData> ParseTagDataAsync(object tagValue, string dataType)
{
var tag = new TagData();
switch (dataType.ToLower())
{
case "int":
if (int.TryParse(tagValue?.ToString(), out int intValue))
tag.Value = intValue;
break;
case "decimal":
if (decimal.TryParse(tagValue?.ToString(), out decimal decimalValue))
tag.Value = decimalValue;
break;
case "bool":
if (bool.TryParse(tagValue?.ToString(), out bool boolValue))
tag.Value = boolValue;
break;
default:
tag.Value = tagValue?.ToString();
break;
}
return tag;
}
public async Task<bool> ValidateDeviceDataAsync(DeviceCurrentStatus data)
{
if (data == null)
return false;
if (string.IsNullOrEmpty(data.DeviceCode))
return false;
if (data.Tags == null || !data.Tags.Any())
return false;
if (data.CumulativeCount < 0)
return false;
return true;
}
public async Task<string> ConvertDataFormatAsync(object value, string dataType)
{
switch (dataType.ToLower())
{
case "int":
if (int.TryParse(value?.ToString(), out int intValue))
return intValue.ToString();
break;
case "decimal":
if (decimal.TryParse(value?.ToString(), out decimal decimalValue))
return decimalValue.ToString("F2");
break;
case "bool":
if (bool.TryParse(value?.ToString(), out bool boolValue))
return boolValue.ToString();
break;
default:
return value?.ToString() ?? "";
}
return "";
}
}
public class DataStorageService : IDataStorageService
{
private readonly ICollectionResultRepository _resultRepository;
private readonly IDeviceStatusRepository _deviceStatusRepository;
private readonly ICollectionLogRepository _logRepository;
public DataStorageService(
ICollectionResultRepository resultRepository,
IDeviceStatusRepository deviceStatusRepository,
ICollectionLogRepository logRepository)
{
_resultRepository = resultRepository;
_deviceStatusRepository = deviceStatusRepository;
_logRepository = logRepository;
}
public async Task SaveCollectionResultAsync(CollectionResult result)
{
await _resultRepository.AddAsync(result);
await _resultRepository.SaveAsync();
}
public async Task SaveDeviceStatusAsync(DeviceCurrentStatus status)
{
var deviceStatus = new DeviceStatus
{
DeviceId = status.DeviceId,
Status = status.Status,
IsRunning = status.IsRunning,
NCProgram = status.NCProgram,
CumulativeCount = status.CumulativeCount,
OperatingMode = status.OperatingMode,
RecordTime = status.RecordTime
};
await _deviceStatusRepository.AddAsync(deviceStatus);
await _deviceStatusRepository.SaveAsync();
}
public async Task SaveRawDataAsync(int deviceId, string rawJson, bool isSuccess, string errorMessage = null)
{
var result = new CollectionResult
{
DeviceId = deviceId,
RawJson = rawJson,
IsSuccess = isSuccess,
ErrorMessage = errorMessage,
CollectionTime = DateTime.Now,
CreatedAt = DateTime.Now
};
await _resultRepository.AddAsync(result);
await _resultRepository.SaveAsync();
}
public async Task LogCollectionAsync(int deviceId, LogLevel logLevel, string message, string data = null)
{
var log = new CollectionLog
{
DeviceId = deviceId,
LogLevel = logLevel.ToString(),
LogCategory = "Collection",
LogMessage = message,
LogData = data,
LogTime = DateTime.Now,
CreatedAt = DateTime.Now
};
await _logRepository.AddAsync(log);
await _logRepository.SaveAsync();
}
public async Task ArchiveOldDataAsync(int daysToKeep = 30)
{
await _resultRepository.DeleteOldResultsAsync(daysToKeep);
await _logRepository.DeleteOldLogsAsync(daysToKeep);
}
}
public class RetryService : IRetryService
{
public async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3, int delayMs = 30000)
{
Exception lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await operation();
}
catch (Exception ex) when (attempt < maxRetries)
{
lastException = ex;
if (ShouldRetry(ex, attempt))
{
await Task.Delay(delayMs);
}
else
{
throw;
}
}
}
throw lastException ?? new Exception("Operation failed after retries");
}
public async Task ExecuteWithRetryAsync(Func<Task> operation, int maxRetries = 3, int delayMs = 30000)
{
await ExecuteWithRetryAsync(async () =>
{
await operation();
return true;
}, maxRetries, delayMs);
}
public bool ShouldRetry(Exception ex, int attemptNumber)
{
// Retry on network-related exceptions
if (ex is HttpRequestException ||
ex is System.Net.Sockets.SocketException ||
ex is System.TimeoutException)
{
return true;
}
// Retry on certain specific exceptions
if (ex.Message.Contains("timeout") ||
ex.Message.Contains("network") ||
ex.Message.Contains("connection"))
{
return true;
}
// Don't retry after max attempts
if (attemptNumber >= 3)
{
return false;
}
return false;
}
}
}