package main import ( "context" "fmt" "log" "time" "spider/internal/config" "spider/internal/handler" "spider/internal/llm" "spider/internal/model" "spider/internal/search" "spider/internal/service" "spider/internal/telegram" "spider/internal/worker" "github.com/redis/go-redis/v9" "gorm.io/driver/mysql" "gorm.io/gorm" ) func main() { // 1. 加载配置 cfg, err := config.Load("configs/config.yaml") if err != nil { log.Fatalf("load config: %v", err) } // 2. 连接 MySQL dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", cfg.MySQL.User, cfg.MySQL.Password, cfg.MySQL.Host, cfg.MySQL.Port, cfg.MySQL.Database) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Fatalf("connect mysql: %v", err) } // 3. AutoMigrate 所有表 err = db.AutoMigrate( &model.ManagedSeed{}, &model.ManagedKeyword{}, &model.ManagedSetting{}, &model.Channel{}, &model.NavSite{}, &model.MerchantRaw{}, &model.MerchantClean{}, &model.Task{}, &model.ConfigRevision{}, ) if err != nil { log.Fatalf("automigrate: %v", err) } log.Println("MySQL tables migrated") // 3a. 初始化 managed_settings 默认值(幂等,已有记录不覆盖) seedSettings(db) // 4. 连接 Redis rdb := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Redis.Host, cfg.Redis.Port), Password: cfg.Redis.Password, DB: cfg.Redis.DB, }) log.Println("Redis connected") // 5. 初始化 TaskService taskSvc := service.NewTaskService(db, rdb) // 5a. 初始化 SettingsService 并加载到 Redis 缓存 settings := service.NewSettingsService(db, rdb) if err := settings.Load(context.Background()); err != nil { log.Printf("load settings into cache: %v", err) } // 5b. 初始化 AccountManager(账号从配置读取,为空时运行时从 DB 动态加载) tgAccounts := make([]telegram.Account, 0, len(cfg.Telegram.Accounts)) for _, a := range cfg.Telegram.Accounts { tgAccounts = append(tgAccounts, telegram.Account{ Phone: a.Phone, SessionFile: a.SessionFile, AppID: cfg.Telegram.AppID, AppHash: cfg.Telegram.AppHash, }) } tgManager := telegram.NewAccountManager(tgAccounts, rdb) // 5c. 初始化 LLM Client(配置缺失时为 nil,phase 会安全跳过) var llmClient *llm.Client if cfg.LLM.APIKey != "" { llmClient = llm.New(cfg.LLM.BaseURL, cfg.LLM.APIKey, cfg.LLM.Model, 30*time.Second) } // 5d. 初始化 Serper Client(配置缺失时为 nil) var serperClient *search.SerperClient if cfg.Serper.APIKey != "" { serperClient = search.NewSerperClient(cfg.Serper.APIKey, cfg.Serper.ResultsPerPage, cfg.Serper.MaxPages) } // 6. 初始化并启动 asynq Worker redisAddr := fmt.Sprintf("%s:%d", cfg.Redis.Host, cfg.Redis.Port) w := worker.New(redisAddr, cfg.Redis.Password, cfg.Redis.DB, db, rdb, tgManager, llmClient, settings, serperClient, cfg.GitHub.Token) go func() { log.Println("asynq worker starting...") if err := w.Start(); err != nil { log.Fatalf("asynq worker error: %v", err) } }() // 7. 初始化 Gin router r := handler.SetupRouter(db, rdb, taskSvc) addr := handler.ServerAddr(cfg.Server.Port) log.Printf("Server starting on %s", addr) if err := r.Run(addr); err != nil { log.Fatalf("gin run: %v", err) } }