fix: 批量邮件 SMTP 改用 MailKit 修复 SSL 465 连接
替换 System.Net.Mail,正确支持隐式 SSL 与 STARTTLS,解决 Syntax error 测试连接失败。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
8ac7e6487c
commit
18b53de20c
@ -22,6 +22,7 @@
|
||||
<PackageReference Include="Fake.Rbac.Application" Version="$(FakePackageVersion)" />
|
||||
<PackageReference Include="Fake.Rbac.Infrastructure" Version="$(FakePackageVersion)" />
|
||||
<PackageReference Include="DocumentFormat.OpenXml" Version="3.0.2" />
|
||||
<PackageReference Include="MailKit" Version="4.17.0" />
|
||||
<PackageReference Include="MiniExcel" Version="1.43.1" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user