feat: 站点适配器 FlareSolverr 集成 + 并发搜索优化

- site_adapter.go: SiteConfig 扩展 UserAgent/Timeout/Extra/FlareSolverrURL 字段
- site_adapter.go: doRequest() GET 请求支持 FlareSolverr 代理绕过 Cloudflare/WAF
- site_adapter.go: buildRequest() 使用 cfg.UserAgent(覆盖默认 UA)
- site_adapter.go: MTeam api_key 认证改为 Authorization: Bearer 格式
- site_adapter.go: 修复 2 个 go vet 冗余 StatusFound 警告
- site.go: Search() 重构为 sync.WaitGroup 并发执行,提升多站搜索性能
- site.go: siteModelToConfig() 改为 SiteService 方法,按 BrowserEmulation 填充 FlareSolverrURL
- site.go: Search() 空关键词返回 [] 而非 error,nil slice 防护
- 新增 system_handler.go: 系统工具探测接口
- 新增 ffmpeg_auto_install.go: FFmpeg 自动安装服务
- 新增 scripts/install-ffmpeg.ps1: Windows FFmpeg 安装脚本
This commit is contained in:
ShukeBta
2026-05-24 17:14:47 +08:00
parent 68083a2840
commit 5d1ae91419
5 changed files with 787 additions and 54 deletions
+43 -12
View File
@@ -13,19 +13,23 @@ import (
"strings"
"time"
"github.com/ShukeBta/MediaStationGo/internal/helper"
"github.com/ShukeBta/MediaStationGo/internal/model"
)
// SiteConfig 站点配置(从 model.Site 解密后的纯文本)。
type SiteConfig struct {
Name string
Type string
URL string
AuthType string
Cookie string
APIKey string
AuthHeader string
Extra map[string]string // JSON 扩展配置
Name string
Type string
URL string
AuthType string
Cookie string
APIKey string
AuthHeader string
UserAgent string // 自定义 User-Agent
Timeout time.Duration // 请求超时
Extra map[string]string // JSON 扩展配置
FlareSolverrURL string // FlareSolverr 服务地址(用于浏览器模拟绕过 Cloudflare/WAF)
}
// SiteSearchResult 站点搜索结果(按站点分组的批量搜索结果)。
@@ -110,7 +114,12 @@ func buildRequest(ctx context.Context, method, rawURL string, cfg SiteConfig, bo
req.Header.Set("Cookie", cfg.Cookie)
}
case "api_key":
if cfg.APIKey != "" {
// MTeam 使用 Authorization: Bearer 格式
if cfg.Type == "mteam" {
if cfg.APIKey != "" {
req.Header.Set("Authorization", "Bearer "+cfg.APIKey)
}
} else if cfg.APIKey != "" {
req.Header.Set("X-API-Key", cfg.APIKey)
}
case "auth_header":
@@ -124,12 +133,34 @@ func buildRequest(ctx context.Context, method, rawURL string, cfg SiteConfig, bo
}
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
// 使用 SiteConfig 中的 UserAgent(如果提供),否则使用默认值
userAgent := cfg.UserAgent
if userAgent == "" {
userAgent = model.DefaultUserAgent
}
req.Header.Set("User-Agent", userAgent)
return req, nil
}
// doRequest 执行 HTTP 请求并返回响应体。
// 当 cfg.FlareSolverrURL 已配置且方法为 GET 时,通过 FlareSolverr 代理请求
// 以绕过 Cloudflare/WAF 挑战验证。
func doRequest(ctx context.Context, client *http.Client, method, rawURL string, cfg SiteConfig, body io.Reader) ([]byte, int, error) {
// ── FlareSolverr 浏览器模拟路径(仅 GET) ──────────────────────────
if cfg.FlareSolverrURL != "" && method == "GET" {
timeout := int(cfg.Timeout.Seconds())
if timeout <= 0 {
timeout = 30
}
pageBody, err := helper.FetchURLWithFlareSolverr(
cfg.FlareSolverrURL, rawURL, cfg.Cookie, timeout, "", nil)
if err != nil {
return nil, 0, fmt.Errorf("flareSolverr: %w", err)
}
return []byte(pageBody), http.StatusOK, nil
}
// ── 直接 HTTP 请求路径 ─────────────────────────────────────────────
req, err := buildRequest(ctx, method, rawURL, cfg, body)
if err != nil {
return nil, 0, err
@@ -176,7 +207,7 @@ func (a *NexusPHPAdapter) Authenticate(ctx context.Context, cfg SiteConfig) erro
}
defer httpResp.Body.Close()
if httpResp.StatusCode == http.StatusFound || httpResp.StatusCode == http.StatusFound {
if httpResp.StatusCode == http.StatusFound {
return fmt.Errorf("authentication failed: redirected to login page")
}
if httpResp.StatusCode != http.StatusOK {
@@ -1022,7 +1053,7 @@ func (a *DiscuzAdapter) Authenticate(ctx context.Context, cfg SiteConfig) error
if err != nil {
return fmt.Errorf("authenticate: %w", err)
}
if status == http.StatusFound || status == http.StatusFound {
if status == http.StatusFound {
return fmt.Errorf("authentication failed: redirected to login")
}
if status != http.StatusOK {