拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于FastAdmin的短视频知识付费系统:架构、部署与二次开发实战

简介一套基于FastAdmin框架的短视频知识付费源码包整合小说阅读功能面向需要搭建内容变现平台的PHP开发者、产品经理和运营人员。系统支持短视频上传分享、包月订阅、单片购买、观影券等付费玩法并结合小说模块适合用于知识付费社区或多媒体阅读平台场景。压缩包共2000个文件以js1422个、HTML212个、markdown文档131个、JSON配置113个和CSS样式107个为主另有txt、SQL、XML及shell脚本总大小44.09MB。代码目录覆盖composer.json、application、public、vendor、runtime、addons、extend及thinkphp可看到依赖管理、前后端入口、插件扩展的完整设计README与xkwo.sql分别说明部署方式和数据初始化流程方便快速梳理项目脉络。已有246人学习下载阅读源码可掌握短视频发布、交易下单、内容加速等多模块的集成逻辑VDN加速提示与小说功能也提供了扩展运营思路。适合需要快速搭建或二次开发知识付费平台的技术人员直接参考。1. 基于 fastadmin 搭建短视频知识付费系统的要义拿到一个叫「基于 fastadmin 框架短视频系统视频知识付费源码」的 zip 包很多人的第一反应是丢到宝塔里解压、配伪静态、装扩展然后发现要么后台进不去要么视频传上去不显示。这其实不是代码有问题而是没先弄清楚 fastadmin 在短视频系统里到底扮演什么角色。fastadmin 是基于 ThinkPHP 5 的后台开发框架它强在 RBAC 权限、一键生成 CRUD、插件机制和后台 UI但短视频涉及的前台播放器、视频转码、支付回调这些并不在 fastadmin 的默认能力范围内。这套源码的价值在于用 fastadmin 管后台用自研的 API 模块负责前台播放和付费逻辑两者通过 token 对接。如果你正准备做视频课程售卖或短视频平台这篇文章会把这套源码的骨架、付费链路和二次开发的关键参数拆开讲清楚。2. fastadmin 框架在短视频系统中的定位与核心机制2.1 为什么是 fastadmin 而不是纯手写后台短视频系统的后台管理天然适合用 fastadmin 这类框架承载因为内容型平台的后台需要反复迭代。视频分类、用户管理、订单列表、 banner 位配置这些功能本质上都是对数据表的增删改查fastadmin 的一键 CRUD 能在几分钟内生成带权限控制的控制器、模型和视图。但要注意一个边界fastadmin 是后台框架它不解决播放器、转码队列、防盗链这些前台问题。你看到的这套源码通常是把前台拆成独立的 API 模块通过路由分发让小程序或 H5 去调接口。后台仍然走 fastadmin 的标准登录态前台走 API 返回的 token两者在数据库层面共享表但会话机制完全独立。源码里的目录结构一般长这样project-root/ ├── application/ │ ├── admin/ # fastadmin 后台控制器 │ ├── api/ # 面向 App/H5 的接口控制器 │ ├── common/ # 公共函数 │ └── index/ # 前台页面控制器 ├── public/ │ ├── uploads/ # 视频存储目录 │ └── assets/ # 静态资源 ├── addons/ # fastadmin 插件目录 └── runtime/ # 缓存与日志核心判断是admin 目录走 fastadmin 的后台权限体系api 目录负责业务逻辑。你改后台菜单、加管理员权限进application/admin你改播放鉴权、支付逻辑进application/api。不要混着改否则权限校验会乱套。2.2 权限校验与插件机制这样影响短视频后台fastadmin 的权限校验建立在 auth 表结构上核心是fa_auth_group和fa_auth_rule。后台每个菜单都是一个规则节点控制器里的$this-auth-check()调用会校验当前管理员是否拥有对应节点权限。短视频系统的后台角色通常分三种超级管理员、内容运营、财务审核。内容运营只能上传视频和管理分类财务只能在订单页操作这些通过给不同角色分配不同规则节点实现。在后台菜单管理里新增一个节点时规则名通常写成video/add、video/edit这种格式控制器名称必须与规则名对应才能生效。插件机制对短视频系统的意义在于支付和 OSS 存储。fastadmin 后台左侧菜单的「插件管理」里安装支付插件本质是把支付类库放入addons目录再通过Hook::listen触发业务钩子。常见的做法是?php // application/api/controller/Order.php use think\Hook; // 预下单成功后触发支付通道钩子 Hook::listen(pay_after_create, $orderData);上面这段代码的意思是订单创建完成后通过钩子把订单数据推给支付插件处理。这样做的好处是解耦你更换支付渠道时只需在addons里切换插件不需要改动订单主逻辑。如果源码里没有封装钩子也可以直接在控制器里实例化支付类。3. 短视频系统核心表结构与数据流转设计3.1 视频表、分类表与用户表的关系设计短视频知识付费最关键的表设计是「视频与订单分离」。视频表只存元数据不存购买状态订单表记录每次交易再通过一张用户观看权限表来关联用户和可看的视频。一个典型的结构如下-- 视频表 CREATE TABLE fa_video ( id int(11) unsigned NOT NULL AUTO_INCREMENT, category_id int(11) NOT NULL COMMENT 分类ID, title varchar(255) NOT NULL COMMENT 视频标题, cover_image varchar(255) DEFAULT NULL COMMENT 封面图, video_url varchar(500) DEFAULT NULL COMMENT 视频地址, duration int(11) DEFAULT 0 COMMENT 时长(秒), price decimal(10,2) DEFAULT 0.00 COMMENT 价格, is_free tinyint(1) DEFAULT 0 COMMENT 0收费1免费, play_count int(11) DEFAULT 0 COMMENT 播放次数, status tinyint(1) DEFAULT 1 COMMENT 1上架0下架, createtime int(11) DEFAULT NULL, updatetime int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT视频表; -- 用户观看权限表 CREATE TABLE fa_user_video_access ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 用户ID, video_id int(11) NOT NULL COMMENT 视频ID, order_id int(11) NOT NULL COMMENT 订单ID, expire_time int(11) DEFAULT 0 COMMENT 过期时间0为永久, createtime int(11) DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_video (user_id, video_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT观看权限表;视频表与分类表通过category_id关联这种设计是内容型平台最稳的表结构因为每个字段都有明确归属。is_free字段负责区分试看与付费price字段只有is_free为 0 时才有意义。如果视频分为「免费试看」和「完整版收费」建议在视频表加一个trailer_url专门存试看片段避免播放器端做复杂切片。fa_user_video_access表解决了一个关键问题用户已购记录与订单记录解耦。即便订单被退款也可以选择保留 access 记录或级联删除业务上更灵活。3.2 订单表设计及支付状态机的推进订单表是知识付费的核心表它需要承载从「创建订单 → 等待支付 → 支付成功 → 发放权限」的完整状态变化状态字段建议用数值CREATE TABLE fa_order ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id int(11) NOT NULL COMMENT 用户ID, video_id int(11) NOT NULL DEFAULT 0 COMMENT 视频ID, amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 支付金额, pay_type varchar(20) DEFAULT wechat COMMENT wechat/alipay, status tinyint(1) DEFAULT 0 COMMENT 0待支付1已支付2已关闭3已退款, transaction_id varchar(64) DEFAULT COMMENT 第三方支付流水号, paytime int(11) DEFAULT 0 COMMENT 支付时间, createtime int(11) NOT NULL, updatetime int(11) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;这里status的状态机是 0 → 1 为主路径2 是用户主动取消或超时未付3 是退款。注意一旦状态从 0 变成 1必须同时写paftime时间因为财务报表和用户观看权限过期时间都要依靠它。订单号不要用数据库自增 id而是用独立生成函数因为自增 id 很容易被爬虫遍历间接暴露订单量。常见的生成方式是日期 微秒 随机数在 PHP 里这样写?php function generate_order_no() { return date(YmdHis) . rand(100000, 999999); }4. 视频知识付费的完整业务闭环下单、回调与播放鉴权4.1 创建付费订单的 API 逻辑用户在前端点「购买」按钮后前端携带 token 和视频 id 请求订单创建接口。这里有一个容易被忽略的细节订单创建接口不能只做插入操作必须做幂等校验。同一用户、同一视频、未支付订单存在时直接返回原订单号而不是重复插入一条新订单。以下是一个简化的下单方法?php // application/api/controller/Order.php public function create() { $user_id $this-auth-id; $video_id $this-request-post(video_id); // 校验视频是否存在 $video \app\common\model\Video::get($video_id); if (!$video || $video-status ! 1) { $this-error(视频不存在或已下架); } // 已购买过不再下单 $access \app\common\model\UserVideoAccess::get([user_id $user_id, video_id $video_id]); if ($access) { $this-error(您已拥有该视频的观看权限); } // 幂等有未支付订单则返回原单 $exist \app\common\model\Order::get([ user_id $user_id, video_id $video_id, status 0 ]); if ($exist) { $this-success(订单已存在, [order_no $exist-order_no]); } $order_no generate_order_no(); $order \app\common\model\Order::create([ order_no $order_no, user_id $user_id, video_id $video_id, amount $video-price, status 0 ]); $this-success(下单成功, [order_no $order-order_no, amount $order-amount]); }这里在插入订单前做了三次判断视频状态、是否已购买、是否存在未支付订单。第二层判断尤其重要如果省略它用户重复点击购买会生成多个待支付订单后续回调处理时就可能给一个视频发放多条权限记录。$video-price直接取自数据库禁止前端传价格参数这是支付接口最基本的安全红线。4.2 支付回调处理和观看权限发放支付回调是整套系统里最容易出 bug 的环节。fastadmin 的 API 模块里回调地址通常配置在application/api/config.php或支付插件后台的「回调地址」输入框里一般长这样https://你的域名/api/pay/notify回调处理的核心逻辑是验签 → 查订单 → 更新状态 → 发放权限。顺序不能随便调换尤其先验签再查订单。?php // application/api/controller/Pay.php public function notify() { $pay_type $this-request-param(pay_type, wechat); $pay new \addons\pay\library\Pay($pay_type); // 1. 验证回调签名是否合法 $result $pay-verifyNotify($this-request-post()); if (!$result) { return fail; } $order_no $result[out_trade_no]; $transaction_id $result[transaction_id]; // 2. 根据订单号查出本地订单 $order \app\common\model\Order::get([order_no $order_no]); if (!$order) { return fail; } // 3. 状态为0待支付才处理防止重复回调 if ($order-status 0) { // 4. 更新订单状态 $order-status 1; $order-transaction_id $transaction_id; $order-paytime time(); $order-save(); // 5. 发放观看权限expire_time0表示永久有效 \app\common\model\UserVideoAccess::create([ user_id $order-user_id, video_id $order-video_id, order_id $order-id, expire_time 0 ]); } // 6. 回执给支付平台 return success; }第 3 步的状态判断至关重要。微信或支付宝的回调机制是「多次通知直到收到 success」如果不用status 0判断每来一次回调就会插入一条重复的权限记录。有些二次开发者把权限发放写到前端查询接口里这是错误做法前端无法验证金额是否正确绕过支付直接造一条订单记录即可免费看课。4.3 播放器端带 token 的鉴权方案付费视频的播放地址不能是明文直链否则用户把视频地址分享出去就能免费看。这套源码里常见的做法是播放地址带token参数播放器每次播放时都携带该参数后端在解析视频地址时动态生成一个有时间限制的临时播放凭证。一个简化版本的原理如下?php // application/api/controller/Video.php public function playUrl() { $user_id $this-auth-id; $video_id $this-request-get(video_id); // 校验用户是否拥有观看权限 $access \app\common\model\UserVideoAccess::get([ user_id $user_id, video_id $video_id ]); if (!$access) { $this-error(您未购买该视频); } $video \app\common\model\Video::get($video_id); // 生成 30 分钟有效的临时播放凭证 $expire time() 1800; $sign md5($video_id . $user_id . $expire . 你的密钥); $play_url $video-video_url . ?expire . $expire . sign . $sign; $this-success(ok, [play_url $play_url]); }playUrl接口每次都实时生成带签名的播放地址签名内容包含视频 id、用户 id 和过期时间并拼接一个只有服务端才知道的密钥这样前端无法自行伪造参数。播放器拿到这个 URL 后在播放器的事件里将src指向带参数的真实地址用户在 30 分钟内有效过期后必须重新请求接口获取新的地址这能有效防止地址被到处转发。提示某些源码在生成播放凭证时用的是file_get_contents去获取视频文件大小这在高并发下会严重拖慢响应。改为在视频上传时把size字段写入数据库播放时直接读到内存里判断性能好得多。5. 部署这套 fastadmin 短视频系统的环境要求与排错5.1 PHP 扩展、伪静态与上传配置这套源码的环境要求通常集中在 PHP 7.1 至 7.4 之间ThinkPHP 5 对 8.0 的支持并不完善尤其是each()等函数在 PHP 8 中已被移除除非源码保证没有这类写法否则不要贸然上高版本。部署环境的必要配置项包括# 宝塔面板中 PHP 需要安装的扩展 fileinfo redis # 用于缓存部分源码非必需 opcache # 提升接口响应速度 # PHP 配置文件 php.ini 中需要调整的项 upload_max_filesize 2048M post_max_size 2050M max_execution_time 300伪静态规则按 fastadmin 官方标准配置。Nginx 的伪静态内容如下location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } }这段规则的含义是当请求的文件在服务器上不存在时将请求重写到index.php并带上原始路径作为参数s。fastadmin 的路由依赖这个参数解析控制器和方法名不配置或配错会出现前台全 404、后台却能访问的现象。上传配置这里有一个常见误区单独调大upload_max_filesize还不够视频传到public/uploads目录后还需要确认该目录有写权限。源码里的 uploads 目录通常默认是 755但部分主机环境需要改为 755 的属主为www用户否则后台能上传图片、视频却失败或进度卡死。5.2 安装过程中最常见的三个坑安装 fastadmin 短视频源码时最容易卡住的地方不是代码本身而是环境和初始化数据。第一个坑是数据库导入不完整。zip 包里的 SQL 文件通常包含表结构和初始数据直接使用宝塔的「导入」功能偶尔会因 SQL 文件过大而超时中断表现为后台能打开但菜单是空的。处理方式是使用命令行导入顺便说一句导入后要检查fa_config表里的__CDN__和__PUBLIC__配置项两者的值需要对应你的域名路径。第二个坑是后台登录后菜单空白或样式错乱。fastadmin 前端资源是public/assets目录下的预编译文件如果访问路径出现 404通常是伪静态没生效或服务器根目录指向错误。确认站点运行目录指向public而不是项目根目录。第三个坑是提示「未授权安装插件」。fastadmin 的后台会检查fa_config中的站点标识不同版本的框架对应不同的标识验证从网上下载的源码包有时会把site字段写成演示站的域名导致在线安装插件受限。遇到这种情况检查数据库fa_config表里site相关的配置值并确认它与你当前的访问域名一致。6. 短视频播放器的对接技巧与付费墙边界处理最后落地到播放器对接和付费墙的细节上。许多短视频源码用的是 vue 或原生 H5 播放器后台会提供播放器 JS 变量来接数据。以常见的方式为例在详情页引入播放器并接收playUrl接口返回的数据var player new Clappr.Player({ source: res.data.play_url, poster: res.data.cover_image, parentId: #player-container, width: 100%, height: 100%, mute: false, autoplay: false }); player.on(play, function() { // 触发一次播放量上报 axios.post(/api/video/reportPlay, { video_id: videoId }); });source直接取后端实时生成的带签名地址poster单独使用封面直链两者解耦。播放量的上报不要写在播放器初始化里因为初始化不等于真正播放用户打开页面但又关闭时也会触发一次数据不准确。关于付费墙的边界建议做成「试看 30 秒完整版需购买」。实现方式并不依赖于视频切片而是让后端实时生成带start参数的播放地址配合播放器的seek拦截player.on(timeupdate, function(currentTime) { if (!isPaid currentTime 30) { player.pause(); showBuyModal(); } });检查访问记录时优先关注fa_order表的状态是否全部为 1以及fa_user_video_access表中是否存在 permission 过期时间小于当前时间戳的记录。只要这两张表与支付平台的对账单能对上这套源码的核心闭环就没有问题。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门