如何进行二次开发
- 如果您需要基于 OpenIM 开发新特性,首先要确定是针对业务侧还是即时通讯核心逻辑。
- 由于 OpenIM 系统本身已经做好了比较多的抽象,大部分聊天的功能已经具备了,不建议修改 IM 本身。
- 如果需要增加 IM 的能力,可以参考以下流程,并提交 PR,以保证未来代码统一性。
服务器
OpenIMServer 主要分为长短连接接口,长连接接口主要是 IM 消息的核心逻辑(逻辑入口位于/internal/msggateway),短连接接口主要是 IM 的 业务逻辑(逻辑入口位于/internal/api/),下面具体介绍如何在 IM 中加上新的业务功能。
1. 开发前提Go 官方文档
proto-gen-go >=1.36.1,protoc-gen-go-grpc >= 1.5.1 ,参考二进制文件存放到环境变量,参考github.com/openimsdk/protocol 注意:IMServer 使用的 protobuf 协议以依赖仓库的形式在 github.com/openimsdk/protocol 中,如果需要修改协议,需要先 fork protocol 仓库, 然后在此仓库上增加新的接口协议,然后在 OpenIMServer 的 go.mod 中引用新的包路径,通过:
replace github.com/openimsdk/protocol => github.com/<your-username>/protocol
其中 your_protocol_path 为你 fork 的 protocol 仓库所在的本地路径。
2. Protobuf 协议增加与生成接口协议。
编写 proto 文件syntax = "proto3";
package openim.relation;
option go_package = "
package openim.relation;
option go_package = "
下面介绍如何在编写 proto 文件后,生成对应的 Go 的 pb 代码。
- 安装执行命令的工具 mage,执行
go install github.com/magefile/mage@latest即可安装。 - 在对应仓库中执行
mage InstallDepend,安装 Go 所需的依赖。 - proto 编辑完毕后,在克隆的 protocol 仓库中直接执行
mage GenGo即可生成对应的 go 代码。 - 更多内容,具体参考校验函数
添加新的 API 功能,包括路由定义和接口定义。
API 路由定义路由组,可直接增加到对应的路由组文件中,否则模仿创建新的路由组文件。
API 接口定义
在对应模块的 Server 结构体,新增相应的 gRPC 方法来实现 Server 接口。然后编写主体的业务逻辑。
其中涉及 DB 更新、插入操作需要下发 SDK 实时通知,可直接模仿s.notificationSender.FriendsInfoUpdateNotification这种类型的通知下发函数。(sdk 对应需要处理新的通知)添加新的 RPC 方法
存储层主要分为三层
- controller:主要用于数据库事务处理和 cache 整合的逻辑控制层
- cache:主要为 db 的数据缓存
- database:数据持久化层,用于业务逻辑的存储
添加 controller 层接口逻辑层调用。
例如我们定义的
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/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})
}
客户端
客户端的主要核心是
定义 Server API 接口服务端的接口,需要在
server_api中定义对应的接口方法。例如我们定义的
AddFriendCategory接口,需添加对应内容:- 在
pkg/api/api.go中定义对应的 Server API 调用变量:
// relation
var(
AddFriend = newApi[relation.ApplyToAddFriendReq, relation.ApplyToAddFriendResp]("/friend/add_friend")
// ...
// 定义 AddFriendCategory 接口
AddFriendCategory = newApi[relation.AddFriendCategoryReq, relation.AddFriendCategoryResp]("/friend/add_friend_category")
)
- 在
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 对应方法
我们需要对 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 层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方法来实现。
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
如何进行二次开发
- 如果您需要基于 OpenIM 开发新特性,首先要确定是针对业务侧还是即时通讯核心逻辑。
- 由于 OpenIM 系统本身已经做好了比较多的抽象,大部分聊天的功能已经具备了,不建议修改 IM 本身。
- 如果需要增加 IM 的能力,可以参考以下流程,并提交 PR,以保证未来代码统一性。
服务器
OpenIMServer 主要分为长短连接接口,长连接接口主要是 IM 消息的核心逻辑(逻辑入口位于/internal/msggateway),短连接接口主要是 IM 的 业务逻辑(逻辑入口位于/internal/api/),下面具体介绍如何在 IM 中加上新的业务功能。
1. 开发前提Go 官方文档
proto-gen-go >=1.36.1,protoc-gen-go-grpc >= 1.5.1 ,参考二进制文件存放到环境变量,参考github.com/openimsdk/protocol 注意:IMServer 使用的 protobuf 协议以依赖仓库的形式在 github.com/openimsdk/protocol 中,如果需要修改协议,需要先 fork protocol 仓库, 然后在此仓库上增加新的接口协议,然后在 OpenIMServer 的 go.mod 中引用新的包路径,通过:
replace github.com/openimsdk/protocol => github.com/<your-username>/protocol
其中 your_protocol_path 为你 fork 的 protocol 仓库所在的本地路径。
2. Protobuf 协议增加与生成接口协议。
编写 proto 文件syntax = "proto3";
package openim.relation;
option go_package = "
package openim.relation;
option go_package = "
下面介绍如何在编写 proto 文件后,生成对应的 Go 的 pb 代码。
- 安装执行命令的工具 mage,执行
go install github.com/magefile/mage@latest即可安装。 - 在对应仓库中执行
mage InstallDepend,安装 Go 所需的依赖。 - proto 编辑完毕后,在克隆的 protocol 仓库中直接执行
mage GenGo即可生成对应的 go 代码。 - 更多内容,具体参考校验函数
添加新的 API 功能,包括路由定义和接口定义。
API 路由定义路由组,可直接增加到对应的路由组文件中,否则模仿创建新的路由组文件。
API 接口定义
在对应模块的 Server 结构体,新增相应的 gRPC 方法来实现 Server 接口。然后编写主体的业务逻辑。
其中涉及 DB 更新、插入操作需要下发 SDK 实时通知,可直接模仿s.notificationSender.FriendsInfoUpdateNotification这种类型的通知下发函数。(sdk 对应需要处理新的通知)添加新的 RPC 方法
存储层主要分为三层
- controller:主要用于数据库事务处理和 cache 整合的逻辑控制层
- cache:主要为 db 的数据缓存
- database:数据持久化层,用于业务逻辑的存储
添加 controller 层接口逻辑层调用。
例如我们定义的
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/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})
}
客户端
客户端的主要核心是
定义 Server API 接口服务端的接口,需要在
server_api中定义对应的接口方法。例如我们定义的
AddFriendCategory接口,需添加对应内容:- 在
pkg/api/api.go中定义对应的 Server API 调用变量:
// relation
var(
AddFriend = newApi[relation.ApplyToAddFriendReq, relation.ApplyToAddFriendResp]("/friend/add_friend")
// ...
// 定义 AddFriendCategory 接口
AddFriendCategory = newApi[relation.AddFriendCategoryReq, relation.AddFriendCategoryResp]("/friend/add_friend_category")
)
- 在
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 对应方法
我们需要对 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 层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方法来实现。