feat: 管理端网站监控,浏览量写入独立 monitor.db
请求日志按日 JSONL;page_views 不进主库,避免统计数据撑大 jiang13.db。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
35
model/db.go
35
model/db.go
@@ -13,25 +13,36 @@ import (
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
// InitDB 初始化 SQLite 并自动迁移
|
||||
func InitDB(dbPath string) error {
|
||||
// MonitorDB 网站监控独立库(page_views),与主库 jiang13.db 分离以免撑大备份
|
||||
var MonitorDB *gorm.DB
|
||||
|
||||
func openSQLite(dbPath string) (*gorm.DB, error) {
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("创建数据库目录失败: %w", err)
|
||||
return nil, fmt.Errorf("创建数据库目录失败: %w", err)
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Warn),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接 SQLite 失败: %w", err)
|
||||
return nil, fmt.Errorf("连接 SQLite 失败: %w", err)
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// InitDB 初始化主库 SQLite 并自动迁移(不含 page_views)
|
||||
func InitDB(dbPath string) error {
|
||||
db, err := openSQLite(dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(
|
||||
&User{}, &Board{}, &Post{}, &Comment{},
|
||||
@@ -60,6 +71,20 @@ func InitDB(dbPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitMonitorDB 打开独立监控库(page_views 只写此处)
|
||||
func InitMonitorDB(monitorDBPath string) error {
|
||||
db, err := openSQLite(monitorDBPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("监控库初始化失败: %w", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&PageView{}); err != nil {
|
||||
return fmt.Errorf("监控库迁移失败: %w", err)
|
||||
}
|
||||
MonitorDB = db
|
||||
log.Println("[model] 监控库初始化完成:", monitorDBPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PingDB 检测数据库连接是否可用(供健康检查使用)
|
||||
func PingDB() error {
|
||||
if DB == nil {
|
||||
|
||||
@@ -481,3 +481,20 @@ type CommunityInstance struct {
|
||||
FirstSeenAt time.Time `json:"first_seen_at"`
|
||||
LastSeenAt time.Time `gorm:"index" json:"last_seen_at"`
|
||||
}
|
||||
|
||||
// PageView 前台路由浏览量(第一方 SPA 信标;存独立 monitor.db;请求日志走 jsonl)
|
||||
type PageView struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `gorm:"index;not null" json:"created_at"`
|
||||
Path string `gorm:"size:512;index" json:"path"`
|
||||
Referrer string `gorm:"size:512" json:"referrer"`
|
||||
IP string `gorm:"size:64;index" json:"ip"` // 完整客户端 IP
|
||||
UA string `gorm:"size:512" json:"ua"`
|
||||
Country string `gorm:"size:8;index" json:"country"`
|
||||
Region string `gorm:"size:64" json:"region"`
|
||||
RegionISO string `gorm:"size:16;index" json:"region_iso"`
|
||||
City string `gorm:"size:64;index" json:"city"`
|
||||
ASN uint `gorm:"index" json:"asn"`
|
||||
ASOrg string `gorm:"size:128" json:"as_org"`
|
||||
IsBot bool `gorm:"default:false;index" json:"is_bot"`
|
||||
}
|
||||
|
||||
54
model/monitor_db_test.go
Normal file
54
model/monitor_db_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestInitMonitorDBUsesSeparateFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
mainPath := filepath.Join(dir, "jiang13.db")
|
||||
monPath := filepath.Join(dir, "monitor.db")
|
||||
|
||||
prevDB, prevMon := DB, MonitorDB
|
||||
t.Cleanup(func() {
|
||||
closeGorm(MonitorDB)
|
||||
closeGorm(DB)
|
||||
DB, MonitorDB = prevDB, prevMon
|
||||
})
|
||||
|
||||
if err := InitDB(mainPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := InitMonitorDB(monPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tableExists(DB, "page_views") {
|
||||
t.Fatal("主库不应创建 page_views")
|
||||
}
|
||||
if !tableExists(MonitorDB, "page_views") {
|
||||
t.Fatal("监控库缺少 page_views")
|
||||
}
|
||||
}
|
||||
|
||||
func tableExists(db *gorm.DB, name string) bool {
|
||||
if db == nil {
|
||||
return false
|
||||
}
|
||||
var n int
|
||||
_ = db.Raw("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", name).Scan(&n).Error
|
||||
return n > 0
|
||||
}
|
||||
|
||||
func closeGorm(db *gorm.DB) {
|
||||
if db == nil {
|
||||
return
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user