PHP+MySQL+Uniapp实现AI短剧创作系统:从数据库到异步任务全解析
1. 为什么我会用 PHP + MySQL + Uniapp 来做 AI 短剧系统
先说个背景。我这两年一直在做内容创作工具类项目,短视频生态火起来之后,短剧成了流量洼地,但传统短剧制作成本高、周期长,根本不是普通团队玩得转的。于是市面上开始出现"AI 短剧"这个概念——用 AI 生成脚本、分镜、配音、甚至直接生成视频素材,再通过程序化手段批量组装成成品短剧。方向没问题,但真正落地时,你会发现最大的瓶颈不是 AI 本身,而是怎么把 AI 能力"流程化"和"产品化"。
我最初也考虑过用 Python FastAPI 或者 Node.js 来做后端,毕竟 AI 生态圈里 Python 是主流。但实际调研之后,我放弃了这个方案,原因有三点。
第一,这个系统的目标用户是短剧创作者和中小型内容工作室,他们普遍没有复杂的运维能力,部署环境大多是阿里云、腾讯云的轻量服务器,甚至是一台 2G 内存的小水管。PHP 在这个场景下有天然优势,扔上去就能跑,不用配 uwsgi、不用管 gunicorn、不用折腾虚拟环境。
第二,AI 短剧系统的核心业务是:剧本管理、角色配置、分镜生成、视频素材管理、成片合成、发布渠道管理。这些本质上是 CRUD + 业务流程编排,PHP 的 Laravel 框架做这类业务太顺手了,开发效率比 Python 快不少。
第三,Uniapp 作为前端选型,是因为短剧创作工具的使用者大量依赖手机端操作——导演在现场要改剧本、剪辑师要上传素材、运营要实时盯数据。Uniapp 一套代码同时输出微信小程序、H5、安卓 App、iOS App,省掉了四套前端团队的维护成本。
这套系统的核心价值可以浓缩成一句话: 把 AI 生成能力从零散的"调用接口"升级成一条可配置、可复用、可协作的创作流水线 。
下面我会按我实际开发的路径,把这套"AI短剧创作系统源码(PHP+MySQL+Uniapp)"的实现方案逐步拆开来讲。这么说吧,你拿到这套实现思路,不是拿到一个玩具 Demo,而是一套可以直接拿去给客户演示、接商单的完整工程骨架。
2. 从需求到数据库表设计:AI 短剧系统到底在管理什么"对象"
很多人在做这类系统时,上来就写代码,结果写到一半发现表结构撑不住业务,回头重构,浪费时间不说,心气也磨没了。我建议你开工前先把领域模型想清楚。
AI 短剧系统里,最核心的领域对象有六个: 剧集(Drama)、剧本(Script)、角色(Character)、分镜(Shot)、成片(Video)、创作任务(Task) 。这六者之间的关系不是简单的父子级,而是"一个剧集有多个剧本版本,一个剧本包含多个角色,一个剧本被拆分成多个分镜,多个分镜最终渲染成一个成片,整个过程由创作任务驱动"。
我用一个实战中常用的表结构设计来说明,这套设计经过了商单项目的验证,字段不冗余,查询效率也不差。
2.1 剧集表与剧本表:版本管理的设计思路
CREATE TABLE `drama` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '创作者ID',
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '剧集名称',
`category` tinyint(4) NOT NULL DEFAULT '0' COMMENT '分类:1甜宠 2悬疑 3逆袭 4都市',
`cover_url` varchar(500) NOT NULL DEFAULT '' COMMENT '封面图',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0草稿 1创作中 2已发布',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='剧集表';
剧集表就是整个系统的最顶层容器。这里的
user_id
一定要建索引,因为系统一旦上线,用户量和作品量增长非常快,用联合查询时
user_id
是最高频的筛选条件。
CREATE TABLE `script` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`drama_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '所属剧集ID',
`version` int(11) NOT NULL DEFAULT '1' COMMENT '版本号',
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '剧本标题',
`content` longtext COMMENT '剧本文本内容(AI生成的原始JSON或Markdown)',
`word_count` int(11) NOT NULL DEFAULT '0' COMMENT '字数',
`is_active` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否当前生效版本',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_drama_id` (`drama_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='剧本表';
剧本表这里的
content
字段类型我用的是
longtext
,因为 AI 生成的剧本可能是一段结构化 JSON,包含场次、对白、旁白、机位建议等,单条数据轻松超过 64KB,用
text
类型会截断。
version
字段是实现"剧本版本管理"的关键,AI 每次重新生成我们不是覆盖原记录,而是插入新版本,这样创作者可以随时对比不同版本之间的差异。
这个设计思路背后的逻辑是:AI 创作不等于"一次性生成一个完美成品",而是"多轮试探、反复修正"。没有版本管理,这个系统在导演眼里就是一个玩具。
2.2 角色表与分镜表:面向 AI 生成的标准化存储
CREATE TABLE `character` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`script_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '所属剧本ID',
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '角色名',
`gender` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1男 2女',
`personality` varchar(500) NOT NULL DEFAULT '' COMMENT '人设关键词,逗号分隔',
`avatar_url` varchar(500) NOT NULL DEFAULT '' COMMENT '角色定妆照',
`voice_url` varchar(500) NOT NULL DEFAULT '' COMMENT 'AI配音音色样本',
`description` text COMMENT '角色详细描述',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_script_id` (`script_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
角色表是整个系统里跟 AI 结合最紧密的一张表。
personality
字段存的是人设关键词,比如"傲娇、毒舌、外冷内热",这些关键词在后续调用 AI 接口时会被拼进 Prompt 模板里;
voice_url
是 AI 配音的音色样本地址,每个角色说话的风格要统一,通过声音克隆类接口做人声锁定。
CREATE TABLE `shot` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`script_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '所属剧本ID',
`sequence` int(11) NOT NULL DEFAULT '0' COMMENT '分镜序号',
`scene_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '场景描述',
`action_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '动作描述',
`dialogue` text COMMENT '对白内容',
`camera_move` varchar(100) NOT NULL DEFAULT '' COMMENT '运镜方式,如推进/拉远/平移',
`duration` int(11) NOT NULL DEFAULT '3' COMMENT '预计时长(秒)',
`image_url` varchar(500) NOT NULL DEFAULT '' COMMENT 'AI生成的参考画面',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_script_id` (`script_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分镜表';
分镜表的设计灵感来自动画制作的"分镜脚本"流程——把剧本拆成最小的可视化单元,AI 出图能力就能逐镜生成画面,真人导演也能按这个表安排拍摄计划。
2.3 创作任务表:让 AI 异步生成不再"卡死"
创作任务表是整个系统稳定性的底座。刚开始做这套系统时,我犯过一个低级错误:在 PHP 里直接同步调用 AI 生成接口,用户在浏览器上等 60 秒,最后网关超时,请求断了,AI 还在后台跑,用户不知道结果,只能再点一次,结果生成了两份,浪费 API 额度。后来我改用"任务队列 + 异步回调"模式,才彻底解决。
CREATE TABLE `task` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL DEFAULT '0',
`drama_id` bigint(20) NOT NULL DEFAULT '0',
`task_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '1生成剧本 2生成分镜 3生成图片 4合成视频',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0等待中 1执行中 2成功 3失败 4取消',
`params` json DEFAULT NULL COMMENT '任务参数,JSON格式',
`result_data` longtext COMMENT '任务结果,JSON格式',
`error_msg` varchar(500) DEFAULT '' COMMENT '错误信息',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id_status` (`user_id`, `status`),
KEY `idx_status_type` (`status`, `task_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='创作任务表';
params
字段用 MySQL 5.7+ 的 JSON 类型,这是 MySQL 原生支持,比存序列化字符串更规范。
idx_user_id_status
这个联合索引非常关键,用户打开 App 看到的"我的创作任务列表"就是查这张表,没有索引的话,用户量和任务量上来后必卡。
3. 后端服务分层:PHP 代码里的 AI 生成链路如何编排
表结构定好之后,接下来是 PHP 后端的架构设计。我用的是 Laravel 框架(PHP 8.1+),项目结构分成 Controller、Service、Repository 三层。后端最复杂的部分在于 AI 生成链路的编排——同一个创作流程里要调用多个 AI 服务,而且这些服务有先后依赖关系。
3.1 服务分层与 AI 接入的适配器模式
先看一个核心问题:AI 服务商有很多,OpenAI、百度文心、讯飞星火、智谱、通义千问,每家都有对话生成接口,但参数格式、响应结构各不相同。如果直接在业务代码里写死各家 SDK,后期切换或者增加服务商时,改动量巨大。
我的方案是引入 适配器模式 :
<?php
namespace App\Services\AI;
interface AIProviderInterface
{
// 生成文本内容(剧本、人设等)
public function generateText(string $prompt, array $options = []): array;
// 生成图片
public function generateImage(string $prompt, array $options = []): array;
// 生成配音
public function generateVoice(string $text, array $options = []): array;
}
然后每个服务商实现这个接口:
<?php
namespace App\Services\AI\Providers;
use App\Services\AI\AIProviderInterface;
class OpenAIService implements AIProviderInterface
{
protected $apiKey;
protected $httpClient;
public function __construct()
{
$this->apiKey = config('ai.providers.openai.api_key');
$this->httpClient = new \GuzzleHttp\Client();
}
public function generateText(string $prompt, array $options = []): array
{
$response = $this->httpClient->post('https://api.openai.com/v1/chat/completions', [
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
],
'json' => [
'model' => $options['model'] ?? 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => $prompt],
],
'temperature' => $options['temperature'] ?? 0.8,
'max_tokens' => $options['max_tokens'] ?? 2048,
],
'timeout' => 120,
]);
$data = json_decode($response->getBody()->getContents(), true);
return [
'content' => $data['choices'][0]['message']['content'] ?? '',
'raw' => $data,
];
}
}
接着用一个工厂类来统一获取服务实例:
<?php
namespace App\Services\AI;
use App\Services\AI\Providers\OpenAIService;
use App\Services\AI\Providers\BaiduWenxinService;
use App\Services\AI\Providers\SparkService;
class AIProviderManager
{
public static function getProvider(string $name): AIProviderInterface
{
return match ($name) {
'openai' => app(OpenAIService::class),
'wenxin' => app(BaiduWenxinService::class),
'spark' => app(SparkService::class),
default => throw new \InvalidArgumentException("未知的AI服务商: {$name}"),
};
}
}
为什么我要花这么大篇幅讲这个适配层?因为很多半路出家的开发者会直接写
$openai = new OpenAI(...)
,把服务商绑死在业务代码里。一旦某个服务商涨价、限流、接口变更,你就要满项目去搜
openai
这个字符串。有了适配器层,换服务商只是改一个配置文件的事。这在接商单时尤其重要——客户的需求经常是"哪个便宜用哪个"。
3.2 剧本生成的 Prompt 模板与流程编排
AI 短剧系统最有技术含量的部分是 Prompt 模板设计。直接写"帮我写一个短剧剧本",AI 产出的内容天马行空,没法用。我设计了一套 结构化 Prompt 模板 ,让 AI 的输出被约束在固定 JSON 结构里。
<?php
namespace App\Services\ShortDrama;
class ScriptGenerator
{
protected $aiProvider;
public function __construct()
{
$this->aiProvider = AIProviderManager::getProvider(config('ai.default_provider'));
}
public function generate(array $params): int
{
// 1. 组装 Prompt
$prompt = $this->buildPrompt($params);
// 2. 调用 AI 生成
$result = $this->aiProvider->generateText($prompt, [
'model' => 'gpt-4o-mini',
'temperature' => 0.9,
'max_tokens' => 4096,
]);
// 3. 解析 AI 返回的 JSON
$scriptData = json_decode($result['content'], true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException('AI返回内容不是有效JSON');
}
// 4. 复核关键字段
if (empty($scriptData['scenes']) || count($scriptData['scenes']) < 5) {
throw new \RuntimeException('生成场景数不足5个,需要重试');
}
// 5. 保存剧本数据
return $this->saveScript($params['drama_id'], $scriptData);
}
protected function buildPrompt(array $params): string
{
$categoryMap = [
1 => '甜宠',
2 => '悬疑',
3 => '逆袭',
4 => '都市'
];
$category = $categoryMap[$params['category']] ?? '都市';
// 从模型训练时的最佳实践出发,这里需要一个严格的结构化输出约束
return <<<PROMPT
你是短剧编剧专家。请为用户创作一部 {$category} 类别的短剧剧本。
【剧集名称】{$params['title']}
【剧情概要】{$params['summary']}
【主要角色】{$params['characters']}
【集数要求】{$params['episodes']}集
【每集时长】约60秒
【输出要求】
请严格按照以下JSON结构输出(注意:只输出JSON,不要包含任何其他文字):
{
"title": "剧集标题",
"logline": "一句话故事梗概",
"characters": [
{"name":"角色名","gender":"男/女","personality":"角色性格","mission":"角色目标"}
],
"episodes": [
{
"episode_no": 1,
"title": "本集标题",
"scenes": [
{
"scene_no": 1,
"location": "场景地点",
"time": "日/夜",
"action_desc": "场景动作描述",
"dialogue": "对白内容",
"camera_move": "运镜方式",
"duration": 8
}
]
}
]
}
要求每集至少包含3个场景,每个场景要有明确的冲突或转折。
PROMPT;
}
protected function saveScript(int $dramaId, array $scriptData): int
{
// 保存到 script 表,返回自增ID
// 代码从略
}
}
这里有个关键点:AI 一次生成的 token 有限,不可能生成整部 30 集短剧。实际操作中,我采用的是" 先生成剧情大纲 + 角色设定 + 前三集完整脚本 ",然后用一个"续写"接口,基于已有内容继续生成后续集数,每续写一次生成 5 集,直到集数补满。这样避免了超长上下文导致的质量退化,也保持了角色设定的一致性。
3.3 异步任务处理:用 Redis 队列解决超时与重试难题
前面我提到同步调用会超时,这里展开讲讲我最终的异步方案。
在 Laravel 里,我们使用队列处理 AI 生成任务。用户在前端点"开始创作",先把任务数据写入 task 表(status=0),同时 dispatch 一个 Job 到 Redis 队列。Workers 进程消费 Job,调用 AI 接口,完成后回写 task 表。
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Task;
use App\Services\ShortDrama\ScriptGenerator;
use Illuminate\Support\Facades\Log;
class GenerateScriptJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $timeout = 300;
public $backoff = [10, 60, 300];
protected $taskId;
public function __construct(int $taskId)
{
$this->taskId = $taskId;
}
public function handle(): void
{
$task = Task::find($this->taskId);
if (!$task) {
return;
}
// 更新任务状态为执行中
$task->update(['status' => 1]);
try {
$params = $task->params;
$generator = new ScriptGenerator();
$scriptId = $generator->generate($params);
$task->update([
'status' => 2,
'result_data' => json_encode(['script_id' => $scriptId]),
]);
} catch (\Throwable $e) {
Log::error('剧本生成失败', [
'task_id' => $task->id,
'error' => $e->getMessage(),
]);
$task->update([
'status' => 3,
'error_msg' => $e->getMessage(),
]);
throw $e;
}
}
}
Laravel 队列的重试机制(
$tries = 3
和
$backoff
)非常实用。AI 接口的失败原因通常是限流或网络抖动,第一次失败等 10 秒重试,第二次失败等 60 秒,第三次失败等 300 秒,第三次还失败就放弃并把错误写进
error_msg
。用户端看到任务状态为"失败"时,可以点"重新生成",新任务会基于原参数重新跑一遍。这是一套健壮的任务闭环。
4. 分镜生成与 AI 配图:一张图一个镜头的生产流水线
剧本搞定之后,下一个核心环节是 分镜生成与 AI 配图 。短剧是视觉内容,每一集观众看到的都是"镜头画面 + 对白配音"的组合。这里我做了两件事:一是把剧本文本解析成结构化的分镜列表,二是为每个分镜调用 AI 绘图接口生成参考画面。
4.1 分镜解析算法:从剧本 JSON 到 shot 表
其实上一步 AI 输出的剧本 JSON 里已经包含了 scenes 数组,分镜解析就是把 episodes 里的 scenes 拆解并写入 shot 表。但这里有一个隐藏的难点: 每个场景的 "duration" 字段加起来要等于 60 秒左右(一集短剧),但 AI 生成时不一定精确 。
我的做法是写一个"时长归一化"逻辑:
public function normalizeDuration(array $scenes, int $targetDuration = 60, int $minSceneDuration = 3): array
{
$total = array_sum(array_column($scenes, 'duration'));
if ($total <= 0) {
$total = $targetDuration;
}
$ratio = $targetDuration / $total;
foreach ($scenes as $index => $scene) {
$scenes[$index]['duration'] = max($minSceneDuration, (int) round($scene['duration'] * $ratio));
}
return $scenes;
}
正常运行时,AI 输出的每集总时长可能只有 45 或 80 秒,我们用这个归一化函数把它缩放到 60 秒左右,同时保证每个分镜最少 3 秒——这个值是从短剧观看习惯里总结出来的:太长的镜头显得拖沓,太短则画面看不清。3-8 秒是一个比较舒适的镜头节奏。
4.2 配图 Prompt 的"一致性"设计
做 AI 短剧最难的是 角色一致性 。用 Midjourney 或 Stable Diffusion 生成角色图,第一张生成一个"穿红色衣服的女孩",第二张同样描述可能就生成另一个完全不同长相的女孩。观众会立刻觉得"这不是同一个人,出戏了"。
我的方案是:给每个角色建立一个"视觉锚点"描述,然后把它拼进每张配图的 Prompt 里。这个锚点在角色创建时生成,通常是一个固定格式:
"固定锚点": 25岁东亚女性,瓜子脸,眼角有泪痣,黑色长发,身高165cm,穿衣风格以白色连衣裙为主
生成分镜配图时,Prompt 模板会加入这个锚点:
protected function buildImagePrompt(array $shot, array $character): string
{
return "电影级短剧画面,"
. "角色:" . $character['visual_anchor'] . ","
. "动作:" . $shot['action_desc'] . ","
. "场景:" . $shot['scene_desc'] . ","
. "运镜:" . $shot['camera_move'] . ","
. "画面比例9:16,竖屏构图,电影感,高细节,3D渲染风格";
}
这里要注意,
visual_anchor
是从角色表里读出来的,每次生成配图都带上它,才能保证 AIGC 出来的多张图在"人设"上不跑偏——这是通过
工程手段解决模型不可控问题
的典型做法。
4.3 图片素材的上传与压缩处理
AI 生成的图片可能单张 3-5MB,直接存云存储成本高不说,前端加载也慢。我封装了一个
ImageProcessor
服务,自动处理上传:
<?php
namespace App\Services\Media;
use Intervention\Image\ImageManager;
class ImageProcessor
{
protected $manager;
public function __construct()
{
// 具体驱动的选择以环境实际支持的扩展为准,这里以GD为主
$this->manager = new ImageManager(['driver' => 'gd']);
}
public function process(string $sourcePath, string $targetPath): array
{
$image = $this->manager->make($sourcePath);
$width = $image->width();
$height = $image->height();
// 竖屏短剧,强制裁剪到 9:16 比例
$image->resize(720, 1280, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
$image->crop(720, 1280, 0, 0);
$image->save($targetPath, 80);
return [
'width' => 720,
'height' => 1280,
'size' => filesize($targetPath),
];
}
}
这里有几个细节值得说。第一,短剧最终的呈现载体是手机竖屏,所以统一做 9:16 裁剪,避免画面出现黑边;第二,压到 720 宽足够清晰,不会对平台审核有压力;第三,JPG 质量设为 80,人眼基本看不出压缩痕迹,但体积能小 60% 以上。
5. Uniapp 前端落地:多端适配下的核心页面与状态管理
前端我用的是 Uniapp + Vue3 组合。选 Vue3 是因为它的组合式 API 写业务逻辑更清爽,尤其是创作这种多步流程。
5.1 页面结构与路由设计
整个 App 分五块:首页(作品列表)、创作中心(创建短剧)、剧本编辑器(剧本与分镜管理)、任务中心(异步任务进度)、我的(个人中心)。
Uniapp 里有一个容易踩坑的点:
路由参数传递
。短剧详情页要接收
drama_id
,很多新手会这样做:
uni.navigateTo({
url: '/pages/drama/detail?id=' + dramaId
})
然后在详情页用
onLoad(options)
接收,这没错,但当参数特别多、比如从列表页带整套筛选条件过去时,URL 会被撑爆或者被转义搞乱。这时候建议这样处理:
// 传递复杂参数
const data = { dramaId: 123, from: 'list', filters: { category: 1, page: 2 } };
uni.navigateTo({
url: '/pages/drama/detail?data=' + encodeURIComponent(JSON.stringify(data))
});
// 接收复杂参数
onLoad(options) {
const data = JSON.parse(decodeURIComponent(options.data));
console.log(data)
}
这套"JSON 序列化 + URL 编码"的方案在处理带嵌套对象的参数时特别稳。单页面内状态共享则用 Pinia,跨页面传递尽量走前端本地存储或请求后端接口,不要依赖 URL 传参——URL 只传 ID。
5.2 创作中心的"多步向导"组件设计
创作中心是用户创建 AI 短剧的入口,我设计成三步向导:
- 第一步:填写剧集基本信息(名称、分类、剧情概要)
- 第二步:配置角色(名字、人设、性别,可添加多个)
- 第三步:选择生成参数(集数、每集时长)并提交任务
每一步的状态存在 Pinia store 里,最后一步点击"开始创作"时统一提交到后端接口并创建任务。
// store/drama.js
import { defineStore } from 'pinia'
export const useDramaStore = defineStore('drama', {
state: () => ({
draftId: null,
title: '',
category: 1,
summary: '',
characters: [],
episodes: 10,
durationPerEpisode: 60,
}),
actions: {
reset() {
this.draftId = null
this.title = ''
this.category = 1
this.summary = ''
this.characters = []
this.episodes = 10
this.durationPerEpisode = 60
}
}
})
这个三步向导看起来简单,但正是"把 AI 能力产品化"的缩影——用户不需要理解 Prompt 是什么,只需要填几个中文选项,系统就能帮他跑完整个创作流程。
5.3 任务进度的实时反馈
任务中心页面要展示每个创作任务的状态。Uniapp 里我使用
setInterval
每 5 秒轮询一次后端接口获取任务状态,当状态变为 2(成功)时,前端自动跳转到剧本详情页。这个轮询方案在并发量不大时完全够用,而且比 WebSocket 实现简单得多。
轮询要注意一个细节:页面离开时一定要清除定时器,否则用户从任务中心跳到详情页,定时器还在后台跑,浪费流量也消耗用户电量。
onUnload() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}
5.4 自定义分享好友与隐私政策适配
短剧系统天然需要分享功能,创作者把自己生成的作品分享给朋友或发布到社群。Uniapp 在微信小程序里的分享需要单独配置。因为整套源码是多个页面结构,建议在 App.vue 的
onLaunch
里统一设置分享逻辑,也可以在
onShareAppMessage
生命周期里针对每个页面定制分享卡片。这里有一个最新搜索里反复出现的坑:
onShareAppMessage 被全局方法覆盖
。如果你的
main.js
里挂载了全局分享逻辑,页面又自己定义了
onShareAppMessage
,两者可能互相覆盖。
我的解法是:在页面实例中主动定义分享,并在
onLoad
里把当前页面的分享参数写入 Pinia,由统一模块读取:
// pages/drama/detail.vue
onShareAppMessage() {
const dramaStore = useDramaStore()
return {
title: `推荐一部短剧《${dramaStore.title}》`,
path: `/pages/drama/detail?id=${dramaStore.draftId}`,
imageUrl: dramaStore.coverUrl
}
}
另外,App 上架安卓应用市场和 iOS 时,涉及隐私政策弹窗。如果用户不同意隐私政策,需要退出 App。Uniapp 里可以这样处理:
// 用户点击不同意的回调
function handleDisagree() {
// 小程序里不能直接退出,引导用户主动关闭
// App端可调用 plus.runtime.quit()
// #ifdef APP-PLUS
plus.runtime.quit();
// #endif
// #ifdef MP-WEIXIN
uni.showModal({
title: '提示',
content: '需要同意隐私政策后才能继续使用',
showCancel: false,
confirmText: '知道了'
})
// #endif
}
这个适配点很多人不重视,结果提审时被卡,反复打回。不同平台的策略差异很大:小程序不能主动退出(这是微信的规则),App 里可以用
plus.runtime.quit()
强制退出。
6. AI 创作者后台:PHP 管理端的审核与发布联动
系统不是做完用户端就完了,一个完整商单还包括管理后台。创作者在 App 里生成的短剧,要经过后台审核之后才能正式发布到各大渠道。
6.1 基于角色的权限控制
用户表里我加了一个
role
字段:
1
普通用户、
2
创作者、
3
管理员。管理员登录后,通过中间件拦截路由,只允许
role=3
的用户访问
/admin/*
路径。
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && $request->user()->role !== 3) {
return response()->json(['message' => '无权限访问'], 403);
}
return $next($request);
}
这套方案在 API 驱动的架构里是最常见的权限控制方式,简单、有效,PHP 和 Laravel 生态里也有一堆现成包可以用。
6.2 审核流与分发
管理员进入后台后,看到的是待审核作品列表,点进详情页可以看到剧本内容、分镜列表、AI 生成图。审核通过后,系统自动把成片信息同步到各视频平台的发布队列——这里我用了一个
publish_channel
表来维护分发渠道:
| 渠道 | 对接方式 | 状态 |
|---|---|---|
| 抖音 | 开放平台 API | 待点击授权 |
| 快手 | 开放平台 API | 待点击授权 |
| 微信小程序 | 内部上架 | 已完成 |
| 自有 H5 | 直接发布 | 已完成 |
分发这块的难点在于各平台 API 要求不一致,目前我的做法是先做"复制文案 + 保存视频到相册"的半自动方案,让运营人员手动复制到对应平台发布。全套自动分发需要申请各个平台的开发者权限,流程比较长,作为源码版本可以预留扩展接口。
7. 环境部署与典型问题排查:从 0 到 1 跑通整套系统
最后这部分是实战经验总结,也是很多开发者拿到源码后卡住的地方。我会按部署顺序来说,每一步都列出我实际碰到的坑。
7.1 PHP 与 MySQL 环境的安装配置
本地开发我建议用 PHPStudy、XAMPP 或者 Docker 都行。在 Linux 服务器上,我习惯用 Docker 装 MySQL 8.0。这里有一点必须提醒: MySQL 8.0 的默认认证插件是 caching_sha2_password,而 PHP 5.x 的老项目可能连接不上 ,需要手动改认证方式。如果你用的是 PHP 8.1 + PDO 方式连接,这个坑基本不存在。
docker run --name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=your_password \
-e MYSQL_DATABASE=short_drama \
-e MYSQL_USER=drama_user \
-e MYSQL_PASSWORD=drama_password \
-d mysql:8.0
PHP 侧的 PDO 连接:
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=short_drama;charset=utf8mb4';
$pdo = new PDO($dsn, 'drama_user', 'drama_password', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
7.2 Uniapp 打包上线实用要点
之前好几个人问过我:Uniapp 怎么打包?这里统一回答。
-
微信小程序
:用 HBuilderX 打开工程,选"运行 → 运行到小程序模拟器",微信开发者工具里打开生成的
dist/dev/mp-weixin目录即可。 - App 安卓包 :HBuilderX 里点"发行 → 原生App-云打包",在后台配置包名和证书(Android 需要签名证书,可以在 HBuilderX 中生成通用证书),等几分钟出包。
- iOS 上架 :需要 Apple 开发者账号,云打包生成 ipa 后用 Transporter 上传到 App Store Connect。
打包时还有个细节:小程序端如果要用
chooseMedia
上传视频,需要在
manifest.json
的 mp-weixin 节点里配置
permission
字段,否则真机调试会报"chooseMedia:fail api scope is not declared"。
7.3 系统上线后的性能瓶颈与优化方向
如果你是用这套源码去做商单,上线后最先会遇到两个性能瓶颈:一是 AI 接口的并发调用速度,二是 MySQL 的查询压力。
AI 接口的并发我很推荐用 Laravel Horizon 来管理 Redis 队列。Horizon 能实时监控队列吞吐量、失败任务、worker 数量,还能按任务类型设置不同的并发数。生成剧本任务的 API 调用频率是每分钟 60 次,配图任务可能是每分钟 30 次,分开控制可以避免一种任务把额度耗尽。
MySQL 的优化主要靠索引和缓存。高频查询如"首页推荐剧集列表""任务列表"都可以加一层 Redis 缓存:查询时先读缓存,没有再查数据库,回填缓存并设置 5 分钟过期。这能撑住三到五万日活完全没问题。
8. 源码版的定位思考与二次开发建议
最后聊聊这套源码该怎么用。如果你是一个独立开发者或小团队负责人,拿到这套 PHP + MySQL + Uniapp 的 AI 短剧创作系统源码,我的建议是不要一上来就大改架构,先把它跑起来,让导演或运营同事试用,收集真实的挫败点,再针对性地迭代。
目前整个系统里,AI 生成剧本、生成分镜、生成配图这条主链路已经完整跑通,属于"能用"的水平。但如果你要让客户掏钱,还值得在几个方向加强:
一个是增加智能校验模块——AI 生成的内容不一定符合短剧平台审核规则,比如"未成年角色形象""医疗用语"等高风险词库可以做成内置敏感词过滤,生成完成后自动预警。
另一个是增加"二次编辑"能力。AI 不是全部,短剧创作的最后一公里必须有人工参与。在剧本编辑页面提供"每个场景可以手动修改对白、重新生成配图、调整时长"这类能力,会大幅提升用户满意度。
还有一个方向是多语言。短剧出海是热门话题,中文短剧在海外平台也火。你可以在角色表、剧本表里预留
language
字段,再接入翻译 API,把整个生产链路做成中英双语——这又是另一套产品了。
AI 短剧这个赛道,代码只是底座,真正值钱的是这套流程编排能力和内容运营体系。先把技术底座做扎实,后面往上叠加的都是溢价空间。希望这篇实现方案能给你提供一条清晰的路径,少走些我踩过的弯路。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)