高性价比的 HLS 加密流媒体网关部署指南


在当今内容为王的时代,视频作品和付费课件已成为版权保护和知识付费的核心资产。然而,大多数开发者和创作者在部署视频分发系统时,往往会面临三个极其头疼的痛点:

  • 资产“裸奔”与嗅探噩梦:由于缺乏物理层面的切片加密,任何用户只需打开浏览器 F12 或使用视频嗅探插件,就能轻松抓取到 .ts 切片地址,甚至直接下载完整的视频源文件。
  • 高昂的流量与存储账单:传统的云存储(如 AWS S3 或阿里云 OSS)不仅收取存储费,更可怕的是下行流量费(Egress Fees)。当你的视频播放量上升,每月的流量账单往往会成为沉重的负担。
  • 复杂的后端架构与运维成本:为了实现动态鉴权,通常需要维护一套复杂的后端服务器(Node.js / Python / Go)来生成 Token。这不仅增加了系统延迟,更引入了服务器维护、扩容和安全防护的额外开销。
    经过深思熟虑与实战打磨,我设计出了一套基于基于 Cloudflare Workers + R2 的高性价比视频防盗链安全网关方案。目前的架构拥有 AES物理加密防下载 + 动态 Token 防盗链 + Cache API 零成本边缘缓存 功能,已经完美实现了从“裸奔”到全栈的加固。

本方案的三大核心优势:

  1. 物理 + 逻辑的双重护城河
    • 物理层:采用 AES-128 对视频切片进行逐一加密,即便文件被拖库,没有 Worker 动态分发的 enc.key 密钥,也只是无法打开的“二进制废料”。
    • 逻辑层:基于 HMAC-SHA256 算法的动态 Token 机制,每一个 M3U8 索引和 TS 切片都带有极短生命周期的防篡改签名。
  2. “零”下行流量费的极致性价比:利用 Cloudflare R2 存储桶免收下行流量费的特性,配合 Worker 边缘分发,大幅降低了流媒体平台的运营成本,真正实现“存储即分发”。
  3. 边缘计算带来的丝滑体验:通过 Cloudflare Worker 在全球 300 多个数据中心进行即时鉴权与 M3U8 动态重写。配合 Cache API 缓存技术,让视频切片在离用户最近的节点“秒开”,告别缓冲等待。

项目源码地址:https://github.com/3543004/Streaming_Anti-Hotlinking_Gateway

接下来,我将手把手带你从本地视频处理、密钥自动化生成,到 Cloudflare Worker 网关部署,再到前端播放器的接入,完整还原这一套全能视频安全网关的构建过程。

一、 最终部署形态与文件归属树

在一切开始前,我们先明确最终各个文件存在于哪里,建立全局观。

1. 文件物理位置归属表

文件类型 最终归属位置 作用
input.mp4 本地电脑 视频母盘(绝对不要上传)
key.info 本地电脑 FFmpeg 切片加密说明书文件,切片完成后即可删除
enc.key 账户 A (R2 存储桶) 16字节 AES 视频解密钥匙
*.ts / *.m3u8 账户 A (R2 存储桶) 加密后的视频切片与播放索引
worker.js 账户 A (Cloudflare Worker) 核心发牌、鉴权、防盗链与缓存网关
config.json 账户 B (Cloudflare Pages) 页面配置文件,设置加密网关域名
index.html 账户 B (Cloudflare Pages) 博客前端 HTML 网页
style.css 账户 B (Cloudflare Pages) 博客样式表
video-loader.js 账户 B (Cloudflare Pages) HLS 播放器调用与动态挂载逻辑

2. 前端项目本地文件夹结构

frontend page/
├── config.json # 加密网关域名
├── index.html # 网页主入口
├── css/
│ └── style.css # 样式文件
└── js/
└── video-loader.js # 播放器核心逻辑

二、 自动化切片与物理加密 (本地 Windows PC)

安全的第一步是在视频离开你的电脑前,就给它套上“物理枷锁”。

  1. 准备目录:在电脑新建文件夹(如 D:\VideoWork),放入待处理的 input.mp4。

  2. 生成密钥材料:在该文件夹内按住 Shift + 鼠标右键,选择 在此处打开 PowerShell 窗口,一次性复制并运行以下全自动脚本:

1
2
3
4
5
6
7
8
9
10
11
# 1. 生成 16 字节随机真实密钥,并写入二进制文件 enc.key
$keyBytes = [byte[]]::new(16); (New-Object Random).NextBytes($keyBytes); [System.IO.File]::WriteAllBytes("$PWD\enc.key", $keyBytes)

