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. 给管理员发送新评论通知
  2. 给评论者发送回复通知

关键特性

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

这样可以实现更好的解耦和可扩展性,避免阻塞主业务流程。

评论加载中...