价格

架构总览

相关源文件

本页面内容基于以下源文件生成:

LunaTV 是一个基于 Next.js 构建的影视聚合平台,采用现代化的前端技术栈与灵活的后端存储策略,支持多源视频搜索、直播流解析、用户认证与数据持久化等核心功能。该项目通过中间件实现请求拦截与认证,利用适配器模式支持多种存储后端(Redis、Upstash、Kvrocks),并通过运行时配置注入实现环境无关的部署能力。

技术栈与框架基础

Next.js 应用配置与 PWA 支持

项目基于 Next.js 框架构建,采用 standalone 输出模式以支持 Docker 容器化部署。核心配置在 next.config.js:1-79 中定义,包含以下关键特性:

  • 输出模式output: 'standalone' 用于生成独立的部署包,减少镜像体积
  • PWA 集成:通过 next-pwa 插件实现渐进式 Web 应用支持,配置了 Service Worker 注册与预缓存
  • 图片策略:设置 unoptimized: true 禁用 Next.js 图片优化,配合 remotePatterns 允许任意 HTTP/HTTPS 源图片加载
  • Webpack 扩展:自定义 SVG 处理规则,使用 @svgr/webpack 将 SVG 转换为 React 组件,同时保留 ?url 查询参数的文件加载能力
javascript
1// next.config.js 核心配置片段
2const nextConfig = {
3  output: 'standalone',
4  reactStrictMode: false,
5  swcMinify: false,
6  experimental: {
7    instrumentationHook: process.env.NODE_ENV === 'production',
8  },
9  images: {
10    unoptimized: true,
11    remotePatterns: [
12      { protocol: 'https', hostname: '**' },
13      { protocol: 'http', hostname: '**' },
14    ],
15  },
16};

React 组件化架构与 UI 框架集成

UI 层采用 Tailwind CSS 作为样式解决方案,配置文件 tailwind.config.ts:1-91 定义了完整的设计系统:

  • 主题色系:定义了 primary 色板(50-900 共 10 个层级),基于 Sky 色系构建品牌色
  • 响应式断点:新增 mobile-landscape 断点处理横屏移动设备
  • 动画系统:内置 fade-inslide-upslide-downslide-in-from-right 四种过渡动画
  • 插件集成:使用 @tailwindcss/forms 插件统一表单样式

PostCSS 配置在 postcss.config.js:1-6 中定义,仅包含 tailwindcssautoprefixer 两个必要插件,保持构建流程简洁。

代码质量工具链配置

项目配置了完整的代码质量保障体系:

  • ESLint.eslintrc.js:1-45 集成了 TypeScript、React、Next.js、Prettier 规则,并配置了 simple-import-sortunused-imports 插件自动优化导入语句
  • Prettier.prettierrc.js:1-7 定义了统一的代码格式化规则(单引号、箭头函数括号、2 空格缩进)
  • Jestjest.config.js:1-30 配置了测试环境,支持路径别名(@/ 映射到 src/)和 SVG 模块 Mock

核心应用架构与生命周期

Root Layout 与全局状态管理

应用入口组件 src/app/layout.tsx:25-130 实现了多层 Provider 嵌套的全局状态管理架构:

正在加载图表渲染器...

