|
@@ -14,18 +14,18 @@ class EventServiceProvider extends ServiceProvider
|
|
|
{
|
|
{
|
|
|
public function boot(): void
|
|
public function boot(): void
|
|
|
{
|
|
{
|
|
|
- Log::info('Email EventServiceProvider booting...');
|
|
|
|
|
|
|
+ //Log::info('Email EventServiceProvider booting...');
|
|
|
|
|
|
|
|
// 方法1: 监听 Laravel 邮件事件(同步和异步都有效)
|
|
// 方法1: 监听 Laravel 邮件事件(同步和异步都有效)
|
|
|
- $this->registerMailEvents();
|
|
|
|
|
|
|
+ //$this->registerMailEvents();
|
|
|
|
|
|
|
|
// 方法2: 监听队列事件(作为备用)
|
|
// 方法2: 监听队列事件(作为备用)
|
|
|
$this->registerQueueEvents();
|
|
$this->registerQueueEvents();
|
|
|
|
|
|
|
|
// 方法3: Hook Mail facade(最可靠)
|
|
// 方法3: Hook Mail facade(最可靠)
|
|
|
- $this->hookMailFacade();
|
|
|
|
|
|
|
+ //$this->hookMailFacade();
|
|
|
|
|
|
|
|
- Log::info('Email EventServiceProvider booted successfully');
|
|
|
|
|
|
|
+ //Log::info('Email EventServiceProvider booted successfully');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -74,6 +74,10 @@ class EventServiceProvider extends ServiceProvider
|
|
|
// 监听邮件任务被推入队列时
|
|
// 监听邮件任务被推入队列时
|
|
|
Event::listen('Illuminate\Queue\Events\JobQueued', function ($event) {
|
|
Event::listen('Illuminate\Queue\Events\JobQueued', function ($event) {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ /*Log::info('JobQueued event triggered', [
|
|
|
|
|
+ 'event_class' => get_class($event),
|
|
|
|
|
+ ]);*/
|
|
|
|
|
+
|
|
|
// JobQueued 事件有不同的属性结构
|
|
// JobQueued 事件有不同的属性结构
|
|
|
$payload = null;
|
|
$payload = null;
|
|
|
|
|
|
|
@@ -94,29 +98,41 @@ class EventServiceProvider extends ServiceProvider
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 如果还是无法获取 payload,直接返回
|
|
// 如果还是无法获取 payload,直接返回
|
|
|
- if (!is_array($payload) || !isset($payload['data']['command'])) {
|
|
|
|
|
|
|
+ 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;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$command = @unserialize($payload['data']['command']);
|
|
$command = @unserialize($payload['data']['command']);
|
|
|
|
|
|
|
|
if (!$command) {
|
|
if (!$command) {
|
|
|
|
|
+ Log::warning('Failed to unserialize command');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 检查是否是 SendQueuedMailable
|
|
// 检查是否是 SendQueuedMailable
|
|
|
if (!$command instanceof \Illuminate\Mail\SendQueuedMailable) {
|
|
if (!$command instanceof \Illuminate\Mail\SendQueuedMailable) {
|
|
|
|
|
+ Log::info('Not a SendQueuedMailable', [
|
|
|
|
|
+ 'command_class' => get_class($command),
|
|
|
|
|
+ ]);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$mailable = $command->mailable;
|
|
$mailable = $command->mailable;
|
|
|
|
|
|
|
|
if (!$mailable instanceof \Illuminate\Mail\Mailable) {
|
|
if (!$mailable instanceof \Illuminate\Mail\Mailable) {
|
|
|
|
|
+ Log::warning('Mailable is not valid');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Log::info('Mail job queued', [
|
|
Log::info('Mail job queued', [
|
|
|
'mailable_class' => get_class($mailable),
|
|
'mailable_class' => get_class($mailable),
|
|
|
|
|
+ 'queue' => $payload['queue'] ?? 'pending',
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
// 关键修复:从 SendQueuedMailable 命令中提取收件人
|
|
// 关键修复:从 SendQueuedMailable 命令中提取收件人
|
|
@@ -135,16 +151,29 @@ class EventServiceProvider extends ServiceProvider
|
|
|
$name = is_array($recipient) ? ($recipient['name'] ?? null) : null;
|
|
$name = is_array($recipient) ? ($recipient['name'] ?? null) : null;
|
|
|
|
|
|
|
|
if (!$email) {
|
|
if (!$email) {
|
|
|
|
|
+ Log::warning('No email address found for recipient', ['recipient' => $recipient]);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 检查是否已存在
|
|
|
|
|
|
|
+ // 清理姓名(去除多余空格,如果为空则设为 null)
|
|
|
|
|
+ if ($name) {
|
|
|
|
|
+ $name = trim(preg_replace('/\s+/', ' ', $name));
|
|
|
|
|
+ if (empty($name)) {
|
|
|
|
|
+ $name = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否已存在(扩大时间窗口到30分钟,避免重复但允许同一用户多次收到不同邮件)
|
|
|
$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(30))
|
|
|
->exists();
|
|
->exists();
|
|
|
|
|
|
|
|
if ($exists) {
|
|
if ($exists) {
|
|
|
|
|
+ Log::info('Email log already exists, skipping', [
|
|
|
|
|
+ 'email' => $email,
|
|
|
|
|
+ 'subject' => $subject,
|
|
|
|
|
+ ]);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -154,18 +183,24 @@ class EventServiceProvider extends ServiceProvider
|
|
|
'subject' => $subject,
|
|
'subject' => $subject,
|
|
|
'content' => $this->extractContentFromMailable($mailable),
|
|
'content' => $this->extractContentFromMailable($mailable),
|
|
|
'template' => $this->detectTemplateFromMailable($mailable),
|
|
'template' => $this->detectTemplateFromMailable($mailable),
|
|
|
- 'status' => 'queued',
|
|
|
|
|
|
|
+ 'status' => 'sent', // 初始状态为 pending(待发送)
|
|
|
|
|
+ 'sent_at' => now(), // 发送时间为空,等待发送成功后更新
|
|
|
'metadata' => json_encode([
|
|
'metadata' => json_encode([
|
|
|
'job_id' => $payload['uuid'] ?? null,
|
|
'job_id' => $payload['uuid'] ?? null,
|
|
|
'mailable_class' => get_class($mailable),
|
|
'mailable_class' => get_class($mailable),
|
|
|
'queue' => $payload['queue'] ?? null,
|
|
'queue' => $payload['queue'] ?? null,
|
|
|
|
|
+ 'connection' => $payload['connection'] ?? null,
|
|
|
|
|
+ 'created_via' => 'JobQueued',
|
|
|
]),
|
|
]),
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
Log::info('Email log created from queued job', [
|
|
Log::info('Email log created from queued job', [
|
|
|
'log_id' => $log->id,
|
|
'log_id' => $log->id,
|
|
|
'email' => $email,
|
|
'email' => $email,
|
|
|
|
|
+ 'name' => $name,
|
|
|
'subject' => $subject,
|
|
'subject' => $subject,
|
|
|
|
|
+ 'status' => 'sent',
|
|
|
|
|
+ 'sent_at' => $log->sent_at,
|
|
|
]);
|
|
]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -176,34 +211,24 @@ class EventServiceProvider extends ServiceProvider
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // 监听队列任务处理前
|
|
|
|
|
- \Illuminate\Support\Facades\Queue::before(function (\Illuminate\Queue\Events\JobProcessing $event) {
|
|
|
|
|
- $this->handleQueueJob($event->job, 'processing');
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 监听队列任务处理后
|
|
|
|
|
- \Illuminate\Support\Facades\Queue::after(function (\Illuminate\Queue\Events\JobProcessed $event) {
|
|
|
|
|
- $this->handleQueueJobAfter($event->job);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 监听队列任务失败
|
|
|
|
|
- \Illuminate\Support\Facades\Queue::failing(function (\Illuminate\Queue\Events\JobFailed $event) {
|
|
|
|
|
- $this->handleQueueJobFailed($event->job, $event->exception);
|
|
|
|
|
- });
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 从 SendQueuedMailable 命令中提取收件人
|
|
* 从 SendQueuedMailable 命令中提取收件人
|
|
|
*/
|
|
*/
|
|
|
protected function extractRecipientsFromCommand($command, $mailable): array
|
|
protected function extractRecipientsFromCommand($command, $mailable): array
|
|
|
{
|
|
{
|
|
|
try {
|
|
try {
|
|
|
- Log::info('Extracting recipients from SendQueuedMailable command');
|
|
|
|
|
|
|
+ Log::info('Extracting recipients from SendQueuedMailable command', [
|
|
|
|
|
+ 'command_class' => get_class($command),
|
|
|
|
|
+ 'mailable_class' => get_class($mailable),
|
|
|
|
|
+ ]);
|
|
|
|
|
|
|
|
// 方法1: 从命令对象的 to 属性获取(Laravel 会在构建时填充)
|
|
// 方法1: 从命令对象的 to 属性获取(Laravel 会在构建时填充)
|
|
|
if (property_exists($command, 'to') && !empty($command->to)) {
|
|
if (property_exists($command, 'to') && !empty($command->to)) {
|
|
|
Log::info('Found recipients in command->to', ['to' => $command->to]);
|
|
Log::info('Found recipients in command->to', ['to' => $command->to]);
|
|
|
- return $command->to;
|
|
|
|
|
|
|
+ return is_array($command->to) ? $command->to : [$command->to];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 方法2: 尝试反射获取命令的 to 属性
|
|
// 方法2: 尝试反射获取命令的 to 属性
|
|
@@ -215,46 +240,79 @@ class EventServiceProvider extends ServiceProvider
|
|
|
|
|
|
|
|
if (!empty($to)) {
|
|
if (!empty($to)) {
|
|
|
Log::info('Extracted recipients from command via reflection', ['to' => $to]);
|
|
Log::info('Extracted recipients from command via reflection', ['to' => $to]);
|
|
|
- return $to;
|
|
|
|
|
|
|
+ return is_array($to) ? $to : [$to];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 方法3: 从 Mailable 的 customer 属性推断(针对 RegistrationNotification)
|
|
|
|
|
- if (property_exists($mailable, 'customer')) {
|
|
|
|
|
- $reflection = new \ReflectionClass($mailable);
|
|
|
|
|
- $property = $reflection->getProperty('customer');
|
|
|
|
|
|
|
+ // 方法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);
|
|
$property->setAccessible(true);
|
|
|
- $customer = $property->getValue($mailable);
|
|
|
|
|
|
|
+ $value = $property->getValue($mailable);
|
|
|
|
|
|
|
|
- if ($customer && isset($customer->email)) {
|
|
|
|
|
|
|
+ // 如果属性值是字符串且看起来像邮箱
|
|
|
|
|
+ if (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
|
|
$recipients = [[
|
|
$recipients = [[
|
|
|
- 'address' => $customer->email,
|
|
|
|
|
- 'name' => $customer->name ?? $customer->first_name ?? null,
|
|
|
|
|
|
|
+ 'address' => $value,
|
|
|
|
|
+ 'name' => null,
|
|
|
]];
|
|
]];
|
|
|
- Log::info('Extracted recipient from mailable customer', ['recipients' => $recipients]);
|
|
|
|
|
|
|
+ Log::info("Extracted email from property {$property->getName()}", ['recipients' => $recipients]);
|
|
|
return $recipients;
|
|
return $recipients;
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 方法4: 调用 envelope 方法
|
|
|
|
|
- if (method_exists($mailable, 'envelope')) {
|
|
|
|
|
- try {
|
|
|
|
|
- $envelope = $mailable->envelope();
|
|
|
|
|
- if ($envelope && method_exists($envelope, 'to')) {
|
|
|
|
|
- $to = $envelope->to();
|
|
|
|
|
- if (!empty($to)) {
|
|
|
|
|
- Log::info('Extracted recipients from envelope', ['to' => $to]);
|
|
|
|
|
- return $to;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::warning('Failed to get envelope: ' . $e->getMessage());
|
|
|
|
|
|
|
+ // 如果属性是对象且有 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', [
|
|
Log::warning('Could not extract recipients from command or mailable', [
|
|
|
'command_class' => get_class($command),
|
|
'command_class' => get_class($command),
|
|
|
'mailable_class' => get_class($mailable),
|
|
'mailable_class' => get_class($mailable),
|
|
|
|
|
+ 'mailable_properties' => array_keys(get_object_vars($mailable)),
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
return [];
|
|
return [];
|
|
@@ -266,6 +324,7 @@ class EventServiceProvider extends ServiceProvider
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Hook Mail facade 以捕获所有邮件发送
|
|
* Hook Mail facade 以捕获所有邮件发送
|
|
|
*/
|
|
*/
|
|
@@ -405,7 +464,7 @@ class EventServiceProvider extends ServiceProvider
|
|
|
|
|
|
|
|
foreach ($to as $email => $name) {
|
|
foreach ($to as $email => $name) {
|
|
|
$updated = EmailLog::where('recipient_email', $email)
|
|
$updated = EmailLog::where('recipient_email', $email)
|
|
|
- ->whereIn('status', ['pending', 'queued', 'processing'])
|
|
|
|
|
|
|
+ ->where('status', 'pending') // 只更新 pending 状态
|
|
|
->orderBy('created_at', 'desc')
|
|
->orderBy('created_at', 'desc')
|
|
|
->limit(1)
|
|
->limit(1)
|
|
|
->update([
|
|
->update([
|
|
@@ -483,6 +542,12 @@ class EventServiceProvider extends ServiceProvider
|
|
|
$to = $this->extractRecipientsFromMailable($mailable);
|
|
$to = $this->extractRecipientsFromMailable($mailable);
|
|
|
$subject = $this->extractSubjectFromMailable($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) {
|
|
foreach ($to as $recipient) {
|
|
|
$email = is_array($recipient) ? ($recipient['address'] ?? $recipient['email'] ?? null) : $recipient;
|
|
$email = is_array($recipient) ? ($recipient['address'] ?? $recipient['email'] ?? null) : $recipient;
|
|
|
|
|
|
|
@@ -490,22 +555,37 @@ class EventServiceProvider extends ServiceProvider
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- EmailLog::where('recipient_email', $email)
|
|
|
|
|
|
|
+ $updated = EmailLog::where('recipient_email', $email)
|
|
|
->where('subject', $subject)
|
|
->where('subject', $subject)
|
|
|
- ->whereIn('status', ['queued', 'processing'])
|
|
|
|
|
|
|
+ ->where('status', 'pending') // 只更新 pending 状态的记录
|
|
|
->orderBy('created_at', 'desc')
|
|
->orderBy('created_at', 'desc')
|
|
|
->limit(1)
|
|
->limit(1)
|
|
|
->update([
|
|
->update([
|
|
|
'status' => 'sent',
|
|
'status' => 'sent',
|
|
|
'sent_at' => now(),
|
|
'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) {
|
|
} catch (\Exception $e) {
|
|
|
- Log::error('Handle queue job after error: ' . $e->getMessage());
|
|
|
|
|
|
|
+ Log::error('Handle queue job after error: ' . $e->getMessage(), [
|
|
|
|
|
+ 'trace' => $e->getTraceAsString(),
|
|
|
|
|
+ ]);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 处理队列任务失败
|
|
* 处理队列任务失败
|
|
|
*/
|
|
*/
|
|
@@ -542,7 +622,7 @@ class EventServiceProvider extends ServiceProvider
|
|
|
|
|
|
|
|
EmailLog::where('recipient_email', $email)
|
|
EmailLog::where('recipient_email', $email)
|
|
|
->where('subject', $subject)
|
|
->where('subject', $subject)
|
|
|
- ->whereIn('status', ['queued', 'processing'])
|
|
|
|
|
|
|
+ ->where('status', 'pending') // 只更新 pending 状态的记录
|
|
|
->orderBy('created_at', 'desc')
|
|
->orderBy('created_at', 'desc')
|
|
|
->limit(1)
|
|
->limit(1)
|
|
|
->update([
|
|
->update([
|
|
@@ -586,35 +666,60 @@ class EventServiceProvider extends ServiceProvider
|
|
|
return $to ?? [];
|
|
return $to ?? [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 方法3: 检查构造函数参数(Customer 对象)
|
|
|
|
|
- if ($reflection->hasProperty('customer')) {
|
|
|
|
|
- $property = $reflection->getProperty('customer');
|
|
|
|
|
|
|
+ // 方法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);
|
|
$property->setAccessible(true);
|
|
|
- $customer = $property->getValue($mailable);
|
|
|
|
|
|
|
+ $value = $property->getValue($mailable);
|
|
|
|
|
|
|
|
- if ($customer && isset($customer->email)) {
|
|
|
|
|
|
|
+ // 如果属性值是字符串且看起来像邮箱
|
|
|
|
|
+ if (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
|
|
$recipients = [[
|
|
$recipients = [[
|
|
|
- 'address' => $customer->email,
|
|
|
|
|
- 'name' => $customer->name ?? $customer->first_name ?? null,
|
|
|
|
|
|
|
+ 'address' => $value,
|
|
|
|
|
+ 'name' => null,
|
|
|
]];
|
|
]];
|
|
|
- Log::info('Extracted recipient from customer property', ['recipients' => $recipients]);
|
|
|
|
|
|
|
+ Log::info("Extracted email from property {$property->getName()}", ['recipients' => $recipients]);
|
|
|
return $recipients;
|
|
return $recipients;
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 方法4: 尝试调用 envelope 方法获取收件人
|
|
|
|
|
- if (method_exists($mailable, 'envelope')) {
|
|
|
|
|
- try {
|
|
|
|
|
- $envelope = $mailable->envelope();
|
|
|
|
|
- if ($envelope && method_exists($envelope, 'to')) {
|
|
|
|
|
- $to = $envelope->to();
|
|
|
|
|
- if (!empty($to)) {
|
|
|
|
|
- Log::info('Extracted to from envelope', ['to' => $to]);
|
|
|
|
|
- return $to;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::warning('Failed to get envelope to: ' . $e->getMessage());
|
|
|
|
|
|
|
+ // 如果属性是对象且有 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;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -632,6 +737,7 @@ class EventServiceProvider extends ServiceProvider
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 从 Mailable 提取主题
|
|
* 从 Mailable 提取主题
|
|
|
*/
|
|
*/
|
|
@@ -643,10 +749,10 @@ class EventServiceProvider extends ServiceProvider
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
// 方法1: 尝试直接访问 subject 属性
|
|
// 方法1: 尝试直接访问 subject 属性
|
|
|
- if (property_exists($mailable, 'subject')) {
|
|
|
|
|
|
|
+ if (property_exists($mailable, 'subject') && !empty($mailable->subject)) {
|
|
|
$subject = $mailable->subject;
|
|
$subject = $mailable->subject;
|
|
|
Log::info('Found subject property', ['subject' => $subject]);
|
|
Log::info('Found subject property', ['subject' => $subject]);
|
|
|
- return $subject ?? '';
|
|
|
|
|
|
|
+ return $subject;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 方法2: 尝试反射获取 subject 属性
|
|
// 方法2: 尝试反射获取 subject 属性
|
|
@@ -656,39 +762,50 @@ class EventServiceProvider extends ServiceProvider
|
|
|
$property = $reflection->getProperty('subject');
|
|
$property = $reflection->getProperty('subject');
|
|
|
$property->setAccessible(true);
|
|
$property->setAccessible(true);
|
|
|
$subject = $property->getValue($mailable);
|
|
$subject = $property->getValue($mailable);
|
|
|
- Log::info('Extracted subject via reflection', ['subject' => $subject]);
|
|
|
|
|
- return $subject ?? '';
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 方法3: 尝试调用 envelope 方法获取主题
|
|
|
|
|
- if (method_exists($mailable, 'envelope')) {
|
|
|
|
|
- try {
|
|
|
|
|
- $envelope = $mailable->envelope();
|
|
|
|
|
- if ($envelope && method_exists($envelope, 'subject')) {
|
|
|
|
|
- $subject = $envelope->subject();
|
|
|
|
|
- if ($subject) {
|
|
|
|
|
- Log::info('Extracted subject from envelope', ['subject' => $subject]);
|
|
|
|
|
- return $subject;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::warning('Failed to get envelope subject: ' . $e->getMessage());
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (!empty($subject)) {
|
|
|
|
|
+ Log::info('Extracted subject via reflection', ['subject' => $subject]);
|
|
|
|
|
+ return $subject;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 方法4: 从类名推断
|
|
|
|
|
|
|
+ // 方法3: 从类名推断并翻译
|
|
|
$className = class_basename($mailable);
|
|
$className = class_basename($mailable);
|
|
|
$defaultSubject = str_replace(['Notification', 'Mail'], '', $className);
|
|
$defaultSubject = str_replace(['Notification', 'Mail'], '', $className);
|
|
|
- Log::info('Using default subject from class name', ['subject' => $defaultSubject]);
|
|
|
|
|
|
|
|
|
|
|
|
+ // 尝试从语言文件获取翻译
|
|
|
|
|
+ $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;
|
|
return $defaultSubject;
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
|
Log::error('Extract subject error: ' . $e->getMessage());
|
|
Log::error('Extract subject error: ' . $e->getMessage());
|
|
|
- return 'Unknown Subject';
|
|
|
|
|
|
|
+ return 'Email Notification';
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 从 Mailable 提取密送
|
|
* 从 Mailable 提取密送
|
|
|
*/
|
|
*/
|