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)) {
|
||||
$app->env->load($envFile);
|
||||
}
|
||||
} else {
|
||||
// 为了兼容性,如果存在.env.local也加载
|
||||
if (is_file(__DIR__ . '/.env.local')) {
|
||||
$app->env->load(__DIR__ . '/.env.local');
|
||||
}
|
||||
}
|
||||
|
||||
use Ratchet\App as RatchetApp;
|
||||
|
||||
Reference in New Issue
Block a user