Skip to content

Architecture

bestZwei edited this page Sep 9, 2026 · 3 revisions

架构

技术栈

技术 说明
框架 Next.js 15(App Router)+ React 19 服务端 API Routes 与客户端页面同仓
语言 TypeScript(strict) 全量类型覆盖
样式 Tailwind CSS(构建期编译)+ CSS 变量主题 亮暗双主题,darkMode: 'class'
数据获取 TanStack Query 搜索/详情/推荐数据的缓存、重试、失效
客户端状态 Zustand(persist → localStorage) 点播源/直播源列表、订阅与播放设置
持久化 Dexie(IndexedDB) 观看历史、播放进度、搜索历史
播放 ArtPlayer + hls.js 广告过滤 loader、代理回退

目录结构

libretv/
├── src/
│   ├── app/
│   │   ├── page.tsx                # 首页:搜索 + 推荐区(?s= URL 驱动)
│   │   ├── watch/page.tsx          # 播放页(唯一入口,URL 即状态)
│   │   ├── live/page.tsx           # 直播页(?url=&name=&tvgId=&epg= 深链)
│   │   ├── about/page.tsx
│   │   ├── layout.tsx              # 主题无闪烁内联脚本 + Providers
│   │   ├── manifest.ts             # PWA manifest
│   │   └── api/                    # 服务端 Route Handlers
│   │       ├── auth/route.ts       # 登录/登出/会话状态
│   │       ├── status/route.ts     # 站点状态(含预置采集站/直播源下发)
│   │       ├── search/route.ts     # 聚合搜索(并行 + 失败隔离 + 过滤)
│   │       ├── detail/route.ts     # 详情(列表接口 → 详情页 HTML 降级)
│   │       ├── douban/route.ts     # 豆瓣推荐(直连 + 缓存 + 代理降级)
│   │       ├── bangumi/calendar/   # Bangumi 每日放送(免 key)
│   │       ├── hot-list/route.ts   # 影视榜单(60s API:豆瓣周榜 + 百度热播)
│   │       ├── proxy/[url]/route.ts# 点播流式代理(SSRF 防护 + m3u8 重写)
│   │       └── live/               # 直播模块 API
│   │           ├── playlist/       # M3U 订阅拉取解析(缓存 10min + format=m3u 导出)
│   │           ├── epg/            # XMLTV 节目单(gzip、24h 窗口、缓存 6h)
│   │           ├── probe/          # 分片级批量测活(单批 50、并发 8)
│   │           └── stream/[url]/   # 直播流长连接代理(响应头 15s 超时,body 不限时长)
│   ├── components/                 # UI 组件(见下)
│   └── lib/                        # 纯逻辑库(可单元测试)
│       ├── cms-parser.ts           # Apple CMS 解析(列表/详情/详情页爬取)
│       ├── m3u-parser.ts           # M3U 直播播放列表解析(tvg-id/logo/分组、去重)
│       ├── xmltv.ts                # XMLTV 解析(gzip、时区、时间窗裁剪、当前/下一档)
│       ├── live-cache.ts           # 直播大对象 TTL 缓存(EPG,独立于通用缓存)
│       ├── env-sources.ts          # DEFAULT_SOURCES 环境变量解析
│       ├── env-live-sources.ts     # DEFAULT_LIVE_SOURCES 环境变量解析
│       ├── m3u8.ts                 # m3u8 重写与广告过滤
│       ├── ssrf.ts                 # 内网地址识别与 DNS 校验
│       ├── auth.ts                 # 会话签名/校验、登录限流
│       ├── douban.ts               # 豆瓣数据获取(降级链 + TTL 缓存)
│       ├── bangumi.ts              # Bangumi 每日放送(免 key,星期分组)
│       ├── douban-weekly.ts        # 影视榜单聚合(60s API,六个榜单归一化)
│       ├── fetch-utils.ts          # 超时/重试/内存缓存/最终重定向 URL
│       ├── db.ts                   # Dexie schema 与历史/进度 CRUD
│       ├── store.ts                # Zustand 全局设置(含直播订阅/收藏/最近)
│       ├── client-api.ts           # 客户端 API 封装(401 → 全局登录框)
│       └── utils.ts                # 时间格式化、图片代理 URL、sha256 等
├── docs/                           # 本 Wiki
├── Dockerfile / docker-compose.yml
└── README.md

组件一览

