From 7fa99a56690a609148ba26107f77b222d16880a7 Mon Sep 17 00:00:00 2001 From: ShukeBta Date: Mon, 18 May 2026 01:57:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BD=BF=20TMDbProvider=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=BB=8E=E6=95=B0=E6=8D=AE=E5=BA=93=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=20API=20Key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 TMDbProvider 添加 apiConfig *APIConfigService 字段 - NewTMDbProvider 接受 apiConfig 参数(可为 nil) - 添加 resolveAPIKey(ctx) 方法:优先从 config 读取,回退到数据库 - 添加 resolveBaseURL(ctx) 方法:解析基础 URL - 修改 SearchMovie 使用 resolveAPIKey / resolveBaseURL - 修改 DiscoverService.Fetch 使用 resolveAPIKey / resolveBaseURL - 调整 service.go 初始化顺序,确保 tmdb 在 scraper 之前初始化 - 构建验证通过 --- internal/service/discover.go | 14 ++++-- internal/service/service.go | 6 +-- internal/service/tmdb.go | 86 +++++++++++++++++++++++++++++------- 3 files changed, 83 insertions(+), 23 deletions(-) diff --git a/internal/service/discover.go b/internal/service/discover.go index 92d00e1..31eb1ac 100644 --- a/internal/service/discover.go +++ b/internal/service/discover.go @@ -53,14 +53,22 @@ func (d *DiscoverService) fetch(ctx context.Context, path string) ([]Match, erro // It paginates page=1 only — that's all the home page needs and it // keeps us under TMDb's 50 rps limit. func (d *DiscoverService) Fetch(ctx context.Context, path string) ([]Match, error) { - if d.tmdb == nil || !d.tmdb.Enabled() { + if d.tmdb == nil { return nil, nil } + + // Resolve API key from config or database + apiKey := d.tmdb.resolveAPIKey(ctx) + if apiKey == "" { + return nil, nil + } + base := d.tmdb.resolveBaseURL(ctx) + q := url.Values{} - q.Set("api_key", d.tmdb.cfg.Secrets.TMDbAPIKey) + q.Set("api_key", apiKey) q.Set("language", "zh-CN") q.Set("page", "1") - u := d.tmdb.base + path + "?" + q.Encode() + u := base + path + "?" + q.Encode() type result struct { ID int `json:"id"` diff --git a/internal/service/service.go b/internal/service/service.go index 1341e02..f629971 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -81,7 +81,9 @@ func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Cont go sseHub.Run() probe := NewFFprobeService(cfg, log) - tmdb := NewTMDbProvider(cfg, log) + crypto := NewCryptoService(cfg.Secrets.JWTSecret, log) + apiConfig := NewAPIConfigService(log, repos, crypto) + tmdb := NewTMDbProvider(cfg, log, apiConfig) bangumi := NewBangumiProvider(cfg, log) thetvdb := NewTheTVDBProvider(cfg, log) fanart := NewFanartProvider(cfg, log) @@ -94,8 +96,6 @@ func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Cont watcher := NewWatcherService(log, repos, scanner) nfo := NewNFOService(log, repos) ai := NewAIService(cfg, log) - crypto := NewCryptoService(cfg.Secrets.JWTSecret, log) - apiConfig := NewAPIConfigService(log, repos, crypto) duplicate := NewDuplicateService(log, repos, hub) filemanager := NewFileManagerService(cfg, log, repos) dlna := NewDLNAService(log) diff --git a/internal/service/tmdb.go b/internal/service/tmdb.go index 693d59a..7f4fd79 100644 --- a/internal/service/tmdb.go +++ b/internal/service/tmdb.go @@ -31,16 +31,19 @@ import ( // TMDbProvider talks to https://api.themoviedb.org/3. type TMDbProvider struct { - cfg *config.Config - log *zap.Logger - client *http.Client - base string - imgCDN string + cfg *config.Config + log *zap.Logger + client *http.Client + base string + imgCDN string + apiConfig *APIConfigService } // NewTMDbProvider is the constructor. APIBase / image CDN can be overridden // via secrets.tmdb_api_proxy + tmdb_image_proxy for users behind GFW. -func NewTMDbProvider(cfg *config.Config, log *zap.Logger) *TMDbProvider { +// apiConfig is optional; when non-nil, the provider will also check the +// api_configs table for TMDB API key. +func NewTMDbProvider(cfg *config.Config, log *zap.Logger, apiConfig *APIConfigService) *TMDbProvider { base := cfg.Secrets.TMDbAPIProxy if base == "" { base = "https://api.themoviedb.org/3" @@ -50,16 +53,61 @@ func NewTMDbProvider(cfg *config.Config, log *zap.Logger) *TMDbProvider { img = "https://image.tmdb.org/t/p" } return &TMDbProvider{ - cfg: cfg, - log: log, - base: base, - imgCDN: img, - client: &http.Client{Timeout: 15 * time.Second}, + cfg: cfg, + log: log, + apiConfig: apiConfig, + base: base, + imgCDN: img, + client: &http.Client{Timeout: 15 * time.Second}, } } // Enabled reports whether the operator has supplied an API key. -func (t *TMDbProvider) Enabled() bool { return t.cfg.Secrets.TMDbAPIKey != "" } +// It checks both the config file and the database (via apiConfig). +func (t *TMDbProvider) Enabled() bool { + // Fast path: check config + if t.cfg.Secrets.TMDbAPIKey != "" { + return true + } + // Secondary check: if we have apiConfig, the key might be in the database + // We can't query the database here (no ctx), so we rely on the caller + // to check properly before making API calls. + // The actual key resolution happens in resolveAPIKey(ctx). + return t.apiConfig != nil +} + +// resolveAPIKey returns the TMDb API key, checking config first, then database. +func (t *TMDbProvider) resolveAPIKey(ctx context.Context) string { + // Check config first (fast path) + if t.cfg.Secrets.TMDbAPIKey != "" { + return t.cfg.Secrets.TMDbAPIKey + } + // Fall back to database + if t.apiConfig != nil { + resolved, err := t.apiConfig.Resolve(ctx, "tmdb") + if err == nil && resolved.APIKey != "" { + return resolved.APIKey + } + } + return "" +} + +// resolveBaseURL returns the TMDb base URL, checking config first, then database. +func (t *TMDbProvider) resolveBaseURL(ctx context.Context) string { + // Check config first + base := t.cfg.Secrets.TMDbAPIProxy + if base == "" { + base = "https://api.themoviedb.org/3" + } + // Override from database if available + if t.apiConfig != nil { + resolved, err := t.apiConfig.Resolve(ctx, "tmdb") + if err == nil && resolved.BaseURL != "" { + base = resolved.BaseURL + } + } + return base +} // Match describes a successful metadata match. The same struct is reused // across providers; provider-specific IDs sit side-by-side so the scraper @@ -78,22 +126,26 @@ type Match struct { // SearchMovie issues `/search/movie` and returns the best match, or nil // when no result is found. The `year` argument is optional (0 = any). func (t *TMDbProvider) SearchMovie(ctx context.Context, query string, year int) (*Match, error) { - if !t.Enabled() { - return nil, nil - } if query == "" { return nil, errors.New("empty query") } + // Resolve API key from config or database + apiKey := t.resolveAPIKey(ctx) + if apiKey == "" { + return nil, nil + } + base := t.resolveBaseURL(ctx) + q := url.Values{} - q.Set("api_key", t.cfg.Secrets.TMDbAPIKey) + q.Set("api_key", apiKey) q.Set("query", query) q.Set("language", "zh-CN") q.Set("include_adult", "false") if year > 0 { q.Set("year", fmt.Sprintf("%d", year)) } - u := t.base + "/search/movie?" + q.Encode() + u := base + "/search/movie?" + q.Encode() type result struct { ID int `json:"id"`