替换 System.Net.Mail,正确支持隐式 SSL 与 STARTTLS,解决 Syntax error 测试连接失败。
fix: 批量邮件 SMTP 改用 MailKit 修复 SSL 465 连接
This commit is contained in:
commit
4e9a79e58e
@ -22,6 +22,7 @@
|
|||||||
<PackageReference Include="Fake.Rbac.Application" Version="$(FakePackageVersion)" />
|
<PackageReference Include="Fake.Rbac.Application" Version="$(FakePackageVersion)" />
|
||||||
<PackageReference Include="Fake.Rbac.Infrastructure" Version="$(FakePackageVersion)" />
|
<PackageReference Include="Fake.Rbac.Infrastructure" Version="$(FakePackageVersion)" />
|
||||||
<PackageReference Include="DocumentFormat.OpenXml" Version="3.0.2" />
|
<PackageReference Include="DocumentFormat.OpenXml" Version="3.0.2" />
|
||||||
|
<PackageReference Include="MailKit" Version="4.17.0" />
|
||||||
<PackageReference Include="MiniExcel" Version="1.43.1" />
|
<PackageReference Include="MiniExcel" Version="1.43.1" />
|
||||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
using System.Net;
|
using System.Net.Sockets;
|
||||||
using System.Net.Mail;
|
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
|
using MailKit.Net.Smtp;
|
||||||
|
using MailKit.Security;
|
||||||
|
using MimeKit;
|
||||||
|
|
||||||
namespace Oms.Application.Services.System;
|
namespace Oms.Application.Services.System;
|
||||||
|
|
||||||
@ -11,17 +13,17 @@ internal static class BatchEmailSmtpHelper
|
|||||||
internal static bool IsSecretMask(string? value) =>
|
internal static bool IsSecretMask(string? value) =>
|
||||||
string.Equals(value?.Trim(), SecretMask, StringComparison.Ordinal);
|
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)
|
internal static SmtpEndpoint ResolveEndpoint(string? serverType)
|
||||||
{
|
{
|
||||||
return (serverType ?? string.Empty).Trim().ToLowerInvariant() switch
|
return (serverType ?? string.Empty).Trim().ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"qiye_ssl" => new SmtpEndpoint("smtp.mxhichina.com", 465, true),
|
"qiye_ssl" => new SmtpEndpoint("smtp.mxhichina.com", 465, SecureSocketOptions.SslOnConnect),
|
||||||
"aliyun" => new SmtpEndpoint("smtp.aliyun.com", 25, false),
|
"aliyun" => new SmtpEndpoint("smtp.aliyun.com", 25, SecureSocketOptions.StartTlsWhenAvailable),
|
||||||
"aliyun_ssl" => new SmtpEndpoint("smtp.aliyun.com", 465, true),
|
"aliyun_ssl" => new SmtpEndpoint("smtp.aliyun.com", 465, SecureSocketOptions.SslOnConnect),
|
||||||
"mxhichina" => new SmtpEndpoint("smtp.mxhichina.com", 25, false),
|
"mxhichina" => new SmtpEndpoint("smtp.mxhichina.com", 25, SecureSocketOptions.StartTlsWhenAvailable),
|
||||||
_ => new SmtpEndpoint("smtp.mxhichina.com", 25, false)
|
_ => new SmtpEndpoint("smtp.mxhichina.com", 25, SecureSocketOptions.StartTlsWhenAvailable)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,14 +35,14 @@ internal static class BatchEmailSmtpHelper
|
|||||||
{
|
{
|
||||||
ValidateCredentials(smtpUsername, smtpPassword);
|
ValidateCredentials(smtpUsername, smtpPassword);
|
||||||
var username = smtpUsername.Trim();
|
var username = smtpUsername.Trim();
|
||||||
var endpoint = ResolveEndpoint(serverType);
|
var message = BuildMessage(
|
||||||
using var client = CreateClient(endpoint, username, smtpPassword);
|
username,
|
||||||
using var message = new MailMessage(username, username)
|
username,
|
||||||
{
|
username,
|
||||||
Subject = "OMS SMTP Test",
|
"OMS SMTP Test",
|
||||||
Body = "OMS batch email connection test"
|
"OMS batch email connection test",
|
||||||
};
|
"plain");
|
||||||
await SendWithClientAsync(client, message, cancellationToken);
|
await SendWithMailKitAsync(username, smtpPassword, serverType, message, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task SendAsync(
|
internal static async Task SendAsync(
|
||||||
@ -62,20 +64,31 @@ internal static class BatchEmailSmtpHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
var username = smtpUsername.Trim();
|
var username = smtpUsername.Trim();
|
||||||
var endpoint = ResolveEndpoint(serverType);
|
var displayName = string.IsNullOrWhiteSpace(fromName) ? username : fromName.Trim();
|
||||||
using var client = CreateClient(endpoint, username, smtpPassword);
|
var message = BuildMessage(username, displayName, recipient, subject, body, bodyType);
|
||||||
var fromAddress = new MailAddress(
|
await SendWithMailKitAsync(username, smtpPassword, serverType, message, cancellationToken);
|
||||||
username,
|
}
|
||||||
string.IsNullOrWhiteSpace(fromName) ? username : fromName.Trim());
|
|
||||||
using var message = new MailMessage
|
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
|
||||||
Subject = subject ?? string.Empty,
|
|
||||||
Body = body ?? string.Empty,
|
|
||||||
IsBodyHtml = string.Equals(bodyType, "html", StringComparison.OrdinalIgnoreCase)
|
|
||||||
};
|
};
|
||||||
message.To.Add(recipient);
|
message.From.Add(new MailboxAddress(displayName, username));
|
||||||
await SendWithClientAsync(client, message, cancellationToken);
|
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)
|
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) =>
|
private static async Task SendWithMailKitAsync(
|
||||||
new(endpoint.Host, endpoint.Port)
|
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
|
Timeout = 30_000
|
||||||
};
|
};
|
||||||
|
|
||||||
private static async Task SendWithClientAsync(
|
|
||||||
SmtpClient client,
|
|
||||||
MailMessage message,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
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}");
|
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