Understanding OpenIM API to RPC Call Logic: A Comprehensive Guide
Introduction
This document provides an in-depth analysis of the API to RPC (Remote Procedure Call) call logic within the OpenIM framework, focusing on the SearchMsg
functionality. We will explore the codebase, tracing the flow from the API endpoint definition to the final RPC method invocation.
Overview of OpenIM's Structure
Before diving into the specific API, it's essential to understand OpenIM's general architecture. OpenIM is structured to separate concerns between its API layer, business logic, and data access layer, facilitating a scalable and maintainable codebase.
API Endpoint Definition
The journey begins in the internal/api/route.go
file, where the API endpoints are defined. Here, msgGroup.POST("/search_msg", m.SearchMsg)
sets up the route for the search message functionality.
msgGroup.POST("/search_msg", m.SearchMsg)
internal/api/route.go
Key File: - Purpose: Route definition for API endpoints.
- Key Function:
msgGroup.POST
func (m *MessageApi) SearchMsg(c *gin.Context) {
a2r.Call(msg.MsgClient.SearchMessage, m.Client, c)
}
API Function Logic
Moving to internal/api/msg.go
, we find the SearchMsg
function. This function acts as a bridge, translating the HTTP request into a format suitable for RPC communication.
internal/api/msg.go
Key File: - Function:
SearchMsg
- Role: Converts HTTP requests to RPC calls.
RPC Call Abstraction
The a2r.Call
function in github.com/OpenIMSDK/tools/a2r
abstracts the RPC call. It handles JSON binding, request validation, and invoking the actual RPC method.
github.com/OpenIMSDK/tools/a2r
Key File: - Function:
Call
- Responsibility: Error handling, request validation, and RPC invocation.
func Call[A, B, C any](
rpc func(client C, ctx context.Context, req *A, options ...grpc.CallOption) (*B, error),
client C,
c *gin.Context,
) {
var req A
if err := c.BindJSON(&req); err != nil {
log.ZWarn(c, "gin bind json error", err, "req", req)
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap()) // 参数错误
return
}
if err := checker.Validate(&req); err != nil {
apiresp.GinError(c, err) // 参数校验失败
return
}
data, err := rpc(client, c, &req)
if err != nil {
apiresp.GinError(c, err) // RPC调用失败
return
}
apiresp.GinSuccess(c, data) // 成功
}
RPC Method Execution
The actual RPC method, SearchMessage
, is defined in internal/rpc/msg/sync_msg.go
. This method handles the business logic for searching messages, interacting with the database, and constructing the response.
internal/rpc/msg/sync_msg.go
Key File: - Function:
SearchMessage
- Operation: Business logic and database interaction.
func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (resp *msg.SearchMessageResp, err error) {
var chatLogs []*sdkws.MsgData
var total int32
resp = &msg.SearchMessageResp{}
if total, chatLogs, err = m.MsgDatabase.SearchMessage(ctx, req); err != nil {
return nil, err
}
var (
sendIDs []string
recvIDs []string
groupIDs []string
sendMap = make(map[string]string)
recvMap = make(map[string]string)
groupMap = make(map[string]*sdkws.GroupInfo)
)
for _, chatLog := range chatLogs {
if chatLog.SenderNickname == "" {
sendIDs = append(sendIDs, chatLog.SendID)
}
switch chatLog.SessionType {
case constant.SingleChatType:
recvIDs = append(recvIDs, chatLog.RecvID)
case constant.GroupChatType, constant.SuperGroupChatType:
groupIDs = append(groupIDs, chatLog.GroupID)
}
}
if len(sendIDs) != 0 {
sendInfos, err := m.User.GetUsersInfo(ctx, sendIDs)
if err != nil {
return nil, err
}
for _, sendInfo := range sendInfos {
sendMap[sendInfo.UserID] = sendInfo.Nickname
}
}
if len(recvIDs) != 0 {
recvInfos, err := m.User.GetUsersInfo(ctx, recvIDs)
if err != nil {
return nil, err
}
for _, recvInfo := range recvInfos {
recvMap[recvInfo.UserID] = recvInfo.Nickname
}
}
if len(groupIDs) != 0 {
groupInfos, err := m.Group.GetGroupInfos(ctx, groupIDs, true)
if err != nil {
return nil, err
}
for _, groupInfo := range groupInfos {
groupMap[groupInfo.GroupID] = groupInfo
}
}
for _, chatLog := range chatLogs {
pbchatLog := &msg.ChatLog{}
utils.CopyStructFields(pbchatLog, chatLog)
pbchatLog.SendTime = chatLog.SendTime
pbchatLog.CreateTime = chatLog.CreateTime
if chatLog.SenderNickname == "" {
pbchatLog.SenderNickname = sendMap[chatLog.SendID]
}
switch chatLog.SessionType {
case constant.SingleChatType:
pbchatLog.RecvNickname = recvMap[chatLog.RecvID]
case constant.GroupChatType, constant.SuperGroupChatType:
pbchatLog.SenderFaceURL = groupMap[chatLog.GroupID].FaceURL
pbchatLog.GroupMemberCount = groupMap[chatLog.GroupID].MemberCount
pbchatLog.RecvID = groupMap[chatLog.GroupID].GroupID
pbchatLog.GroupName = groupMap[chatLog.GroupID].GroupName
pbchatLog.GroupOwner = groupMap[chatLog.GroupID].OwnerUserID
pbchatLog.GroupType = groupMap[chatLog.GroupID].GroupType
}
resp.ChatLogs = append(resp.ChatLogs, pbchatLog)
}
resp.ChatLogsNum = total
return resp, nil
}
Request and Response Structures
Understanding the request and response structures is crucial. The SearchMessageReq
and SearchMessageResp
structs define the data format for the request and response of the search message functionality.
Structures:
SearchMessageReq
SearchMessageResp
Database Interaction
Finally, pkg/common/db/controller/msg.go
contains the database logic. This file handles the actual querying of messages and the preparation of data to be sent back to the client.
pkg/common/db/controller/msg.go
Key File: - Role: Database querying and data preparation.
func (db *commonMsgDatabase) SearchMessage(ctx context.Context, req *pbmsg.SearchMessageReq) (total int32, msgData []*sdkws.MsgData, err error) {
var totalMsgs []*sdkws.MsgData
total, msgs, err := db.msgDocDatabase.SearchMessage(ctx, req)
if err != nil {
return 0, nil, err
}
for _, msg := range msgs {
if msg.IsRead {
msg.Msg.IsRead = true
}
totalMsgs = append(totalMsgs, convert.MsgDB2Pb(msg.Msg))
}
return total, totalMsgs, nil
}
func (db *commonMsgDatabase) ConvertMsgsDocLen(ctx context.Context, conversationIDs []string) {
db.msgDocDatabase.ConvertMsgsDocLen(ctx, conversationIDs)
}
SearchMsg API Documentation
Overview
The SearchMsg
API allows users to search for messages based on various criteria. This document details the API request structure, required parameters, and examples of use.
Request URL
http://x.x.x.x:10002/msg/search_msg
Header Parameters
Header Name | Example Value | Required | Type | Description |
---|---|---|---|---|
operationID | 1646445464564 | Yes | string | Used for global link tracing |
token | eyJhbxxxx3Xs | Yes | string | Administrator's token |
Request Parameters
JSON Payload
{
"sendID": "exampleSender",
"recvID": "exampleReceiver",
"msgType": 101,
"sendTime": "2023-01-01T00:00:00Z",
"sessionType": 1,
"pagination": {
"pageNumber": 1,
"showNumber": 20
}
}
Parameters Description
Field Name | Required | Type | Description |
---|---|---|---|
sendID | Yes | string | Sender's ID |
recvID | Yes | string | Receiver's ID |
msgType | No | int | Type of the message |
sendTime | No | string | Time when the message was sent (ISO 8601 format) |
sessionType | Yes | int | Type of session (e.g., 1 for individual chat) |
pagination | No | object | Pagination details |
pagination.pageNumber | No | int | Page number for pagination |
pagination.showNumber | No | int | Number of items per page |
Response
Success Response
Code: 200 OK
Content Example:
{
"status": 0,
"data": {
"messages": [
{
"id": "msg123",
"content": "Hello world!",
...
}
],
"total": 150
}
}
Error Response
Code: 400 Bad Request
Content Example:
{
"status": 1,
"error": "Invalid request parameters"
}
Remarks
- The
sendTime
should be in ISO 8601 format for consistency. - Ensure that the
pagination
object is used correctly to manage large datasets. - The
msgType
field is optional and can be used to filter messages by type.
Conclusion
This document has traced the path from an API endpoint definition to the RPC method execution within the OpenIM framework, using the SearchMsg
functionality as an example. Understanding this flow is crucial for developers working with OpenIM, as it provides insights into the framework's design and operation.