- 评论区有效@提及渲染为蓝色链接跳转/space/{uid},无效@保持纯文本
- 输入@触发悬浮窗,根据输入实时搜索用户(含头像+等级)
- 无匹配时显示未搜索到用户,选中后插入为@用户名 格式(尾部空格)
- 悬浮窗定位于输入光标上方,不遮挡输入内容
- 后端Comment新增Mentions字段,批量加载提及映射供前端判断
- SearchUsers/parseMentions 统一限制LV2+用户
- 所有默认头像由首字符文字改为统一SVG人形图标(nav/home/space/mention等7处)
175 lines
5.3 KiB
Go
175 lines
5.3 KiB
Go
package repository
|
||
|
||
import (
|
||
"metazone.cc/metalab/internal/model"
|
||
"metazone.cc/metalab/internal/service"
|
||
)
|
||
|
||
// CreateMention 创建@提及记录
|
||
func (r *CommentRepo) CreateMention(mention *model.CommentMention) error {
|
||
return r.db.Create(mention).Error
|
||
}
|
||
|
||
// FindMentionsByComment 查询某条评论的@提及列表
|
||
func (r *CommentRepo) FindMentionsByComment(commentID uint) ([]model.CommentMention, error) {
|
||
var list []model.CommentMention
|
||
err := r.db.Where("comment_id = ?", commentID).Find(&list).Error
|
||
return list, err
|
||
}
|
||
|
||
// LoadMentionsForComments 批量加载多条评论的有效@提及映射(username -> uid)
|
||
// 传入的 comments 会被原地修改,填充 Mentions 字段
|
||
func (r *CommentRepo) LoadMentionsForComments(comments []model.Comment) error {
|
||
if len(comments) == 0 {
|
||
return nil
|
||
}
|
||
|
||
commentIDs := make([]uint, len(comments))
|
||
for i, c := range comments {
|
||
commentIDs[i] = c.ID
|
||
}
|
||
|
||
// 查询 comment_mentions JOIN users,拿到 username 和 uid
|
||
type mentionRow struct {
|
||
CommentID uint
|
||
UID uint
|
||
Username string
|
||
}
|
||
var rows []mentionRow
|
||
err := r.db.Table("comment_mentions").
|
||
Select("comment_mentions.comment_id, comment_mentions.uid, users.username").
|
||
Joins("LEFT JOIN users ON users.id = comment_mentions.uid").
|
||
Where("comment_mentions.comment_id IN ?", commentIDs).
|
||
Find(&rows).Error
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 按 comment_id 分组
|
||
mentionMap := make(map[uint]map[string]uint)
|
||
for _, row := range rows {
|
||
if mentionMap[row.CommentID] == nil {
|
||
mentionMap[row.CommentID] = make(map[string]uint)
|
||
}
|
||
mentionMap[row.CommentID][row.Username] = row.UID
|
||
}
|
||
|
||
for i := range comments {
|
||
if m, ok := mentionMap[comments[i].ID]; ok {
|
||
comments[i].Mentions = m
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// SearchUsersByLevel 搜索指定等级及以上用户(用于@艾特),返回 uid/username/avatar/exp
|
||
func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel int, limit int) ([]struct {
|
||
UID uint
|
||
Username string
|
||
Avatar string
|
||
Exp int
|
||
}, error) {
|
||
type result struct {
|
||
UID uint
|
||
Username string
|
||
Avatar string
|
||
Exp int
|
||
}
|
||
var list []result
|
||
|
||
threshold := model.LevelThresholds[minLevel]
|
||
|
||
err := r.db.Table("users").
|
||
Select("id, username, avatar, exp").
|
||
Where("username LIKE ? AND exp >= ? AND deleted_at IS NULL", "%"+keyword+"%", threshold).
|
||
Order("exp DESC").
|
||
Limit(limit).
|
||
Find(&list).Error
|
||
|
||
var res []struct {
|
||
UID uint
|
||
Username string
|
||
Avatar string
|
||
Exp int
|
||
}
|
||
for _, u := range list {
|
||
res = append(res, struct {
|
||
UID uint
|
||
Username string
|
||
Avatar string
|
||
Exp int
|
||
}{UID: u.UID, Username: u.Username, Avatar: u.Avatar, Exp: u.Exp})
|
||
}
|
||
return res, err
|
||
}
|
||
|
||
// GetUsernameByID 通过 userID 获取用户名(用于通知内容)
|
||
func (r *CommentRepo) GetUsernameByID(userID uint) (string, error) {
|
||
var username string
|
||
err := r.db.Table("users").Select("username").Where("id = ?", userID).Scan(&username).Error
|
||
return username, err
|
||
}
|
||
|
||
// FindPostIDByComment 查询评论所属的文章ID
|
||
func (r *CommentRepo) FindPostIDByComment(commentID uint) (uint, error) {
|
||
var postID uint
|
||
err := r.db.Table("comments").Select("post_id").Where("id = ?", commentID).Scan(&postID).Error
|
||
return postID, err
|
||
}
|
||
|
||
// UpdateRootID 更新评论的 root_id 字段(顶级评论创建后回填)
|
||
func (r *CommentRepo) UpdateRootID(id, rootID uint) error {
|
||
return r.db.Model(&model.Comment{}).Where("id = ?", id).Update("root_id", rootID).Error
|
||
}
|
||
|
||
// FindPostAuthorID 查询文章的作者 user_id
|
||
func (r *CommentRepo) FindPostAuthorID(postID uint) (uint, error) {
|
||
var authorID uint
|
||
err := r.db.Table("posts").Select("user_id").Where("id = ?", postID).Scan(&authorID).Error
|
||
return authorID, err
|
||
}
|
||
|
||
// AggregateCommentsByAuthor 按日期聚合评论量(作者所有文章在时间段内的评论)
|
||
func (r *CommentRepo) AggregateCommentsByAuthor(userID uint, since string) ([]service.TrendPoint, error) {
|
||
var results []service.TrendPoint
|
||
err := r.db.Table("comments").
|
||
Select("TO_CHAR(comments.created_at, 'YYYY-MM-DD') AS date, COUNT(*) AS value").
|
||
Joins("JOIN posts ON posts.id = comments.post_id").
|
||
Where("posts.user_id = ? AND comments.created_at >= ?", userID, since).
|
||
Group("TO_CHAR(comments.created_at, 'YYYY-MM-DD')").
|
||
Order("date ASC").
|
||
Scan(&results).Error
|
||
if results == nil {
|
||
results = []service.TrendPoint{}
|
||
}
|
||
return results, err
|
||
}
|
||
|
||
// ListAllComments 后台评论列表(支持关键词搜索、分页)
|
||
func (r *CommentRepo) ListAllComments(keyword string, showDeleted bool, offset, limit int) ([]model.AdminCommentRow, int64, error) {
|
||
var total int64
|
||
q := r.db.Table("comments").
|
||
Joins("LEFT JOIN users ON users.id = comments.user_id").
|
||
Joins("LEFT JOIN posts ON posts.id = comments.post_id")
|
||
|
||
if !showDeleted {
|
||
q = q.Where("comments.is_deleted = false")
|
||
}
|
||
if keyword != "" {
|
||
like := "%" + keyword + "%"
|
||
q = q.Where("comments.body LIKE ? OR users.username LIKE ? OR posts.title LIKE ?", like, like, like)
|
||
}
|
||
|
||
if err := q.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
var rows []model.AdminCommentRow
|
||
err := q.
|
||
Select("comments.id, comments.post_id, posts.title as post_title, comments.body, users.username as author_name, comments.is_deleted, comments.created_at, comments.updated_at").
|
||
Order("comments.created_at DESC").
|
||
Offset(offset).Limit(limit).
|
||
Scan(&rows).Error
|
||
return rows, total, err
|
||
}
|