feat(comments): 软删占位、理由与恢复,管理删仅对访客打码

自删/管理删差异文案与理由可见性,评论者与管理员免马赛克,并支持恢复与处理人链接。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 07:43:48 +08:00
parent 5052bcd805
commit c6fdbb7193
11 changed files with 941 additions and 141 deletions

View File

@@ -366,8 +366,8 @@ func (s *ModerationService) PurgePost(actor *Actor, id uint) error {
})
}
// SoftDeleteComment 软删单条评论(不级联子树,后台点对点清理)
func (s *ModerationService) SoftDeleteComment(actor *Actor, id uint) error {
// SoftDeleteComment 软删单条评论(不级联子树);须选类型,「其他」才须填理由
func (s *ModerationService) SoftDeleteComment(actor *Actor, id uint, deleteType, deleteReason string) error {
cm, _, err := s.loadCommentUnscoped(actor, id)
if err != nil {
return err
@@ -375,6 +375,29 @@ func (s *ModerationService) SoftDeleteComment(actor *Actor, id uint) error {
if cm.DeletedAt.Valid {
return errors.New("评论已在回收站")
}
deleteType = strings.TrimSpace(deleteType)
deleteReason = strings.TrimSpace(deleteReason)
if !model.ValidPostDeleteType(deleteType) {
return errors.New("无效的删除类型")
}
if deleteType == model.PostDeleteTypeOther {
if deleteReason == "" {
return errors.New("请填写删除理由")
}
if len([]rune(deleteReason)) > 200 {
return errors.New("删除理由不能超过 200 字")
}
} else {
deleteReason = ""
}
updates := map[string]interface{}{
"delete_type": deleteType,
"delete_reason": deleteReason,
"deleted_by": actor.ID,
}
if err := s.db.Model(cm).Updates(updates).Error; err != nil {
return err
}
if err := s.db.Delete(cm).Error; err != nil {
return err
}
@@ -385,7 +408,7 @@ func (s *ModerationService) SoftDeleteComment(actor *Actor, id uint) error {
return nil
}
// RestoreComment 恢复单条评论
// RestoreComment 恢复单条评论,并清空删除元数据
func (s *ModerationService) RestoreComment(actor *Actor, id uint) error {
cm, _, err := s.loadCommentUnscoped(actor, id)
if err != nil {
@@ -394,7 +417,13 @@ func (s *ModerationService) RestoreComment(actor *Actor, id uint) error {
if !cm.DeletedAt.Valid {
return errors.New("评论未被删除")
}
if err := s.db.Unscoped().Model(&model.Comment{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
updates := map[string]interface{}{
"deleted_at": nil,
"delete_type": "",
"delete_reason": "",
"deleted_by": 0,
}
if err := s.db.Unscoped().Model(&model.Comment{}).Where("id = ?", id).Updates(updates).Error; err != nil {
return err
}
if cm.Status == model.ContentStatusPublished {