feat: 评论/帖子编辑历史与编辑标记,内容锁定

- 新增 smartDiff 差异计算与 EditHistoryModal 编辑历史弹窗,替换原 CommentEditHistoryModal
- 新增 PostEditedMark 编辑标记展示
- 新增内容锁定机制(content_lock)防止并发编辑冲突
- 审核与通知服务适配
This commit is contained in:
2026-09-25 23:41:45 +08:00
parent cab8230803
commit 5270fee2b7
40 changed files with 2521 additions and 350 deletions

View File

@@ -81,6 +81,7 @@ type AdminContentComment struct {
BoardID uint `json:"board_id"`
Content string `json:"content"`
Status string `json:"status"`
Edited bool `json:"edited"` // 相对创建已编辑(同评论侧规则),用于隐藏无修订的历史入口
Deleted bool `json:"deleted"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
@@ -223,13 +224,14 @@ func (s *ModerationService) ListAdminComments(actor *Actor, status, keyword stri
Content string
Status string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt
BoardID uint
PostTitle string
UserID uint
}
var rows []row
if err := q.Select("comments.id, comments.post_id, comments.content, comments.status, comments.created_at, comments.deleted_at, posts.board_id, posts.title AS post_title, comments.user_id").
if err := q.Select("comments.id, comments.post_id, comments.content, comments.status, comments.created_at, comments.updated_at, comments.deleted_at, posts.board_id, posts.title AS post_title, comments.user_id").
Order("comments.id DESC").
Offset((page - 1) * adminContentPageSize).Limit(adminContentPageSize).
Scan(&rows).Error; err != nil {
@@ -276,6 +278,7 @@ func (s *ModerationService) ListAdminComments(actor *Actor, status, keyword stri
BoardID: r.BoardID,
Content: r.Content,
Status: r.Status,
Edited: r.UpdatedAt.Sub(r.CreatedAt) > time.Minute,
CreatedAt: r.CreatedAt,
Board: boards[r.BoardID],
User: users[r.UserID],
@@ -354,7 +357,8 @@ func (s *ModerationService) SoftDeletePost(actor *Actor, id uint, deleteType, de
"delete_reason": deleteReason,
"deleted_by": actor.ID,
}
if err := s.db.Model(post).Updates(updates).Error; err != nil {
// UpdateColumns:删除元数据非内容编辑,不刷新 updated_at(详情页「更新于」由该字段推导)
if err := s.db.Model(post).UpdateColumns(updates).Error; err != nil {
return 0, err
}
if err := s.db.Delete(post).Error; err != nil {
@@ -375,7 +379,8 @@ func (s *ModerationService) RestorePost(actor *Actor, id uint) error {
if !post.DeletedAt.Valid {
return errors.New("帖子未被删除")
}
return s.db.Unscoped().Model(&model.Post{}).Where("id = ?", id).Update("deleted_at", nil).Error
// UpdateColumn:恢复非内容编辑,不刷新 updated_at
return s.db.Unscoped().Model(&model.Post{}).Where("id = ?", id).UpdateColumn("deleted_at", nil).Error
}
// PurgePost 彻底删除已软删帖子及其评论(仅站长)
@@ -391,6 +396,9 @@ func (s *ModerationService) PurgePost(actor *Actor, id uint) error {
return ErrContentNotDeleted
}
return s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Where("post_id = ?", id).Delete(&model.PostEditHistory{}).Error; err != nil {
return err
}
if err := tx.Unscoped().Where("post_id = ?", id).Delete(&model.Comment{}).Error; err != nil {
return err
}
@@ -430,7 +438,8 @@ func (s *ModerationService) SoftDeleteComment(actor *Actor, id uint, deleteType,
"delete_reason": deleteReason,
"deleted_by": actor.ID,
}
if err := s.db.Model(cm).Updates(updates).Error; err != nil {
// UpdateColumns:删除元数据非内容编辑,不刷新 updated_at(该字段用于 edited 判定)
if err := s.db.Model(cm).UpdateColumns(updates).Error; err != nil {
return err
}
if err := s.db.Delete(cm).Error; err != nil {
@@ -461,7 +470,8 @@ func (s *ModerationService) RestoreComment(actor *Actor, id uint) error {
"delete_reason": "",
"deleted_by": 0,
}
if err := s.db.Unscoped().Model(&model.Comment{}).Where("id = ?", id).Updates(updates).Error; err != nil {
// UpdateColumns:恢复非内容编辑,不刷新 updated_at(该字段用于 edited 判定)
if err := s.db.Unscoped().Model(&model.Comment{}).Where("id = ?", id).UpdateColumns(updates).Error; err != nil {
return err
}
if cm.Status == model.ContentStatusPublished {