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.

552 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Haoliang.Models.System;
using Haoliang.Models.Device;
using Haoliang.Data.Repositories;
namespace Haoliang.Core.Services
{
public interface IAlarmNotificationService
{
Task SendAlarmNotificationAsync(Alarm alarm);
Task SendBulkAlarmNotificationsAsync(IEnumerable<Alarm> alarms);
Task<bool> SendSmsNotificationAsync(string phoneNumber, string message);
Task<bool> SendEmailNotificationAsync(string email, string subject, string message);
Task<bool> SendWechatNotificationAsync(string openId, string message);
Task<IEnumerable<AlarmNotification>> GetNotificationHistoryAsync(DateTime startDate, DateTime endDate);
Task<bool> ConfigureNotificationChannelAsync(NotificationChannel channel);
Task<IEnumerable<NotificationChannel>> GetAvailableChannelsAsync();
Task TestNotificationChannelAsync(NotificationChannel channel);
Task<int> GetFailedNotificationCountAsync(DateTime date);
Task<bool> ResendFailedNotificationsAsync(int maxRetries = 3);
}
public class AlarmNotificationService : IAlarmNotificationService
{
private readonly IAlarmNotificationRepository _notificationRepository;
private readonly ISystemConfigRepository _configRepository;
private readonly ILoggingService _loggingService;
private readonly INotificationSender _smsSender;
private readonly INotificationSender _emailSender;
private readonly INotificationSender _wechatSender;
private readonly ConcurrentDictionary<string, NotificationChannel> _channels;
public AlarmNotificationService(
IAlarmNotificationRepository notificationRepository,
ISystemConfigRepository configRepository,
ILoggingService loggingService,
INotificationSender smsSender,
INotificationSender emailSender,
INotificationSender wechatSender)
{
_notificationRepository = notificationRepository;
_configRepository = configRepository;
_loggingService = loggingService;
_smsSender = smsSender;
_emailSender = emailSender;
_wechatSender = wechatSender;
_channels = new ConcurrentDictionary<string, NotificationChannel>();
}
public async Task SendAlarmNotificationAsync(Alarm alarm)
{
if (alarm == null)
throw new ArgumentNullException(nameof(alarm));
if (!alarm.IsActive)
{
await _loggingService.LogDebugAsync($"Skipping notification for inactive alarm {alarm.AlarmId}");
return;
}
// Get notification channels for this alarm
var channels = await GetNotificationChannelsForAlarm(alarm);
foreach (var channel in channels)
{
try
{
await SendNotificationViaChannelAsync(alarm, channel);
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Failed to send alarm {alarm.AlarmId} via {channel.ChannelType}: {ex.Message}", ex);
}
}
}
public async Task SendBulkAlarmNotificationsAsync(IEnumerable<Alarm> alarms)
{
if (alarms == null)
throw new ArgumentNullException(nameof(alarms));
var alarmList = alarms.ToList();
await _loggingService.LogInformationAsync($"Processing bulk notifications for {alarmList.Count} alarms");
var notificationTasks = alarmList.Select(alarm => SendAlarmNotificationAsync(alarm));
await Task.WhenAll(notificationTasks);
await _loggingService.LogInformationAsync($"Completed bulk notifications for {alarmList.Count} alarms");
}
public async Task<bool> SendSmsNotificationAsync(string phoneNumber, string message)
{
try
{
var smsConfig = await GetSmsConfigurationAsync();
if (!smsConfig.IsEnabled)
{
await _loggingService.LogWarningAsync("SMS notifications are disabled");
return false;
}
var result = await _smsSender.SendAsync(phoneNumber, message, smsConfig);
if (result.Success)
{
await _loggingService.LogInformationAsync($"SMS sent successfully to {phoneNumber}");
}
else
{
await _loggingService.LogErrorAsync($"SMS failed to {phoneNumber}: {result.ErrorMessage}");
}
return result.Success;
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"SMS notification error: {ex.Message}", ex);
return false;
}
}
public async Task<bool> SendEmailNotificationAsync(string email, string subject, string message)
{
try
{
var emailConfig = await GetEmailConfigurationAsync();
if (!emailConfig.IsEnabled)
{
await _loggingService.LogWarningAsync("Email notifications are disabled");
return false;
}
var result = await _emailSender.SendAsync(email, subject, message, emailConfig);
if (result.Success)
{
await _loggingService.LogInformationAsync($"Email sent successfully to {email}");
}
else
{
await _loggingService.LogErrorAsync($"Email failed to {email}: {result.ErrorMessage}");
}
return result.Success;
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Email notification error: {ex.Message}", ex);
return false;
}
}
public async Task<bool> SendWechatNotificationAsync(string openId, string message)
{
try
{
var wechatConfig = await GetWechatConfigurationAsync();
if (!wechatConfig.IsEnabled)
{
await _loggingService.LogWarningAsync("WeChat notifications are disabled");
return false;
}
var result = await _wechatSender.SendAsync(openId, message, wechatConfig);
if (result.Success)
{
await _loggingService.LogInformationAsync($"WeChat message sent successfully to {openId}");
}
else
{
await _loggingService.LogErrorAsync($"WeChat message failed to {openId}: {result.ErrorMessage}");
}
return result.Success;
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"WeChat notification error: {ex.Message}", ex);
return false;
}
}
public async Task<IEnumerable<AlarmNotification>> GetNotificationHistoryAsync(DateTime startDate, DateTime endDate)
{
return await _notificationRepository.GetNotificationsByDateRangeAsync(startDate, endDate);
}
public async Task<bool> ConfigureNotificationChannelAsync(NotificationChannel channel)
{
if (channel == null)
throw new ArgumentNullException(nameof(channel));
// Validate channel configuration
if (!await ValidateChannelConfigurationAsync(channel))
return false;
// Save channel configuration
await _configRepository.UpsertAsync(new SystemConfig
{
ConfigKey = $"notification_{channel.ChannelType}_enabled",
ConfigValue = channel.IsEnabled.ToString(),
Category = "Notification",
Description = $"Enable {channel.ChannelType} notifications"
});
await _configRepository.UpsertAsync(new SystemConfig
{
ConfigKey = $"notification_{channel.ChannelType}_config",
ConfigValue = System.Text.Json.JsonSerializer.Serialize(channel.Settings),
Category = "Notification",
Description = $"{channel.ChannelType} notification settings"
});
_channels[channel.ChannelType] = channel;
await _loggingService.LogInformationAsync($"Configured notification channel: {channel.ChannelType}");
return true;
}
public async Task<IEnumerable<NotificationChannel>> GetAvailableChannelsAsync()
{
var channels = new List<NotificationChannel>();
// SMS Channel
if (await IsChannelEnabledAsync("SMS"))
{
channels.Add(new NotificationChannel
{
ChannelType = "SMS",
IsEnabled = true,
Settings = await GetSmsConfigurationAsync()
});
}
// Email Channel
if (await IsChannelEnabledAsync("Email"))
{
channels.Add(new NotificationChannel
{
ChannelType = "Email",
IsEnabled = true,
Settings = await GetEmailConfigurationAsync()
});
}
// WeChat Channel
if (await IsChannelEnabledAsync("WeChat"))
{
channels.Add(new NotificationChannel
{
ChannelType = "WeChat",
IsEnabled = true,
Settings = await GetWechatConfigurationAsync()
});
}
return channels;
}
public async Task TestNotificationChannelAsync(NotificationChannel channel)
{
if (channel == null)
throw new ArgumentNullException(nameof(channel));
var testMessage = $"This is a test notification from CNC System at {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
switch (channel.ChannelType)
{
case "SMS":
await SendSmsNotificationAsync(channel.TestPhoneNumber, testMessage);
break;
case "Email":
await SendEmailNotificationAsync(channel.TestEmail, "CNC System Test", testMessage);
break;
case "WeChat":
await SendWechatNotificationAsync(channel.TestOpenId, testMessage);
break;
default:
throw new ArgumentException($"Unsupported channel type: {channel.ChannelType}");
}
await _loggingService.LogInformationAsync($"Test notification sent via {channel.ChannelType}");
}
public async Task<int> GetFailedNotificationCountAsync(DateTime date)
{
var startOfDay = date.Date;
var endOfDay = startOfDay.AddDays(1);
return await _notificationRepository.GetFailedNotificationsCountAsync(startOfDay, endOfDay);
}
public async Task<bool> ResendFailedNotificationsAsync(int maxRetries = 3)
{
var failedNotifications = await _notificationRepository.GetFailedNotificationsAsync(maxRetries);
if (!failedNotifications.Any())
{
await _loggingService.LogInformationAsync("No failed notifications to resend");
return true;
}
var successCount = 0;
var retryTasks = failedNotifications.Select(async notification =>
{
try
{
await RetryFailedNotificationAsync(notification);
successCount++;
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Failed to resend notification {notification.NotificationId}: {ex.Message}", ex);
}
});
await Task.WhenAll(retryTasks);
await _loggingService.LogInformationAsync($"Resent {successCount}/{failedNotifications.Count} failed notifications");
return successCount > 0;
}
private async Task<IEnumerable<NotificationChannel>> GetNotificationChannelsForAlarm(Alarm alarm)
{
var channels = new List<NotificationChannel>();
var alarmType = alarm.AlarmType.ToLower();
// Get global notification settings
var smsEnabled = await IsChannelEnabledAsync("SMS");
var emailEnabled = await IsChannelEnabledAsync("Email");
var wechatEnabled = await IsChannelEnabledAsync("WeChat");
// High severity alarms get all channels
if (alarm.AlarmSeverity == AlarmSeverity.Critical || alarm.AlarmSeverity == AlarmSeverity.High)
{
if (smsEnabled) channels.Add(await CreateSmsChannelAsync());
if (emailEnabled) channels.Add(await CreateEmailChannelAsync());
if (wechatEnabled) channels.Add(await CreateWechatChannelAsync());
}
// Medium severity alarms get email and SMS
else if (alarm.AlarmSeverity == AlarmSeverity.Medium)
{
if (emailEnabled) channels.Add(await CreateEmailChannelAsync());
if (smsEnabled) channels.Add(await CreateSmsChannelAsync());
}
// Low severity alarms get email only
else if (alarm.AlarmSeverity == AlarmSeverity.Low && emailEnabled)
{
channels.Add(await CreateEmailChannelAsync());
}
return channels;
}
private async Task SendNotificationViaChannelAsync(Alarm alarm, NotificationChannel channel)
{
var subject = $"CNC Alarm: {alarm.AlarmType} - {alarm.DeviceName}";
var message = CreateAlarmMessage(alarm);
switch (channel.ChannelType)
{
case "SMS":
await SendSmsNotificationAsync(channel.Recipients.FirstOrDefault(), message);
break;
case "Email":
await SendEmailNotificationAsync(channel.Recipients.FirstOrDefault(), subject, message);
break;
case "WeChat":
await SendWechatNotificationAsync(channel.Recipients.FirstOrDefault(), message);
break;
}
}
private string CreateAlarmMessage(Alarm alarm)
{
return $@"
🚨 CNC System Alarm Alert 🚨
Device: {alarm.DeviceName} ({alarm.DeviceCode})
Type: {alarm.AlarmType}
Severity: {alarm.AlarmSeverity}
Time: {alarm.CreateTime:yyyy-MM-dd HH:mm:ss}
Description: {alarm.Title}
Please take immediate action.
".Trim();
}
private async Task<NotificationSettings> GetSmsConfigurationAsync()
{
// Get SMS configuration from database or use defaults
return new NotificationSettings
{
IsEnabled = true,
ApiKey = await _configRepository.GetValueAsync("sms_api_key") ?? "",
ApiSecret = await _configRepository.GetValueAsync("sms_api_secret") ?? "",
Provider = await _configRepository.GetValueAsync("sms_provider") ?? "twilio"
};
}
private async Task<NotificationSettings> GetEmailConfigurationAsync()
{
return new NotificationSettings
{
IsEnabled = true,
SmtpServer = await _configRepository.GetValueAsync("smtp_server") ?? "smtp.gmail.com",
SmtpPort = int.Parse(await _configRepository.GetValueAsync("smtp_port") ?? "587"),
Username = await _configRepository.GetValueAsync("smtp_username") ?? "",
Password = await _configRepository.GetValueAsync("smtp_password") ?? "",
UseSsl = bool.Parse(await _configRepository.GetValueAsync("smtp_ssl") ?? "true")
};
}
private async Task<NotificationSettings> GetWechatConfigurationAsync()
{
return new NotificationSettings
{
IsEnabled = true,
AppId = await _configRepository.GetValueAsync("wechat_app_id") ?? "",
AppSecret = await _configRepository.GetValueAsync("wechat_app_secret") ?? "",
TemplateId = await _configRepository.GetValueAsync("wechat_template_id") ?? ""
};
}
private async Task<bool> IsChannelEnabledAsync(string channelType)
{
var configKey = $"notification_{channelType}_enabled";
var configValue = await _configRepository.GetValueAsync(configKey);
return bool.TryParse(configValue, out bool enabled) && enabled;
}
private async Task<bool> ValidateChannelConfigurationAsync(NotificationChannel channel)
{
// Add validation logic based on channel type
switch (channel.ChannelType)
{
case "SMS":
return !string.IsNullOrEmpty(channel.TestPhoneNumber);
case "Email":
return !string.IsNullOrEmpty(channel.TestEmail);
case "WeChat":
return !string.IsNullOrEmpty(channel.TestOpenId);
default:
return false;
}
}
private async Task<NotificationChannel> CreateSmsChannelAsync()
{
return new NotificationChannel
{
ChannelType = "SMS",
IsEnabled = true,
Recipients = await GetSmsRecipientsAsync(),
TestPhoneNumber = await _configRepository.GetValueAsync("sms_test_phone") ?? "+1234567890",
Settings = await GetSmsConfigurationAsync()
};
}
private async Task<NotificationChannel> CreateEmailChannelAsync()
{
return new NotificationChannel
{
ChannelType = "Email",
IsEnabled = true,
Recipients = await GetEmailRecipientsAsync(),
TestEmail = await _configRepository.GetValueAsync("email_test_address") ?? "test@example.com",
Settings = await GetEmailConfigurationAsync()
};
}
private async Task<NotificationChannel> CreateWechatChannelAsync()
{
return new NotificationChannel
{
ChannelType = "WeChat",
IsEnabled = true,
Recipients = await GetWechatRecipientsAsync(),
TestOpenId = await _configRepository.GetValueAsync("wechat_test_openid") ?? "test_openid",
Settings = await GetWechatConfigurationAsync()
};
}
private async Task<List<string>> GetSmsRecipientsAsync()
{
var configValue = await _configRepository.GetValueAsync("sms_recipients");
return string.IsNullOrEmpty(configValue) ? new List<string>() : configValue.Split(',').Select(s => s.Trim()).ToList();
}
private async Task<List<string>> GetEmailRecipientsAsync()
{
var configValue = await _configRepository.GetValueAsync("email_recipients");
return string.IsNullOrEmpty(configValue) ? new List<string>() : configValue.Split(',').Select(s => s.Trim()).ToList();
}
private async Task<List<string>> GetWechatRecipientsAsync()
{
var configValue = await _configRepository.GetValueAsync("wechat_recipients");
return string.IsNullOrEmpty(configValue) ? new List<string>() : configValue.Split(',').Select(s => s.Trim()).ToList();
}
private async Task RetryFailedNotificationAsync(AlarmNotification notification)
{
// Retry logic here
await _notificationRepository.MarkAsRetriedAsync(notification.NotificationId);
}
}
// Supporting interfaces and classes
public interface INotificationSender
{
Task<NotificationResult> SendAsync(string recipient, string message, NotificationSettings settings);
}
public class NotificationResult
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public string ReferenceId { get; set; }
}
public class NotificationSettings
{
public bool IsEnabled { get; set; }
public string ApiKey { get; set; }
public string ApiSecret { get; set; }
public string Provider { get; set; }
public string SmtpServer { get; set; }
public int SmtpPort { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool UseSsl { get; set; }
public string AppId { get; set; }
public string AppSecret { get; set; }
public string TemplateId { get; set; }
}
// Additional repository interface for alarm notifications
public interface IAlarmNotificationRepository : IRepository<AlarmNotification>
{
Task<IEnumerable<AlarmNotification>> GetNotificationsByDateRangeAsync(DateTime startDate, DateTime endDate);
Task<IEnumerable<AlarmNotification>> GetFailedNotificationsAsync(int maxRetries = 3);
Task<int> GetFailedNotificationsCountAsync(DateTime startDate, DateTime endDate);
Task<bool> MarkAsRetriedAsync(int notificationId);
Task<IEnumerable<AlarmNotification>> GetNotificationsByAlarmAsync(int alarmId);
}
}