即时通讯开源项目OpenIM配置离线推送全攻略

本文涉及的产品
对象存储 OSS,20GB 3个月
日志服务 SLS,月写入数据量 50GB 1个月
文件存储 NAS,50GB 3个月
简介: 即时通讯开源项目OpenIM配置离线推送全攻略

如何进行二次开发
如果您需要基于 OpenIM 开发新特性,首先要确定是针对业务侧还是即时通讯核心逻辑。
由于 OpenIM 系统本身已经做好了比较多的抽象,大部分聊天的功能已经具备了,不建议修改 IM 本身。
如果需要增加 IM 的能力,可以参考以下流程,并提交 PR,以保证未来代码统一性。
服务器
OpenIMServer 主要分为长短连接接口,长连接接口主要是 IM 消息的核心逻辑(逻辑入口位于/internal/msggateway),短连接接口主要是 IM 的 业务逻辑(逻辑入口位于/internal/api/),下面具体介绍如何在 IM 中加上新的业务功能。

  1. 开发前提​
    搭建环境
    搭建 Go 环境,推荐 Go 版本 >= 1.22,参考Go 官方文档
    搭建 grpc 环境,推荐 proto-gen-go >=1.36.1,protoc-gen-go-grpc >= 1.5.1 ,参考grpc 官方文档
    搭建 proto 环境,推荐 protoc >= 5.29.2,将其二进制文件存放到环境变量,参考proto 官方文档

fork OpenIMServer 依赖的外部仓库 protocol
clone 官方的后台协议仓库: github.com/openimsdk/protocol
注意:IMServer 使用的 protobuf 协议以依赖仓库的形式在 github.com/openimsdk/protocol 中,如果需要修改协议,需要先 fork protocol 仓库, 然后在此仓库上增加新的接口协议,然后在 OpenIMServer 的 go.mod 中引用新的包路径,通过:
replace github.com/openimsdk/protocol => github.com//protocol
其中 your_protocol_path 为你 fork 的 protocol 仓库所在的本地路径。

  1. Protobuf 协议增加与生成​
    下面以 Go 为例,介绍如何完整的生成一个新的接口协议。

编写 proto 文件​
首先根据业务需求,定义一个新的功能。本文以在 Friend 模块添加一个 AddFriendCategory 为例,我们需要在 Friend 模块的 proto 文件,添加对应的功能,文件在 relation/relation.proto。
编写 proto 文件,定义新的 AddFriendCategory 接口方法,如:
syntax = "proto3";

package openim.relation;

option go_package = "http://github.com/openimsdk/protocol/relation";

// 定义 AddFriendCategory 的请求参数
message AddFriendCategoryReq {
string ownerUserID = 1;
string friendUserID = 2;
int category = 3;
}

// 定义 AddFriendCategory 的响应参数
// 如果无需返回参数,则不需要添加定义。错误码和错误信息已经默认定义。
message AddFriendCategoryResp {
}

// 定义一个 Friend 模块的 RPC 服务
service Friend {
// 定义一个 AddFriendCategory 的 RPC 方法
rpc AddFriendCategory(AddFriendCategoryReq) returns (AddFriendCategoryResp);
}

这里面分别定义了一个请求参数 AddFriendCategoryReq,一个响应参数 AddFriendCategoryResp,以及一个 RPC 服务 Friend,其中包含的新增 RPC 方法 AddFriendCategory。

上面这个主要的关注点为:
定义 RPC 方法的请求参数 -> 定义 RPC 方法的响应参数 -> 在 RPC 服务内定义 RPC 方法。

生成 Go 代码​
下面介绍如何在编写 proto 文件后,生成对应的 Go 的 pb 代码。

安装执行命令的工具 mage,执行 go install github.com/magefile/mage@latest 即可安装。
在对应仓库中执行 mage InstallDepend,安装 Go 所需的依赖。
proto 编辑完毕后,在克隆的 protocol 仓库中直接执行 mage GenGo 即可生成对应的 go 代码。
更多内容,具体参考用 mage 生成 PB 文件。
添加校验函数​
如果需要对 RPC 函数的请求添加校验,同样在 protocol 仓库中添加。

例如我们定义的 AddFriendCategory 接口,需在 relation/relation.go 中增加如下代码:

