package main import ( "context" "log" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "proxyrotator/internal/api" "proxyrotator/internal/config" "proxyrotator/internal/importer" "proxyrotator/internal/selector" "proxyrotator/internal/store" "proxyrotator/internal/telegram" "proxyrotator/internal/tester" ) func main() { // 配置日志 slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, }))) // 加载配置 cfg := config.Load() slog.Info("starting proxyrotator", "listen_addr", cfg.ListenAddr, "max_concurrency", cfg.MaxConcurrency, "max_test_limit", cfg.MaxTestLimit, "lease_ttl", cfg.LeaseTTL, "telegram_enabled", cfg.TelegramBotToken != "", ) // 连接数据库 ctx := context.Background() pgStore, err := store.NewPgStore(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("failed to connect to database: %v", err) } defer pgStore.Close() slog.Info("connected to database") // 初始化组件 imp := importer.NewImporter() tst := tester.NewHTTPTester() sel := selector.NewSelector(pgStore, cfg.LeaseTTL) // 初始化 Telegram Bot bot := telegram.NewBot(cfg, pgStore) // 启动 Telegram Bot if err := bot.Start(ctx); err != nil { slog.Warn("failed to start telegram bot", "error", err) } defer bot.Stop() // 创建路由 router := api.NewRouter(pgStore, imp, tst, sel, cfg) // 创建 HTTP 服务器 server := &http.Server{ Addr: cfg.ListenAddr, Handler: router, ReadTimeout: 30 * time.Second, WriteTimeout: 120 * time.Second, IdleTimeout: 60 * time.Second, } // 启动后台任务:清理过期租约 go cleanupLoop(ctx, pgStore) // 启动服务器(在 goroutine 中) go func() { slog.Info("server listening", "addr", cfg.ListenAddr) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("server error: %v", err) } }() // 等待中断信号 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit slog.Info("shutting down server...") // 优雅关闭 shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { log.Fatalf("server shutdown error: %v", err) } slog.Info("server stopped") } // cleanupLoop 定期清理过期租约 func cleanupLoop(ctx context.Context, s *store.PgStore) { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: deleted, err := s.DeleteExpiredLeases(ctx) if err != nil { slog.Error("failed to cleanup expired leases", "error", err) } else if deleted > 0 { slog.Info("cleaned up expired leases", "count", deleted) } } } }