Files
anxinyan/server-api/app/support/MessageDispatcher.php
wushumin 9aac78b8da first
2026-05-11 15:28:27 +08:00

175 lines
5.0 KiB
PHP

<?php
namespace app\support;
use support\think\Db;
class MessageDispatcher
{
public function sendInboxEvent(string $eventCode, array $context): bool
{
$userId = (int)($context['user_id'] ?? 0);
$bizType = trim((string)($context['biz_type'] ?? ''));
$bizId = (int)($context['biz_id'] ?? 0);
if ($userId <= 0 || $bizType === '' || $bizId <= 0) {
return false;
}
if (!$this->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;
}
}