chore(websocket): 更新ws_server

This commit is contained in:
2025-12-20 14:44:01 +08:00
parent f8291dd2ba
commit f577e47be6
8 changed files with 2155 additions and 5 deletions

355
docs/websocket/README.md Normal file
View File

@@ -0,0 +1,355 @@
# WebSocket Server 说明文档
## 1. 概述
WebSocket Server 是基于 Ratchet 库实现的纯 PHP WebSocket 服务器,主要用于为智能客服系统提供实时通信支持,特别是为不支持 EventSource 的微信小程序提供流式请求处理能力。
## 2. 安装与依赖
### 2.1 依赖库
- **Ratchet**: 纯 PHP WebSocket 实现库
```bash
composer require cboden/ratchet
```
### 2.2 环境要求
- PHP 7.4+
- Composer
- ThinkPHP 6.x
## 3. 服务器文件结构
### 3.1 核心文件
- **启动脚本**: `src/ws_server.php` - WebSocket 服务器主启动文件
- **智能客服控制器**: `src/addon/aikefu/api/controller/WebSocket.php` - aikefu 插件的 WebSocket 控制器实现
### 3.2 目录结构
```
├── backend/
│ ├── docs/
│ │ └── websocket/
│ │ └── README.md # 本文档
│ └── src/
│ ├── ws_server.php # WebSocket 服务器启动脚本
│ └── addon/
│ └── aikefu/
│ └── api/
│ └── controller/
│ └── WebSocket.php # aikefu 插件 WebSocket 控制器
```
## 4. 服务器启动与配置
### 4.1 启动命令
在 `src` 目录下执行:
```bash
php ws_server.php
```
### 4.2 默认配置
- **监听地址**: `0.0.0.0` (所有网络接口)
- **端口**: `8080`
- **WebSocket 地址**: `ws://localhost:8080`
### 4.3 自定义配置
可以在 `ws_server.php` 中修改以下配置:
```php
// 配置WebSocket服务器
$httpHost = 'localhost'; // 客户端连接时使用的主机名
$port = 8080; // WebSocket服务器端口
$address = '0.0.0.0'; // 监听所有网络接口
```
## 5. WebSocket 路径
### 5.1 默认测试路径
- **路径**: `/ws`
- **功能**: 用于测试 WebSocket 连接
- **示例**: `ws://localhost:8080/ws`
### 5.2 智能客服路径
- **路径**: `/ws/aikefu`
- **功能**: 智能客服聊天功能接口
- **示例**: `ws://localhost:8080/ws/aikefu`
## 6. 消息格式
### 6.1 客户端发送消息格式
```json
{
"message": "用户输入的消息",
"token": "用户认证令牌",
"session_id": "会话ID",
"action": "chat" // 动作类型
}
```
### 6.2 服务器响应消息格式
#### 聊天响应
```json
{
"type": "chat",
"message": "客服回复的消息内容",
"session_id": "会话ID",
"complete": false // 是否完成响应
}
```
#### 错误响应
```json
{
"type": "error",
"message": "错误信息描述",
"code": "错误代码"
}
```
#### 系统消息
```json
{
"type": "system",
"message": "系统消息内容"
}
```
## 7. 客户端使用示例
### 7.1 JavaScript 示例
```javascript
// 创建WebSocket连接
const ws = new WebSocket('ws://localhost:8080/ws/aikefu');
// 连接打开时
ws.onopen = function(event) {
console.log('WebSocket连接已打开');
// 发送聊天消息
ws.send(JSON.stringify({
message: '你好',
token: 'user_token_here',
session_id: 'session_123',
action: 'chat'
}));
};
// 接收消息
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
if (data.type === 'chat') {
// 处理聊天消息
console.log('客服回复:', data.message);
if (data.complete) {
console.log('对话完成');
}
} else if (data.type === 'error') {
// 处理错误
console.error('错误:', data.message);
}
};
// 连接关闭时
ws.onclose = function(event) {
console.log('WebSocket连接已关闭');
};
// 连接错误时
ws.onerror = function(error) {
console.error('WebSocket错误:', error);
};
```
### 7.2 微信小程序示例
```javascript
// 创建WebSocket连接
const ws = wx.connectSocket({
url: 'ws://localhost:8080/ws/aikefu',
header: {
'content-type': 'application/json'
}
});
// 连接打开时
wx.onSocketOpen(function(res) {
console.log('WebSocket连接已打开', res);
// 发送聊天消息
wx.sendSocketMessage({
data: JSON.stringify({
message: '你好',
token: 'user_token_here',
session_id: 'session_123',
action: 'chat'
})
});
});
// 接收消息
wx.onSocketMessage(function(res) {
const data = JSON.parse(res.data);
console.log('收到消息:', data);
if (data.type === 'chat') {
// 处理聊天消息
console.log('客服回复:', data.message);
if (data.complete) {
console.log('对话完成');
}
} else if (data.type === 'error') {
// 处理错误
console.error('错误:', data.message);
}
});
// 连接关闭时
wx.onSocketClose(function(res) {
console.log('WebSocket连接已关闭', res);
});
// 连接错误时
wx.onSocketError(function(res) {
console.error('WebSocket错误:', res);
});
```
## 8. 功能特性
### 8.1 实时通信
- 支持双向实时通信
- 消息即时推送
- 支持流式响应
### 8.2 智能客服集成
- 与现有智能客服系统无缝集成
- 支持上下文会话管理
- 支持多用户并发访问
### 8.3 插件化设计
- 支持为不同插件注册独立的 WebSocket 控制器
- 路径格式:`/ws/{addon_name}`
- 便于扩展其他插件的 WebSocket 支持
## 9. 故障排除
### 9.1 常见问题
#### 问题:服务器无法启动
**可能原因**
- 端口被占用
- 依赖库未安装
- PHP 版本不兼容
**解决方案**
- 检查端口占用情况:`netstat -an | findstr 8080`
- 重新安装依赖:`composer install`
- 确保 PHP 版本 >= 7.4
#### 问题:客户端无法连接
**可能原因**
- 服务器未启动
- 网络防火墙限制
- WebSocket 地址错误
**解决方案**
- 确认服务器已启动
- 检查防火墙设置,确保端口 8080 开放
- 验证 WebSocket 地址格式
#### 问题:消息发送失败
**可能原因**
- 消息格式错误
- 认证失败
- 服务器内部错误
**解决方案**
- 检查消息格式是否符合要求
- 验证用户认证信息
- 查看服务器日志获取详细错误信息
### 9.2 日志查看
服务器启动时会输出详细日志,包括:
- 已注册的 WebSocket 控制器
- 连接信息
- 错误信息
## 10. 扩展开发
### 10.1 为其他插件添加 WebSocket 支持
1. 在插件目录下创建 WebSocket 控制器:
```
addon/{addon_name}/api/controller/WebSocket.php
```
2. 实现 MessageComponentInterface 接口:
```php
<?php
namespace addon\{addon_name}\api\controller;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class WebSocket implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
// 处理连接打开
}
public function onMessage(ConnectionInterface $conn, $msg) {
// 处理收到的消息
}
public function onClose(ConnectionInterface $conn) {
// 处理连接关闭
}
public function onError(ConnectionInterface $conn, \Exception $e) {
// 处理错误
}
}
```
3. 重启 WebSocket 服务器,新插件的 WebSocket 控制器将自动注册到 `/ws/{addon_name}` 路径
## 11. 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| v1.0 | 2025-12-19 | 初始版本,支持智能客服系统的 WebSocket 通信 |
## 12. 联系方式
如有问题或建议,请联系技术支持团队。

