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#
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;
|
|
}
|
|
}
|
|
} |