OpenIM源码分析
我需要的仅仅是基础的IM聊天功能。不想花那么多钱的情况下,OpenIM是一个不错的选择.
介绍OpenIM
研究这个框架,源于公司一个项目对于即时通讯的需要,之前涉及到这个需求,都采用商业方案,比如腾讯云提供的IM服务,环信等等,商业的云服务稳定,强大,要价高,但有的时候,我需要的仅仅是基础的IM聊天功能。不想花那么多钱的情况下,开源社区就是我最强大的后盾。OpenIM是由前微信技术专家打造的开源即时通讯解决方案,提供完整组件和一键部署能力,支持私有化部署和数据隐私保护,具备高性能、微服务、集群化特性,适用于企业级即时通讯需求。实际测试下,OpenIM对服务器的资源需求,真的不算大,官方介绍,核心服务仅需要4G内存。
部署结构分析
一、依赖服务安装
OpenIM已经准备好的依赖组件的Docker服务配置,位于项目open-im-server下。
$ git clone https://github.com/openimsdk/open-im-server && cd open-im-server
组件类别 | |||
组件清单 |
|
| admin-front web-front |
依赖启动
$ docker compose up -d // 启动核心组件和测试前端
$ docker compose --profile m up -d // 启动运维组件二、文档补充说明
- Minio的访问配置:Minio主要用于存储用户对话过程中的文件,而这些请求是直接进minio的,因此需要配置域名,或用户可以访问的IP地址。若是内网,给Minio一个内网IP即可。端口号默认Docker启动Minio使用的端口,映射到了主体的10005,具体查看minio.yml配置确认。
- Go的代理:golang语言没限制,不过golang依赖库的机制对github有非常强的依赖,大部分的依赖库都在github上,源码运行,需要配置go依赖的代理。
三、核心服务启动
在上面的准备基础完成之后,使用openim的指定的编译工具,mage start就可以直接启动核心服务了,这里可参考官网原文,不赘述。
如果自己的系统需要IM,那么到此为止,便可以基于OpenIM,来进行二次开发了。但如果你想直接上手试一下消息,就需要下面的AppServer演示服务接口。
四、AppServer演示接口服务
核心服务中,有USER的逻辑,但是却没有账号体系,因此上面的web-front和admin-front项目,可以打开,但却没办法登录,这需要另外的实现账号机制的后端的API服务。这个服务即AppServer,需要另外部署。
- 服务获取:
$ git clone https://github.com/openimsdk/chat&& cd chat如果您是在本地部署,那么admin-front的默认端口应该是11002,浏览器打开http://localhost:11002即可。
系统初始的管理员账号与密码是:chatAdmin/chatAdmin。
五、核心服务结构
- 主服务:
- openim-api:对客户端曝露的核心服务,http协议,信息查询,与消息发送请求。
- openim-msggateway:对客户端曝露的核心服务,ws协议,处理客户端实时消息发送与监听。
- openim-cmdutil
- openim-crontask
- openim-msgtransfer
- openim-push
- 底层服务
- openim-rpc-auth
- openim-rpc-conversation
- openim-rpc-friend
- openim-rpc-group
- openim-rpc-msg
- openim-rpc-third
- openim-rpc-user
主服务与底层服务之间的关系如下图:

六、SDK结构
OpenIM的SDK支持多平台,核心使用golang编写,封装了对API服务请求,与长链接的处理逻辑。而各端的封装主要完成了客户端参数配置,与桥接Golang的部分。