func (x *AddFriendCategoryReq) Check() error {
if x.OwnerUserID == "" {
return errors.New("OwnerUserID is empty")
}
if x.FriendUserID == "" {
return errors.New("FriendUserID is empty")
}
if x.Category == 0 {
return errors.New("Category is empty")
}
return nil
}

  1. API 功能添加​
    添加新的 API 功能,包括路由定义和接口定义。

API 路由定义​
定义路由的文件在 /internal/api/router.go,我们需要在 newGinRouter 函数中定义对应的路由,如: 例如我们要定义一个 Friend 模块的 AddFriendCategory 接口,我们可以在 newGinRouter 函数中增加如下代码:
// friend routing group
{
f := NewFriendApi(relation.NewFriendClient(friendConn))
friendRouterGroup := r.Group("/friend")
friendRouterGroup.POST("/delete_friend", f.DeleteFriend)
// ......

// 新增 AddFriendCategory 接口的路由
friendRouterGroup.POST("/add_friend_category", f.AddFriendCategory)
}

如果增加的接口属于一个路由组,可直接增加到对应的路由组文件中,否则模仿创建新的路由组文件。

API 接口定义​
根据上面的路由定义,我们需要在 /internal/api/friend/friend.go 中增加对应的接口定义。
如果 API 的 JSON 请求与 RPC 的 Request 请求一致,可以直接调用 a2r.Call 函数。否则需要自己解析 JSON 请求,然后调用 gRPC 接口(可参考 Message 模块的 SendMessage 接口)。 例如:

// 当 API 的 Request 与 JSON 请求一致
func (o FriendApi) AddFriendCategory(c gin.Context) {
// AddFriendCategory 为在 RPC 定义的方法
a2r.Call(c,relation.FriendClient.AddFriendCategory, o.client)
}

  1. 添加 RPC 方法​
    在对应模块的 Server 结构体,新增相应的 gRPC 方法来实现 Server 接口。然后编写主体的业务逻辑。
    其中涉及 DB 更新、插入操作需要下发 SDK 实时通知,可直接模仿 s.notificationSender.FriendsInfoUpdateNotification 这种类型的通知下发函数。(sdk 对应需要处理新的通知)

添加新的 RPC 方法​
在 internal/rpc/relation/friend/friend.go 中增加新的 rpc 方法 AddFriendCategory,并编写主体的业务逻辑。

// AddFriendCategory 添加好友分组

func (s friendServer) AddFriendCategory(ctx context.Context, req relation.AddFriendCategoryReq) (*relation.AddFriendCategoryResp, error) {
// 实现具体的业务逻辑
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
return nil, err
}

_, err = s.db.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
if err != nil {
return nil, err
}

// 调用 DB 操作
if err := s.db.AddFriendCategory(ctx,req.OwnerUserID, req.FriendUserID,req.category); err != nil {
return nil, err
}

// 调用 sdk 下发通知(如果有对应的 DB 操作)
s.notification.FriendCategoryAddNotification(ctx, req.OwnerUserID, req.FriendUserID) // 仅举例,具体通知函数需要根据业务需求实现

return &relation.AddFriendCategoryResp{}, nil
}

对应的通知下发函数 FriendCategoryAddNotification 应在 internal/rpc/relation/notification.go 中实现。

func (f *FriendNotificationSender) FriendCategoryAddNotification(ctx context.Context,fromUserID, toUserID string) {
tips := sdkws.FriendInfoChangedTips{FromToUserID: &sdkws.FromToUserID{}}
tips.FromToUserID.FromUserID = fromUserID
tips.FromToUserID.ToUserID = toUserID
f.setSortVersion(ctx, &tips.FriendVersion, &tips.FriendVersionID, database.FriendVersionName, toUserID, &tips.FriendSortVersion)
f.Notification(ctx, fromUserID, toUserID, constant.FriendCategoryAddNotification, &tips)
}

此处调用的 constant.FriendCategoryAddNotification 需要添加到 protocol 仓库下的 constant/constant.go 中定义。

const(
FriendApplicationApprovedNotification = 1201 // add_friend_response
// ...
// 新增 FriendCategoryAddNotification 常量
FriendCategoryAddNotification = 1211
)

并且需要更新 sdkws/sdkws.proto 中的对应字段。且在编写完后执行命令,重新生成对应的 sdkws/sdkws.pb.go 文件。

