From 18b53de20c474c1c67bb3c5f25244261148bbd8f Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Sat, 11 Jul 2026 11:59:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=89=B9=E9=87=8F=E9=82=AE=E4=BB=B6=20S?= =?UTF-8?q?MTP=20=E6=94=B9=E7=94=A8=20MailKit=20=E4=BF=AE=E5=A4=8D=20SSL?= =?UTF-8?q?=20465=20=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 替换 System.Net.Mail,正确支持隐式 SSL 与 STARTTLS,解决 Syntax error 测试连接失败。 Co-authored-by: Cursor --- src/Oms.Application/Oms.Application.csproj | 1 + .../Services/System/BatchEmailSmtpHelper.cs | 108 +++++++++++------- 2 files changed, 66 insertions(+), 43 deletions(-) diff --git a/src/Oms.Application/Oms.Application.csproj b/src/Oms.Application/Oms.Application.csproj index b2b5427..efc0437 100644 --- a/src/Oms.Application/Oms.Application.csproj +++ b/src/Oms.Application/Oms.Application.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Oms.Application/Services/System/BatchEmailSmtpHelper.cs b/src/Oms.Application/Services/System/BatchEmailSmtpHelper.cs index 5be7ca5..c95c8c0 100644 --- a/src/Oms.Application/Services/System/BatchEmailSmtpHelper.cs +++ b/src/Oms.Application/Services/System/BatchEmailSmtpHelper.cs @@ -1,6 +1,8 @@ -using System.Net; -using System.Net.Mail; +using System.Net.Sockets; using Fake.Application; +using MailKit.Net.Smtp; +using MailKit.Security; +using MimeKit; namespace Oms.Application.Services.System; @@ -11,17 +13,17 @@ internal static class BatchEmailSmtpHelper internal static bool IsSecretMask(string? value) => string.Equals(value?.Trim(), SecretMask, StringComparison.Ordinal); - internal sealed record SmtpEndpoint(string Host, int Port, bool EnableSsl); + internal sealed record SmtpEndpoint(string Host, int Port, SecureSocketOptions SocketOptions); internal static SmtpEndpoint ResolveEndpoint(string? serverType) { return (serverType ?? string.Empty).Trim().ToLowerInvariant() switch { - "qiye_ssl" => new SmtpEndpoint("smtp.mxhichina.com", 465, true), - "aliyun" => new SmtpEndpoint("smtp.aliyun.com", 25, false), - "aliyun_ssl" => new SmtpEndpoint("smtp.aliyun.com", 465, true), - "mxhichina" => new SmtpEndpoint("smtp.mxhichina.com", 25, false), - _ => new SmtpEndpoint("smtp.mxhichina.com", 25, false) + "qiye_ssl" => new SmtpEndpoint("smtp.mxhichina.com", 465, SecureSocketOptions.SslOnConnect), + "aliyun" => new SmtpEndpoint("smtp.aliyun.com", 25, SecureSocketOptions.StartTlsWhenAvailable), + "aliyun_ssl" => new SmtpEndpoint("smtp.aliyun.com", 465, SecureSocketOptions.SslOnConnect), + "mxhichina" => new SmtpEndpoint("smtp.mxhichina.com", 25, SecureSocketOptions.StartTlsWhenAvailable), + _ => new SmtpEndpoint("smtp.mxhichina.com", 25, SecureSocketOptions.StartTlsWhenAvailable) }; } @@ -33,14 +35,14 @@ internal static class BatchEmailSmtpHelper { ValidateCredentials(smtpUsername, smtpPassword); var username = smtpUsername.Trim(); - var endpoint = ResolveEndpoint(serverType); - using var client = CreateClient(endpoint, username, smtpPassword); - using var message = new MailMessage(username, username) - { - Subject = "OMS SMTP Test", - Body = "OMS batch email connection test" - }; - await SendWithClientAsync(client, message, cancellationToken); + var message = BuildMessage( + username, + username, + username, + "OMS SMTP Test", + "OMS batch email connection test", + "plain"); + await SendWithMailKitAsync(username, smtpPassword, serverType, message, cancellationToken); } internal static async Task SendAsync( @@ -62,20 +64,31 @@ internal static class BatchEmailSmtpHelper } var username = smtpUsername.Trim(); - var endpoint = ResolveEndpoint(serverType); - using var client = CreateClient(endpoint, username, smtpPassword); - var fromAddress = new MailAddress( - username, - string.IsNullOrWhiteSpace(fromName) ? username : fromName.Trim()); - using var message = new MailMessage + var displayName = string.IsNullOrWhiteSpace(fromName) ? username : fromName.Trim(); + var message = BuildMessage(username, displayName, recipient, subject, body, bodyType); + await SendWithMailKitAsync(username, smtpPassword, serverType, message, cancellationToken); + } + + private static MimeMessage BuildMessage( + string username, + string displayName, + string to, + string? subject, + string? body, + string? bodyType) + { + var message = new MimeMessage { - From = fromAddress, - Subject = subject ?? string.Empty, - Body = body ?? string.Empty, - IsBodyHtml = string.Equals(bodyType, "html", StringComparison.OrdinalIgnoreCase) + Subject = subject ?? string.Empty }; - message.To.Add(recipient); - await SendWithClientAsync(client, message, cancellationToken); + message.From.Add(new MailboxAddress(displayName, username)); + message.To.Add(MailboxAddress.Parse(to)); + var isHtml = string.Equals(bodyType, "html", StringComparison.OrdinalIgnoreCase); + message.Body = new TextPart(isHtml ? "html" : "plain") + { + Text = body ?? string.Empty + }; + return message; } private static void ValidateCredentials(string smtpUsername, string smtpPassword) @@ -91,32 +104,41 @@ internal static class BatchEmailSmtpHelper } } - private static SmtpClient CreateClient(SmtpEndpoint endpoint, string username, string password) => - new(endpoint.Host, endpoint.Port) + private static async Task SendWithMailKitAsync( + string username, + string password, + string? serverType, + MimeMessage message, + CancellationToken cancellationToken) + { + var endpoint = ResolveEndpoint(serverType); + using var client = new SmtpClient { - EnableSsl = endpoint.EnableSsl, - DeliveryMethod = SmtpDeliveryMethod.Network, - UseDefaultCredentials = false, - Credentials = new NetworkCredential(username, password), Timeout = 30_000 }; - private static async Task SendWithClientAsync( - SmtpClient client, - MailMessage message, - CancellationToken cancellationToken) - { try { - await client.SendMailAsync(message, cancellationToken); + await client.ConnectAsync(endpoint.Host, endpoint.Port, endpoint.SocketOptions, cancellationToken); + await client.AuthenticateAsync(username, password, cancellationToken); + await client.SendAsync(message, cancellationToken); + await client.DisconnectAsync(true, cancellationToken); } - catch (SmtpException ex) + catch (AuthenticationException ex) + { + throw new BusinessException($"SMTP 认证失败:{ex.Message}"); + } + catch (SmtpCommandException ex) { throw new BusinessException($"SMTP 发送失败:{ex.Message}"); } - catch (Exception ex) when (ex is InvalidOperationException or FormatException) + catch (SmtpProtocolException ex) { - throw new BusinessException($"SMTP 发送失败:{ex.Message}"); + throw new BusinessException($"SMTP 协议错误:{ex.Message}"); + } + catch (Exception ex) when (ex is IOException or SocketException or TimeoutException) + { + throw new BusinessException($"SMTP 连接失败:{ex.Message}"); } } }