✕
开启精彩搜索
热门搜索
emby EMBY 解析 免流 源码 IPV6 阿里 阿里云 青龙 音乐 网心 网心云 webdav 阿里云盘 unraid 洗白 Rclone 刮削 云梦

从零搭建 Cloudflare Workers + D1 版 IPTV 管理系统完整教程

把一套传统的 PHP + MySQL IPTV 管理系统,完整迁移到 Cloudflare Workers + D1 边缘计算平台,实现免服务器、全球加速、零运维成本。


📖 前言

传统的 IPTV 管理系统(比如”群晖IPTV”)一般基于 PHP + MySQL 架构,需要:

  • 一台 VPS 服务器
  • 安装宝塔面板或 LNMP 环境
  • 定期维护(更新系统、备份数据、续费服务器)

这套方案的问题:

  • ❌ 成本高:VPS 每月至少几十块钱
  • ❌ 运维麻烦:系统更新、安全补丁、备份都要管
  • ❌ 访问慢:单点服务器,跨地区访问延迟大
  • ❌ 容易被打:暴露 IP,被 DDoS 就崩

替代方案:Cloudflare Workers + D1

  • ✅ 几乎零成本:Workers 每天 10 万次免费请求,D1 有 5GB 免费存储
  • ✅ 全球加速:Cloudflare 全球 300+ 节点,用户就近访问
  • ✅ 免运维:不需要管服务器,Cloudflare 全托管
  • ✅ 免备案:Workers 默认域名不需要备案(自定义域名看情况)
  • ✅ 自动 HTTPS:无需配置证书

代价:

  • 需要把 PHP 代码重写成 TypeScript
  • 数据库从 MySQL 换成 SQLite(语法有差异)
  • 一些 PHP 特性(gzcompress、openssl_encrypt)需要用 JS 替代

本文完整记录了这次迁移的全过程,供有类似需求的朋友参考。


🏗️ 一、准备工作

1.1 账号和工具

名称 用途 获取方式
Cloudflare 账号 托管 Workers、D1、自定义域名 https://dash.cloudflare.com
Node.js(≥ 18) 本地开发和部署 https://nodejs.org
Wrangler CLI Cloudflare 官方命令行工具 npm install -g wrangler
代码编辑器 VS Code 或其他 https://code.visualstudio.com

1.2 原系统文件

从原 PHP 项目里你需要准备:

  • 源代码(login.php、data.php、admin.php 等)
  • 数据库导出(.sql 文件,MySQL dump)
  • 静态资源(views/css/、views/js/、views/fonts/、views/images/)

🚀 二、创建 Worker 项目

2.1 初始化项目

mkdir iptv-worker
cd iptv-worker
npm init -y
npm install -D wrangler typescript @cloudflare/workers-types
npm install crypto-js

2.2 项目结构

iptv-worker/
├── src/
│   ├── index.ts                # Worker 入口
│   ├── crypto.ts               # AES + MD5 工具
│   ├── db.ts                   # D1 数据库封装
│   ├── handlers/
│   │   ├── clientLogin.ts      # 客户端登录
│   │   ├── clientData.ts       # 客户端数据
│   │   ├── clientMisc.ts       # 版本/背景图/IP 查询
│   │   ├── adminAuth.ts        # 后台登录
│   │   ├── adminDashboard.ts   # 后台首页
│   │   ├── adminUsers.ts       # 用户管理
│   │   ├── adminAuthor.ts      # 授权管理
│   │   ├── adminException.ts   # 异常管理
│   │   ├── adminMeal.ts        # 套餐管理
│   │   ├── adminOrder.ts       # 订单管理
│   │   ├── adminEpg.ts         # EPG 管理
│   │   ├── adminMovie.ts       # 点播管理
│   │   ├── adminChannel.ts     # 频道管理
│   │   └── adminSys.ts         # 系统设置
│   └── html/
│       ├── adminLogin.ts       # 登录页 HTML
│       └── adminLayout.ts      # 后台通用布局
├── public/                     # 静态资源
│   ├── favicon.ico
│   └── views/
│       ├── css/
│       ├── js/
│       ├── fonts/
│       └── images/
├── schema.sql                  # D1 初始化 SQL
├── convert.js                  # MySQL → SQLite 转换脚本
├── wrangler.toml               # Wrangler 配置
├── tsconfig.json
└── package.json

