百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Node+Websocket实现消息未读之点不完的小红点

cac55 2024-10-26 08:14 17 浏览 0 评论

作者: 蓝色的秋风

转发链接:https://mp.weixin.qq.com/s/dBTog_m7xBnmMeaLE-d16g

前言

这个项目本来是我学生时代为了找工作的一个练手项目,但是没想到受到了很多的关注,star也快要破K了,这也激励着我不断去完善他,一方面是得对得起关注学习的人,另一方面也是想让自己能过通过慢慢完善一个项目来让自己提高。

Github:https://github.com/hua1995116/webchat

今天给大家带来的是基于Websocket+Node+Redis未读消息功能,可能更加偏向于实战方向,需要对Websocket和Node有一些了解,当然不了解也可以看看效果,效果链接( https://www.qiufengh.com/ )说不定会激起你学习的动力~

下面我通过自己思考的方式来进行讲解,代码可能讲的不多,但是核心逻辑都进行了讲解,上面也有github地址,有兴趣的可以进行详细地查看。自己的idea或多或少会有一些不成熟,但是我还是厚着脸皮出来抛头露脸,如果有什么建议还请大家多多提出,能让我更加完善这个作品。

设计

首先对于消息未读,大家都很熟悉,就是各种聊天的时候,出现的红点点,且是强迫症者必须清理的一个小点点,如所示。我会带大家实现一个这样的功能。

由于一对一的方式更加简单,我现在只考虑多对多的情况,也就是在一个房间(也可以称为群组,后面都以房间称呼)中的未读消息,那么设计这样的一个功能,首先我将它分成了3种用户。

  • 离线用户
  • 在线用户
  • 在线用户且进入群组的用户

离线用户

这种场景就相当于我们退出微信,但是别人在房间里发的消息,当我们再次打开的时候依然能够看到房间增长的未读消息。

在线用户

这种场景就是相当我们停留在聊天列表页面,当他人在房间中发送消息,我们能够实时的看到未读消息的条数在增长。

场景示例。

在线用户且在房间的用户

这种场景其实就比较普通了,当别人发送新的消息,我们就能实时看到,此时是不需要标记未读消息的。

场景示例。

流程图

主要流程可以简化为三个部分,分别为用户,推送功能,消息队列。

用户可以是消息提供者也可以是消息接受者。以下就是这个过程。

当然在这个过程中涉及比较复杂的消息的存储,如何推送,获取,同步等问题,下面就是对这个过程进行详细的描述

图上的流程解释

A. 存储在Node缓存中的房间用户列表(此处信息也可以存在Redis中)

B. 存储在Redis中的未读消息列表

C. 存储在MongoDB中的未读消息列表

  1. 用户1进入首页。
  2. 用户1进入房间,重置用户在房间1的未读消息,触发更新模块去更新B未读消息列表。
  3. 用户1向向房间B中发送了一条消息。
  4. 后端需要去获取房间用户列表,判断用户是否在房间?
  5. 是,因为在房间中的用户已经读取了最新消息,不需要进行计数。
  6. 否,若用户不在房间中,更新其的未读消息计数
  7. 从缓存中获取用户的消息进行分发。
  8. 用户2登录我们的项目,从离线用户变成了在线用户。
  9. 用户2登录时,触发查询模块,去获取其当前在各个房间未读消息情况。
  10. 查询模块去查询Redis中的未读消息,若Redis中没有数据,会继续向数据库中查询,若没有则返回0给用户。
  11. Redis缓存将会每分钟和数据库同步一次,保证数据的持久化。

环境

  • Node: 8.5.0 +
  • Npm: 5.3.0 +
  • MongoDB
  • Redis

为什么是redis ?

介绍

Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,是一个高性能的key-value数据库。具有性能极高,丰富的数据类型,原子,丰富的特性等优势。

redis 具有以下5种数据结构

  • String——字符串
  • Hash——字典
  • List——列表
  • Set——集合
  • Sorted Set——有序集合

想要深入了解这5种存储结构可以查看http://www.runoob.com/w3cnote/redis-use-scene.html

安装

windows

http://www.cnblogs.com/jaign/articles/7920588.html

mac

brew install redis

ubuntu

apt-get install redis

redhat

yum install redis

centos

https://www.cnblogs.com/zuidongfeng/p/8032505.html

运行客户端

redis-cli

可视化工具安装

windows

https://pan.baidu.com/s/1kU8sY3P

mac

https://pan.baidu.com/s/10vpdhw7YfDD7G4yZCGtqQg

源码编译

http://docs.redisdesktop.com/en/latest/install/#build-from-source

项目中的数据结构

在本项目中我们用String 来存储用户的未读消息记录,利用其incr命令来进行自增操作。利用Hash结构 来存储我们websocket连接时用户的socket-id。

上面说了计数利用Redis的Stirng数据结构, 在Redis 我们的计数key-value是这样的。

username-roomid - number

例子: hua1995116-room1 - 1

我们的Socket-id则为Hash结构。

  • socketId
    • username - socketid

例子:

  • socketId
    • hua1995116 - En4ilYqDpk-P5_tzAAAG

MongoDB

本项目一开始就使用了MongoDB,Node天然搭配的MongoDB的优势,这里就不再进行讲解,Node操作MongoDB的模块叫做mongoose,具体的参数方法,可以查看官方文档。

https://mongoosejs.com/docs/4.x/index.html

MongoDB下载地址

https://www.mongodb.com/download-center/community

可视化下载地址

https://github.com/mrvautin/adminMongo

websocket + node 实现

下面我们通过一开始的3种用户的场景来具体说明实现的代码。

离线用户变成在线用户

客户端在登录时会发送一个login事件,以下是后端逻辑。

// 建立连接socket.on('login',async (user) => {    console.log('socket login!');    const {name} = user;    if (!name) {      return;    }    socket.name = name;    const roomInfo = {};    // 初始化socketId    await updatehCache('socketId', name, socket.id);    for(let i = 0; i < roomList.length; i++) {      const roomid = roomList[i];      const key = `${name}-${roomid}`;      // 循环所有房间      const res = await findOne({username: key});      const count = await getCacheById(key);      if(res) {        // 数据库查数据, 若缓存中没有数据,更新缓存        if(+count === 0) {          updateCache(key, res.roomInfo);        }        roomInfo[roomid] = res.roomInfo;      } else {        roomInfo[roomid] = +count;      }    }    // 通知自己有多少条未读消息    socket.emit('count', roomInfo);});

用户从离线变成在线状态,建立socket连接时候,会发送一个login事件, 服务端就会去查询当前用户的未读消息情况,从MongoDB和Redis分别查询,若Redis中没有数据,则向数据库查询。

在线用户进入房间

客户端在加入房间说话会发送一个room事件,以下是后端逻辑

// 加入房间socket.on('room', async (user) => {    console.log('socket add room!');    const {name, roomid} = user;    if (!name || !roomid) {      return;    }    socket.name = name;    socket.roomid = roomid;    if (!users[roomid]) {      users[roomid] = {};    }    // 初始化user    users[roomid][name] = Object.assign({}, {      socketid: socket.id    }, user);     // 初始化user    const key = `${name}-${roomid}`;    await updatehCache('socketId', name, socket.id);    // 进入房间默认置空,表示全部已读    await resetCacheById(key);    // 进行会话    socket.join(roomid);    const onlineUsers = {};    for(let item in users[roomid]) {      onlineUsers[item] = {};      onlineUsers[item].src = users[roomid][item].src;    }    io.to(roomid).emit('room', onlineUsers);    global.logger.info(`${name} 加入了 ${roomid}`);});

服务端接收到客户端发送的room事件,来重置该用户房间内的未读消息,并且该用户加入房间列表。

在房间中的用户发送消息

客户端在加入房间说话会发送一个message事件,以下是后端逻辑

socket.on('message', async (msgObj) => {    console.log('socket message!');     //向所有客户端广播发布的消息    const {username, src, msg, img, roomid, time} = msgObj;    if(!msg && !img) {      return;    }    ... // 此处为向数据库存入消息    const usersList = await gethAllCache('socketId');// 所有用户列表    usersList.map(async item => {      if(!users[roomid][item]) {  // 判断是否在房间内        const key = `${item}-${roomid}`        await inrcCache(key);        const socketid = await gethCacheById('socketId', item);        const count = await getCacheById(key);        const roomInfo = {};        roomInfo[roomid] = count;        socket.to(socketid).emit('count', roomInfo);    }}) 

此步骤略微复杂,主要是房间中的用户发送消息,需要经过判断,哪部分用户需要计数,哪部分用户不需要计数,从图中可以看出,不在房间内的用户都需要计数。

接下来还需要推送,那么哪些用户需要实时地推送呢,对的,就是那些在线用户并且不在房间内的用户。因此在这里也需要一个判断。

这样就完美了,能够精确地给用户增加计数,并且精确地推送给需要的用户。

后记

在线演示: https://www.qiufengh.com/

github地址: https://github.com/hua1995116/webchat


推荐Vue学习资料文章:

Vue 小技巧,策略模式实现动态表单验证

入口开始解读Vue源码系列(一)——造物创世

入口开始解读Vue源码系列(二)——new Vue 的故事

入口开始解读Vue源码系列(三)——initMixin

入口开始解读Vue源码系列(四)——实现一个基础的 Vue 双向绑定

入口开始解读Vue源码系列(五)——$mount 内部实现

入口开始解读Vue源码系列(六)$mount 实现compile parse生成AST

入口开始解读Vue源码系列(七)$mount 实现compile optimize标记

深入浅出探索 Vue 路由「值得收藏」

学会使用Vue JSX,一车老干妈都是你的

细聊Vue 3 系列之 JSX 语法

「速围」尤雨溪详细介绍 Vue 3 的最新进展

细聊single-spa + vue来实现前端微服务项目

前端新工具—vite从入门到实践

一文带你搞懂Vue3 底层源码

9个优秀的 VUE 开源项目

细聊Single-Spa + Vue Cli 微前端落地指南「实践」

通俗易懂的Vue异步更新策略及 nextTick 原理

通俗易懂的Vue响应式原理以及依赖收集

原生JS +Vue实现框选功能

Vue.js轮播库热门精选

一文带你搞懂vue/react应用中实现ssr(服务端渲染)

Vue+CSS3 实现图片滑块效果

教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(上)

教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(下)

vue实现一个6个输入框的验证码输入组件

一用惊人的Vue实践技巧「值得推荐」

Vue常见的面试知识点汇总(上)「附答案」

Vue常见的面试知识点汇总(下)「附答案」

Kbone原理详解与小程序技术选型

为什么我不再用Vue,改用React?

让Jenkins自动部署你的Vue项目「实践」

20个免费的设计资源 UI套件背景图标CSS框架

Deno将停止使用TypeScript,并公布五项具体理由

前端骨架屏都是如何生成的

Vue原来可以这样写开发效率杠杠的

用vue简单写一个音乐播放组件「附源码」

为什么Vue3.0不再使用defineProperty实现数据监听?

「干货」学会这些Vue小技巧,可以早点下班和女神约会

探索 Vue-Multiselect

细品30张脑图带你从零开始学Vue

Vue后台项目中遇到的技术难点以及解决方案

手把手教你Electron + Vue实战教程(五)

手把手教你Electron + Vue实战教程(四)

手把手教你Electron + Vue实战教程(三)

手把手教你Electron + Vue实战教程(二)

手把手教你Electron + Vue实战教程(一)

收集22种开源Vue模板和主题框架「干货」

如何写出优秀后台管理系统?11个经典模版拿去不谢「干货」

手把手教你实现一个Vue自定义指令懒加载

基于 Vue 和高德地图实现地图组件「实践」

一个由 Vue 作者尤雨溪开发的 web 开发工具—vite

是什么让我爱上了Vue.js

1.1万字深入细品Vue3.0源码响应式系统笔记「上」

1.1万字深入细品Vue3.0源码响应式系统笔记「下」

「实践」Vue 数据更新7 种情况汇总及延伸解决总结

尤大大细说Vue3 的诞生之路「译」

提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器

大厂Code Review总结Vue开发规范经验「值得学习」

Vue3 插件开发详解尝鲜版「值得收藏」

带你五步学会Vue SSR

记一次Vue3.0技术干货分享会

Vue 3.x 如何有惊无险地快速入门「进阶篇」

「干货」微信支付前后端流程整理(Vue+Node)

带你了解 vue-next(Vue 3.0)之 炉火纯青「实践」

「干货」Vue+高德地图实现页面点击绘制多边形及多边形切割拆分

「干货」Vue+Element前端导入导出Excel

「实践」Deno bytes 模块全解析

细品pdf.js实践解决含水印、电子签章问题「Vue篇」

基于vue + element的后台管理系统解决方案

Vue仿蘑菇街商城项目(vue+koa+mongodb)

基于 electron-vue 开发的音乐播放器「实践」

「实践」Vue项目中标配编辑器插件Vue-Quill-Editor

基于 Vue 技术栈的微前端方案实践

消息队列助你成为高薪 Node.js 工程师

Node.js 中的 stream 模块详解

「干货」Deno TCP Echo Server 是怎么运行的?

「干货」了不起的 Deno 实战教程

「干货」通俗易懂的Deno 入门教程

Deno 正式发布,彻底弄明白和 node 的区别

「实践」基于Apify+node+react/vue搭建一个有点意思的爬虫平台

「实践」深入对比 Vue 3.0 Composition API 和 React Hooks

前端网红框架的插件机制全梳理(axios、koa、redux、vuex)

深入Vue 必学高阶组件 HOC「进阶篇」

深入学习Vue的data、computed、watch来实现最精简响应式系统

10个实例小练习,快速入门熟练 Vue3 核心新特性(一)

10个实例小练习,快速入门熟练 Vue3 核心新特性(二)

教你部署搭建一个Vue-cli4+Webpack移动端框架「实践」

2020前端就业Vue框架篇「实践」

详解Vue3中 router 带来了哪些变化?

Vue项目部署及性能优化指导篇「实践」

Vue高性能渲染大数据Tree组件「实践」

尤大大细品VuePress搭建技术网站与个人博客「实践」

10个Vue开发技巧「实践」

是什么导致尤大大选择放弃Webpack?【vite 原理解析】

带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】

带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】

实践Vue 3.0做JSX(TSX)风格的组件开发

一篇文章教你并列比较React.js和Vue.js的语法【实践】

手拉手带你开启Vue3世界的鬼斧神工【实践】

深入浅出通过vue-cli3构建一个SSR应用程序【实践】

怎样为你的 Vue.js 单页应用提速

聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总

【新消息】Vue 3.0 Beta 版本发布,你还学的动么?

Vue真是太好了 壹万多字的Vue知识点 超详细!

Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5

深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】

手把手教你深入浅出vue-cli3升级vue-cli4的方法

Vue 3.0 Beta 和React 开发者分别杠上了

手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件

Vue3 尝鲜

总结Vue组件的通信

Vue 开源项目 TOP45

2020 年,Vue 受欢迎程度是否会超过 React?

尤雨溪:Vue 3.0的设计原则

使用vue实现HTML页面生成图片

实现全栈收银系统(Node+Vue)(上)

实现全栈收银系统(Node+Vue)(下)

vue引入原生高德地图

Vue合理配置WebSocket并实现群聊

多年vue项目实战经验汇总

vue之将echart封装为组件

基于 Vue 的两层吸顶踩坑总结

Vue插件总结【前端开发必备】

Vue 开发必须知道的 36 个技巧【近1W字】

构建大型 Vue.js 项目的10条建议

深入理解vue中的slot与slot-scope

手把手教你Vue解析pdf(base64)转图片【实践】

使用vue+node搭建前端异常监控系统

推荐 8 个漂亮的 vue.js 进度条组件

基于Vue实现拖拽升级(九宫格拖拽)

手摸手,带你用vue撸后台 系列二(登录权限篇)

手摸手,带你用vue撸后台 系列三(实战篇)

前端框架用vue还是react?清晰对比两者差异

Vue组件间通信几种方式,你用哪种?【实践】

浅析 React / Vue 跨端渲染原理与实现

10个Vue开发技巧助力成为更好的工程师

手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】

1W字长文+多图,带你了解vue的双向数据绑定源码实现

深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】

干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现

手把手教你D3.js 实现数据可视化极速上手到Vue应用

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】

Vue3.0权限管理实现流程【实践】

后台管理系统,前端Vue根据角色动态设置菜单栏和路由

作者: 蓝色的秋风

转发链接:https://mp.weixin.qq.com/s/dBTog_m7xBnmMeaLE-d16g

相关推荐

Mac电脑强制删除任何软件方法-含自启动应用

对于打工者来说,进入企业上班使用的电脑大概率是会被监控起来,比如各种流行的数据防泄漏DLP,奇安信天擎,甚至360安全卫士,这些安全软件你想卸载是非常困难的,甚至卸载后它自己又安装回来了,并且还在你不...

Linux基础知识 | 文件与目录大全讲解

1.linux文件权限与目录配置1.文件属性Linux一般将文件可存取的身份分为三个类别,分别是owner/group/others,且三种身份各read/write/execute等权限文...

文件保护不妥协:2025 年 10 款顶级加密工具推荐

数据安全无小事,2025年这10款加密工具凭借独特功能脱颖而出,从个人到企业场景全覆盖,第一款为Ping32,其余为国外英文软件。1.Ping32企业级加密核心工具,支持200+文件格...

省心省力 一个软件搞定系统维护_省心安装在哪里能找到

◆系统类似于我们居住的房间,需要经常打理才能保持清洁、高效。虽然它本身也自带一些清理和优化的工具,但借助于好用的第三方工具来执行这方面的任务,会更让人省心省力。下面笔者就为大家介绍一款集多项功能于一身...

JAVA程序员常用的几个工具类_java程序员一般用什么软件写程序

好的工具做起事来常常事半功倍,下面介绍几个开发中常用到的工具类,收藏一下,也许后面真的会用到。字符串处理:org.apache.commons.lang.StringUtilsisBlank(Char...

手工解决Windows10的若干难题_windows10系统卡顿怎么解决

【电脑报在线】很多朋友已经开始使用Win10,估计还只是测试版本的原因,使用过程中难免会出现一些问题,这里介绍解决一些解决难题的技巧。技巧1:让ProjectSpartan“重归正途”从10074...

System32文件夹千万不能删除,看完这篇你就知道为什么了

C:\Windows\System32目录是Windows操作系统的关键部分,重要的系统文件存储在该目录中。网上的一些恶作剧者可能会告诉你删除它,但你不应该尝试去操作,如果你尝试的话,我们会告诉你会发...

Windows.old 文件夹:系统备份的解析与安全删除指南

Windows.old是Windows系统升级(如Win10升Win11)或重装时,系统自动在C盘创建的备份文件夹,其核心作用是保留旧系统的文件、程序与配置,为“回退旧系统”提供保...

遇到疑难杂症?Windows 10回收站问题巧解决

回收站是Windows10的一个重要组件。然而,我们在使用过程中,可能会遇到一些问题。例如,不论回收站里有没有文件,都显示同一个图标,让人无法判别回收站的空和满的真实情况;没有了像Windows7...

卸载软件怎么彻底删掉?简单几个步骤彻底卸载,电脑小白看过来

日常工作学习生活中,我们需要在安装一些软件程序,但随着软件的更新迭代速度,很多时候我们需要重新下载安装新的程序,这时就需要将旧的一些软件程序进行卸载。但是卸载软件虽然很简单,但是很多小伙伴们表示卸载不...

用不上就删!如何完全卸载OneDrive?

作为Windows10自带的云盘,OneDrive为资料的自动备份和同步提供了方便。然而,从隐私或其他方面考虑,有些人不愿意使用OneDrive。但Windows10本身不提供直接卸载OneDri...

【Linux知识】Linux下快速删除大量文件/文件夹方法

在Linux下,如果需要快速删除大量文件或文件夹,可以使用如下方法:使用rm命令删除文件:可以使用rm命令删除文件,例如:rm-rf/path/to/directory/*这个命令会递...

清理系统不用第三方工具_清理系统垃圾用什么软件

清理优化系统一定要借助于优化工具吗?其实,手动优化系统也没有那么神秘,掌握了方法和技巧,系统清理也是一件简单和随心的事。一方面要为每一个可能产生累赘的文件找到清理的方法,另一方面要寻找能够提高工作效率...

系统小技巧:软件卸载不了?这里办法多

在正常情况下,我们都是通过软件程序组中的卸载图标,或利用控制面板中的“程序和功能”模块来卸载软件的。但有时,我们也会发现利用卸载图标无法卸载软件或者卸载图标干脆丢失找不到了,甚至控制面板中卸载软件的功...

麒麟系统无法删除文件夹_麒麟系统删除文件权限不够

删除文件夹方法例:sudorm-rf文件夹名称。删除文件方法例:sudorm-r文件名包括扩展名。如果没有权限,给文件夹加一下权限再删。加最高权限chmod775文件名加可执行权限...

取消回复欢迎 发表评论: