mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-11 15:56:37 +08:00
fix: 资源占用/登录稳定性/QB整理入库/第三方播放404 综合修复
资源占用(Docker 部署 CPU/内存长期居高): - 云盘探测预算改为按尝试扣减,杜绝队列满时对每个文件反复入队 并刷出数万条 WARN(实测日志 41165 条) - 探测队列满时给文件挂 30 分钟退避 + 告警限速为每分钟一条 - 扫描时每个文件的海报/背景图由同步下载(单张最长 20s)改为 后台预取队列,云盘大库扫描不再串行拉图数小时 - PlaybackInfo 的云盘 ffprobe 探测改异步(原同步最长 8s, 既拖慢起播又放大云盘流量),带单飞去重 - 访问日志跳过 /api/health 与静态资源;logging.level/format 配置真正生效(此前是死配置) 登录稳定性(经常登录报错): - refresh token 未及时落库期间,刷新请求可识别「待落库令牌」, 不再把用户踢回登录页;轮换/登出后取消后台补写,防止旧令牌复活 QB 下载整理入库: - 新增 download.path_mappings 设置:自定义下载器→本程序路径映射 (每行 客户端路径=本地路径),并复用 compose 环境变量映射规则 - 应用重启后补整理最近 24h 内完成的种子(此前重启即永久漏掉) - 下载客户端初始化失败仍注册并惰性重连(容器启动顺序免疫) - 硬链接跨文件系统(EXDEV)自动降级为复制,保种语义不变 第三方播放器 404: - 播放处理器不再把所有错误吞成 404:媒体不存在→404, 云盘解析失败/STRM 关闭→502+原因 - 存库的云盘播放 URL 规范化为相对路径,免疫扫描时固化的旧 host - 云盘媒体 SupportsDirectPlay=false,强制走带鉴权的 DirectStream
This commit is contained in:
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"go.uber.org/zap"
|
||||
@@ -405,30 +406,45 @@ func TestEmbyPlaybackInfoProbesMissingCloudTrackMetadata(t *testing.T) {
|
||||
}
|
||||
svc.SetCloudProbe(resolver, prober)
|
||||
|
||||
pb, err := svc.PlaybackInfo(t.Context(), "cloud-probe-1", "user-1")
|
||||
if err != nil {
|
||||
if _, err := svc.PlaybackInfo(t.Context(), "cloud-probe-1", "user-1"); err != nil {
|
||||
t.Fatalf("playback info: %v", err)
|
||||
}
|
||||
|
||||
// 探测现在是异步的(同步探测曾把起播拖慢最多 8 秒并放大云盘流量)。
|
||||
// 轮询等待后台探测结果落库。
|
||||
var persisted model.Media
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for {
|
||||
if err := svc.repo.DB.First(&persisted, "id = ?", "cloud-probe-1").Error; err != nil {
|
||||
t.Fatalf("reload media: %v", err)
|
||||
}
|
||||
if persisted.DurationSec > 0 || time.Now().After(deadline) {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if persisted.DurationSec != 3661 || persisted.Width != 3840 || persisted.Height != 2160 || persisted.VideoCodec != "hevc" || persisted.AudioCodec != "eac3" {
|
||||
t.Fatalf("probe metadata not persisted: %#v", persisted)
|
||||
}
|
||||
if resolver.typ != "openlist" || resolver.ref != "/Movies/Movie.mkv" {
|
||||
t.Fatalf("resolver called with typ=%q ref=%q", resolver.typ, resolver.ref)
|
||||
}
|
||||
if prober.rawURL != "http://cdn.example.test/Movie.mkv" || prober.headers["Authorization"] != "Bearer probe-token" {
|
||||
t.Fatalf("probe called with url=%q headers=%#v", prober.rawURL, prober.headers)
|
||||
}
|
||||
|
||||
// 落库之后,再次请求 PlaybackInfo 应当带上完整轨道元数据。
|
||||
pb, err := svc.PlaybackInfo(t.Context(), "cloud-probe-1", "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("playback info (second): %v", err)
|
||||
}
|
||||
src := pb["MediaSources"].([]map[string]any)[0]
|
||||
if src["RunTimeTicks"] != int64(3661)*10_000_000 {
|
||||
t.Fatalf("runtime ticks not filled from probe: %#v", src)
|
||||
t.Fatalf("runtime ticks not filled after async probe: %#v", src)
|
||||
}
|
||||
streams := src["MediaStreams"].([]map[string]any)
|
||||
if len(streams) != 2 || streams[0]["Codec"] != "hevc" || streams[1]["Codec"] != "eac3" {
|
||||
t.Fatalf("media streams not filled from probe: %#v", streams)
|
||||
}
|
||||
var persisted model.Media
|
||||
if err := svc.repo.DB.First(&persisted, "id = ?", "cloud-probe-1").Error; err != nil {
|
||||
t.Fatalf("reload media: %v", err)
|
||||
}
|
||||
if persisted.DurationSec != 3661 || persisted.Width != 3840 || persisted.Height != 2160 || persisted.VideoCodec != "hevc" || persisted.AudioCodec != "eac3" {
|
||||
t.Fatalf("probe metadata not persisted: %#v", persisted)
|
||||
t.Fatalf("media streams not filled after async probe: %#v", streams)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +454,11 @@ func newTestEmbyService(t *testing.T) *EmbyService {
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
// 内存库 + 异步探测协程:限制为单连接,避免连接池新建连接时
|
||||
// 拿到一个空白的 :memory: 实例(no such table)。
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Library{}, &model.Series{}, &model.Media{}, &model.Favorite{}, &model.PlaybackHistory{}, &model.User{}, &model.Setting{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user