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 }