前几天在项目codergb-hub中为了实现在线聊天功能,学习了一下websocket的基本使用,在此记录一下。前端使用浏览器websocket API,后端(node)使用koa-websocket实现。
- 引入koa-websocket并做初始化
const KoaWebsocket = require('koa-websocket');
const app = new Koa();
const webApp = KoaWebsocket(app);
webApp.listen(SOCKET_APP_PORT,()=>{
console.log("socket服务创建成功");
})
- 注册路由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;
- 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);
})
}
- 用户
//目标聊天用户
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
演示地址