package search import ( "fmt" "strings" ) // Result 单个搜索结果。 type Result struct { Title string `json:"title"` URL string `json:"url"` Content string `json:"content"` Engine string `json:"engine"` } // Response SearXNG /search?format=json 的标准响应(兼容旧响应结构)。 type Response struct { Query string `json:"query"` Results []Result `json:"results"` } // Client 搜索引擎聚合编排器。 // // 持有一组按优先级排序的 Engine,Search 按顺序逐个调用,命中(成功且有结果)即返回; // 单个引擎失败/无结果时降级到下一引擎,全部失败再汇总根因。启用的引擎与顺序由 // search_config.json 的 active 列表声明,替代此前写死代码里的硬编码回退链。 type Client struct { engines []Engine } // NewConfiguredClient 按配置构建聚合 Client。 // // - cfg 为 nil(无配置文件)时使用默认硬编码链:SearXNG → 搜狗 HTTP → 真实浏览器, // 保证旧行为不因缺配置而改变。 // - cfg 非 nil 时按 active 列表顺序构建引擎,跳过 disabled 或未知类型者。 func NewConfiguredClient(cfg *SearchConfig) *Client { if cfg == nil { return &Client{engines: defaultEngines()} } c := &Client{} for _, name := range cfg.Active { ec, ok := cfg.Engines[name] if !ok || !ec.Enabled { continue } if e, err := engineFromConfig(name, ec); err != nil { // 未知类型直接跳过,不阻塞整链。 continue } else { c.engines = append(c.engines, e) } } // 配置全被跳过时回退默认链,避免空组织。 if len(c.engines) == 0 { c.engines = defaultEngines() } return c } // defaultEngines 无配置时的默认硬编码链(保持旧行为)。 func defaultEngines() []Engine { return []Engine{ &searxngEngine{client: NewSearXNGClient("")}, &sogouEngine{client: NewSogouClient()}, &browserEngine{searcher: GetBrowserSearcher()}, } } // EngineNames 返回当前生效的引擎名(有序),供诊断/展示。 func (c *Client) EngineNames() []string { names := make([]string, 0, len(c.engines)) for _, e := range c.engines { names = append(names, e.Name()) } return names } // Search 按配置顺序逐个调用引擎,命中即返回首个有结果的引擎。 // 全部引擎失败时聚合各引擎错误。 func (c *Client) Search(query string) ([]Result, error) { var errParts []string for _, e := range c.engines { res, err := e.Search(query) if err == nil && len(res) > 0 { return res, nil } errParts = append(errParts, fmt.Sprintf("%s:%v", e.Name(), orNil(err))) } return nil, fmt.Errorf("全部搜索引擎无结果:[%s]", strings.Join(errParts, ";")) } func orNil(err error) any { if err == nil { return "无结果" } return err } func truncate(s string, n int) string { r := []rune(s) if len(r) <= n { return s } return string(r[:n]) + "..." }