本文目录导读:

我来详细讲解如何在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实时推送、邮件队列处理等功能。