# 2. 生成 16 字节随机 IV 向量,并转为 32 位小写十六进制字符
$ivBytes = [byte[]]::new(16); (New-Object Random).NextBytes($ivBytes); $ivHex = ([System.BitConverter]::ToString($ivBytes) -replace '-','').ToLower()

# 3. 自动生成 key.info 说明书
$keyInfoContent = "enc.key`nenc.key`n$ivHex"
[System.IO.File]::WriteAllText("$PWD\key.info", $keyInfoContent)

Write-Host "✅ enc.key密钥 与 key.info 生成完毕!IV: $ivHex" -ForegroundColor Green

完成后按 回车,文件夹下会生成 enc.key(真钥匙)和 key.info(FFmpeg 切片加密说明书)。

  1. 接着在 PowerShell 窗口中运行以下命令,将视频切分为每 6 秒一个的高清加密片段(.ts):
1
2
3
4
5
6
7
8
9
ffmpeg -i input.mp4 `
-c:v libx264 -profile:v high -level 4.1 -preset fast `
-force_key_frames "expr:gte(t,n_forced*2)" -sc_threshold 0 `
-c:a aac -b:a 128k `
-vf "scale=w=1920:h=1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" `
-hls_time 6 -hls_playlist_type vod `
-hls_key_info_file key.info `
-hls_segment_filename "slice_%03d.ts" `
output.m3u8

[!info]
上面的命令强制每 2 秒一个关键帧,确保 6 秒精准切片,首屏加载速度提升 40%。

  • -force_key_frames "expr:gte(t,n_forced*2)":自动计算 2 秒一个关键帧,无论视频是 24、 25、 30 还是 60 帧,统统适用,无视帧率,全自动匹配。
  • -sc_threshold 0:禁用“场景切换自动产生关键帧”。这能确保切片时长极其均匀,不会因为画面突然闪烁就在不该切的地方乱切。
  • -hls_time 6:切片时长为 6 秒。
    更多详细的 FFmpeg 命令参数请参见 FFmpeg 官方文献:https://ffmpeg.org/documentation.html
  1. 删除 key.info 和 input.mp4。只保留 enc.key、output.m3u8 和成堆的 slice_xxx.ts 用于下一步上传。

三、 部署后端安全网关及资产存储 (Cloudflare 账户 A)

这一步我们将 Cloudflare Worker 变成一个“会思考”的闸机:它负责校验票据(Token)、重写索引文件、并管理边缘缓存。

[!important]
流媒体安全网关及资产存储必须在同一个 Cloudflare 账户上。

1. 创建 R2 存储桶并上传视频

  1. 在 Cloudflare 账户 A 中创建 R2 存储桶 video-bucket(名称可自定义)。
  2. 根据你的分类需求新建任意名称的文件夹(大写或小写英文字符及符号),用来储存 视频切片播放列表等相关文件
  3. 在 Cloudflare R2 存储桶页面,将刚才生成的 enc.keyoutput.m3u8slice_xxx.ts 等全部上传到该文件夹中。

2. 部署 Cloudflare Worker 代码

  1. 粘贴下方提供的 最终版 Worker 代码 并部署。核心代码片段(逻辑说明):
    • HMAC-SHA256 签名:确保 URL 无法被伪造。
    • Cache API:将切片缓存至全球边缘节点,后续请求不再消耗 R2 读取次数。
    • 2小时时效:Token 足够覆盖看完长视频,过期即失效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* Cloudflare 账户 A 的 Worker:企业级全能视频安全网关
* 包含:2小时长效发牌、防篡改哈希、R2读取拦截、动态M3U8重写、Cache API成本优化
*/

