- 48 个 Go 文件所有注释(行注释/块注释/行尾注释,含 _test.go)翻译为中文 - 保留技术标识符:SECURITY_TODO(n)、unsafe-inline、sqlite/mysql、路由参数等 - 代码、字符串字面量、日志消息保持英文原文,零逻辑改动 - go build/vet 通过,go test -count=1 ./... 全绿
105 lines
3.2 KiB
Go
105 lines
3.2 KiB
Go
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]))
|
|
}
|