message FriendInfo {
string ownerUserID = 1;
string remark = 2;
// ...

// 新增 Category 字段
int32 category = 9;
}

  1. 添加存储层接口​
    存储层主要分为三层
    controller:主要用于数据库事务处理和 cache 整合的逻辑控制层
    cache:主要为 db 的数据缓存
    database:数据持久化层,用于业务逻辑的存储

添加 controller 层接口​
在 pkg/common/storage/controller 中,增加新的接口,实现对应的接口,提供给 RPC 逻辑层调用。

例如我们定义的 AddFriendCategory 接口,需在 pkg/common/storage/controller/friend.go 中增加如下代码:

type FriendDatabase interface {
CheckIn(ctx context.Context, user1, user2 string) (inUser1Friends bool, inUser2Friends bool, err error)
// ...

// 定义 Controller 层的 AddFriendCategory 接口
AddFriendCategory(ctx context.Context, ownerUserID, friendUserID string, category int) error
}

// 实现 AddFriendCategory 接口

func (f *FriendDatabase) AddFriendCategory(ctx context.Context, ownerUserID, friendUserID string, category int) error {
// 实现对应的业务逻辑,如数据转换等。

if err := f.friend.AddFriendCategory(ctx, ownerUserID, friendUserID, category); err != nil {
return err
}

return f.cache.DeleteFriend(ownerUserID, friendUserID).DelMaxFriendVersion(ownerUserID).ChainExecDel(ctx)
}

添加 cache 层接口​
在 pkg/common/storage/cache 中增加新的接口,在 pkg/common/storage/cache/cachekey 中实现对应的 Key,并实现对应的接口,提供给 controller 层调用。

我们定义的 AddFriendCategory 接口,可以直接调用 cache 层已有的 DeleteFriend 接口即可。

Notice: cache 层通常是在更新时删除缓存,当获取数据时再去更新数据写入缓存。采用了写时删除,读时更新的策略。
添加 database 层接口​
在 pkg/common/storage/model 中,定义对应数据库的 model 结构体,然后在 pkg/common/storage/database 中增加新的接口,并实现对应的接口,提供给 cache 层整合。

例如,我们定义的 AddFriendCategory 接口,需要在 pkg/common/storage/model/friend.go 中定义对应的 model 结构体添加对应字段, 然后在 pkg/common/storage/database/friend.go 中添加对应的接口供 cache 层整合,在 pkg/common/storage/database/mgo/friend.go 中实现对应的数据库操作。

model/friend.go

type Friend struct {
ID primitive.ObjectID bson:"_id"
OwnerUserID string bson:"owner_user_id"
// ...
Category int bson:"category" // 新增 Category 字段
}

database/friend.go

type Friend interface {
UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) (err error)
// ...
// 定义 DB 层的 AddFriendCategory 接口
AddFriendCategory(ctx context.Context, ownerUserID, friendUserID string, category int) error
}

database/mgo/friend.go

func (f *FriendMgo) AddFriendCategory(ctx context.Context, ownerUserID, friendUserID string, category int) error{
return f.UpdateByMap(ctx, ownerUserID, friendUserID, map[string]any{"category": category})
}

客户端
客户端的主要核心是 OpenIM SDK Core,负责管理 WebSocket 长连接、提供事件的处理回调机制。

SDK Core 接口添加​
定义 Server API 接口​
如果新增的方法需要调用服务端的接口,需要在 server_api 中定义对应的接口方法。

例如我们定义的 AddFriendCategory 接口,需添加对应内容:

在 pkg/api/api.go 中定义对应的 Server API 调用变量:
// relation
var(
AddFriend = newApirelation.ApplyToAddFriendReq, relation.ApplyToAddFriendResp
// ...
// 定义 AddFriendCategory 接口
AddFriendCategory = newApirelation.AddFriendCategoryReq, relation.AddFriendCategoryResp
)

在 relation/server_api.go 中添加对应内容:
func (r Relation) AddFriendCategory(ctx context.Context, req relation.AddFriendCategoryReq) error {
// 实现对应的逻辑和数据转换
req.OwnerUserID = r.loginUserID
return api.AddFriendCategory.Execute(ctx, req)
}

将这个接口定义到 open_im_sdk/relation.go 中,以便下游 SDK 调用。

func AddFriendCategory(callback open_im_sdk_callback.Base, operationID string, req string){
call(callback, operationID, UserForSDK.Relation().AddFriendCategory, req)
}

