从零构建AI视频SaaS系统:多服务商协作架构实战
记录我们在 ThinkPHP 6 React 全栈项目中踩过的坑与架构取舍一、背景为什么做这套系统去年团队接了一个需求帮一家销售公司做一套AI数字分身视频系统。听起来简单——调个API生成视频就行。但真正动手才发现生产级AI视频系统远比想象中复杂- 不同客户的AI预算不同有人用得起OpenAI有人只能用免费模型- 销售团队不懂AI prompt需要傻瓜式生成- 企业级SaaS必须支持多租户隔离- 视频生成动辄几十秒不能让用户干等这篇文章分享我们在架构设计和代码实现上的关键决策。演示地址Vireum 维朗 - AI数字人视频销售平台---二、多AI服务商协作不想被一家绑架2.1 问题单一供应商的风险最初方案是直接对接一个AI视频服务商。上线第二周就遇到了问题- 某免费模型高峰期频繁报错error code 1305高流量- 另一个服务商突然调价客户成本翻倍- 不同场景适合不同模型产品介绍 vs 数字人口播2.2 方案配置化多服务商架构我们设计了一套企业级AI配置机制php// 企业AI配置表company_ai_config├── company_id // 企业ID├── text_provider // 文本生成服务商├── video_provider // 视频生成服务商├── api_key_xxx // 各服务商API Key└── allowed_providers // 该企业可用的服务商列表(JSON)关键代码逻辑php// 获取企业可用的视频服务商public static function getAvailableVideoProviders($companyId, $userId){$config self::getCompanyConfig($companyId);$providers [];foreach ($config[allowed_providers] as $key) {if (self::isProviderAvailable($key, $config)) {$providers[$key] self::PROVIDERS[$key];}}return $providers;}2.3 智能推荐不同场景匹配不同服务商| 场景 | 推荐服务商 | 理由 ||---|---|---|| 15秒产品广告 | 可灵AI | 画面质量高适合商业场景 || 数字人口播 | 通义千问/火山引擎 | 中文口型同步效果好 || 批量低成本视频 | 文心一言免费模型 | 成本低适合测试 || 海外客户 | OpenAI | 英文场景效果最佳 |推荐逻辑根据 prompt 关键词自动匹配phppublic function recommendProvider($prompt, $availableProviders){// 根据prompt内容、时长、风格推荐最优服务商$matcher new VideoFeatureMatcher();return $matcher-analyze($prompt, $availableProviders);}---三、四端SaaS架构不是简单的复制粘贴3.1 架构演进最初我们只做了销售端后来需求不断膨胀v1.0 销售端销售人员用↓v1.5 企业后台管理员看数据↓v2.0 平台管理运营审核企业↓v2.5 落地页SEO获客最终形成四端架构┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ 销售端 │ │ 企业后台 │ │ 平台管理 │ │ 落地页 ││ (React) │ │ (React) │ │ (React) │ │ (React) │└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘│ │ │ │└──────────────────┴──────────────────┴──────────────────┘│┌───────┴───────┐│ ThinkPHP 6 ││ 统一API │└───────┬───────┘│┌───────┴───────┐│ MySQL Redis │└───────────────┘3.2 三端路由隔离ThinkPHP 路由按端分组各自独立认证中间件php// 平台端Route::group(/api/platform, function () {Route::get(companies, ...);// ...})-middleware([PlatformAuth]);// 企业端Route::group(/api/company, function () {Route::get(videos, ...);// ...})-middleware([CompanyAuth]);// 销售端Route::group(/api/sales, function () {Route::post(video/startGenerate, ...);// ...})-middleware([SalesAuth]);3.3 关键设计套餐权限控制企业套餐和个人套餐互斥套餐等级由 sort_order 决定php// 降级校验不允许从高级套餐降级if ($newPlan[sort_order] $currentPlan[sort_order]) {return error(当前已开通更高等级套餐无法降级);}功能权限用 JSON 字段控制5个核心开关json{ai_polish: true,video_generate: true,digital_human: true,ai_chat: false,custom_ai: false}---四、演示模式让客户放心体验4.1 需求ToB系统最怕什么潜在客户想体验但担心误操作把数据搞乱。我们需要一个演示模式- 一键开启所有写操作被拦截- 读操作完全正常体验流畅- 前端有提示但不要太突兀4.2 后端实现ThinkPHP 中间件拦截phpclass DemoMode{public function handle($request, \Closure $next){if (!config(app.demo_mode)) {return $next($request); // 正常模式直接放行}// GET请求放行只读if (strtoupper($request-method()) GET) {return $next($request);}// 白名单接口放行登录/登出等$allowList [/api/platform/login, /api/sales/login, ...];if (in_array(/ . $request-pathinfo(), $allowList)) {return $next($request);}// 其余写操作拦截return json([code 403, msg 演示模式下不允许修改数据]);}}4.3 前端实现前端动态获取配置条件渲染提示tsxconst [demoMode, setDemoMode] useState(false);useEffect(() {fetch(/api/config).then(res res.json()).then(data setDemoMode(data.data?.demo_mode || false));}, []);// 右下角小标签hover显示详情{demoMode (div classNamefixed bottom-4 right-4 groupdiv classNamebg-amber-500 text-white text-xs px-3 py-1.5 rounded-full演示模式/divdiv classNameabsolute bottom-full right-0 mb-2 bg-gray-800 text-white text-xs px-3 py-2 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity仅支持查看不允许修改数据/div/div)}效果右下角一个小圆点不挡内容鼠标放上去才显示完整提示。---五、技术选型思考5.1 为什么选 ThinkPHP 6考虑过 Laravel 和 Spring Boot最终选了 ThinkPHP- **客户环境**大部分中小企业用虚拟主机PHP部署成本最低- **二开生态**国内PHP程序员多客户后续招人维护容易- **框架成熟度**TP6的ORM、中间件、队列都很稳定5.2 为什么前端三端都用 React TS- **类型安全**SaaS系统接口多TypeScript能减少80%的运行时错误- **组件复用**四端共用一套组件库Button、Card、Select等- **Tailwind CSS**不写CSS文件开发效率提升明显5.3 队列为什么用数据库驱动视频生成是异步任务需要队列。考虑过 Redis 队列但最终选了数据库驱动- 中小企业不一定有Redis- 数据库队列更可靠任务不会丢- ThinkPHP Queue 对数据库队列支持很好---六、踩过的坑坑1视频状态查询不能前端模拟进度最初前端用 setInterval 模拟进度条从0%涨到100%。结果视频实际5分钟才生成前端已经显示100%了。**解决**前端每5秒真实轮询 checkVideoStatus 接口后端返回真实进度。坑2图片URL必须带完整域名前端开发时用相对路径上线后发现图片打不开。**解决**后端统一用 APP_URL 环境变量拼接完整URLphppublic static function fixUrl($url){if (empty($url)) return ;if (strpos($url, http) 0) return $url;return config(app.app_url) . $url;}坑3SOP话术表的creator字段最初用 user_id 记录创建者后来发现企业管理员和销售用户是两张表外键约束搞不定。**解决**用 created_by 字段不设置外键约束通过 user_type 区分用户类型。---七、写在最后这篇文章分享了我们团队在构建AI视频SaaS系统过程中的一些技术决策和踩坑记录。多服务商协作、四端架构、演示模式这些设计都是真实业务场景中倒逼出来的方案。目前这套系统已经完整落地涵盖了从AI视频生成、数字人播报、SOP话术管理到客户跟进的全链路。如果你也在做类似的方向或者对 ThinkPHP React 全栈架构感兴趣欢迎交流。**技术交流话题**- 多AI服务商的failover设计- SaaS多租户数据隔离方案- ThinkPHP 6 队列任务最佳实践- React Tailwind 组件库设计--- 本文 purely 技术分享不涉及商业推广。如需了解项目源码细节或技术合作可私信交流。---