// 核心签名算法:生成不可逆的安全印记
async function generateSignature(folderPath, expires, secret) {
const encoder = new TextEncoder();
const data = encoder.encode(`${folderPath}:${expires}:${secret}`);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

export default {
// 注意:务必带有 ctx 参数以支持 Cache API 后台异步写入
async fetch(request, env, ctx) {
const url = new URL(request.url);

// --- 1. CORS 多域名白名单校验 ---
const origin = request.headers.get('Origin');
const safeOrigin = origin ? origin.toLowerCase() : "";
const ALLOWED_ORIGINS_STR = env.ALLOWED_ORIGINS || "https://www.example.com";
const SECRET_KEY = env.SECRET_KEY || "YOUR_SUPER_SECRET_KEY";
const allowedOriginsList = ALLOWED_ORIGINS_STR.split(',').map(domain => domain.trim().toLowerCase());
const corsOrigin = allowedOriginsList.includes(safeOrigin) ? origin : "null";

const corsHeaders = {
'Access-Control-Allow-Origin': corsOrigin,
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};

// 放行浏览器 OPTIONS 跨域预检
if (request.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}

// 非法域名强行阻断(非 .ts 文件直接拦截,防止白嫖 M3U8)
if (corsOrigin === "null" && !url.pathname.endsWith('.ts')) {
return new Response('CORS Blocked: 非法来源', { status: 403, headers: corsHeaders });
}

// --- 2. 路由 A:API 发牌器 ---
if (url.pathname === '/api/get-token') {
const rawPath = url.searchParams.get('path');
if (!rawPath) return new Response('Missing path', { status: 400, headers: corsHeaders });

// Token 寿命设定为 7200 秒 (2小时),防止长视频看一半被踢
const expires = Math.floor(Date.now() / 1000) + 7200;

// 智能提取干净路径,剔除可能的域名干扰
let cleanPath = "";
try {
cleanPath = new URL(rawPath).pathname;
} catch (e) {
cleanPath = new URL(rawPath, 'http://dummy.com').pathname;
}

const folderPath = cleanPath.substring(0, cleanPath.lastIndexOf('/'));
const token = await generateSignature(folderPath, expires, SECRET_KEY);

const separator = rawPath.includes('?') ? '&' : '?';
const secureUrl = `${rawPath}${separator}token=${token}&expires=${expires}`;

return new Response(JSON.stringify({ secureUrl }), {
headers: { 'Content-Type': 'application/json', ...corsHeaders }
});
}

// --- 3. 路由 B:拦截与 Token 验真 ---
const token = url.searchParams.get('token');
const expires = url.searchParams.get('expires');
const now = Math.floor(Date.now() / 1000);

if (!token || !expires || parseInt(expires) < now) {
return new Response('Token Expired: 票据失效', { status: 403, headers: corsHeaders });
}

// 重新计算哈希,比对访客带来的票据是否被篡改
const pathForCheck = url.pathname.substring(0, url.pathname.lastIndexOf('/'));
const expectedToken = await generateSignature(pathForCheck, expires, SECRET_KEY);

if (token !== expectedToken) {
return new Response('Invalid Signature: 签名错误', { status: 403, headers: corsHeaders });
}

// --- 4. 路由 C:引入 Cache API 防 R2 账单爆炸 ---
const cache = caches.default;
// 剔除 URL 中的临时 Token 参数,将纯净的路径作为全网唯一的缓存 Key
const cacheKeyUrl = new URL(url.pathname, url.origin).toString();
const cacheKey = new Request(cacheKeyUrl, request);

let response = await cache.match(cacheKey);

if (!response) {
// 缓存没命中,去 R2 桶拿货
const objectKey = decodeURIComponent(url.pathname.slice(1));
const object = await env.BUCKET.get(objectKey);

if (!object) {
return new Response('File Not Found', { status: 404, headers: corsHeaders });
}

const headers = new Headers(corsHeaders);

if (url.pathname.endsWith('.m3u8')) {
// M3U8 动态重写逻辑 (绝不缓存)
const text = await object.text();
const rewrittenLines = text.split('\n').map(line => {
const trimmed = line.trim();
// 给物理密钥 (.key) 打上 Token
if (trimmed.startsWith('#EXT-X-KEY')) {
return trimmed.replace(/URI="([^"]+)"/, (match, uri) => {
const sep = uri.includes('?') ? '&' : '?';
return `URI="${uri}${sep}token=${token}&expires=${expires}"`;
});
}
// 给所有的视频切片 (.ts) 打上 Token
if (trimmed && !trimmed.startsWith('#')) {
const sep = trimmed.includes('?') ? '&' : '?';
return `${trimmed}${sep}token=${token}&expires=${expires}`;
}
return line;
});

headers.set('Content-Type', 'application/x-mpegURL');
headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
response = new Response(rewrittenLines.join('\n'), { headers });

} else {
// TS 切片与 KEY 二进制流逻辑 (边缘节点缓存1个月)
if (url.pathname.endsWith('.key')) headers.set('Content-Type', 'application/octet-stream');
if (url.pathname.endsWith('.ts')) headers.set('Content-Type', 'video/MP2T');

headers.set('Cache-Control', 'public, max-age=2592000');
response = new Response(object.body, { headers });
}

// 将合法的切片存入缓存,下次请求秒开,不扣 R2 读取次数
if (response.status === 200 && !url.pathname.endsWith('.m3u8')) {
ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
} else {
// 命中缓存时,需重新克隆 Response,补齐访客对应的 CORS 头
response = new Response(response.body, response);
response.headers.set('Access-Control-Allow-Origin', corsOrigin);
}

return response;
}
}
  1. 为 Worker绑定自定义域(例如:https://video.example.com
  2. 为 Worker 配置 R2 存储桶
    • 绑定 R2 存储桶:添加变量 BUCKET,绑定你建立的的 R2 存储桶。
  3. 设置 Worker 环境变量
    • SECRET_KEY:填入一串随机的高强度密码(如 MySuperSafeKey2026!
    • ALLOWED_ORIGINS:填入你账户 B 准备上线的网站域名(例如 https://www.your-blog.com)。如果有本地测试,可以加逗号拼接:https://www.your-blog.com, http://localhost:3000

四、 部署前端代理与展示网页 (Cloudflare 账户 B)

为了让方案更通用,采用了 “配置与逻辑分离” 的设计。在网页前端根目录放一个 JSON,以后更换网关的 Worker 域名只需要修改那个简单的 JSON 文本文件;甚至可以直接在 Cloudflare Pages 的后台管理界面里点击修改这个文件并保存,全站立即生效。除此之外还可以轻松地在 JSON 里添加更多配置参数(比如播放器的主题颜色、默认音量等),而不需要把这些乱七八糟的东西都塞进 HTML 里。

  1. 在网页前端根目录新建一个 config.json 文件,写入流媒体网关域名。例如下面的代码:
1
2
3
{
"workerDomain": "https://video.example.com"
}
  1. 动态加载逻辑
    前端 video-loader.js 会先异步读取 config.json 获取网关地址,再通过 fetch 向 Worker 索要当前视频的专属“入场券”,其优势为:
    • 零跨域报错:前端主动拼接 Worker 绝对路径。
    • 多实例支持:同一个页面放多个播放器也互不干扰。
    • 智能切换:更换 Worker 域名只需改 1 个 JSON 文件,全站秒生效。

video-loader.js 文件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
document.addEventListener('DOMContentLoaded', async () => {
// 1. 优先加载配置文件
const workerDomain = await loadConfig();

if (workerDomain) {
// 2. 配置加载成功后,再启动播放器初始化
initAllSecurePlayers(workerDomain);
} else {
console.error("无法启动播放器:配置文件加载失败。");
}
});

/**
* 从根目录读取 config.json
*/
async function loadConfig() {
try {
// 请求根目录下的 config.json
const response = await fetch('/config.json');
if (!response.ok) throw new Error('config.json 不存在');
const config = await response.json();
return config.workerDomain;
} catch (error) {
console.error("加载 config.json 出错:", error);
return null;
}
}

/**
* 批量扫描并初始化播放器
*/
async function initAllSecurePlayers(workerDomain) {
const playerContainers = document.querySelectorAll('.video-player');

for (const container of playerContainers) {
const rawVideoPath = container.getAttribute('data-path');
if (!rawVideoPath) continue;

try {
// 使用配置中的域名发起请求
const response = await fetch(`${workerDomain}/api/get-token?path=${encodeURIComponent(rawVideoPath)}`);

if (!response.ok) throw new Error('Worker 鉴权拒绝');
const data = await response.json();

let finalUrl = data.secureUrl;

// 拼接绝对地址
if (finalUrl.startsWith('/')) {
finalUrl = `${workerDomain}${finalUrl}`;
}

createSingleArtPlayer(container, finalUrl);

} catch (error) {
console.error(`视频 [${rawVideoPath}] 授权失败:`, error);
container.innerHTML = `<div class="player-error">视频加载失败,请检查配置或网络</div>`;
}
}
}

/**
* 实例化 Artplayer
*/
function createSingleArtPlayer(container, secureUrl) {
const art = new Artplayer({
container: container,
url: secureUrl,
type: 'm3u8',
customType: {
m3u8: function (video, url) {
initHlsPlayerDirectly(video, url);
},
},
setting: true,
playbackRate: true,
fullscreen: true,
pip: true,
volume: 0.8
});

art.on('play', () => {
Artplayer.instances.forEach((instance) => {
if (instance !== art) instance.pause();
});
});
}

/**
* HLS 内核加载
*/
function initHlsPlayerDirectly(videoElement, initialUrl) {
if (Hls.isSupported()) {
// 加入快速启动配置参数
const hls = new Hls({
// 限制播放器只下载当前所需清晰度的切片
capLevelToPlayerSize: true,
// 降低初始缓冲要求,尽快吐出画面
maxBufferLength: 10,
maxMaxBufferLength: 30,
});

hls.loadSource(initialUrl);
hls.attachMedia(videoElement);
videoElement.addEventListener('destroy', () => hls.destroy());
} else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
videoElement.src = initialUrl;
}
}
  1. 根据本指南第一部分的前端树形结构,将在本地建立好 index.html、CSS、JS 等文件登录 Cloudflare 账户 B;进入 Cloudflare Pages,通过 上传文件夹GitHub 仓库同步,将 frontend page 整个目录部署上线。

index.html 文件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 安全播放中心</title>
<link rel="stylesheet" href="./css/style.css">
<!-- 引入第三方依赖 -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/artplayer/dist/artplayer.js"></script>
<!-- 引入HLS 播放器调用与动态挂载逻辑 -->
<script src="./js/video-loader.js" defer></script>
</head>
<body>
<header class="site-header">
<h1>基于 Cloudflare R2 与 Worker 的企业级视频加密播放演示</h1>
<p>多域名跨域(CORS)防护 + R2 存储读取 + API 鉴权发牌 + m3u8 动态重写</p>
</header>

<main class="video-list-container">
<article class="video-card">
<h2>ArtPlayer 现代化 HTML5 视频播放器</h2>
<div class="video-player video-box" data-path="/视频文件夹/output.m3u8"></div>
</article>

</main>
</body>
</html>

CSS 文件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/* 基础重置与排版 */
:root {
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--text-main: #2c3e50;
--text-muted: #7f8c8d;
--accent-color: #3498db;
}

body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-main);
}

