fix: 评论系统全量检查修复(7 项 Gap 全部修复)

- Delete 权限:新增 requesterID/isAdmin 参数,Service 层鉴权(仅本人/管理员可删)
- 通知内容:改用 GetUsernameByID 显示真实用户名,非数字 UID
- SearchUsers:Repository 返回 exp,Service 调用 GetLevelByExp 计算等级
- 通知高亮:Notification 新增 CommentID 字段,消息链接支持 #comment-{id}
- 已删除提示:前端 hash 检测 + 3 秒超时 Toast "该评论已不存在"
- Admin CSS:移除不存在的 comments.css 引用
- 错误哨兵:改用 common.ErrCommentNotFound/ErrCommentEmpty/PermissionDenied
This commit is contained in:
2026-06-01 14:05:47 +08:00
parent 36c571b6bb
commit 43c35ddaf2
12 changed files with 119 additions and 45 deletions

View File

@ -28,7 +28,7 @@ func NewCommentService(repo commentStore, notifier commentNotifier) *CommentServ
// CreateRoot 创建顶级评论
func (s *CommentService) CreateRoot(userID uint, postID uint, body string) (*model.Comment, error) {
if strings.TrimSpace(body) == "" {
return nil, errors.New("评论内容不能为空")
return nil, common.ErrCommentEmpty
}
postAuthorID, err := s.repo.FindPostAuthorID(postID)
@ -60,11 +60,14 @@ func (s *CommentService) CreateRoot(userID uint, postID uint, body string) (*mod
// 通知文章作者(评论者 ≠ 作者RelatedID 存 postID 以便消息页生成跳转链接
if s.notifier != nil && userID != postAuthorID {
commenterUID, _ := s.repo.GetUserUIDByID(userID)
commenterName, _ := s.repo.GetUsernameByID(userID)
if commenterName == "" {
commenterName = "用户"
}
s.notifier.Create(postAuthorID, model.NotifyComment,
"新评论",
fmt.Sprintf("%s 评论了你的文章", commenterUID),
&comment.PostID)
fmt.Sprintf("%s 评论了你的文章", commenterName),
&comment.PostID, &comment.ID)
}
return comment, nil
@ -73,13 +76,13 @@ func (s *CommentService) CreateRoot(userID uint, postID uint, body string) (*mod
// CreateReply 创建回复
func (s *CommentService) CreateReply(userID uint, parentID uint, body string) (*model.Comment, error) {
if strings.TrimSpace(body) == "" {
return nil, errors.New("回复内容不能为空")
return nil, common.ErrCommentEmpty
}
parent, err := s.repo.FindByID(parentID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("父评论不存在")
return nil, common.ErrCommentNotFound
}
return nil, fmt.Errorf("查询父评论失败: %w", err)
}
@ -108,22 +111,25 @@ func (s *CommentService) CreateReply(userID uint, parentID uint, body string) (*
// 通知被回复者不自己回复自己RelatedID 存 postID 以便消息页生成跳转链接
if s.notifier != nil && parent.UserID != userID {
commenterUID, _ := s.repo.GetUserUIDByID(userID)
commenterName, _ := s.repo.GetUsernameByID(userID)
if commenterName == "" {
commenterName = "用户"
}
s.notifier.Create(parent.UserID, model.NotifyCommentReply,
"新回复",
fmt.Sprintf("%s 回复了你的评论", commenterUID),
&parent.PostID)
fmt.Sprintf("%s 回复了你的评论", commenterName),
&parent.PostID, &comment.ID)
}
return comment, nil
}
// Delete 软删除评论
func (s *CommentService) Delete(commentID uint) error {
// Delete 软删除评论requesterID 为请求者isAdmin 表示是否为管理员)
func (s *CommentService) Delete(commentID uint, requesterID uint, isAdmin bool) error {
comment, err := s.repo.FindByID(commentID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("评论不存在")
return common.ErrCommentNotFound
}
return fmt.Errorf("查询评论失败: %w", err)
}
@ -132,6 +138,11 @@ func (s *CommentService) Delete(commentID uint) error {
return nil
}
// 权限检查:仅本人或管理员可删除
if !isAdmin && comment.UserID != requesterID {
return common.ErrPermissionDenied
}
if err := s.repo.SoftDelete(commentID); err != nil {
return fmt.Errorf("删除评论失败: %w", err)
}
@ -182,7 +193,7 @@ func (s *CommentService) SearchUsers(keyword string) ([]model.UserSearchResult,
users[i] = model.UserSearchResult{
UID: row.UID,
Username: row.Username,
Level: 0,
Level: model.GetLevelByExp(row.Exp),
}
}
return users, nil

View File

@ -84,7 +84,7 @@ func (s *LevelService) AddExp(userID uint, delta int) (newExp int, levelUp bool,
_ = s.notifSvc.Create(userID, model.NotifyLevelUp,
"等级提升",
fmt.Sprintf("恭喜您!您的等级已提升至 %s", model.GetLevelName(newLevel)),
nil)
nil, nil)
}
}

