feat(admin): 独立后台壳、站点分析与聊天监管,公开站拆出主题切换与访问埋点

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 04:00:14 +08:00
parent cda5fad2e5
commit 9a55c2f6f2
41 changed files with 5655 additions and 1810 deletions

View File

@@ -0,0 +1,88 @@
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
}