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.
427 lines
16 KiB
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
|
|
}
|
|
|