Files
jiang13-bbs/backend/service/telemetry.go

89 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"crypto/sha256"
"encoding/hex"
"errors"
"regexp"
"strings"
"time"
"github.com/freefire/jiang13-bbs/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
var (
vidUUIDRe = regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
)
// TelemetryService 轻量页面访问埋点
type TelemetryService struct {
db *gorm.DB
}
func NewTelemetryService(db *gorm.DB) *TelemetryService {
return &TelemetryService{db: db}
}
func todayDate() time.Time {
now := time.Now()
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
}
func hashVid(vid string) string {
sum := sha256.Sum256([]byte(strings.TrimSpace(strings.ToLower(vid))))
return hex.EncodeToString(sum[:])
}
// ValidVisitorID 校验浏览器访客 ID(UUID)
func ValidVisitorID(vid string) bool {
return vidUUIDRe.MatchString(strings.TrimSpace(vid))
}
// RecordPageView 记录一次页面浏览:PV 必增;合法 vid 首次出现当日才 UV+1
func (s *TelemetryService) RecordPageView(vid string, loggedIn bool) error {
day := todayDate()
err := s.db.Transaction(func(tx *gorm.DB) error {
var st model.SiteDailyStats
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("date = ?", day).First(&st).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
st = model.SiteDailyStats{Date: day}
if err := tx.Create(&st).Error; err != nil {
return err
}
// 重新锁住新建行
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("date = ?", day).First(&st).Error; err != nil {
return err
}
} else if err != nil {
return err
}
updates := map[string]interface{}{
"pv": st.PV + 1,
"updated_at": time.Now(),
}
if loggedIn {
updates["logged_in_pv"] = st.LoggedInPV + 1
}
if ValidVisitorID(vid) {
vis := model.SiteDailyVisitor{Date: day, VidHash: hashVid(vid)}
res := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&vis)
if res.Error != nil {
return res.Error
}
if res.RowsAffected > 0 {
updates["uv"] = st.UV + 1
}
}
return tx.Model(&model.SiteDailyStats{}).Where("date = ?", day).Updates(updates).Error
})
return err
}