// 将 data/*.json(站点与分组)导入 MySQL。建议空库首次使用,或加 -wipe 清空业务表后再导。 // // go run ./cmd/importjson -data ./data -wipe package main import ( "encoding/json" "flag" "fmt" "log" "os" "path/filepath" "gorm.io/datatypes" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "mengyaping-backend/config" "mengyaping-backend/models" "mengyaping-backend/storage" ) func main() { dataDir := flag.String("data", "./data", "JSON 目录(websites.json、groups.json)") wipe := flag.Bool("wipe", false, "导入前 TRUNCATE 所有 monitor_* 表(慎用)") flag.Parse() cfg := config.GetConfig() // 大批量 INSERT 时单条超过 200ms 会触发 GORM 默认「SLOW SQL」提示,易被误认为报错;导入工具改为静默 SQL 日志 db, err := gorm.Open(mysql.Open(cfg.DatabaseDSN()), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { log.Fatal(err) } if err := db.AutoMigrate( &storage.MonitorGroup{}, &storage.MonitorWebsite{}, &storage.MonitorWebsiteURL{}, &storage.MonitorWebsiteGroup{}, &storage.MonitorProbeLatest{}, &storage.MonitorProbeHour{}, &storage.MonitorProbeDay{}, &storage.MonitorKV{}, ); err != nil { log.Fatal(err) } if *wipe { _ = db.Exec("SET FOREIGN_KEY_CHECKS = 0").Error for _, tbl := range []string{ "monitor_probe_latest", "monitor_probe_hour", "monitor_probe_day", "monitor_kv", "monitor_checks", "monitor_website_urls", "monitor_website_groups", "monitor_websites", "monitor_groups", } { _ = db.Exec("TRUNCATE TABLE " + tbl).Error } _ = db.Exec("SET FOREIGN_KEY_CHECKS = 1").Error log.Println("已清空 monitor_* 表") } websitesPath := filepath.Join(*dataDir, "websites.json") raw, err := os.ReadFile(websitesPath) if err != nil { log.Fatalf("读取 websites.json: %v", err) } var websites []models.Website if err := json.Unmarshal(raw, &websites); err != nil { log.Fatal(err) } groupsPath := filepath.Join(*dataDir, "groups.json") var fileGroups []models.Group if g, err := os.ReadFile(groupsPath); err == nil { _ = json.Unmarshal(g, &fileGroups) } if len(fileGroups) == 0 { fileGroups = models.DefaultGroups } for i, g := range fileGroups { if err := db.Create(&storage.MonitorGroup{ ID: g.ID, Name: g.Name, SortOrder: i + 1, }).Error; err != nil { log.Printf("导入分组 %s: %v", g.ID, err) } } for _, w := range websites { if len(w.Groups) == 0 && w.Group != "" { w.Groups = []string{w.Group} } row := &storage.MonitorWebsite{ ID: w.ID, Name: w.Name, LegacyGroup: "", Favicon: w.Favicon, Title: w.Title, IPAddresses: datatypes.JSON(mustJSON(w.IPAddresses)), CreatedAt: w.CreatedAt, UpdatedAt: w.UpdatedAt, } if err := db.Create(row).Error; err != nil { log.Printf("网站 %s: %v", w.ID, err) continue } for i, u := range w.URLs { _ = db.Create(&storage.MonitorWebsiteURL{ WebsiteID: w.ID, URLID: u.ID, URL: u.URL, Remark: u.Remark, SortOrder: i, }) } for _, gid := range w.Groups { _ = db.Create(&storage.MonitorWebsiteGroup{WebsiteID: w.ID, GroupID: gid}) } } fmt.Printf("已导入 %d 个网站\n", len(websites)) log.Println("探测历史由运行时写入 monitor_probe_* 汇总表,不再从 records.json 导入") } func mustJSON(v []string) []byte { if v == nil { return []byte("[]") } b, err := json.Marshal(v) if err != nil { return []byte("[]") } return b }