12 KiB
12 KiB
WebSocket 使用示例
📦 已创建的文件
✅ /utils/websocket-manager.js - WebSocket 管理器(已完成)
🚀 快速开始
1. 在 App.vue 中初始化连接
<script>
import wsManager from '@/utils/websocket-manager.js'
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['vuex_token', 'vuex_user'])
},
onLaunch() {
console.log('App Launch')
// 如果已登录,连接 WebSocket
if (this.vuex_token) {
this.connectWebSocket()
}
},
methods: {
connectWebSocket() {
// 替换为你的 WebSocket 服务器地址
const wsUrl = `wss://your-server.com/chat?token=${this.vuex_token}`
wsManager.connect(wsUrl, {
reconnectInterval: 3000, // 重连间隔 3 秒
maxReconnectAttempts: 10, // 最多重连 10 次
heartbeatInterval: 30000 // 心跳间隔 30 秒
})
// 监听连接成功
wsManager.on('open', () => {
console.log('[App] WebSocket 连接成功')
uni.showToast({
title: '已连接',
icon: 'success'
})
})
// 监听连接关闭
wsManager.on('close', (res) => {
console.log('[App] WebSocket 已关闭', res)
})
// 监听连接错误
wsManager.on('error', (err) => {
console.error('[App] WebSocket 错误', err)
uni.showToast({
title: '连接失败',
icon: 'none'
})
})
// 监听重连
wsManager.on('reconnect', (attempts) => {
console.log(`[App] 正在重连 (${attempts})`)
})
// 监听消息(全局消息处理)
wsManager.on('message', (data) => {
console.log('[App] 收到消息:', data)
// 根据消息类型处理
switch (data.type) {
case 'message':
// 新消息
this.handleNewMessage(data)
break
case 'notification':
// 系统通知
this.handleNotification(data)
break
default:
console.log('[App] 未知消息类型:', data.type)
}
})
},
handleNewMessage(data) {
// 更新 Vuex 中的消息列表
this.$store.commit('addMessage', data)
// 显示新消息提示
uni.showTabBarRedDot({
index: 1 // 消息页的 tabBar 索引
})
},
handleNotification(data) {
uni.showToast({
title: data.content,
icon: 'none'
})
}
},
onHide() {
// App 进入后台,保持连接
console.log('App Hide')
},
onShow() {
// App 从后台回到前台
console.log('App Show')
// 如果连接已断开,重新连接
const state = wsManager.getState()
if (!state.isConnected && !state.isConnecting) {
this.connectWebSocket()
}
}
}
</script>
2. 在聊天页面中使用
方式 A:修改现有的 dialogBox.vue
<template>
<view>
<!-- 消息列表 -->
<scroll-view
:scroll-top="scrollTop"
:scroll-with-animation="true"
class="scroll"
scroll-y="true"
>
<view v-for="(msg, index) in messageList" :key="index" class="message-item">
<!-- 我方消息 -->
<view v-if="msg.fromUserId == vuex_user.id" class="my-message">
<text>{{ msg.content }}</text>
</view>
<!-- 对方消息 -->
<view v-else class="other-message">
<text>{{ msg.content }}</text>
</view>
</view>
</scroll-view>
<!-- 输入框 -->
<view class="input-box">
<input
v-model="inputValue"
placeholder="请输入消息"
@confirm="sendMessage"
/>
<button @click="sendMessage">发送</button>
</view>
</view>
</template>
<script>
import wsManager from '@/utils/websocket-manager.js'
import { mapState } from 'vuex'
export default {
data() {
return {
messageList: [],
inputValue: '',
scrollTop: 0,
targetUserId: '', // 对方用户 ID
messageHandler: null
}
},
computed: {
...mapState(['vuex_user'])
},
onLoad(options) {
// 获取对方用户 ID
this.targetUserId = options.userId || ''
// 加载历史消息
this.loadHistoryMessages()
// 监听新消息
this.messageHandler = (data) => {
// 只处理与当前聊天对象相关的消息
if (data.fromUserId === this.targetUserId || data.toUserId === this.targetUserId) {
this.messageList.push({
fromUserId: data.fromUserId,
toUserId: data.toUserId,
content: data.content,
timestamp: data.timestamp
})
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom()
})
// 发送已读回执
this.sendReadReceipt(data.messageId)
}
}
wsManager.on('message', this.messageHandler)
},
onUnload() {
// 移除消息监听
if (this.messageHandler) {
wsManager.off('message', this.messageHandler)
}
},
methods: {
// 加载历史消息
async loadHistoryMessages() {
try {
const res = await this.$u.api.getMessages({
userId: this.targetUserId,
page: 1,
pageSize: 50
})
this.messageList = res.data || []
this.$nextTick(() => {
this.scrollToBottom()
})
} catch (e) {
console.error('加载历史消息失败:', e)
}
},
// 发送消息
sendMessage() {
if (!this.inputValue.trim()) {
uni.showToast({
title: '请输入消息',
icon: 'none'
})
return
}
// 通过 WebSocket 发送
wsManager.send({
type: 'message',
fromUserId: this.vuex_user.id,
toUserId: this.targetUserId,
content: this.inputValue,
timestamp: Date.now()
})
// 立即显示到界面(乐观更新)
this.messageList.push({
fromUserId: this.vuex_user.id,
toUserId: this.targetUserId,
content: this.inputValue,
timestamp: Date.now(),
sending: true // 标记为发送中
})
// 清空输入框
this.inputValue = ''
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom()
})
},
// 发送已读回执
sendReadReceipt(messageId) {
wsManager.send({
type: 'read',
messageId: messageId,
userId: this.vuex_user.id
})
},
// 滚动到底部
scrollToBottom() {
this.scrollTop = 99999
}
}
}
</script>
3. 在 Vuex 中管理 WebSocket 状态(可选)
// store/index.js
export default new Vuex.Store({
state: {
// ... 其他状态
wsConnected: false,
unreadCount: 0,
messages: []
},
mutations: {
// WebSocket 连接状态
SET_WS_CONNECTED(state, connected) {
state.wsConnected = connected
},
// 新消息
ADD_MESSAGE(state, message) {
state.messages.push(message)
state.unreadCount++
},
// 清空未读
CLEAR_UNREAD(state) {
state.unreadCount = 0
}
},
actions: {
// 在 App.vue 中调用
wsConnected({ commit }) {
commit('SET_WS_CONNECTED', true)
},
wsDisconnected({ commit }) {
commit('SET_WS_CONNECTED', false)
}
}
})
📋 消息协议设计
客户端 → 服务器
1. 发送文字消息
{
"type": "message",
"fromUserId": "user123",
"toUserId": "user456",
"content": "你好",
"timestamp": 1635678901234
}
2. 发送心跳
{
"type": "ping",
"timestamp": 1635678901234
}
3. 发送已读回执
{
"type": "read",
"messageId": "msg123",
"userId": "user123"
}
服务器 → 客户端
1. 推送消息
{
"type": "message",
"messageId": "msg123",
"fromUserId": "user456",
"toUserId": "user123",
"content": "你好",
"timestamp": 1635678901234
}
2. 心跳响应
{
"type": "pong",
"timestamp": 1635678901234
}
3. 系统通知
{
"type": "notification",
"content": "系统维护通知",
"timestamp": 1635678901234
}
4. 错误消息
{
"type": "error",
"code": 401,
"message": "未授权",
"timestamp": 1635678901234
}
🔧 后端实现参考
Node.js + ws
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })
// 存储所有连接(用户 ID -> WebSocket)
const clients = new Map()
wss.on('connection', (ws, req) => {
// 从 URL 获取 token
const token = new URL(req.url, 'http://localhost').searchParams.get('token')
// 验证 token,获取用户 ID
const userId = verifyToken(token)
if (!userId) {
ws.close(4001, '未授权')
return
}
// 保存连接
clients.set(userId, ws)
console.log(`用户 ${userId} 已连接`)
// 处理消息
ws.on('message', (message) => {
const data = JSON.parse(message)
switch (data.type) {
case 'message':
// 转发消息
handleMessage(data)
break
case 'ping':
// 心跳响应
ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }))
break
case 'read':
// 已读回执
handleReadReceipt(data)
break
}
})
// 处理断开
ws.on('close', () => {
clients.delete(userId)
console.log(`用户 ${userId} 已断开`)
})
})
// 转发消息
function handleMessage(data) {
const { toUserId, fromUserId, content } = data
// 保存到数据库
saveMessage({ fromUserId, toUserId, content })
// 推送给接收者
const targetWs = clients.get(toUserId)
if (targetWs && targetWs.readyState === WebSocket.OPEN) {
targetWs.send(JSON.stringify({
type: 'message',
messageId: generateId(),
fromUserId,
toUserId,
content,
timestamp: Date.now()
}))
}
}
📊 完整流程图
┌─────────────┐
│ App 启动 │
└──────┬──────┘
│
▼
┌─────────────┐
│ 检查登录状态 │
└──────┬──────┘
│
▼ (已登录)
┌─────────────┐
│ 连接 WS │ ← wsManager.connect()
└──────┬──────┘
│
▼
┌─────────────┐
│ 监听消息 │ ← wsManager.on('message')
└──────┬──────┘
│
├─→ (收到消息) → 更新 UI
│
└─→ (发送消息) → wsManager.send()
│
▼
┌─────────────┐
│ 服务器处理 │
└──────┬──────┘
│
▼
┌─────────────┐
│ 推送给对方 │
└─────────────┘
✅ 测试清单
功能测试
- 连接成功
- 发送消息
- 接收消息
- 心跳正常
- 手动断开
- 自动重连
- 消息队列
场景测试
- App 切换到后台
- App 从后台恢复
- 网络断开
- 网络恢复
- 服务器重启
- 并发多人聊天
性能测试
- 消息延迟 < 200ms
- 重连时间 < 5s
- 内存占用正常
- CPU 占用正常
🎉 总结
现在你已经有:
-
✅ WebSocket 管理器 (
utils/websocket-manager.js)- 自动重连
- 心跳检测
- 消息队列
- 事件监听
-
✅ 使用示例
- App.vue 初始化
- 聊天页面使用
- Vuex 状态管理
-
✅ 消息协议
- 客户端 → 服务器
- 服务器 → 客户端
-
✅ 后端参考
- Node.js + ws 实现
🚀 下一步
-
确认后端技术栈
- 告诉我你用什么后端,我帮你写完整的服务端代码
-
集成到现有聊天页面
- 我帮你修改
dialogBox.vue
- 我帮你修改
-
添加更多功能
- 图片消息
- 语音消息
- 消息已读
- 输入中状态
随时告诉我需要什么!🎯