IM(即时通讯)系统

1. WebSocket (最主流)

  • 原理 :它是 HTML5 的协议。首先通过 HTTP 进行一次"握手",然后升级协议为 ws(加密为 wss)。一旦连接建立,就像打长途电话,双方可以随时说话,不用再挂断重拨。

  • 优势:全双工、延迟极低。

2. SSE (Server-Sent Events)

  • 原理:单向通道。服务器可以不断地往前端发流数据。

  • 优势 :实现简单,自动重连,适合像 ChatGPT 那样的打字机效果。

3. 长轮询 (Polling)


🚩 总结

Vue3 实时会话 = Socket.io (或 MQTT.js) + Composition API (Hooks) + 后端 Node.js/Java 支

那这个socket.io要是发送图片呢?emo表情呢,还有的话是文档excel之类的呢?还有的心跳检测 ,消息去重与有序性,鉴权(在 query 或者是 auth 参数),到底怎么写呢?

哈哈哈。。。

  • 原理:前端每隔 1 秒问一次:"有消息吗?"。

  • 现状:已经过时,浪费流量和性能,除非要兼容极老旧的浏览器。

    |----------------------------|-------------------------------------|-----------------------------------|
    | 框架名称 | 特点 | 适用场景 |
    | Socket.io | 王者级别。自带断线重连、心跳检测、房间管理。 | 复杂的 IM 聊天室、协同办公。 |
    | Vue-Socket.io-Extended | 专门为 Vue 封装的插件,支持 Vue3 和 Pinia/Vuex。 | Vue3 项目快速集成。 |
    | MQTT.js | 基于发布/订阅模式。 | 物联网 (IoT)、 ThingLinks 这种项目必备。 |
    | GoEasy | 国内商业服务,极其稳定,三行代码上线。 | 懒人福音,不想折腾后端服务器的首选。 |
    | | | |

    在 Vue3 中,我们通常把 WebSocket 逻辑封装在一个 Hook 里,方便多个页面复用。

    1. 安装依赖

    Bash

    复制代码
    npm install socket.io-client

    2. 封装 useSocket.ts

    TypeScript

    复制代码
    import { ref, onUnmounted } from 'vue';
    import { io, Socket } from 'socket.io-client';
    
    export function useSocket(url: String) {
      const socket = io(url);
      const messages = ref([]);
      const isConnected = ref(false);
    
      // 监听连接
      socket.on('connect', () => {
        isConnected.value = true;
        console.log('连接成功:', socket.id);
      });
    
      // 监听服务端发来的消息
      socket.on('chat_message', (msg) => {
        messages.value.push(msg);
      });
    
      // 发送消息的方法
      const sendMessage = (text: string) => {
        socket.emit('chat_message', { text, time: new Date() });
      };
    
      // 组件卸载时断开连接,防止内存泄漏
      onUnmounted(() => {
        socket.disconnect();
      });
    
      return { messages, isConnected, sendMessage };
    }

    3. 页面使用

    HTML

    复制代码
    <template>
      <div class="chat-container">
        <p v-if="!isConnected">正在连接服务器...</p>
        <div v-for="(item, index) in messages" :key="index">
          {{ item.text }}
        </div>
        <input v-model="inputMsg" @keyup.enter="handleSend" />
        <button @click="handleSend">发送</button>
      </div>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    import { useSocket } from '@/hooks/useSocket';
    
    const inputMsg = ref('');
    const { messages, isConnected, sendMessage } = useSocket('http://localhost:3000');
    
    const handleSend = () => {
      if (inputMsg.value) {
        sendMessage(inputMsg.value);
        inputMsg.value = '';
      }
    };
    </script>
  • 心跳检测 (Heartbeat):必须每隔一段时间(如 30 秒)发个"乒乓包",确认对方还活着,否则连接会被运营商强行掐断。

  • 消息去重与有序性 :在高并发下,消息可能会乱序,前端需要根据 msgIdtimestamp 进行排序。

  • 安全性 :WebSocket 也要鉴权!建立连接时,记得在 query 或者是 auth 参数里带上你的 Token

Socket.io 中,发送复杂内容本质上只有两种路径:"发文件流""发 URL 地址"

