「实战」搭建完整的IM(即时通讯)应用(1)
发布于 22 天前 作者 hezhongfeng 2566 次浏览 来自 分享

介绍

即时通讯应用服务,整套包含服务端管理端客户端

预计3篇分享:这次是第一篇,项目的整体介绍和实体关系的梳理

现已部署上线,客户端管理端,欢迎体验

可以注册客户端账号,也可以使用初始默认账号,现有初始账号说明:

账号 密码 说明
admin 123456 管理端账号
user 123456 客户端普通用户账号
muteuser 123456 客户端被禁言用户账号
disabled 123456 客户端被封禁用户账号
member1 123456 客户端普通用户账号
member2 123456 客户端普通用户账号
123456 客户端普通用户账号
member30 123456 客户端普通用户账号

功能简介

  1. 注册,登录,个人、群组聊天,个人信息编辑等基础功能
  2. 申请添加好友和申请入群
  3. 表情,图片,视频,定位信息支持
  4. 聊天会话列表记录
  5. 消息记录(微信的消息记录真实一言难尽)
  6. 支持多点同时登录
  7. 百度 UNIT 机器人自动回复(todo)
  8. 管理端,进行角色和权限的管理,群状态管理(我也当一回马化腾)

需求简介

移动互联网发展至今,以微信为首的即时通讯服务已经融入了我们生活中的各个角落,在公司的一些业务中也扮演着重要的角色,对于即时通讯我们公司原来是使用的环信的服务,但是有很多定制化的需求无法实现,所以后来决定内部开发一个满足定制化需求的即时通讯微服务。

使用socket.io框架是因为当时后端缺人,加上看了一些例子后觉得使用起来真的很方便,而且全平台支持,所以这个微服务就在前端团队进行落地实践,目前效果还不错。

社区目前这方面的内容比较少或者太简陋(只有一个公共的聊天室这种)。另外就是在业务开发过程中被 PM 搞得很难受,所以想脱离一些特有的业务上的东西,实现一个功能简单五脏俱全的不掺杂公司业务的 IM 应用,包含服务端,管理端和客户端。客户端的模仿对象是微信,因为我很熟悉,不用在产品上面思考太多,另外就是试用的人很熟悉,不需要太多的沟通成本。

框架简介

要开发一套完整的即时通讯服务,需要以下部分:

  1. 服务端:用来实现基础的服务接口和数据持久化
  2. 客户端:完成登录、聊天等基础功能,类似微信
  3. 管理端:管理群组、用户和角色权限

server

为企业级框架和应用而生

选用阿里的 egg.js 框架做支撑,看中的原因是他们内部大规模的落地和安全方面做得比较好,没有选择 nest 的原因是集成 socket.io 比较麻烦,ORM 选用 sequelize,数据库是 mysql ,之前一起使用过,上手难度小

admin

开箱即用的中台前端/设计解决方案

选择 Ant Design Pro 作为模板开发管理端,选用的原因是我对 Vue 全家桶比较熟悉,想借着这个机会熟悉下整套 React 生态 的开发流程,感受下目前国内两大开发框架的本质区别和殊途同归,Ant Design Pro 已经发布了好几年了,也的确给中小型企业带来效率的提升,也正好适合我这的需求。

client

🛠️ Vue.js 开发的标准工具

使用 @vue/cli 搭建 IM 服务的客户端,一个移动端的 H5 项目,UI 框架使用的有赞 vant,集成了我的开源组件vue-page-stack和黄老师的better-scroll,实现 IM 的基础功能

实体关系

作为一个前端工程师,大多数的日常工作是不需要思考实体关系的。但是,就我的实际体验来看,懂得实体关系可以帮助我们更好的理解业务模型。而对产品和业务理解的提升对我们的帮助是非常大的,可以在需求评审的时候发现很多不符合逻辑的地方(怎么又要吐槽产品经理了),这时候能提出来就会主动避免我们在后续的过程中进行反复开发,同时可以和产品侧的同学形成比较良好的互动(而不是互怼)。下面简单罗列下比较重要的实体关系:

通过上图可以看到 user 是整个关系图中的核心,下面介绍下各个实体之间的关系:

  1. user 和 user_info(用户信息) 是一对一的关系
  2. user 和 role(角色)是多对多的关系
  3. role 和 right(权限)是多对多的关系
  4. user 和 apply(申请)是多对多的关系,申请都是涉及到两个 user(申请人和被申请人)
  5. user 和 group(群组)是多对多的关系
  6. group 和 conversation(会话) 是一对一的关系
  7. friend 和 conversation(会话) 是一对一的关系
  8. conversation 和 message(消息)是 1 对多的关系
  9. friend(好友关系) 和 user 没有直接关系,friend 由两个 user 确定

下面详细介绍下会话、角色与权限:

会话

完成一个即时通讯应用,需要考虑的第一个事情就是会话,就是我们微信里面的对话窗口。思考会话和消息、用户、群组之间的关系花费了不少的精力,最终形成以下的基本关系:

  1. 2 个用户参与的聊天属于建立了 Friend 关系(互为好友)
  2. 多个用户参与的聊天组成了群组关系
  3. Friend 和会话之间的关系是 1 对 1 的关系,可以通过 Friend 找到此 Friend 的会话,也可以通过会话确定 Friend
  4. 群组和会话之间的关系是 1 对 1 的关系,可以通过群组找到此群组的会话,也可以通过会话确定群组
  5. 消息属于某个会话,可以根据会话查看对应的消息列表
  6. 保存消息的时候更新会话的激活时间,用户的会话列表根据激活时间排序,也就是最近的会话再最前面

