怎样在PHP项目中实现用户自定义通知?

wen java案例 2

本文目录导读:

怎样在PHP项目中实现用户自定义通知?

  1. 系统架构设计
  2. 核心通知系统实现
  3. 用户通知偏好管理
  4. 自定义通知规则
  5. 使用示例
  6. 前端实现示例

我来详细讲解如何在PHP项目中实现用户自定义通知系统。

系统架构设计

数据库设计

-- 通知类型表
CREATE TABLE notification_types (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    code VARCHAR(50) UNIQUE NOT NULL,
    description TEXT,
    is_system TINYINT(1) DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 用户通知偏好表
CREATE TABLE user_notification_preferences (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    notification_type_id INT NOT NULL,
    email_enabled TINYINT(1) DEFAULT 1,
    sms_enabled TINYINT(1) DEFAULT 0,
    push_enabled TINYINT(1) DEFAULT 1,
    in_app_enabled TINYINT(1) DEFAULT 1,
    custom_settings JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (notification_type_id) REFERENCES notification_types(id),
    UNIQUE KEY (user_id, notification_type_id)
);
-- 通知记录表
CREATE TABLE notifications (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    type_id INT NOT NULL,VARCHAR(255) NOT NULL,
    message TEXT NOT NULL,
    data JSON,
    read_at TIMESTAMP NULL,
    sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (type_id) REFERENCES notification_types(id),
    INDEX idx_user_notification (user_id, read_at)
);
-- 通知通道表
CREATE TABLE notification_channels (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    code VARCHAR(50) UNIQUE NOT NULL,
    handler_class VARCHAR(255) NOT NULL,
    config_schema JSON,
    is_active TINYINT(1) DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

核心通知系统实现

通知管理器类

<?php
namespace App\Notifications;
class NotificationManager {
    private $db;
    private $channels = [];
    private $templates = [];
    public function __construct($db) {
        $this->db = $db;
        $this->loadChannels();
    }
    /**
     * 加载可用的通知通道
     */
    private function loadChannels() {
        $query = "SELECT * FROM notification_channels WHERE is_active = 1";
        $result = $this->db->query($query);
        while ($channel = $result->fetch_assoc()) {
            if (class_exists($channel['handler_class'])) {
                $handler = new $channel['handler_class']($channel);
                $this->channels[$channel['code']] = $handler;
            }
        }
    }
    /**
     * 发送通知
     */
    public function send($userId, $typeCode, $data = []) {
        // 获取通知类型
        $type = $this->getNotificationTypeByCode($typeCode);
        if (!$type) {
            throw new \Exception("Notification type not found: {$typeCode}");
        }
        // 获取用户偏好
        $preferences = $this->getUserPreferences($userId, $type['id']);
        // 根据偏好发送通知
        $sentChannels = [];
        foreach ($preferences as $channelCode => $enabled) {
            if ($enabled && isset($this->channels[$channelCode])) {
                try {
                    $this->channels[$channelCode]->send($userId, $type, $data);
                    $sentChannels[] = $channelCode;
                } catch (\Exception $e) {
                    // 记录错误但继续发送其他通道
                    error_log("Failed to send notification via {$channelCode}: " . $e->getMessage());
                }
            }
        }
        // 记录通知到数据库
        $this->saveNotification($userId, $type, $data, $sentChannels);
        return $sentChannels;
    }
    /**
     * 批量发送通知
     */
    public function sendBulk($userIds, $typeCode, $data = []) {
        $results = [];
        foreach ($userIds as $userId) {
            try {
                $channels = $this->send($userId, $typeCode, $data);
                $results[$userId] = ['success' => true, 'channels' => $channels];
            } catch (\Exception $e) {
                $results[$userId] = ['success' => false, 'error' => $e->getMessage()];
            }
        }
        return $results;
    }
    /**
     * 获取用户通知偏好
     */
    private function getUserPreferences($userId, $typeId) {
        $query = "SELECT * FROM user_notification_preferences 
                  WHERE user_id = ? AND notification_type_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param('ii', $userId, $typeId);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows > 0) {
            $pref = $result->fetch_assoc();
            return [
                'email' => $pref['email_enabled'],
                'sms' => $pref['sms_enabled'],
                'push' => $pref['push_enabled'],
                'in_app' => $pref['in_app_enabled']
            ];
        }
        // 默认偏好
        return [
            'email' => true,
            'in_app' => true,
            'push' => true,
            'sms' => false
        ];
    }
    /**
     * 保存通知到数据库
     */
    private function saveNotification($userId, $type, $data, $channels) {
        $title = $this->renderTemplate($type['name'] . '_title', $data);
        $message = $this->renderTemplate($type['name'] . '_message', $data);
        $query = "INSERT INTO notifications (user_id, type_id, title, message, data) 
                  VALUES (?, ?, ?, ?, ?)";
        $stmt = $this->db->prepare($query);
        $jsonData = json_encode([
            'data' => $data,
            'channels' => $channels
        ]);
        $stmt->bind_param('iisss', $userId, $type['id'], $title, $message, $jsonData);
        $stmt->execute();
    }
    /**
     * 获取通知类型
     */
    private function getNotificationTypeByCode($code) {
        $query = "SELECT * FROM notification_types WHERE code = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param('s', $code);
        $stmt->execute();
        return $stmt->get_result()->fetch_assoc();
    }
    /**
     * 渲染模板
     */
    private function renderTemplate($templateCode, $data) {
        // 从数据库或文件加载模板
        $template = $this->loadTemplate($templateCode);
        // 替换变量
        foreach ($data as $key => $value) {
            $template = str_replace("{{{$key}}}", $value, $template);
        }
        return $template;
    }
}

通知通道接口

<?php
namespace App\Notifications\Channels;
interface NotificationChannelInterface {
    public function send($userId, $type, $data);
    public function validateConfig();
}

邮件通道实现

<?php
namespace App\Notifications\Channels;
use App\Notifications\Channels\NotificationChannelInterface;
class EmailChannel implements NotificationChannelInterface {
    private $config;
    public function __construct($channelData) {
        $this->config = json_decode($channelData['config_schema'], true);
    }
    public function send($userId, $type, $data) {
        // 获取用户邮箱
        $email = $this->getUserEmail($userId);
        // 渲染邮件内容
        $subject = $this->renderSubject($type, $data);
        $body = $this->renderBody($type, $data);
        // 发送邮件
        return mail($email, $subject, $body, $this->getHeaders());
    }
    private function getUserEmail($userId) {
        // 从数据库获取用户邮箱
        return "user{$userId}@example.com";
    }
    private function renderSubject($type, $data) {
        return "[{$type['name']}] " . ($data['subject'] ?? 'Notification');
    }
    private function renderBody($type, $data) {
        $html = "<h1>{$type['name']}</h1>";
        $html .= "<p>" . ($data['message'] ?? 'You have a new notification') . "</p>";
        if (isset($data['action_url'])) {
            $html .= "<a href='{$data['action_url']}' style='padding: 10px 20px; background: #007bff; color: white; text-decoration: none;'>View Details</a>";
        }
        return $html;
    }
    private function getHeaders() {
        return "MIME-Version: 1.0\r\n" .
               "Content-type: text/html; charset=UTF-8\r\n" .
               "From: notifications@example.com\r\n";
    }
    public function validateConfig() {
        return isset($this->config['smtp_host']) && 
               isset($this->config['smtp_port']);
    }
}

用户通知偏好管理

偏好设置控制器

<?php
namespace App\Controllers;
class NotificationPreferencesController {
    private $db;
    private $notificationManager;
    public function __construct($db) {
        $this->db = $db;
        $this->notificationManager = new \App\Notifications\NotificationManager($db);
    }
    /**
     * 获取用户所有通知偏好
     */
    public function getPreferences($userId) {
        $query = "SELECT nt.*, unp.* 
                  FROM notification_types nt
                  LEFT JOIN user_notification_preferences unp 
                  ON nt.id = unp.notification_type_id AND unp.user_id = ?
                  ORDER BY nt.name";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param('i', $userId);
        $stmt->execute();
        $preferences = [];
        $result = $stmt->get_result();
        while ($row = $result->fetch_assoc()) {
            $preferences[] = [
                'type_id' => $row['id'],
                'type_name' => $row['name'],
                'type_code' => $row['code'],
                'description' => $row['description'],
                'channels' => [
                    'email' => $row['email_enabled'] ?? 1,
                    'sms' => $row['sms_enabled'] ?? 0,
                    'push' => $row['push_enabled'] ?? 1,
                    'in_app' => $row['in_app_enabled'] ?? 1
                ],
                'custom_settings' => json_decode($row['custom_settings'] ?? '{}', true)
            ];
        }
        return $preferences;
    }
    /**
     * 更新用户通知偏好
     */
    public function updatePreference($userId, $typeId, $settings) {
        // 检查是否已存在偏好设置
        $query = "SELECT id FROM user_notification_preferences 
                  WHERE user_id = ? AND notification_type_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param('ii', $userId, $typeId);
        $stmt->execute();
        if ($stmt->get_result()->num_rows > 0) {
            // 更新现有偏好
            $query = "UPDATE user_notification_preferences SET 
                      email_enabled = ?, sms_enabled = ?, 
                      push_enabled = ?, in_app_enabled = ?,
                      custom_settings = ?,
                      updated_at = NOW()
                      WHERE user_id = ? AND notification_type_id = ?";
        } else {
            // 创建新偏好
            $query = "INSERT INTO user_notification_preferences 
                      (user_id, notification_type_id, email_enabled, sms_enabled, 
                       push_enabled, in_app_enabled, custom_settings) 
                      VALUES (?, ?, ?, ?, ?, ?, ?)";
        }
        $stmt = $this->db->prepare($query);
        $customSettings = json_encode($settings['custom_settings'] ?? []);
        $email = $settings['email'] ?? 1;
        $sms = $settings['sms'] ?? 0;
        $push = $settings['push'] ?? 1;
        $inApp = $settings['in_app'] ?? 1;
        $stmt->bind_param('iiiiisi', 
            $email, $sms, $push, $inApp, 
            $customSettings, $userId, $typeId
        );
        return $stmt->execute();
    }
    /**
     * 批量更新通知偏好
     */
    public function batchUpdatePreferences($userId, $preferences) {
        $success = true;
        foreach ($preferences as $typeId => $settings) {
            if (!$this->updatePreference($userId, $typeId, $settings)) {
                $success = false;
            }
        }
        return $success;
    }
}

自定义通知规则

通知规则引擎

<?php
namespace App\Notifications;
class NotificationRuleEngine {
    private $db;
    private $rules = [];
    public function __construct($db) {
        $this->db = $db;
        $this->loadRules();
    }
    /**
     * 加载用户自定义规则
     */
    private function loadRules() {
        $query = "SELECT * FROM notification_rules WHERE is_active = 1";
        $result = $this->db->query($query);
        while ($rule = $result->fetch_assoc()) {
            $this->rules[] = $this->parseRule($rule);
        }
    }
    /**
     * 解析规则
     */
    private function parseRule($rule) {
        return [
            'id' => $rule['id'],
            'user_id' => $rule['user_id'],
            'conditions' => json_decode($rule['conditions'], true),
            'actions' => json_decode($rule['actions'], true),
            'priority' => $rule['priority']
        ];
    }
    /**
     * 评估规则
     */
    public function evaluate($event, $context) {
        $matchedRules = [];
        foreach ($this->rules as $rule) {
            if ($this->matchesUser($rule, $context['user_id'])) {
                if ($this->evaluateConditions($rule['conditions'], $event, $context)) {
                    $matchedRules[] = $rule;
                }
            }
        }
        // 按优先级排序
        usort($matchedRules, function($a, $b) {
            return $b['priority'] - $a['priority'];
        });
        return $matchedRules;
    }
    /**
     * 检查规则是否匹配用户
     */
    private function matchesUser($rule, $userId) {
        return $rule['user_id'] === null || $rule['user_id'] === $userId;
    }
    /**
     * 评估条件
     */
    private function evaluateConditions($conditions, $event, $context) {
        foreach ($conditions as $condition) {
            if (!$this->evaluateSingleCondition($condition, $event, $context)) {
                return false;
            }
        }
        return true;
    }
    /**
     * 评估单个条件
     */
    private function evaluateSingleCondition($condition, $event, $context) {
        $field = $condition['field'];
        $operator = $condition['operator'];
        $value = $condition['value'];
        $actualValue = $context[$field] ?? null;
        switch ($operator) {
            case 'equals':
                return $actualValue === $value;
            case 'not_equals':
                return $actualValue !== $value;
            case 'contains':
                return strpos($actualValue, $value) !== false;
            case 'greater_than':
                return $actualValue > $value;
            case 'less_than':
                return $actualValue < $value;
            case 'in':
                return in_array($actualValue, $value);
            case 'not_in':
                return !in_array($actualValue, $value);
            default:
                return true;
        }
    }
}

使用示例

发送通知示例

<?php
// 初始化通知管理器
$notificationManager = new \App\Notifications\NotificationManager($db);
// 发送新订单通知
$notificationManager->send(
    $userId = 123,
    $typeCode = 'new_order',
    $data = [
        'order_id' => 45678,
        'total' => '$199.99',
        'items' => 3,
        'action_url' => '/orders/45678'
    ]
);
// 批量发送通知
$userIds = [123, 456, 789];
$results = $notificationManager->sendBulk(
    $userIds,
    'promotion',
    [
        'promotion_name' => 'Summer Sale',
        'discount' => '20%',
        'expiry' => '2024-08-31'
    ]
);

用户偏好设置示例

<?php
// 获取用户通知偏好
$prefsController = new \App\Controllers\NotificationPreferencesController($db);
$preferences = $prefsController->getPreferences($userId);
// 更新特定通知类型的偏好
$prefsController->updatePreference($userId, $typeId = 1, [
    'email' => true,
    'sms' => false,
    'push' => true,
    'in_app' => true,
    'custom_settings' => [
        'quiet_hours_start' => '22:00',
        'quiet_hours_end' => '08:00',
        'digest_frequency' => 'daily'
    ]
]);

自定义规则示例

<?php
// 创建自定义规则
$ruleEngine = new \App\Notifications\NotificationRuleEngine($db);
// 定义规则
$rule = [
    'user_id' => $userId,
    'conditions' => [
        [
            'field' => 'amount',
            'operator' => 'greater_than',
            'value' => 1000
        ],
        [
            'field' => 'type',
            'operator' => 'equals',
            'value' => 'purchase'
        ]
    ],
    'actions' => [
        'send_email' => true,
        'send_sms' => true,
        'priority' => 1
    ]
];
// 保存规则到数据库
$query = "INSERT INTO notification_rules (user_id, conditions, actions, priority) 
          VALUES (?, ?, ?, ?)";
$stmt = $db->prepare($query);
$stmt->bind_param('issi', 
    $rule['user_id'],
    json_encode($rule['conditions']),
    json_encode($rule['actions']),
    $rule['priority']
);
$stmt->execute();
// 评估规则
$matchedRules = $ruleEngine->evaluate('order_completed', [
    'user_id' => $userId,
    'amount' => 1500,
    'type' => 'purchase'
]);

前端实现示例

通知偏好设置页面

<!-- notification-preferences.html -->
<div class="notification-settings">
    <h2>Notification Preferences</h2>
    <div class="notification-types">
        <div class="notification-type" v-for="pref in preferences">
            <div class="type-header">
                <h3>{{ pref.type_name }}</h3>
                <p>{{ pref.description }}</p>
            </div>
            <div class="channel-settings">
                <label class="channel-toggle">
                    <input type="checkbox" 
                           v-model="pref.channels.email"
                           @change="updatePreference(pref)">
                    Email
                </label>
                <label class="channel-toggle">
                    <input type="checkbox" 
                           v-model="pref.channels.sms"
                           @change="updatePreference(pref)">
                    SMS
                </label>
                <label class="channel-toggle">
                    <input type="checkbox" 
                           v-model="pref.channels.push"
                           @change="updatePreference(pref)">
                    Push Notification
                </label>
                <label class="channel-toggle">
                    <input type="checkbox" 
                           v-model="pref.channels.in_app"
                           @change="updatePreference(pref)">
                    In-App
                </label>
            </div>
            <div class="custom-settings" v-if="pref.custom_settings">
                <h4>Custom Settings</h4>
                <!-- 自定义设置内容 -->
            </div>
        </div>
    </div>
</div>
<script>
new Vue({
    el: '.notification-settings',
    data: {
        preferences: []
    },
    mounted() {
        this.loadPreferences();
    },
    methods: {
        loadPreferences() {
            fetch('/api/notifications/preferences')
                .then(response => response.json())
                .then(data => {
                    this.preferences = data;
                });
        },
        updatePreference(pref) {
            fetch(`/api/notifications/preferences/${pref.type_id}`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    channels: pref.channels,
                    custom_settings: pref.custom_settings
                })
            });
        }
    }
});
</script>

这样就实现了一个完整的自定义通知系统,包括:

  • 灵活的通道管理
  • 用户偏好设置
  • 自定义通知规则
  • 完整的数据库支持
  • 前后端分离的实现

你可以根据实际需求进一步扩展,比如添加WebSocket实时推送、邮件队列处理等功能。

抱歉,评论功能暂时关闭!