View File

@@ -0,0 +1,231 @@
# WebSocket 测试文件说明
本目录包含用于测试 WebSocket 服务器的各种测试文件,支持不同环境和场景的测试需求。
## 测试文件列表
1. **`test_websocket.php`** - PHP 客户端测试脚本
2. **`test_websocket.html`** - 浏览器端 JavaScript 测试页面
3. **`test_wechat_miniprogram.js`** - 微信小程序客户端测试代码
4. **`test_readme.md`** - 本测试文件说明文档
## 测试前准备
在开始测试之前,请确保:
1. **启动 WebSocket 服务器**
```bash
cd d:/projects/shop-projects/backend/src
php ws_server.php
```
2. **确保服务器运行正常**
服务器启动后,您应该看到类似以下输出:
```
WebSocket服务器已启动监听地址: ws://localhost:8080
已注册WebSocket控制器的addon路径
- ws://localhost:8080/ws/aikefu (已注册)
默认测试路径:
- ws://localhost:8080/ws (默认路径,用于连接测试)
按 Ctrl+C 停止服务器
```
## 1. PHP 客户端测试 (`test_websocket.php`)
### 功能说明
用于在 PHP 环境下测试 WebSocket 服务器的连接和基本功能。
### 使用方法
1. **直接运行测试脚本**
```bash
cd d:/projects/shop-projects/backend/docs/websocket
php test_websocket.php
```
2. **测试不同路径**
- 默认测试路径:`ws://localhost:8080/ws`
- aikefu 插件路径:`ws://localhost:8080/ws/aikefu`
可以通过修改脚本中的 `$wsUrl` 变量来切换测试路径。
### 预期输出
成功连接并发送消息后,您应该看到类似以下输出:
```
正在连接到 WebSocket 服务器: ws://localhost:8080/ws
✅ 成功连接到 WebSocket 服务器
📤 发送 ping 消息
📤 发送测试消息
📥 收到消息: {"type":"welcome","message":"欢迎连接到默认WebSocket测试路径","info":"此路径仅用于测试,不提供实际功能。请使用/ws/{addonName}连接到具体的addon服务。"}
📥 收到消息: {"type":"pong"}
📥 收到消息: {"type":"info","message":"收到消息,但默认路径不提供实际功能","received":{"message":"Hello WebSocket!","action":"test"}}
```
## 2. 浏览器端 JavaScript 测试 (`test_websocket.html`)
### 功能说明
用于在浏览器环境下测试 WebSocket 服务器的连接和交互功能,提供直观的用户界面。
### 使用方法
1. **打开测试页面**
- 直接用浏览器打开 `test_websocket.html` 文件
- 或者通过 Web 服务器访问:`http://your-domain/docs/websocket/test_websocket.html`
2. **配置测试参数**
- 在 "设置" 区域输入 WebSocket 服务器地址
- 默认地址:`ws://localhost:8080/ws`
- aikefu 插件地址:`ws://localhost:8080/ws/aikefu`
3. **连接和测试**
- 点击 "连接" 按钮建立 WebSocket 连接
- 使用 "发送 Ping"、"发送测试消息" 或 "发送自定义消息" 按钮进行测试
- 在消息区域查看发送和接收的消息
### 主要功能
- **连接管理**:连接、断开连接、重连
- **消息发送**
- Ping 消息:测试服务器响应
- 测试消息:发送预设的测试数据
- 自定义消息:支持发送任意 JSON 格式消息
- **消息显示**
- 美化显示 JSON 格式消息
- 区分发送和接收的消息
- 显示消息时间和发送者
## 3. 微信小程序客户端测试 (`test_wechat_miniprogram.js`)
### 功能说明
用于在微信小程序环境下测试 WebSocket 服务器的连接和通信功能,模拟实际的微信小程序客户端环境。
### 使用方法
1. **集成测试代码**
- 将 `test_wechat_miniprogram.js` 文件中的代码复制到微信小程序的页面 JavaScript 文件中
- 将注释中的 WXML 和 WXSS 代码分别复制到对应的 `.wxml` 和 `.wxss` 文件中
2. **配置服务器地址**
- 修改 `wsUrl` 变量为您的 WebSocket 服务器地址
- 注意:微信小程序要求使用 HTTPS/WSS 协议,需要配置 SSL 证书
3. **运行测试**
- 在微信开发者工具中打开小程序项目
- 进入包含测试代码的页面
- 点击 "连接" 按钮建立 WebSocket 连接
- 使用测试按钮发送消息并查看接收结果
### 注意事项
- **微信小程序 WebSocket 限制**
- 只支持 HTTPS/WSS 协议
- 需要在小程序管理后台配置合法域名
- 最多同时存在 5 个 WebSocket 连接
- **调试建议**
- 使用微信开发者工具的调试模式查看日志
- 先在浏览器端测试确保服务器正常
- 注意网络环境和防火墙设置
## 测试场景和用例
### 基本连接测试
| 测试场景 | 预期结果 | 测试文件 |
|---------|---------|---------|
| 连接默认测试路径 | 成功连接,收到欢迎消息 | 所有测试文件 |
| 连接 aikefu 插件路径 | 成功连接 | 所有测试文件 |
| 断开连接 | 连接关闭,收到关闭通知 | 所有测试文件 |
| 重连 | 成功重新建立连接 | 浏览器和小程序测试文件 |
### 消息功能测试
| 测试场景 | 预期结果 | 测试文件 |
|---------|---------|---------|
| 发送 Ping 消息 | 收到 Pong 响应 | 所有测试文件 |
| 发送测试消息 | 收到服务器处理结果 | 所有测试文件 |
| 发送 JSON 消息 | 正确解析和处理 | 所有测试文件 |
| 发送错误格式消息 | 收到错误提示 | 所有测试文件 |
## 常见问题和解决方案
### 1. 连接失败
**可能原因**
- WebSocket 服务器未启动
- 服务器地址或端口错误
- 防火墙或网络设置阻止连接
- 微信小程序域名未配置
**解决方案**
- 确认服务器已启动并运行正常
- 检查服务器地址和端口配置
- 关闭防火墙或添加例外规则
- 在微信小程序管理后台配置合法域名
### 2. 消息发送失败
**可能原因**
- WebSocket 连接已关闭
- 消息格式不正确
- 服务器处理错误
**解决方案**
- 检查连接状态,必要时重新连接
- 确保消息格式符合 JSON 规范
- 查看服务器日志排查错误
### 3. 接收消息异常
**可能原因**
- 服务器返回格式错误
- 网络传输问题
- 客户端解析错误
**解决方案**
- 检查服务器代码确保返回正确格式
- 检查网络连接稳定性
- 查看客户端日志排查解析错误
## 测试完成后的清理
测试完成后,您可以:
1. **停止 WebSocket 服务器**
在运行服务器的终端中按 `Ctrl+C` 停止服务器。
2. **关闭测试客户端**
- 浏览器测试:关闭浏览器标签页
- PHP 测试:测试完成后自动退出
- 微信小程序测试:退出小程序页面
## 扩展测试建议
1. **性能测试**
- 测试同时连接多个客户端
- 测试大消息传输
- 测试长时间连接稳定性
2. **异常场景测试**
- 服务器意外关闭
- 网络中断恢复
- 消息丢失场景
3. **功能完整性测试**
- 测试所有消息类型
- 测试错误处理机制
- 测试安全验证功能
## 联系和支持
如果在测试过程中遇到问题,请联系开发人员或查看服务器日志获取更多信息。
---
**更新时间**2025-12-19
**版本**1.0.0
**维护人员**WebSocket 开发团队