2.3 wrangler.toml 配置

name = "iptv-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"

[[d1_databases]]
binding = "DB"
database_name = "iptv_db"
database_id = "你的数据库ID"

[vars]
PANEL_MD5_KEY = "tvkey_"

[assets]
directory = "./public"
binding = "ASSETS"

💾 三、D1 数据库

3.1 创建 D1 数据库

npx wrangler d1 create iptv_db

输出会给出 database_id,把它填到 wrangler.toml 里。

3.2 表结构设计

原 MySQL 的 11 张表:

表名 用途
iptv_admin 管理员账号
iptv_adminrec 后台操作日志
iptv_category 频道分类
iptv_channels 频道源列表
iptv_config 系统配置项
iptv_epg 电子节目单
iptv_meals 套餐
iptv_payment 订单
iptv_users 用户
qhtv_movie 点播接口
eztv_movie 备用点播接口

关键差异(MySQL vs SQLite):

MySQL SQLite
INT(11) AUTO_INCREMENT INTEGER PRIMARY KEY AUTOINCREMENT
VARCHAR(255) TEXT
ENGINE=InnoDB 无
DEFAULT CHARSET=utf8 无
反引号 `table` 双引号 "table" 或不加

建表 SQL 放在 schema.sql 里,执行:

npx wrangler d1 execute iptv_db --remote --file=./schema.sql

🔄 四、数据迁移(MySQL → SQLite)

这是最繁琐的一步,核心是把 MySQL dump 转换成 SQLite 兼容格式。

4.1 转换脚本 convert.js

写一个 Node.js 脚本,处理以下转换:

const fs = require('fs');
const content = fs.readFileSync(process.argv[2], 'utf8');

// 1. 输出建表语句(手动维护 tableCreates 对象)
// 2. 逐行解析 INSERT INTO 语句
// 3. 处理转义符(\' → '',\" → "")
// 4. 拆分超长 INSERT(避免 SQLITE_TOOBIG)
// 5. 去掉 MySQL 特有语法

关键处理点:

问题 解决方案
转义符 \' 替换为 ''(SQLite 用双引号转义)
大 INSERT 超限 按 500 行拆成小 INSERT
列数不匹配 检查原表和 SQLite 表定义是否一致
中文乱码 用 UTF-8 读取文件

4.2 导入 D1

# 清空旧数据
npx wrangler d1 execute iptv_db --remote --command="DELETE FROM iptv_admin; DELETE FROM iptv_users; ..."

# 导入新数据
npx wrangler d1 execute iptv_db --remote --file=./d1_import.sql

避坑指南:

  • 一次导入失败会全部回滚,所以任何一条 SQL 出错都会导致整体失败
  • 遇到 SQLITE_TOOBIG 就拆 INSERT
  • 遇到 UNIQUE constraint failed 就先清空表
  • 遇到 no such table 就先建表

🔐 五、核心加密逻辑

原 PHP 用了 AES-128-ECB 和 MD5,需要完整迁移到 JS。

5.1 MD5 实现

不要自己手写,用 crypto-js:

import MD5 from 'crypto-js/md5';
export function md5(input: string): string {
    return MD5(input).toString();
}

踩坑记录:Web Crypto API 不支持 MD5,crypto.subtle.digest('MD5', ...) 会报错。必须用 crypto-js 或 js-md5。

5.2 AES-128-ECB 加密

Web Crypto 只有 AES-CBC,没有 AES-ECB。用 CBC + 全零 IV + 单块加密模拟 ECB:

export async function aesEncrypt(plaintext: string, key: string): Promise<string> {
    const keyObj = await importKey(key);
    const padded = pkcs7Pad(encoder.encode(plaintext));
    const blockSize = 16;
    const result = new Uint8Array(padded.length);
    const zeroIV = new Uint8Array(blockSize);
    for (let i = 0; i < padded.length; i += blockSize) {
        const encrypted = await crypto.subtle.encrypt(
            { name: 'AES-CBC', iv: zeroIV },
            keyObj,
            padded.slice(i, i + blockSize)
        );
        result.set(new Uint8Array(encrypted).slice(0, blockSize), i);
    }
    return btoa(String.fromCharCode(...result));
}