用户认证机制
一、概括:
客户端交互的接口都在API服务中以http接口方式实现,而消息收发都在msggateway中以websocket协议实现。以下API定义在OpenIMAPI服务中,OpenIMAPI本身只起到接口转接的作用,具体的逻辑定义在下面的RPC服务,user&auth中。
二、接口
- 用户接口
u := NewUserApi(user.NewUserClient(userConn), client, cfg.Discovery.RpcService)
{
userRouterGroup := r.Group("/user")
userRouterGroup.POST("/user_register", u.UserRegister)
userRouterGroup.POST("/update_user_info", u.UpdateUserInfo)
userRouterGroup.POST("/update_user_info_ex", u.UpdateUserInfoEx)
userRouterGroup.POST("/set_global_msg_recv_opt", u.SetGlobalRecvMessageOpt)
userRouterGroup.POST("/get_users_info", u.GetUsersPublicInfo)
userRouterGroup.POST("/get_all_users_uid", u.GetAllUsersID)
userRouterGroup.POST("/account_check", u.AccountCheck)
userRouterGroup.POST("/get_users", u.GetUsers)
userRouterGroup.POST("/get_users_online_status", u.GetUsersOnlineStatus)
userRouterGroup.POST("/get_users_online_token_detail", u.GetUsersOnlineTokenDetail)
userRouterGroup.POST("/subscribe_users_status", u.SubscriberStatus)
userRouterGroup.POST("/get_users_status", u.GetUserStatus)
userRouterGroup.POST("/get_subscribe_users_status", u.GetSubscribeUsersStatus)
userRouterGroup.POST("/process_user_command_add", u.ProcessUserCommandAdd)
userRouterGroup.POST("/process_user_command_delete", u.ProcessUserCommandDelete)
userRouterGroup.POST("/process_user_command_update", u.ProcessUserCommandUpdate)
auserRouterGroup.POST("/process_user_command_get", u.ProcessUserCommandGet)
userRouterGroup.POST("/process_user_command_get_all", u.ProcessUserCommandGetAll)
userRouterGroup.POST("/add_notification_account", u.AddNotificationAccount)
userRouterGroup.POST("/update_notification_account", u.UpdateNotificationAccountInfo)
userRouterGroup.POST("/search_notification_account", u.SearchNotificationAccount)
}- 认证相关
a := NewAuthApi(pbAuth.NewAuthClient(authConn))
authRouterGroup := r.Group("/auth")
authRouterGroup.POST("/get_admin_token", a.GetAdminToken)
authRouterGroup.POST("/get_user_token", a.GetUserToken)
authRouterGroup.POST("/parse_token", a.ParseToken)
authRouterGroup.POST("/force_logout", a.ForceLogout) 三、认证逻辑

四、Token说明
- TokenKey In Redis
// 前缀:用户ID:登录平台
UID_PID_TOKEN_STATUS:4834513665:Web- TokenValue说明:{Token:TokenFlag}
TokenFlag取值如下,也就是说,TokenValue将以JsonMap的形式保存,最里可以看到最多4种情况
- NormalToken = 0
- InValidToken = 1
- KickedToken = 2
- ExpiredToken = 3
五、AuthDatabase Controller
database controller是openim对数据层的封装,底层可能是对mongodb或是redis的数据服务。
认证服务Auth是openim底层服务,在启动时,会实例化AuthDatabase,Auth服务实现的4四认证相关接口,便是依赖authDatabase实现认证数据的管理。
authDatabase实现的四个方法如下,即操作上面Token的方法:
- GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error)
- CreateToken(ctx context.Context, userID string, platformID int) (string, error)
- BatchSetTokenMapByUidPid(ctx context.Context, tokens []string) error
- SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error消息类型说明MsgGateway提供了一个WebSocket服务,监听客户端的ws链接,并负责消息监听与处理。
一、OpenIM五种系统消息类型
- TextMessage(Ping/Pone Message)
- BinaryMessage(Chat Message)
- CloseMessage(客户端关闭连接)
- PingMessage
- PongMessage
二、BinaryMessage(Req/Resp)
type Req struct {
ReqIdentifier int32 `json:"reqIdentifier" validate:"required"`
Token string `json:"token"`
SendID string `json:"sendID" validate:"required"`
OperationID string `json:"operationID" validate:"required"`
MsgIncr string `json:"msgIncr" validate:"required"`
Data []byte `json:"data"`
}
type Resp struct {
ReqIdentifier int32 `json:"reqIdentifier"`
MsgIncr string `json:"msgIncr"`
OperationID string `json:"operationID"`
ErrCode int `json:"errCode"`
ErrMsg string `json:"errMsg"`
Data []byte `json:"data"`
}三、ReqIdentifier
每一个消息类型,对应底层rpc服务msg都有对应的处理方法。一般用户的消息,则对应着WSSendMsg这个类型。
WSGetNewestSeq = 1001
WSPullMsgBySeqList = 1002
WSSendMsg = 1003 // 一般聊天消息
WSSendSignalMsg = 1004
WSPullMsg = 1005
WSGetConvMaxReadSeq = 1006
WsPullConvLastMessage = 1007
WSPushMsg = 2001
WSKickOnlineMsg = 2002
WsLogoutMsg = 2003
WsSetBackgroundStatus = 2004
WsSubUserOnlineStatus = 2005
WSDataError = 3001花了两天时间,反复翻了几遍源码,结合以上核心的概念说明,OpenIM的基本结构已经相对清晰,后面有深入的分析再续。