1. 发送图片与文档(主流推荐:URL 模式)

不要直接把几 MB 的图片或 Excel 转成 Base64 塞进 Socket 管道!这会导致消息拥塞,甚至让所有人的聊天卡顿。

标准流程:

上传:前端 Axios 接口将文件传到服务器(如 OSS、MinIO)。

获取 URL :服务器返回文件的访问地址(例如 http://cdn.com/a.xlsx)。

发消息:通过 Socket.io 发送一个包含 URL 和文件类型的 JSON 对象。

复制代码
// 发送文件的消息结构
const fileMsg = {
  type: 'file', // 或者 'image'
  content: 'http://xxx.com/test.xlsx',
  fileName: '2026年报表.xlsx',
  fileSize: '1.2MB'
};
socket.emit('chat_message', fileMsg);

二、 鉴权(Auth & Query)

为了安全,不能让谁都能连上你的 Socket。通常在建立连接的那一刻就要验证身份。

JavaScript

复制代码
import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  // 方式 1:auth 属性(推荐,更安全,数据在握手包里)
  auth: {
    token: uni.getStorageSync('token') 
  },
  // 方式 2:query 属性(放在 URL 参数里,容易被拦截或在日志里暴露)
  query: {
    room: 'room_101'
  }
});

后端(Node.js 示例)拦截:

javascript 复制代码
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (validateToken(token)) {
    next(); // 校验通过
  } else {
    next(new Error("鉴权失败"));
  }
});

JavaScript

四、 消息去重与有序性

这是高并发下的难点。

1. 消息去重(防止重复发送)

  • 前端生成 nonce (唯一 ID):每次点击发送按钮,生成一个 UUID。

  • 后端校验:后端维护一个最近消息 ID 集合,如果发现 ID 重复,直接丢弃,不进行广播。

2. 有序性(防止消息乱序)

由于网络抖动,消息 A 先发可能后到

  • 解决 :每一条消息都带上一个服务端生成 的递增 sequence_id 或高精度时间戳。前端拿到消息后,先按这个 ID 放入缓冲区排序,然后再渲染到屏幕上。

直接看代码吧:

复制代码
// useChat.ts
import { ref } from 'vue';
import { io } from 'socket.io-client';

export const useChat = (roomId) => {
  const socket = io('wss://api.thinglinks.com', {
    auth: { token: 'YOUR_TOKEN' }
  });

  const chatList = ref([]);

  // 1. 处理接收(去重与排序)
  socket.on('message', (newMsg) => {
    // 检查 ID 是否已存在(去重)
    if (chatList.value.find(m => m.id === newMsg.id)) return;
    
    chatList.value.push(newMsg);
    // 按照时间戳排序(有序)
    chatList.value.sort((a, b) => a.time - b.time);
  });

  // 2. 发送逻辑(区分类型)
  const send = (type, content) => {
    const msg = {
      id: generateUUID(), // 前端先给个临时 ID
      type: type, // text, image, file
      content: content,
      time: Date.now()
    };
    socket.emit('message', msg);
  };

  return { chatList, send };
};
相关推荐
小p2 小时前
nestjs 学习 18:prisma 通关
node.js
织_网3 小时前
Nest.js:Node.js后端开发的现代企业级解决方案,赋能AI全栈开发
javascript·人工智能·node.js
Leisureconfused3 小时前
【记录】Node版本兼容性问题及解决
前端·vue.js·npm·node.js
小p19 小时前
nestjs 学习17:封装一个微服务注册与配置中心的动态模块
node.js
老王以为19 小时前
深入理解 AbortController:从底层原理到跨语言设计哲学
javascript·设计模式·node.js
渠过客20 小时前
【运维】PM2 使用完全指南:Node.js 应用进程管理利器
运维·node.js
小粉粉hhh1 天前
Node.js(一)——初始Node.js
node.js
不会写程序的未来程序员1 天前
nvm 安装教程:Node.js 版本管理全攻略 (Win/Mac/Linux) + .nvmrc 实战
linux·macos·node.js·前端开发·环境配置·nvm
米丘1 天前
Vite 开发服务器启动时,如何将 client 注入 HTML?
javascript·node.js·vite