View File

@@ -0,0 +1,754 @@
<!--
Uniapp WebSocket 测试页面
用于在 Uniapp 项目中测试微信小程序的 WebSocket 功能
使用方法
1. Uniapp 项目的 pages 目录下创建 ws-test 目录
2. 将此文件保存为 ws-test.vue
3. pages.json 中注册该页面
4. 在微信开发者工具中运行项目并访问该页面
-->
<template>
<view class="container">
<view class="header">
<text class="title">WebSocket 测试</text>
<view class="status" :class="statusClass">
{{ connectionStatus }}
</view>
</view>
<view class="settings">
<view class="form-item">
<text class="label">服务器地址</text>
<input
class="input"
v-model="wsUrl"
placeholder="wss://your-domain.com/ws/aikefu"
/>
</view>
<view class="btn-group">
<button
class="btn btn-primary"
@click="connectWebSocket"
:disabled="connecting"
>
{{ connecting ? '连接中...' : '连接' }}
</button>
<button
class="btn btn-default"
@click="disconnectWebSocket"
:disabled="!connected"
>
断开连接
</button>
<button
class="btn btn-warn"
@click="reconnectWebSocket"
:disabled="connecting"
>
重连
</button>
</view>
</view>
<view class="message-area">
<scroll-view
class="message-list"
scroll-y="true"
:scroll-top="scrollTop"
@scroll="onScroll"
>
<view
v-for="(message, index) in messages"
:key="message.id"
class="message-item"
:class="message.type"
>
<view class="message-header">
<text class="sender">{{ message.sender }}</text>
<text class="time">{{ message.time }}</text>
</view>
<view class="message-content">
{{ formatMessage(message.content) }}
</view>
</view>
</scroll-view>
</view>
<view class="input-area">
<textarea
class="message-input"
v-model="inputMessage"
placeholder="输入要发送的消息..."
@confirm="sendCustomMessage"
></textarea>
<view class="btn-group">
<button class="btn btn-sm" @click="sendPing">Ping</button>
<button class="btn btn-sm" @click="sendTestMessage">测试消息</button>
<button class="btn btn-sm btn-primary" @click="sendCustomMessage">发送</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
wsUrl: 'wss://your-domain.com/ws/aikefu', // 替换为你的 WebSocket 服务器地址
connectionStatus: '未连接',
connected: false,
connecting: false,
socketTask: null,
messages: [],
inputMessage: '',
scrollTop: 0,
autoScroll: true
};
},
computed: {
statusClass() {
if (this.connecting) return 'status-connecting';
if (this.connected) return 'status-connected';
return 'status-disconnected';
}
},
onLoad() {
// 页面加载时自动连接
this.connectWebSocket();
},
onUnload() {
// 页面卸载时断开连接
this.closeWebSocket();
},
onHide() {
// 页面隐藏时断开连接
this.closeWebSocket();
},
methods: {
// 连接 WebSocket
connectWebSocket() {
if (this.connected || this.connecting) return;
this.connecting = true;
this.updateStatus('连接中...');
try {
// 创建 WebSocket 连接 (Uniapp API)
this.socketTask = uni.connectSocket({
url: this.wsUrl,
header: {
'content-type': 'application/json'
},
method: 'GET',
success: (res) => {
console.log('WebSocket 连接请求发送成功', res);
},
fail: (err) => {
console.error('WebSocket 连接请求发送失败', err);
this.connecting = false;
this.updateStatus('连接失败');
this.addMessage('系统', 'WebSocket 连接请求发送失败: ' + JSON.stringify(err), 'error');
}
});
// 监听 WebSocket 连接打开
this.socketTask.onOpen((res) => {
console.log('WebSocket 连接已打开', res);
this.connected = true;
this.connecting = false;
this.updateStatus('已连接');
this.addMessage('系统', 'WebSocket 连接已打开', 'system');
});
// 监听 WebSocket 接收到服务器的消息
this.socketTask.onMessage((res) => {
console.log('收到服务器消息', res.data);
this.addMessage('服务器', res.data, 'received');
});
// 监听 WebSocket 连接关闭
this.socketTask.onClose((res) => {
console.log('WebSocket 连接已关闭', res);
this.connected = false;
this.connecting = false;
this.updateStatus('已断开');
this.addMessage('系统', 'WebSocket 连接已关闭', 'system');
});
// 监听 WebSocket 错误
this.socketTask.onError((res) => {
console.error('WebSocket 连接发生错误', res);
this.connected = false;
this.connecting = false;
this.updateStatus('连接错误');
this.addMessage('系统', 'WebSocket 连接发生错误: ' + JSON.stringify(res), 'error');
});
} catch (error) {
console.error('WebSocket 连接异常', error);
this.connecting = false;
this.updateStatus('连接异常');
this.addMessage('系统', 'WebSocket 连接异常: ' + error.message, 'error');
}
},
// 断开 WebSocket 连接
disconnectWebSocket() {
if (!this.connected && !this.connecting) return;
this.closeWebSocket();
this.connected = false;
this.connecting = false;
this.updateStatus('已断开');
this.addMessage('系统', 'WebSocket 连接已手动断开', 'system');
},
// 关闭 WebSocket 连接(内部使用)
closeWebSocket() {
if (this.socketTask) {
try {
this.socketTask.close({
code: 1000,
reason: '用户主动断开连接'
});
} catch (error) {
console.error('关闭 WebSocket 连接失败', error);
}
this.socketTask = null;
}
},
// 重连 WebSocket
reconnectWebSocket() {
this.closeWebSocket();
this.connected = false;
this.connecting = false;
// 延迟 1 秒后重连
setTimeout(() => {
this.connectWebSocket();
}, 1000);
},
// 发送 Ping 消息
sendPing() {
this.sendMessage(JSON.stringify({ action: 'ping' }));
},
// 发送测试消息
sendTestMessage() {
this.sendMessage(JSON.stringify({
message: '你好,这是 Uniapp 测试消息!',
action: 'test',
timestamp: Date.now()
}));
},
// 发送自定义消息
sendCustomMessage() {
if (!this.inputMessage.trim()) {
uni.showToast({
title: '请输入消息内容',
icon: 'none'
});
return;
}
try {
// 尝试解析为 JSON
JSON.parse(this.inputMessage);
this.sendMessage(this.inputMessage);
} catch (error) {
// 不是 JSON包装为普通消息
this.sendMessage(JSON.stringify({
message: this.inputMessage,
action: 'chat'
}));
}
// 清空输入框
this.inputMessage = '';
},
// 发送消息(通用方法)
sendMessage(message) {
if (!this.connected || !this.socketTask) {
uni.showToast({
title: 'WebSocket 未连接',
icon: 'none'
});
this.updateStatus('已断开');
return;
}
try {
// 使用 Uniapp API 发送消息
this.socketTask.send({
data: message,
success: () => {
this.addMessage('我', message, 'sent');
},
fail: (err) => {
console.error('发送消息失败', err);
this.addMessage('系统', '发送消息失败: ' + JSON.stringify(err), 'error');
}
});
} catch (error) {
console.error('发送消息异常', error);
this.addMessage('系统', '发送消息异常: ' + error.message, 'error');
}
},
// 更新连接状态
updateStatus(status) {
this.connectionStatus = status;
},
// 添加消息到消息列表
addMessage(sender, content, type) {
const message = {
id: Date.now(),
sender,
content,
type,
time: this.formatTime(new Date())
};
this.messages.push(message);
// 自动滚动到底部
this.scrollToBottom();
},
// 滚动到底部
scrollToBottom() {
if (this.autoScroll) {
// 延迟执行以确保 DOM 已更新
this.$nextTick(() => {
uni.createSelectorQuery().in(this)
.select('.message-list')
.boundingClientRect((rect) => {
if (rect) {
this.scrollTop = rect.height;
}
})
.exec();
});
}
},
// 格式化时间
formatTime(date) {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
},
// 格式化消息内容(美化 JSON
formatMessage(content) {
try {
const parsed = JSON.parse(content);
return JSON.stringify(parsed, null, 2);
} catch (error) {
return content;
}
},
// 处理滚动事件
onScroll(e) {
const { scrollTop, scrollHeight, clientHeight } = e.detail;
// 判断是否滚动到底部附近
this.autoScroll = scrollTop + clientHeight >= scrollHeight - 20;
}
}
};
</script>
<style scoped>
.container {
display: flex;
flex-direction: column;
height: 100vh;
padding: 20rpx;
background-color: #f5f5f5;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #eee;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.status {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
font-weight: bold;
}
.status-connecting {
background-color: #fff3cd;
color: #856404;
}
.status-connected {
background-color: #d4edda;
color: #155724;
}
.status-disconnected {
background-color: #f8d7da;
color: #721c24;
}
.settings {
background-color: #fff;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.form-item {
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
}
.label {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
}
.input {
height: 80rpx;
padding: 0 20rpx;
border: 1rpx solid #ddd;
border-radius: 10rpx;
font-size: 28rpx;
background-color: #f9f9f9;
}
.btn-group {
display: flex;
justify-content: flex-start;
gap: 15rpx;
flex-wrap: wrap;
}
.btn {
flex: 1;
min-width: 150rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 10rpx;
font-size: 28rpx;
border: none;
}
.btn-primary {
background-color: #007aff;
color: #fff;
}
.btn-default {
background-color: #f5f5f5;
color: #333;
border: 1rpx solid #ddd;
}
.btn-warn {
background-color: #ff3b30;
color: #fff;
}
.btn-sm {
height: 60rpx;
line-height: 60rpx;
font-size: 24rpx;
min-width: 100rpx;
}
.message-area {
flex: 1;
background-color: #fff;
border-radius: 10rpx;
margin-bottom: 20rpx;
overflow: hidden;
}
.message-list {
height: 100%;
padding: 20rpx;
}
.message-item {
margin-bottom: 20rpx;
padding: 15rpx;
border-radius: 10rpx;
max-width: 80%;
}
.message-item.sent {
align-self: flex-end;
margin-left: auto;
background-color: #d4edda;
border-right: 4rpx solid #28a745;
}
.message-item.received {
align-self: flex-start;
background-color: #e9ecef;
border-left: 4rpx solid #007bff;
}
.message-item.system {
align-self: center;
background-color: #fff3cd;
border-left: 4rpx solid #856404;
max-width: 90%;
}
.message-item.error {
align-self: center;
background-color: #f8d7da;
border-left: 4rpx solid #dc3545;
max-width: 90%;
}
.message-header {
display: flex;
justify-content: space-between;
margin-bottom: 10rpx;
font-size: 24rpx;
color: #666;
}
.message-content {
font-size: 28rpx;
line-height: 1.5;
word-break: break-all;
white-space: pre-wrap;
}
.input-area {
background-color: #fff;
border-radius: 10rpx;
padding: 20rpx;
}
.message-input {
width: 100%;
min-height: 120rpx;
max-height: 200rpx;
padding: 20rpx;
margin-bottom: 20rpx;
border: 1rpx solid #ddd;
border-radius: 10rpx;
font-size: 28rpx;
background-color: #f9f9f9;
}
</style>
<script module="pages.json">
// 该模块仅用于示例,实际应在项目根目录的 pages.json 中配置
/*
{
"pages": [
{
"path": "pages/ws-test/ws-test",
"style": {
"navigationBarTitleText": "WebSocket 测试"
}
}
]
}
*/
</script>
<script module="README">
/*
# Uniapp WebSocket 测试页面使用说明
## 功能说明
用于在 Uniapp 项目中测试微信小程序的 WebSocket 功能,支持连接管理、消息发送和接收。
## 使用方法
### 1. 创建页面
将本文件保存为 `pages/ws-test/ws-test.vue`
### 2. 配置页面路由
在 `pages.json` 中添加以下配置:
```json
{
"pages": [
{
"path": "pages/ws-test/ws-test",
"style": {
"navigationBarTitleText": "WebSocket 测试"
}
}
]
}
```
### 3. 配置服务器地址
在 `ws-test.vue` 文件中修改 `wsUrl` 变量:
```javascript
data() {
return {
wsUrl: 'wss://your-domain.com/ws/aikefu', // 替换为你的 WebSocket 服务器地址
// ...
};
}
```
### 4. 运行测试
- 使用 HBuilderX 开发工具
- 选择 "运行" -> "运行到小程序模拟器" -> "微信开发者工具"
- 在微信开发者工具中访问该页面
## 注意事项
### 微信小程序限制
1. **域名配置**:需要在微信小程序管理后台配置合法域名
2. **协议支持**:仅支持 HTTPS/WSS 协议
3. **连接数限制**:最多同时存在 5 个 WebSocket 连接
### Uniapp API 特点
1. Uniapp 使用 `uni.connectSocket()` 替代微信小程序的 `wx.connectSocket()`
2. Uniapp 使用 `uni.createSelectorQuery()` 替代微信小程序的 `wx.createSelectorQuery()`
3. 页面生命周期钩子函数使用 Vue 组件的 `onLoad()`、`onUnload()`、`onHide()`
## 扩展功能
### 添加消息类型支持
可以根据业务需求扩展支持的消息类型:
```javascript
// 发送聊天消息
sendChatMessage() {
this.sendMessage(JSON.stringify({
action: 'chat',
message: this.inputMessage,
userId: 'user123',
timestamp: Date.now()
}));
}
// 发送指令消息
sendCommand(command, params) {
this.sendMessage(JSON.stringify({
action: 'command',
command,
params,
timestamp: Date.now()
}));
}
```
### 添加自动重连机制
可以实现更智能的自动重连机制:
```javascript
data() {
return {
// ...
reconnectAttempts: 0,
maxReconnectAttempts: 5,
reconnectDelay: 1000
};
},
methods: {
// 自动重连
autoReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
this.addMessage('系统', '自动重连失败,已达到最大尝试次数', 'error');
return;
}
this.reconnectAttempts++;
this.addMessage('系统', `尝试自动重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`, 'system');
setTimeout(() => {
this.connectWebSocket();
}, this.reconnectDelay);
},
// 连接成功后重置重连计数
onConnectSuccess() {
this.reconnectAttempts = 0;
// ...
}
}
```
## 常见问题
### 1. 连接失败
**可能原因**
- 服务器地址错误
- 未配置合法域名
- 服务器未启动或网络不可达
- 使用了 HTTP 协议而不是 HTTPS
**解决方案**
- 检查服务器地址和端口
- 在微信小程序管理后台配置合法域名
- 确保服务器正常运行
- 切换到 WSS 协议
### 2. 消息发送失败
**可能原因**
- WebSocket 连接已关闭
- 消息格式不正确
- 网络中断
**解决方案**
- 检查连接状态,必要时重新连接
- 确保消息格式为 JSON 字符串
- 检查网络连接
### 3. 接收消息异常
**可能原因**
- 服务器返回格式错误
- 客户端解析错误
**解决方案**
- 检查服务器返回的消息格式
- 调试客户端解析逻辑
## 联系和支持
如果在使用过程中遇到问题,请联系开发人员或查看相关文档。
---
**更新时间**2025-12-19
**版本**1.0.0
**维护人员**WebSocket 开发团队
*/
</script>

