feat: @搜索优先展示已关注用户,结果限制最多5条

- SearchUsersByLevel SQL新增LEFT JOIN user_follows,按已关注优先+exp降序
- SearchUsers/followerUID参数贯穿repo→service→controller
- 继续输入时前端自动重新搜索新关键词
This commit is contained in:
2026-06-03 01:15:38 +08:00
parent fb9724faed
commit 864a488ee0
5 changed files with 21 additions and 15 deletions

View File

@ -41,7 +41,7 @@ func (ctrl *CommentController) Delete(c *gin.Context) {
// SearchUsers GET /api/users/search?q=keyword // SearchUsers GET /api/users/search?q=keyword
func (ctrl *CommentController) SearchUsers(c *gin.Context) { func (ctrl *CommentController) SearchUsers(c *gin.Context) {
_, _, ok := common.GetGinUser(c) uid, _, ok := common.GetGinUser(c)
if !ok { if !ok {
common.Error(c, http.StatusUnauthorized, "请先登录") common.Error(c, http.StatusUnauthorized, "请先登录")
return return
@ -53,7 +53,7 @@ func (ctrl *CommentController) SearchUsers(c *gin.Context) {
return return
} }
users, err := ctrl.commentService.SearchUsers(q) users, err := ctrl.commentService.SearchUsers(q, uid)
if err != nil { if err != nil {
common.Error(c, http.StatusInternalServerError, "搜索用户失败") common.Error(c, http.StatusInternalServerError, "搜索用户失败")
return return

View File

@ -115,7 +115,7 @@ type commentUseCase interface {
Delete(commentID uint, requesterID uint, isAdmin bool) error Delete(commentID uint, requesterID uint, isAdmin bool) error
ListRootComments(postID uint, page, pageSize int) ([]model.Comment, int64, error) ListRootComments(postID uint, page, pageSize int) ([]model.Comment, int64, error)
ListReplies(rootID uint) ([]model.Comment, error) ListReplies(rootID uint) ([]model.Comment, error)
SearchUsers(keyword string) ([]model.UserSearchResult, error) SearchUsers(keyword string, followerUID uint) ([]model.UserSearchResult, error)
} }
// reactionUseCase ReactionController 对 ReactionService 的最小依赖ISP4 个方法) // reactionUseCase ReactionController 对 ReactionService 的最小依赖ISP4 个方法)

View File

@ -67,7 +67,8 @@ func (r *CommentRepo) LoadMentionsForComments(comments []model.Comment) error {
} }
// SearchUsersByLevel 搜索指定等级及以上用户(用于@艾特),返回 uid/username/avatar/exp // SearchUsersByLevel 搜索指定等级及以上用户(用于@艾特),返回 uid/username/avatar/exp
func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel int, limit int) ([]struct { // followerUID > 0 时按已关注优先排序其次按exp降序
func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel, limit int, followerUID uint) ([]struct {
UID uint UID uint
Username string Username string
Avatar string Avatar string
@ -83,12 +84,17 @@ func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel int, limit int
threshold := model.LevelThresholds[minLevel] threshold := model.LevelThresholds[minLevel]
err := r.db.Table("users"). q := r.db.Table("users AS u").
Select("id AS uid, username, avatar, exp"). Select("u.id AS uid, u.username, u.avatar, u.exp").
Where("username LIKE ? AND exp >= ? AND deleted_at IS NULL", "%"+keyword+"%", threshold). Where("u.username LIKE ? AND u.exp >= ? AND u.deleted_at IS NULL", "%"+keyword+"%", threshold).
Order("exp DESC"). Order("u.exp DESC")
Limit(limit).
Find(&list).Error if followerUID > 0 {
q = q.Joins("LEFT JOIN user_follows f ON f.followee_id = u.id AND f.follower_id = ?", followerUID).
Order("(f.follower_id IS NOT NULL) DESC, u.exp DESC")
}
err := q.Limit(limit).Find(&list).Error
var res []struct { var res []struct {
UID uint UID uint

View File

@ -190,9 +190,9 @@ func (s *CommentService) ListAllComments(keyword string, showDeleted bool, page,
return rows, total, err return rows, total, err
} }
// SearchUsers 搜索LV2+用户(用于@艾特悬浮窗) // SearchUsers 搜索LV2+用户(用于@艾特悬浮窗)已关注优先最多5条
func (s *CommentService) SearchUsers(keyword string) ([]model.UserSearchResult, error) { func (s *CommentService) SearchUsers(keyword string, followerUID uint) ([]model.UserSearchResult, error) {
rows, err := s.repo.SearchUsersByLevel(keyword, 2, 10) rows, err := s.repo.SearchUsersByLevel(keyword, 2, 5, followerUID)
if err != nil { if err != nil {
return nil, fmt.Errorf("搜索用户失败: %w", err) return nil, fmt.Errorf("搜索用户失败: %w", err)
} }
@ -219,7 +219,7 @@ func (s *CommentService) parseMentions(commentID uint, body string) {
continue continue
} }
seen[username] = true seen[username] = true
rows, _ := s.repo.SearchUsersByLevel(username, 2, 1) rows, _ := s.repo.SearchUsersByLevel(username, 2, 1, 0)
if len(rows) > 0 { if len(rows) > 0 {
_ = s.repo.CreateMention(&model.CommentMention{ _ = s.repo.CreateMention(&model.CommentMention{
CommentID: commentID, CommentID: commentID,

View File

@ -155,7 +155,7 @@ type commentStore interface {
CreateMention(mention *model.CommentMention) error CreateMention(mention *model.CommentMention) error
FindPostIDByComment(commentID uint) (uint, error) FindPostIDByComment(commentID uint) (uint, error)
FindPostAuthorID(postID uint) (uint, error) FindPostAuthorID(postID uint) (uint, error)
SearchUsersByLevel(keyword string, minLevel, limit int) ([]struct { SearchUsersByLevel(keyword string, minLevel, limit int, followerUID uint) ([]struct {
UID uint UID uint
Username string Username string
Avatar string Avatar string