搜索好友接口优化

This commit is contained in:
2025-12-14 15:19:24 +08:00
parent b189251a6f
commit 4f2b0bb213
3 changed files with 423 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
/**
* package api
* 作用全局搜索API处理器
*/
package api
import (
"strconv"
"xk-websocket-v2/internal/service"
"xk-websocket-v2/internal/utils"
"github.com/gin-gonic/gin"
)
/**
* GlobalSearchHandler
* 功能:聚合搜索(联系人、群聊、聊天记录)
* 路径GET /api/search?keyword=xxx&type=all|contacts|groups|messages&limit=20
*
* 参数说明:
* - keyword: 搜索关键词(必填)
* - type: 搜索类型可选值all默认、contacts、groups、messages
* - limit: 每类结果的最大数量默认20
*
* 返回结构:
* {
* "contacts": [{ "user": {...}, "remark_name": "xxx", "room_id": "xxx" }],
* "groups": [{ "room_id": "xxx", "room_name": "xxx", ... }],
* "messages": [{ "id": 1, "room_id": "xxx", "content": "xxx", "sender": {...} }]
* }
*/
func GlobalSearchHandler(c *gin.Context) {
// 获取当前用户ID
userID, exists := c.Get("user_id")
if !exists {
utils.Unauthorized(c, "未登录")
return
}
// 获取搜索参数
keyword := c.Query("keyword")
if keyword == "" {
utils.BadRequest(c, "搜索关键词不能为空")
return
}
searchType := c.DefaultQuery("type", "all")
if searchType != "all" && searchType != "contacts" && searchType != "groups" && searchType != "messages" {
searchType = "all"
}
limitStr := c.DefaultQuery("limit", "20")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 || limit > 100 {
limit = 20
}
// 执行搜索
result, err := service.SearchSvc.GlobalSearch(userID.(string), keyword, searchType, limit)
if err != nil {
utils.InternalError(c, "搜索失败")
return
}
utils.SuccessWithData(c, result, "搜索成功")
}

View File

