Webman框架中强大的即时通讯推送服务插件Push 并用它实现一对一聊天、群聊、消息推送等功能(可深度定制)

webman/push 是一款功能强大的 WebSocket 通信插件,可以在 Webman 框架中轻松实现实时通信。本文将带你完整实现:

  1. 私聊功能
  2. 群聊功能
  3. 公共广播
  4. 自定义业务消息处理

并详细展示前后端集成的具体实现。

1. 环境准备

安装插件

使用 Composer 安装:

composer require webman/push

安装完成后,webman/push 会自动生成插件配置文件,位于 config/plugin/webman/push/

2. 插件配置

配置文件概览

1. 配置 WebSocket 核心信息

文件路径:config/plugin/webman/push/app.php

return [
    'enable'       => true,
    'websocket'    => 'websocket://0.0.0.0:3131',
    'api'          => 'http://0.0.0.0:3232',
    'app_key'      => 'your_app_key', // 客户端连接密钥
    'app_secret'   => 'your_app_secret', // 服务端推送密钥
    'channel_hook' => 'http://127.0.0.1:8787/plugin/webman/push/hook',
    'auth'         => '/plugin/webman/push/auth'
];

2. 配置路由

文件路径:config/plugin/webman/push/route.php

use app\controller\ChatController;

// 配置 WebSocket 服务端逻辑
return [
    '/ws' => [ChatController::class, 'handle'], // 将所有 WebSocket 请求交由 ChatController 处理

    '/plugin/webman/push/auth' => function (\support\Request $request) {
        $channel_name = $request->input('channel_name');
        $user_id = $request->input('user_id');

        // 私有频道鉴权
        if ($channel_name === "private-user-$user_id") {
            return ['auth' => true];
        }

        return ['auth' => false];
    },

    '/plugin/webman/push/hook' => function (\support\Request $request) {
        $data = $request->post();
        if ($data['event'] === 'channel_added') {
            // 用户上线逻辑
            // 例如:记录用户上线时间
        } elseif ($data['event'] === 'channel_removed') {
            // 用户下线逻辑
            // 例如:记录用户下线时间
        }
        return response('ok');
    },
];

3. 后端功能实现

为了实现消息处理逻辑,我们在 ChatController 中集中管理所有 WebSocket 消息。

ChatController

文件路径:app/controller/ChatController.php

namespace app\controller;

use Workerman\Connection\TcpConnection;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class ChatController
{
    // 存储在线用户连接
    protected static $connections = [];

    public function handle(TcpConnection $connection, $data)
    {
        // WebSocket 连接建立
        $connection->onWebSocketConnect = function ($connection) {
            $token = $_GET['token'] ?? null;
            if (!$token) {
                $connection->send(json_encode(['error' => 'Token required']));
                $connection->close();
                return;
            }

            // 解析 JWT 令牌
            try {
                $decoded = JWT::decode($token, new Key('your_secret_key', 'HS256'));
                $userId = $decoded->user_id;
            } catch (\Exception $e) {
                $connection->send(json_encode(['error' => 'Invalid token']));
                $connection->close();
                return;
            }

            // 存储用户连接
            $connection->userId = $userId;
            self::$connections[$userId] = $connection;

            // 通知所有人该用户上线
            $this->broadcast([
                'type' => 'user_online',
                'user_id' => $userId,
            ]);
        };

        // 消息处理
        $connection->onMessage = function ($connection, $data) {
            $message = json_decode($data, true);
            if (!isset($message['type'])) {
                $connection->send(json_encode(['error' => 'Invalid message format']));
                return;
            }

            // 根据消息类型分发
            switch ($message['type']) {
                case 'private_message':
                    $this->handlePrivateMessage($connection, $message);
                    break;

                case 'group_message':
                    $this->handleGroupMessage($connection, $message);
                    break;

                case 'public_message':
                    $this->handlePublicMessage($connection, $message);
                    break;

                default:
                    $connection->send(json_encode(['error' => 'Unknown message type']));
            }
        };

        // 连接关闭
        $connection->onClose = function ($connection) {
            if (isset($connection->userId)) {
                unset(self::$connections[$connection->userId]);

                // 通知所有人该用户下线
                $this->broadcast([
                    'type' => 'user_offline',
                    'user_id' => $connection->userId,
                ]);
            }
        };
    }

    // 私聊
    protected function handlePrivateMessage($connection, $message)
    {
        $toUserId = $message['to_user_id'] ?? null;
        $content = $message['content'] ?? '';

        if ($toUserId && isset(self::$connections[$toUserId])) {
            self::$connections[$toUserId]->send(json_encode([
                'type' => 'private_message',
                'from_user_id' => $connection->userId,
                'content' => $content,
            ]));
        } else {
            $connection->send(json_encode(['error' => 'User not online']));
        }
    }

    // 群聊
    protected function handleGroupMessage($connection, $message)
    {
        $groupId = $message['group_id'] ?? null;
        $content = $message['content'] ?? '';

        $groupMembers = $this->getGroupMembers($groupId);

        foreach ($groupMembers as $userId) {
            if (isset(self::$connections[$userId]) && $userId !== $connection->userId) {
                self::$connections[$userId]->send(json_encode([
                    'type' => 'group_message',
                    'from_user_id' => $connection->userId,
                    'group_id' => $groupId,
                    'content' => $content,
                ]));
            }
        }
    }

    // 公共消息
    protected function handlePublicMessage($connection, $message)
    {
        $content = $message['content'] ?? '';
        $this->broadcast([
            'type' => 'public_message',
            'from_user_id' => $connection->userId,
            'content' => $content,
        ]);
    }

    // 广播消息给所有在线用户
    protected function broadcast($data)
    {
        foreach (self::$connections as $conn) {
            $conn->send(json_encode($data));
        }
    }

    // 获取群组成员(伪代码)
    protected function getGroupMembers($groupId)
    {
        return [1, 2, 3];
    }
}

