chore(websocket): 更新ws_server

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

View File

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