feat: 新增文章阅读量计数(已登录去重 + 访客防刷)
- Post 模型新增 ViewsCount 冗余计数字段 - 新建 PostReadLog 表(user_id + post_id 唯一索引),已登录用户每篇文章只计一次 - 新建 PostGuestReadLog 表(visitor_id + post_id 唯一索引),访客基于 Cookie 标识去重 - Repository 层新增 RecordRead / RecordGuestRead / IncrementViewsCount - Service 层编排去重-计数逻辑,计数失败不影响页面渲染 - Controller 层 ShowPage + ShowAPI 触发计数,仅 approved 状态生效 - 双层防刷:唯一索引去重 + 2 秒冷却间隔 - 访客 Cookie (visitor_id) 基于 crypto/rand 生成,30 天有效期
This commit is contained in:
@ -1,9 +1,12 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"metazone.cc/metalab/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// PostRepo 帖子数据访问
|
||||
@ -189,3 +192,52 @@ func (r *PostRepo) Restore(id uint) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readCooldown 两次阅读记录最小间隔
|
||||
const readCooldown = 2 * time.Second
|
||||
|
||||
// RecordRead 记录已登录用户阅读文章(防刷量:取去重+冷却)
|
||||
// 返回 true 表示首次阅读(新记录),false 表示重复或冷却中
|
||||
func (r *PostRepo) RecordRead(userID, postID uint) (bool, error) {
|
||||
// 冷却检查:同用户两次阅读之间至少间隔 2 秒
|
||||
var lastRead model.PostReadLog
|
||||
if err := r.db.Where("user_id = ?", userID).
|
||||
Order("read_at DESC").First(&lastRead).Error; err == nil {
|
||||
if time.Since(lastRead.ReadAt) < readCooldown {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
result := r.db.Clauses(clause.OnConflict{DoNothing: true}).
|
||||
Create(&model.PostReadLog{UserID: userID, PostID: postID})
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
return result.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// RecordGuestRead 记录访客阅读文章(基于 Cookie visitor_id 去重+冷却)
|
||||
// 返回 true 表示首次阅读(新记录),false 表示重复或冷却中
|
||||
func (r *PostRepo) RecordGuestRead(visitorID string, postID uint) (bool, error) {
|
||||
// 冷却检查:同访客两次阅读之间至少间隔 2 秒
|
||||
var lastRead model.PostGuestReadLog
|
||||
if err := r.db.Where("visitor_id = ?", visitorID).
|
||||
Order("read_at DESC").First(&lastRead).Error; err == nil {
|
||||
if time.Since(lastRead.ReadAt) < readCooldown {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
result := r.db.Clauses(clause.OnConflict{DoNothing: true}).
|
||||
Create(&model.PostGuestReadLog{VisitorID: visitorID, PostID: postID})
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
return result.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// IncrementViewsCount 阅读数 +1
|
||||
func (r *PostRepo) IncrementViewsCount(postID uint) error {
|
||||
return r.db.Model(&model.Post{}).Where("id = ?", postID).
|
||||
Update("views_count", gorm.Expr("views_count + 1")).Error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user