404 lines
12 KiB
C#
404 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using System.Net.Mail;
|
|
using System.Net;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Extensions.Logging;
|
|
using ZhonTai.Admin.Services;
|
|
using ZhonTai.DynamicApi;
|
|
using ZhonTai.DynamicApi.Attributes;
|
|
using NPP.SmartSchedue.Api.Contracts.Services.Notification;
|
|
using NPP.SmartSchedue.Api.Contracts.Services.Notification.Input;
|
|
using NPP.SmartSchedue.Api.Contracts.Services.Notification.Output;
|
|
using NPP.SmartSchedue.Api.Contracts.Core.Configuration;
|
|
|
|
namespace NPP.SmartSchedue.Api.Services.Notification;
|
|
|
|
/// <summary>
|
|
/// 通知服务 邮件
|
|
/// </summary>
|
|
[DynamicApi(Area = "app")]
|
|
public class EmailNotificationService : BaseService, IEmailNotificationService, IDynamicApi
|
|
{
|
|
private readonly ILogger<EmailNotificationService> _logger;
|
|
private readonly EmailConfiguration _emailConfig;
|
|
private readonly INotificationTemplateService _templateService;
|
|
|
|
public EmailNotificationService(
|
|
ILogger<EmailNotificationService> logger,
|
|
IOptions<EmailConfiguration> emailConfig,
|
|
INotificationTemplateService templateService)
|
|
{
|
|
_logger = logger;
|
|
_emailConfig = emailConfig.Value;
|
|
_templateService = templateService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 发送单个邮件
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<SendEmailOutput> SendEmailAsync(SendEmailInput input)
|
|
{
|
|
var output = new SendEmailOutput
|
|
{
|
|
RecipientEmail = input.RecipientEmail,
|
|
Subject = input.Subject
|
|
};
|
|
|
|
try
|
|
{
|
|
if (!_emailConfig.Enabled)
|
|
{
|
|
output.ErrorMessage = "邮件服务已禁用";
|
|
return output;
|
|
}
|
|
|
|
if (!_emailConfig.IsValid())
|
|
{
|
|
output.ErrorMessage = "邮件配置无效";
|
|
return output;
|
|
}
|
|
|
|
using var smtpClient = CreateSmtpClient();
|
|
using var mailMessage = CreateMailMessage(input.RecipientEmail, input.Subject, input.Content, input.IsHtml);
|
|
|
|
// 添加附件
|
|
if (input.Attachments?.Any() == true)
|
|
{
|
|
AddAttachments(mailMessage, input.Attachments);
|
|
}
|
|
|
|
await smtpClient.SendMailAsync(mailMessage);
|
|
|
|
output.IsSuccess = true;
|
|
_logger.LogInformation("邮件发送成功:{Email} - {Subject}", input.RecipientEmail, input.Subject);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
output.ErrorMessage = ex.Message;
|
|
_logger.LogError(ex, "发送邮件失败:{Email} - {Subject}", input.RecipientEmail, input.Subject);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 批量发送邮件
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<BatchSendEmailOutput> BatchSendEmailAsync(BatchSendEmailInput input)
|
|
{
|
|
var output = new BatchSendEmailOutput
|
|
{
|
|
TotalCount = input.Recipients.Count
|
|
};
|
|
|
|
if (!_emailConfig.Enabled)
|
|
{
|
|
return CreateFailedBatchOutput(input.Recipients, "邮件服务已禁用");
|
|
}
|
|
|
|
if (!_emailConfig.IsValid())
|
|
{
|
|
return CreateFailedBatchOutput(input.Recipients, "邮件配置无效");
|
|
}
|
|
|
|
// 并行发送邮件
|
|
var tasks = input.Recipients.Select(async recipient =>
|
|
{
|
|
var emailInput = new SendEmailInput
|
|
{
|
|
RecipientEmail = recipient,
|
|
Subject = input.Subject,
|
|
Content = input.Content,
|
|
IsHtml = input.IsHtml
|
|
};
|
|
|
|
return await SendEmailAsync(emailInput);
|
|
});
|
|
|
|
var results = await Task.WhenAll(tasks);
|
|
|
|
output.Results.AddRange(results);
|
|
output.SuccessCount = results.Count(r => r.IsSuccess);
|
|
output.FailedCount = results.Count(r => !r.IsSuccess);
|
|
|
|
_logger.LogInformation("批量发送邮件完成:总数 {Total},成功 {Success},失败 {Failed}",
|
|
output.TotalCount, output.SuccessCount, output.FailedCount);
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 个性化批量发送邮件
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<BatchSendEmailOutput> BatchSendPersonalizedEmailAsync(BatchSendPersonalizedEmailInput input)
|
|
{
|
|
var output = new BatchSendEmailOutput
|
|
{
|
|
TotalCount = input.EmailItems.Count
|
|
};
|
|
|
|
if (!_emailConfig.Enabled)
|
|
{
|
|
return CreateFailedBatchOutput(input.EmailItems.Select(e => e.RecipientEmail), "邮件服务已禁用");
|
|
}
|
|
|
|
if (!_emailConfig.IsValid())
|
|
{
|
|
return CreateFailedBatchOutput(input.EmailItems.Select(e => e.RecipientEmail), "邮件配置无效");
|
|
}
|
|
|
|
// 并行发送邮件
|
|
var tasks = input.EmailItems.Select(async item =>
|
|
{
|
|
var emailInput = new SendEmailInput
|
|
{
|
|
RecipientEmail = item.RecipientEmail,
|
|
Subject = item.Subject,
|
|
Content = item.Content,
|
|
IsHtml = item.IsHtml,
|
|
Attachments = item.Attachments
|
|
};
|
|
|
|
return await SendEmailAsync(emailInput);
|
|
});
|
|
|
|
var results = await Task.WhenAll(tasks);
|
|
|
|
output.Results.AddRange(results);
|
|
output.SuccessCount = results.Count(r => r.IsSuccess);
|
|
output.FailedCount = results.Count(r => !r.IsSuccess);
|
|
|
|
_logger.LogInformation("个性化批量发送邮件完成:总数 {Total},成功 {Success},失败 {Failed}",
|
|
output.TotalCount, output.SuccessCount, output.FailedCount);
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 使用模板发送邮件
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<SendEmailOutput> SendEmailByTemplateAsync(SendEmailByTemplateInput input)
|
|
{
|
|
var output = new SendEmailOutput
|
|
{
|
|
RecipientEmail = input.RecipientEmail
|
|
};
|
|
|
|
try
|
|
{
|
|
// 渲染模板
|
|
var subjectInput = new RenderTemplateInput
|
|
{
|
|
Template = input.SubjectTemplate,
|
|
Variables = input.Variables ?? new Dictionary<string, string>()
|
|
};
|
|
var subjectResult = await _templateService.RenderTemplateAsync(subjectInput);
|
|
|
|
var contentInput = new RenderTemplateInput
|
|
{
|
|
Template = input.ContentTemplate,
|
|
Variables = input.Variables ?? new Dictionary<string, string>()
|
|
};
|
|
var contentResult = await _templateService.RenderTemplateAsync(contentInput);
|
|
|
|
// 检查渲染是否成功
|
|
if (!subjectResult.Success)
|
|
{
|
|
output.ErrorMessage = "主题模板渲染失败: " + subjectResult.ErrorMessage;
|
|
return output;
|
|
}
|
|
|
|
if (!contentResult.Success)
|
|
{
|
|
output.ErrorMessage = "内容模板渲染失败: " + contentResult.ErrorMessage;
|
|
return output;
|
|
}
|
|
|
|
// 创建邮件发送请求
|
|
var emailInput = new SendEmailInput
|
|
{
|
|
RecipientEmail = input.RecipientEmail,
|
|
Subject = subjectResult.RenderedContent,
|
|
Content = contentResult.RenderedContent,
|
|
IsHtml = input.IsHtml
|
|
};
|
|
|
|
return await SendEmailAsync(emailInput);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
output.ErrorMessage = ex.Message;
|
|
_logger.LogError(ex, "使用模板发送邮件失败:{Email}", input.RecipientEmail);
|
|
return output;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证邮箱地址格式
|
|
/// </summary>
|
|
public async Task<bool> IsValidEmailAsync(string email)
|
|
{
|
|
return await Task.FromResult(IsValidEmail(email));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查邮件服务器连接状态
|
|
/// </summary>
|
|
public async Task<EmailServerStatusOutput> CheckEmailServerStatusAsync()
|
|
{
|
|
var output = new EmailServerStatusOutput
|
|
{
|
|
SmtpServer = _emailConfig.SmtpServer,
|
|
SmtpPort = _emailConfig.SmtpPort
|
|
};
|
|
|
|
var startTime = DateTime.Now;
|
|
|
|
try
|
|
{
|
|
if (!_emailConfig.IsValid())
|
|
{
|
|
output.ErrorMessage = "邮件配置无效";
|
|
return output;
|
|
}
|
|
|
|
using var smtpClient = CreateSmtpClient();
|
|
|
|
// 创建一个测试邮件来验证连接
|
|
var testMail = new MailMessage
|
|
{
|
|
From = new MailAddress(_emailConfig.SenderEmail, _emailConfig.SenderName),
|
|
Subject = "连接测试",
|
|
Body = "这是一个连接测试邮件,可以忽略。",
|
|
IsBodyHtml = false
|
|
};
|
|
testMail.To.Add(_emailConfig.SenderEmail); // 发送给自己进行测试
|
|
|
|
await smtpClient.SendMailAsync(testMail);
|
|
|
|
output.IsAvailable = true;
|
|
output.ResponseTimeMs = (int)(DateTime.Now - startTime).TotalMilliseconds;
|
|
|
|
_logger.LogInformation("邮件服务器连接测试成功:{Server}:{Port}, 响应时间:{ResponseTime}ms",
|
|
_emailConfig.SmtpServer, _emailConfig.SmtpPort, output.ResponseTimeMs);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
output.ErrorMessage = ex.Message;
|
|
output.ResponseTimeMs = (int)(DateTime.Now - startTime).TotalMilliseconds;
|
|
_logger.LogError(ex, "邮件服务器连接测试失败");
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
#region 私有辅助方法
|
|
|
|
/// <summary>
|
|
/// 验证邮箱地址格式
|
|
/// </summary>
|
|
private bool IsValidEmail(string email)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(email))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
var emailRegex = new Regex(
|
|
@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
|
|
|
return emailRegex.IsMatch(email);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建SMTP客户端
|
|
/// </summary>
|
|
private SmtpClient CreateSmtpClient()
|
|
{
|
|
return new SmtpClient(_emailConfig.SmtpServer, _emailConfig.SmtpPort)
|
|
{
|
|
Credentials = new NetworkCredential(_emailConfig.SenderEmail, _emailConfig.SenderPassword),
|
|
EnableSsl = _emailConfig.EnableSsl,
|
|
DeliveryMethod = SmtpDeliveryMethod.Network,
|
|
Timeout = _emailConfig.TimeoutSeconds * 1000
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建邮件消息
|
|
/// </summary>
|
|
private MailMessage CreateMailMessage(string recipientEmail, string subject, string content, bool isHtml)
|
|
{
|
|
var mailMessage = new MailMessage
|
|
{
|
|
From = new MailAddress(_emailConfig.SenderEmail, _emailConfig.SenderName),
|
|
Subject = subject,
|
|
Body = content,
|
|
IsBodyHtml = isHtml
|
|
};
|
|
|
|
mailMessage.To.Add(recipientEmail);
|
|
return mailMessage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加附件
|
|
/// </summary>
|
|
private void AddAttachments(MailMessage mailMessage, List<string> attachments)
|
|
{
|
|
foreach (var attachmentPath in attachments)
|
|
{
|
|
if (File.Exists(attachmentPath))
|
|
{
|
|
var attachment = new Attachment(attachmentPath);
|
|
mailMessage.Attachments.Add(attachment);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogWarning("附件文件不存在:{Path}", attachmentPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建失败的批量发送输出
|
|
/// </summary>
|
|
private BatchSendEmailOutput CreateFailedBatchOutput(IEnumerable<string> recipients, string errorMessage)
|
|
{
|
|
var output = new BatchSendEmailOutput
|
|
{
|
|
TotalCount = recipients.Count(),
|
|
FailedCount = recipients.Count()
|
|
};
|
|
|
|
foreach (var recipient in recipients)
|
|
{
|
|
output.Results.Add(new SendEmailOutput
|
|
{
|
|
RecipientEmail = recipient,
|
|
IsSuccess = false,
|
|
ErrorMessage = errorMessage
|
|
});
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
#endregion
|
|
} |