PHP消息通知怎么实现

wen PHP项目 3

本文目录导读:

PHP消息通知怎么实现

  1. 拉取模式(Polling)- 最简单
  2. 长轮询(Long Polling)- 折中方案
  3. WebSocket - 推荐方案
  4. Server-Sent Events (SSE) - 适合单向推送
  5. 完整企业级方案(推荐)
  6. 推送平台/二要素认证
  7. 实际项目推荐选择
  8. 性能优化建议

在PHP中实现消息通知系统,有多种方案,从简单到复杂,我为你整理了几种主流实现方式:


拉取模式(Polling)- 最简单

原理

前端定时向后端发送请求,检查是否有新消息。

实现示例

// client.js
setInterval(async () => {
    const response = await fetch('/api/notifications/check');
    const data = await response.json();
    if (data.hasNew) {
        // 处理新消息
        showNotification(data.messages);
    }
}, 3000); // 每3秒检查一次
// server.php
<?php
// /api/notifications/check
$lastId = $_GET['last_id'] ?? 0;
$notifications = getNotificationsSince($lastId);
echo json_encode(['hasNew' => !empty($notifications), 'messages' => $notifications]);

优点:实现简单,兼容性好
缺点:实时性差,请求频繁浪费资源


长轮询(Long Polling)- 折中方案

原理

客户端发出请求后,服务器保持连接直到有新消息或超时才返回。

<?php
// long-polling.php
set_time_limit(30); // 设置30秒超时
$lastId = $_GET['last_id'] ?? 0;
$startTime = time();
while (time() - $startTime < 25) { // 25秒内循环检查
    $newMessages = getMessagesSince($lastId);
    if (!empty($newMessages)) {
        echo json_encode(['messages' => $newMessages, 'last_id' => $newMessages[count($newMessages)-1]['id']]);
        exit;
    }
    usleep(500000); // 休息0.5秒再查
}
echo json_encode(['messages' => [], 'last_id' => $lastId]);

优点:实时性较好,比轮询节省资源
缺点:服务器资源占用较高,不适合海量连接


WebSocket - 推荐方案

原理

全双工通信,客户端和服务器都可以随时发送消息。

使用 Ratchet(PHP WebSocket库)

// composer.json
{
    "require": {
        "cboden/ratchet": "^0.4.4"
    }
}
// WebSocket服务器
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class NotificationServer implements MessageComponentInterface {
    protected $clients;
    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }
    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        echo "新连接: {$conn->resourceId}\n";
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        // 接收用户ID进行绑定
        $data = json_decode($msg, true);
        $from->userId = $data['user_id'] ?? null;
    }
    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
    }
    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
    // 发送通知给特定用户
    public function sendNotification($userId, $message) {
        foreach ($this->clients as $client) {
            if ($client->userId === $userId) {
                $client->send(json_encode(['type' => 'notification', 'message' => $message]));
            }
        }
    }
}
// 启动服务器
$server = \Ratchet\Server\IoServer::factory(
    new \Ratchet\Http\HttpServer(
        new \Ratchet\WebSocket\WsServer(
            new NotificationServer()
        )
    ),
    8080
);
$server->run();

客户端代码

// client.js
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
    // 发送用户ID进行绑定
    ws.send(JSON.stringify({ user_id: user_id }));
};
ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'notification') {
        alert(data.message.message);
        updateNotificationCount();
    }
};

优点:实时性最好,双向通信
缺点:需要保持长连接,服务器配置相对复杂


Server-Sent Events (SSE) - 适合单向推送

原理

服务器向浏览器单向推送数据,基于HTTP。

<?php
// sse.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// 必须设置
@ini_set('implicit_flush', 1);
@ob_end_clean();
$lastEventId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? 0;
while (true) {
    $messages = getMessagesSince($lastEventId);
    if (!empty($messages)) {
        foreach ($messages as $msg) {
            echo "id: {$msg['id']}\n";
            echo "data: " . json_encode($msg) . "\n\n";
        }
        ob_flush();
        flush();
    }
    // 保持连接,发送心跳
    echo ": heartbeat\n\n";
    // flush();
    sleep(1);
}

客户端

// client.js
var source = new EventSource('/sse.php');
source.onmessage = function(event) {
    var data = JSON.parse(event.data);
    console.log('收到通知:', data);
    // 处理通知...
};

优点:实时性好,自动重连,使用简单
缺点:只能服务端向客户端单向推送


完整企业级方案(推荐)

结合数据库、Redis队列和WebSocket:

[业务触发] → [写入DB] → [发布到Redis] → [WebSocket服务器消费] → [推送客户端]
<?php
// 1. 业务端:发送通知
function sendNotification($userId, $type, $content) {
    // 保存到数据库
    $notification = [
        'user_id' => $userId,
        'type' => $type,
        'content' => $content,
        'created_at' => date('Y-m-d H:i:s')
    ];
    $db->insert('notifications', $notification);
    // 发布到Redis队列
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->lPush('notification_queue', json_encode($notification));
    // 通知WebSocket服务器有新消息
    $redis->publish('notification_channel', json_encode($notification));
}
// 2. WebSocket服务器
// 在Ratchet中订阅Redis并推送到客户端
public function onOpen(ConnectionInterface $conn) {
    // 连接Redis并订阅通知
    $this->redis = new \Redis();
    $this->redis->connect('127.0.0.1', 6379);
    $this->redis->subscribe(['notification_channel'], [$this, 'handleRedisMessage']);
}
public function handleRedisMessage($redis, $channel, $message) {
    $data = json_decode($message, true);
    $this->sendNotification($data['user_id'], $data);
}

推送平台/二要素认证

服务商集成

  • 极光推送(JPush) - 支持Web/App推送
  • 个推(Getui) - 偏移动端
  • 阿里云推送 - 支持Uni/Push
// 示例:使用极光推送
// composer require jpush/jpush
use JPush\Client as JPushClient;
function sendPush($userId, $content) {
    $client = new \JPush\Client('your_app_key', 'your_master_secret');
    $push = $client->push()
        ->setPlatform('all')
        ->addRegistrationId($userId)
        ->setNotificationAlert($content);
    try {
        return $push->send();
    } catch (\Exception $e) {
        error_log('推送失败: ' . $e->getMessage());
    }
}

实际项目推荐选择

场景 推荐方案
简单后台管理 轮询,5秒一次
中小型应用 长轮询 或 SSE
实时聊天/多人协作 WebSocket (Ratchet)
移动端推送 极光/个推/阿里推送
大型应用 Redis + WebSocket + 消息队列

性能优化建议

// 1. 使用Redis缓存已读状态
// 2. 消息只存有变更的,减少数据库查询
// 3. 错误处理要考虑网络异常
// 4. 使用Pulsar/Kafka等MQ异步处理大批量消息
// 5. 监控WebSocket连接数,设置最大连接数限制

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