You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openim-sdk-cpp/go/chao-sdk-core/internal/user/user.go

427 lines
16 KiB

// Copyright © 2023 OpenIM SDK. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package user
import (
"context"
"fmt"
"github.com/openimsdk/openim-sdk-core/v3/internal/cache"
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
"github.com/openimsdk/openim-sdk-core/v3/pkg/syncer"
authPb "github.com/openimsdk/protocol/auth"
"github.com/openimsdk/protocol/sdkws"
userPb "github.com/openimsdk/protocol/user"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback"
"github.com/openimsdk/openim-sdk-core/v3/pkg/common"
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
PbConstant "github.com/openimsdk/protocol/constant"
)
type BasicInfo struct {
Nickname string
FaceURL string
}
// User is a struct that represents a user in the system.
type User struct {
db_interface.DataBase
loginUserID string
listener func() open_im_sdk_callback.OnUserListener
userSyncer *syncer.Syncer[*model_struct.LocalUser, syncer.NoResp, string]
commandSyncer *syncer.Syncer[*model_struct.LocalUserCommand, syncer.NoResp, string]
conversationCh chan common.Cmd2Value
UserBasicCache *cache.Cache[string, *BasicInfo]
OnlineStatusCache *cache.Cache[string, *userPb.OnlineStatus]
}
// SetListener sets the user's listener.
func (u *User) SetListener(listener func() open_im_sdk_callback.OnUserListener) {
u.listener = listener
}
// NewUser creates a new User object.
func NewUser(dataBase db_interface.DataBase, loginUserID string, conversationCh chan common.Cmd2Value) *User {
user := &User{DataBase: dataBase, loginUserID: loginUserID, conversationCh: conversationCh}
user.initSyncer()
user.UserBasicCache = cache.NewCache[string, *BasicInfo]()
user.OnlineStatusCache = cache.NewCache[string, *userPb.OnlineStatus]()
return user
}
func (u *User) initSyncer() {
u.userSyncer = syncer.New[*model_struct.LocalUser, syncer.NoResp, string](
func(ctx context.Context, value *model_struct.LocalUser) error {
return u.InsertLoginUser(ctx, value)
},
func(ctx context.Context, value *model_struct.LocalUser) error {
return fmt.Errorf("not support delete user %s", value.UserID)
},
func(ctx context.Context, serverUser, localUser *model_struct.LocalUser) error {
return u.DataBase.UpdateLoginUser(context.Background(), serverUser)
},
func(user *model_struct.LocalUser) string {
return user.UserID
},
nil,
func(ctx context.Context, state int, server, local *model_struct.LocalUser) error {
switch state {
case syncer.Update:
u.listener().OnSelfInfoUpdated(utils.StructToJsonString(server))
if server.Nickname != local.Nickname || server.FaceURL != local.FaceURL {
_ = common.TriggerCmdUpdateMessage(ctx, common.UpdateMessageNode{Action: constant.UpdateMsgFaceUrlAndNickName,
Args: common.UpdateMessageInfo{SessionType: constant.SingleChatType, UserID: server.UserID, FaceURL: server.FaceURL, Nickname: server.Nickname}}, u.conversationCh)
}
}
return nil
},
)
u.commandSyncer = syncer.New[*model_struct.LocalUserCommand, syncer.NoResp, string](
func(ctx context.Context, command *model_struct.LocalUserCommand) error {
// Logic to insert a command
return u.DataBase.ProcessUserCommandAdd(ctx, command)
},
func(ctx context.Context, command *model_struct.LocalUserCommand) error {
// Logic to delete a command
return u.DataBase.ProcessUserCommandDelete(ctx, command)
},
func(ctx context.Context, serverCommand *model_struct.LocalUserCommand, localCommand *model_struct.LocalUserCommand) error {
// Logic to update a command
if serverCommand == nil || localCommand == nil {
return fmt.Errorf("nil command reference")
}
return u.DataBase.ProcessUserCommandUpdate(ctx, serverCommand)
},
func(command *model_struct.LocalUserCommand) string {
// Return a unique identifier for the command
if command == nil {
return ""
}
return command.Uuid
},
func(a *model_struct.LocalUserCommand, b *model_struct.LocalUserCommand) bool {
// Compare two commands to check if they are equal
if a == nil || b == nil {
return false
}
return a.Uuid == b.Uuid && a.Type == b.Type && a.Value == b.Value
},
func(ctx context.Context, state int, serverCommand *model_struct.LocalUserCommand, localCommand *model_struct.LocalUserCommand) error {
if u.listener == nil {
return nil
}
switch state {
case syncer.Delete:
u.listener().OnUserCommandDelete(utils.StructToJsonString(serverCommand))
case syncer.Update:
u.listener().OnUserCommandUpdate(utils.StructToJsonString(serverCommand))
case syncer.Insert:
u.listener().OnUserCommandAdd(utils.StructToJsonString(serverCommand))
}
return nil
},
)
}
//func (u *User) equal(a, b *model_struct.LocalUser) bool {
// if a.CreateTime != b.CreateTime {
// log.ZDebug(context.Background(), "user equal", "a", a.CreateTime, "b", b.CreateTime)
// }
// if a.UserID != b.UserID {
// log.ZDebug(context.Background(), "user equal", "a", a.UserID, "b", b.UserID)
// }
// if a.Ex != b.Ex {
// log.ZDebug(context.Background(), "user equal", "a", a.Ex, "b", b.Ex)
// }
//
// if a.Nickname != b.Nickname {
// log.ZDebug(context.Background(), "user equal", "a", a.Nickname, "b", b.Nickname)
// }
// if a.FaceURL != b.FaceURL {
// log.ZDebug(context.Background(), "user equal", "a", a.FaceURL, "b", b.FaceURL)
// }
// if a.AttachedInfo != b.AttachedInfo {
// log.ZDebug(context.Background(), "user equal", "a", a.AttachedInfo, "b", b.AttachedInfo)
// }
// if a.GlobalRecvMsgOpt != b.GlobalRecvMsgOpt {
// log.ZDebug(context.Background(), "user equal", "a", a.GlobalRecvMsgOpt, "b", b.GlobalRecvMsgOpt)
// }
// if a.AppMangerLevel != b.AppMangerLevel {
// log.ZDebug(context.Background(), "user equal", "a", a.AppMangerLevel, "b", b.AppMangerLevel)
// }
// return a.UserID == b.UserID && a.Nickname == b.Nickname && a.FaceURL == b.FaceURL &&
// a.CreateTime == b.CreateTime && a.AttachedInfo == b.AttachedInfo &&
// a.Ex == b.Ex && a.GlobalRecvMsgOpt == b.GlobalRecvMsgOpt && a.AppMangerLevel == b.AppMangerLevel
//}
// DoNotification handles incoming notifications for the user.
func (u *User) DoNotification(ctx context.Context, msg *sdkws.MsgData) {
log.ZDebug(ctx, "user notification", "msg", *msg)
go func() {
switch msg.ContentType {
case constant.UserInfoUpdatedNotification:
u.userInfoUpdatedNotification(ctx, msg)
case constant.UserStatusChangeNotification:
u.userStatusChangeNotification(ctx, msg)
case constant.UserCommandAddNotification:
u.userCommandAddNotification(ctx, msg)
case constant.UserCommandDeleteNotification:
u.userCommandDeleteNotification(ctx, msg)
case constant.UserCommandUpdateNotification:
u.userCommandUpdateNotification(ctx, msg)
default:
// log.Error(operationID, "type failed ", msg.ClientMsgID, msg.ServerMsgID, msg.ContentType)
}
}()
}
// userInfoUpdatedNotification handles notifications about updated user information.
func (u *User) userInfoUpdatedNotification(ctx context.Context, msg *sdkws.MsgData) {
log.ZDebug(ctx, "userInfoUpdatedNotification", "msg", *msg)
tips := sdkws.UserInfoUpdatedTips{}
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
log.ZError(ctx, "comm.UnmarshalTips failed", err, "msg", msg.Content)
return
}
if tips.UserID == u.loginUserID {
u.SyncLoginUserInfo(ctx)
} else {
log.ZDebug(ctx, "detail.UserID != u.loginUserID, do nothing", "detail.UserID", tips.UserID, "u.loginUserID", u.loginUserID)
}
}
// userStatusChangeNotification get subscriber status change callback
func (u *User) userStatusChangeNotification(ctx context.Context, msg *sdkws.MsgData) {
log.ZDebug(ctx, "userStatusChangeNotification", "msg", *msg)
tips := sdkws.UserStatusChangeTips{}
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
log.ZError(ctx, "comm.UnmarshalTips failed", err, "msg", msg.Content)
return
}
if tips.FromUserID == u.loginUserID {
log.ZDebug(ctx, "self terminal login", "tips", tips)
return
}
u.SyncUserStatus(ctx, tips.FromUserID, tips.Status, tips.PlatformID)
}
// userCommandAddNotification handle notification when user add favorite
func (u *User) userCommandAddNotification(ctx context.Context, msg *sdkws.MsgData) {
log.ZDebug(ctx, "userCommandAddNotification", "msg", *msg)
tip := sdkws.UserCommandAddTips{}
if tip.ToUserID == u.loginUserID {
u.SyncAllCommand(ctx)
} else {
log.ZDebug(ctx, "ToUserID != u.loginUserID, do nothing", "detail.UserID", tip.ToUserID, "u.loginUserID", u.loginUserID)
}
}
// userCommandDeleteNotification handle notification when user delete favorite
func (u *User) userCommandDeleteNotification(ctx context.Context, msg *sdkws.MsgData) {
log.ZDebug(ctx, "userCommandAddNotification", "msg", *msg)
tip := sdkws.UserCommandDeleteTips{}
if tip.ToUserID == u.loginUserID {
u.SyncAllCommand(ctx)
} else {
log.ZDebug(ctx, "ToUserID != u.loginUserID, do nothing", "detail.UserID", tip.ToUserID, "u.loginUserID", u.loginUserID)
}
}
// userCommandUpdateNotification handle notification when user update favorite
func (u *User) userCommandUpdateNotification(ctx context.Context, msg *sdkws.MsgData) {
log.ZDebug(ctx, "userCommandAddNotification", "msg", *msg)
tip := sdkws.UserCommandUpdateTips{}
if tip.ToUserID == u.loginUserID {
u.SyncAllCommand(ctx)
} else {
log.ZDebug(ctx, "ToUserID != u.loginUserID, do nothing", "detail.UserID", tip.ToUserID, "u.loginUserID", u.loginUserID)
}
}
// GetUsersInfoFromSvr retrieves user information from the server.
func (u *User) GetUsersInfoFromSvr(ctx context.Context, userIDs []string) ([]*model_struct.LocalUser, error) {
resp, err := util.CallApi[userPb.GetDesignateUsersResp](ctx, constant.GetUsersInfoRouter, userPb.GetDesignateUsersReq{UserIDs: userIDs})
if err != nil {
return nil, sdkerrs.WrapMsg(err, "GetUsersInfoFromSvr failed")
}
return datautil.Batch(ServerUserToLocalUser, resp.UsersInfo), nil
}
// GetSingleUserFromSvr retrieves user information from the server.
func (u *User) GetSingleUserFromSvr(ctx context.Context, userID string) (*model_struct.LocalUser, error) {
users, err := u.GetUsersInfoFromSvr(ctx, []string{userID})
if err != nil {
return nil, err
}
if len(users) > 0 {
return users[0], nil
}
return nil, sdkerrs.ErrUserIDNotFound.WrapMsg(fmt.Sprintf("getSelfUserInfo failed, userID: %s not exist", userID))
}
// getSelfUserInfo retrieves the user's information.
func (u *User) getSelfUserInfo(ctx context.Context) (*model_struct.LocalUser, error) {
userInfo, errLocal := u.GetLoginUser(ctx, u.loginUserID)
if errLocal != nil {
srvUserInfo, errServer := u.GetServerUserInfo(ctx, []string{u.loginUserID})
if errServer != nil {
return nil, errServer
}
if len(srvUserInfo) == 0 {
return nil, sdkerrs.ErrUserIDNotFound
}
userInfo = ServerUserToLocalUser(srvUserInfo[0])
_ = u.InsertLoginUser(ctx, userInfo)
}
return userInfo, nil
}
// updateSelfUserInfo updates the user's information.
func (u *User) updateSelfUserInfo(ctx context.Context, userInfo *sdkws.UserInfo) error {
userInfo.UserID = u.loginUserID
if err := util.ApiPost(ctx, constant.UpdateSelfUserInfoRouter, userPb.UpdateUserInfoReq{UserInfo: userInfo}, nil); err != nil {
return err
}
_ = u.SyncLoginUserInfo(ctx)
return nil
}
// updateSelfUserInfoEx updates the user's information with Ex field.
func (u *User) updateSelfUserInfoEx(ctx context.Context, userInfo *sdkws.UserInfoWithEx) error {
userInfo.UserID = u.loginUserID
if err := util.ApiPost(ctx, constant.UpdateSelfUserInfoExRouter, userPb.UpdateUserInfoExReq{UserInfo: userInfo}, nil); err != nil {
return err
}
_ = u.SyncLoginUserInfo(ctx)
return nil
}
// CRUD user command
func (u *User) ProcessUserCommandAdd(ctx context.Context, userCommand *userPb.ProcessUserCommandAddReq) error {
if err := util.ApiPost(ctx, constant.ProcessUserCommandAdd, userPb.ProcessUserCommandAddReq{UserID: u.loginUserID, Type: userCommand.Type, Uuid: userCommand.Uuid, Value: userCommand.Value}, nil); err != nil {
return err
}
return u.SyncAllCommand(ctx)
}
// ProcessUserCommandDelete delete user's choice
func (u *User) ProcessUserCommandDelete(ctx context.Context, userCommand *userPb.ProcessUserCommandDeleteReq) error {
if err := util.ApiPost(ctx, constant.ProcessUserCommandDelete, userPb.ProcessUserCommandDeleteReq{UserID: u.loginUserID,
Type: userCommand.Type, Uuid: userCommand.Uuid}, nil); err != nil {
return err
}
return u.SyncAllCommand(ctx)
}
// ProcessUserCommandUpdate update user's choice
func (u *User) ProcessUserCommandUpdate(ctx context.Context, userCommand *userPb.ProcessUserCommandUpdateReq) error {
if err := util.ApiPost(ctx, constant.ProcessUserCommandUpdate, userPb.ProcessUserCommandUpdateReq{UserID: u.loginUserID,
Type: userCommand.Type, Uuid: userCommand.Uuid, Value: userCommand.Value}, nil); err != nil {
return err
}
return u.SyncAllCommand(ctx)
}
// ProcessUserCommandGet get user's choice
func (u *User) ProcessUserCommandGetAll(ctx context.Context) ([]*userPb.CommandInfoResp, error) {
localCommands, err := u.DataBase.ProcessUserCommandGetAll(ctx)
if err != nil {
return nil, err // Handle the error appropriately
}
var result []*userPb.CommandInfoResp
for _, localCommand := range localCommands {
result = append(result, &userPb.CommandInfoResp{
Type: localCommand.Type,
CreateTime: localCommand.CreateTime,
Uuid: localCommand.Uuid,
Value: localCommand.Value,
})
}
return result, nil
}
// ParseTokenFromSvr parses a token from the server.
func (u *User) ParseTokenFromSvr(ctx context.Context) (int64, error) {
resp, err := util.CallApi[authPb.ParseTokenResp](ctx, constant.ParseTokenRouter, authPb.ParseTokenReq{})
return resp.ExpireTimeSeconds, err
}
// GetServerUserInfo retrieves user information from the server.
func (u *User) GetServerUserInfo(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error) {
resp, err := util.CallApi[userPb.GetDesignateUsersResp](ctx, constant.GetUsersInfoRouter, &userPb.GetDesignateUsersReq{UserIDs: userIDs})
if err != nil {
return nil, err
}
return resp.UsersInfo, nil
}
// subscribeUsersStatus Presence status of subscribed users.
func (u *User) subscribeUsersStatus(ctx context.Context, userIDs []string) ([]*userPb.OnlineStatus, error) {
resp, err := util.CallApi[userPb.SubscribeOrCancelUsersStatusResp](ctx, constant.SubscribeUsersStatusRouter, &userPb.SubscribeOrCancelUsersStatusReq{
UserID: u.loginUserID,
UserIDs: userIDs,
Genre: PbConstant.SubscriberUser,
})
if err != nil {
return nil, err
}
return resp.StatusList, nil
}
// unsubscribeUsersStatus Unsubscribe a user's presence.
func (u *User) unsubscribeUsersStatus(ctx context.Context, userIDs []string) error {
_, err := util.CallApi[userPb.SubscribeOrCancelUsersStatusResp](ctx, constant.SubscribeUsersStatusRouter, &userPb.SubscribeOrCancelUsersStatusReq{
UserID: u.loginUserID,
UserIDs: userIDs,
Genre: PbConstant.Unsubscribe,
})
if err != nil {
return err
}
return nil
}
// getSubscribeUsersStatus Get the online status of subscribers.
func (u *User) getSubscribeUsersStatus(ctx context.Context) ([]*userPb.OnlineStatus, error) {
resp, err := util.CallApi[userPb.GetSubscribeUsersStatusResp](ctx, constant.GetSubscribeUsersStatusRouter, &userPb.GetSubscribeUsersStatusReq{
UserID: u.loginUserID,
})
if err != nil {
return nil, err
}
return resp.StatusList, nil
}
// getUserStatus Get the online status of users.
func (u *User) getUserStatus(ctx context.Context, userIDs []string) ([]*userPb.OnlineStatus, error) {
resp, err := util.CallApi[userPb.GetUserStatusResp](ctx, constant.GetUserStatusRouter, &userPb.GetUserStatusReq{
UserID: u.loginUserID,
UserIDs: userIDs,
})
if err != nil {
return nil, err
}
return resp.StatusList, nil
}