View File

@@ -0,0 +1,283 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 测试客户端</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.header {
background: #f5f5f5;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
.connection-info {
margin-bottom: 20px;
}
.status {
padding: 5px 10px;
border-radius: 3px;
font-weight: bold;
}
.status.connected {
background: #d4edda;
color: #155724;
}
.status.disconnected {
background: #f8d7da;
color: #721c24;
}
.input-area {
margin-bottom: 20px;
}
.input-area textarea {
width: 100%;
height: 100px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 10px;
}
.input-area button {
margin-right: 10px;
padding: 8px 15px;
background: #007bff;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.input-area button:hover {
background: #0056b3;
}
.messages {
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
height: 300px;
overflow-y: auto;
background: #f9f9f9;
}
.message {
margin-bottom: 10px;
padding: 10px;
border-radius: 5px;
}
.message.received {
background: #e9ecef;
border-left: 4px solid #007bff;
}
.message.sent {
background: #d4edda;
border-right: 4px solid #28a745;
text-align: right;
}
.message.error {
background: #f8d7da;
border-left: 4px solid #dc3545;
}
.message-header {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.settings {
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
}
.settings input {
width: 300px;
padding: 5px;
margin: 5px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>WebSocket 服务器测试客户端</h1>
<p>用于测试 WebSocket 服务器的连接和基本功能</p>
</div>
<div class="settings">
<h3>设置</h3>
<label>WebSocket 服务器地址: </label>
<input type="text" id="ws-url" value="ws://localhost:8080/ws" placeholder="ws://localhost:8080/ws">
<br>
<button onclick="connectWebSocket()">连接</button>
<button onclick="disconnectWebSocket()">断开连接</button>
</div>
<div class="connection-info">
<strong>连接状态: </strong>
<span id="connection-status" class="status disconnected">未连接</span>
</div>
<div class="input-area">
<textarea id="message-input" placeholder="输入要发送的消息..."></textarea>
<br>
<button onclick="sendPing()">发送 Ping</button>
<button onclick="sendTestMessage()">发送测试消息</button>
<button onclick="sendMessage()">发送自定义消息</button>
</div>
<div class="messages" id="messages-container">
<!-- 消息将显示在这里 -->
</div>
</div>
<script>
let ws = null;
// 连接 WebSocket 服务器
function connectWebSocket() {
const wsUrl = document.getElementById('ws-url').value;
// 先断开已有连接
if (ws) {
ws.close();
}
try {
ws = new WebSocket(wsUrl);
ws.onopen = function() {
updateStatus('connected', '已连接');
addMessage('系统', '成功连接到 WebSocket 服务器', 'received');
};
ws.onmessage = function(event) {
addMessage('服务器', event.data, 'received');
};
ws.onclose = function() {
updateStatus('disconnected', '已断开连接');
addMessage('系统', '与 WebSocket 服务器的连接已断开', 'error');
};
ws.onerror = function(error) {
addMessage('系统', 'WebSocket 错误: ' + error, 'error');
};
} catch (error) {
addMessage('系统', '连接失败: ' + error, 'error');
}
}
// 断开连接
function disconnectWebSocket() {
if (ws) {
ws.close();
ws = null;
}
}
// 发送 Ping 消息
function sendPing() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
addMessage('系统', 'WebSocket 未连接', 'error');
return;
}
const message = JSON.stringify({action: 'ping'});
ws.send(message);
addMessage('我', message, 'sent');
}
// 发送测试消息
function sendTestMessage() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
addMessage('系统', 'WebSocket 未连接', 'error');
return;
}
const message = JSON.stringify({message: 'Hello WebSocket!', action: 'test'});
ws.send(message);
addMessage('我', message, 'sent');
}
// 发送自定义消息
function sendMessage() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
addMessage('系统', 'WebSocket 未连接', 'error');
return;
}
const messageInput = document.getElementById('message-input');
const message = messageInput.value.trim();
if (!message) {
addMessage('系统', '请输入消息内容', 'error');
return;
}
try {
// 尝试解析为 JSON如果不是 JSON 则直接发送
JSON.parse(message);
ws.send(message);
} catch (error) {
// 不是 JSON作为普通文本发送
ws.send(JSON.stringify({message: message}));
}
addMessage('我', message, 'sent');
messageInput.value = '';
}
// 更新连接状态
function updateStatus(statusClass, text) {
const statusElement = document.getElementById('connection-status');
statusElement.className = `status ${statusClass}`;
statusElement.textContent = text;
}
// 添加消息到消息列表
function addMessage(sender, content, type) {
const messagesContainer = document.getElementById('messages-container');
const messageElement = document.createElement('div');
messageElement.className = `message ${type}`;
const now = new Date();
const time = now.toLocaleTimeString();
messageElement.innerHTML = `
<div class="message-header">${sender} - ${time}</div>
<div class="message-content">${formatMessage(content)}</div>
`;
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 格式化消息显示(美化 JSON
function formatMessage(content) {
try {
const parsed = JSON.parse(content);
return JSON.stringify(parsed, null, 2);
} catch (error) {
return content;
}
}
// 页面加载完成后自动连接
window.onload = function() {
connectWebSocket();
};
// 页面关闭时断开连接
window.onbeforeunload = function() {
disconnectWebSocket();
};
</script>
</body>
</html>

View File

@@ -0,0 +1,72 @@
<?php
/**
* WebSocket 服务器 PHP 客户端测试脚本
* 用于测试 WebSocket 服务器的连接和基本功能
*
* 使用方法php test_websocket.php
* 注意:需要先启动 WebSocket 服务器 (php ws_server.php)
*/
use Ratchet\Client\WebSocket;use React\EventLoop\Factory;
use React\Socket\Connector;
use Ratchet\RFC6455\Messaging\MessageInterface;
require __DIR__ . '/../../src/vendor/autoload.php';
// WebSocket 服务器地址
$wsUrl = 'ws://localhost:8080/ws'; // 默认测试路径
// $wsUrl = 'ws://localhost:8080/ws/aikefu'; // aikefu 插件路径
echo "正在连接到 WebSocket 服务器: {$wsUrl}\n";
// 创建事件循环
$loop = Factory::create();
// 创建连接
$connector = new Connector($loop);
// 连接到 WebSocket 服务器
$connector($wsUrl)
->then(function ($conn) use ($loop) {
echo "✅ 成功连接到 WebSocket 服务器\n";
// 创建 WebSocket 客户端
$ws = new WebSocket($conn);
// 收到消息时的处理
$ws->on('message', function (MessageInterface $msg) use ($ws) {
echo "📥 收到消息: {$msg}\n";
});
// 连接关闭时的处理
$ws->on('close', function ($code = null, $reason = null) {
echo "❌ 连接已关闭: {$code} - {$reason}\n";
});
// 发送 ping 消息测试
echo "📤 发送 ping 消息\n";
$ws->send(json_encode(['action' => 'ping']));
// 发送测试消息
echo "📤 发送测试消息\n";
$ws->send(json_encode(['message' => 'Hello WebSocket!', 'action' => 'test']));
// 3秒后关闭连接
$loop->addTimer(3, function () use ($ws) {
echo "⏰ 关闭连接\n";
$ws->close();
});
// 如果是 aikefu 插件路径,可以发送聊天消息测试
// $ws->send(json_encode(['message' => '你好,客服', 'token' => 'your_token', 'action' => 'chat']));
}, function ($e) use ($loop) {
echo "❌ 连接失败: {$e->getMessage()}\n";
$loop->stop();
});
// 运行事件循环
$loop->run();
echo "测试完成\n";

View File

@@ -0,0 +1,429 @@
/**
* 微信小程序 WebSocket 客户端测试代码
* 用于测试微信小程序环境下与 WebSocket 服务器的连接和通信
*
* 使用方法:
* 1. 将此代码复制到微信小程序的页面 JavaScript 文件中
* 2. 在对应的 WXML 文件中添加测试按钮和消息显示区域
* 3. 在微信开发者工具中运行测试
*/
// WebSocket 连接实例
let ws = null;
// 页面数据
Page({
data: {
wsUrl: 'wss://your-domain.com/ws/aikefu', // 替换为你的 WebSocket 服务器地址
connectionStatus: '未连接',
messages: [],
inputMessage: ''
},
// 生命周期函数--监听页面加载
onLoad: function(options) {
this.connectWebSocket();
},
// 生命周期函数--监听页面卸载
onUnload: function() {
this.closeWebSocket();
},
// 生命周期函数--监听页面隐藏
onHide: function() {
this.closeWebSocket();
},
// 连接 WebSocket 服务器
connectWebSocket: function() {
const that = this;
const wsUrl = this.data.wsUrl;
// 创建 WebSocket 连接
ws = wx.connectSocket({
url: wsUrl,
header: {
'content-type': 'application/json'
},
method: 'GET',
success: function(res) {
console.log('WebSocket 连接请求发送成功', res);
that.updateStatus('连接中...');
},
fail: function(err) {
console.error('WebSocket 连接请求发送失败', err);
that.updateStatus('连接失败');
that.addMessage('系统', 'WebSocket 连接请求发送失败: ' + JSON.stringify(err), 'error');
}
});
// 监听 WebSocket 连接打开
ws.onOpen(function(res) {
console.log('WebSocket 连接已打开', res);
that.updateStatus('已连接');
that.addMessage('系统', 'WebSocket 连接已打开', 'system');
});
// 监听 WebSocket 接收到服务器的消息
ws.onMessage(function(res) {
console.log('收到服务器消息', res.data);
that.addMessage('服务器', res.data, 'received');
});
// 监听 WebSocket 连接关闭
ws.onClose(function(res) {
console.log('WebSocket 连接已关闭', res);
that.updateStatus('已断开');
that.addMessage('系统', 'WebSocket 连接已关闭', 'system');
});
// 监听 WebSocket 错误
ws.onError(function(res) {
console.error('WebSocket 连接发生错误', res);
that.updateStatus('连接错误');
that.addMessage('系统', 'WebSocket 连接发生错误: ' + JSON.stringify(res), 'error');
});
},
// 关闭 WebSocket 连接
closeWebSocket: function() {
if (ws) {
wx.closeSocket();
ws = null;
}
},
// 发送 Ping 消息
sendPing: function() {
this.sendMessage(JSON.stringify({ action: 'ping' }));
},
// 发送测试消息
sendTestMessage: function() {
this.sendMessage(JSON.stringify({
message: '你好,这是微信小程序的测试消息!',
action: 'test'
}));
},
// 发送自定义消息
sendCustomMessage: function() {
const message = this.data.inputMessage.trim();
if (!message) {
wx.showToast({
title: '请输入消息内容',
icon: 'none'
});
return;
}
try {
// 尝试解析为 JSON如果不是 JSON 则包装为 JSON
JSON.parse(message);
this.sendMessage(message);
} catch (error) {
// 不是 JSON作为普通文本发送
this.sendMessage(JSON.stringify({ message: message }));
}
// 清空输入框
this.setData({
inputMessage: ''
});
},
// 发送消息
sendMessage: function(message) {
if (!ws) {
wx.showToast({
title: 'WebSocket 未连接',
icon: 'none'
});
return;
}
// 检查 WebSocket 连接状态
wx.getSocketTask().then(task => {
if (task.readyState === 1) { // 连接已打开
wx.sendSocketMessage({
data: message,
success: () => {
this.addMessage('我', message, 'sent');
},
fail: (err) => {
console.error('发送消息失败', err);
this.addMessage('系统', '发送消息失败: ' + JSON.stringify(err), 'error');
}
});
} else {
wx.showToast({
title: 'WebSocket 连接未打开',
icon: 'none'
});
this.updateStatus('连接已断开');
}
}).catch(err => {
console.error('获取 SocketTask 失败', err);
this.updateStatus('连接已断开');
});
},
// 更新输入框内容
onInputMessage: function(e) {
this.setData({
inputMessage: e.detail.value
});
},
// 更新连接状态
updateStatus: function(status) {
this.setData({
connectionStatus: status
});
},
// 添加消息到消息列表
addMessage: function(sender, content, type) {
const messages = this.data.messages;
const now = new Date();
const time = now.toLocaleTimeString();
// 尝试美化 JSON 格式
let formattedContent = content;
try {
const parsed = JSON.parse(content);
formattedContent = JSON.stringify(parsed, null, 2);
} catch (error) {
// 不是 JSON保持原样
}
messages.push({
id: Date.now(),
sender: sender,
content: formattedContent,
type: type,
time: time
});
this.setData({
messages: messages
});
// 滚动到最新消息
this.scrollToBottom();
},
// 滚动到消息底部
scrollToBottom: function() {
wx.createSelectorQuery().select('#message-list').boundingClientRect(function(rect) {
if (rect) {
wx.pageScrollTo({
scrollTop: rect.height,
duration: 300
});
}
}).exec();
},
// 重连 WebSocket
reconnectWebSocket: function() {
this.closeWebSocket();
setTimeout(() => {
this.connectWebSocket();
}, 1000);
}
});
// 对应的 WXML 文件示例
/*
<view class="container">
<view class="header">
<text class="title">WebSocket 测试</text>
<text class="status {{connectionStatus === '已连接' ? 'connected' : connectionStatus === '连接中...' ? 'connecting' : 'disconnected'}}">
{{connectionStatus}}
</text>
</view>
<view class="settings">
<input class="url-input" placeholder="请输入 WebSocket 服务器地址" value="{{wsUrl}}" bindinput="onInputUrl" />
<button class="btn" bindtap="connectWebSocket" type="primary" size="mini">连接</button>
<button class="btn" bindtap="closeWebSocket" type="default" size="mini">断开</button>
<button class="btn" bindtap="reconnectWebSocket" type="warn" size="mini">重连</button>
</view>
<view class="message-area">
<scroll-view id="message-list" scroll-y="true" class="message-list">
<block wx:for="{{messages}}" wx:key="id">
<view class="message-item {{item.type}}">
<view class="message-header">
<text class="sender">{{item.sender}}</text>
<text class="time">{{item.time}}</text>
</view>
<view class="message-content">{{item.content}}</view>
</view>
</block>
</scroll-view>
</view>
<view class="input-area">
<textarea class="message-input" placeholder="请输入消息..." value="{{inputMessage}}" bindinput="onInputMessage" />
<view class="btn-group">
<button class="btn" bindtap="sendPing" type="default" size="mini">发送 Ping</button>
<button class="btn" bindtap="sendTestMessage" type="default" size="mini">测试消息</button>
<button class="btn" bindtap="sendCustomMessage" type="primary" size="mini">发送</button>
</view>
</view>
</view>
*/
// 对应的 WXSS 样式文件示例
/*
.container {
display: flex;
flex-direction: column;
height: 100vh;
padding: 20rpx;
background-color: #f5f5f5;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
}
.status {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
font-weight: bold;
}
.status.connected {
background-color: #d4edda;
color: #155724;
}
.status.connecting {
background-color: #fff3cd;
color: #856404;
}
.status.disconnected {
background-color: #f8d7da;
color: #721c24;
}
.settings {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding: 20rpx;
background-color: white;
border-radius: 10rpx;
}
.url-input {
flex: 1;
height: 80rpx;
padding: 20rpx;
margin-right: 20rpx;
border: 1px solid #ddd;
border-radius: 10rpx;
font-size: 28rpx;
}
.btn {
margin-right: 10rpx;
}
.message-area {
flex: 1;
margin-bottom: 20rpx;
background-color: white;
border-radius: 10rpx;
overflow: hidden;
}
.message-list {
height: 100%;
padding: 20rpx;
}
.message-item {
margin-bottom: 20rpx;
padding: 15rpx;
border-radius: 10rpx;
max-width: 80%;
}
.message-item.sent {
align-self: flex-end;
margin-left: auto;
background-color: #d4edda;
border-right: 4rpx solid #28a745;
}
.message-item.received {
align-self: flex-start;
background-color: #e9ecef;
border-left: 4rpx solid #007bff;
}
.message-item.system {
align-self: center;
background-color: #fff3cd;
border-left: 4rpx solid #856404;
max-width: 90%;
}
.message-item.error {
align-self: center;
background-color: #f8d7da;
border-left: 4rpx solid #dc3545;
max-width: 90%;
}
.message-header {
display: flex;
justify-content: space-between;
margin-bottom: 10rpx;
font-size: 24rpx;
color: #666;
}
.message-content {
font-size: 28rpx;
line-height: 1.5;
word-break: break-all;
}
.input-area {
background-color: white;
padding: 20rpx;
border-radius: 10rpx;
}
.message-input {
width: 100%;
height: 120rpx;
padding: 20rpx;
margin-bottom: 20rpx;
border: 1px solid #ddd;
border-radius: 10rpx;
font-size: 28rpx;
background-color: #f9f9f9;
}
.btn-group {
display: flex;
justify-content: flex-end;
}
*/