package models import ( "crypto/md5" "encoding/hex" "fmt" "strings" "time" "gorm.io/gorm" ) // 评论状态常量。 const ( CommentPending = 0 // 待审核 CommentApproved = 1 // 已通过 CommentRejected = 2 // 已拒绝(软拒绝;后台仍可查看,但前台不再显示) ) // Comment 表示读者对文章提交的一条评论。评论可通过 ParentID 进行嵌套, // 作者可以是已登录用户(UserID),也可以通过长期有效的 GuestToken Cookie // 标识的匿名访客。 type Comment struct { ID uint `gorm:"primarykey" json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` ArticleID uint `gorm:"not null;index" json:"article_id"` ParentID *uint `gorm:"index" json:"parent_id,omitempty"` // 作者标识:已登录用户使用 UserID;匿名访客使用随机的 // GuestToken(保存于 Cookie 中),以便后续页面加载时能查看 // 自己待审核/私密的评论。 UserID *uint `gorm:"index" json:"user_id,omitempty"` GuestToken string `gorm:"size:64;index" json:"-"` AuthorName string `gorm:"not null;size:64" json:"author_name"` Email string `gorm:"not null;size:255" json:"-"` EmailHash string `gorm:"size:64;index" json:"email_hash"` Website string `gorm:"size:255" json:"website,omitempty"` Content string `gorm:"type:text;not null" json:"content"` IsPrivate bool `gorm:"default:false;index" json:"is_private"` Status int `gorm:"default:0;index" json:"status"` IPAddress string `gorm:"size:64" json:"-"` UserAgent string `gorm:"size:512" json:"-"` Article Article `gorm:"foreignKey:ArticleID" json:"-"` } // TableName 覆盖 GORM 默认的表名。 func (Comment) TableName() string { return "comments" } // HashEmail 返回小写并去除空格后的邮箱地址的 md5 哈希值。 // 这是 Gravatar 所期望的格式。 func HashEmail(email string) string { sum := md5.Sum([]byte(strings.ToLower(strings.TrimSpace(email)))) return hex.EncodeToString(sum[:]) } // GravatarURL 根据此评论的邮箱哈希返回 Gravatar 头像 URL。 // 当没有对应的 Gravatar 头像时,回退到 "identicon" 默认头像。 func (c *Comment) GravatarURL(size int) string { if size <= 0 { size = 64 } hash := c.EmailHash if hash == "" { hash = HashEmail(c.Email) } return fmt.Sprintf("https://www.gravatar.com/avatar/%s?s=%d&d=identicon", hash, size) } // MaskedEmail 返回把本地部分部分遮盖后的邮箱,供后台列表展示: // 既能提示身份,又不暴露完整地址。 func (c *Comment) MaskedEmail() string { email := c.Email at := strings.LastIndex(email, "@") if at <= 0 { return email } local := email[:at] host := email[at:] if len(local) <= 2 { return string(local[0]) + "***" + host } return string(local[0]) + "***" + string(local[len(local)-1]) + host } // AuthorInitial 返回 AuthorName 的首个大写字符,用作禁用 Gravatar 时 // 的文本头像占位符。名称为空时返回 "?"。 func (c *Comment) AuthorInitial() string { name := strings.TrimSpace(c.AuthorName) if name == "" { return "?" } r := []rune(name) return strings.ToUpper(string(r[0])) }