简介:面向短剧创业者、影视小程序开发者与中小型内容平台运营者的2024版视频短剧SAAS系统源码,采用SAAS多租户架构,可快速搭建自有短剧分发平台,支持微信小程序与公众号H5双端接入。资源包共494个文件,以251个js业务逻辑脚本、138个vue前端页面组件为主,辅以scss样式、png与jpg图片素材及json配置、sql建库脚本等,压缩包约4.3MB,目录分层清晰便于二次开发。系统内置分销商等级自定义价格、二级分销、VIP会员体系,并提供VIP、积分、经销商三类卡密兑换,支持多云存储平台自由切换、批量导入与接口采集,配套完整搭建教程文档。已有2050人学习下载,适合需要低成本落地短剧SAAS平台、研究分销与会员变现逻辑的开发者参考。
1. 拿到一套短剧 SAAS 源码,先别急着解压跑起来
上周有个做小程序外包的朋友甩给我一个压缩包,文件名写着「2024最新版视频短剧SAAS系统源码 影视短剧小程序源码.rar」,问我能不能一周内搭起来给客户看。我打开一看,这不是一个普通的小程序 Demo,而是一整套带平台端、运营后台、小程序端的多租户短剧分账系统——换句话说,它想解决的是「一个团队同时运营 N 个短剧小程序、按剧集付费解锁、给内容方分账」这件事。这类源码在软件/插件圈子里流通得不少,但真正能跑通、能二次开发的不多。这篇笔记就按我拆包的顺序,从技术栈、目录结构、核心付费解锁链路,一路讲到怎么改造成自己的业务,适合想接短剧项目或做 SAAS 二次开发的开发者照着复现。
2. 技术栈与目录结构:这套 SAAS 到底由哪几块拼起来
短剧 SAAS 和单机版短剧小程序最大的区别,是它天生要「多租户」。一个平台方要能开多个小程序账号,每个账号下有独立的剧库、独立的付费配置、独立的分账比例。所以拆这套源码,第一件事不是看页面,而是看它怎么把「平台 - 租户 - 用户」三层关系落到数据库和目录上。
2.1 从压缩包到可运行工程:先认清四端结构
解压后我一般先tree -L 2扫一遍顶层,这类 SAAS 源码基本是四端分离的:
# 解压后典型顶层结构(目录名各版本略有差异,按实际为准) shortplay-saas/ ├── admin/ # 平台运营后台(Vue 系,管租户、剧库、分账) ├── merchant/ # 商户/租户后台(管自己的小程序和剧集) ├── server/ # 后端服务(PHP/Java/Node 都有,看具体版本) ├── miniprogram/ # 微信小程序端(用户看剧、付费解锁) ├── docs/ # 部署文档、SQL 初始化脚本 └── docker-compose.yml四端里,server是核心,admin和merchant是两套不同权限的后台,miniprogram是 C 端。判断一套短剧 SAAS 是否完整,我只看两点:docs里有没有初始化 SQL,server里有没有多租户的中间件或拦截器。缺前者,你连表都建不起来;缺后者,它就是个套了 SAAS 壳的单商户系统。
技术栈上,这类 2024 版源码常见组合是:后端 PHP(ThinkPHP/Laravel)或 Java(Spring Boot),前端后台 Vue2/Vue3 + Element,小程序原生或 uni-app,数据库 MySQL 5.7/8.0,缓存 Redis,对象存储走 OSS/COS。下面这张表是我拆包时整理的模块职责,方便你对号入座:
| 目录 | 职责 | 二次开发频率 |
|---|---|---|
| admin | 平台方:开租户、审剧、看全平台流水 | 低 |
| merchant | 租户方:上剧、配价格、看自己分账 | 中 |
| server | 鉴权、剧集、订单、分账、回调 | 高 |
| miniprogram | 播放、付费、分享、我的 | 高 |
提示:不同版本目录名可能是
api、manage、h5之类,别死记名字,按「谁给谁开账号」的权限关系去认,比记目录名靠谱。
2.2 多租户是怎么落地的:一个 tenant_id 贯穿全链路
短剧 SAAS 的多租户,绝大多数版本走的是「共享库 + tenant_id 字段隔离」这条最省事的路,而不是每个租户一个独立库。原因很现实:独立库运维成本高,小团队扛不住。所以你在server里会反复看到tenant_id这个字段,从剧集表、订单表一路贯穿到分账表。
我一般会先找租户解析的入口,通常在中间件或拦截器里:
// 常见做法:从请求头或小程序 appid 反查租户,写入上下文 public function handle($request, Closure $next) { // 小程序端带 appid,后台端带登录 token 里的 tenant_id $appid = $request->header('X-Appid'); $tenant = TenantModel::where('appid', $appid)->where('status', 1)->find(); if (!$tenant) { return json(['code' => 401, 'msg' => '租户不存在或已停用']); } // 把租户上下文挂到请求上,后续所有查询自动带 tenant_id $request->tenantId = $tenant->id; $request->tenant = $tenant; return $next($request); }这段逻辑的关键在两点:一是租户来源,C 端靠appid,后台靠登录态里的tenant_id,两条入口必须都覆盖,否则后台能越权看到别家数据;二是status校验,租户被停用后所有请求要立刻拒绝,这是 SAAS 的命门。参数上,X-Appid是自定义头,小程序端在request封装里统一注入即可。
数据层再配合一个全局作用域(Laravel 的 Global Scope 或 ThinkPHP 的查询钩子),把tenant_id自动拼进 where 条件,业务代码就不用每条 SQL 手写了。这套设计不新鲜,但短剧 SAAS 里最容易翻车的地方恰恰是「漏拼」——某个统计接口忘了带租户条件,A 商户就能看到 B 商户的流水。
2.3 剧集与付费模型:短剧的钱是怎么收上来的
短剧的商业模式决定了它的数据模型:剧(drama)→ 集(episode)→ 付费配置(price/unlock rule)。用户看前几集免费,后面按集或按整剧付费解锁。所以核心表至少有这么几张:
-- 剧集主表:按租户隔离 CREATE TABLE `drama` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `tenant_id` int unsigned NOT NULL COMMENT '所属租户', `title` varchar(120) NOT NULL COMMENT '剧名', `cover` varchar(255) DEFAULT '' COMMENT '封面', `free_episode` tinyint unsigned DEFAULT 3 COMMENT '免费集数', `status` tinyint DEFAULT 1 COMMENT '1上架 0下架', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 解锁记录:用户对某剧某集的付费凭证 CREATE TABLE `drama_unlock` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `tenant_id` int unsigned NOT NULL, `user_id` int unsigned NOT NULL, `drama_id` int unsigned NOT NULL, `episode_id` int unsigned NOT NULL, `order_id` bigint unsigned DEFAULT 0 COMMENT '关联订单', `create_time` int unsigned DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_ep` (`user_id`,`episode_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;drama_unlock上的唯一索引uk_user_ep是防重复扣费的第一道闸,我见过不少版本漏了这个索引,结果用户连点两下就扣两次钱,退款工单能把你淹了。free_episode放在剧表而不是集表,是因为短剧运营习惯「整剧前 N 集免费」,改一个字段就能调策略,比逐集配置省事。
3. 核心链路拆解:从点开一集到分账到账
跑通这套系统,光把服务起起来不算数,得把「用户付费解锁 → 订单回调 → 分账入账」这条主链路走一遍,才算真跑通。这一章我按请求顺序拆,每段都给出可对照的代码位置和参数含义。
3.1 播放鉴权:为什么不能直接把视频地址甩给前端
短剧最容易被白嫖的环节就是播放。如果小程序拿到的是 OSS 永久直链,用户抓包就能把整部剧拖走。所以合格的短剧 SAAS 一定有一层播放鉴权:前端请求「我要看第 N 集」,后端校验免费或已解锁,再返回一个带时效签名的临时地址。
// 播放地址签发:校验解锁状态后返回短时效签名 URL public function playUrl(Request $request) { $episodeId = (int)$request->input('episode_id'); $userId = $request->userId; $tenantId = $request->tenantId; $episode = EpisodeModel::where('id', $episodeId) ->where('tenant_id', $tenantId) ->find(); if (!$episode) { return json(['code' => 404, 'msg' => '剧集不存在']); } // 免费集直接放行,付费集查解锁记录 $drama = DramaModel::find($episode->drama_id); $isFree = $episode->sort <= $drama->free_episode; if (!$isFree) { $unlocked = DramaUnlockModel::where('user_id', $userId) ->where('episode_id', $episodeId)->count(); if (!$unlocked) { return json(['code' => 403, 'msg' => '未解锁', 'data' => ['need_pay' => 1]]); } } // 签名有效期 10 分钟,过期需重新请求 $url = OssService::signUrl($episode->video_key, 600); return json(['code' => 0, 'data' => ['url' => $url]]); }逻辑上分三步:先按tenant_id定位剧集防越权,再判断免费/已解锁,最后签发短时效 URL。参数600是签名有效期秒数,我一般设 5~10 分钟,太短用户拖动进度条就失效,太长又失去防盗意义。need_pay: 1这个返回约定很重要,前端靠它弹付费框,别改成别的字段名,否则小程序端要跟着改。
3.2 下单与支付回调:幂等是这条链路的命
解锁付费走的是微信支付 JSAPI,流程是「创建订单 → 拉起支付 → 支付回调 → 写解锁记录」。这条链路里唯一不能出错的是回调的幂等,因为微信会重复推送。
// 支付回调:以订单号做幂等,重复通知直接返回成功 public function notify() { $data = $this->verifySign(); // 验签,失败直接抛异常 $order = OrderModel::where('order_no', $data['out_trade_no'])->find(); if (!$order) { return 'FAIL'; } // 已处理过的订单,直接应答成功,避免重复发货 if ($order->status == 1) { return 'SUCCESS'; } Db::startTrans(); try { $order->status = 1; $order->pay_time = time(); $order->save(); // 写解锁记录,唯一索引兜底防重 DramaUnlockModel::create([ 'tenant_id' => $order->tenant_id, 'user_id' => $order->user_id, 'drama_id' => $order->drama_id, 'episode_id' => $order->episode_id, 'order_id' => $order->id, ]); Db::commit(); } catch (\Exception $e) { Db::rollback(); return 'FAIL'; // 返回失败让微信重试 } return 'SUCCESS'; }这里有两层幂等:订单status判断是第一层,drama_unlock的唯一索引是第二层。血泪经验是,只做第一层不够——并发回调可能同时读到status=0,两个事务都往下走,第二层的唯一索引会抛异常,所以必须包在事务里,异常回滚后返回FAIL让微信重试,重试时status已是 1,直接成功。参数out_trade_no是商户订单号,务必在创建订单时就落库,别用微信返回的transaction_id反查,那个字段回调前拿不到。
3.3 分账结算:平台和内容方怎么分钱
SAAS 的「S」最终体现在分账上。用户付的钱不是全进平台口袋,要按租户配置的比例分给内容方。常见做法是订单完成后不立即分账,而是落一条分账流水,由定时任务按周期汇总。
// 分账流水生成:订单支付成功后异步触发 public function createSettle(OrderModel $order) { $tenant = TenantModel::find($order->tenant_id); // 平台抽成比例,租户表里配置,如 30 表示平台拿 30% $platformRate = $tenant->platform_rate; $amount = $order->amount; SettleModel::create([ 'tenant_id' => $order->tenant_id, 'order_id' => $order->id, 'total_amount' => $amount, 'platform_fee' => round($amount * $platformRate / 100, 2), 'merchant_fee' => round($amount * (100 - $platformRate) / 100, 2), 'settle_status' => 0, // 0待结算 1已结算 ]); }platform_rate放在租户表而不是全局配置,是因为不同商户谈判的抽成比例不一样,这是 SAAS 必须支持的。金额计算用round(..., 2)保留两位,别用浮点直接比较,分账对不上账的时候你会怀念这个习惯。settle_status留了状态位,方便后续接提现或对账流程,二次开发时这块扩展空间最大。
4. 本地跑起来:环境、初始化与第一个可访问页面
源码能不能跑,取决于三件事:依赖版本对不对、数据库初始化脚本全不全、配置项填没填。这一章按我实际部署的顺序写,新手照着走能少踩一半坑。
4.1 环境准备与依赖安装
先确认版本,这类 2024 版源码对 PHP 版本通常卡在 7.4~8.1,MySQL 5.7 以上,Redis 必装(缓存和队列都用它)。Node 版本看后台前端,Vue2 项目用 14/16,Vue3 用 16/18,别用最新的 20+,容易在node-sass上翻车。
# 后端依赖(以 Composer 项目为例) cd server composer install --no-dev # 生产依赖,本地调试可去掉 --no-dev cp .env.example .env # 复制配置模板 php think migrate:run # 或导入 docs 下的 init.sql # 后台前端 cd ../admin npm install --registry=https://registry.npmmirror.com npm run build # 或 npm run dev 本地调试 # 小程序端 cd ../miniprogram # 用开发者工具导入,修改 project.config.json 里的 appidcomposer install报内存不足就加COMPOSER_MEMORY_LIMIT=-1;npm install卡在某个包上,先换镜像源再重试,别急着删node_modules重来,多半是网络问题不是依赖冲突。
4.2 数据库初始化与配置项清单
docs目录下的 SQL 一般分两份:建表脚本和初始数据(管理员账号、默认租户、基础配置)。导入顺序不能反,先建表再灌数据。导入后重点检查这几个配置项,缺一个系统就起不来:
| 配置项 | 位置 | 说明 |
|---|---|---|
| DB_HOST/DB_NAME | server/.env | 数据库连接 |
| REDIS_HOST | server/.env | 缓存与队列 |
| OSS_KEY/SECRET | 后台系统设置 | 视频与封面存储 |
| WX_APPID/SECRET | 租户配置 | 小程序登录与支付 |
| PLATFORM_RATE | 租户表 | 默认分账比例 |
注意:
WX_APPID这类配置在多租户系统里通常不在.env,而在租户表或后台设置里,因为每个租户的小程序不一样。第一次跑先在后台建一个测试租户,把 appid 填进去,别直接改.env,否则第二个租户就没法配了。
4.3 起服务与冒烟验证
后端用php think run或配 Nginx 指向public,后台前端npm run dev起在 8080 之类,小程序用开发者工具打开。冒烟顺序我固定这么走:后台能登录 → 建一个租户 → 租户后台能上架一部剧 → 小程序能拉到剧列表 → 点开免费集能播 → 付费集弹付费框。这六步全过,说明主链路通了,剩下的都是配置细节。
# 后端本地起服务(ThinkPHP 示例) php think run -H 0.0.0.0 -p 8000 # 验证接口是否通 curl http://127.0.0.1:8000/api/drama/list -H "X-Appid: 你的测试appid"curl返回 401 先查租户status和appid是否匹配,返回空列表查剧的status和tenant_id。这一步别跳过,直接开小程序调试,报错信息会被前端吞掉,排查成本翻倍。
5. 避坑与排查:这套源码最容易翻车的五个地方
拆这类 SAAS 源码,坑基本集中在「多租户隔离」和「支付回调」两块。下面五条是我和同行踩过的,按现象、原因、解决写,照着排能省不少时间。
5.1 后台能看到别家租户的数据
现象:用 A 租户账号登录商户后台,剧列表里混进了 B 租户的剧。原因:某个列表接口手写 SQL 时漏了tenant_id条件,全局作用域没覆盖到原生查询。解决:全局搜Db::query和->query(,逐个补租户条件;更稳的做法是把租户条件收进一个查询构造器方法,业务层只调它,不直接拼 SQL。
5.2 支付成功但剧没解锁
现象:微信扣款成功,用户回来还是提示未解锁。原因:回调地址配错,或回调里验签失败直接返回FAIL,微信重试几次后放弃。解决:先看server日志里回调有没有进来,没进来查 Nginx 是否放行了notify路径(很多配置只放行index);进来了看验签,密钥填错是最常见的,尤其是租户自己的商户号密钥和平台密钥搞混。
5.3 视频能播但拖动进度条就 403
现象:免费集播放正常,一拖进度条就报错。原因:签名 URL 有效期太短,或 CDN 回源时把签名参数丢了。解决:把签名有效期从 5 分钟调到 10~15 分钟;CDN 侧确认「忽略参数缓存」没开,开了会把签名当缓存键的一部分,导致命中率暴跌还报错。
5.4 分账金额对不上,差几分钱
现象:平台流水和分账表汇总差几分。原因:浮点运算直接相加,或round用在了汇总而不是单笔。解决:单笔分账时round到分,汇总时用整数分存储(金额乘 100 存 int),展示时再除。这套源码如果金额字段是decimal(10,2)就还好,是float的话建议二次开发时改掉。
5.5 小程序端请求全部 401
现象:小程序所有接口返回 401,后台却正常。原因:X-Appid头没注入,或注入的 appid 在租户表里查不到。解决:检查小程序request封装里有没有统一加头;再查租户表appid字段是否和project.config.json里的一致,测试号和生产号别混用。
6. 二次开发改造:把通用 SAAS 改成自己的业务
这套源码的价值不在「能跑」,而在「能改」。通用 SAAS 为了适配所有客户,很多地方做得比较泛,真正落地时你得按自己的业务收窄。我一般从三个方向下手:付费模式、分账逻辑、内容审核。
6.1 付费模式扩展:从按集解锁到会员制
原版多是「按集/按剧解锁」,但很多短剧团队想上「会员看全站」。改造点在playUrl的鉴权分支,加一层会员校验即可,不用动订单表:
// 在免费/解锁判断之后,追加会员判断 if (!$isFree && !$unlocked) { $vip = VipModel::where('user_id', $userId) ->where('tenant_id', $tenantId) ->where('expire_time', '>', time()) ->find(); if (!$vip) { return json(['code' => 403, 'msg' => '未解锁', 'data' => ['need_pay' => 1]]); } }会员表按租户隔离,expire_time用时间戳比日期字符串好比较。这样改的好处是订单链路完全不动,会员开通走另一套下单逻辑,两套付费并存,运营可以自由组合。
6.2 分账逻辑改造:接入内容方多级分成
原版是「平台 + 租户」两级,实际业务里常有「平台 → 租户 → 内容方」三级。改造思路是把SettleModel从单条记录改成按分成方拆多条,用一张settle_detail表存每个收款方的金额:
| 字段 | 含义 |
|---|---|
| settle_id | 关联主分账记录 |
| role_type | 1平台 2租户 3内容方 |
| receiver_id | 收款方 ID |
| amount | 该方金额(整数分) |
生成分账时按配置的比例逐级算,每级round到分,最后一级用「总额减前几级」兜底,保证加起来等于总额。这个兜底技巧能避免分账尾差,对账时省很多事。
6.3 验证方法与上线前检查
改完别直接上,我固定跑一遍回归:免费集能播、付费集未付弹框、付款后能播、重复回调不重复解锁、A 租户看不到 B 租户数据、分账汇总等于订单总额。这六条写成脚本或手动清单都行,每次发版前过一遍。尤其是多租户隔离那条,改任何列表接口都要重测,这是最容易回归出问题的地方。
从那以后我每次接这类 SAAS 源码,都强制先把「租户解析中间件 + 支付回调幂等 + 分账尾差」这三处单独拎出来读一遍再动手,别信「开箱即用」四个字。希望帮到你。
本文还有配套的精品资源,点击获取