chore(websocket): 更新ws_server
This commit is contained in:
355
docs/websocket/README.md
Normal file
355
docs/websocket/README.md
Normal 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. 联系方式
|
||||||
|
|
||||||
|
如有问题或建议,请联系技术支持团队。
|
||||||
231
docs/websocket/test_readme.md
Normal file
231
docs/websocket/test_readme.md
Normal 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 开发团队
|
||||||
754
docs/websocket/test_uniapp_wechat.vue
Normal file
754
docs/websocket/test_uniapp_wechat.vue
Normal 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>
|
||||||
283
docs/websocket/test_websocket.html
Normal file
283
docs/websocket/test_websocket.html
Normal 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>
|
||||||
72
docs/websocket/test_websocket.php
Normal file
72
docs/websocket/test_websocket.php
Normal 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";
|
||||||
429
docs/websocket/test_wechat_miniprogram.js
Normal file
429
docs/websocket/test_wechat_miniprogram.js
Normal 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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
31
src/app/api/controller/Text.php
Normal file
31
src/app/api/controller/Text.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\model\web\Help as HelpModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*测试
|
||||||
|
* @author Administrator
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Text extends BaseApi
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础信息
|
||||||
|
*/
|
||||||
|
public function cronVerifyCodeExpire()
|
||||||
|
{
|
||||||
|
$order_id = $this->params['order_id'] ?? 0;
|
||||||
|
if (empty($order_id)) {
|
||||||
|
return $this->response($this->error('', 'REQUEST_ID'));
|
||||||
|
}
|
||||||
|
$res = event('CronVerifyCodeExpire', ['relate_id' => $order_id]);
|
||||||
|
dd($res);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -29,11 +29,6 @@ if ($appEnv) {
|
|||||||
if (is_file($envFile)) {
|
if (is_file($envFile)) {
|
||||||
$app->env->load($envFile);
|
$app->env->load($envFile);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 为了兼容性,如果存在.env.local也加载
|
|
||||||
if (is_file(__DIR__ . '/.env.local')) {
|
|
||||||
$app->env->load(__DIR__ . '/.env.local');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use Ratchet\App as RatchetApp;
|
use Ratchet\App as RatchetApp;
|
||||||
|
|||||||
Reference in New Issue
Block a user