Webman框架中强大的即时通讯推送服务插件Push 并用它实现一对一聊天、群聊、消息推送等功能(可深度定制)
webman/push
是一款功能强大的 WebSocket 通信插件,可以在 Webman 框架中轻松实现实时通信。本文将带你完整实现:
- 私聊功能
- 群聊功能
- 公共广播
- 自定义业务消息处理
并详细展示前后端集成的具体实现。
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' // 订阅鉴权(仅限于私有频道)
});
版权声明:本文为原创文章,版权归 全栈开发技术博客 所有。
本文链接:https://www.lvtao.net/dev/webman-push.html
转载时须注明出处及本声明