websocket实现在线聊天(React + Koa + MySQL)

565 阅读2分钟

前几天在项目codergb-hub中为了实现在线聊天功能,学习了一下websocket的基本使用,在此记录一下。前端使用浏览器websocket API,后端(node)使用koa-websocket实现。

  1. 引入koa-websocket并做初始化
const KoaWebsocket = require('koa-websocket');
const app = new Koa();
const webApp = KoaWebsocket(app);
webApp.listen(SOCKET_APP_PORT,()=>{
  console.log("socket服务创建成功");
})
  1. 注册路由router
const Router = require("koa-router");
const chatRouter = new Router();
const {
  create,
  getChatQueue,
  getChatContent
}=require("../controller/chat.controller")
chatRouter.all("/chat",create)
chatRouter.all("/chat/queue",getChatQueue);
chatRouter.all("/chat/content",getChatContent);
module.exports = chatRouter;
  1. controller层,建立websocket连接,通过全局变量contextMap存储创建的websocket,实现简单在线聊天功能需要携带源用户ID和目标用户ID,判断目标用户是否在线,不在线则isOnline置为0,反之则值为1,并将聊天记记录存入数据库。使用map存储当前socket,方便于删除和查找。
const contextMap=new Map();
async create(ctx,next){
  //获取源用户ID和目标用户ID
  const { targetUser,sourceUser } = ctx.query;
  //将源用户存入全局变量contextMap中,表示用户在线。
  contextMap.set(sourceUser,ctx);
  ctx.websocket.on('message', async function(message) {
    const context = contextMap.get(targetUser);
    const self = contextMap.get(sourceUser);
    //获取所有当前用户的聊天队列(可以忽略)
    const data = await getChatQueueService(targetUser,'0','150000');
    if(data && data.list && data.list.length!==0) {
      const isExists = data.list.find((item) => {
        return item.sourceUser.userId=== sourceUser && item.targetUser.userId === targetUser;
      })
      if (!isExists) {
        await recentUserChatService(sourceUser, targetUser, message.toString());
      }else{
        console.log(isExists);
        await updateQueueMessage(isExists.id,message.toString());
      }
    }else if(data && data.list.length===0){
      await recentUserChatService(sourceUser, targetUser, message.toString());
    }
    //用户在线则发送消息,并将消息存入数据库,在线标识置为1
    if(context){
      await createService(sourceUser,targetUser,message.toString(),1);
      context.websocket.send(message.toString());
    }else{
      //用户不在线,在线标识置为0
      await createService(sourceUser,targetUser,message.toString(),0);
    }
    if(self) self.websocket.send('success');
  });
  //关闭socket连接时从全局变量中删除用户
  ctx.websocket.on("close",()=>{
     contextMap.delete(sourceUser);
  })
}

  1. 用户
//目标聊天用户
const [targetUser,setTargetUser] = useState<string>(userId);
const [targetUserName,setTargetUserName] = useState<string>(userName);
//聊天队列
const [chatQueue,setChatQueue] = useState<IChatQueue[]>([]);
const [chatQueueTotal,setChatQueueTotal] = useState<number>(0)

const [chatContent,setContent] = useState<string>("");
const login = useLoginMsg();

const [chatWebSocket,setChatWebSocket] = useState<CommonWebSocket|null>(null);//发布聊天
const [historyWebSocket,setHistoryWebSocket] = useState<CommonWebSocket|null>(null);//聊天记录
const [queueWebSocket,setQueueWebSocket] = useState<CommonWebSocket|null>(null);//聊天队列
useEffect(()=>{
  //获取聊天队列(当前登陆人)
  let params={
    targetUser:login.userMsg.userId,//当前登陆人
    targetChatUser:targetUser //目标聊天用户是否再聊天队列里
  }
  const webSocket = new CommonWebSocket(CHAT_QUEUE,params);
  setQueueWebSocket(webSocket);
  webSocket.websocket.onmessage=function(e){
    const data:IResponseType<IPage<IChatQueue[]>> = JSON.parse(e.data);
    if(data.status===200){
      setChatQueue(data.data.list);
      setChatQueueTotal(data.data.count);
    }
  }
},[login.userMsg.userId]);
//聊天发布(交互socket)
useEffect(()=>{
  if(targetUser && login.userMsg.userId){
    let params={
      targetUser:targetUser,
      sourceUser:login.userMsg.userId
    }
    let webSocket = new CommonWebSocket(CHAT, params);
    setChatWebSocket(webSocket);
    webSocket.websocket.onmessage=function(e){
      if(historyWebSocket){
        historyWebSocket.sendSocketMsg('pull');
      }
      if(queueWebSocket){
        queueWebSocket.sendSocketMsg('pull');
      }
    }
    webSocket.websocket.onopen=function(){
      console.log("建立成功")
    }
  }
},[targetUser,login.userMsg.userId,historyWebSocket])
//聊天记录
const [contentHistory,setContentHistory] = useState<IChatQueue[]>([]);
const [contentHistoryTotal,setContentHistoryTotal] = useState<number>(0);
useEffect(()=>{
  if(targetUser){
    let params={
      targetUser:targetUser,
      sourceUser:login.userMsg.userId,
      limit:30000
    }
    let webSocket = new CommonWebSocket(CHAT_CONTENT, params);
    setHistoryWebSocket(webSocket);
    webSocket.websocket.onmessage=function(e){
      const data:IResponseType<IPage<IChatQueue[]>> = JSON.parse(e.data);
      if(data.status===200){
        setContentHistory(data.data.list);
      }
      if(queueWebSocket){
        queueWebSocket.sendSocketMsg("pull")
      }
    }
    webSocket.websocket.onopen=function(){
      console.log("建立成功")
    }
  }
},[targetUser,queueWebSocket])
//聊天输入
const chatInp=(e:ChangeEvent<HTMLTextAreaElement>)=>{
  if(e.currentTarget && e.currentTarget.value!==undefined && e.currentTarget.value!==null){
    setContent(e.currentTarget.value)
  }
}
const publishContent=()=>{
  if(chatWebSocket){
    chatWebSocket.sendSocketMsg(chatContent);
    setContent("")
  }
}
const [currentIndex,setCurrentIndex] = useState<number>(0);
const userClick=(item:IChatQueue,index:number)=>{
  setTargetUser(item.sourceUser.userId);
  setTargetUserName(item.sourceUser.userName);
  setCurrentIndex(index);
}
//发布消息回到底部
const contentRef = useRef<HTMLDivElement|null>(null);
useEffect(()=>{
  console.log(contentRef.current?.offsetHeight,contentRef.current?.scrollHeight)
  if(contentRef.current && (contentRef.current.offsetHeight <= contentRef.current.scrollHeight)){
    contentRef.current.scrollTop = contentRef.current.scrollHeight//-contentRef.current.offsetHeight
  }
},[contentRef.current,contentHistory.length])

第一次学习,逻辑实现较为简单主要是实现实时聊天以及实时获取聊天队列。项目地址
codergb-hub
演示地址