package imser import ( "fmt" "strings" "91porn-server/app/appg" "91porn-server/common" "91porn-server/common/imclient" "91porn-server/models/v/imusermod" "91porn-server/models/v/usermod" ) const friendListFirstPageNow int64 = 1 type IMUserMapping struct { UID uint64 IMUserID int64 } type IMFriendItem struct { FriendIMUserID int64 `json:"friendImUserId"` UID uint64 `json:"uid,omitempty"` NickName string `json:"nickName,omitempty"` Avatar string `json:"avatar,omitempty"` Remark string `json:"remark,omitempty"` Status int `json:"status"` CreatedAt int64 `json:"createdAt,omitempty"` UpdatedAt int64 `json:"updatedAt,omitempty"` } type IMHistoryMessage struct { MessageID string `json:"messageId"` SenderID int64 `json:"senderId"` ReceiverID int64 `json:"receiverId"` SenderUserID uint64 `json:"senderUserId,omitempty"` SenderNickName string `json:"senderNickName,omitempty"` SenderAvatar string `json:"senderAvatar,omitempty"` ReceiverUserID uint64 `json:"receiverUserId,omitempty"` ReceiverNickName string `json:"receiverNickName,omitempty"` ReceiverAvatar string `json:"receiverAvatar,omitempty"` Content string `json:"content"` MessageType string `json:"messageType"` Seq int64 `json:"seq"` CreatedAt int64 `json:"createdAt"` FromSelf bool `json:"fromSelf"` } // ResolveStoredIMUserID 解析用户的 IM 平台 user_id: // 优先 usermod.ImUserID → imusermod 映射表 → 都没有则调用 IM 注册接口创建。 // 保证返回的 imUserId > 0,或返回错误,调用方无需再判 0。 func ResolveStoredIMUserID(uid uint64) (int64, error) { // 直接复用 EnsureSDKUser:它的"查→兜底注册"流程跟这里需要的语义一致。 return EnsureSDKUser(uid) } func ResolveUIDByIMUserID(imUserID int64) (uint64, error) { if imUserID <= 0 { return 0, fmt.Errorf("imUserId is empty") } // imUserId → uid 以 imusermod 为唯一真源 mapping, err := imusermod.FindByIMUserIDQuiet(imUserID) if err != nil { return 0, err } if mapping.UID <= 0 { return 0, fmt.Errorf("im user not found") } return mapping.UID, nil } func BusinessUsersByIMUserIDs(imUserIDs []int64) (map[int64]*usermod.User, error) { out := make(map[int64]*usermod.User) ids := uniquePositiveInt64(imUserIDs) if len(ids) == 0 { return out, nil } // imUserId → uid 走 imusermod(唯一真源),再按 uid 批量取业务用户 mappings, err := imusermod.FindByIMUserIDsQuiet(ids) if err != nil { return nil, err } uids := make([]uint64, 0, len(mappings)) uidByIM := make(map[int64]uint64, len(mappings)) for _, mapping := range mappings { if mapping.UID <= 0 || mapping.IMUserID <= 0 { continue } uids = append(uids, mapping.UID) uidByIM[mapping.IMUserID] = mapping.UID } if len(uids) == 0 { return out, nil } usersByUID, err := usermod.FindUsersMapByUID(uids) if err != nil { return nil, err } for imUserID, uid := range uidByIM { if u := usersByUID[uid]; u != nil { out[imUserID] = u } } return out, nil } func EnsureFriendsBidirectional(uid, peerUID uint64) (selfIMUserID, peerIMUserID int64, added bool, err error) { if uid == 0 || peerUID == 0 { return 0, 0, false, fmt.Errorf("uid is empty") } if uid == peerUID { return 0, 0, false, fmt.Errorf("cannot add self") } c := newSDKClient() if !c.Enabled() { return 0, 0, false, nil } selfIMUserID, err = EnsureSDKUser(uid) if err != nil { return 0, 0, false, err } peerIMUserID, err = EnsureSDKUser(peerUID) if err != nil { return 0, 0, false, err } addedForward, err := ensureFriendDirect(c, selfIMUserID, peerIMUserID) if err != nil { return 0, 0, false, err } addedReverse, err := ensureFriendDirect(c, peerIMUserID, selfIMUserID) if err != nil { return 0, 0, false, err } return selfIMUserID, peerIMUserID, addedForward || addedReverse, nil } func FriendList(uid uint64, now int64) (imUserID int64, usedNow int64, nextNow int64, friends []IMFriendItem, err error) { c := newSDKClient() if !c.Enabled() { return 0, 0, 0, []IMFriendItem{}, nil } imUserID, err = EnsureSDKUser(uid) if err != nil { return 0, 0, 0, nil, err } if now <= 0 { now = friendListFirstPageNow } var relations []imclient.Friend err = withAppToken(c, func(token string) error { var listErr error relations, listErr = c.FriendList(imUserID, now, token) return listErr }) if err != nil { return 0, 0, 0, nil, err } friendIMIDs := make([]int64, 0, len(relations)) for _, relation := range relations { if relation.FriendID > 0 { friendIMIDs = append(friendIMIDs, relation.FriendID) } } userByIM, err := BusinessUsersByIMUserIDs(friendIMIDs) if err != nil { return 0, 0, 0, nil, err } nextNow = now items := make([]IMFriendItem, 0, len(relations)) for _, relation := range relations { if relation.UpdatedAt > nextNow { nextNow = relation.UpdatedAt } item := IMFriendItem{ FriendIMUserID: relation.FriendID, Remark: relation.Remark, Status: relation.Status, CreatedAt: relation.CreatedAt, UpdatedAt: relation.UpdatedAt, } if u := userByIM[relation.FriendID]; u != nil { item.UID = u.UID item.NickName = u.Name item.Avatar = common.BindUrl(appg.Conf.URL.OriginUrl, u.Portrait) } items = append(items, item) } return imUserID, now, nextNow, items, nil } func HistoryMessages(uid, peerUID uint64, req imclient.HistoryMessageRequest) (selfIMUserID, peerIMUserID int64, messages []IMHistoryMessage, err error) { c := newSDKClient() if !c.Enabled() { return 0, 0, []IMHistoryMessage{}, nil } selfIMUserID, err = EnsureSDKUser(uid) if err != nil { return 0, 0, nil, err } peerIMUserID, err = ResolveStoredIMUserID(peerUID) if err != nil { return 0, 0, nil, err } if peerIMUserID <= 0 { return 0, 0, nil, fmt.Errorf("peer im user not found") } req.UserID1 = selfIMUserID req.UserID2 = peerIMUserID var history []imclient.MessageDetail err = withAppToken(c, func(token string) error { var historyErr error history, historyErr = c.HistoryMessages(req, token) return historyErr }) if err != nil { return 0, 0, nil, err } messageIMIDs := make([]int64, 0, len(history)*2) for _, msg := range history { if msg.SenderID > 0 { messageIMIDs = append(messageIMIDs, msg.SenderID) } if msg.ReceiverID > 0 { messageIMIDs = append(messageIMIDs, msg.ReceiverID) } } userByIM, err := BusinessUsersByIMUserIDs(messageIMIDs) if err != nil { return 0, 0, nil, err } out := make([]IMHistoryMessage, 0, len(history)) for _, msg := range history { item := IMHistoryMessage{ MessageID: msg.MessageID, SenderID: msg.SenderID, ReceiverID: msg.ReceiverID, Content: msg.Content, MessageType: msg.MessageType, Seq: msg.Seq, CreatedAt: msg.CreatedAt, FromSelf: msg.SenderID == selfIMUserID, } if u := userByIM[msg.SenderID]; u != nil { item.SenderUserID = u.UID item.SenderNickName = u.Name item.SenderAvatar = common.BindUrl(appg.Conf.URL.OriginUrl, u.Portrait) } if u := userByIM[msg.ReceiverID]; u != nil { item.ReceiverUserID = u.UID item.ReceiverNickName = u.Name item.ReceiverAvatar = common.BindUrl(appg.Conf.URL.OriginUrl, u.Portrait) } out = append(out, item) } return selfIMUserID, peerIMUserID, out, nil } func ensureFriendDirect(c *imclient.Client, userIMID, friendIMID int64) (bool, error) { var added bool err := withAppToken(c, func(token string) error { relations, err := c.FriendList(userIMID, friendListFirstPageNow, token) if err != nil { return err } for _, relation := range relations { if relation.FriendID == friendIMID && relation.Status == 0 { added = false return nil } } err = c.DirectAddFriend(imclient.DirectAddFriendRequest{ UserID: userIMID, FriendID: friendIMID, }, token) if err == nil { added = true } return err }) if isDuplicateFriendError(err) { return false, nil } return added, err } func isDuplicateFriendError(err error) bool { if err == nil { return false } msg := strings.ToLower(err.Error()) return strings.Contains(msg, "already") || strings.Contains(msg, "exists") || strings.Contains(msg, "重复") || strings.Contains(msg, "已存在") || strings.Contains(msg, "好友关系已存在") } func uniquePositiveInt64(in []int64) []int64 { out := make([]int64, 0, len(in)) seen := make(map[int64]struct{}, len(in)) for _, v := range in { if v <= 0 { continue } if _, ok := seen[v]; ok { continue } seen[v] = struct{}{} out = append(out, v) } return out }