using AngleSharp;
using AngleSharp.Css.Dom;
using Dpz.Core.EnumLibrary;
using Dpz.Core.Infrastructure;
using Dpz.Core.Public.ViewModel;
using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Service;
using Dpz.Core.Service.RepositoryService;
using HandlebarsDotNet;
using Hangfire;
using MailKit;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MimeKit;
using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration;
namespace Dpz.Core.Hangfire;
/// <summary>
/// 邮件发送(已过时)
/// </summary>
/// <remarks>
/// 此类已被标记为过时,请使用消息队列方式发送邮件:
/// - 发布 SendMasterEmailMessage 消息代替 SendMasterAsync
/// - 发布 SendReplyEmailMessage 消息代替 SendCommenterAsync
/// </remarks>
[Obsolete("请使用消息队列方式发送邮件(SendMasterEmailMessage 和 SendReplyEmailMessage)")]
public class EmailSenderActivator(
IConfiguration configuration,
ICommentService commentService,
IEmailHistoryService emailHistoryService,
IProtocolLogger protocolLogger,
ILogger<EmailSenderActivator> logger
) : JobActivator
{
private readonly EmailConfiguration _smtpConfiguration =
configuration.GetSection("EmailService").Get<EmailConfiguration>()
?? throw new Exception("EmailService configuration is null");
private readonly bool _openService =
configuration.GetSection("MailServiceOpen").Get<bool?>() ?? false;
/// <summary>
/// 原始模板内容
/// </summary>
private static readonly Lazy<ValueTask<string>> MessageTemplate = new(async () =>
await LoadTemplateAsync("MessageTemplate.html")
);
/// <summary>
/// 评论回复通知模板
/// </summary>
private static readonly Lazy<ValueTask<string>> ReplyTemplate = new(async () =>
await LoadTemplateAsync("ReplyTemplate.html")
);
/// <summary>
/// 发送给管理员的新评论通知(已过时)
/// </summary>
/// <param name="comment">评论信息</param>
[Obsolete("请使用消息队列发布 SendMasterEmailMessage 消息")]
[ProlongExpirationTime]
public async Task SendMasterAsync(VmPublishComment comment)
{
if (!_openService)
{
logger.LogWarning("邮件服务已停用");
return;
}
// 填充邮件内容模板
var htmlBody = await FillMessageTemplateAsync(comment);
if (string.IsNullOrEmpty(htmlBody))
{
return;
}
// 创建邮件
var message = CreateMimeMessage(
_smtpConfiguration.MasterEmail,
$"收到了来自 {comment.NickName} 新评论",
htmlBody
);
// 创建邮件历史记录
var request = new AddEmailHistoryRequest
{
ToRecipients = [_smtpConfiguration.MasterEmail],
SenderEmail = _smtpConfiguration.Email,
ContentPreview = htmlBody,
CcRecipients = [],
Subject = message.Subject ?? "",
SentTime = null,
Status = EmailSendStatus.Pending,
};
// 发送邮件并记录
await SendEmailAsync(message, request);
}
/// <summary>
/// 发送给访客评论者的回复通知(已过时)
/// </summary>
/// <param name="reply">回复信息</param>
[Obsolete("请使用消息队列发布 SendReplyEmailMessage 消息")]
[ProlongExpirationTime]
public async Task SendCommenterAsync(VmPublishComment? reply)
{
if (!_openService || string.IsNullOrWhiteSpace(reply?.ReplyId))
{
return;
}
// 根据回复ID获取评论
var comment = await commentService.GetComment(reply.ReplyId);
if (comment is { Commenter: VmGuestCommenter guestCommenter })
{
await SendReplyEmailAsync(guestCommenter, comment, reply.NickName, reply.CommentText);
}
}
/// <summary>
/// 发送给会员评论者的回复通知(已过时)
/// </summary>
/// <param name="reply">回复信息</param>
[Obsolete("请使用消息队列发布 SendReplyEmailMessage 消息")]
[ProlongExpirationTime]
public async Task SendCommenterAsync(MembleComment? reply)
{
if (!_openService || string.IsNullOrEmpty(reply?.ReplyId))
{
return;
}
var comment = await commentService.GetComment(reply.ReplyId);
if (comment is { Commenter: VmGuestCommenter guestCommenter })
{
await SendReplyEmailAsync(
guestCommenter,
comment,
reply.User?.Name ?? "",
reply.CommentText
);
}
}
/// <summary>
/// 填充评论回复模板
/// </summary>
/// <param name="comment"></param>
/// <param name="nickName"></param>
/// <param name="commentText"></param>
/// <returns></returns>
private async Task<string?> FillReplyTemplateAsync(
CommentViewModel comment,
string nickName,
string commentText
)
{
var templateContent = await ReplyTemplate.Value;
return FillTemplate(
templateContent,
new Dictionary<string, string>
{
{ nameof(VmGuestCommenter.NickName), comment.Commenter.NickName },
{ nameof(nickName), nickName },
{
nameof(CommentViewModel.PublishTime),
comment.PublishTime.ToString("yyyy-MM-dd HH:mm:ss")
},
{ nameof(CommentViewModel.CommentText), await HandleContent(comment.CommentText) },
{ "replyTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") },
{ "replyText", await HandleContent(commentText) },
}
);
}
/// <summary>
/// 填充消息通知模板
/// </summary>
/// <param name="comment"></param>
/// <returns></returns>
private async Task<string> FillMessageTemplateAsync(VmPublishComment comment)
{
var templateContent = await MessageTemplate.Value;
return FillTemplate(
templateContent,
new Dictionary<string, string>
{
{ nameof(VmPublishComment.NickName), comment.NickName },
{ nameof(VmPublishComment.Email), comment.Email },
{
nameof(VmPublishComment.SendTime),
comment.SendTime.ToString("yyyy-MM-dd HH:mm:ss")
},
{ nameof(VmPublishComment.CommentText), await HandleContent(comment.CommentText) },
}
);
}
/// <summary>
/// markdown 转换
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
private static async Task<string> HandleContent(string? content)
{
if (string.IsNullOrEmpty(content))
{
return "";
}
var html = content.MarkdownToHtml();
var context = BrowsingContext.New(Configuration.Default);
var document = await context.OpenAsync(y => y.Content(html));
var images = document.GetElementsByTagName("img");
foreach (var image in images)
{
image.SetStyle("max-width:100%");
}
return document.Body?.InnerHtml ?? "";
}
/// <summary>
/// 加载模板文件的内容
/// </summary>
/// <param name="templateName">模板名</param>
/// <returns></returns>
private static async Task<string> LoadTemplateAsync(string templateName)
{
var templatePath = Path.Combine(AppContext.BaseDirectory, "Template", templateName);
if (!File.Exists(templatePath))
{
return string.Empty;
}
using var reader = new StreamReader(templatePath);
return await reader.ReadToEndAsync();
}
/// <summary>
/// 发送邮件,记录邮件
/// </summary>
/// <param name="message"></param>
/// <param name="request"></param>
private async Task SendEmailAsync(MimeMessage message, AddEmailHistoryRequest request)
{
using var client = new SmtpClient(protocolLogger);
client.ServerCertificateValidationCallback = (_, _, _, _) => true;
try
{
// 设置邮件状态为正在发送
request.Status = EmailSendStatus.Pending;
// 连接到邮件服务器并进行认证
await client.ConnectAsync(_smtpConfiguration.Smtp, _smtpConfiguration.Port);
await client.AuthenticateAsync(_smtpConfiguration.Email, _smtpConfiguration.Password);
// 发送邮件
await client.SendAsync(message);
// 更新邮件发送状态为已发送
request.Status = EmailSendStatus.Sent;
request.SentTime = DateTime.Now;
}
catch (Exception e)
{
// 捕获邮件发送失败的异常并记录日志
logger.LogError(e, "邮件发送失败");
// 更新邮件发送状态为失败
request.Status = EmailSendStatus.Failed;
}
finally
{
// 断开连接
await client.DisconnectAsync(true);
// 保存邮件发送记录
await emailHistoryService.AddAsync(request);
}
}
/// <summary>
/// 创建 MimeMessage
/// </summary>
/// <param name="recipientEmail">收件人</param>
/// <param name="subject">主题</param>
/// <param name="body">内容</param>
/// <returns></returns>
private MimeMessage CreateMimeMessage(string recipientEmail, string subject, string body)
{
var message = new MimeMessage();
message.To.Add(new MailboxAddress("", recipientEmail));
message.From.Add(new MailboxAddress("叫我阿胖-邮件服务", _smtpConfiguration.Email));
message.Subject = subject;
var bodyBuilder = new BodyBuilder { HtmlBody = body };
message.Body = bodyBuilder.ToMessageBody();
return message;
}
/// <summary>
/// 发送回复邮件
/// </summary>
/// <param name="guestCommenter"></param>
/// <param name="comment"></param>
/// <param name="nickName"></param>
/// <param name="commentText"></param>
private async Task SendReplyEmailAsync(
VmGuestCommenter guestCommenter,
CommentViewModel comment,
string nickName,
string commentText
)
{
// 填充评论回复模板
var htmlBody = await FillReplyTemplateAsync(comment, nickName, commentText);
if (string.IsNullOrEmpty(htmlBody))
{
return;
}
// 创建邮件
var message = CreateMimeMessage(
guestCommenter.GetEmail(),
"您的评论有新的回复了。",
htmlBody
);
// 创建邮件历史记录
var request = new AddEmailHistoryRequest
{
ToRecipients = [guestCommenter.GetEmail()],
SenderEmail = _smtpConfiguration.Email,
ContentPreview = htmlBody,
CcRecipients = [],
Subject = message.Subject ?? "",
SentTime = null,
Status = EmailSendStatus.Pending,
};
// 发送邮件并记录历史
await SendEmailAsync(message, request);
}
private static string FillTemplate(string templateContent, Dictionary<string, string> values)
{
var handlebars = Handlebars.Create();
handlebars.Configuration.TextEncoder = new IgnoreTextEncoder();
var template = handlebars.Compile(templateContent);
return template(values);
}
internal class EmailConfiguration
{
public required string Email { get; set; }
public required string MasterEmail { get; set; }
public required string Password { get; set; }
public required string Smtp { get; set; }
public required int Port { get; set; }
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这个代码文件是一个邮件发送服务类,用于在博客或评论系统中发送邮件通知。让我详细解释一下代码的结构和功能:
主要功能
这是一个基于 Hangfire 的邮件发送激活器,主要用于:
- 给管理员发送新评论通知
- 给评论者发送回复通知
关键特性
1. 过时标记
整个类被标记为 [Obsolete],表明这是一个已弃用的实现,建议使用消息队列方式替代。
2. 依赖注入
public class EmailSenderActivator(
IConfiguration configuration,
ICommentService commentService,
IEmailHistoryService emailHistoryService,
IProtocolLogger protocolLogger,
ILogger<EmailSenderActivator> logger
) : JobActivator
使用构造函数注入了配置、评论服务、邮件历史服务、协议日志和日志记录器。
3. 配置管理
- 从配置中读取 SMTP 邮件服务器设置
- 支持开关控制邮件服务是否启用
4. 模板系统
private static readonly Lazy<ValueTask<string>> MessageTemplate = new(async () =>
await LoadTemplateAsync("MessageTemplate.html")
);
使用懒加载方式加载 HTML 邮件模板。
主要方法
1. SendMasterAsync - 发送给管理员
向管理员发送新评论通知邮件。
2. SendCommenterAsync - 发送给评论者
有两个重载版本,分别处理游客评论者和会员评论者的回复通知。
3. 模板处理方法
FillMessageTemplateAsync: 填充管理员通知模板FillReplyTemplateAsync: 填充回复通知模板HandleContent: 将 Markdown 内容转换为 HTML,并处理图片样式
4. 邮件发送核心方法
SendEmailAsync: 实际的邮件发送逻辑,包含错误处理和历史记录CreateMimeMessage: 创建邮件消息对象
技术亮点
1. Markdown 支持
var html = content.MarkdownToHtml();
支持 Markdown 格式的评论内容转换为 HTML。
2. HTML 处理
使用 AngleSharp 库处理 HTML 内容,自动为图片添加响应式样式。
3. 模板引擎
使用 HandlebarsDotNet 作为模板引擎,支持动态内容填充。
4. 错误处理和日志
完善的异常处理和日志记录,确保邮件发送失败时能够正确记录。
5. 邮件历史记录
每次邮件发送都会记录到数据库,包含发送状态、时间等信息。
安全特性
- SMTP 连接时跳过证书验证(适用于内部环境)
- 支持 SMTP 认证
- 邮件发送状态跟踪(待发送、已发送、失败)
使用建议
由于类已标记为过时,建议迁移到基于消息队列的实现:
- 使用
SendMasterEmailMessage消息替代SendMasterAsync - 使用
SendReplyEmailMessage消息替代SendCommenterAsync
这样可以实现更好的解耦和可扩展性,避免阻塞主业务流程。
AI 正在分析代码…
评论加载中...