即时通讯开源项目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 方法来实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/69377.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度解析:网站快速收录与网站内容更新频率的关系

本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/97.html 网站快速收录与网站内容更新频率之间存在着密切的关系。以下是对这一关系的深度解析: 一、内容更新频率对网站快速收录的影响 提高收录速度 定期发布新内容会促使搜索…

【个人开发】macbook m1 Lora微调qwen大模型

本项目参考网上各类教程整理而成,为个人学习记录。 项目github源码地址:Lora微调大模型 项目中微调模型为:qwen/Qwen1.5-4B-Chat。 去年新发布的Qwen/Qwen2.5-3B-Instruct同样也适用。 微调步骤 step0: 环境准备 conda create --name fin…

c++计算机教程

目的 做出-*/%计算机 要求 做出可以计算-*/%的计算机 实现 完整代码 #include<bits/stdc.h> int main() {std::cout<<"加 减- 乘* 除/ 取余% \没有了|(因为可以算三位)"<<"\n"<<"提示:每打完一个符号或打完一个数,\…

了解Linux 中 make 与 Makefile

目录 一、为什么开发者需要构建工具&#xff1f; 二、make/Makefile 1. Makefile基本规则 2.清理项目 三、make的工作原理 一、为什么开发者需要构建工具&#xff1f; 在软件开发中&#xff0c;我们经常面临这样的场景&#xff1a;一个项目包含数十个源代码文件&#xff…

RK3568中,使用cmake搭建C++工程进行RGA开发

在 RK3568 平台上使用 C 配合 RGA (Raster Graphics Acceleration) 进行图像加速开发&#xff0c;以下是详细的配置步骤和示例&#xff1a; 1. 环境准备 安装 RK3568 SDK 确保已安装 Rockchip 官方提供的 SDK&#xff08;如 Linux SDK&#xff09;&#xff0c;RGA 头文件和库通…

win11右击显示全部

正常&#xff1a; 输入&#xff1a; reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve 重启或刷新进程 刷新&#xff1a; taskkill /f /im explorer.exe & start explorer.exe 成功&#xff1a;

Redis基础--常用数据结构的命令及底层编码

零.前置知识 关于时间复杂度,按照以下视角看待. redis整体key的个数 -- O(N)当前key对应的value中的元素个数 -- O(N)当前命令行中key的个数 -- O(1) 一.string 1.1string类型常用命令 1.2string类型内部编码 二.Hash 哈希 2.1hash类型常用命令 2.2hash类型内部编码 2.3ha…

React 设计模式:实用指南

React 提供了众多出色的特性以及丰富的设计模式&#xff0c;用于简化开发流程。开发者能够借助 React 组件设计模式&#xff0c;降低开发时间以及编码的工作量。此外&#xff0c;这些模式让 React 开发者能够构建出成果更显著、性能更优越的各类应用程序。 本文将会为您介绍五…

SpringBoo项目标准测试样例

文章目录 概要Controller Api 测试源码单元测试集成测试 概要 Spring Boot项目测试用例 测试方式是否调用数据库使用的注解特点单元测试&#xff08;Mock Service&#xff09;❌ 不调用数据库WebMvcTest MockBean只测试 Controller 逻辑&#xff0c;速度快集成测试&#xff0…

Unity扩展编辑器使用整理(一)

准备工作 在Unity工程中新建Editor文件夹存放编辑器脚本&#xff0c; Unity中其他的特殊文件夹可以参考官方文档链接&#xff0c;如下&#xff1a; Unity - 手册&#xff1a;保留文件夹名称参考 (unity3d.com) 一、菜单栏扩展 1.增加顶部菜单栏选项 使用MenuItem&#xff…

Vue3+codemirror6实现公式(规则)编辑器

实现截图 实现/带实现功能 插入标签 插入公式 提示补全 公式验证 公式计算 需要的依赖 "codemirror/autocomplete": "^6.18.4","codemirror/lang-javascript": "^6.2.2","codemirror/state": "^6.5.2","cod…

K8S QoS等级

在 Kubernetes (K8S) 中&#xff0c;QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;等级用于定义 Pod 在资源调度和管理过程中的优先级&#xff0c;确保在资源紧张时能够更好地管理和分配资源。Kubernetes 根据 Pod 的资源请求和限制将 Pod 分为三种 QoS …

4.PPT:日月潭景点介绍【18】

目录 NO1、2、3、4​ NO5、6、7、8 ​ ​NO9、10、11、12 ​ 表居中或者水平/垂直居中单元格内容居中或者水平/垂直居中 NO1、2、3、4 新建一个空白演示文稿&#xff0c;命名为“PPT.pptx”&#xff08;“.pptx”为扩展名&#xff09;新建幻灯片 开始→版式“PPT_素材.doc…

如何在macOS上安装Ollama

安装Ollama 安装Ollama的步骤相对简单&#xff0c;以下是基本的安装指南&#xff1a; 访问官方网站&#xff1a;打开浏览器&#xff0c;访问Ollama的官方网站。 下载安装包&#xff1a;根据你的操作系统&#xff0c;选择相应的安装包进行下载。 运行安装程序&#xff1a;下载完…

开源项目介绍-词云生成

开源词云项目是一个利用开源技术生成和展示词云的工具或框架&#xff0c;广泛应用于文本分析、数据可视化等领域。以下是几个与开源词云相关的项目及其特点&#xff1a; Stylecloud Stylecloud 是一个由 Maximilianinir 创建和维护的开源项目&#xff0c;旨在通过扩展 wordclou…

Redis双写一致性(数据库与redis数据一致性)

一 什么是双写一致性&#xff1f; 当修改了数据库&#xff08;MySQL&#xff09;中的数据&#xff0c;也要同时更新缓存&#xff08;redis&#xff09;中的数据&#xff0c;缓存中的数据要和数据库中的数据保持一致 双写一致性&#xff0c;根据业务对时间上的要求&#xff0c;…

笔记:新能源汽车零部件功率级测试怎么进行?

摘要:本文旨在梳理主机厂对新能源汽车核心零部件功率级测试需求,通过试验室的主流设备仪器集成,快速实现试验方案搭建,并体现测试测量方案的时效性、便捷性优势。目标是通过提升实现设备的有效集成能力、实现多设备测试过程的有效协同、流程化测试,可快速采集、分析当前数…

C32.【C++ Cont】静态实现双向链表及STL库的list

目录 1.知识回顾 2.静态实现演示图 3.静态实现代码 1.初始双向链表 2.头插 3.遍历链表 4.查找某个值 4.任意位置之后插入元素 5.任意位置之前插入元素 6.删除任意位置的元素 4.STL库的list 1.知识回顾 96.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删 97.【C…

二级C语言题解:矩阵主、反对角线元素之和,二分法求方程根,处理字符串中 * 号

目录 一、程序填空&#x1f4dd; --- 矩阵主、反对角线元素之和 题目&#x1f4c3; 分析&#x1f9d0; 二、程序修改&#x1f6e0;️ --- 二分法求方程根 题目&#x1f4c3; 分析&#x1f9d0; 三、程序设计&#x1f4bb; --- 处理字符串中 * 号 题目&#x1f…

采用idea中的HTTP Client插件测试

1.安装插件 采用idea中的HTTP Client插件进行接口测试,好处是不用打开post/swagger等多个软件,并且可以保存测试时的参数,方便后续继续使用. 高版本(2020版本以上)的idea一般都自带这个插件,如果没有也可以单独安装. 2.使用 插件安装完成(或者如果idea自带插件),会在每个Con…