验证对齐方法:用 PHP 和 Node 分别加密同一段数据,结果必须完全一致。


🎨 六、后台管理系统

6.1 后台页面结构

原来 PHP 用 .php 文件混排 HTML 和逻辑,迁移后改成前后端分离:

  • 后端:Worker 输出 HTML 字符串(服务端渲染)
  • 前端:HTML 里用原生 JS fetch 调用 API

6.2 通用布局

后台有 10 个页面,全都套用同一个布局(adminLayout.ts),只需要替换中间 <main> 内容。

export function adminLayout(
    active: string,        // 当前激活的菜单
    title: string,         // 页面标题
    content: string,       // 主内容 HTML
    username: string,      // 登录用户名
    dateNow: string        // 当前时间
): string {
    // 返回完整 HTML 骨架
}

6.3 后台登录(JWT)

原 PHP 用 Session,Workers 是无状态的,改用 JWT (JSON Web Token) 存到 Cookie。

// 登录成功后生成 JWT
const token = await createToken(username, panelKey, remember ? 7 * 86400 : 86400);

// 通过 Set-Cookie 下发
'Set-Cookie': `admin_token=${token}; Path=/; Max-Age=${maxAge}; HttpOnly; SameSite=Lax; Secure`

6.4 后台的 10 个页面

页面 路径 功能
首页 /admin 8 个统计卡片 + 频道分类统计
授权 /admin/author 待授权用户列表、批量授权
用户 /admin/user 已授权用户、套餐修改、到期管理
异常 /admin/exception VPN/设备 ID 更换用户管理
套餐 /admin/meal 套餐增删改
订单 /admin/order 订单列表(只读)
EPG /admin/epg EPG 增删改、绑定频道
点播 /admin/movie 点播接口管理
频道 /admin/channel 分类管理 + 频道编辑
系统 /admin/sys 6 个子配置页

📱 七、客户端接口

App 需要 5 个核心接口,全都用 AES 加密返回:

接口 方法 作用
/login.php POST 登录,返回用户状态、套餐、加密的 dataurl
/data.php POST 返回频道列表(AES 加密)
/getver.php GET 返回版本信息(JSON)
/bg.php GET 返回随机背景图 URL
/getIpInfo.php GET 返回 IP 归属地

7.1 登录接口逻辑

1. 从请求体解析 JSON(mac、androidid、model 等)
2. 校验 MAC 是否合法
3. 查询 iptv_users 表
   - 存在 → 更新 ip、region、lasttime
   - 不存在 → 创建试用账号(根据 trialdays 配置)
4. 检查套餐内容
5. 组装响应 JSON
6. 用 md5 计算 AES 密钥
7. AES 加密返回

7.2 数据接口逻辑

1. 从请求体解析 JSON(androidid、mac、rand)
2. 查询用户,得到 meal
3. 查询套餐的 content(下划线分隔的分类名)
4. 遍历分类,查询每个分类下的频道
5. 按频道名分组(同名频道多个源)
6. 组装 JSON
7. 用 md5 计算 AES 密钥(含 rand 参数)
8. AES 加密返回

7.3 关键的加解密一致性

客户端(App)和服务器(Worker)必须用完全一致的算法:

  • 相同密钥推导逻辑:md5(md5(签名+应用名+包名+固定串) + 应用名 + 包名 + rand)
  • 相同的 AES 模式(AES-128-ECB)
  • 相同的字符替换(PHP 里做了 f/b、t/y 互换)

测试方法:把 App 端的加密逻辑用 Node 重写一遍,两边结果对比。


⚙️ 八、静态资源托管

8.1 Cloudflare Assets

在 wrangler.toml 里配置:

[assets]
directory = "./public"
binding = "ASSETS"

Wrangler 会自动把 public/ 下的文件作为静态资源托管,访问路径就是文件相对路径。

8.2 关键:字体文件

踩坑:materialdesignicons.min.css 里用 @font-face 引用了 ../fonts/materialdesignicons.woff2,如果只拷贝 CSS 没拷 fonts,所有图标会显示为方框。

完整拷贝:views/css/、views/js/、views/fonts/、views/images/ 全部要放到 public/views/ 下。


🌐 九、域名和路由

9.1 默认 workers.dev 域名

部署后自动生成 xxx.workers.dev 域名,可以立即使用。

