Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

313 lines
8.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}