/* 顶部标题区 */
.site-header {
text-align: center;
padding: 40px 20px;
background-color: var(--card-bg);
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
margin-bottom: 40px;
}

.site-header h1 {
margin: 0 0 10px 0;
font-size: 28px;
}

.site-header p {
margin: 0;
color: var(--text-muted);
}

/* 视频列表容器 */
.video-list-container {
max-width: 900px;
margin: 0 auto;
padding: 0 20px;
}

/* 独立视频卡片 */
.video-card {
background: var(--card-bg);
border-radius: 12px;
padding: 24px;
margin-bottom: 40px;
box-shadow: 0 4px 15px rgba(0,0,0,0.03);
}

.video-card h2 {
margin-top: 0;
font-size: 20px;
color: var(--accent-color);
}

.video-card p {
color: var(--text-muted);
font-size: 15px;
margin-bottom: 20px;
}

/* 播放器容器 (强制 16:9 比例) */
.video-box {
width: 100%;
aspect-ratio: 16 / 9;
background-color: #000;
border-radius: 8px;
overflow: hidden;
}

/* 错误提示样式 */
.player-error {
color: #ff4d4f;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 16px;
font-weight: bold;
}
  1. 为 Cloudflare Pages 设置 自定义域名 ;填入在 Cloudflare 账户 B 准备上 ALLOWED_ORIGINS 中填写的域名(例如 https://www.your-blog.com),之后就可以用这个自定义域名访问了。

五、结语与安全进阶建议

至此,一套基于 Cloudflare Workers + Cloudflare R2 存储 + AES-128 的企业级流媒体防盗网关就构建完成了。我们用极低的开发成本,实现了一个兼具硬核防御、全球加速与超低账单的系统。

如果你希望将这套架构用于更严苛的高价值付费课程场景,建议在此基础上进行以下三项进阶加固:

  1. 接口防刷(Rate Limiting)
    为了防止黑客使用脚本批量请求 /api/get-token 接口拉取 Token,可以在 Worker 中加入简单的频率限制,或者在 Cloudflare 仪表盘中针对该路径配置 Rate Limiting 规则(例如限制单个 IP 每分钟最多请求 10 次)。
  2. 动态跑马灯水印(防手机录屏)
    防得了下载,防不了录屏。最有效的防录屏手段是跑马灯水印。
    你可以在 Artplayer 实例化时,加入 customHTML 或在视频上方叠一层 Canvas,将登录用户的 ID、IP 地址或手机号以半透明形式在画面中随机移动。一旦视频泄露,可直接精准追责。
  3. 前端 JS 代码混淆
    将包含请求逻辑的 video-loader.js 使用 TerserJavaScript Obfuscator 进行混淆压缩,增加逆向工程的难度,防止他人轻易分析出你的 API 调用参数。

网络安全本质上是一场“攻防成本”的博弈。世界上没有绝对无法破译的系统,但我们的目标是将攻击者的盗版成本推高到远超其收益的程度。
这套 Serverless 架构不仅展示了 Cloudflare 生态的强大,也为个人开发者和中小型团队提供了一种高性价比的解决方案。