package models import ( "time" "gorm.io/gorm" ) // ArticleView 表示一条唯一的文章浏览记录。 // 每条记录跟踪一次(按 IP 或用户)对文章的唯一访问。 type ArticleView struct { ID uint `gorm:"primarykey" json:"id"` CreatedAt time.Time `json:"created_at"` ArticleID uint `gorm:"not null;index:idx_article_views_article" json:"article_id"` UserID *uint `gorm:"index:idx_article_views_user" json:"user_id"` // 匿名用户为 NULL IP string `gorm:"size:64;not null;index:idx_article_views_ip" json:"ip"` UserAgent string `gorm:"size:512" json:"user_agent"` IsBot bool `gorm:"default:false;index:idx_article_views_bot" json:"is_bot"` Article Article `gorm:"foreignKey:ArticleID" json:"-"` User *User `gorm:"foreignKey:UserID" json:"-"` } // TableName 覆盖 GORM 默认的表名。 func (ArticleView) TableName() string { return "article_views" } // BeforeCreate 钩子确保不会创建重复记录。 // 这是在应用层去重之外的另一道安全校验。 func (av *ArticleView) BeforeCreate(tx *gorm.DB) error { var count int64 query := tx.Model(&ArticleView{}). Where("article_id = ? AND ip = ?", av.ArticleID, av.IP) if av.UserID != nil { query = query.Where("user_id = ?", *av.UserID) } else { query = query.Where("user_id IS NULL") } query.Count(&count) if count > 0 { // 记录已存在,跳过创建 return gorm.ErrDuplicatedKey } return nil }