9.2 绑定自定义域名

在 Cloudflare Dashboard 里给 Worker 绑定自定义域名(比如 iptv.example.com),需要:

  1. 域名 NS 指向 Cloudflare
  2. Worker → Settings → Triggers → Custom Domains → 添加

好处:

  • 更短的 URL
  • 可以开启 CDN 缓存
  • 可以单独配置防火墙规则

🧪 十、测试清单

10.1 后台

  • [ ] 登录页正常显示(有图标、有样式)
  • [ ] admin/123456 能登录
  • [ ] 首页 8 个统计卡片数字正确
  • [ ] 用户列表能翻页、能搜索
  • [ ] 授权页能批量授权
  • [ ] 频道页能增加分类、编辑频道
  • [ ] 系统设置能修改配置
  • [ ] 修改密码后能用新密码登录

10.2 客户端

  • [ ] /login.php 返回加密字符串
  • [ ] /data.php 返回加密字符串(长度正常)
  • [ ] /getver.php 返回 JSON
  • [ ] /bg.php 返回图片 URL
  • [ ] /getIpInfo.php?ip=myip 返回归属地

10.3 App 端

  • [ ] App 能连上服务器
  • [ ] 能登录成功
  • [ ] 能看到频道列表
  • [ ] 能播放直播源

🐛 十一、常见问题汇总

问题 原因 解决方案
Could not resolve "crypto-js/md5" 未安装依赖 npm install crypto-js
no such table: iptv_admin 数据库未初始化 执行 schema.sql
no such column: secret_key 表结构不匹配 ALTER TABLE iptv_admin ADD COLUMN secret_key TEXT
SQLITE_TOOBIG 单条 SQL 太长 拆分 INSERT(每 500 行)
UNIQUE constraint failed 主键冲突 先 DELETE FROM 清空
unrecognized token: "\" MySQL 转义符 替换 \' 为 ''
MD5 结果不一致 算法实现不同 用 crypto-js
图标显示方框 字体文件缺失 拷贝 views/fonts/
$.cookie is not a function jquery.cookie 缺失 引入 CDN
登录后跳转”页面开发中” 路由未实现 补 handleXXX 路由

💰 十二、成本分析

项目 免费额度 本系统预估用量 是否够用
Workers 请求 10 万次/天 几百-几千次 ✅
Workers CPU 10ms/请求 5-20ms ✅
D1 读取 500 万行/天 几万行 ✅
D1 写入 10 万行/天 几千行 ✅
D1 存储 5GB 几十 MB ✅
Assets 带宽 无限 - ✅

结论:个人/小规模运营完全免费。除非用户量上千、每天几十万请求,才需要考虑付费(Workers Paid $5/月起)。


🎯 十三、总结

迁移的核心工作

  1. PHP → TypeScript:所有业务逻辑重写
  2. MySQL → SQLite:表结构、SQL 语法微调
  3. Session → JWT:无状态登录
  4. OpenSSL → Web Crypto:加解密重写
  5. 服务端渲染 → 混合渲染:HTML 用 JS 动态生成

迁移的优势

  • ✅ 零服务器:不用买 VPS,不用管系统
  • ✅ 全球加速:300+ 节点就近服务
  • ✅ 自动扩容:流量突增不宕机
  • ✅ 免运维:不用打补丁、不用备份(Cloudflare 有备份机制)
  • ✅ 自动 HTTPS:证书自动续期

迁移的挑战

  • ❌ 冷启动:第一次部署需要 2-3 小时
  • ❌ 调试麻烦:Workers 环境不能像 PHP 一样直接改文件
  • ❌ PHP 特性缺失:gzcompress、openssl_encrypt 需要替代

适合的场景

  • ✅ IPTV、直播、点播系统
  • ✅ API 网关、Webhook 转发
  • ✅ 小型后台管理系统
  • ✅ 反代、转发、限流

不适合的场景

  • ❌ 需要大量文件读写
  • ❌ 长连接(WebSocket 虽有但受限)
  • ❌ 复杂的图片处理(需要配合 R2)

📚 参考资源


作者注:本文基于真实的 IPTV 系统迁移项目整理,所有代码片段都经过实际验证。如果你也在做类似的迁移,希望这份文档能帮你少踩坑。

最后更新:2026-09-25

💬 评论区 0