mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 22:36:37 +08:00
优化,排查项目问题
This commit is contained in:
@@ -26,6 +26,7 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -73,6 +74,7 @@ type EmbyRemoteService struct {
|
||||
repo *repository.Container
|
||||
crypto *CryptoService
|
||||
http *http.Client
|
||||
stream *http.Client // 流式代理专用(视频/字幕),无整体 Timeout
|
||||
cache *RuntimeCacheService
|
||||
}
|
||||
|
||||
@@ -87,6 +89,12 @@ func NewEmbyRemoteService(cfg *config.Config, log *zap.Logger, repo *repository.
|
||||
Timeout: embyRemoteHTTPTimeout,
|
||||
Transport: &embyRemoteTransport{base: http.DefaultTransport},
|
||||
},
|
||||
// 流式代理必须用无整体 Timeout 的 client:http.Client.Timeout
|
||||
// 覆盖整个响应体读取过程,15s 的常规超时会让代理播放播到
|
||||
// 15 秒整被掐断。生命周期由请求 ctx 控制。
|
||||
stream: &http.Client{
|
||||
Transport: &embyRemoteTransport{base: http.DefaultTransport},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,15 +360,9 @@ func (r *EmbyRemoteService) resolveRemoteUserID(ctx context.Context, acct *model
|
||||
return
|
||||
}
|
||||
cfg.RemoteUserID = uid
|
||||
raw := map[string]string{}
|
||||
_ = json.Unmarshal([]byte(acct.Config), &raw)
|
||||
raw["remote_user_id"] = uid
|
||||
data, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
acct.Config = string(data)
|
||||
_ = r.repo.StrmAccount.Update(ctx, acct)
|
||||
_ = r.updateAccountConfig(ctx, acct, func(raw map[string]string) {
|
||||
raw["remote_user_id"] = uid
|
||||
})
|
||||
}
|
||||
|
||||
// CleanupOrphanMounts 清理账号已删除的残留挂载(老版本删除账号未级联),
|
||||
@@ -495,19 +497,28 @@ func (r *EmbyRemoteService) ensureTokenOnLine(ctx context.Context, acct *model.S
|
||||
return nil
|
||||
}
|
||||
|
||||
// persistToken 把认证得到的 token / user id 加密写回账号配置(下次请求免登录)。
|
||||
func (r *EmbyRemoteService) persistToken(ctx context.Context, acct *model.StrmAccount, cfg *EmbyRemoteConfig) error {
|
||||
if acct == nil {
|
||||
// acctCfgMu 序列化对账号 Config 的读-改-写。并发请求若各自基于请求开始
|
||||
// 时的快照做整包覆盖,会互相丢失更新(刚持久化的 token / active_line 被
|
||||
// 旧快照覆盖回去)。
|
||||
var acctCfgMu sync.Mutex
|
||||
|
||||
// updateAccountConfig 在互斥下重读账号最新 Config,应用 mutate 后写回,
|
||||
// 并同步调用方持有的 acct 快照。
|
||||
func (r *EmbyRemoteService) updateAccountConfig(ctx context.Context, acct *model.StrmAccount, mutate func(raw map[string]string)) error {
|
||||
if acct == nil || r.repo == nil {
|
||||
return nil
|
||||
}
|
||||
acctCfgMu.Lock()
|
||||
defer acctCfgMu.Unlock()
|
||||
raw := map[string]string{}
|
||||
if fresh, err := r.repo.StrmAccount.FindByID(ctx, acct.ID); err == nil && fresh != nil {
|
||||
acct.Config = fresh.Config // 以 DB 最新值为基线,避免覆盖并发写入
|
||||
}
|
||||
if strings.TrimSpace(acct.Config) != "" {
|
||||
_ = json.Unmarshal([]byte(acct.Config), &raw)
|
||||
}
|
||||
raw["api_key"] = r.crypto.Encrypt(cfg.Token)
|
||||
raw["remote_user_id"] = cfg.RemoteUserID
|
||||
if strings.TrimSpace(raw["username"]) == "" {
|
||||
raw["username"] = cfg.Username
|
||||
if mutate != nil {
|
||||
mutate(raw)
|
||||
}
|
||||
data, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
@@ -517,6 +528,20 @@ func (r *EmbyRemoteService) persistToken(ctx context.Context, acct *model.StrmAc
|
||||
return r.repo.StrmAccount.Update(ctx, acct)
|
||||
}
|
||||
|
||||
// persistToken 把认证得到的 token / user id 加密写回账号配置(下次请求免登录)。
|
||||
func (r *EmbyRemoteService) persistToken(ctx context.Context, acct *model.StrmAccount, cfg *EmbyRemoteConfig) error {
|
||||
if acct == nil {
|
||||
return nil
|
||||
}
|
||||
return r.updateAccountConfig(ctx, acct, func(raw map[string]string) {
|
||||
raw["api_key"] = r.crypto.Encrypt(cfg.Token)
|
||||
raw["remote_user_id"] = cfg.RemoteUserID
|
||||
if strings.TrimSpace(raw["username"]) == "" {
|
||||
raw["username"] = cfg.Username
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// doGet 向远程 Emby 发起带 api_key 的 GET,把响应 JSON 解码到 out。
|
||||
// 401 时自动重认证一次再重试(凭据过期场景)。连接失败时按线路优先级自动切换。
|
||||
func (r *EmbyRemoteService) doGet(ctx context.Context, acct *model.StrmAccount, cfg *EmbyRemoteConfig, path string, q url.Values, out any) error {
|
||||
@@ -567,22 +592,28 @@ func (r *EmbyRemoteService) doGetOnLine(ctx context.Context, acct *model.StrmAcc
|
||||
if err != nil {
|
||||
return fmt.Errorf("请求远程 Emby 失败: %w", err)
|
||||
}
|
||||
data, readErr := io.ReadAll(io.LimitReader(resp.Body, 8<<20))
|
||||
// 读 8MB+1 以区分"刚好 8MB"与"被截断":截断的 JSON 会让
|
||||
// Unmarshal 报 unexpected end,难以定位;这里显式报错。
|
||||
data, readErr := io.ReadAll(io.LimitReader(resp.Body, (8<<20)+1))
|
||||
resp.Body.Close()
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
if len(data) > 8<<20 {
|
||||
return fmt.Errorf("远程 Emby 响应超过 8MB 上限(路径 %s):请减小分页或 Fields 字段", path)
|
||||
}
|
||||
if resp.StatusCode == http.StatusUnauthorized && attempt == 0 {
|
||||
// 401:只清当前线路的内存 token 并立即重认证;不在此时删除
|
||||
// DB 里的 api_key——①外层还会按线路故障转移(其他线路可能
|
||||
// 存有自己的 token);②纯 api_key 账号删除后无法再认证,一次
|
||||
// 线路误报就会把账号“砖化”。重认证成功后 persistToken 会用
|
||||
// 新 token 覆盖 api_key。
|
||||
cfg.Token = ""
|
||||
master.Token = ""
|
||||
if acct != nil {
|
||||
raw := map[string]string{}
|
||||
_ = json.Unmarshal([]byte(acct.Config), &raw)
|
||||
delete(raw, "api_key")
|
||||
enc, _ := json.Marshal(raw)
|
||||
acct.Config = string(enc)
|
||||
_ = r.repo.StrmAccount.Update(ctx, acct)
|
||||
if err := r.ensureTokenOnLine(ctx, acct, cfg); err != nil {
|
||||
return fmt.Errorf("认证重试失败: %w", err)
|
||||
}
|
||||
master.Token = cfg.Token
|
||||
master.RemoteUserID = cfg.RemoteUserID
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode >= 300 {
|
||||
@@ -957,7 +988,7 @@ func (r *EmbyRemoteService) proxyVideoStreamOnLine(ctx context.Context, w http.R
|
||||
if rangeHeader := req.Header.Get("Range"); rangeHeader != "" {
|
||||
upstream.Header.Set("Range", rangeHeader)
|
||||
}
|
||||
resp, err := r.http.Do(upstream)
|
||||
resp, err := r.stream.Do(upstream)
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接远程 Emby 视频流失败: %w", err)
|
||||
}
|
||||
@@ -1028,7 +1059,7 @@ func (r *EmbyRemoteService) proxySubtitleOnLine(ctx context.Context, w http.Resp
|
||||
return err
|
||||
}
|
||||
upstream.Header.Set("X-Emby-Token", cfg.Token)
|
||||
resp, err := r.http.Do(upstream)
|
||||
resp, err := r.stream.Do(upstream)
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接远程 Emby 字幕流失败: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user