mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 11:46:36 +08:00
585edeb984
资源占用(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
79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/glebarez/sqlite"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/ShukeBta/MediaStationGo/internal/config"
|
|
"github.com/ShukeBta/MediaStationGo/internal/model"
|
|
"github.com/ShukeBta/MediaStationGo/internal/repository"
|
|
)
|
|
|
|
func newTokenTestRepo(t *testing.T) *repository.Container {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.AutoMigrate(&model.User{}, &model.RefreshToken{}, &model.Setting{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return repository.New(db)
|
|
}
|
|
|
|
// TestRefreshAcceptsPendingDelayedToken 验证:登录时因 SQLite 写压力未及时
|
|
// 落库的 refresh token(仍在后台补写队列中)在刷新时被接受,而不是把用户
|
|
// 踢回登录页(历史上「经常登录报错」的来源之一)。
|
|
func TestRefreshAcceptsPendingDelayedToken(t *testing.T) {
|
|
repos := newTokenTestRepo(t)
|
|
cfg := &config.Config{}
|
|
cfg.Secrets.JWTSecret = "test-secret"
|
|
svc := NewTokenService(cfg, zap.NewNop(), repos)
|
|
|
|
u := &model.User{Username: "u1", PasswordHash: "x", Role: "user", Tier: "free", IsActive: true}
|
|
if err := repos.User.Create(t.Context(), u); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
refreshToken := "pending-token-value"
|
|
hash := repository.HashToken(refreshToken)
|
|
if !svc.trackDelayedStore(u.ID, hash, time.Now().Add(time.Hour)) {
|
|
t.Fatal("trackDelayedStore returned false")
|
|
}
|
|
|
|
pair, err := svc.Refresh(t.Context(), refreshToken)
|
|
if err != nil {
|
|
t.Fatalf("Refresh rejected pending delayed token: %v", err)
|
|
}
|
|
if pair == nil || pair.AccessToken == "" || pair.RefreshToken == "" {
|
|
t.Fatalf("Refresh returned incomplete pair: %+v", pair)
|
|
}
|
|
// 轮换后旧令牌应从 pending 表移除,不能再次使用。
|
|
if _, still := svc.pendingDelayedStore(hash); still {
|
|
t.Fatal("rotated pending token still tracked")
|
|
}
|
|
if _, err := svc.Refresh(t.Context(), refreshToken); err == nil {
|
|
t.Fatal("rotated pending token should not refresh twice")
|
|
}
|
|
}
|
|
|
|
// TestRefreshRejectsExpiredPendingToken 验证过期的待落库令牌不会被接受。
|
|
func TestRefreshRejectsExpiredPendingToken(t *testing.T) {
|
|
repos := newTokenTestRepo(t)
|
|
cfg := &config.Config{}
|
|
cfg.Secrets.JWTSecret = "test-secret"
|
|
svc := NewTokenService(cfg, zap.NewNop(), repos)
|
|
|
|
refreshToken := "expired-pending"
|
|
hash := repository.HashToken(refreshToken)
|
|
svc.trackDelayedStore("user-x", hash, time.Now().Add(-time.Minute))
|
|
|
|
if _, err := svc.Refresh(t.Context(), refreshToken); err == nil {
|
|
t.Fatal("expired pending token should be rejected")
|
|
}
|
|
}
|