Skip to content

Commit 589ae2b

Browse files
committed
docs: improve the develop new features docs contents.
1 parent 2ff950f commit 589ae2b

1 file changed

Lines changed: 257 additions & 36 deletions

File tree

Lines changed: 257 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,280 @@
11
---
22
title: '如何进行二次开发'
33
sidebar_position: 6
4-
54
---
65

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

8+
- 如果您需要基于 OpenIM 开发新特性,首先要确定是针对业务侧还是即时通讯核心逻辑。
9+
- 由于 OpenIM 系统本身已经做好了比较多的抽象,大部分聊天的功能已经具备了,不建议修改 IM 本身。
10+
- 如果需要增加 IM 的能力,可以参考以下流程,并提交 PR,以保证未来代码统一性。
11+
12+
# 服务器
13+
14+
> OpenIMServer 主要分为长短连接接口,长连接接口主要是 IM 消息的核心逻辑(逻辑入口位于/internal/msggateway),短连接接口主要是 IM 的
15+
> 业务逻辑(逻辑入口位于/internal/api/),下面具体介绍如何在 IM 中加上新的业务功能。
16+
17+
## 1. 开发前提
18+
19+
- 搭建环境
20+
- 搭建 Go 环境,参考[Go 官方文档](https://golang.org/doc/install)
21+
- 搭建 grpc 环境,参考[grpc 官方文档](https://grpc.io/docs/languages/go/quickstart/)
22+
23+
* fork OpenIMServer 依赖的外部仓库 protocol
24+
25+
- clone 官方的后台协议仓库: [github.com/openimsdk/protocol](https://github.com/openimsdk/protocol)
26+
27+
**注意**:IMServer 使用的 protobuf 协议以依赖仓库的形式在 `github.com/openimsdk/protocol` 中,如果需要修改协议,需要先 fork protocol 仓库,
28+
然后在此仓库上增加新的接口协议,然后在 OpenIMServer 的 `go.mod` 中引用新的包路径,通过:
29+
30+
`replace github.com/openimsdk/protocol => ./your_protocol_path`
31+
32+
其中 `your_protocol_path` 为你 fork 的 protocol 仓库所在的本地路径。
33+
34+
## 2. Protobuf 协议增加与生成
35+
36+
下面以 Go 为例,介绍如何完整的生成一个新的接口协议。
37+
38+
### 编写 proto 文件
39+
40+
- 首先根据业务需求,定义一个新的模块。本文以 `hello` 为例,创建对应的 module 文件夹和 proto 文件,如 `hello/hello.proto`
41+
- 编写 proto 文件,定义新的接口方法,如:
42+
43+
```proto
44+
syntax = "proto3";
45+
46+
package openim.hello;
47+
48+
// 定义生成的 go 语言包名,通常为 github.com/openimsdk/protocol/<module>,其中 module 为具体的模块名称。
49+
option go_package = "github.com/openimsdk/protocol/hello";
50+
51+
// 定义 Hello 的请求参数
52+
message HelloRequest {
53+
string name = 1;
54+
}
55+
56+
// 定义 Hello 的响应参数
57+
message HelloResponse {
58+
string message = 1;
59+
UserInfo user = 2; // 引用自定义的 message 结构
60+
}
61+
62+
// 自定义 message 结构
63+
message UserInfo {
64+
string name = 1;
65+
int32 age = 2;
66+
}
1367
14-
## 服务器
15-
> OpenIMServer主要分为长短连接接口,长连接接口主要是IM消息的核心逻辑(逻辑入口位于/internal/msggateway),短连接接口主要是IM的
16-
业务逻辑(逻辑入口位于/internal/api/),下面具体介绍如何在IM中加上新的业务功能。
68+
// 定义一个 Hello 模块的 RPC 服务
69+
service HelloService {
70+
// 定义一个 SayHello 的 RPC 方法
71+
rpc SayHello(HelloRequest) returns (HelloResponse);
72+
}
73+
```
74+
75+
这里面分别定义了一个请求参数 `HelloRequest`,一个响应参数 `HelloResponse`,一个自定义的结构体 `UserInfo`,以及一个 RPC 服务 `HelloService`,其中包含的 RPC 方法 `SayHello`
76+
77+
上面这个主要的关注点为:
78+
定义生成的 go_package 路径 -> 定义 RPC 方法的请求和响应 message 结构,如果有需要定义的通用结构可以单独定义 message 结构(如 `UserInfo`)-> 定义 RPC 服务和方法。
79+
80+
### 生成 Go 代码
81+
82+
下面介绍如何在编写 proto 文件后,生成对应的 Go 的 pb 代码。
83+
84+
- 安装执行命令的工具 mage,执行 `go install github.com/magefile/mage@latest` 即可安装。
85+
- 在对应仓库中执行 `mage InstallDepend`,安装 Go 所需的依赖。
86+
- proto 编辑完毕后,在克隆的 protocol 仓库中直接执行 `mage GenGo` 即可生成对应的 go 代码。
87+
- 更多内容,具体参考[用 mage 生成 PB 文件](https://github.com/openimsdk/protocol/blob/main/mage-README.md)
1788

89+
## 3. API 功能添加
1890

19-
### 1、开发前提
20-
- 搭建环境
21-
- 搭建Go环境,参考[Go官方文档](https://golang.org/doc/install)
22-
- 搭建grpc环境,参考[grpc官方文档](https://grpc.io/docs/languages/go/quickstart/)
23-
+ fork OpenIMServer依赖的外部仓库
24-
- clone官方的后台协议仓库: github.com/openimsdk/protocol
91+
添加新的 API 功能,包括路由定义和接口定义。
2592

26-
****:IMServer使用的protobuf协议以依赖仓库的形式在github.com/openimsdk/protocol中,如果需要修改协议,需要先fork protocol仓库,
27-
然后在此仓库上增加新的接口协议,然后在OpenIMServer的go.mod中引用新的协议通过:
93+
### API 路由定义
2894

29-
`replace github.com/openimsdk/protocol => ./your_protocol_path`
30-
### 2、协议增加与生成
31-
- 编写proto文件,定义新的接口协议
32-
- 生成go代码
33-
- proto编辑完毕后在克隆的protocol仓库中直接执行`gen.cmd`或者`gen.sh`即可生成go代码
34-
### 3、api功能添加
35-
- 在/internal/api/router.go文件中增加新的接口包括定义路由,如果增加的接口属于一个路由组,可直接增加到对应的路由组文件中,否则模仿创建新的路由组文件。
36-
- 在/internal/api/xxx.go中如果api的json请求和rpc的request请求一致,可以直接调用a2r.Call函数,否则需要自己解析json请求,然后调用grpc接口(可模仿SendMessage接口)。
37-
### 4、rpc功能添加
95+
- 定义路由的文件在 `/internal/api/router.go`,我们需要在 `newGinRouter` 函数中定义对应的路由,如:
96+
例如我们要定义一个 Friend 模块的 `AddFriendCategory` 接口,我们可以在 `newGinRouter` 函数中增加如下代码:
97+
98+
```go
99+
// friend routing group
100+
{
101+
f := NewFriendApi(relation.NewFriendClient(friendConn))
102+
friendRouterGroup := r.Group("/friend")
103+
friendRouterGroup.POST("/delete_friend", f.DeleteFriend)
104+
// ......
105+
106+
// 新增 AddFriendCategory 接口的路由
107+
friendRouterGroup.POST("/add_friend_category", f.AddFriendCategory)
108+
}
38109
```
39-
举例:在/internal/rpc/group/group.go中增加新的rpc函数,使用groupServer结构体实现对应的grpc的server接口,然后编写主体业务逻辑,其中涉及db更新
40-
插入操作需要下发sdk实时通知,可直接模仿 g.notification.GroupApplicationAgreeMemberEnterNotification这种类型的通知下发函数(sdk对应需要处理新的通知)
110+
111+
如果增加的接口属于一个路由组,可直接增加到对应的路由组文件中,否则模仿创建新的路由组文件。
112+
113+
### API 接口定义
114+
115+
根据上面的路由定义,我们需要在 `/internal/api/friend/friend.go` 中增加对应的接口定义。
116+
如果 API 的 JSON 请求与 RPC 的 Request 请求一致,可以直接调用 `a2r.Call` 函数,否则需要自己解析 JSON 请求,然后调用 gRPC 接口(可参考 Message 模块的 `SendMessage` 接口)。
117+
例如:
118+
119+
```go
120+
// 如果 API 的 Request 与 JSON 请求一致
121+
func (o *FriendApi) AddFriendCategory(c *gin.Context) {
122+
// AddFriendCategory 为在 RPC 定义的方法
123+
a2r.Call(c,relation.FriendClient.AddFriendCategory, o.client)
124+
}
125+
126+
// 如果 API 的 Request 与 JSON 请求不一致,需要自己解析 JSON 请求
127+
func (o *FriendApi) AddFriendCategory(c *gin.Context) {
128+
var req apistruct.AddFriendCategoryReq{}
129+
130+
if err := c.BindJSON(&req); err != nil {
131+
apiresp.GinError(c,errs.ErrArgs.WithDetail(err.Error()).Wrap())
132+
return
133+
}
134+
135+
resp, err := o.client.AddFriendCategory(c, &req)
136+
if err != nil {
137+
apiresp.GinError(c,err)
138+
return
139+
}
140+
141+
apiresp.GinSuccess(c, resp)
142+
}
41143
```
42-
### 5、存储层接口增加
144+
145+
## 4. 添加 RPC 方法
146+
147+
在对应模块的 Server 结构体,新增相应的 gRPC 方法来实现 Server 接口。然后编写主体的业务逻辑。
148+
其中涉及 DB 更新、插入操作需要下发 SDK 实时通知,可直接模仿 `g.notification.GroupApplicationAgreeMemberEnterNotification` 这种类型的通知下发函数。(sdk 对应需要处理新的通知)
149+
150+
### 添加新的 RPC 方法
151+
152+
`internal/rpc/relation/friend/friend.go` 中增加新的 rpc 方法 `AddFriendCategory`,并编写主体的业务逻辑。
153+
154+
```go
155+
156+
// AddFriendCategory 添加好友分组
157+
158+
func (s *friendServer) AddFriendCategory(ctx context.Context, req *relation.AddFriendCategoryReq) (*relation.AddFriendCategoryResp, error) {
159+
160+
// 实现具体的业务逻辑
161+
// ...
162+
163+
// 调用 DB 操作
164+
if err := s.db.AddFriendCategory(req.UserId, req.CategoryName); err != nil {
165+
return nil, err
166+
}
167+
168+
// 调用 sdk 下发通知(如果有对应的 DB 操作)
169+
s.notification.FriendCategoryAddNotification(req.UserId, req.CategoryName) // 仅举例,具体通知函数需要根据业务需求实现
170+
171+
return &relation.AddFriendCategoryResp{}, nil
172+
}
173+
174+
```
175+
176+
对应的通知下发函数 `FriendCategoryAddNotification` 应在 `internal/rpc/relation/notification.go` 中实现。
177+
178+
```go
179+
func (f *FriendNotificationSender) FriendCategoryAddNotification(ctx context.Context, userID string, categoryName string) {
180+
tips := sdkws.FriendCategoryAddTips{UserID: userID, CategoryName: categoryName}
181+
f.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.FriendCategoryAddNotification, &tips)
182+
}
183+
184+
```
185+
186+
## 5. 添加存储层接口
187+
43188
> 存储层主要分为三层
44-
- controller:主要用于数据库事务处理和cache整合的逻辑控制层
45-
- cache:主要为db的数据缓存
46-
- database:数据持久化层,用于业务逻辑的存储
189+
>
190+
> - controller:主要用于数据库事务处理和 cache 整合的逻辑控制层
191+
> - cache:主要为 db 的数据缓存
192+
> - database:数据持久化层,用于业务逻辑的存储
193+
194+
### 添加 controller 层接口
195+
196+
`pkg/common/storage/controller` 中,增加新的接口,实现对应的接口,提供给 RPC 逻辑层调用。
197+
198+
例如我们定义的 `AddFriendCategory` 接口,需在 `pkg/common/storage/controller/friend.go` 中增加如下代码:
199+
200+
```go
201+
202+
type friendDatabase struct {
203+
friend database.Friend
204+
cache cache.FriendCache
205+
}
206+
207+
type FriendDatabase interface {
208+
CheckIn(ctx context.Context, user1, user2 string) (inUser1Friends bool, inUser2Friends bool, err error)
209+
// ...
210+
211+
// 新增 AddFriendCategory 接口
212+
AddFriendCategory(ctx context.Context, userID, categoryName string) error
213+
}
214+
215+
// 实现 AddFriendCategory 接口
216+
217+
func (f *FriendDatabase) AddFriendCategory(ctx context.Context, userID, categoryName string) error {
218+
// 实现对应的业务逻辑,如数据转换等。
219+
220+
if err := f.friend.AddFriendCategory(ctx, userID, categoryName); err != nil {
221+
return err
222+
}
223+
224+
return f.cache.AddFriendCategory(ctx, userID, categoryName)
225+
}
226+
227+
```
228+
229+
### 添加 cache 层接口
230+
231+
`pkg/common/storage/cache` 中增加新的接口,在 `pkg/common/storage/cache/cachekey` 中实现对应的 Key,并实现对应的接口,提供给 controller 层调用。
232+
233+
例如我们定义的 `AddFriendCategory` 接口,需在 `pkg/common/storage/cache/cachekey/friend.go` 中实现其前缀和对应的 Get 函数,
234+
`pkg/common/storage/cache/friend.go` 定义供 controller 层调用的接口,并在 `pkg/common/storage/cache/redis/friend.go` 实现对应的缓存逻辑。
235+
236+
**cachekey/friend.go**
237+
238+
```go
239+
240+
const (
241+
FriendCategoryKey = "FRIEND_CATEGORY:"
242+
)
243+
244+
func GetFriendCategoryKey(userID, categoryName string) string {
245+
return FriendCategoryKey + userID + "-" + categoryName
246+
}
47247
```
48-
- 在pkg\common\storage\controller中增加新的接口,实现对应的接口,提供给rpc逻辑层调用。
49-
- 在pkg\common\storage\cache中增加新的接口,(pkg\common\storage\cache\cachekey中存储了缓存所有的key前缀)实现对应的接口,
50-
提供给controller层调用。
51-
- 在pkg\common\storage\model中可定义数据库的model结构体,pkg\common\storage\database中增加新的接口,实现对应的接口,提供给cache层整合。
248+
249+
**cache/friend.go**
250+
251+
```go
252+
type FriendCache interface {
253+
BatchDeleter
254+
CloneFriendCache() FriendCache
255+
// ...
256+
257+
// 新增 AddFriendCategory 接口
258+
AddFriendCategory(ctx context.Context, userID, categoryName string) error
259+
}
52260
```
53261

54-
## 客户端
262+
**cache/redis/friend.go**
55263

264+
```go
265+
func (f *FriendCacheRedis) AddFriendCategory(ctx context.Context, userID, categoryName string) error {
266+
// 实现对应的缓存逻辑
267+
key := cachekey.GetFriendCategoryKey(userID, categoryName)
268+
return f.redis.Set(ctx, key)
269+
}
270+
```
56271

272+
### 添加 database 层接口
57273

274+
- 在 pkg/common/storage/model 中可定义数据库的 model 结构体,pkg/common/storage/database 中增加新的接口,实现对应的接口,提供给 cache 层整合。
58275

276+
# 客户端
59277

278+
```
279+
280+
```

0 commit comments

Comments
 (0)