关键实现细节

  1. 环境变量读取:根据 NEXT_PUBLIC_STORAGE_TYPE 环境变量决定配置来源,localstorage 模式直接使用环境变量,其他模式从数据库读取配置(src/app/layout.tsx:42-74
  2. 运行时配置注入:通过 dangerouslySetInnerHTML 将服务端配置注入到 window.RUNTIME_CONFIG 全局变量,使客户端组件能访问服务端配置(src/app/layout.tsx:106-111
  3. 主题系统:使用 next-themes 库实现深色/浅色模式切换,配置 disableTransitionOnChange 避免页面加载时的主题闪烁

服务端配置注入机制

配置管理系统是应用的核心基础设施,src/lib/config.ts:61-331 实现了多层级配置合并与缓存策略:

配置合并流程

  1. 文件配置解析:尝试解析 ConfigFile JSON 字符串,失败时使用空对象(src/lib/config.ts:62-67
  2. 源站点合并:将文件中的 api_site 配置与数据库中的 SourceConfig 合并,已存在的源只更新关键字段,新源标记为 from: 'config'src/lib/config.ts:70-94
  3. 来源标记:不在文件配置中的源标记为 from: 'custom',用于区分用户自定义源(src/lib/config.ts:96-100

缓存策略

typescript
1// src/lib/config.ts:293-315
2export async function getConfig(): Promise<AdminConfig> {
3  // 直接使用内存缓存
4  if (cachedConfig) {
5    return cachedConfig;
6  }
7
8  // 读 db
9  let adminConfig: AdminConfig | null = null;
10  try {
11    adminConfig = await db.getAdminConfig();
12  } catch (e) {
13    console.error('获取管理员配置失败:', e);
14  }
15
16  // db 中无配置,执行一次初始化
17  if (!adminConfig) {
18    adminConfig = await getInitConfig("");
19  }
20  adminConfig = configSelfCheck(adminConfig);
21  cachedConfig = adminConfig;
22  db.saveAdminConfig(cachedConfig);
23  return cachedConfig;
24}

配置自检机制configSelfCheck 函数确保所有必要属性存在且类型正确,防止配置缺失导致的运行时错误(src/lib/config.ts:317-331

应用启动流程与定时任务

Docker 环境下的应用启动由 start.js:14-70 控制,实现了健康检查与定时任务调度:

正在加载图表渲染器...

启动流程关键点

  1. Manifest 生成:启动前执行 generate-manifest.js 脚本生成 PWA 清单文件(start.js:14-22
  2. 健康检查:通过轮询 /login 端点检测服务就绪状态,收到 2xx 响应后停止轮询(start.js:29-57
  3. Cron 调度:服务就绪后立即执行一次 /api/cron 任务,随后每小时重复执行(start.js:42-50

数据持久化与存储策略

多后端存储适配器模式

存储层采用抽象工厂模式,src/lib/db.ts:19-83 定义了统一的存储接口与多种后端实现:

正在加载图表渲染器...

存储后端选择逻辑src/lib/db.ts:19-31):

环境变量值存储实现适用场景
redisRedisStorage传统服务器部署
upstashUpstashRedisStorageServerless/边缘环境
kvrocksKvrocksStorage高性能持久化场景
localstoragenull单机演示/开发环境

数据迁移机制DbManager 构造函数自动触发数据迁移,先执行结构迁移再执行密码哈希迁移,迁移过程异步执行不阻塞启动(src/lib/db.ts:53-66

核心数据模型定义

src/lib/types.ts:4-69 定义了业务核心数据结构与存储契约:

播放记录模型src/lib/types.ts:4-15):

typescript
1export interface PlayRecord {
2  title: string;           // 视频标题
3  source_name: string;     // 来源站点名称
4  cover: string;           // 封面图片 URL
5  year: string;            // 发行年份
6  index: number;           // 当前集数
7  total_episodes: number;  // 总集数
8  play_time: number;       // 播放进度(秒)
9  total_time: number;      // 总时长(秒)
10  save_time: number;       // 保存时间戳
11  search_title: string;    // 搜索时使用的标题
12}

收藏模型src/lib/types.ts:17-27):

typescript
1export interface Favorite {
2  source_name: string;
3  total_episodes: number;
4  title: string;
5  year: string;
6  cover: string;
7  save_time: number;
8  search_title: string;
9  origin?: 'vod' | 'live';  // 内容来源类型
10}

IStorage 接口src/lib/types.ts:29-69)定义了 20+ 个方法,覆盖播放记录、收藏、用户管理、搜索历史、管理员配置等完整业务场景,确保所有存储后端行为一致。

安全认证与请求处理

中间件认证拦截流程

src/middleware.ts:1-138 实现了完整的请求拦截与认证逻辑:

正在加载图表渲染器...

路径白名单src/middleware.ts:117-131):

typescript
1function shouldSkipAuth(pathname: string): boolean {
2  const skipPaths = [
3    '/login',
4    '/warning',
5    '/api/login',
6    '/api/register',
7    '/api/logout',
8    '/api/cron',
9    '/api/server-config',
10    '/_next',
11    '/favicon.ico',
12    '/robots.txt',
13    '/manifest.json',
14    '/icons/',
15    '/logo.png',
16    '/screenshot.png',
17  ];
18  return skipPaths.some((path) => pathname.startsWith(path));
19}

Matcher 配置src/middleware.ts:134-138):使用负向先行断言排除静态资源与认证相关路径,确保中间件只处理需要认证的页面请求。

src/lib/auth.ts:4-65 提供了认证信息的解析与验证能力:

服务端 Cookie 解析src/lib/auth.ts:4-23):

typescript
1export function getAuthInfoFromCookie(request: NextRequest): {
2  password?: string;
3  username?: string;
4  signature?: string;
5  timestamp?: number;
6} | null {
7  const authCookie = request.cookies.get('auth');
8  if (!authCookie) return null;
9  
10  try {
11    const decoded = decodeURIComponent(authCookie.value);
12    const authData = JSON.parse(decoded);
13    return authData;
14  } catch (error) {
15    return null;
16  }
17}

客户端 Cookie 解析src/lib/auth.ts:25-65):

  • 手动解析 document.cookie 字符串
  • 处理双重 URL 编码情况(检测解码后是否仍包含 %
  • 返回包含 role 字段的扩展类型(owner | admin | user

多模式认证策略

存储模式Cookie 内容验证方式
localstoragepassword 明文直接比对环境变量 PASSWORD
其他模式username + signature签名验证(需确认签名算法实现位置)

业务服务与工具库

直播流解析与 EPG 集成

src/lib/live.ts:29-89 实现了直播频道的获取、解析与缓存:

频道获取流程

  1. 缓存检查:优先返回内存中的 cachedLiveChannels[key]
  2. 配置查找:从 LiveConfig 数组中查找匹配的直播源配置
  3. 频道刷新:调用 refreshLiveChannels 获取并解析 M3U 播放列表
  4. EPG 解析:根据 M3U 中的 tvg-url 或配置中的 epg URL 获取电子节目单
  5. 配置更新:将频道数量写回配置并持久化

M3U 解析src/lib/live.ts:51-81):

typescript
1export async function refreshLiveChannels(liveInfo: {
2  key: string;
3  name: string;
4  url: string;
5  ua?: string;
6  epg?: string;
7  from: 'config' | 'custom';
8  channelNumber?: number;
9  disabled?: boolean;
10}): Promise&lt;number&gt; {
11  // 清除旧缓存
12  if (cachedLiveChannels[liveInfo.key]) {
13    delete cachedLiveChannels[liveInfo.key];
14  }
15  
16  // 获取 M3U 内容
17  const ua = liveInfo.ua || defaultUA;
18  const response = await fetch(liveInfo.url, {
19    headers: { 'User-Agent': ua },
20  });
21  const data = await response.text();
22  
23  // 解析 M3U 并获取 EPG
24  const result = parseM3U(liveInfo.key, data);
25  const epgUrl = liveInfo.epg || result.tvgUrl;
26  const epgs = await parseEpg(epgUrl, ua, 
27    result.channels.map(c => c.tvgId).filter(Boolean));
28  
29  // 缓存结果
30  cachedLiveChannels[liveInfo.key] = {
31    channelNumber: result.channels.length,
32    channels: result.channels,
33    epgUrl: epgUrl,
34    epgs: epgs,
35  };
36  
37  return result.channels.length;
38}

图片代理与资源优化

src/lib/utils.ts:4-107 提供了图片处理与视频质量检测工具:

豆瓣图片代理策略src/lib/utils.ts:5-61):

代理类型处理方式适用场景
server通过 /api/image-proxy 服务端代理需要隐藏源地址
cmliussss-cdn-tencent替换域名为 img.doubanio.cmliussss.net使用腾讯 CDN 加速
cmliussss-cdn-ali替换域名为 img.doubanio.cmliussss.com使用阿里 CDN 加速
custom拼接自定义代理 URL自建代理服务

视频质量检测src/lib/utils.ts:63-107):

  • 使用 HLS.js 加载 M3U8 播放列表
  • 通过 HEAD 请求测量网络延迟(ping 时间)
  • 从视频元数据提取分辨率信息
  • 设置 4 秒超时防止长时间等待