定义 SDK 对应方法​
在相应模块的 api.go 中定义对应的方法,如:

我们需要在 internal/relation/api.go 中实现对应的逻辑方法:

func (r Relation) AddFriendCategory(ctx context.Context, req sdkpb.AddFriendCategoryReq) (*sdkpb.AddFriendCategoryResp, error) {
// 调用 Server API 的接口
sReq:= &relation.AddFriendCategoryReq{ OwnerUserID: r.loginUserID, FriendUserID: req.friendUserID, Category: req.Category}
if err := r.AddFriendCategory(ctx,sReq) ; err != nil {
return nil, err
}

r.relationSyncMutex.Lock()
defer r.relationSyncMutex.Unlock()
if err := r.IncrSyncFriends(ctx); err != nil {
return nil, err
}

return &sdkpb.AddFriendCategoryResp, nil
}

处理 Server 下发通知​
我们需要对 Server 下发的通知进行处理,需要在 internal/relation/notification.go 中实现对应的通知处理方法。

例如我们定义的 FriendCategoryAddNotification 接口,需在 internal/relation/notification.go 中增加如下代码:

func (r Relation) doNotification(ctx context.Context, msg sdkws.MsgData) error {
r.relationSyncMutex.Lock()
defer r.relationSyncMutex.Unlock()

switch msg.ContentType {
case constant.FriendRemarkSetNotification:
// ...

// 添加对应的通知处理
case constant.FriendCategoryAddNotification:
var tips sdkws.FriendCategoryAddTips // 定义对应的通知结构体
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.FromToUserID != nil {
if tips.FromToUserID.FromUserID == r.loginUserID {
// 包含回调的方法
return r.IncrSyncFriends(ctx)
}
}
}
}

在 IncrSyncFriends 的方法需要写入本地 DB 中,所以需要将更新转换函数的内容: 更新 internal/relation/conversion.go 中的 ServerFriendToLocalFriend 函数。

func ServerFriendToLocalFriend(info sdkws.FriendInfo) model_struct.LocalFriend {
return &model_struct.LocalFriend{
OwnerUserID: info.OwnerUserID,
FriendUserID: info.FriendUser.UserID,
Remark: info.Remark,
CreateTime: info.CreateTime,
AddSource: info.AddSource,
OperatorUserID: info.OperatorUserID,
Nickname: info.FriendUser.Nickname,
FaceURL: info.FriendUser.FaceURL,
Ex: info.Ex,
IsPinned: info.IsPinned,
// 新增 Category 字段
Category: info.Category,
}
}

处理本地 DB 层​
如果涉及到 db 操作,需要调用 db 层的接口,更新本地的 db 数据。

在 pkg/db/db_interface/databse.go 添加接口方法 供 sdk 调用。
此处使用的是现有的 UpdateFriend 方法来实现。

更新 pkg/db/model_struct/data_model_struct.go对应的 LocalFriend 结构体
在 pkg/db/model_struct/data_model_struct.go 中的 LocalFriend 结构体中添加对应的字段:

type LocalFriend struct {
OwnerUserID string gorm:"column:owner_user_id;primary_key;type:varchar(64)" json:"ownerUserID"
FriendUserID string gorm:"column:friend_user_id;primary_key;type:varchar(64)" json:"userID"
Remark string gorm:"column:remark;type:varchar(255)" json:"remark"
// ...
// 添加 Category 字段
Category int32 gorm:"column:category" json:"category"
}

在 pkg/db/friend_model.go中,添加具体实现方法。
此处调用了已存在的 UpdateFriend 方法来实现。

