👋 欢迎来到盒子IM,本文档限时免费开放中…
-
- 本文档是开源项目盒子IM的详细介绍文档,涵盖环境搭建、功能设计、原理实现和后期部署等内容
- 本文档内容主要是针对开源版,但同样适用于商业版
- 如果文档中有疏漏或者其他建议,请与作者联系
00-前言
今天是2024.08.04,盒子IM3.0版本今天正式上线,过来更新一波文档。
自从去年2.0版本上线以来,盒子IM的关注度有了明显的增长,star数量仅今年就已经增长了1k+,甚至有几位抖音和公众号博主自发地对盒子进行了介绍和推广,让我收获了满满的成就感。
截止到今年3月份之前,本着为爱发电的精神,盒子IM一直都是纯开源且免费的。但是如果一直“发电”,终有被耗干的一天。所以今年3月份开始推出了第一个付费功能-单人音视频通话,6月底又推出了多人音音视频通话,未来还会推出更为完善的商业版。
不过尽管盒子IM未来会一定程度走商业化的路线,但是开源版本的功能也是会一直更新的,并且已经开源的功能也会一直开源下去。
这一年以来,盒子IM一直保持了高强度的更新,同时也几乎耗尽了我几乎所有的业余时间。很多小伙伴的私信和群里的消息,可能没有办法一一回复,还请见谅。
01-基础介绍
1. 特点
- 盒子IM是一个仿微信实现的开源聊天软件,支持内网部署,不依赖任何收费SDK或组件
- 支持web端和移动端同时在线以及消息同步
- 后端服务支持集群化部署,具有良好的横向扩展能力
- 消息推送功能已进行SDK封装,可快速接入企业项目
2. 合适人群
如果您是以下人群之一,那么盒子IM将会非常适合您:
- 企业中的项目需要开发IM模块,希望快速整合盒子IM的部分功能
- 对IM系统比较感兴趣,想学习如何独立编写一个优雅且高性能的IM系统
3. 前置技能
尽管作者已经十分努力的降低盒子IM的使用门槛,但是在学习盒子IM前,还是需要您已经掌握以下技能:
后端:Springboot、Mybatis-plus、Netty、Mysql、Redis
前端:Vue、Uniapp
4. 功能展示
02-本地环境搭建
1. 基础环境安装
1.1. 安装GIT、JDK17、IDEA、Maven、HbuilderX(略)
1.2. 安装Mysql8.0
下载地址:https://dev.mysql.com/downloads/installer/
选择8.0.39版本并下载,下载后双击安装即可
1.3. 安装Redis
下载地址:Releases · microsoftarchive/redis · GitHub
ps:微软官方维护的支持 Windows平台的 Redis 安装包只有 Redis2.0 和 Redis3.0 的部分版本,这里我们下载最后更新的3.0.504即可
下载后双击安装即可
1.4. 安装MinIO
官网下载minio.exe:
在minio.exe同级目录打开命令行,执行启动:
.\minio.exe server C:\data\minio
启动成功页面如下,默认密码为minioadmin/minioadmin
1.5. 安装node18
下载地址:https://nodejs.org/download/release/v18.19.0/
选择node-v18.19.0-x64.msi 下载,下载后双击安装即可
2. 本地环境启动
2.1. 启动后端
- 通过git下载代码:git clone https://gitee.com/bluexsx/box-im.git
- 将项目导入IDEA,并修改maven配置和JDK17
- 打开im-platform中的application-dev.yml文件,将数据库和minio的账号以及密码修改为自己的
- 生成数据库,数据库脚本在/db/im-platform目录
- 通过IDEA启动im-platform服务和im-server服务
2.2. 启动web端
进入im-web目录,打开命令行窗口执行以下命令:
npm install
npm run serve
访问地址: http://localhost:8080/
2.3. 启动桌面端(商业版)
进入im-web目录,打开命令行窗口执行以下命令:
npm install
npm run electron:serve
2.4. 启动uniapp端-h5
2.4.1. 安装依赖
进入im-uniapp目录,安装依赖包:
npm install
2.4.2. 启动
将im-uniapp目录导入到hbuilderx中,选择选中项目,通过”运行”->”运行到浏览器”->”Chrome”启动
访问地址: http://localhost:5173/h5/#
2.5. 启动uniapp端-微信小程序
2.5.1. 安装依赖
进入im-uniapp目录,安装依赖包:
npm install
2.5.2. 修改配置
将.env.js里面的127.0.0.1换成电脑的IP,可以是内网IP
2.5.3. 申请小程序
如果您还没appid,请前往微信公众平台注册小程序,注册成功后,在小程序后台可获得您的appid
将appid配置到项目的manifest.json中:
2.5.4. 微信开发者工具
下载并安装微信开发者工具,安装后打开,选择菜单:设置->代理设置,选择使用系统代理:
切换到安全选项,把服务端口打开:
然后回到builder选择菜单:运行->运行到终端->运行设置,设置安装的微信开发者工具路径
2.5.5. 启动
选择选中项目,通过”运行”->”开发环境-微信小程序”启动,开发者工具将自行启动:
2.6. 启动uniapp端-安卓APP
2.6.1. 安装依赖
进入im-uniapp目录,安装依赖包:
npm install
2.6.2. 修改配置
将.env.js里面的127.0.0.1换成电脑的IP,可以是内网IP
2.6.3. 连接手机
用USB线将安卓手机连接到电脑,并在”开发者选中”中打开“USB调试选项”。
2.6.4. 启动
在hubuildx中,选择菜单:运行->运行到手机或模拟器->运行到andriod APP基座:
点击“运行”后,留意手机的安装提示,选择“继续安装”,等待APP启动即可
2.7. 启动uniapp端-IOS APP
流程有点繁琐,可以看看网上这篇博客,作者亲测可正常启动:
03-数据库和架构设计
1. 技术选型
后端框架:Springboot、Netty、Mybatis-plus、Jwt
前端框架:Vue、Uniapp、Webrtc
2. 开源组件
组件 |
是否必须 |
主要作用 |
mysql |
是 |
存储用户、群聊、消息等数据 |
redis |
是 |
数据缓存、消息队列 |
minio |
否 |
存储文件,如头像、图片、语音、文件 |
coturn |
否 |
音视频通话时用于协助打洞和转发 |
3. 数据库设计
表名 |
表名(中文) |
创建时机 |
说明 |
im_user |
用户表 |
用户注册 |
记录用户基本信息 |
im_friend |
好友表 |
添加好友 |
记录好友关系以及好友的昵称、头像 |
im_private_message |
私聊消息表 |
发送私聊消息 |
记录与好友之间的聊天消息 |
im_group |
群组表 |
创建群组 |
记录群组信息 |
im_group_member |
群组成员表 |
邀请好友进群聊 |
记录群组中的成员信息 |
im_group_message |
群聊消息表 |
发送群聊消息 |
记录与群聊中的聊天消息 |
im_sensitive_word |
敏感词 |
后台创建 |
发送消息时如果匹配敏感词会变成’**’ |
4. 项目结构
模块 |
功能 |
可运行 |
说明 |
im-platform |
业务平台服务 |
是 |
负责接收前端的http请求,处理盒子IM的所有业务 |
im-server |
消息推送服务 |
是 |
仅实现消息推送,不参与任何业务。其他服务(im-platform)需通过im-client与im-server通信 |
im-client |
消息推送sdk |
否 |
集成到其他服务(im-platform),使其能够与im-server通信,实现消息推送 |
im-common |
公共包 |
否 |
被所有后端模块引用 |
im-web |
web页面 |
是 |
web端页面 |
im-uniapp |
app页面 |
是 |
移动端页面,包括app、h5、微信小程序 |
以下是简化后的消息推送流程图,可以大致体现出每个组件之间的关系:
04-用户登录和鉴权
-
1. 方案选择
- 方案一(session):整合Spring security,同时将session缓存到redis实现集群化管理
- 方案二(token): 通过jwt生成token,每次请求都携带此token,后端通过拦截器解析token
两种方案各有优缺点,盒子IM早期采用的是方案一,但是遇到了两个问题:
- 同一个浏览器无法同时登陆多个账号:第2个账号登录时,会自动顶掉第一个账号的session
- ws连接校验不容易实现: session是通过http的header中的sessionId实现,因此与http协议是强捆绑关系,并不支持ws协议
所以最后改用了方案二,也就是使用token的方式
2. 交互流程
-
2.1. 获取token
主要步骤代码:
步骤类名方法1,2,3,4UserServiceImpllogin2.2. token校验
-
token的校验通过拦截器实现,对所有接口(除登录等个别接口)进行统一拦截校验:
图中只是已请求用户消息接口为例,其他请求亦是如此。主要步骤代码:
步骤类名方法2AuthInterceptorpreHandle
2.3. 刷新token
accessToken的过期时间只有半个小时,而refreshToken7天才会过期。
当accessToken过期时,需要调用/refreshToken接口,换取新的token。
-
主要步骤代码:步骤类名方法1,2,3,4UserServiceImplrefreshToken3. 用户无感知刷新token
上一小节中介绍了当token过期时如何去换取新的token,那么问题来了,程序应该如何感知到token已经过期了呢?
- 方案一:前端用定时器对token的过期时间进行检测,如果快过期了,则进行token刷新
- 方案二:通过前端的http拦截器实现,用户发请求时被此拦截器捕获,此时如果发现token过期时,进行刷新token,然后重新发起请求
显然方案二更为优雅,所以盒子IM采用是方案二。
主要实现代码(前端):
目录文件名方法im-web/src/api/httpRequest.js所有im-uniapp/common/request.js所有
05-接入消息推送(实现聊天功能)
1. 前言
前面已经介绍过,消息推送功能由im-server实现,并且已经封装了sdk(im-client)。
现在我们把消息推送功能看成一个黑盒,暂时不深究它的具体实现,本小节的目标是在im-platform中集成im-client,集成之后,im-platform只需要调用im-client的api,就能够把消息推送到前端
同样,如果您企业中的项目需要集成消息推送功能,也是同样的接入方式,只是将im-platform替换成您的服务
2. im-client相关API介绍
2.1. API列表
API |
说明 |
sendPrivateMessage |
推送私聊消息 |
sendGroupMessage |
推送群聊消息 |
sendSystemMessage |
推送系统消息 |
isOnline |
用户是否在线 |
2.2. 私聊消息API参数
参数 |
类型 |
必填 |
说明 |
sender |
IMUserInfo |
是 |
发送方的信息,包括发送方的id和终端类型 |
recvId |
Long |
是 |
接收方id |
recvTerminals |
List<Integer> |
否 |
接收者的终端类型,默认全部 |
sendToSelf |
Boolean |
否 |
是否发送给自己的其他终端,默认true |
sendResult |
Boolean |
否 |
是否需要回推发送结果,默认true |
data |
泛型 |
是 |
消息内容,类型由应用层定义 |
2.3. 群聊消息API参数
参数 |
类型 |
必填 |
说明 |
sender |
IMUserInfo |
是 |
发送方的信息,包括发送方的id和终端类型 |
recvIds |
List<Long> |
否 |
接收者id列表(一般填群成员id,为空则不会推送) |
recvTerminals |
List<Integer> |
否 |
接收者终端类型,默认所有类型 |
sendToSelf |
Boolean |
否 |
是否需要同时推送给自己的其他终端,默认true |
sendResult |
Boolean |
否 |
是否需要回推发送结果,默认true |
data |
泛型 |
是 |
消息内容,类型由应用层定义 |
2.4. 系统消息API参数
参数 |
类型 |
必填 |
说明 |
recvIds |
List<Long> |
否 |
接收者id列表(一般填群成员id,为空则不会推送) |
recvTerminals |
List<Integer> |
否 |
接收者终端类型,默认所有类型 |
sendResult |
Boolean |
否 |
是否需要回推发送结果,默认true |
data |
泛型 |
是 |
消息内容,类型由应用层定义 |
3. 快速接入
3.1. 后端接入
3.1.1. 在im-platform的pom.xml中引入im-client依赖
<dependency>
<groupId>com.bx</groupId>
<artifactId>im-client</artifactId>
<version>3.0.0</version>
</dependency>
注:上面的依赖包目前并没有上传到公开仓库,需要自行编译并上传到自己的maven私仓,或者直接把代码整合到自己的项目
3.1.2. 消息推送使用了redis充当MQ进行通信,所以要在application.yml中配置redis地址
spring:
redis:
host: 127.0.0.1
port: 6379
3.1.3. 调用IMClient的API进行消息推送
推送私聊消息代码样例(群聊和系统消息也是类似的方式,不再赘述):
@Autowired
private IMClient imClient;
public void sendMessage(){
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
// 发送方的id和终端类型
sendMessage.setSender(new IMUserInfo(1L, IMTerminalType.APP.code()));
// 对方的id
sendMessage.setRecvId(2L);
// 推送给对方所有终端
sendMessage.setRecvTerminals(IMTerminalType.codes());
// 同时推送给自己的其他类型终端
sendMessage.setSendToSelf(true);
// 需要回推发送结果,将在IMListener接收发送结果
sendMessage.setSendResult(true);
// 推送的内容
sendMessage.setData(msgInfo);
// 推送消息
imClient.sendPrivateMessage(sendMessage);
}
3.1.4. 监听发送结果
如果需要监听消息推送的结果,需要以下两步:
- 编写消息监听类,实现MessageListener,并加上@IMListener
- 发送消息时指定sendResult字段为true
监听器类代码样例:
@Slf4j
@IMListener(type = IMListenerType.PRIVATE_MESSAGE)
public class PrivateMessageListener implements MessageListener {
@Override
public void process(IMSendResult<PrivateMessageVO> result){
PrivateMessageVO messageInfo = result.getData();
if(result.getCode().equals(IMSendCode.SUCCESS.code())){
log.info("消息发送成功,消息id:{},发送者:{},接收者:{},终端:{}",messageInfo.getId(),result.getSender().getId(),result.getReceiver().getId(),result.getReceiver().getTerminal());
}
}
}
3.2. 前端接入
3.2.1. 将wssocket.js放到im-web中的/api目录
3.2.2. 接收消息
前端代码样例:
import * as wsApi from './api/wssocket';
let wsUrl = 'ws://localhost:8878/im'
let token = "您的token";
wsApi.connect(wsUrl,token);
wsApi.onConnect(() => {
// 连接打开
console.log("连接成功");
});
wsApi.onMessage((cmd,msgInfo) => {
if (cmd == 2) {
// 异地登录,强制下线
console.log("您已在其他地方登陆,将被强制下线");
} else if (cmd == 3) {
// 私聊消息
console.log(msgInfo);
} else if (cmd == 4) {
// 群聊消息
console.log(msgInfo);
}else if (cmd == 5){
// 系统消息
console.log(msgInfo);
}
})
wsApi.onClose((e) => {
if (e.code != 3000) {
console.log("意外断开,进行重连");
wsApi.reconnect(wsUrl,token);
}else{
console.log("主动断开");
}
});
4. 实现聊天功能(私聊+群聊)
当我们的im-platform集成im-client之后,实现私聊和群聊功能就会变得非常简单,通过简单的crud以及调用api即可实现。
以下是聊天相关的业务代码(代码比较简单,小伙伴可以自行阅读):
类名 |
方法 |
说明 |
PrivateMessageServiceImpl |
sendMessage |
推送私聊消息 |
GroupMessageServiceImpl |
sendMessage |
推送群聊消息 |
PrivateMessageListener |
process |
监听私聊消息结果,消息发送成功后,状态修改为送达 |
GroupMessageListener |
process |
监听群聊消息结果,暂时没实际作用 |
暂无评论内容