YingXingAI/docs/WebSocket使用示例.md

608 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WebSocket 使用示例
## 📦 已创建的文件
`/utils/websocket-manager.js` - WebSocket 管理器(已完成)
---
## 🚀 快速开始
### 1. 在 App.vue 中初始化连接
```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`
```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 状态(可选)
```javascript
// 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. 发送文字消息
```json
{
"type": "message",
"fromUserId": "user123",
"toUserId": "user456",
"content": "你好",
"timestamp": 1635678901234
}
```
#### 2. 发送心跳
```json
{
"type": "ping",
"timestamp": 1635678901234
}
```
#### 3. 发送已读回执
```json
{
"type": "read",
"messageId": "msg123",
"userId": "user123"
}
```
---
### 服务器 → 客户端
#### 1. 推送消息
```json
{
"type": "message",
"messageId": "msg123",
"fromUserId": "user456",
"toUserId": "user123",
"content": "你好",
"timestamp": 1635678901234
}
```
#### 2. 心跳响应
```json
{
"type": "pong",
"timestamp": 1635678901234
}
```
#### 3. 系统通知
```json
{
"type": "notification",
"content": "系统维护通知",
"timestamp": 1635678901234
}
```
#### 4. 错误消息
```json
{
"type": "error",
"code": 401,
"message": "未授权",
"timestamp": 1635678901234
}
```
---
## 🔧 后端实现参考
### Node.js + ws
```javascript
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 占用正常
---
## 🎉 总结
现在你已经有
1. **WebSocket 管理器** (`utils/websocket-manager.js`)
- 自动重连
- 心跳检测
- 消息队列
- 事件监听
2. **使用示例**
- App.vue 初始化
- 聊天页面使用
- Vuex 状态管理
3. **消息协议**
- 客户端 服务器
- 服务器 客户端
4. **后端参考**
- Node.js + ws 实现
---
## 🚀 下一步
1. **确认后端技术栈**
- 告诉我你用什么后端我帮你写完整的服务端代码
2. **集成到现有聊天页面**
- 我帮你修改 `dialogBox.vue`
3. **添加更多功能**
- 图片消息
- 语音消息
- 消息已读
- 输入中状态
随时告诉我需要什么!🎯