685 lines
12 KiB
Markdown
685 lines
12 KiB
Markdown
# WebSocket 功能文档
|
|
|
|
## 概述
|
|
|
|
本项目基于 Laravel-S 和 Swoole 实现了完整的 WebSocket 功能,支持实时通信、消息推送、广播等功能。
|
|
|
|
## 功能特性
|
|
|
|
- ✅ 实时双向通信
|
|
- ✅ 用户连接管理
|
|
- ✅ 点对点消息发送
|
|
- ✅ 群发消息/广播
|
|
- ✅ 频道订阅/取消订阅
|
|
- ✅ 心跳机制
|
|
- ✅ 自动重连
|
|
- ✅ 在线状态管理
|
|
- ✅ 系统通知推送
|
|
- ✅ 数据更新推送
|
|
|
|
## 架构设计
|
|
|
|
### 后端组件
|
|
|
|
#### 1. WebSocketHandler (`app/Services/WebSocket/WebSocketHandler.php`)
|
|
|
|
WebSocket 处理器,实现了 Swoole 的 `WebSocketHandlerInterface` 接口。
|
|
|
|
**主要方法:**
|
|
- `onOpen()`: 处理连接建立事件
|
|
- `onMessage()`: 处理消息接收事件
|
|
- `onClose()`: 处理连接关闭事件
|
|
|
|
**支持的消息类型:**
|
|
- `ping/pong`: 心跳检测
|
|
- `heartbeat`: 心跳确认
|
|
- `chat`: 私聊消息
|
|
- `broadcast`: 广播消息
|
|
- `subscribe/unsubscribe`: 频道订阅/取消订阅
|
|
|
|
#### 2. WebSocketService (`app/Services/WebSocket/WebSocketService.php`)
|
|
|
|
WebSocket 服务类,提供便捷的 WebSocket 操作方法。
|
|
|
|
**主要方法:**
|
|
- `sendToUser($userId, $data)`: 发送消息给指定用户
|
|
- `sendToUsers($userIds, $data)`: 发送消息给多个用户
|
|
- `broadcast($data, $excludeUserId)`: 广播消息给所有用户
|
|
- `sendToChannel($channel, $data)`: 发送消息给指定频道
|
|
- `getOnlineUserCount()`: 获取在线用户数
|
|
- `isUserOnline($userId)`: 检查用户是否在线
|
|
- `sendSystemNotification()`: 发送系统通知
|
|
- `pushDataUpdate()`: 推送数据更新
|
|
|
|
#### 3. WebSocketController (`app/Http/Controllers/System/WebSocket.php`)
|
|
|
|
WebSocket API 控制器,提供 HTTP 接口用于管理 WebSocket 连接。
|
|
|
|
### 前端组件
|
|
|
|
#### WebSocketClient (`resources/admin/src/utils/websocket.js`)
|
|
|
|
WebSocket 客户端封装类。
|
|
|
|
**功能:**
|
|
- 自动连接和重连
|
|
- 心跳机制
|
|
- 消息类型路由
|
|
- 事件监听
|
|
- 连接状态管理
|
|
|
|
## 配置说明
|
|
|
|
### Laravel-S 配置 (`config/laravels.php`)
|
|
|
|
```php
|
|
'websocket' => [
|
|
'enable' => env('LARAVELS_WEBSOCKET', true),
|
|
'handler' => \App\Services\WebSocket\WebSocketHandler::class,
|
|
],
|
|
|
|
'swoole_tables' => [
|
|
'wsTable' => [
|
|
'size' => 102400,
|
|
'column' => [
|
|
['name' => 'value', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 1024],
|
|
['name' => 'expiry', 'type' => \Swoole\Table::TYPE_INT, 'size' => 4],
|
|
],
|
|
],
|
|
],
|
|
```
|
|
|
|
### 环境变量
|
|
|
|
在 `.env` 文件中添加:
|
|
|
|
```env
|
|
LARAVELS_WEBSOCKET=true
|
|
```
|
|
|
|
## API 接口
|
|
|
|
### 1. 获取在线用户数
|
|
|
|
```
|
|
GET /admin/websocket/online-count
|
|
```
|
|
|
|
**响应:**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "success",
|
|
"data": {
|
|
"online_count": 10
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. 获取在线用户列表
|
|
|
|
```
|
|
GET /admin/websocket/online-users
|
|
```
|
|
|
|
**响应:**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "success",
|
|
"data": {
|
|
"user_ids": [1, 2, 3, 4, 5],
|
|
"count": 5
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. 检查用户在线状态
|
|
|
|
```
|
|
POST /admin/websocket/check-online
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"user_id": 1
|
|
}
|
|
```
|
|
|
|
**响应:**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "success",
|
|
"data": {
|
|
"user_id": 1,
|
|
"is_online": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. 发送消息给指定用户
|
|
|
|
```
|
|
POST /admin/websocket/send-to-user
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"user_id": 1,
|
|
"type": "notification",
|
|
"data": {
|
|
"title": "新消息",
|
|
"message": "您有一条新消息"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. 发送消息给多个用户
|
|
|
|
```
|
|
POST /admin/websocket/send-to-users
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"user_ids": [1, 2, 3],
|
|
"type": "notification",
|
|
"data": {
|
|
"title": "系统通知",
|
|
"message": "系统将在今晚进行维护"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. 广播消息
|
|
|
|
```
|
|
POST /admin/websocket/broadcast
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"type": "notification",
|
|
"data": {
|
|
"title": "公告",
|
|
"message": "欢迎使用新版本"
|
|
},
|
|
"exclude_user_id": 1 // 可选:排除某个用户
|
|
}
|
|
```
|
|
|
|
### 7. 发送消息到频道
|
|
|
|
```
|
|
POST /admin/websocket/send-to-channel
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"channel": "orders",
|
|
"type": "data_update",
|
|
"data": {
|
|
"order_id": 123,
|
|
"status": "paid"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 8. 发送系统通知
|
|
|
|
```
|
|
POST /admin/websocket/send-notification
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"title": "系统维护",
|
|
"message": "系统将于今晚 23:00-24:00 进行维护",
|
|
"type": "warning",
|
|
"extra_data": {
|
|
"start_time": "23:00",
|
|
"end_time": "24:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 9. 发送通知给指定用户
|
|
|
|
```
|
|
POST /admin/websocket/send-notification-to-users
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"user_ids": [1, 2, 3],
|
|
"title": "订单更新",
|
|
"message": "您的订单已发货",
|
|
"type": "success"
|
|
}
|
|
```
|
|
|
|
### 10. 推送数据更新
|
|
|
|
```
|
|
POST /admin/websocket/push-data-update
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"user_ids": [1, 2, 3],
|
|
"resource_type": "order",
|
|
"action": "update",
|
|
"data": {
|
|
"id": 123,
|
|
"status": "shipped"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 11. 推送数据更新到频道
|
|
|
|
```
|
|
POST /admin/websocket/push-data-update-channel
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"channel": "orders",
|
|
"resource_type": "order",
|
|
"action": "create",
|
|
"data": {
|
|
"id": 124,
|
|
"customer": "张三",
|
|
"amount": 100.00
|
|
}
|
|
}
|
|
```
|
|
|
|
### 12. 断开用户连接
|
|
|
|
```
|
|
POST /admin/websocket/disconnect-user
|
|
```
|
|
|
|
**请求参数:**
|
|
```json
|
|
{
|
|
"user_id": 1
|
|
}
|
|
```
|
|
|
|
## 前端使用示例
|
|
|
|
### 1. 基本连接
|
|
|
|
```javascript
|
|
import { getWebSocket, closeWebSocket } from '@/utils/websocket'
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
|
|
const userStore = useUserStore()
|
|
|
|
// 连接 WebSocket
|
|
const ws = getWebSocket(userStore.userInfo.id, userStore.token, {
|
|
onOpen: (event) => {
|
|
console.log('WebSocket 已连接')
|
|
},
|
|
onMessage: (message) => {
|
|
console.log('收到消息:', message)
|
|
},
|
|
onError: (error) => {
|
|
console.error('WebSocket 错误:', error)
|
|
},
|
|
onClose: (event) => {
|
|
console.log('WebSocket 已关闭')
|
|
}
|
|
})
|
|
|
|
// 连接
|
|
ws.connect()
|
|
```
|
|
|
|
### 2. 监听特定消息类型
|
|
|
|
```javascript
|
|
// 监听通知消息
|
|
ws.on('notification', (data) => {
|
|
message.success(data.title, data.message)
|
|
})
|
|
|
|
// 监听数据更新
|
|
ws.on('data_update', (data) => {
|
|
console.log('数据更新:', data.resource_type, data.action)
|
|
// 刷新数据
|
|
loadData()
|
|
})
|
|
```
|
|
|
|
### 3. 发送消息
|
|
|
|
```javascript
|
|
// 发送心跳
|
|
ws.send('heartbeat', { timestamp: Date.now() })
|
|
|
|
// 发送私聊消息
|
|
ws.send('chat', {
|
|
to_user_id: 2,
|
|
content: '你好,这是一条私聊消息'
|
|
})
|
|
|
|
// 订阅频道
|
|
ws.send('subscribe', { channel: 'orders' })
|
|
|
|
// 取消订阅
|
|
ws.send('unsubscribe', { channel: 'orders' })
|
|
```
|
|
|
|
### 4. 发送广播消息
|
|
|
|
```javascript
|
|
ws.send('broadcast', {
|
|
message: '这是一条广播消息'
|
|
})
|
|
```
|
|
|
|
### 5. 断开连接
|
|
|
|
```javascript
|
|
// 断开连接
|
|
ws.disconnect()
|
|
|
|
// 或使用全局方法
|
|
closeWebSocket()
|
|
```
|
|
|
|
### 6. 在 Vue 组件中使用
|
|
|
|
```vue
|
|
<template>
|
|
<div>
|
|
<a-button @click="connectWebSocket">连接 WebSocket</a-button>
|
|
<a-button @click="disconnectWebSocket">断开连接</a-button>
|
|
<a-button @click="sendMessage">发送消息</a-button>
|
|
<div>连接状态: {{ connectionStatus }}</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { getWebSocket } from '@/utils/websocket'
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
|
|
const userStore = useUserStore()
|
|
const ws = ref(null)
|
|
const connectionStatus = ref('未连接')
|
|
|
|
const connectWebSocket = () => {
|
|
ws.value = getWebSocket(userStore.userInfo.id, userStore.token, {
|
|
onOpen: () => {
|
|
connectionStatus.value = '已连接'
|
|
},
|
|
onMessage: (message) => {
|
|
handleMessage(message)
|
|
},
|
|
onClose: () => {
|
|
connectionStatus.value = '已断开'
|
|
}
|
|
})
|
|
|
|
ws.value.connect()
|
|
}
|
|
|
|
const disconnectWebSocket = () => {
|
|
if (ws.value) {
|
|
ws.value.disconnect()
|
|
connectionStatus.value = '已断开'
|
|
}
|
|
}
|
|
|
|
const sendMessage = () => {
|
|
if (ws.value && ws.value.isConnected) {
|
|
ws.value.send('chat', {
|
|
to_user_id: 2,
|
|
content: '测试消息'
|
|
})
|
|
}
|
|
}
|
|
|
|
const handleMessage = (message) => {
|
|
switch (message.type) {
|
|
case 'notification':
|
|
message.success(message.data.title, message.data.message)
|
|
break
|
|
case 'data_update':
|
|
// 处理数据更新
|
|
break
|
|
case 'chat':
|
|
// 处理聊天消息
|
|
break
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
connectWebSocket()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
disconnectWebSocket()
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## 消息格式
|
|
|
|
### 服务端发送的消息格式
|
|
|
|
```json
|
|
{
|
|
"type": "notification",
|
|
"data": {
|
|
"title": "标题",
|
|
"message": "内容",
|
|
"type": "info",
|
|
"timestamp": 1641234567
|
|
}
|
|
}
|
|
```
|
|
|
|
### 客户端发送的消息格式
|
|
|
|
```json
|
|
{
|
|
"type": "chat",
|
|
"data": {
|
|
"to_user_id": 2,
|
|
"content": "消息内容"
|
|
}
|
|
}
|
|
```
|
|
|
|
## 启动和停止
|
|
|
|
### 启动 Laravel-S 服务
|
|
|
|
```bash
|
|
php bin/laravels start
|
|
```
|
|
|
|
### 停止 Laravel-S 服务
|
|
|
|
```bash
|
|
php bin/laravels stop
|
|
```
|
|
|
|
### 重启 Laravel-S 服务
|
|
|
|
```bash
|
|
php bin/laravels restart
|
|
```
|
|
|
|
### 重载 Laravel-S 服务(平滑重启)
|
|
|
|
```bash
|
|
php bin/laravels reload
|
|
```
|
|
|
|
### 查看服务状态
|
|
|
|
```bash
|
|
php bin/laravels status
|
|
```
|
|
|
|
## WebSocket 连接地址
|
|
|
|
### 开发环境
|
|
|
|
```
|
|
ws://localhost:5200/ws?user_id={user_id}&token={token}
|
|
```
|
|
|
|
### 生产环境
|
|
|
|
```
|
|
wss://yourdomain.com/ws?user_id={user_id}&token={token}
|
|
```
|
|
|
|
## Nginx 配置示例
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name yourdomain.com;
|
|
root /path/to/your/project/public;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.php?$query_string;
|
|
}
|
|
|
|
# WebSocket 代理配置
|
|
location /ws {
|
|
proxy_pass http://127.0.0.1:5200;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header Host $host;
|
|
proxy_read_timeout 86400;
|
|
}
|
|
|
|
location ~ \.php$ {
|
|
fastcgi_pass 127.0.0.1:9000;
|
|
fastcgi_index index.php;
|
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
include fastcgi_params;
|
|
}
|
|
}
|
|
```
|
|
|
|
## 使用场景
|
|
|
|
### 1. 实时通知
|
|
|
|
```php
|
|
// 发送系统通知
|
|
$webSocketService->sendSystemNotification(
|
|
'系统维护',
|
|
'系统将于今晚进行维护',
|
|
'warning'
|
|
);
|
|
```
|
|
|
|
### 2. 订单状态更新
|
|
|
|
```php
|
|
// 推送订单状态更新给相关人员
|
|
$webSocketService->pushDataUpdate(
|
|
[$order->user_id],
|
|
'order',
|
|
'update',
|
|
[
|
|
'id' => $order->id,
|
|
'status' => $order->status,
|
|
'updated_at' => $order->updated_at
|
|
]
|
|
);
|
|
```
|
|
|
|
### 3. 实时聊天
|
|
|
|
```javascript
|
|
// 发送私聊消息
|
|
ws.send('chat', {
|
|
to_user_id: 2,
|
|
content: '你好'
|
|
})
|
|
```
|
|
|
|
### 4. 数据监控
|
|
|
|
```php
|
|
// 推送系统监控数据到特定频道
|
|
$webSocketService->sendToChannel('system_monitor', 'monitor', [
|
|
'cpu_usage' => 75,
|
|
'memory_usage' => 80,
|
|
'disk_usage' => 60
|
|
]);
|
|
```
|
|
|
|
## 注意事项
|
|
|
|
1. **连接认证**: WebSocket 连接时需要提供 `user_id` 和 `token` 参数
|
|
2. **心跳机制**: 客户端默认每 30 秒发送一次心跳
|
|
3. **自动重连**: 连接断开后会自动尝试重连,最多重试 5 次
|
|
4. **并发限制**: Swoole Table 最多支持 102,400 个连接
|
|
5. **内存管理**: 注意内存泄漏问题,定期重启服务
|
|
6. **安全性**: 生产环境建议使用 WSS (WebSocket Secure)
|
|
7. **日志监控**: 查看日志文件 `storage/logs/swoole-YYYY-MM.log`
|
|
|
|
## 故障排查
|
|
|
|
### 1. 无法连接 WebSocket
|
|
|
|
- 检查 Laravel-S 服务是否启动
|
|
- 检查端口 5200 是否被占用
|
|
- 检查防火墙设置
|
|
- 查看日志文件
|
|
|
|
### 2. 连接频繁断开
|
|
|
|
- 检查网络稳定性
|
|
- 调整心跳间隔
|
|
- 检查服务器资源使用情况
|
|
|
|
### 3. 消息发送失败
|
|
|
|
- 检查用户是否在线
|
|
- 检查消息格式是否正确
|
|
- 查看错误日志
|
|
|
|
## 参考资料
|
|
|
|
- [Laravel-S 文档](https://github.com/hhxsv5/laravel-s)
|
|
- [Swoole 文档](https://www.swoole.com/)
|
|
- [WebSocket API](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket)
|
|
|
|
## 更新日志
|
|
|
|
### 2024-02-08
|
|
|
|
- ✅ 初始版本发布
|
|
- ✅ 实现基础 WebSocket 功能
|
|
- ✅ 实现消息推送功能
|
|
- ✅ 实现频道订阅功能
|
|
- ✅ 实现前端客户端封装
|
|
- ✅ 实现管理 API 接口
|