mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
42 KiB
42 KiB
MediaStationGo 重构架构设计方案
版本: v1.0 | 日期: 2026-02-04 | 作者: Architect (Bob)
本文档基于原版 MediaStation(~220 API)与当前 Go 版实现(~50+ API)的差距分析, 设计完整的重构架构方案,涵盖数据模型、文件结构、依赖、任务分解和跨模块约定。
一、差距总览
| 维度 | 原版 (Python) | 当前 Go 版 | 差距 | 优先级 |
|---|---|---|---|---|
| API 端点总数 | ~220 | ~50+ | ~170 个缺失 | - |
| 认证 | access_token + refresh_token | 单一 JWT(24h) | 缺 Token 刷新 + 细粒度权限 | P0 |
| 用户系统 | 19 项细粒度权限 | admin/user 两角色 | 缺完整 RBAC | P0 |
| Emby 兼容层 | ~50 端点 | 无 | 完全缺失 | P0 |
| 站点管理 | 6 种 PT 站类型 + 聚合搜索 | 仅 RSS 订阅 | 完全缺失站点抽象层 | P0 |
| 通知渠道 | Telegram/微信/Bark/Webhook/Email | 无 | 完全缺失 | P0 |
| 下载客户端 | qBittorrent + Transmission + Aria2 | 仅 qBittorrent | 缺 2 个适配器 | P0 |
| 统计端点 | 8 个 | 1 个 (/api/stats) | 缺趋势/热门/监控等 | P1 |
| 批量操作 | 8 个 | 无 | 完全缺失 | P1 |
| 文件管理器 | 浏览/移动/复制/删除/重命名 | 无 | 完全缺失 | P1 |
| API 配置管理 | 9 端点 + 加密存储 | 散落在 Config/Setting | 需统一 ApiConfig 表 | P1 |
| 发现页 | 12 个推荐区块 | 2 个 (trending + popular) | 缺 10 个区块 | P1 |
| DLNA 投屏 | stub (3 端点) | 无 | 可延后 | P2 |
| 授权/Plus | 13 端点 | 无 | 可延后 | P2 |
二、新增/修改的数据模型
2.1 新增模型定义
// ═══════════════════════════════════════════
// 1. 用户权限模型 (替代 admin/user 二元角色)
// ═══════════════════════════════════════════
// UserPermission stores 19 fine-grained permission flags per user.
// Admin users implicitly have all permissions; Plus tier users too.
type UserPermission struct {
ID string `gorm:"primaryKey;size:36)" json:"id"`
UserID string `gorm:"uniqueIndex;size:36;not null" json:"user_id"`
// Default-on permissions (granted to new users)
CanViewDashboard bool `gorm:"default:true" json:"can_view_dashboard"`
CanPlayMedia bool `gorm:"default:true" json:"can_play_media"`
CanCast bool `gorm:"default:true" json:"can_cast"`
CanExternalPlayer bool `gorm:"default:true" json:"can_external_player"`
CanFavorite bool `gorm:"default:true" json:"can_favorite"`
CanViewHistory bool `gorm:"default:true" json:"can_view_history"`
// Restricted permissions (admin-only by default)
CanEditMedia bool `gorm:"default:false" json:"can_edit_media"`
CanRescrape bool `gorm:"default:false" json:"can_rescrape"`
CanUseAI bool `gorm:"default:false" json:"can_use_ai"`
CanCaptureFrames bool `gorm:"default:false" json:"can_capture_frames"`
CanManageDownloads bool `gorm:"default:false" json:"can_manage_downloads"`
CanViewDiscover bool `gorm:"default:false" json:"can_view_discover"`
CanManageSubscriptions bool `gorm:"default:false" json:"can_manage_subscriptions"`
CanManageSites bool `gorm:"default:false" json:"can_manage_sites"`
CanUseAIAssistant bool `gorm:"default:false" json:"can_use_ai_assistant"`
CanManageUsers bool `gorm:"default:false" json:"can_manage_users"`
CanManageFiles bool `gorm:"default:false" json:"can_manage_files"`
CanManageStrm bool `gorm:"default:false" json:"can_manage_strm"`
CanAccessSettings bool `gorm:"default:false" json:"can_access_settings"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ═══════════════════════════════════════════
// 2. Refresh Token 模型
// ═══════════════════════════════════════════
// RefreshToken stores long-lived refresh tokens for token rotation.
type RefreshToken struct {
ID string `gorm:"primaryKey;size:36)" json:"id"`
UserID string `gorm:"index;size:36;not null" json:"user_id"`
TokenHash string `gorm:"uniqueIndex;size:128;not null" json:"-"`
ExpiresAt time.Time `gorm:"index" json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
Revoked bool `gorm:"default:false" json:"revoked"`
}
// ═══════════════════════════════════════════
// 3. 下载客户端模型 (多客户端支持)
// ═══════════════════════════════════════════
// DownloadClient represents a configured download client (qBittorrent / Transmission / Aria2).
type DownloadClient struct {
Base
Name string `gorm:"size:128;not null" json:"name"`
Type string `gorm:"size:32;not null" json:"type"` // qbittorrent / transmission / aria2
Host string `gorm:"size:512;not null" json:"host"` // http://host:port
Port int `json:"port"` // 0 = included in host
Username string `gorm:"size:255" json:"username,omitempty"`
Password string `gorm:"size:255" json:"-"` // stored encrypted or plain
Enabled bool `gorm:"default:true" json:"enabled"`
Category string `gorm:"size:255" json:"category,omitempty"` // default save category
}
// DownloadTask — MODIFY existing: add ClientID, InfoHash, and more fields.
// New fields to ADD to existing model:
// ClientID string `gorm:"size:36" json:"client_id"` // FK -> DownloadClient.ID
// InfoHash string `gorm:"size:64;index" json:"info_hash,omitempty"`
// TotalSize int64 `json:"total_size,omitempty"`
// Progress float32 `json:"progress"` // 0.0-1.0
// SpeedDown int64 `json:"speed_down,omitempty"` // bytes/sec
// SpeedUp int64 `json:"speed_up,omitempty"` // bytes/sec
// Message string `gorm:"size:512" json:"message,omitempty"`
// ═══════════════════════════════════════════
// 4. 站点管理模型 (PT 站 / RSS)
// ═══════════════════════════════════════════
// Site represents a PT site or custom RSS source configuration.
type Site struct {
Base
Name string `gorm:"size:128;not null" json:"name"`
BaseURL string `gorm:"size:1024" json:"base_url"`
SiteType string `gorm:"size:32;not null" json:"site_type"` // nexusphp / gazelle / unit3d / mteam / discuz / custom_rss
AuthType string `gorm:"size:16;not null" json:"auth_type"` // cookie / api_key / auth_header
Cookie string `gorm:"type:text" json:"-"` // encrypted at rest
APIKey string `gorm:"size:512" json:"-"` // encrypted at rest
AuthHeader string `gorm:"size:1024" json:"-"` // encrypted at rest
UserAgent string `gorm:"size:512" json:"user_agent,omitempty"`
RSSURL string `gorm:"size:2048" json:"rss_url,omitempty"`
TimeoutSec int `gorm:"default:30" json:"timeout_sec"`
Priority int `gorm:"default:0" json:"priority"`
UseProxy bool `gorm:"default:false" json:"use_proxy"`
RateLimit int `gorm:"default:0" json:"rate_limit"` // 0 = unlimited
Enabled bool `gorm:"default:true" json:"enabled"`
// Runtime status (not persisted to DB on every request, updated by poller)
LoginStatus *string `gorm:"size:32" json:"login_status,omitempty"` // ok / failed / untested
UploadBytes int64 `json:"upload_bytes,omitempty"`
DownloadBytes int64 `json:"download_bytes,omitempty"`
}
// ═══════════════════════════════════════════
// 5. 通知渠道模型
// ═══════════════════════════════════════════
// NotifyChannel represents a notification delivery channel.
type NotifyChannel struct {
Base
Name string `gorm:"size:128;not null" json:"name"`
ChannelType string `gorm:"size:32;not null" json:"channel_type"` // telegram / wechat_bark / webhook / email
Enabled bool `gorm:"default:true" json:"enabled"`
// Type-specific config stored as JSON blob
// Telegram: { "bot_token": "...", "chat_id": "..." }
// WeChat: { "sendkey": "..." }
// Bark: { "server": "...", "key": "..." }
// Webhook: { "url": "...", "method": "POST", "headers": {} }
// Email: { "smtp_host", "smtp_port", "username", "password", "to_address", "from_name" }
Config string `gorm:"type:text" json:"config,omitempty"`
EncryptedConfig string `gorm:"type:text" json:"-"` // encrypted sensitive fields
// Which events trigger this channel (comma-separated or JSON array)
Events string `gorm:"type:text" json:"events"` // subscription_hit, download_complete, scrape_failed, system_alert
}
// ═══════════════════════════════════════════
// 6. API 配置管理模型 (统一 Provider Key 管理)
// ═══════════════════════════════════════════
// ApiConfig stores external API credentials with optional encryption.
type ApiConfig struct {
ID string `gorm:"primaryKey;size:36)" json:"id"`
Provider string `gorm:"size:64;uniqueIndex;not null" json:"provider"` // tmdb / douban / bangumi / thetvdb / fanart / openai / deepseek / siliconflow / adult
APIKey string `gorm:"size:512" json:"-"` // encrypted
BaseURL string `gorm:"size:512" json:"base_url,omitempty"`
Extra string `gorm:"type:text" json:"extra,omitempty"` // provider-specific extra config (JSON)
Enabled bool `gorm:"default:true" json:"enabled"`
Description string `gorm:"size:255" json:"description,omitempty"`
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
TestResult string `gorm:"size:32" json:"test_result,omitempty"` // success / failure / untested
UpdatedAt time.Time `json:"updated_at"`
}
// ═══════════════════════════════════════════
// 7. STRM 文件模型 (外部存储支持)
// ═══════════════════════════════════════════
// STRMRecord maps a Media item to an external storage URL.
// When Media.Path is a .strm file, its content is read as the real URL.
type STRMRecord struct {
Base
MediaID string `gorm:"uniqueIndex;size:36;not null" json:"media_id"`
URL string `gorm:"size:2048;not null" json:"url"` // actual remote URL
Protocol string `gorm:"size:32" json:"protocol"` // webdav / alist / s3 / http / https
}
// ═══════════════════════════════════════════
// 8. 增强订阅模型 (扩展现有 Subscription)
// ═══════════════════════════════════════════
// Add to existing Subscription:
// TMDbID int `json:"tmdb_id,omitempty"` // link to scraped media
// MediaType string `gorm:"size:32" json:"media_type,omitempty"` // movie / tv / anime
// Year int `json:"year,omitempty"`
// QualityFilter []byte `json:"quality_filter,omitempty"` // ordered priority list (JSON)
// MinSizeMB int `json:"min_size_mb,omitempty"`
// MaxSizeMB int `json:"max_size_mb,omitempty"`
// ExcludeKeys string `gorm:"size:1024" json:"exclude_keywords,omitempty"`
// IncludeKeys string `gorm:"size:1024" json:"include_keywords,omitempty"`
// TotalDownloaded int `json:"total_downloaded,omitempty"`
// Status string `gorm:"size:32;default:active" json:"status"` // active / paused / disabled
// ═══════════════════════════════════════════
// 9. 字幕模型 (独立实体,从 Media 中分离)
// ═══════════════════════════════════════════
// SubtitleTrack represents a subtitle file associated with media.
type SubtitleTrack struct {
Base
MediaID string `gorm:"index;size:36;not null" json:"media_id"`
Language string `gorm:"size:16" json:"language"` // zh / en / und
LanguageName string `gorm:"size:128" json:"language_name,omitempty"`
Path string `gorm:"size:1024;not null" json:"path"`
Source string `gorm:"size:32" json:"source"` // external / embedded / uploaded
Codec string `gorm:"size:16" json:"codec"` // srt / ass / vtt / ssa
IsInternal bool `gorm:"default:false" json:"is_internal"` // embedded in container
StreamIdx int `json:"stream_idx,omitempty"` // for embedded subs
}
2.2 需修改的现有模型
| 模型 | 修改内容 |
|---|---|
| User | 新增字段:Tier (free/plus), Nickname, IsActive, AvatarURL(保留), LastLoginAt(保留) |
| Media | 新增字段:DoubanID, FileHash(SHA256), IsDuplicate, DuplicateOfID, STRMURL, Resolution, AudioChannels, HdrFormat, FrameRate, ColorSpace, BitDepth, Genres(JSON) |
| Library | 新增字段:ScanIntervalMin, MetadataLanguage, AdultContent, PreferNFO, EnableWatch, MinFileSizeMB |
| Playlist | 新增字段:Description, CoverURL |
| PlaybackHistory | 新增字段:DeviceType, IPAddress, PlayedAt(保留 WatchedAt) |
三、新增/修改的文件清单(完整路径)
3.1 后端新增文件
internal/
├── model/
│ ├── permission.go # UserPermission model
│ ├── refresh_token.go # RefreshToken model
│ ├── download_client.go # DownloadClient model
│ ├── site.go # Site model (PT sites)
│ ├── notify_channel.go # NotifyChannel model
│ ├── api_config.go # ApiConfig model
│ ├── strm.go # STRMRecord model
│ └── subtitle_track.go # SubtitleTrack model
│
├── repository/
│ ├── permission_repo.go # UserPermission CRUD
│ ├── refresh_token_repo.go # RefreshToken CRUD
│ ├── download_client_repo.go # DownloadClient CRUD
│ ├── site_repo.go # Site CRUD
│ ├── notify_channel_repo.go # NotifyChannel CRUD
│ ├── api_config_repo.go # ApiConfig CRUD
│ ├── strm_repo.go # STRMRecord CRUD
│ └── subtitle_track_repo.go # SubtitleTrack CRUD
│
├── middleware/
│ ├── permission.go # RequirePermission(permissionKey) middleware
│ └── emby_auth.go # Emby auth (X-Emby-Token / Bearer / username+password)
│
├── service/
│ ├── permission_svc.go # Permission business logic (defaults, checks, grant/revoke)
│ ├── token_svc.go # JWT pair issuance, refresh rotation, revocation
│ ├── download_adapter.go # DownloadClient adapter interface + registry
│ ├── qbittorrent_adp.go # qBittorrent adapter (refactor from qbittorrent.go)
│ ├── transmission_adp.go # Transmission RPC adapter [NEW]
│ ├── aria2_adp.go # Aria2 JSON-RPC adapter [NEW]
│ ├── download_manager_svc.go # Multi-client orchestration (dispatch by client_type)
│ ├── site_svc.go # Site CRUD + test connection + browse resources
│ ├── site_adapter.go # SiteAdapter interface + NexusPHP/Gazelle/UNIT3D/MTeam/Discuz adapters
│ ├── site_search_svc.go # Cross-site aggregated search
│ ├── notify_svc.go # Notification dispatch engine (event → channels)
│ ├── notify_telegram.go # Telegram sender
│ ├── notify_wechat.go # Server酱 (WeChat) sender
│ ├── notify_bark.go # Bark sender
│ ├── notify_webhook.go # Webhook sender
│ ├── notify_email.go # Email (SMTP) sender
│ ├── api_config_svc.go # ApiConfig CRUD + encryption + test connection
│ ├── crypto_svc.go # AES-128-CBC encrypt/decrypt for sensitive data
│ ├── strm_svc.go # STRM file management + protocol whitelist
│ ├── emby_handler_svc.go # Emby API handler (~50 endpoints)
│ ├── douban_scraper.go # Douban metadata provider [NEW]
│ ├── discover_feed_svc.go # Multi-source discovery feed (12 sections)
│ ├── batch_svc.go # Batch operations (scan/scrape/delete/move/favorite/watched/rename/ai-rename)
│ ├── filemanager_svc.go # File browser + move/copy/delete/mkdir/rename
│ ├── duplicate_svc.go # File hash-based duplicate detection
│ ├── stats_enhanced_svc.go # Trend/top-content/top-users/monitor/user-stats
│ ├── sse_hub.go # SSE (Server-Sent Events) event stream hub
│ ├── external_player_svc.go # External player protocol URLs (8 players)
│ ├── thumbnail_svc.go # FFmpeg video frame capture (thumbnail)
│ ├── scheduler_svc.go # Cron-based task scheduler (robfig/cron)
│ └── backup_svc.go # System backup/restore
│
├── handler/
│ ├── permission_handler.go # GET/PUT user permissions, POST reset
│ ├── refresh_handler.go # POST /api/auth/refresh
│ ├── download_client_handler.go # CRUD + test for download clients
│ ├── site_handler.go # Site CRUD + test + browse + userdata
│ ├── site_search_handler.go # GET /api/search/sites (cross-site search)
│ ├── notify_handler.go # NotifyChannel CRUD + test
│ ├── api_config_handler.go # ApiConfig CRUD + test + providers list
│ ├── strm_handler.go # STRM config + media-level STRM URL management
│ ├── emby_router.go # All Emby-compatible routes (~50 routes)
│ ├── douban_search_handler.go # GET /api/search/douban
│ ├── discover_feed_handler.go # GET /api/discover/feed, GET /api/discover/sections
│ ├── batch_handler.go # 8 batch operation endpoints
│ ├── filemanager_handler.go # File browser + operations
│ ├── duplicate_handler.go # Hash compute + scan + list + unmark
│ ├── stats_enhanced_handler.go # 7 new stats endpoints
│ ├── sse_handler.go # GET /api/system/events (SSE stream)
│ ├── external_player_handler.go# External URLs + protocols
│ ├── thumbnail_handler.go # GET /api/media/:id/thumbnail
│ ├── subtitle_enhanced_handler.go # Upload/scan/extract/manage subtitles
│ ├── scheduler_handler.go # Scheduler management
│ └── backup_handler.go # Backup/restore
│
└── emby_types.go # Emby-specific DTOs (User, Item, Session, etc.)
3.2 后端需修改的文件
| 文件 | 修改内容 |
|---|---|
model/model.go |
添加新模型引用到 AllModels();修改 User/Media/Library/Playlist/PlaybackHistory 结构体 |
repository/repository.go |
Container 添加新 Repository 字段 |
middleware/middleware.go |
添加 RequirePermission 构造函数 |
service/service.go |
Container 添加新 Service 字段;Boot() 启动新后台服务 |
service/auth.go |
支持 Tier 字段;签发 access_token + refresh_token 对;集成权限检查 |
handler/handler.go |
Register() 注册所有新路由分组 |
config/config.go |
新增通知配置段、DLNA 配置段、调度器配置段 |
cmd/server/main.go |
初始化新服务 |
3.3 前端新增文件
web/src/
├── pages/
│ ├── SitesPage.tsx # 站点管理页面
│ ├── SiteSearchPage.tsx # 跨站资源搜索页面
│ ├── FileManagerPage.tsx # 文件管理器页面
│ ├── SettingsPage.tsx # 系统设置页面(多 Tab)
│ │ # 内含子组件:
│ │ ├── components/settings/
│ │ │ ├── GeneralTab.tsx # 通用设置 Tab
│ │ │ ├── AccountTab.tsx # 账户设置 Tab
│ │ │ ├── UsersTab.tsx # 用户管理 Tab
│ │ │ ├── LibrariesTab.tsx # 媒体库设置 Tab
│ │ │ ├── ScrapeOrganizeTab.tsx# 整理与刮削设置 Tab
│ │ │ ├── DownloadTab.tsx # 下载客户端设置 Tab
│ │ │ ├── NotifyTab.tsx # 通知渠道设置 Tab
│ │ │ ├── SchedulerTab.tsx # 定时任务设置 Tab
│ │ │ ├── SystemTab.tsx # 系统设置 Tab
│ │ │ ├── ApiConfigTab.tsx # API 配置 Tab
│ │ │ ├── LicenseTab.tsx # 授权管理 Tab
│ │ │ └── AdultTab.tsx # Adult Provider 设置 Tab
│ │ ├── ConfigGroup.tsx # 配置表单通用组件
│ │ └── ConfigRow.tsx # 配置行组件
│ ├── StatsEnhancedPage.tsx # 增强统计仪表盘
│ ├── StrmPage.tsx # STRM 文件管理页面
│ ├── HistoryPage.tsx # 观看历史页面
│ ├── DlnaPage.tsx # DLNA 投屏页面
│ ├── AiAssistantPage.tsx # AI 助手对话页面
│ ├── PosterWallPage.tsx # 海报墙视图
│ └── LicensePage.tsx # 授权管理页面
│
├── api/
│ ├── permission.ts # 权限 API (get/update/reset)
│ ├── refresh.ts # Token 刷新 API
│ ├── downloadClient.ts # 下载客户端 API
│ ├── site.ts # 站点 API
│ ├── siteSearch.ts # 跨站搜索 API
│ ├── notify.ts # 通知渠道 API
│ ├── apiConfig.ts # API 配置管理 API
│ ├── strm.ts # STRM API
│ ├── douban.ts # 豆瓣搜索 API
│ ├── discoverFeed.ts # 发现聚合 API (多源 feed)
│ ├── batch.ts # 批量操作 API
│ ├── filemanager.ts # 文件管理器 API
│ ├── duplicate.ts # 重复检测 API
│ ├── statsEnhanced.ts # 增强统计 API
│ ├── sse.ts # SSE 连接工具
│ ├── externalPlayer.ts # 外部播放器 API
│ ├── thumbnail.ts # 截图 API
│ ├── subtitleEnhanced.ts # 增强字幕 API (上传/提取/删除)
│ ├── scheduler.ts # 定时任务 API
│ ├── backup.ts # 备份/恢复 API
│ ├── license.ts # 授权 API
│ └── dlna.ts # DLNA API
│
├── stores/
│ ├── permissions.ts # 权限状态 store (Zustand)
│ ├── settings.ts # 系统设置 store (Zustand)
│ ├── notifications.ts # 通知消息 store (Zustand)
│ └── sse.ts # SSE 连接 store (Zustand)
│
├── hooks/
│ ├── usePermission.ts # usePermission(key) hook
│ ├── useSSE.ts # SSE 事件流 hook
│ └── useExternalPlayer.ts # 外部播放器协议生成 hook
│
├── components/
│ ├── settings/ # 设置页子组件目录
│ ├── FileTree.tsx # 文件树组件
│ ├── FileTreeNode.tsx # 文件树节点组件
│ ├── PermissionGuard.tsx # 权限守卫组件 (<PermissionGuard permission="can_play_media">)
│ ├── DiscoverSection.tsx # 发现页区块组件
│ ├── SiteCard.tsx # 站点卡片组件
│ ├── DownloadClientCard.tsx # 下载客户端卡片组件
│ ├── NotifyChannelCard.tsx # 通知渠道卡片组件
│ ├── ApiConfigCard.tsx # API 配置卡片组件
│ ├── BatchOperationBar.tsx # 批量操作栏组件
│ ├── PlayerProtocolList.tsx # 播放器协议列表组件
│ ├── StatsChart.tsx # 统计图表组件
│ └── AppEmpty.tsx # 空状态占位组件
│
└── types/
└── index.ts # 扩展: 添加新类型定义
3.4 前端需修改的文件
| 文件 | 修改内容 |
|---|---|
App.tsx |
新增 ~15 个路由 (Sites/FileManager/Settings/StatsEnhanced/Strm/History/DLNA/AiAssistant/PosterWall/License);添加 <PermissionGuard> 路由级守卫 |
stores/auth.ts |
新增 permissions 对象、tokenRefresh() 方法、tier 字段 |
components/RequireAuth.tsx |
集成权限检查逻辑 |
components/Layout.tsx |
侧边栏新增菜单项(站点管理/文件管理/STRM/DLNA/统计/设置) |
types/index.ts |
新增所有新模型的 TypeScript 类型 |
四、依赖包列表
4.1 Go 新增依赖
# 已有依赖保持不变,新增:
github.com/robfig/cron/v3 v3.0.1 # 定时任务调度器 (APScheduler 替代)
golang.org/x/crypto/v0 latest # crypto/aes (AES-128-CBC 加密)
github.com/go-resty/resty/v3 v3.10.0 # HTTP client (Transmission/Aria2/Site 调用)
github.com/gabriel-vasile/mimetype v1.4.2 # MIME 类型检测 (字幕格式识别)
github.com/disintegration/imaging v4.0.0 # 图片处理 (缩略图截取/缩放)
4.2 npm 新增依赖
# 已有依赖保持不变,新增:
@mui/icons-material ^5.14.0 # Material Design 图标库(设置页需要大量图标)
recharts ^2.5.0 # React 图表库(统计图表)
react-virtuoso ^4.6.0 # 虚拟滚动(大列表性能)
dayjs ^1.11.10 # 轻量日期库(替代 moment.js)
framer-motion ^11.0.0 # 动画库(发现页过渡动画)
五、任务分解(按实现顺序)
T01: 项目基础设施增强(认证+权限+加密+SSE 核心)
优先级: P0 | 依赖: 无 | 预估代码量: ~2000 行
| 类别 | 文件 |
|---|---|
| 新模型 | model/permission.go, model/refresh_token.go, model/api_config.go |
| Repository | permission_repo.go, refresh_token_repo.go, api_config_repo.go |
| Service | service/permission_svc.go, service/token_svc.go, service/crypto_svc.go, service/sse_hub.go |
| Middleware | middleware/permission.go |
| Handler | handler/permission_handler.go, handler/refresh_handler.go, handler/api_config_handler.go |
| Handler (mod) | handler/handler.go (注册新路由) |
| Service (mod) | service/auth.go (支持 token 对 + tier), service/service.go (Container 扩展) |
| Model (mod) | model/model.go (AllModels 扩展, User 字段扩展) |
| 前端 | stores/auth.ts (permissions + refresh), stores/sse.ts, stores/permissions.ts, hooks/usePermission.ts, hooks/useSSE.ts, components/PermissionGuard.tsx, types/index.ts (扩展), api/permission.ts, api/refresh.ts, api/apiConfig.ts |
核心交付物:
- 19 项细粒度权限系统的完整链路(模型→仓库→中间件→服务→Handler→前端守卫)
- Access Token (60min) + Refresh Token (30天) 双令牌机制
- AES-128-CBC 加密服务(敏感数据加解密,兼容明文迁移)
- SSE 事件流 Hub(替代 WebSocket 的备选实时通道)
- ApiConfig 表 + 9 个管理端点(统一 API Key 管理 + 加密存储 + 测试连接)
T02: 多下载客户端 + 通知渠道 + 定时任务
优先级: P0 | 依赖: T01(加密服务用于密码加密) | 预估代码量: ~3000 行
| 类别 | 文件 |
|---|---|
| 新模型 | model/download_client.go |
| Repository | download_client_repo.go, notify_channel_repo.go |
| Service | service/download_adapter.go (接口), service/qbittorrent_adp.go (重构), service/transmission_adp.go, service/aria2_adp.go, service/download_manager_svc.go, service/notify_svc.go, service/notify_telegram.go, service/notify_wechat.go, service/notify_bark.go, service/notify_webhook.go, service/notify_email.go, service/scheduler_svc.go |
| Handler | handler/download_client_handler.go, handler/notify_handler.go, handler/scheduler_handler.go |
| Handler (mod) | handler/handler.go, handler/downloads.go (改为通过 DownloadManager) |
| Service (mod) | service/downloads.go (重构为多客户端分发), service/service.go (Boot 启动 scheduler + notifier) |
| 前端 | api/downloadClient.ts, api/notify.ts, api/scheduler.ts, components/DownloadClientCard.tsx, components/NotifyChannelCard.tsx |
核心交付物:
- DownloadClient 适配器模式(Interface + qBittorrent/Transmission/Aria2 三实现)
- 5 种通知渠道(Telegram/Server酱/Bark/Webhook/Email)完整发送链路
- 事件驱动通知引擎(subscription_hit / download_complete / scrape_failed / system_alert)
- 基于 robfig/cron 的定时任务调度器(替代 APScheduler),内置 6 个预配置任务
- 下载客户端热插拔(运行时添加/移除/测试连接)
T03: 站点管理 + Emby API 兼容层
优先级: P0 | 依赖: T01(加密服务用于 Cookie/APIKey 存储) | 预估代码量: ~4000 行
| 类别 | 文件 |
|---|---|
| 新模型 | model/site.go |
| Repository | site_repo.go |
| Service | service/site_svc.go, service/site_adapter.go (接口+6种适配器), service/site_search_svc.go, service/emby_handler_svc.go, service/strm_svc.go |
| Model | model/strm.go, emby_types.go |
| Handler | handler/site_handler.go, handler/site_search_handler.go, handler/emby_router.go (~50 路由), handler/strm_handler.go |
| Middleware | middleware/emby_auth.go |
| 前端 | api/site.ts, api/siteSearch.ts, api/strm.ts, pages/SitesPage.tsx, pages/SiteSearchPage.tsx, pages/StrmPage.tsx, components/SiteCard.tsx |
核心交付物:
- 6 种 PT 站点类型适配器(NexusPHP / Gazelle / UNIT3D / MTeam / Discuz / Custom RSS)
- 3 种认证方式(Cookie / API Key / Authorization Header),全部加密存储
- 跨站聚合搜索(并发搜索多个站点,合并去重排序)
- 站点资源浏览器(分页浏览种子列表)
- Emby API 兼容层(~50 端点):认证 → 系统信息 → 媒体库 → Items → PlaybackInfo → 流代理 → 字幕 → 进度上报
- STRM 文件管理(外部存储以"文件"形式入库,WebDAV/Alist/S3/HTTP 协议白名单校验)
- Emby 认证中间件(X-Emby-Token / Bearer / Username+Password 三种方式)
T04: 增强功能集(发现页+统计+批量操作+文件管理+播放增强+刮削增强)
优先级: P1 | 依赖: T01, T02, T03 | 预估代码量: ~3500 行
| 类别 | 文件 |
|---|---|
| Service | service/discover_feed_svc.go (12 区块), service/stats_enhanced_svc.go, service/batch_svc.go, service/filemanager_svc.go, service/duplicate_svc.go, service/douban_scraper.go, service/external_player_svc.go, service/thumbnail_svc.go, service/backup_svc.go |
| Model | model/subtitle_track.go |
| Repository | subtitle_track_repo.go, strm_repo.go |
| Handler | handler/discover_feed_handler.go, handler/stats_enhanced_handler.go, handler/batch_handler.go, handler/filemanager_handler.go, handler/duplicate_handler.go, handler/douban_search_handler.go, handler/external_player_handler.go, handler/thumbnail_handler.go, handler/subtitle_enhanced_handler.go, handler/backup_handler.go, handler/recycle.go (扩展) |
| 前端 (pages) | pages/SettingsPage.tsx (+12个子Tab组件), pages/StatsEnhancedPage.tsx, pages/FileManagerPage.tsx, pages/HistoryPage.tsx, pages/PosterWallPage.tsx, pages/AiAssistantPage.tsx, pages/DlnaPage.tsx, pages/LicensePage.tsx |
| 前端 (api) | api/douban.ts, api/discoverFeed.ts, api/batch.ts, api/filemanager.ts, api/duplicate.ts, api/statsEnhanced.ts, api/externalPlayer.ts, api/thumbnail.ts, api/subtitleEnhanced.ts, api/backup.ts, api/license.ts, api/dlna.ts, api/sse.ts |
| 前端 (components) | components/settings/* (12个Tab), components/FileTree.tsx, components/FileTreeNode.tsx, components/DiscoverSection.tsx, components/BatchOperationBar.tsx, components/PlayerProtocolList.tsx, components/StatsChart.tsx, components/AppEmpty.tsx |
| 前端 (stores) | stores/settings.ts, stores/notifications.ts |
| 前端 (hooks) | hooks/useExternalPlayer.ts |
| 前端 (mod) | App.tsx (15个新路由 + 权限守卫), Layout.tsx (侧边栏扩展), types/index.ts (类型扩展) |
核心交付物:
- 增强发现页(12 个推荐区块:本地最近电影/剧集 + TMDb 4 区块 + 豆瓣 4 区块 + Bangumi 每日)
- 豆瓣刮削器(DoubanProvider,补充中文元数据,Cookie 认证)
- 增强统计(8 端点:概览/播放趋势/热门内容/活跃用户/媒体库统计/系统监控/用户统计/记录播放)
- 批量操作(8 个端点:扫描/刮削/删除/移动/收藏/标记已看/重命名/AI重命名)
- 文件管理器(目录浏览 + 移动/复制/删除/创建文件夹/重命名/重命名预览)
- 外部播放器直链(8 种协议:PotPlayer/VLC/IINA/Infuse/NPlayer/MX/MPV/MPC-HC)
- 视频截帧(FFmpeg thumbnail 提取,用于卡片封面)
- 增强字幕管理(上传/扫描外挂/检测内嵌/提取内嵌/删除)
- 文件哈希重复检测(SHA256 哈希计算 + 重复标记/取消)
- 系统备份/恢复(SQLite 数据库备份)
- DLNA stub(设备发现/投屏框架,可后续完善)
- 授权/Plus 系统(13 端点框架,可后续接入验证服务器)
T05: 集成优化 + 测试 + 文档
优先级: P1 | 依赖: T01-T04 全部完成 | 预估代码量: ~1000 行
| 类别 | 文件 |
|---|---|
| 配置 | config/config.go (新增段: Notify, DLNA, Scheduler, License) |
| 入口 | cmd/server/main.go (初始化所有新服务) |
| 集成 | service/service.go (最终 Container + Boot + Close) |
| 路由整合 | handler/handler.go (最终全部路由注册) |
| 文档 | docs/refactor-migration-guide.md (迁移指南) |
| 测试 | 各 service 对应的 _test.go 补充 |
核心交付物:
- 所有新服务的启动/关闭生命周期整合
- 最终路由注册(目标 ~220 个端点)
- 配置文件 Schema 更新(新增 Notify/DLNA/Scheduler/License 段)
- 迁移指南文档(从旧版升级的数据变更说明)
六、共享知识(跨文件约定)
6.1 错误码规范
// 应用错误码常量 (package apperr)
const (
ErrOK = 0
ErrInvalidParams = 40001
ErrUnauthorized = 40101
ErrForbidden = 40301
ErrNotFound = 40401
ErrConflict = 40901
ErrRateLimit = 42901
ErrInternal = 50001
ErrExternalService = 50201 // 第三方服务不可达
ErrScraperFailed = 50401 // 刮削超时/失败
ErrTranscodeFailed = 50501 // FFmpeg 转码失败
ErrDownloadClient = 50601 // 下载客户端错误
ErrSiteAuthFailed = 50701 // 站点认证失败
ErrEncryptFailed = 50801 // 加密操作失败
)
// 统一错误响应格式
type APIResponse struct {
Code int `json:"code"` // 业务错误码
Data any `json:"data,omitempty"` // 成功时的 payload
Message string `json:"message,omitempty"` // 人可读错误信息
}
6.2 API 响应格式约定
成功响应 (2xx):
{
"code": 0,
"data": { ... },
"message": "ok"
}
分页列表响应:
{
"code": 0,
"data": {
"items": [...],
"total": 150,
"page": 1,
"page_size": 50
}
}
错误响应 (non-2xx):
{
"code": 40101,
"data": null,
"message": "token expired"
}
6.3 权限检查约定
// 后端中间件用法
r.POST("/media/:id/scrape",
middleware.AuthRequired(secret),
middleware.RequirePermission("can_rescrape"), // NEW
middleware.AdminRequired(), // still works (implies all permissions)
scrapeOneHandler(svc),
)
// 权限解析优先级:
// 1. role == "admin" → 自动拥有所有权限(跳过 DB 查询)
// 2. tier == "plus" → 自动拥有所有权限(跳过 DB 查询)
// 3. 普通 user → 查询 user_permissions 表,逐字段判断
// 前端路由守卫用法
<Route path="discover" element={
<PermissionGuard permission="can_view_discover">
<DiscoverPage />
</PermissionGuard>
} />
6.4 敏感数据加密约定
// 加密标识前缀
const EncryptPrefix = "enc:v1:"
// 加密流程:
// 1. 检查值是否已以 enc:v1: 开头 → 是则跳过(避免双重加密)
// 2. 使用 AES-128-CBC 加密,密钥从 APP_SECRET_KEY 派生 (PBKDF2 + SHA256)
// 3. 存储为 enc:v1:<base64_ciphertext>
// 解密流程:
// 1. 检查是否以 enc:v1: 开头 → 否则返回原文(明文兼容)
// 2. AES-128-CBC 解密
// 3. 返回明文
// 适用字段: Site.Cookie/Site.APIKey/Site.AuthHeader /
// DownloadClient.Password / NotifyChannel.EncryptedConfig /
// ApiConfig.APIKey / ApiConfig.Extra
6.5 Emby API 兼容层约定
路由前缀: /emby/
认证方式 (按优先级):
1. X-Emby-Token: <token> (Emby 标准头)
2. Authorization: Bearer <token> (标准 JWT)
3. query: ?token=<token> (URL 参数,用于<video> src)
4. POST body: {Username, Password} (Emby AuthenticateByName)
Emby Token 与内部 JWT 的映射关系:
- Emby 认证成功后,返回 EmbyUserId + AccessToken (即内部 JWT)
- 后续请求通过映射表查找对应用户
6.6 事件系统约定
// 支持两种推送通道:
// 1. WebSocket (已有) — 用于需要双向通信的场景 (scan progress, transcode status)
// Topics: scan | scrape | transcode | download
// 2. SSE (新增) — 用于服务端单向推送场景 (notification, task progress)
// Events: notification | task_progress | alert
// 认证: 一次性 OTP ticket (GET /api/system/events/ticket → 10s 有效)
// 或 Authorization Bearer (降级方案)
// 通知事件类型:
const (
EventSubscriptionHit = "subscription_hit" // 订阅命中新资源
EventDownloadComplete = "download_complete" // 下载完成
EventScrapeFailed = "scrape_failed" // 刮削失败
EventSystemAlert = "system_alert" // 系统告警 (磁盘满/CPU高等)
)
6.7 下载客户端适配器接口
type DownloadAdapter interface {
// 生命周期
Initialize(ctx context.Context, cfg DownloadClientConfig) error
Ping(ctx context.Context) error
// 任务操作
AddTorrent(ctx context.Context, url, savePath string) (string, error) // returns hash/id
Pause(ctx context.Context, hash string) error
Resume(ctx context.Context, hash string) error
Remove(ctx context.Context, hash string, deleteFiles bool) error
// 状态查询
List(ctx context.Context, filter string) ([]TorrentInfo, error)
GetGlobalStats(ctx context.Context) (*GlobalStats, error) // Aria2 only
}
七、任务依赖关系图
graph TD
T01[T01: 基础设施<br/>认证+权限+加密+SSE] --> T03
T01 --> T02
T02[T02: 多下载客户端<br/>+通知+定时任务] --> T04
T03[T03: 站点管理<br/>+Emby兼容层] --> T04
T04[T04: 增强功能集<br/>发现+统计+批量+文件管理+播放增强] --> T05
T05[T05: 集成优化+测试+文档]
style T01 fill:#e1f5fe
style T02 fill:#fff3e0
style T03 fill:#fce4ec
style T04 fill:#f3e5f5
style T05 fill:#e8f5e9
八、预估代码规模汇总
| 任务 | 新增后端文件 | 新增前端文件 | 修改文件 | 预估行数 |
|---|---|---|---|---|
| T01 | ~18 | ~14 | ~8 | ~2000 |
| T02 | ~16 | ~8 | ~4 | ~3000 |
| T03 | ~14 | ~8 | ~4 | ~4000 |
| T04 | ~20 | ~35 | ~5 | ~3500 |
| T05 | ~2 | ~2 | ~5 | ~1000 |
| 合计 | ~70 | ~67 | ~26 | ~13500 |
对比当前代码库(Go ~5000 行,前端 ~3000 行),本次重构将使代码总量增长约 2.5 倍。
九、实施建议
分期策略
- Phase 1(必须做): T01 + T02 → 核心基础设施和多客户端/通知,解决最大痛点
- Phase 2(应该做): T03 → Emby 兼容层是差异化竞争力(让 Infuse/Kodi 直连)
- Phase 3(最好做): T04 → 功能完整性(对标原版 ~220 API)
- Phase 4(收尾): T05 → 打磨质量
可并行化的独立模块
- Emby 兼容层与站点管理可以独立开发(仅共享基础模型/T01)
- 通知渠道之间完全独立(每种渠道一个文件)
- 下载客户端适配器之间完全独立
- 前端各页面可以并行开发
风险控制
- Emby 兼容层复杂度最高:建议先实现核心 15 个端点(认证+媒体列表+播放+进度),再逐步补全
- PT 站适配器:每个站点类型的 HTML 解析差异大,建议先实现 NexusPHP(覆盖最广),其他用通用 RSS 替代
- 加密迁移:必须保证旧明文数据的读取兼容(detect prefix 策略)