|
@@ -5,8 +5,6 @@ namespace Longyi\Email\Providers;
|
|
|
use Illuminate\Support\ServiceProvider;
|
|
use Illuminate\Support\ServiceProvider;
|
|
|
use Longyi\Email\Models\EmailLog;
|
|
use Longyi\Email\Models\EmailLog;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
-use Illuminate\Support\Facades\Mail;
|
|
|
|
|
-use Illuminate\Mail\Events\MessageSending;
|
|
|
|
|
use Illuminate\Mail\Events\MessageSent;
|
|
use Illuminate\Mail\Events\MessageSent;
|
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Event;
|
|
|
|
|
|
|
@@ -14,405 +12,55 @@ class EventServiceProvider extends ServiceProvider
|
|
|
{
|
|
{
|
|
|
public function boot(): void
|
|
public function boot(): void
|
|
|
{
|
|
{
|
|
|
- //Log::info('Email EventServiceProvider booting...');
|
|
|
|
|
-
|
|
|
|
|
- // 方法1: 监听 Laravel 邮件事件(同步和异步都有效)
|
|
|
|
|
- //$this->registerMailEvents();
|
|
|
|
|
-
|
|
|
|
|
- // 方法2: 监听队列事件(作为备用)
|
|
|
|
|
- $this->registerQueueEvents();
|
|
|
|
|
-
|
|
|
|
|
- // 方法3: Hook Mail facade(最可靠)
|
|
|
|
|
- //$this->hookMailFacade();
|
|
|
|
|
-
|
|
|
|
|
- //Log::info('Email EventServiceProvider booted successfully');
|
|
|
|
|
|
|
+ // 只监听 Laravel 官方邮件事件(MessageSent),
|
|
|
|
|
+ // 无论是同步 Mail::send() 还是异步 Mail::queue() 都会触发,
|
|
|
|
|
+ // 且不会响应非邮件队列任务,避免产生日志噪音。
|
|
|
|
|
+ $this->registerMailEvents();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 注册邮件事件监听器
|
|
* 注册邮件事件监听器
|
|
|
|
|
+ *
|
|
|
|
|
+ * 只监听 Laravel 官方的邮件事件,而不是队列事件,这样:
|
|
|
|
|
+ * - 同步 Mail::send() 与异步 Mail::queue() 都会触发;
|
|
|
|
|
+ * - 非邮件队列任务(如 LogApiRequestJob)不会触发,避免日志噪音。
|
|
|
*/
|
|
*/
|
|
|
protected function registerMailEvents(): void
|
|
protected function registerMailEvents(): void
|
|
|
{
|
|
{
|
|
|
- // Laravel 9+ 使用这些事件
|
|
|
|
|
- Event::listen(MessageSending::class, function (MessageSending $event) {
|
|
|
|
|
- Log::info('MessageSending event triggered', [
|
|
|
|
|
- 'message_id' => $event->message->getMessageId(),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- $this->logEmailFromMessage($event->message, 'pending');
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 邮件发送成功后记录(Symfony Mailer 的 $event->message 是 Symfony\Component\Mime\Email)
|
|
|
Event::listen(MessageSent::class, function (MessageSent $event) {
|
|
Event::listen(MessageSent::class, function (MessageSent $event) {
|
|
|
- Log::info('MessageSent event triggered');
|
|
|
|
|
-
|
|
|
|
|
- $this->updateEmailStatus($event->message, 'sent');
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 兼容旧版本的事件名称
|
|
|
|
|
- Event::listen('illuminate.mail.sending', function ($event) {
|
|
|
|
|
- Log::info('illuminate.mail.sending event triggered');
|
|
|
|
|
-
|
|
|
|
|
- if (isset($event->message)) {
|
|
|
|
|
- $this->logEmailFromMessage($event->message, 'pending');
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- Event::listen('illuminate.mail.sent', function ($event) {
|
|
|
|
|
- Log::info('illuminate.mail.sent event triggered');
|
|
|
|
|
-
|
|
|
|
|
- if (isset($event->message)) {
|
|
|
|
|
- $this->updateEmailStatus($event->message, 'sent');
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ $this->logSentEmail($event->message);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 注册队列事件监听器
|
|
|
|
|
|
|
+ * 从 Symfony Mailer 的 Email 对象记录已发送邮件。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param \Symfony\Component\Mime\Email $message
|
|
|
*/
|
|
*/
|
|
|
- protected function registerQueueEvents(): void
|
|
|
|
|
- {
|
|
|
|
|
- // 监听邮件任务被推入队列时
|
|
|
|
|
- Event::listen('Illuminate\Queue\Events\JobQueued', function ($event) {
|
|
|
|
|
- try {
|
|
|
|
|
- /*Log::info('JobQueued event triggered', [
|
|
|
|
|
- 'event_class' => get_class($event),
|
|
|
|
|
- ]);*/
|
|
|
|
|
-
|
|
|
|
|
- // JobQueued 事件有不同的属性结构
|
|
|
|
|
- $payload = null;
|
|
|
|
|
-
|
|
|
|
|
- if (is_object($event)) {
|
|
|
|
|
- // Laravel 10+ 的结构
|
|
|
|
|
- if (isset($event->payload)) {
|
|
|
|
|
- $payload = $event->payload;
|
|
|
|
|
- }
|
|
|
|
|
- // 或者通过 job 获取
|
|
|
|
|
- elseif (isset($event->job)) {
|
|
|
|
|
- $payload = $event->job->payload();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果 payload 是字符串,解析为数组
|
|
|
|
|
- if (is_string($payload)) {
|
|
|
|
|
- $payload = json_decode($payload, true);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果还是无法获取 payload,直接返回
|
|
|
|
|
- if (!is_array($payload)) {
|
|
|
|
|
- Log::warning('Payload is not an array', ['payload_type' => gettype($payload)]);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!isset($payload['data']['command'])) {
|
|
|
|
|
- Log::info('No command in payload', ['payload_keys' => array_keys($payload)]);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $command = @unserialize($payload['data']['command']);
|
|
|
|
|
-
|
|
|
|
|
- if (!$command) {
|
|
|
|
|
- Log::warning('Failed to unserialize command');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查是否是 SendQueuedMailable
|
|
|
|
|
- if (!$command instanceof \Illuminate\Mail\SendQueuedMailable) {
|
|
|
|
|
- Log::info('Not a SendQueuedMailable', [
|
|
|
|
|
- 'command_class' => get_class($command),
|
|
|
|
|
- ]);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $mailable = $command->mailable;
|
|
|
|
|
-
|
|
|
|
|
- if (!$mailable instanceof \Illuminate\Mail\Mailable) {
|
|
|
|
|
- Log::warning('Mailable is not valid');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Log::info('Mail job queued', [
|
|
|
|
|
- 'mailable_class' => get_class($mailable),
|
|
|
|
|
- 'queue' => $payload['queue'] ?? 'pending',
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- // 关键修复:从 SendQueuedMailable 命令中提取收件人
|
|
|
|
|
- $recipients = $this->extractRecipientsFromCommand($command, $mailable);
|
|
|
|
|
-
|
|
|
|
|
- if (empty($recipients)) {
|
|
|
|
|
- Log::warning('No recipients found in command or mailable');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $subject = $this->extractSubjectFromMailable($mailable);
|
|
|
|
|
-
|
|
|
|
|
- // 记录日志
|
|
|
|
|
- foreach ($recipients as $recipient) {
|
|
|
|
|
- $email = is_array($recipient) ? ($recipient['address'] ?? $recipient['email'] ?? null) : $recipient;
|
|
|
|
|
- $name = is_array($recipient) ? ($recipient['name'] ?? null) : null;
|
|
|
|
|
-
|
|
|
|
|
- if (!$email) {
|
|
|
|
|
- Log::warning('No email address found for recipient', ['recipient' => $recipient]);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 清理姓名(去除多余空格,如果为空则设为 null)
|
|
|
|
|
- if ($name) {
|
|
|
|
|
- $name = trim(preg_replace('/\s+/', ' ', $name));
|
|
|
|
|
- if (empty($name)) {
|
|
|
|
|
- $name = null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查是否已存在(扩大时间窗口到30分钟,避免重复但允许同一用户多次收到不同邮件)
|
|
|
|
|
- $exists = EmailLog::where('recipient_email', $email)
|
|
|
|
|
- ->where('subject', $subject)
|
|
|
|
|
- ->where('created_at', '>', now()->subMinutes(30))
|
|
|
|
|
- ->exists();
|
|
|
|
|
-
|
|
|
|
|
- if ($exists) {
|
|
|
|
|
- Log::info('Email log already exists, skipping', [
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'subject' => $subject,
|
|
|
|
|
- ]);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $log = EmailLog::create([
|
|
|
|
|
- 'recipient_email' => $email,
|
|
|
|
|
- 'recipient_name' => $name,
|
|
|
|
|
- 'subject' => $subject,
|
|
|
|
|
- 'content' => $this->extractContentFromMailable($mailable),
|
|
|
|
|
- 'template' => $this->detectTemplateFromMailable($mailable),
|
|
|
|
|
- 'status' => 'sent', // 初始状态为 pending(待发送)
|
|
|
|
|
- 'sent_at' => now(), // 发送时间为空,等待发送成功后更新
|
|
|
|
|
- 'metadata' => json_encode([
|
|
|
|
|
- 'job_id' => $payload['uuid'] ?? null,
|
|
|
|
|
- 'mailable_class' => get_class($mailable),
|
|
|
|
|
- 'queue' => $payload['queue'] ?? null,
|
|
|
|
|
- 'connection' => $payload['connection'] ?? null,
|
|
|
|
|
- 'created_via' => 'JobQueued',
|
|
|
|
|
- ]),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- Log::info('Email log created from queued job', [
|
|
|
|
|
- 'log_id' => $log->id,
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'name' => $name,
|
|
|
|
|
- 'subject' => $subject,
|
|
|
|
|
- 'status' => 'sent',
|
|
|
|
|
- 'sent_at' => $log->sent_at,
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Failed to process queued mail job: ' . $e->getMessage(), [
|
|
|
|
|
- 'trace' => $e->getTraceAsString(),
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 SendQueuedMailable 命令中提取收件人
|
|
|
|
|
- */
|
|
|
|
|
- protected function extractRecipientsFromCommand($command, $mailable): array
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- Log::info('Extracting recipients from SendQueuedMailable command', [
|
|
|
|
|
- 'command_class' => get_class($command),
|
|
|
|
|
- 'mailable_class' => get_class($mailable),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- // 方法1: 从命令对象的 to 属性获取(Laravel 会在构建时填充)
|
|
|
|
|
- if (property_exists($command, 'to') && !empty($command->to)) {
|
|
|
|
|
- Log::info('Found recipients in command->to', ['to' => $command->to]);
|
|
|
|
|
- return is_array($command->to) ? $command->to : [$command->to];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法2: 尝试反射获取命令的 to 属性
|
|
|
|
|
- $reflection = new \ReflectionClass($command);
|
|
|
|
|
- if ($reflection->hasProperty('to')) {
|
|
|
|
|
- $property = $reflection->getProperty('to');
|
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
|
- $to = $property->getValue($command);
|
|
|
|
|
-
|
|
|
|
|
- if (!empty($to)) {
|
|
|
|
|
- Log::info('Extracted recipients from command via reflection', ['to' => $to]);
|
|
|
|
|
- return is_array($to) ? $to : [$to];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法3: 从 Mailable 的特定属性推断(customer, subscribersList, order 等)
|
|
|
|
|
- $recipientProperties = ['customer', 'subscribersList', 'order', 'invoice', 'shipment', 'refund'];
|
|
|
|
|
-
|
|
|
|
|
- foreach ($recipientProperties as $propName) {
|
|
|
|
|
- if (property_exists($mailable, $propName)) {
|
|
|
|
|
- $reflection = new \ReflectionClass($mailable);
|
|
|
|
|
- $property = $reflection->getProperty($propName);
|
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
|
- $obj = $property->getValue($mailable);
|
|
|
|
|
-
|
|
|
|
|
- if ($obj && isset($obj->email) && filter_var($obj->email, FILTER_VALIDATE_EMAIL)) {
|
|
|
|
|
- $recipients = [[
|
|
|
|
|
- 'address' => $obj->email,
|
|
|
|
|
- 'name' => $obj->name ?? $obj->first_name . ' ' . ($obj->last_name ?? '') ?? $obj->customer_full_name ?? null,
|
|
|
|
|
- ]];
|
|
|
|
|
- Log::info("Extracted recipient from mailable->{$propName}", [
|
|
|
|
|
- 'recipients' => $recipients,
|
|
|
|
|
- 'object_type' => get_class($obj),
|
|
|
|
|
- ]);
|
|
|
|
|
- return $recipients;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法4: 检查 toAddresses 属性
|
|
|
|
|
- if (property_exists($mailable, 'toAddresses')) {
|
|
|
|
|
- $toAddresses = $mailable->toAddresses;
|
|
|
|
|
- if (!empty($toAddresses)) {
|
|
|
|
|
- Log::info('Found toAddresses property', ['toAddresses' => $toAddresses]);
|
|
|
|
|
- return is_array($toAddresses) ? $toAddresses : [$toAddresses];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法5: 通过反射遍历所有属性,智能识别邮箱
|
|
|
|
|
- $reflection = new \ReflectionClass($mailable);
|
|
|
|
|
- $properties = $reflection->getProperties();
|
|
|
|
|
-
|
|
|
|
|
- foreach ($properties as $property) {
|
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
|
- $value = $property->getValue($mailable);
|
|
|
|
|
-
|
|
|
|
|
- // 如果属性值是字符串且看起来像邮箱
|
|
|
|
|
- if (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
|
|
|
|
- $recipients = [[
|
|
|
|
|
- 'address' => $value,
|
|
|
|
|
- 'name' => null,
|
|
|
|
|
- ]];
|
|
|
|
|
- Log::info("Extracted email from property {$property->getName()}", ['recipients' => $recipients]);
|
|
|
|
|
- return $recipients;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果属性是对象且有 email 字段
|
|
|
|
|
- if (is_object($value) && isset($value->email) && filter_var($value->email, FILTER_VALIDATE_EMAIL)) {
|
|
|
|
|
- $recipients = [[
|
|
|
|
|
- 'address' => $value->email,
|
|
|
|
|
- 'name' => $value->name ?? $value->first_name . ' ' . ($value->last_name ?? '') ?? null,
|
|
|
|
|
- ]];
|
|
|
|
|
- Log::info("Extracted email from object property {$property->getName()}", [
|
|
|
|
|
- 'recipients' => $recipients,
|
|
|
|
|
- 'object_type' => get_class($value),
|
|
|
|
|
- ]);
|
|
|
|
|
- return $recipients;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Log::warning('Could not extract recipients from command or mailable', [
|
|
|
|
|
- 'command_class' => get_class($command),
|
|
|
|
|
- 'mailable_class' => get_class($mailable),
|
|
|
|
|
- 'mailable_properties' => array_keys(get_object_vars($mailable)),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- return [];
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Extract recipients from command error: ' . $e->getMessage(), [
|
|
|
|
|
- 'trace' => $e->getTraceAsString(),
|
|
|
|
|
- ]);
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Hook Mail facade 以捕获所有邮件发送
|
|
|
|
|
- */
|
|
|
|
|
- protected function hookMailFacade(): void
|
|
|
|
|
- {
|
|
|
|
|
- // 这个方法通过扩展 Mailer 来实现
|
|
|
|
|
- // 但由于复杂性,我们主要依赖事件监听
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Swift_Message 对象记录邮件
|
|
|
|
|
- */
|
|
|
|
|
- protected function logEmailFromMessage($message, string $status): void
|
|
|
|
|
|
|
+ protected function logSentEmail($message): void
|
|
|
{
|
|
{
|
|
|
try {
|
|
try {
|
|
|
$to = $message->getTo();
|
|
$to = $message->getTo();
|
|
|
|
|
|
|
|
if (empty($to)) {
|
|
if (empty($to)) {
|
|
|
- Log::warning('No recipients in message');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- foreach ($to as $email => $name) {
|
|
|
|
|
- // 检查是否已存在(避免重复)
|
|
|
|
|
- $exists = EmailLog::where('recipient_email', $email)
|
|
|
|
|
- ->where('subject', $message->getSubject() ?? '')
|
|
|
|
|
- ->where('created_at', '>', now()->subMinutes(5))
|
|
|
|
|
- ->exists();
|
|
|
|
|
-
|
|
|
|
|
- if ($exists) {
|
|
|
|
|
- Log::info('Email log already exists, skipping', ['email' => $email]);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $log = EmailLog::create([
|
|
|
|
|
- 'recipient_email' => $email,
|
|
|
|
|
- 'recipient_name' => is_string($name) ? $name : null,
|
|
|
|
|
- 'subject' => $message->getSubject() ?? '',
|
|
|
|
|
- 'content' => $this->extractContentFromMessage($message),
|
|
|
|
|
- 'template' => $this->detectTemplateFromMessage($message),
|
|
|
|
|
- 'status' => $status,
|
|
|
|
|
- 'metadata' => json_encode([
|
|
|
|
|
- 'from' => $this->formatAddresses($message->getFrom()),
|
|
|
|
|
- 'cc' => $this->formatAddresses($message->getCc()),
|
|
|
|
|
- 'bcc' => $this->formatAddresses($message->getBcc()),
|
|
|
|
|
- 'reply_to' => $this->formatAddresses($message->getReplyTo()),
|
|
|
|
|
- 'message_id' => $message->getMessageId(),
|
|
|
|
|
- ]),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- Log::info('Email log created from message', [
|
|
|
|
|
- 'log_id' => $log->id,
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'status' => $status,
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Failed to log email from message: ' . $e->getMessage(), [
|
|
|
|
|
- 'trace' => $e->getTraceAsString(),
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Mailable 对象记录邮件
|
|
|
|
|
- */
|
|
|
|
|
- protected function logEmailFromMailable($mailable, string $status, ?string $jobId = null): void
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- // 提取收件人
|
|
|
|
|
- $to = $this->extractRecipientsFromMailable($mailable);
|
|
|
|
|
-
|
|
|
|
|
- if (empty($to)) {
|
|
|
|
|
- Log::warning('No recipients in mailable');
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $subject = $this->extractSubjectFromMailable($mailable);
|
|
|
|
|
|
|
+ $subject = $message->getSubject() ?? '';
|
|
|
|
|
|
|
|
- foreach ($to as $recipient) {
|
|
|
|
|
- $email = is_array($recipient) ? ($recipient['address'] ?? $recipient['email'] ?? null) : $recipient;
|
|
|
|
|
- $name = is_array($recipient) ? ($recipient['name'] ?? null) : null;
|
|
|
|
|
|
|
+ foreach ($to as $address) {
|
|
|
|
|
+ $email = $address instanceof \Symfony\Component\Mime\Address
|
|
|
|
|
+ ? $address->getAddress()
|
|
|
|
|
+ : (string) $address;
|
|
|
|
|
|
|
|
- if (!$email) {
|
|
|
|
|
|
|
+ if (! $email) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 检查是否已存在
|
|
|
|
|
|
|
+ $name = $address instanceof \Symfony\Component\Mime\Address ? $address->getName() : null;
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否已存在(避免重复)
|
|
|
$exists = EmailLog::where('recipient_email', $email)
|
|
$exists = EmailLog::where('recipient_email', $email)
|
|
|
->where('subject', $subject)
|
|
->where('subject', $subject)
|
|
|
->where('created_at', '>', now()->subMinutes(5))
|
|
->where('created_at', '>', now()->subMinutes(5))
|
|
@@ -422,467 +70,77 @@ class EventServiceProvider extends ServiceProvider
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $log = EmailLog::create([
|
|
|
|
|
|
|
+ EmailLog::create([
|
|
|
'recipient_email' => $email,
|
|
'recipient_email' => $email,
|
|
|
- 'recipient_name' => $name,
|
|
|
|
|
|
|
+ 'recipient_name' => $name ?: null,
|
|
|
'subject' => $subject,
|
|
'subject' => $subject,
|
|
|
- 'content' => $this->extractContentFromMailable($mailable),
|
|
|
|
|
- 'template' => $this->detectTemplateFromMailable($mailable),
|
|
|
|
|
- 'status' => $status,
|
|
|
|
|
|
|
+ 'content' => $this->extractContentFromMessage($message),
|
|
|
|
|
+ 'template' => $this->detectTemplateFromMessage($message),
|
|
|
|
|
+ 'status' => 'sent',
|
|
|
|
|
+ 'sent_at' => now(),
|
|
|
'metadata' => json_encode([
|
|
'metadata' => json_encode([
|
|
|
- 'job_id' => $jobId,
|
|
|
|
|
- 'mailable_class' => get_class($mailable),
|
|
|
|
|
- 'cc' => $this->extractCcFromMailable($mailable),
|
|
|
|
|
- 'bcc' => $this->extractBccFromMailable($mailable),
|
|
|
|
|
|
|
+ 'from' => $this->formatAddresses($message->getFrom()),
|
|
|
|
|
+ 'cc' => $this->formatAddresses($message->getCc()),
|
|
|
|
|
+ 'bcc' => $this->formatAddresses($message->getBcc()),
|
|
|
|
|
+ 'reply_to' => $this->formatAddresses($message->getReplyTo()),
|
|
|
|
|
+ 'message_id' => $message->getMessageId(),
|
|
|
]),
|
|
]),
|
|
|
]);
|
|
]);
|
|
|
-
|
|
|
|
|
- Log::info('Email log created from mailable', [
|
|
|
|
|
- 'log_id' => $log->id,
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'status' => $status,
|
|
|
|
|
- ]);
|
|
|
|
|
}
|
|
}
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Failed to log email from mailable: ' . $e->getMessage(), [
|
|
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ Log::error('Failed to log sent email: '.$e->getMessage(), [
|
|
|
'trace' => $e->getTraceAsString(),
|
|
'trace' => $e->getTraceAsString(),
|
|
|
]);
|
|
]);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 更新邮件状态
|
|
|
|
|
- */
|
|
|
|
|
- protected function updateEmailStatus($message, string $status): void
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- $to = $message->getTo();
|
|
|
|
|
-
|
|
|
|
|
- if (empty($to)) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- foreach ($to as $email => $name) {
|
|
|
|
|
- $updated = EmailLog::where('recipient_email', $email)
|
|
|
|
|
- ->where('status', 'pending') // 只更新 pending 状态
|
|
|
|
|
- ->orderBy('created_at', 'desc')
|
|
|
|
|
- ->limit(1)
|
|
|
|
|
- ->update([
|
|
|
|
|
- 'status' => $status,
|
|
|
|
|
- 'sent_at' => now(),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- if ($updated) {
|
|
|
|
|
- Log::info('Email status updated', [
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'status' => $status,
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Failed to update email status: ' . $e->getMessage());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理队列任务
|
|
|
|
|
- */
|
|
|
|
|
- protected function handleQueueJob($job, string $status): void
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- $payload = $job->payload();
|
|
|
|
|
-
|
|
|
|
|
- if (!isset($payload['data']['command'])) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $command = @unserialize($payload['data']['command']);
|
|
|
|
|
-
|
|
|
|
|
- if (!$command || !property_exists($command, 'mailable')) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $mailable = $command->mailable;
|
|
|
|
|
-
|
|
|
|
|
- if (!$mailable instanceof \Illuminate\Mail\Mailable) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $this->logEmailFromMailable($mailable, $status, $job->getJobId());
|
|
|
|
|
-
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Handle queue job error: ' . $e->getMessage());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理队列任务完成后
|
|
|
|
|
- */
|
|
|
|
|
- protected function handleQueueJobAfter($job): void
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- $payload = $job->payload();
|
|
|
|
|
-
|
|
|
|
|
- if (!isset($payload['data']['command'])) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $command = @unserialize($payload['data']['command']);
|
|
|
|
|
-
|
|
|
|
|
- if (!$command || !property_exists($command, 'mailable')) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $mailable = $command->mailable;
|
|
|
|
|
-
|
|
|
|
|
- if (!$mailable instanceof \Illuminate\Mail\Mailable) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $to = $this->extractRecipientsFromMailable($mailable);
|
|
|
|
|
- $subject = $this->extractSubjectFromMailable($mailable);
|
|
|
|
|
-
|
|
|
|
|
- Log::info('Queue job completed, updating status', [
|
|
|
|
|
- 'job_id' => $job->getJobId(),
|
|
|
|
|
- 'recipients_count' => count($to),
|
|
|
|
|
- 'subject' => $subject,
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- foreach ($to as $recipient) {
|
|
|
|
|
- $email = is_array($recipient) ? ($recipient['address'] ?? $recipient['email'] ?? null) : $recipient;
|
|
|
|
|
-
|
|
|
|
|
- if (!$email) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $updated = EmailLog::where('recipient_email', $email)
|
|
|
|
|
- ->where('subject', $subject)
|
|
|
|
|
- ->where('status', 'pending') // 只更新 pending 状态的记录
|
|
|
|
|
- ->orderBy('created_at', 'desc')
|
|
|
|
|
- ->limit(1)
|
|
|
|
|
- ->update([
|
|
|
|
|
- 'status' => 'sent',
|
|
|
|
|
- 'sent_at' => now(),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- if ($updated) {
|
|
|
|
|
- Log::info('Email status updated to sent', [
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'subject' => $subject,
|
|
|
|
|
- ]);
|
|
|
|
|
- } else {
|
|
|
|
|
- Log::warning('Failed to update email status', [
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'subject' => $subject,
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Handle queue job after error: ' . $e->getMessage(), [
|
|
|
|
|
- 'trace' => $e->getTraceAsString(),
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理队列任务失败
|
|
|
|
|
- */
|
|
|
|
|
- protected function handleQueueJobFailed($job, $exception): void
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- $payload = $job->payload();
|
|
|
|
|
-
|
|
|
|
|
- if (!isset($payload['data']['command'])) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $command = @unserialize($payload['data']['command']);
|
|
|
|
|
-
|
|
|
|
|
- if (!$command || !property_exists($command, 'mailable')) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $mailable = $command->mailable;
|
|
|
|
|
-
|
|
|
|
|
- if (!$mailable instanceof \Illuminate\Mail\Mailable) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $to = $this->extractRecipientsFromMailable($mailable);
|
|
|
|
|
- $subject = $this->extractSubjectFromMailable($mailable);
|
|
|
|
|
-
|
|
|
|
|
- foreach ($to as $recipient) {
|
|
|
|
|
- $email = is_array($recipient) ? ($recipient['address'] ?? $recipient['email'] ?? null) : $recipient;
|
|
|
|
|
-
|
|
|
|
|
- if (!$email) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- EmailLog::where('recipient_email', $email)
|
|
|
|
|
- ->where('subject', $subject)
|
|
|
|
|
- ->where('status', 'pending') // 只更新 pending 状态的记录
|
|
|
|
|
- ->orderBy('created_at', 'desc')
|
|
|
|
|
- ->limit(1)
|
|
|
|
|
- ->update([
|
|
|
|
|
- 'status' => 'failed',
|
|
|
|
|
- 'error_message' => $exception->getMessage(),
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Handle queue job failed error: ' . $e->getMessage());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Mailable 提取收件人
|
|
|
|
|
- */
|
|
|
|
|
- protected function extractRecipientsFromMailable($mailable): array
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- Log::info('Extracting recipients from mailable', [
|
|
|
|
|
- 'class' => get_class($mailable),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- // 方法1: 尝试直接访问 to 属性
|
|
|
|
|
- if (property_exists($mailable, 'to')) {
|
|
|
|
|
- $to = $mailable->to;
|
|
|
|
|
- Log::info('Found to property', ['to' => $to]);
|
|
|
|
|
- return $to ?? [];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法2: 尝试反射获取 to 属性
|
|
|
|
|
- $reflection = new \ReflectionClass($mailable);
|
|
|
|
|
-
|
|
|
|
|
- if ($reflection->hasProperty('to')) {
|
|
|
|
|
- $property = $reflection->getProperty('to');
|
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
|
- $to = $property->getValue($mailable);
|
|
|
|
|
- Log::info('Extracted to via reflection', ['to' => $to]);
|
|
|
|
|
- return $to ?? [];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法3: 检查常见属性(customer, subscribersList, order 等)
|
|
|
|
|
- $recipientProperties = ['customer', 'subscribersList', 'order', 'invoice', 'shipment', 'refund'];
|
|
|
|
|
-
|
|
|
|
|
- foreach ($recipientProperties as $propName) {
|
|
|
|
|
- if ($reflection->hasProperty($propName)) {
|
|
|
|
|
- $property = $reflection->getProperty($propName);
|
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
|
- $obj = $property->getValue($mailable);
|
|
|
|
|
-
|
|
|
|
|
- if ($obj && isset($obj->email)) {
|
|
|
|
|
- $recipients = [[
|
|
|
|
|
- 'address' => $obj->email,
|
|
|
|
|
- 'name' => $obj->name ?? $obj->first_name ?? $obj->customer_full_name ?? null,
|
|
|
|
|
- ]];
|
|
|
|
|
- Log::info("Extracted recipient from mailable->{$propName}", ['recipients' => $recipients]);
|
|
|
|
|
- return $recipients;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法4: 检查 toAddresses 属性
|
|
|
|
|
- if (property_exists($mailable, 'toAddresses')) {
|
|
|
|
|
- $toAddresses = $mailable->toAddresses;
|
|
|
|
|
- if (!empty($toAddresses)) {
|
|
|
|
|
- Log::info('Found toAddresses property', ['toAddresses' => $toAddresses]);
|
|
|
|
|
- return $toAddresses;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法5: 通过反射查找所有可能的邮箱字段
|
|
|
|
|
- $properties = $reflection->getProperties();
|
|
|
|
|
-
|
|
|
|
|
- foreach ($properties as $property) {
|
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
|
- $value = $property->getValue($mailable);
|
|
|
|
|
-
|
|
|
|
|
- // 如果属性值是字符串且看起来像邮箱
|
|
|
|
|
- if (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
|
|
|
|
- $recipients = [[
|
|
|
|
|
- 'address' => $value,
|
|
|
|
|
- 'name' => null,
|
|
|
|
|
- ]];
|
|
|
|
|
- Log::info("Extracted email from property {$property->getName()}", ['recipients' => $recipients]);
|
|
|
|
|
- return $recipients;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果属性是对象且有 email 字段
|
|
|
|
|
- if (is_object($value) && isset($value->email) && filter_var($value->email, FILTER_VALIDATE_EMAIL)) {
|
|
|
|
|
- $recipients = [[
|
|
|
|
|
- 'address' => $value->email,
|
|
|
|
|
- 'name' => $value->name ?? $value->first_name ?? null,
|
|
|
|
|
- ]];
|
|
|
|
|
- Log::info("Extracted email from object property {$property->getName()}", ['recipients' => $recipients]);
|
|
|
|
|
- return $recipients;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Log::warning('Could not extract recipients from mailable', [
|
|
|
|
|
- 'class' => get_class($mailable),
|
|
|
|
|
- 'properties' => array_keys(get_object_vars($mailable)),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- return [];
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Extract recipients error: ' . $e->getMessage(), [
|
|
|
|
|
- 'trace' => $e->getTraceAsString(),
|
|
|
|
|
- ]);
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Mailable 提取主题
|
|
|
|
|
- */
|
|
|
|
|
- protected function extractSubjectFromMailable($mailable): string
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- Log::info('Extracting subject from mailable', [
|
|
|
|
|
- 'class' => get_class($mailable),
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- // 方法1: 尝试直接访问 subject 属性
|
|
|
|
|
- if (property_exists($mailable, 'subject') && !empty($mailable->subject)) {
|
|
|
|
|
- $subject = $mailable->subject;
|
|
|
|
|
- Log::info('Found subject property', ['subject' => $subject]);
|
|
|
|
|
- return $subject;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法2: 尝试反射获取 subject 属性
|
|
|
|
|
- $reflection = new \ReflectionClass($mailable);
|
|
|
|
|
-
|
|
|
|
|
- if ($reflection->hasProperty('subject')) {
|
|
|
|
|
- $property = $reflection->getProperty('subject');
|
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
|
- $subject = $property->getValue($mailable);
|
|
|
|
|
-
|
|
|
|
|
- if (!empty($subject)) {
|
|
|
|
|
- Log::info('Extracted subject via reflection', ['subject' => $subject]);
|
|
|
|
|
- return $subject;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法3: 从类名推断并翻译
|
|
|
|
|
- $className = class_basename($mailable);
|
|
|
|
|
- $defaultSubject = str_replace(['Notification', 'Mail'], '', $className);
|
|
|
|
|
-
|
|
|
|
|
- // 尝试从语言文件获取翻译
|
|
|
|
|
- $translationKey = 'shop::app.emails.' . strtolower($className) . '.subject';
|
|
|
|
|
- $translatedSubject = trans($translationKey);
|
|
|
|
|
-
|
|
|
|
|
- if ($translatedSubject && $translatedSubject !== $translationKey) {
|
|
|
|
|
- Log::info('Using translated subject', ['subject' => $translatedSubject]);
|
|
|
|
|
- return $translatedSubject;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法4: 根据邮件类型返回默认主题
|
|
|
|
|
- $subjectMap = [
|
|
|
|
|
- 'RegistrationNotification' => trans('shop::app.emails.customers.registration.subject'),
|
|
|
|
|
- 'SubscriptionNotification' => trans('shop::app.emails.customers.subscribed.subject'),
|
|
|
|
|
- 'EmailVerificationNotification' => trans('shop::app.emails.customers.verification.subject'),
|
|
|
|
|
- 'UpdatePasswordNotification' => trans('shop::app.emails.customers.update-password.subject'),
|
|
|
|
|
- 'ResetPasswordNotification' => trans('shop::app.emails.customers.reset-password.subject'),
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- if (isset($subjectMap[$className])) {
|
|
|
|
|
- Log::info('Using mapped subject', ['subject' => $subjectMap[$className]]);
|
|
|
|
|
- return $subjectMap[$className];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Log::info('Using default subject from class name', ['subject' => $defaultSubject]);
|
|
|
|
|
- return $defaultSubject;
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error('Extract subject error: ' . $e->getMessage());
|
|
|
|
|
- return 'Email Notification';
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Mailable 提取密送
|
|
|
|
|
- */
|
|
|
|
|
- protected function extractBccFromMailable($mailable): array
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- return property_exists($mailable, 'bcc') ? ($mailable->bcc ?? []) : [];
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Message 提取内容
|
|
|
|
|
|
|
+ * 从 Symfony Email 提取内容(优先 HTML,回退纯文本)
|
|
|
*/
|
|
*/
|
|
|
protected function extractContentFromMessage($message): string
|
|
protected function extractContentFromMessage($message): string
|
|
|
{
|
|
{
|
|
|
try {
|
|
try {
|
|
|
- $body = $message->getBody();
|
|
|
|
|
- return $body ? ($body->toString() ?? '') : '';
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- return '';
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ $html = method_exists($message, 'getHtmlBody') ? $message->getHtmlBody() : null;
|
|
|
|
|
+ $text = method_exists($message, 'getTextBody') ? $message->getTextBody() : null;
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Mailable 提取内容
|
|
|
|
|
- */
|
|
|
|
|
- protected function extractContentFromMailable($mailable): string
|
|
|
|
|
- {
|
|
|
|
|
- try {
|
|
|
|
|
- if (method_exists($mailable, 'render')) {
|
|
|
|
|
- $content = $mailable->render();
|
|
|
|
|
- return substr($content, 0, 65535);
|
|
|
|
|
- }
|
|
|
|
|
- return '';
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
|
|
+ $content = $html ?: $text ?: '';
|
|
|
|
|
+
|
|
|
|
|
+ return substr((string) $content, 0, 65535);
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
return '';
|
|
return '';
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 从 Message 检测模板
|
|
|
|
|
|
|
+ * 从 Symfony Email 检测模板
|
|
|
*/
|
|
*/
|
|
|
protected function detectTemplateFromMessage($message): ?string
|
|
protected function detectTemplateFromMessage($message): ?string
|
|
|
{
|
|
{
|
|
|
try {
|
|
try {
|
|
|
|
|
+ // Symfony Mailer 的 header 读取
|
|
|
$headers = $message->getHeaders();
|
|
$headers = $message->getHeaders();
|
|
|
|
|
|
|
|
- if ($headers && $headers->has('X-Template-Name')) {
|
|
|
|
|
- return $headers->get('X-Template-Name')->getFieldBody();
|
|
|
|
|
|
|
+ if ($headers && method_exists($headers, 'get')) {
|
|
|
|
|
+ $header = $headers->get('X-Template-Name');
|
|
|
|
|
+ if ($header) {
|
|
|
|
|
+ return $header->getBodyAsString();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$subject = $message->getSubject();
|
|
$subject = $message->getSubject();
|
|
|
|
|
+
|
|
|
return $this->detectTemplateBySubject($subject);
|
|
return $this->detectTemplateBySubject($subject);
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 Mailable 检测模板
|
|
|
|
|
- */
|
|
|
|
|
- protected function detectTemplateFromMailable($mailable): ?string
|
|
|
|
|
- {
|
|
|
|
|
- $className = get_class($mailable);
|
|
|
|
|
- $subject = $this->extractSubjectFromMailable($mailable);
|
|
|
|
|
-
|
|
|
|
|
- return $this->detectTemplateBySubject($subject, $className);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 根据主题检测模板类型
|
|
* 根据主题检测模板类型
|
|
|
*/
|
|
*/
|
|
|
protected function detectTemplateBySubject(?string $subject, ?string $className = ''): ?string
|
|
protected function detectTemplateBySubject(?string $subject, ?string $className = ''): ?string
|
|
|
{
|
|
{
|
|
|
- $text = strtolower(($subject ?? '') . ' ' . ($className ?? ''));
|
|
|
|
|
|
|
+ $text = strtolower(($subject ?? '').' '.($className ?? ''));
|
|
|
|
|
|
|
|
if (str_contains($text, 'registration')) {
|
|
if (str_contains($text, 'registration')) {
|
|
|
return 'customer_registration';
|
|
return 'customer_registration';
|
|
@@ -913,7 +171,7 @@ class EventServiceProvider extends ServiceProvider
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 格式化地址
|
|
|
|
|
|
|
+ * 格式化地址(兼容 Symfony Address 对象数组)
|
|
|
*/
|
|
*/
|
|
|
protected function formatAddresses($addresses): array
|
|
protected function formatAddresses($addresses): array
|
|
|
{
|
|
{
|
|
@@ -922,11 +180,19 @@ class EventServiceProvider extends ServiceProvider
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$result = [];
|
|
$result = [];
|
|
|
- foreach ($addresses as $email => $name) {
|
|
|
|
|
- $result[] = [
|
|
|
|
|
- 'email' => $email,
|
|
|
|
|
- 'name' => is_string($name) ? $name : null,
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($addresses as $key => $address) {
|
|
|
|
|
+ if ($address instanceof \Symfony\Component\Mime\Address) {
|
|
|
|
|
+ $result[] = [
|
|
|
|
|
+ 'email' => $address->getAddress(),
|
|
|
|
|
+ 'name' => $address->getName(),
|
|
|
|
|
+ ];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $result[] = [
|
|
|
|
|
+ 'email' => is_string($address) ? $address : $key,
|
|
|
|
|
+ 'name' => is_string($address) ? null : $address,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
return $result;
|