组件 职责
providers.tsx QueryClient + Theme + Toast + Auth 四层 Provider 嵌套
theme.tsx 亮暗主题上下文与三态切换按钮(light/dark/system)
auth.tsx 认证上下文;监听全局 401 事件弹出登录框
header.tsx 顶部导航 + 通用抽屉(Drawer)骨架
search(page.tsx 内联) 搜索框、历史 chips、结果网格、空态引导
douban-section.tsx 首页推荐区(RecommendSection):豆瓣 / Bangumi / 影视榜单三源按设置分支渲染
video-card.tsx 搜索结果卡片 / 豆瓣卡片(封面多级降级)
detail-modal.tsx 详情弹窗(海报 + 元信息 + 剧集 + 复制链接)
source-manager.tsx 设置抽屉:点播源 CRUD、数据源订阅(点播源 + 直播源)、过滤开关、封面加载方式、配置导入导出
history-panel.tsx 观看历史抽屉(Dexie liveQuery 实时刷新)
player-shell.tsx ArtPlayer/HLS 封装:广告过滤、快捷键、长按倍速、代理回退
live-player.tsx 直播播放器(独立于点播):协议分发(hls.js/mpegts.js 动态加载)、代理回退、H.265 提示
live-channel-list.tsx 直播频道侧栏:分组标签条、搜索、收藏/最近、批量测活状态点与筛选
live-epg-panel.tsx 节目单面板:当前节目进度、接下来列表、按需查询
live-source-manager.tsx 设置抽屉「直播源」分区:订阅增删、探活、启用勾选、导出 .m3u
switch-source.tsx 换源面板:跨源搜索 → 同名匹配 → 测速排序 → 切换
toast.tsx 并行堆叠 Toast + 全局 Loading 遮罩

数据流

搜索

浏览器                      服务端                        上游
  │ POST /api/search          │                            │
  │ {wd, sources[], filter}   │── 并行 fetch(8s 超时)────→│ 采集站 ×N
  │                           │← JSON ─────────────────────│
  │← {list, failures[]} ──────│ 合并/去重/排序/成人过滤      │
  • 客户端把自定义点播源配置随请求上传(源配置仅存于用户浏览器);
  • 单源失败进入 failures,不影响其他源结果;
  • React Query 以 ['search', wd, selectedKeys, filter] 为缓存键。

播放

/watch?source=key&id=vodId&index=n&title=..&sourceUrl=..
  │
  ├─ resolveSource(): 从 Zustand store 取点播源配置(URL 参数兜底,支持分享链接)
  ├─ GET /api/detail → episodes[] → 当前集 URL
  └─ PlayerShell(ArtPlayer+hls)
       ├─ 直连 m3u8
       └─ 致命网络错误 → /api/proxy/<url> 重试一次(cookie 鉴权)

进度:video:timeupdate(5s 节流)+ 暂停 + 卸载 → IndexedDB progress; 历史:进入播放页 2s 后 upsert,仅存定位信息(sourceKey + vodId + episodeIndex),不存全集 URL。

换源

switch-source.tsxPOST /api/search(按标题)→ 每源取完全同名结果(否则第一条)→ GET /api/detail 并发测速(接口耗时)→ 按速度排序展示 → 切换时保留集数索引。

直播

/live?url=..&name=..&tvgId=..&epg=..
  │
  ├─ GET /api/live/playlist?url=<m3u>   (服务端拉取解析,缓存 10min)
  │     └─ m3u-parser → LiveChannel[] → 分组合并去重
  ├─ LivePlayer
  │     ├─ m3u8 → hls.js(直播参数)/ .flv → mpegts.js(动态 import)
  │     └─ 直连失败 → /api/live/stream/<url> 重试(响应头 15s 超时,body 长连接透传)
  ├─ GET /api/live/probe                (批量分片级测活:manifest → variant → 分片)
  └─ GET /api/live/epg?url=<xmltv>&channel=<tvgId>   (服务端缓存 6h,按需查询)

直播订阅/启用/收藏/最近观看存于 zustand persist;频道列表不落库。详见 Live-IPTV

与旧版的架构差异

维度 旧版 本版
API 层 浏览器内 monkey-patch window.fetch 假装 /api/* 真实服务端 Route Handlers
聚合搜索位置 用户浏览器(IP 暴露、无法缓存) 服务端
鉴权 页面下发 sha256(password),auth= 哈希即凭证 httpOnly cookie + HMAC 签名会话
m3u8 分片 重写为 /proxy/... 但丢失鉴权参数 → 401 cookie 同源自动携带,天然通过
页面间状态 URL + localStorage 双总线(10+ key) URL 即状态 + IndexedDB
播放入口 watch.html → player.html 跳转链 单一 /watch 路由
转义 4 种手工转义函数,存在 XSS 缺口 React 自动转义,无 innerHTML
主题 CSS 变量 + class 策略,三态切换

Clone this wiki locally