目录
相关文章
|
3天前
|
机器学习/深度学习 人工智能 自然语言处理
PAI Model Gallery 支持云上一键部署 DeepSeek-V3、DeepSeek-R1 系列模型
DeepSeek 系列模型以其卓越性能在全球范围内备受瞩目,多次评测中表现优异,性能接近甚至超越国际顶尖闭源模型(如OpenAI的GPT-4、Claude-3.5-Sonnet等)。企业用户和开发者可使用 PAI 平台一键部署 DeepSeek 系列模型,实现 DeepSeek 系列模型与现有业务的高效融合。
|
3天前
|
人工智能 搜索推荐 Docker
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
DeepSeek R1 + LobeChat + Ollama:快速本地部署模型,创建个性化 AI 助手
1959 101
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
|
1月前
|
供应链 监控 安全
对话|企业如何构建更完善的容器供应链安全防护体系
阿里云与企业共筑容器供应链安全
171370 17
|
10天前
|
Linux iOS开发 MacOS
deepseek部署的详细步骤和方法,基于Ollama获取顶级推理能力!
DeepSeek基于Ollama部署教程,助你免费获取顶级推理能力。首先访问ollama.com下载并安装适用于macOS、Linux或Windows的Ollama版本。运行Ollama后,在官网搜索“deepseek”,选择适合你电脑配置的模型大小(如1.5b、7b等)。通过终端命令(如ollama run deepseek-r1:1.5b)启动模型,等待下载完成即可开始使用。退出模型时输入/bye。详细步骤如下图所示,轻松打造你的最强大脑。
8532 86
|
1月前
|
供应链 监控 安全
对话|企业如何构建更完善的容器供应链安全防护体系
随着云计算和DevOps的兴起,容器技术和自动化在软件开发中扮演着愈发重要的角色,但也带来了新的安全挑战。阿里云针对这些挑战,组织了一场关于云上安全的深度访谈,邀请了内部专家穆寰、匡大虎和黄竹刚,深入探讨了容器安全与软件供应链安全的关系,分析了当前的安全隐患及应对策略,并介绍了阿里云提供的安全解决方案,包括容器镜像服务ACR、容器服务ACK、网格服务ASM等,旨在帮助企业构建涵盖整个软件开发生命周期的安全防护体系。通过加强基础设施安全性、技术创新以及倡导协同安全理念,阿里云致力于与客户共同建设更加安全可靠的软件供应链环境。
150307 32
|
1天前
|
人工智能 自然语言处理 JavaScript
宜搭上新,DeepSeek 插件来了!
钉钉宜搭近日上线了DeepSeek插件,无需编写复杂代码,普通用户也能轻松调用强大的AI大模型能力。安装后,平台新增「AI生成」组件,支持创意内容生成、JS代码编译、工作汇报等场景,大幅提升工作效率。快来体验这一高效智能的办公方式吧!
883 5
|
2天前
|
API 开发工具 Python
阿里云PAI部署DeepSeek及调用
本文介绍如何在阿里云PAI EAS上部署DeepSeek模型,涵盖7B模型的部署、SDK和API调用。7B模型只需一张A10显卡,部署时间约10分钟。文章详细展示了模型信息查看、在线调试及通过OpenAI SDK和Python Requests进行调用的步骤,并附有测试结果和参考文档链接。
659 5
阿里云PAI部署DeepSeek及调用
|
11天前
|
人工智能 自然语言处理 Java
Spring AI,搭建个人AI助手
本期主要是实操性内容,聊聊AI大模型,并使用Spring AI搭建属于自己的AI助手、知识库。本期所需的演示源码笔者托管在Gitee上(https://gitee.com/catoncloud/spring-ai-demo),读者朋友可自行查阅。
938 41
Spring AI,搭建个人AI助手
|
3天前
|
机器学习/深度学习 人工智能 并行计算
一文了解火爆的DeepSeek R1 | AIGC
DeepSeek R1是由DeepSeek公司推出的一款基于强化学习的开源推理模型,无需依赖监督微调或人工标注数据。它在数学、代码和自然语言推理任务上表现出色,具备低成本、高效率和多语言支持等优势,广泛应用于教育辅导、金融分析等领域。DeepSeek R1通过长链推理、多语言支持和高效部署等功能,显著提升了复杂任务的推理准确性,并且其创新的群体相对策略优化(GRPO)算法进一步提高了训练效率和稳定性。此外,DeepSeek R1的成本低至OpenAI同类产品的3%左右,为用户提供了更高的性价比。
785 10
|
2月前
|
弹性计算 人工智能 安全
对话 | ECS如何构筑企业上云的第一道安全防线
随着中小企业加速上云,数据泄露、网络攻击等安全威胁日益严重。阿里云推出深度访谈栏目,汇聚产品技术专家,探讨云上安全问题及应对策略。首期节目聚焦ECS安全性,提出三道防线:数据安全、网络安全和身份认证与权限管理,确保用户在云端的数据主权和业务稳定。此外,阿里云还推出了“ECS 99套餐”,以高性价比提供全面的安全保障,帮助中小企业安全上云。
201994 15
对话 | ECS如何构筑企业上云的第一道安全防线