@@ -0,0 +1,352 @@
/**
* package service
* 作用:聚合搜索服务
*/
package service
import (
"strings"
"xk-websocket-v2/internal/model"
"gorm.io/gorm"
)
// SearchService 搜索服务结构体
type SearchService struct {
DB *gorm.DB
}
// SearchSvc 全局单例
var SearchSvc *SearchService
/**
* InitSearchService
* 功能:初始化搜索服务
*/
func InitSearchService(db *gorm.DB) {
SearchSvc = &SearchService{DB: db}
}
// SearchResult 搜索结果结构
type SearchResult struct {
Contacts []ContactSearchResult `json:"contacts"`
Groups []GroupSearchResult `json:"groups"`
Messages []MessageSearchResult `json:"messages"`
}
// ContactSearchResult 联系人搜索结果
type ContactSearchResult struct {
User *model.User `json:"user"`
RemarkName string `json:"remark_name"`
RoomID string `json:"room_id"`
}
// GroupSearchResult 群聊搜索结果
type GroupSearchResult struct {
RoomID string `json:"room_id"`
RoomName string `json:"room_name"`
RoomAvatar string `json:"room_avatar"`
OwnerID string `json:"owner_id"`
MemberCount int `json:"member_count"`
}
// MessageSearchResult 消息搜索结果
type MessageSearchResult struct {
ID uint `json:"id"`
RoomID string `json:"room_id"`
RoomName string `json:"room_name"`
Content string `json:"content"`
MessageType int `json:"message_type"`
CreatedAt string `json:"created_at"`
Sender *model.User `json:"sender"`
IsGroupChat bool `json:"is_group_chat"`
MatchContent string `json:"match_content"`
}
/**
* GlobalSearch
* 功能:全局聚合搜索(联系人、群聊、聊天记录)
* @param userID 当前用户ID
* @param keyword 搜索关键词
* @param searchType 搜索类型all, contacts, groups, messages
* @param limit 每类结果的最大数量
*/
func (s *SearchService) GlobalSearch(userID, keyword, searchType string, limit int) (*SearchResult, error) {
result := &SearchResult{
Contacts: []ContactSearchResult{},
Groups: []GroupSearchResult{},
Messages: []MessageSearchResult{},
}
if keyword == "" {
return result, nil
}
keyword = strings.TrimSpace(keyword)
likeKeyword := "%" + keyword + "%"
// 搜索联系人
if searchType == "all" || searchType == "contacts" {
contacts, err := s.searchContacts(userID, likeKeyword, limit)
if err == nil {
result.Contacts = contacts
}
}
// 搜索群聊
if searchType == "all" || searchType == "groups" {
groups, err := s.searchGroups(userID, likeKeyword, limit)
if err == nil {
result.Groups = groups
}
}
// 搜索聊天记录
if searchType == "all" || searchType == "messages" {
messages, err := s.searchMessages(userID, likeKeyword, limit)
if err == nil {
result.Messages = messages
}
}
return result, nil
}
/**
* searchContacts
* 功能:搜索联系人(好友)
* 搜索字段:用户名、备注名、邮箱、手机号
*/
func (s *SearchService) searchContacts(userID, likeKeyword string, limit int) ([]ContactSearchResult, error) {
var contacts []model.UserContact
var results []ContactSearchResult
// 查询当前用户的好友
err := s.DB.Where("user_id = ?", userID).Find(&contacts).Error
if err != nil {
return results, err
}
// 获取所有好友的用户ID
contactIDs := make([]string, 0, len(contacts))
contactMap := make(map[string]model.UserContact)
for _, c := range contacts {
contactIDs = append(contactIDs, c.ContactID)
contactMap[c.ContactID] = c
}
if len(contactIDs) == 0 {
return results, nil
}
// 查询匹配的用户
var users []model.User
err = s.DB.Where("id IN ?", contactIDs).
Where("name LIKE ? OR email LIKE ? OR phone LIKE ?", likeKeyword, likeKeyword, likeKeyword).
Limit(limit).
Find(&users).Error
if err != nil {
return results, err
}
// 组装结果
for _, user := range users {
contact := contactMap[user.ID]
userCopy := user
results = append(results, ContactSearchResult{
User: &userCopy,
RemarkName: contact.RemarkName,
RoomID: contact.RoomID,
})
}
// 同时搜索备注名匹配的联系人
var remarkContacts []model.UserContact
err = s.DB.Where("user_id = ? AND remark_name LIKE ?", userID, likeKeyword).
Limit(limit).
Find(&remarkContacts).Error
if err == nil {
remarkIDs := make([]string, 0)
existingIDs := make(map[string]bool)
for _, r := range results {
existingIDs[r.User.ID] = true
}
for _, c := range remarkContacts {
if !existingIDs[c.ContactID] {
remarkIDs = append(remarkIDs, c.ContactID)
}
}
if len(remarkIDs) > 0 {
var remarkUsers []model.User
s.DB.Where("id IN ?", remarkIDs).Find(&remarkUsers)
userMap := make(map[string]model.User)
for _, u := range remarkUsers {
userMap[u.ID] = u
}
for _, c := range remarkContacts {
if u, ok := userMap[c.ContactID]; ok && !existingIDs[c.ContactID] {
userCopy := u
results = append(results, ContactSearchResult{
User: &userCopy,
RemarkName: c.RemarkName,
RoomID: c.RoomID,
})
}
}
}
}
return results, nil
}
/**
* searchGroups
* 功能:搜索群聊
* 搜索字段:群名称
*/
func (s *SearchService) searchGroups(userID, likeKeyword string, limit int) ([]GroupSearchResult, error) {
var results []GroupSearchResult
// 获取用户所在的群聊
var members []model.RoomMember
err := s.DB.Where("user_id = ?", userID).Find(&members).Error
if err != nil {
return results, err
}
roomIDs := make([]string, 0, len(members))
for _, m := range members {
if strings.HasPrefix(m.RoomID, "group_") {
roomIDs = append(roomIDs, m.RoomID)
}
}
if len(roomIDs) == 0 {
return results, nil
}
// 查询匹配的群聊
var rooms []model.ChatRoom
err = s.DB.Where("room_id IN ? AND room_type = ? AND room_name LIKE ?", roomIDs, "group", likeKeyword).
Limit(limit).
Find(&rooms).Error
if err != nil {
return results, err
}
// 获取每个群的成员数量
for _, room := range rooms {
var memberCount int64
s.DB.Model(&model.RoomMember{}).Where("room_id = ?", room.RoomID).Count(&memberCount)
results = append(results, GroupSearchResult{
RoomID: room.RoomID,
RoomName: room.RoomName,
RoomAvatar: room.RoomAvatar,
OwnerID: room.OwnerID,
MemberCount: int(memberCount),
})
}
return results, nil
}
/**
* searchMessages
* 功能:搜索聊天记录(仅文本消息)
* 搜索字段:消息内容
*/
func (s *SearchService) searchMessages(userID, likeKeyword string, limit int) ([]MessageSearchResult, error) {
var results []MessageSearchResult
// 获取用户有权限查看的房间
var contacts []model.UserContact
s.DB.Where("user_id = ?", userID).Find(&contacts)
var members []model.RoomMember
s.DB.Where("user_id = ?", userID).Find(&members)
roomIDs := make([]string, 0)
for _, c := range contacts {
if c.RoomID != "" {
roomIDs = append(roomIDs, c.RoomID)
}
}
for _, m := range members {
roomIDs = append(roomIDs, m.RoomID)
}
if len(roomIDs) == 0 {
return results, nil
}
// 查询匹配的消息(仅文本消息)
var messages []model.ChatMessage
err := s.DB.Where("room_id IN ? AND message_type = ? AND content LIKE ?", roomIDs, 0, likeKeyword).
Order("created_at DESC").
Limit(limit).
Find(&messages).Error
if err != nil {
return results, err
}
// 获取发送者信息和房间信息
senderIDs := make([]string, 0)
roomIDSet := make(map[string]bool)
for _, m := range messages {
senderIDs = append(senderIDs, m.SenderUserID)
roomIDSet[m.RoomID] = true
}
var users []model.User
if len(senderIDs) > 0 {
s.DB.Where("id IN ?", senderIDs).Find(&users)
}
userMap := make(map[string]*model.User)
for i := range users {
userMap[users[i].ID] = &users[i]
}
uniqueRoomIDs := make([]string, 0, len(roomIDSet))
for rid := range roomIDSet {
uniqueRoomIDs = append(uniqueRoomIDs, rid)
}
var rooms []model.ChatRoom
if len(uniqueRoomIDs) > 0 {
s.DB.Where("room_id IN ?", uniqueRoomIDs).Find(&rooms)
}
roomMap := make(map[string]*model.ChatRoom)
for i := range rooms {
roomMap[rooms[i].RoomID] = &rooms[i]
}
for _, msg := range messages {
room := roomMap[msg.RoomID]
roomName := ""
isGroupChat := false
if room != nil {
roomName = room.RoomName
isGroupChat = room.RoomType == "group"
}
matchContent := msg.Content
if len(matchContent) > 100 {
matchContent = matchContent[:100] + "..."
}
results = append(results, MessageSearchResult{
ID: msg.ID,
RoomID: msg.RoomID,
RoomName: roomName,
Content: msg.Content,
MessageType: msg.MessageType,
CreatedAt: msg.CreatedAt.Format("2006-01-02 15:04:05"),
Sender: userMap[msg.SenderUserID],
IsGroupChat: isGroupChat,
MatchContent: matchContent,
})
}
return results, nil
}