89 lines
2.1 KiB
Go
89 lines
2.1 KiB
Go
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
|
||
}
|