也就是说,用户和会话没有直接的关系,只能通过用户对应的单聊和群聊去获取会话,这样做可以有以下的好处:

  1. 无论是单聊还是群聊,连接上的用户只要 join 进对应的会话 room 里面就可以,消息也是在对应的 room 里面发布
  2. 无论是单聊还是群聊,消息的保存和查询都比较简单,都是只针对这个会话
  3. 获取个人的会话列表也变得很简单,用户的会话列表通过查询用户『所有的 Friend 和群组』->『所有的会话』->『排序会话(根据激活时间)』,就可以获取

角色和权限

为了设计一个灵活、通用、方便的权限管理系统,本系统采用 RBAC(基于角色的访问控制)控制,来设计一个通用的『用户角色权限』平台,方便后期扩展。

RBAC

RBAC(基于角色的访问控制)是指用户通过角色与权限进行关联。即一个用户拥有若干角色,每一个角色拥有若干权限(当然了,别把冲突的角色和权限配在一起)。这样,就构造成“用户—角色—权限”的授权模型。在这种模型中,用户与角色之间、角色与权限之间,一般是多对多的关系。

本系统默认的角色和权限

本系统默认有管理员、一般用户、禁言用户和封禁用户这几种角色,给不同的角色分配不同的权限,所以需要针对管理和发言等接口路由做一下统一的鉴权(通过中间件的方式)处理,具体方式和方法在后端项目中会详细说明。本系统暂时采用预先定义了角色和权限的方式,后续想要扩展的话可以编辑角色和权限。

管理员

没见过微信的管理端,但是可以想象一下,管理员可以配置用户的角色和权限,可以编辑群组的状态:

  1. 登录的权限
  2. 群组状态的编辑
  3. 针对用户的角色和权限的编辑

普通用户

注册登录后,可以正常的添加好友和加入群组,可以修改个人基础信息和处理申请

  1. 注册登录
  2. 编辑个人基础信息
  3. 添加好友,申请入群
  4. 处理好友申请和入群申请
  5. 聊天

禁言用户

  1. 注册登录
  2. 编辑个人基础信息
  3. 添加好友,申请入群
  4. 处理好友申请和入群申请

封禁用户

无法登录

角色的组合

举个例子:现在有一个新版的个人中心需要上线测试,首先新建一个角色『测试个人中心』,再给这个角色分配对应的权限;然后给普通用户做个分组,选出一些人配置上这个角色,这样就可以进行测试了。

即时通讯原理

下面说下即时通讯服务的核心通讯原理,和一般的 http 服务一样,有一个服务端和客户端进行通讯,只不过详细的协议和处理方式不一样。

WebSocket

由于历史原因,现在主流的 http 协议是无状态协议(HTTP2 暂时应用不广泛),一般情况是由客户端主动发起请求,然后服务端去响应。那么为了实现服务端向客户端推送信息,就需要前端主动向后端去轮询,这种方式低效且容易出错,在之前我们的管理端首页确实是这么做的(5s 一次)。

为了实现这种服务端主动推送信息的需求, HTML5 开始提供一种在单个 TCP 连接上进行全双工通讯的协议,也就是 WebSocket。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。WebSocket 协议在 2008 年诞生,2011 年成为国际标准,目前绝大部分浏览器都已经支持了。

WebSocket 的用法相当简单:

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) {
  console.log("Connection open ...");
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};

有了 WebSocket 协议让服务端主动推送信息有了先进的武器,那么有没有什么方式可以兼容新旧浏览器呢?其实很多人想到了这点,答案就是socket.io

socket.io

socket.io进一步封装了WebSocket的接口,而且可以在旧版本浏览器中自主切换到使用轮询的方式进行通讯(我们使用者是不会感知的),形成了一套统一的接口,大大减轻了开发的负担。主要具有以下优点:

  1. 封装出了一套非常易用的接口,前后端统一,使用非常简单
  2. 全平台支持(原生和 H5,微信小程序中也有对应的实现)
  3. 自适应浏览器,在比较老的浏览器中主动切换使用轮询的方式,不需要我们自己搞轮询

这是 socket.io 主页

最快,最可靠的即时通讯引擎(FEATURING THE FASTEST AND MOST RELIABLE REAL-TIME ENGINE)

使用起来真的很简单:

var io = require('socket.io')(80);
var cfg = require('./config.json');
var tw = require('node-tweet-stream')(cfg);
tw.track('socket.io');
tw.track('javascript');
tw.on('tweet', function(tweet){
  io.emit('tweet', tweet);
});

总结

有了以上的基础,我们就基本完成了开始写代码之前的准备:明确了这个应用的基础功能和实体之间的关系,也明确了基础的技术方案选型,划分了3个项目的各自任务。下面就开始完成 server 端吧

未完待续:下一篇介绍 server 端

回到顶部