时间解析工具

src/lib/time.ts:5-56 提供了自定义时间格式的兼容解析:

支持的格式

  1. ISO 8601:包含 T- 的标准格式
  2. 自定义格式YYYYMMDDHHMMSS +ZZZZ(如 20250824000000 +0800

解析逻辑src/lib/time.ts:5-25):

typescript
1export function parseCustomTimeFormat(timeStr: string): Date {
2  // 标准格式直接返回
3  if (timeStr.includes('T') || timeStr.includes('-')) {
4    return new Date(timeStr);
5  }
6
7  // 处理 "20250824000000 +0800" 格式
8  const match = timeStr.match(
9    /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\s*([+-]\d{4})$/
10  );
11
12  if (match) {
13    const [, year, month, day, hour, minute, second, timezone] = match;
14    const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}${timezone}`;
15    return new Date(isoString);
16  }
17
18  return new Date(timeStr);
19}

核心设计决策与取舍

架构模式选择

  1. 适配器模式用于存储层:通过 IStorage 接口抽象存储操作,支持 Redis、Upstash、Kvrocks 等多种后端,使业务逻辑与存储实现解耦。代价是增加了间接层,但换来了部署灵活性(src/lib/db.ts:19-31

  2. 中间件认证而非 API 路由守卫:在 Edge Middleware 层统一处理认证,避免每个 API 路由重复验证逻辑。限制是无法访问完整 Node.js API,但 Next.js 的 NextRequest/NextResponse 足够满足需求(src/middleware.ts:7-56

  3. 运行时配置注入而非构建时环境变量:通过 window.RUNTIME_CONFIG 将服务端配置传递给客户端,支持同一构建产物在不同环境部署。代价是首屏渲染前需要等待配置加载(src/app/layout.tsx:106-111

  4. 内存缓存 + 数据库持久化:配置数据在内存中缓存,减少数据库访问频率,同时通过 configSelfCheck 确保数据完整性。风险是重启后缓存丢失,但通过启动时自动加载解决(src/lib/config.ts:293-315

  5. Standalone 输出模式:Next.js 配置为 output: 'standalone',生成包含必要依赖的独立目录,显著减小 Docker 镜像体积。代价是无法使用某些依赖动态 require 的特性(next.config.js:4-5

技术选型理由

技术用途选型理由替代方案
Next.js全栈框架SSR/SSG 支持、API 路由、Middleware 能力Nuxt.js、SvelteKit
Tailwind CSS样式方案原子化 CSS、设计系统一致性、构建时优化styled-components、CSS Modules
next-pwaPWA 支持与 Next.js 深度集成、零配置 Service Workerworkbox-webpack-plugin
next-themes主题切换无闪烁切换、系统主题跟随、SSR 友好自定义 Context 实现
Redis/Kvrocks数据存储高性能键值存储、支持过期时间、持久化PostgreSQL、MongoDB
HLS.js视频播放浏览器端 HLS 支持、质量检测能力Video.js、Plyr

已知限制与边界条件

  1. localstorage 模式无持久化:当 STORAGE_TYPE=localstorage 时,存储层返回 null,用户数据仅保存在浏览器本地(src/lib/db.ts:27-29

  2. 图片优化禁用images.unoptimized: true 导致无法使用 Next.js 图片优化 CDN,所有图片原样输出(next.config.js:18-20

  3. SWC 压缩禁用swcMinify: false 回退到 Terser 压缩,可能影响构建速度(next.config.js:11

  4. Cron 依赖内部 HTTP 调用:定时任务通过 http.get 调用 /api/cron,在无外网访问的容器环境可能失败(start.js:60-70

  5. 双重 Cookie 编码兼容:客户端解析需要处理双重 URL 编码情况,增加了复杂度(src/lib/auth.ts:59-65

模块依赖关系

正在加载图表渲染器...

依赖方向说明

  • 应用层 → 服务层RootLayout 依赖 ConfigService 获取站点配置,Middleware 依赖 AuthService 解析认证信息
  • 服务层 → 基础设施层ConfigServiceLiveService 都依赖 DbManager 进行数据持久化
  • 跨层依赖:所有模块都依赖 Types 模块定义的接口与数据结构

关键配置与启动流程

环境变量配置

变量名用途默认值
NEXT_PUBLIC_STORAGE_TYPE存储后端类型localstorage
NEXT_PUBLIC_SITE_NAME站点名称MoonTV
PASSWORD访问密码(必填)
NEXT_PUBLIC_SEARCH_MAX_PAGE搜索最大页数5
NEXT_PUBLIC_DOUBAN_PROXY_TYPE豆瓣代理类型cmliussss-cdn-tencent
NEXT_PUBLIC_FLUID_SEARCH流式搜索开关true

启动命令序列

bash
1# Docker 环境启动流程
21. node start.js
32. 执行 generate-manifest.js 生成 PWA 清单
43. 启动 Next.js standalone server
54. 轮询 /login 检测服务就绪
65. 执行首次 Cron 任务
76. 进入每小时 Cron 循环

配置初始化流程

src/lib/config.ts:185-292 中的 getInitConfig 函数定义了配置初始化逻辑:

  1. 解析传入的配置文件 JSON
  2. 从环境变量读取站点配置(站点名称、公告、搜索页数等)
  3. 从环境变量读取代理配置(豆瓣代理类型、URL)
  4. 初始化用户配置(空用户列表)
  5. 合并文件中的源站点配置
  6. 初始化自定义分类与直播配置