package collect import ( "context" "fmt" "geo/internal/config" "os" "path/filepath" "sync" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" "github.com/gofiber/fiber/v2/log" ) // CollectManager 收集管理器 - 单浏览器临时Page模式 type CollectManager struct { ctx context.Context config *config.Config logger log.AllLogger browser *rod.Browser // 全局唯一的浏览器实例 mu sync.RWMutex // 保护浏览器访问的互斥锁 collectors map[string]CollectorInterface // 平台名称 -> Collector 实例 } // NewCollectManager 创建收集管理器(服务启动时调用) func NewCollectManager(ctx context.Context, cfg *config.Config, logger log.AllLogger) *CollectManager { manager := &CollectManager{ ctx: ctx, config: cfg, logger: logger, collectors: make(map[string]CollectorInterface), } // 注册所有平台的 Collector manager.registerCollectors() // 服务启动时立即创建全局浏览器 manager.initBrowser() return manager } // initBrowser 初始化全局浏览器实例 func (m *CollectManager) initBrowser() { m.logger.Info("🚀 正在初始化浏览器...") // 创建用户数据目录(所有平台共用) userDataDir := filepath.Join(m.config.Sys.ChromeDataDir, "global", "main") os.MkdirAll(userDataDir, 0755) // 使用 launcher 启动 Chrome l := launcher.New(). Bin(m.config.Sys.ChromePath). UserDataDir(userDataDir). Headless(false). // 有头模式,显示窗口 Leakless(true). // 让 Chrome 进程独立运行 Set("disable-blink-features", "AutomationControlled"). Set("window-size", "1920,1080"). Set("start-maximized", ""). Set("lang", "zh-CN") url, err := l.Launch() if err != nil { m.logger.Errorf("❌ 启动浏览器失败: %v", err) panic(fmt.Sprintf("启动浏览器失败: %v", err)) } m.logger.Infof("✅ 浏览器启动成功: %s", url) // 连接到浏览器 m.browser = rod.New().Context(m.ctx).ControlURL(url).MustConnect() m.logger.Info("✅ 浏览器连接成功") } // Close 关闭管理器(注意:不关闭浏览器,让浏览器进程继续运行) func (m *CollectManager) Close() { m.mu.Lock() defer m.mu.Unlock() m.logger.Info("🔒 CollectManager 关闭(浏览器保持运行)...") // 注意:这里不调用 m.browser.Close() // 让浏览器进程继续运行,下次服务启动时可以复用 m.logger.Info("✅ Manager 已关闭,浏览器仍在后台运行") } // registerCollectors 注册所有平台的 Collector func (m *CollectManager) registerCollectors() { // 文心一言 params := &CollectParams{Platform: "wenxin"} m.collectors["wenxin"] = NewWenxinCollector(m.ctx, params, m.config, m.logger, m.browser, nil) // DeepSeek params = &CollectParams{Platform: "deepseek"} m.collectors["deepseek"] = NewDeepseekCollector(m.ctx, params, m.config, m.logger, m.browser, nil) // 豆包 params = &CollectParams{Platform: "doubao"} m.collectors["doubao"] = NewDoubaoCollector(m.ctx, params, m.config, m.logger, m.browser, nil) // 通义千问 params = &CollectParams{Platform: "qianwen"} m.collectors["qianwen"] = NewQianwenCollector(m.ctx, params, m.config, m.logger, m.browser, nil) } // GetCollector 获取指定平台的 Collector func (m *CollectManager) GetCollector(platform string) (CollectorInterface, error) { m.mu.RLock() defer m.mu.RUnlock() collector, ok := m.collectors[platform] if !ok { return nil, fmt.Errorf("不支持的平台: %s", platform) } return collector, nil } // AskQuestion 向指定平台提问(每次创建临时 Page) func (m *CollectManager) AskQuestion(platform string, question string) (*CollectResult, error) { collector, err := m.GetCollector(platform) if err != nil { return nil, err } return collector.AskQuestion(question) }