4. 前端集成

引入 Push.js

在前端页面中加载 Push.js 并连接 WebSocket:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webman Chat</title>
    <script src="/plugin/webman/push/push.js"></script>
</head>
<body>
    <h1>Webman Chat</h1>
    <div id="messages"></div>
    <input type="text" id="messageInput" placeholder="Enter your message">
    <button onclick="sendPrivateMessage(2, document.getElementById('messageInput').value)">Send Private Message</button>
    <button onclick="sendGroupMessage(1, document.getElementById('messageInput').value)">Send Group Message</button>
    <button onclick="sendPublicMessage(document.getElementById('messageInput').value)">Send Public Message</button>

    <script>
        var connection = new Push({
            url: 'ws://127.0.0.1:3131?token=<your_jwt_token>',
            app_key: 'your_app_key',
            auth: '/plugin/webman/push/auth'
        });

        // 订阅用户频道
        var user_channel = connection.subscribe('user-1');
        user_channel.on('private_message', function (data) {
            console.log('私聊消息:', data);
            displayMessage(data);
        });

        // 订阅群聊频道
        var group_channel = connection.subscribe('group-2');
        group_channel.on('group_message', function (data) {
            console.log('群聊消息:', data);
            displayMessage(data);
        });

        // 订阅公共频道
        var public_channel = connection.subscribe('public');
        public_channel.on('public_message', function (data) {
            console.log('公共广播:', data);
            displayMessage(data);
        });

        function displayMessage(data) {
            var messagesDiv = document.getElementById('messages');
            var messageElement = document.createElement('div');
            messageElement.textContent = `${data.from_user_id}: ${data.content}`;
            messagesDiv.appendChild(messageElement);
        }

        // 发送私聊消息
        function sendPrivateMessage(toUserId, content) {
            connection.trigger('user-1', 'client-message', { content: content });
        }

        // 发送群聊消息
        function sendGroupMessage(groupId, content) {
            connection.trigger('group-2', 'client-message', { content: content });
        }

        // 发送公共消息
        function sendPublicMessage(content) {
            connection.trigger('public', 'client-message', { content: content });
        }
    </script>
</body>
</html>

5. HTTPS/WSS 配置

在生产环境下建议使用 HTTPS 和 WSS,通过 Nginx 代理 WebSocket:

server {
    location /app/your_app_key {
        proxy_pass http://127.0.0.1:3131;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header X-Real-IP $remote_addr;
    }
}

重启nginx后,使用以下方式连接服务端

var connection = new Push({
    url: 'wss://example.com',
    app_key: '<app_key,在config/plugin/webman/push/app.php里获取>',
    auth: '/plugin/webman/push/auth' // 订阅鉴权(仅限于私有频道)
});

标签: PHP

相关文章

如何在PHP框架Workerman中实现异步任务处理

在现代Web应用中,处理繁重的业务逻辑时,避免主业务流程被长时间阻塞是非常重要的。Workerman是一个高性能的PHP Socket框架,支持异步任务处理,可以有效地解决这一问题。本文将详细介...

PHP中使用CURL下载远程超大文件的方法

在使用PHP进行开发时,我们经常需要从远程服务器下载文件。当文件体积较大时,普通的文件操作和cURL方法可能会因为内存限制或执行时间限制而失败。本文将介绍如何使用PHP中的cURL扩展来高效地下...

PHP命名空间使用详解

在 PHP 中,命名空间(Namespace)是一种将相关的类、函数和常量组织到一个逻辑分组中的方式。它有助于避免命名冲突,并提高代码的可维护性和可扩展性。一、命名空间的定义使用 namespa...

复习一下PHP中的类和对象

面向对象编程(OOP)是一种编程范式,它通过类和对象的概念来组织代码。PHP 作为一种广泛使用的服务器端脚本语言,从 PHP 5 开始就全面支持面向对象编程。本文将深入探讨 PHP 中类和对象的...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件