mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-04-28 14:29:19 +08:00
feat: provide the interface required by js sdk (#2712)
* fix: GroupApplicationAcceptedNotification * fix: GroupApplicationAcceptedNotification * fix: NotificationUserInfoUpdate * cicd: robot automated Change * fix: component * fix: getConversationInfo * feat: cron task * feat: cron task * feat: cron task * feat: cron task * feat: cron task * fix: minio config url recognition error * update gomake version * update gomake version * fix: seq conversion bug * fix: redis pipe exec * fix: ImportFriends * fix: A large number of logs keysAndValues length is not even * feat: mark read aggregate write * feat: online status supports redis cluster * feat: online status supports redis cluster * feat: online status supports redis cluster * merge * merge * read seq is written to mongo * read seq is written to mongo * fix: invitation to join group notification * fix: friend op_user_id * feat: optimizing asynchronous context * feat: optimizing memamq size * feat: add GetSeqMessage * feat: GroupApplicationAgreeMemberEnterNotification * feat: GroupApplicationAgreeMemberEnterNotification * feat: go.mod * feat: go.mod * feat: join group notification and get seq * feat: join group notification and get seq * feat: avoid pulling messages from sessions with a large number of max seq values of 0 * feat: API supports gzip * go.mod * fix: nil pointer error on close * fix: listen error * fix: listen error * update go.mod * feat: add log * fix: token parse token value * fix: GetMsgBySeqs boundary issues * fix: sn_ not sort * fix: sn_ not sort * fix: sn_ not sort * fix: jssdk add * fix: jssdk support * fix: jssdk support * fix: jssdk support --------- Co-authored-by: withchao <withchao@users.noreply.github.com>
This commit is contained in:
+90
-32
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
apiresp.GinSuccess(c, resp)
|
||||
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, body)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user