分享
  1. 首页
  2. 文章

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

OpenIM · · 774 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

如何进行二次开发

  • 如果您需要基于 OpenIM 开发新特性,首先要确定是针对业务侧还是即时通讯核心逻辑。
  • 由于 OpenIM 系统本身已经做好了比较多的抽象,大部分聊天的功能已经具备了,不建议修改 IM 本身。
  • 如果需要增加 IM 的能力,可以参考以下流程,并提交 PR,以保证未来代码统一性。

服务器

OpenIMServer 主要分为长短连接接口,长连接接口主要是 IM 消息的核心逻辑(逻辑入口位于/internal/msggateway),短连接接口主要是 IM 的 业务逻辑(逻辑入口位于/internal/api/),下面具体介绍如何在 IM 中加上新的业务功能。

1. 开发前提Go 官方文档
  • 搭建 grpc 环境,推荐 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 = "

    下面介绍如何在编写 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

    关注微信
    774 次点击
    暂无回复
    添加一条新回复 (您需要 后才能回复 没有账号 ?)
    • 请尽量让自己的回复能够对别人有帮助
    • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
    • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
    • 图片支持拖拽、截图粘贴等方式上传

    用户登录

    没有账号?注册
    (追記) (追記ここまで)

    今日阅读排行

      加载中
    (追記) (追記ここまで)

    一周阅读排行

      加载中

    关注我

    • 扫码关注领全套学习资料 关注微信公众号
    • 加入 QQ 群:
      • 192706294(已满)
      • 731990104(已满)
      • 798786647(已满)
      • 729884609(已满)
      • 977810755(已满)
      • 815126783(已满)
      • 812540095(已满)
      • 1006366459(已满)
      • 692541889

    • 关注微信公众号
    • 加入微信群:liuxiaoyan-s,备注入群
    • 也欢迎加入知识星球 Go粉丝们(免费)

    给该专栏投稿 写篇新文章

    每篇文章有总共有 5 次投稿机会

    收入到我管理的专栏 新建专栏