最近做了聊天,功能点大概如下:

  1. 群聊
  2. @全员
  3. 群主禁言
  4. 只看群主
  5. 聊天表情?

技术选择

  1. 基于Socket.io做技术基础
  2. 基于node-redis做聊天消息缓存
  3. 基于node-schedule定时存储Redis中的聊天消息

服务器端

const server = require('http').createServer();

const io = require('socket.io')(server, {
  path: '/socket/chat',
  serveClient: false,
  // below are engine.IO options
  pingInterval: 10000,
  pingTimeout: 5000,
  cookie: false
});

server.listen(3000);
复制代码

客户端

  • 如有需要代理/socket/chat/path设置一致
  • 不设置path,就是默认的socket.io
proxy: {
    "/socket/chat/*": {
      "target": "ws://localhost:8000/socket/chat",
      "ws": true
    },
}
复制代码
  • 连接socket
const ws = io('localhost:4000', {
  path: '/socket/chat',
  transports: ['websocket'],
  reconnection: true,
});

ws.on('connect', () => {});

ws.on('error', error => {
  console.log(error);
});

ws.on('disconnect', reason => {
  console.log('disconnect', reason);
});

window.addEventListener('beforeunload', () => {
  ws.emit('client:disconnect');
});
复制代码
  1. Web端加入房间
// Web端
socket.emit('room:join', id);
// Server端
socket.on('room:join', roomId => {
    socket.join(roomId, error => {
      if (error) {
        server.log([...errorTags, 'join'], { roomId, error });
      }
      socket.on('chat:room:user-send', listener);
    });
});
复制代码
  1. 进入房间后,监听Server端的消息
// Web端
socket.on('chat:room:server-send', this.handleSocket);
// Server端
socket.to(msg.roomId).emit('chat:room:server-send', newMsg);
复制代码
  1. 聊天消息存储到Redis
await redis.ZADDAsync(key, msg.time, JSON.stringify(msg));
复制代码
  1. 分页获取Redis消息
// Web端
// 获取历史消息
socket.emit(
  'room.msg:list',
  { roomId: id, ps, pn, isBottom: true },
  this.handleHistoryMessage
);
// Server端
socket.on(
    'room.msg:list',
    async ({ roomId, ps = 20, pn = 1 }, fn) => {
      const key = getKey('CHAT:MSG', roomId);
    
      const data = await redis.zrevrangebyscoreAsync(
        key,
        Date.now(),
        0,
        'LIMIT',
        (pn - 1) * ps,
        pn * ps
      );
      const list = data.map(it => JSON.parse(it));
      fn({ list, pn });
    }
);
复制代码
  1. 定时存储到Mongo,未完待续...
  2. 表情未完待续...
  3. socket建立太多带来的单机使用问题...

相关方法

  1. 初始化历史消息或者每次接受到聊天消息时,自动滚动到底部,查看最新消息
// 滚动到底部
scrollTop = () => {
    setTimeout(() => {
      this.newsCon.scrollTop = 100000000;
    }, 200);
};
复制代码
  1. 上滑加载更多历史消息
_onScrollEvent = () => {
    const { id } = this.props;
    let { pn } = this.state;
    if (this.newsCon.scrollTop === 0) {
      pn += 1;
      socket.emit(
        'room.msg:list',
        { roomId: id, ps, pn },
        this.handleHistoryMessage
      );
    }
  };
 // React中div
<div
  className="news-con"
  ref={e => {
    this.newsCon = e;
  }}
  onScrollCapture={debounce(this._onScrollEvent, 100)}
>
</div>
复制代码

相关技术学习文章

End

  • 第一次做聊天相关技术,有任何问题欢迎交流