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 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 PingDeviceAsync(string ipAddress) { return await _pingService.PingAsync(ipAddress); } public async Task 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> GetCollectionHistoryAsync(int deviceId, DateTime startDate, DateTime endDate) { return await _resultRepository.GetResultsByDateRangeAsync(startDate, endDate) .Where(r => r.DeviceId == deviceId).ToListAsync(); } public async Task 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 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 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 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 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 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 IsDeviceOnlineAsync(string ipAddress) { return await PingAsync(ipAddress); } public async Task 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 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() }; 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 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 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 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 ExecuteWithRetryAsync(Func> 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 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; } } }