diff --git a/cmd/openim-api/main.go b/cmd/openim-api/main.go index e29ed2a59..3690cfc99 100644 --- a/cmd/openim-api/main.go +++ b/cmd/openim-api/main.go @@ -15,10 +15,9 @@ package main import ( - _ "net/http/pprof" - "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" "github.com/openimsdk/tools/system/program" + _ "net/http/pprof" ) func main() { diff --git a/go.mod b/go.mod index 09c626bc7..b6baca2a1 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/openimsdk/protocol v0.0.72 + github.com/openimsdk/protocol v0.0.72-alpha.41 github.com/openimsdk/tools v0.0.50-alpha.16 github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 diff --git a/go.sum b/go.sum index 00ecc7ed7..6f5475274 100644 --- a/go.sum +++ b/go.sum @@ -319,8 +319,8 @@ github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/openimsdk/gomake v0.0.14-alpha.5 h1:VY9c5x515lTfmdhhPjMvR3BBRrRquAUCFsz7t7vbv7Y= github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI= -github.com/openimsdk/protocol v0.0.72 h1:K+vslwaR7lDXyBzb07UuEQITaqsgighz7NyXVIWsu6A= -github.com/openimsdk/protocol v0.0.72/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= +github.com/openimsdk/protocol v0.0.72-alpha.41 h1:SMMoTc1iu+wtRqUqmIgqPJFejLgPeauOwoJ4VVG4iMQ= +github.com/openimsdk/protocol v0.0.72-alpha.41/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= github.com/openimsdk/tools v0.0.50-alpha.16 h1:bC1AQvJMuOHtZm8LZRvN8L5mH1Ws2VYdL+TLTs1iGSc= github.com/openimsdk/tools v0.0.50-alpha.16/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= diff --git a/internal/api/jssdk/jssdk.go b/internal/api/jssdk/jssdk.go index 7f136c74c..2fcbf5ec6 100644 --- a/internal/api/jssdk/jssdk.go +++ b/internal/api/jssdk/jssdk.go @@ -1,11 +1,15 @@ package jssdk import ( + "context" "github.com/gin-gonic/gin" "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/protocol/group" + "github.com/openimsdk/protocol/jssdk" "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/relation" "github.com/openimsdk/protocol/sdkws" - "github.com/openimsdk/tools/a2r" + "github.com/openimsdk/protocol/user" "github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/utils/datautil" "sort" @@ -16,16 +20,22 @@ const ( defaultGetActiveConversation = 100 ) -func NewJSSdkApi(msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk { +func NewJSSdkApi(user user.UserClient, friend relation.FriendClient, group group.GroupClient, msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk { return &JSSdk{ - msg: msg, - conv: conv, + user: user, + friend: friend, + group: group, + msg: msg, + conv: conv, } } type JSSdk struct { - msg msg.MsgClient - conv conversation.ConversationClient + user user.UserClient + friend relation.FriendClient + group group.GroupClient + msg msg.MsgClient + conv conversation.ConversationClient } func (x *JSSdk) GetActiveConversations(c *gin.Context) { @@ -36,25 +46,71 @@ func (x *JSSdk) GetConversations(c *gin.Context) { call(c, x.getConversations) } -func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, error) { - req, err := a2r.ParseRequest[ActiveConversationsReq](ctx) - if err != nil { - return nil, err +func (x *JSSdk) fillConversations(ctx context.Context, conversations []*jssdk.ConversationMsg) error { + if len(conversations) == 0 { + return nil + } + var ( + userIDs []string + groupIDs []string + ) + for _, c := range conversations { + if c.Conversation.GroupID == "" { + userIDs = append(userIDs, c.Conversation.UserID) + } else { + groupIDs = append(groupIDs, c.Conversation.GroupID) + } + } + var ( + userMap map[string]*sdkws.UserInfo + friendMap map[string]*relation.FriendInfoOnly + groupMap map[string]*sdkws.GroupInfo + ) + if len(userIDs) > 0 { + users, err := field(ctx, x.user.GetDesignateUsers, &user.GetDesignateUsersReq{UserIDs: userIDs}, (*user.GetDesignateUsersResp).GetUsersInfo) + if err != nil { + return err + } + friends, err := field(ctx, x.friend.GetFriendInfo, &relation.GetFriendInfoReq{OwnerUserID: conversations[0].Conversation.OwnerUserID, FriendUserIDs: userIDs}, (*relation.GetFriendInfoResp).GetFriendInfos) + if err != nil { + return err + } + userMap = datautil.SliceToMap(users, (*sdkws.UserInfo).GetUserID) + friendMap = datautil.SliceToMap(friends, (*relation.FriendInfoOnly).GetFriendUserID) + } + if len(groupIDs) > 0 { + resp, err := x.group.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{GroupIDs: groupIDs}) + if err != nil { + return err + } + groupMap = datautil.SliceToMap(resp.GroupInfos, (*sdkws.GroupInfo).GetGroupID) } + for _, c := range conversations { + if c.Conversation.GroupID == "" { + c.User = userMap[c.Conversation.UserID] + c.Friend = friendMap[c.Conversation.UserID] + } else { + c.Group = groupMap[c.Conversation.GroupID] + } + } + return nil +} + +func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActiveConversationsReq) (*jssdk.GetActiveConversationsResp, error) { if req.Count <= 0 || req.Count > maxGetActiveConversation { req.Count = defaultGetActiveConversation } - opUserID := mcontext.GetOpUserID(ctx) + req.OwnerUserID = mcontext.GetOpUserID(ctx) conversationIDs, err := field(ctx, x.conv.GetConversationIDs, - &conversation.GetConversationIDsReq{UserID: opUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs) + &conversation.GetConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs) if err != nil { return nil, err } if len(conversationIDs) == 0 { - return &ConversationsResp{}, nil + return &jssdk.GetActiveConversationsResp{}, nil } readSeq, err := field(ctx, x.msg.GetHasReadSeqs, - &msg.GetHasReadSeqsReq{UserID: opUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs) + &msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs) if err != nil { return nil, err } @@ -64,24 +120,24 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er return nil, err } if len(activeConversation) == 0 { - return &ConversationsResp{}, nil + return &jssdk.GetActiveConversationsResp{}, nil } sortConversations := sortActiveConversations{ Conversation: activeConversation, } if len(activeConversation) > 1 { pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs, - &conversation.GetPinnedConversationIDsReq{UserID: opUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs) + &conversation.GetPinnedConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs) if err != nil { return nil, err } sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs) } sort.Sort(&sortConversations) - sortList := sortConversations.Top(req.Count) + sortList := sortConversations.Top(int(req.Count)) conversations, err := field(ctx, x.conv.GetConversations, &conversation.GetConversationsReq{ - OwnerUserID: opUserID, + OwnerUserID: req.OwnerUserID, ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string { return c.ConversationID })}, (*conversation.GetConversationsResp).GetConversations) @@ -90,7 +146,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er } msgs, err := field(ctx, x.msg.GetSeqMessage, &msg.GetSeqMessageReq{ - UserID: opUserID, + UserID: req.OwnerUserID, Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs { return &msg.ConversationSeqs{ ConversationID: c.ConversationID, @@ -104,7 +160,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string { return c.ConversationID }) - resp := make([]ConversationMsg, 0, len(sortList)) + resp := make([]*jssdk.ConversationMsg, 0, len(sortList)) for _, c := range sortList { conv, ok := conversationMap[c.ConversationID] if !ok { @@ -114,13 +170,16 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 { lastMsg = msgList.Msgs[0] } - resp = append(resp, ConversationMsg{ + resp = append(resp, &jssdk.ConversationMsg{ Conversation: conv, LastMsg: lastMsg, MaxSeq: c.MaxSeq, ReadSeq: readSeq[c.ConversationID], }) } + if err := x.fillConversations(ctx, resp); err != nil { + return nil, err + } var unreadCount int64 for _, c := range activeConversation { count := c.MaxSeq - readSeq[c.ConversationID] @@ -128,24 +187,20 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er unreadCount += count } } - return &ConversationsResp{ + return &jssdk.GetActiveConversationsResp{ Conversations: resp, UnreadCount: unreadCount, }, nil } -func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) { - req, err := a2r.ParseRequest[conversation.GetConversationsReq](ctx) - if err != nil { - return nil, err - } +func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversationsReq) (*jssdk.GetConversationsResp, error) { req.OwnerUserID = mcontext.GetOpUserID(ctx) - conversations, err := field(ctx, x.conv.GetConversations, req, (*conversation.GetConversationsResp).GetConversations) + conversations, err := field(ctx, x.conv.GetConversations, &conversation.GetConversationsReq{OwnerUserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*conversation.GetConversationsResp).GetConversations) if err != nil { return nil, err } if len(conversations) == 0 { - return &ConversationsResp{}, nil + return &jssdk.GetConversationsResp{}, nil } req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string { return c.ConversationID @@ -177,19 +232,22 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) { return nil, err } } - resp := make([]ConversationMsg, 0, len(conversations)) + resp := make([]*jssdk.ConversationMsg, 0, len(conversations)) for _, c := range conversations { var lastMsg *sdkws.MsgData if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 { lastMsg = msgList.Msgs[0] } - resp = append(resp, ConversationMsg{ + resp = append(resp, &jssdk.ConversationMsg{ Conversation: c, LastMsg: lastMsg, MaxSeq: maxSeqs[c.ConversationID], ReadSeq: readSeqs[c.ConversationID], }) } + if err := x.fillConversations(ctx, resp); err != nil { + return nil, err + } var unreadCount int64 for conversationID, maxSeq := range maxSeqs { count := maxSeq - readSeqs[conversationID] @@ -197,7 +255,7 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) { unreadCount += count } } - return &ConversationsResp{ + return &jssdk.GetConversationsResp{ Conversations: resp, UnreadCount: unreadCount, }, nil diff --git a/internal/api/jssdk/stu.go b/internal/api/jssdk/stu.go deleted file mode 100644 index 2f63975b3..000000000 --- a/internal/api/jssdk/stu.go +++ /dev/null @@ -1,22 +0,0 @@ -package jssdk - -import ( - "github.com/openimsdk/protocol/conversation" - "github.com/openimsdk/protocol/sdkws" -) - -type ActiveConversationsReq struct { - Count int `json:"count"` -} - -type ConversationMsg struct { - Conversation *conversation.Conversation `json:"conversation"` - LastMsg *sdkws.MsgData `json:"lastMsg"` - MaxSeq int64 `json:"maxSeq"` - ReadSeq int64 `json:"readSeq"` -} - -type ConversationsResp struct { - UnreadCount int64 `json:"unreadCount"` - Conversations []ConversationMsg `json:"conversations"` -} diff --git a/internal/api/jssdk/tools.go b/internal/api/jssdk/tools.go index c57457d9f..c19d8970b 100644 --- a/internal/api/jssdk/tools.go +++ b/internal/api/jssdk/tools.go @@ -3,8 +3,14 @@ package jssdk import ( "context" "github.com/gin-gonic/gin" + "github.com/openimsdk/tools/a2r" "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/checker" + "github.com/openimsdk/tools/errs" "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + "io" + "strings" ) func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A, opts ...grpc.CallOption) (*B, error), req *A, get func(*B) C) (C, error) { @@ -16,11 +22,56 @@ func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A return get(resp), nil } -func call[R any](c *gin.Context, fn func(ctx *gin.Context) (R, error)) { - resp, err := fn(c) +func call[A, B any](c *gin.Context, fn func(ctx context.Context, req *A) (*B, error)) { + var isJSON bool + switch contentType := c.GetHeader("Content-Type"); { + case contentType == "": + isJSON = true + case strings.Contains(contentType, "application/json"): + isJSON = true + case strings.Contains(contentType, "application/protobuf"): + case strings.Contains(contentType, "application/x-protobuf"): + default: + apiresp.GinError(c, errs.ErrArgs.WrapMsg("unsupported content type")) + return + } + var req *A + if isJSON { + var err error + req, err = a2r.ParseRequest[A](c) + if err != nil { + apiresp.GinError(c, err) + return + } + } else { + body, err := io.ReadAll(c.Request.Body) + if err != nil { + apiresp.GinError(c, err) + return + } + req = new(A) + if err := proto.Unmarshal(body, any(req).(proto.Message)); err != nil { + apiresp.GinError(c, err) + return + } + if err := checker.Validate(&req); err != nil { + apiresp.GinError(c, err) + return + } + } + resp, err := fn(c, req) + if err != nil { + apiresp.GinError(c, err) + return + } + if isJSON { + apiresp.GinSuccess(c, resp) + return + } + body, err := proto.Marshal(any(resp).(proto.Message)) if err != nil { apiresp.GinError(c, err) return } - apiresp.GinSuccess(c, resp) + apiresp.GinSuccess(c, body) } diff --git a/internal/api/jssdk_test.go b/internal/api/jssdk_test.go deleted file mode 100644 index 472ca56b5..000000000 --- a/internal/api/jssdk_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package api - -import ( - "github.com/openimsdk/protocol/msg" - "sort" - "testing" -) - -func TestName(t *testing.T) { - val := sortActiveConversations{ - Conversation: []*msg.ActiveConversation{ - { - ConversationID: "100", - LastTime: 100, - }, - { - ConversationID: "200", - LastTime: 200, - }, - { - ConversationID: "300", - LastTime: 300, - }, - { - ConversationID: "400", - LastTime: 400, - }, - }, - //PinnedConversationIDs: map[string]struct{}{ - // "100": {}, - // "300": {}, - //}, - } - sort.Sort(&val) - t.Log(val) - -} diff --git a/internal/api/router.go b/internal/api/router.go index f87ec526c..560516d30 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -77,7 +77,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En r.Use(prommetricsGin(), gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc)) u := NewUserApi(*userRpc) m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID) - j := jssdk.NewJSSdkApi(messageRpc.Client, conversationRpc.Client) + j := jssdk.NewJSSdkApi(userRpc.Client, friendRpc.Client, groupRpc.Client, messageRpc.Client, conversationRpc.Client) userRouterGroup := r.Group("/user") { userRouterGroup.POST("/user_register", u.UserRegister) diff --git a/internal/rpc/relation/friend.go b/internal/rpc/relation/friend.go index 9d55ba4d9..2f4843a8e 100644 --- a/internal/rpc/relation/friend.go +++ b/internal/rpc/relation/friend.go @@ -273,7 +273,14 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFri return &relation.SetFriendRemarkResp{}, nil } -// ok. +func (s *friendServer) GetFriendInfo(ctx context.Context, req *relation.GetFriendInfoReq) (*relation.GetFriendInfoResp, error) { + friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs) + if err != nil { + return nil, err + } + return &relation.GetFriendInfoResp{FriendInfos: convert.FriendOnlyDB2PbOnly(friends)}, nil +} + func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) { resp = &relation.GetDesignatedFriendsResp{} if datautil.Duplicate(req.FriendUserIDs) { diff --git a/pkg/common/convert/friend.go b/pkg/common/convert/friend.go index 8d6cfad18..6d346b0f4 100644 --- a/pkg/common/convert/friend.go +++ b/pkg/common/convert/friend.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" + "github.com/openimsdk/protocol/relation" "github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/tools/utils/datautil" @@ -35,9 +36,7 @@ func FriendPb2DB(friend *sdkws.FriendInfo) *model.Friend { return dbFriend } -func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, - getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error), -) (*sdkws.FriendInfo, error) { +func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (*sdkws.FriendInfo, error) { users, err := getUsers(ctx, []string{friendDB.FriendUserID}) if err != nil { return nil, err @@ -53,11 +52,7 @@ func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, }, nil } -func FriendsDB2Pb( - ctx context.Context, - friendsDB []*model.Friend, - getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error), -) (friendsPb []*sdkws.FriendInfo, err error) { +func FriendsDB2Pb(ctx context.Context, friendsDB []*model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (friendsPb []*sdkws.FriendInfo, err error) { if len(friendsDB) == 0 { return nil, nil } @@ -86,7 +81,21 @@ func FriendsDB2Pb( friendsPb = append(friendsPb, friendPb) } return friendsPb, nil +} +func FriendOnlyDB2PbOnly(friendsDB []*model.Friend) []*relation.FriendInfoOnly { + return datautil.Slice(friendsDB, func(f *model.Friend) *relation.FriendInfoOnly { + return &relation.FriendInfoOnly{ + OwnerUserID: f.OwnerUserID, + FriendUserID: f.FriendUserID, + Remark: f.Remark, + CreateTime: f.CreateTime.UnixMilli(), + AddSource: f.AddSource, + OperatorUserID: f.OperatorUserID, + Ex: f.Ex, + IsPinned: f.IsPinned, + } + }) } func FriendRequestDB2Pb(ctx context.Context, friendRequests []*model.FriendRequest, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) {