YingXingAI/docs/WebSocket使用示例.md

12 KiB
Raw Blame History

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 占用正常

🎉 总结

现在你已经有:

  1. WebSocket 管理器 (utils/websocket-manager.js)

    • 自动重连
    • 心跳检测
    • 消息队列
    • 事件监听
  2. 使用示例

    • App.vue 初始化
    • 聊天页面使用
    • Vuex 状态管理
  3. 消息协议

    • 客户端 → 服务器
    • 服务器 → 客户端
  4. 后端参考

    • Node.js + ws 实现

🚀 下一步

  1. 确认后端技术栈

    • 告诉我你用什么后端,我帮你写完整的服务端代码
  2. 集成到现有聊天页面

    • 我帮你修改 dialogBox.vue
  3. 添加更多功能

    • 图片消息
    • 语音消息
    • 消息已读
    • 输入中状态

随时告诉我需要什么!🎯