shouldSendByPreference($userId, $eventCode)) { return false; } $rule = Db::name('message_rules') ->where('event_code', $eventCode) ->where('channel', 'inbox') ->where('is_enabled', 1) ->order('id', 'asc') ->find(); if (!$rule) { $disabledRule = Db::name('message_rules') ->where('event_code', $eventCode) ->where('channel', 'inbox') ->find(); if ($disabledRule) { return false; } } $template = null; if ($rule) { $template = Db::name('message_templates') ->where('id', (int)$rule['template_id']) ->where('channel', 'inbox') ->where('is_enabled', 1) ->find(); } $alreadySent = Db::name('message_logs') ->where('user_id', $userId) ->where('biz_type', $bizType) ->where('biz_id', $bizId) ->where('channel', 'inbox') ->where('status', 'sent') ->find(); if ($alreadySent) { return false; } $title = $this->renderTemplate( $template['title'] ?? (string)($context['fallback_title'] ?? ''), $context ); $content = $this->renderTemplate( $template['content'] ?? (string)($context['fallback_content'] ?? ''), $context ); if ($title === '' && $content === '') { return false; } $now = date('Y-m-d H:i:s'); Db::name('user_messages')->insert([ 'user_id' => $userId, 'title' => $title !== '' ? $title : '系统通知', 'content' => $content, 'biz_type' => $bizType, 'biz_id' => $bizId, 'is_read' => 0, 'read_at' => null, 'created_at' => $now, 'updated_at' => $now, ]); Db::name('message_logs')->insert([ 'user_id' => $userId, 'template_id' => $template['id'] ?? null, 'biz_type' => $bizType, 'biz_id' => $bizId, 'channel' => 'inbox', 'status' => 'sent', 'fail_reason' => '', 'sent_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]); return true; } private function shouldSendByPreference(int $userId, string $eventCode): bool { $preferenceKey = $this->preferenceKeyForEvent($eventCode); if ($preferenceKey === '') { return true; } $configValue = Db::name('system_configs') ->where('config_group', 'user_settings') ->where('config_key', 'user_' . $userId) ->value('config_value'); if (!is_string($configValue) || $configValue === '') { return $this->defaultPreference($preferenceKey); } $decoded = json_decode($configValue, true); if (!is_array($decoded)) { return $this->defaultPreference($preferenceKey); } if (!array_key_exists($preferenceKey, $decoded)) { return $this->defaultPreference($preferenceKey); } return (bool)$decoded[$preferenceKey]; } private function preferenceKeyForEvent(string $eventCode): string { return match ($eventCode) { 'order_created' => 'notify_order', 'return_shipped' => 'notify_order', 'return_received' => 'notify_order', 'report_published' => 'notify_report', 'supplement_required' => 'notify_supplement', 'ticket_reply', 'ticket_waiting_user', 'ticket_resolved', 'ticket_closed' => 'notify_ticket', default => '', }; } private function defaultPreference(string $key): bool { return match ($key) { 'marketing_notify', 'privacy_mode' => false, default => true, }; } private function renderTemplate(string $text, array $context): string { if ($text === '') { return ''; } return preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', function (array $matches) use ($context) { $key = $matches[1]; if (!array_key_exists($key, $context)) { return ''; } $value = $context[$key]; if (is_scalar($value) || $value === null) { return (string)$value; } return ''; }, $text) ?? $text; } }