View File

@ -26,7 +26,7 @@ func NewNotificationService(repo notifRepo) *NotificationService {
}
// Create 创建消息
func (s *NotificationService) Create(userID uint, notifyType, title, content string, relatedID *uint) error {
func (s *NotificationService) Create(userID uint, notifyType, title, content string, relatedID, commentID *uint) error {
n := &model.Notification{
UserID: userID,
NotifyType: notifyType,
@ -34,6 +34,7 @@ func (s *NotificationService) Create(userID uint, notifyType, title, content str
Content: content,
IsRead: false,
RelatedID: relatedID,
CommentID: commentID,
}
return s.repo.Create(n)
}
@ -92,7 +93,7 @@ func (s *NotificationService) NotifyAuditApproved(userID uint, auditType string,
_ = s.Create(userID, model.NotifyAuditApproved,
"审核已通过",
"你的"+typeName+"修改已通过审核",
&relatedID)
&relatedID, nil)
}
// NotifyAuditRejected 审核拒绝通知
@ -108,14 +109,14 @@ func (s *NotificationService) NotifyAuditRejected(userID uint, auditType, reason
_ = s.Create(userID, model.NotifyAuditRejected,
"审核未通过",
content,
&relatedID)
&relatedID, nil)
}
// NotifyPostApproved 稿件审核通过通知
func (s *NotificationService) NotifyPostApproved(userID uint, postTitle string, postID uint) {
title := "稿件审核通过"
content := "你的稿件《" + postTitle + "》已通过审核,现已公开发布"
_ = s.Create(userID, model.NotifyPostApproved, title, content, &postID)
_ = s.Create(userID, model.NotifyPostApproved, title, content, &postID, nil)
}
// NotifyPostRejected 稿件审核拒绝通知
@ -125,5 +126,5 @@ func (s *NotificationService) NotifyPostRejected(userID uint, postTitle, reason
if reason != "" {
content += ",理由:" + reason
}
_ = s.Create(userID, model.NotifyPostRejected, title, content, &postID)
_ = s.Create(userID, model.NotifyPostRejected, title, content, &postID, nil)
}

View File

@ -86,7 +86,7 @@ type taskStore interface {
// levelNotifier 等级升级通知接口
type levelNotifier interface {
Create(userID uint, notifyType, title, content string, relatedID *uint) error
Create(userID uint, notifyType, title, content string, relatedID, commentID *uint) error
}
// EnergyStore EnergyService 所需的完整仓储接口
@ -113,7 +113,7 @@ type energyStore = EnergyStore
// energyNotifier EnergyService 所需的通知接口ISP
type energyNotifier interface {
Create(userID uint, notifyType, title, content string, relatedID *uint) error
Create(userID uint, notifyType, title, content string, relatedID, commentID *uint) error
}
// commentStore CommentService 所需的最小仓储接口ISP
@ -132,12 +132,14 @@ type commentStore interface {
SearchUsersByLevel(keyword string, minLevel, limit int) ([]struct {
UID string
Username string
Exp int
}, error)
GetUserUIDByID(userID uint) (string, error)
GetUsernameByID(userID uint) (string, error)
ListAllComments(keyword string, showDeleted bool, offset, limit int) ([]model.AdminCommentRow, int64, error)
}
// commentNotifier CommentService 所需的通知接口ISP
type commentNotifier interface {
Create(userID uint, notifyType, title, content string, relatedID *uint) error
Create(userID uint, notifyType, title, content string, relatedID, commentID *uint) error
}