资讯动态

ThinkPHP+ffmpeg队列实现体育赛事HLS切片视频管理系统

发布时间:2026/10/21 3:01:59 来源:尧图企业网站定制
前阵子帮朋友维护一个体育赛事回放的小平台原片全是几GB的高清录像赛后访问量一上来直接把老方案整崩了。后来我用ThinkPHP重写了一套体育赛事切片视频管理系统把每场比赛的录像自动切成HLS分片再靠后台管理赛事、管理转码任务、管理分片文件才真正把这事儿扛住了。这篇就把这套系统的设计思路、数据结构、切片链路、播放器鉴权和排障过程完整捋一遍给想用PHP做视频点播后台的朋友做个参考。尤其是切片的坑能帮你少走不少弯路。1. 为什么是ThinkPHP这套系统的定位和选型逻辑1.1 管理后台的真正瓶颈不在并发而在业务编排很多人一听到“视频系统”第一反应就是上云转码、微服务、前后端分离甚至恨不得搞一套分布式任务调度。但实际情况要看项目规模。体育赛事的切片视频管理核心难点从来不是高并发而是业务编排一场比赛有原片、回放、集锦、多机位切片任务几十个状态有排队、转码中、成功、失败还得做失败重试、磁盘清理、播放鉴权。这类系统的数据量撑死也就几百场赛事、上万条分片记录单机MySQL完全没压力。真正的复杂度在于“切片任务的调度”和“文件与业务数据的对应关系”。ThinkPHP这种MVC框架处理这种后台管理业务反而顺手ORM把关联查询做好了验证器帮我把表单校验挡住队列组件又直接解决了切片任务的异步执行。我用一年多的体会是选型不需要追新能把自己从重复劳动里解放出来就够了。1.2 我选ThinkPHP的四个具体理由第一模型关联省事。赛事、视频资源、转码任务、分片记录之间是典型的一对多和多对一关系ThinkPHP的模型关联写起来很直接页面要展示“某场比赛的所有录像和各自切片状态”一条查询链就能拿全。第二队列组件是现成的。TP的Queue类在Linux下配合php think queue:work就能跑常驻消费者切片这种耗时操作放到队列里Web请求不会卡死。虽然也可以自己写一个proc_open的调度器但现成的队列管理、失败重试、任务延迟这些能力没必要重复造轮子。第三验证器和中间件够用。后台有很多表单比如创建赛事、添加视频资源、配置切片参数用TP的验证器统一校验参数格式比自己在控制器里写一堆if判断干净得多。权限中间件直接过滤未登录的访问播放鉴权接口也能挂在同一个管道里。第四生态和排查成本低。TP在国内用的人多遇到问题是“搜得到答案”的。对一个小团队或者个人维护者来说技术栈越常见后续维护成本越低。注意这里说的ThinkPHP以6.x版本为例。如果你还在用5.x队列和中间件的写法略有差异但设计思路完全一样。1.3 什么时候不该用这套方案也得说清楚边界。如果你要做的是几十万路并发的直播平台或者需要复杂的版权检测、多码率自适应转码那PHP管理后台只适合做“管理端”转码和分发都得交给专业组件或云服务。我这套方案适合的是比赛场次可控、原片量不大、主要做点播回放和集锦管理的场景。一个学校内部赛事库、一家本地栏目组的体育内容管理这类需求用ThinkPHP这套架构性价比很高。2. 数据模型设计赛事、视频、转码任务、分片怎么存切片视频管理系统跟普通CMS最大的区别在于它要管理的不只是“一个视频文件”而是一堆由转码产生的分片文件、转码任务状态、失败原因以及和赛事维度的归属关系。表结构设计得不好后面写统计和重试逻辑会很痛苦。2.1 核心表结构四个表分别管什么我最后落地的核心表一共四张分别是赛事表、视频资源表、转码任务表、分片记录表。赛事表match记录一场比赛的基本信息比如对阵双方、比赛时间、所属赛事阶段、状态。字段可以精简成CREATE TABLE match ( id int(11) unsigned NOT NULL AUTO_INCREMENT, name varchar(128) NOT NULL DEFAULT COMMENT 赛事名称例如足球友谊赛A队VS B队, category varchar(64) NOT NULL DEFAULT COMMENT 项目分类足球/篮球/羽毛球等, match_time datetime NOT NULL COMMENT 比赛时间, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0待发布 1已发布 2已下线, created_at datetime DEFAULT NULL, updated_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_match_time (match_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT赛事表;视频资源表video一场比赛下可以挂多个视频全场录像、精彩集锦、单节回放都算独立视频条目。每个条目需要记录原片路径、转码状态和最终生成的m3u8地址CREATE TABLE video ( id int(11) unsigned NOT NULL AUTO_INCREMENT, match_id int(11) unsigned NOT NULL COMMENT 所属赛事ID, title varchar(255) NOT NULL COMMENT 视频标题如第一节回放, source_path varchar(500) NOT NULL COMMENT 原始视频文件路径, duration int(11) NOT NULL DEFAULT 0 COMMENT 原片时长(秒), status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待切片 1切片中 2切片成功 3切片失败, m3u8_path varchar(500) DEFAULT COMMENT 生成的m3u8相对路径, file_size bigint(20) NOT NULL DEFAULT 0 COMMENT 原片大小(字节), created_at datetime DEFAULT NULL, updated_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_match_id (match_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT视频资源表;转码任务表transcode_task每一个视频的切片操作生成一条任务。之所以单独建表而不是只用video表的status字段是因为重试次数、失败原因、执行耗时这些信息靠单字段记录不够而且一个视频可能因为码率版本不同需要多次切片。状态机字段是这套设计的核心后面会细讲。分片记录表segment记录转码成功后生成的每一个ts分片。分片多但每条记录很小批量插入即可。有了这张表就能知道每个视频有多少分片、某段分片是否丢失、磁盘占用按视频维度统计。CREATE TABLE segment ( id int(11) unsigned NOT NULL AUTO_INCREMENT, video_id int(11) unsigned NOT NULL, file_index int(11) NOT NULL DEFAULT 0 COMMENT 分片序号, file_path varchar(500) NOT NULL DEFAULT COMMENT ts文件相对路径, duration decimal(10,3) NOT NULL DEFAULT 0 COMMENT 分片时长(秒), file_size int(11) NOT NULL DEFAULT 0 COMMENT 分片大小(字节), created_at datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_video_index (video_id, file_index) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT分片记录表;2.2 状态字段为什么一定要用“小状态机”很多新手会把video表的status写成简单的0和1比如“0未处理 1已处理”。但视频管理不是一次性的会有重试、部分失败、重新切片的情况单存一个状态根本不够。我采用的是一个极简状态机0 - 1 - 2 / 30待切片视频刚创建还没有转码任务。1切片中已有任务正在执行此时可以防止重复提交切片请求。2切片成功m3u8和ts分片都已生成可以播放。3切片失败任务执行出错前端展示“重试”按钮。这个状态机的关键点在于只有“3失败”和“0待切片”状态才能发起新的切片任务“1切片中”会被接口直接拒绝。这样即使用户手抖连点几次“重新切片”也不会同时起一堆ffmpeg进程把服务器打满。2.3 查询索引和磁盘占用统计设计索引的时候要考虑后台列表页的查询习惯。最常见的列表是“按赛事查看所有视频”所以video.match_id必须有索引。另一个高频操作是“找所有失败视频”方便后台批量重试所以video.status也要有索引。转码任务表按video_id和status查询比较多同样建普通索引就够了。统计磁盘占用时用segment表聚合SELECT video_id, COUNT(*) AS ts_count, SUM(file_size) AS total_bytes FROM segment GROUP BY video_id;这个查询在数据量大的时候会稍微慢一点但几千个视频没问题。老视频的清理也靠它找出所有历史赛事的分片按占用空间排序然后按策略删除文件再删记录。提示分片文件不要直接存在数据库业务库所在磁盘的根目录建议单独挂数据盘免得磁盘写满把数据库也拖死。我遇到过磁盘满了导致MySQL只读的情况教训深刻。3. 切片转码链路ffmpeg参数与ThinkPHP队列的配合3.1 为什么选HLS而不是直接放MP4体育赛事回放有几个特点视频长全场可能一两个小时、用户会拖动进度条、移动端访问占比高。直接放一个MP4浏览器得先下载整个文件才能从头播拖动进度条时要依赖服务端支持Range请求移动端的兼容性还不稳定。HLSHTTP Live Streaming把视频切成一小段一小段的ts文件通过m3u8索引文件按需加载。播放器先读m3u8列表再按顺序请求ts分片拖动进度条相当于直接定位到对应的分片序号体验好很多。对于点播场景m3u8可以设置成完整的vod列表播放器一次性拿到全部分片地址支持拖动很方便。3.2 ffmpeg切片命令逐参数拆解核心命令长这样ffmpeg -y -i /data/uploads/source_20240101.mp4 \ -c:v libx264 -preset veryfast -crf 23 \ -c:a aac -b:a 128k \ -start_number 0 \ -hls_time 10 \ -hls_list_size 0 \ -hls_playlist_type vod \ -hls_segment_filename /data/hls/video_123_%04d.ts \ /data/hls/video_123.m3u8逐条解释一下关键参数-c:v libx264视频编码用H.264。为什么不直接复制流原片可能是各种格式统一转成H.264能保证浏览器和播放器的兼容性而且可以顺便压缩体积。-preset veryfast转码速度和输出文件大小的平衡点。用ultrafast会快但文件大用slow画质好但太慢。赛事切片追求的是速度和稳定veryfast是我试下来最稳的。-crf 23画质控制。CRF值越小画质越好、文件越大23是中规中矩的档位回放看得过去。如果机位原片是高清素材我会用22。-c:a aac -b:a 128k音频转AAC码率128kbps赛事解说和现场音足够。-hls_time 10每个ts分片10秒。太长首屏加载慢太短分片文件太多10秒是我实测的均衡值。-hls_list_size 0不限制m3u8列表条数意思是生成完整的播放列表。直播场景才需要设为几秒只保留最近几个分片点播一定设成0。-hls_playlist_type vod声明这是点播列表播放器可以安全地缓存和跳转。-hls_segment_filename ..._%04d.ts分片文件的命名规则%04d表示从0000开始递增。转码完成后需要检查m3u8文件是否生成、分片数量是否合理ffprobe -v error -count_frames -select_streams v:0 -show_entries streamwidth,height,duration -of csvp0 /data/hls/video_123.m3u8这个命令能读出m3u8列表里视频的宽高和总时长用来和原片对比判断切片是否完整。3.3 转码任务入队与失败重试这里的核心原则是永远不要在Web请求里直接执行ffmpeg。一个转码任务可能跑几分钟浏览器早就超时而且同时来几个请求就能把CPU占满。正确做法是提交任务到队列由后台worker慢慢消费。ThinkPHP里用队列很简单。先定义一个任务类namespace app\job; use think\facade\Log; use think\queue\Job; class SliceVideoJob { public function fire(Job $job, $data) { $videoId $data[video_id]; $video \app\model\Video::find($videoId); if (!$video || $video-status ! 1) { $job-delete(); return; } try { $result $this-transcode($video); $video-status 2; $video-m3u8_path $result[m3u8_path]; $video-save(); $job-delete(); } catch (\Throwable $e) { Log::error(切片失败: . $e-getMessage()); $attempts $job-attempts(); if ($attempts 3) { $video-status 3; $video-save(); $job-delete(); } else { $job-release(10); // 10秒后重试 } } } private function transcode($video) { // 组装ffmpeg命令用exec执行检查退出码 // 如果退出码非0抛异常让队列进入重试逻辑 } }推开入口处控制器提交任务use think\facade\Queue; $taskData [video_id $videoId]; Queue::push(SliceVideoJob::class, $taskData, video_slice);这里有几个细节要注意。第一exec执行ffmpeg时必须检查返回值。ffmpeg成功退出时返回0任何非0值都说明有问题。很多人直接用exec($cmd, $output)不检查返回码任务失败还当成成功这是大忌。第二任务类里的状态判断很关键。只有status 1切片中才继续执行这是为了防止重试时任务重复执行。如果状态已经是2直接删任务不做任何操作。第三$job-release(10)的意思是把失败任务延迟10秒重新放回队列。重试超过3次就把视频状态标记为失败方便后台人工介入。要格外注意exec和shell_exec的区别exec只返回最后一行输出shell_exec返回全部输出但拿不到退出码。所以我的做法是用exec加引用参数传返回码exec($cmd, $output, $returnCode); if ($returnCode ! 0) { throw new \RuntimeException(ffmpeg exit code: . $returnCode); }3.4 队列Worker部署和内存控制队列写好了还得正确部署worker。TP6的队列命令是php think queue:work --queue video_slice --daemon --memory256参数说明--queue video_slice只消费video_slice队列避免和邮件、统计等其他任务混在一起。--daemon常驻运行不要用crontab每隔几秒跑一次queue:listen常驻能省掉进程反复启动的开销。--memory256内存超过256MB自动重启防止长时间运行积累内存泄漏。生产环境建议用supervisor这类进程管理工具守护workerworker挂了能自动拉起。我见过一晚上转码任务全部卡住就是因为worker进程悄悄退了没人发现。对资源紧张的小服务器可以限制并发ffmpeg进程数量。比如在worker里加一个信号量控制同一时间最多跑两个转码任务$sem sem_get(ftok(__FILE__, t), 2); sem_acquire($sem); // 执行ffmpeg sem_release($sem);或者更简单用-threads 2限制ffmpeg本身线程数避免单任务把整个CPU吃光。4. 后台管理功能列表、进度、重试与清理4.1 赛事视频汇总列表怎么查管理端首先要有“赛事列表页”和“视频列表页”。赛事列表页要展示的不只是赛事信息还包括该赛事下视频的汇总情况视频总数、切片成功数、失败数、待处理数。用ThinkPHP的模型关联可以一条查询拿全use app\model\Match; use app\model\Video; $matchList Match::with([videos]) -order(match_time, desc) -paginate(15); // 模板里可以循环 $match-videos实时统计 status 分布但如果数据量大了关联查询粒度太细会慢。我后来直接在match表加了冗余字段比如video_total、video_ready、video_failed每次切片任务状态变化时把这些冗余字段更新一下。列表页就不需要每行都做聚合了速度快很多。想清楚了再决定要不要做冗余。如果赛事总量不超过几百条没必要冗余直接聚合就行。冗余字段带来的问题是更新多次但这事儿换来的却是列表页的秒开体验我觉得值。4.2 切片失败后的重试该做什么重试绝不是把status改回0然后重新入队那么简单。重点是要清理“残留文件”。一个视频切片失败可能是切到一半断的磁盘上已经留下了一堆半截的ts文件。如果不清理就重新切片残留文件会一直堆在磁盘里。我的重试逻辑是先把视频状态改为0待切片。删除该视频ID相关的所有ts分片和m3u8文件。删除segment表中的分片记录。重新推一个转码任务到队列。清理文件的操作在TP里可以用一个控制器的接口触发public function retry($videoId) { $video Video::find($videoId); if (!$video || !in_array($video-status, [2, 3])) { return $this-error(当前状态不允许重试); } $hlsDir $this-getHlsDir($videoId); if (is_dir($hlsDir)) { // 删除目录下所有分片和m3u8 array_map(unlink, glob($hlsDir . /*.ts)); unlink($hlsDir . /*.m3u8); } Segment::where(video_id, $videoId)-delete(); $video-status 0; $video-save(); Queue::push(SliceVideoJob::class, [video_id $videoId], video_slice); return $this-success(已重新加入切片队列); }这里有个容易忽略的小坑如果video状态是2已经成功用户点击重试意味着要重新切片。这时候同样要清理旧分片否则新旧文件混在一起m3u8列表会乱掉。所以接口里允许状态2也走重试流程但前端文案要提示清楚“重新切片会删除旧文件”。4.3 用定时任务守住磁盘空间赛事视频有一个特点越老的比赛回放价值越低。全场录像的ts分片通常只留最近几个月的热度期期限过了就可以清理不必永久保留。我做一个定时任务每天凌晨检查所有已成功的视频按创建时间排序找出超过保留期比如90天的视频再按固定规则删除优先保留季后赛或决赛这类重要场次的视频怎么区分我在match表里加了一个is_important字段定时任务跳过它。普通场次超过保留期直接清理。清理任务的核心命令很简单php think clear_expired_video控制器逻辑public function handle() { $expireDays 90; $deadline date(Y-m-d H:i:s, strtotime(-{$expireDays} days)); $videos Video::where(status, 2) -where(created_at, , $deadline) -select(); foreach ($videos as $video) { if ($video-match-is_important) { continue; } // 删除目录、删除分片记录、删除视频记录 } }定时任务运行前最好先统计一下磁盘剩余空间空间不足时自动调整清理范围避免磁盘写满。5. 播放器接入与签名鉴权m3u8和ts文件的权限处理5.1 播放器选型与hls.js的接入后台管理好了最终还是得让用户能在前端看视频。体育赛事点播页面我用的方案是视频标签 hls.js库兜底Safari和iOS原生支持HLS直接赋给video标签的src就行其他浏览器用hls.js实时解析并分段请求ts。前端代码大致长这样video idplayer controls playsinline/video script srchttps://cdn.jsdelivr.net/npm/hls.js1/script script const video document.getElementById(player); const playlistUrl /play/url?video_id123; if (Hls.isSupported()) { const hls new Hls({ maxBufferLength: 30, maxMaxBufferLength: 60 }); hls.loadSource(playlistUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, function (event, data) { if (data.fatal) { switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: hls.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: hls.recoverMediaError(); break; default: hls.destroy(); } } }); } else if (video.canPlayType(application/vnd.apple.mpegurl)) { video.src playlistUrl; } /script解释一下为什么要加maxBufferLength。赛事视频分片多如果默认缓冲策略太激进会一口气拉很多ts文件带宽消耗大、磁盘缓存也扛不住。稍微限制一下缓冲区长度让播放器按需加载对服务器更友好。5.2 给m3u8和ts做时效签名视频内容不能是裸奔的至少要防止别人拿到地址就到处转发。我给播放地址做了时效签名。签名逻辑设计为根据视频ID、过期时间戳、密钥计算一个MD5签名字符串。public function playbackUrl($videoId) { $expire time() 3600; $sign md5($videoId . $expire . your_secret_key); return url(/hls/play, [video_id $videoId, expire $expire, sign $sign]); }服务端校验签名过期或签名不对直接返回403public function play($videoId) { $expire input(expire); $sign input(sign); if (time() $expire || md5($videoId . $expire . your_secret_key) ! $sign) { abort(403, 播放地址已失效); } $video Video::find($videoId); // 返回m3u8文件内容或者重定向到实际的m3u8地址 }接下来是最容易踩的坑m3u8文件本身带签名容易但ts分片怎么带签名假如m3u8文件里的分片地址是/hls/video_123_0000.ts播放器请求ts文件时原生video标签是不会帮你带上任何自定义请求头的。如果ts地址没有签名那么用户拿到m3u8地址后可以绕过你前面设置的权限直接遍历ts文件名把全片拉走。我试过几种方案最后拿出的是“改写m3u8内容”的方式后端读取m3u8文件内容把每个ts分片的地址改写为带同样签名参数的地址。public function play($videoId) { // 校验签名略 $video Video::find($videoId); $content file_get_contents($video-m3u8_path); $pattern /^([^#].*\.ts)$/m; $content preg_replace_callback($pattern, function ($matches) { $tsPath $matches[1]; $expire input(expire); $sign md5($videoId . $expire . your_secret_key); return $tsPath . ?expire . $expire . sign . $sign; }, $content); return response($content)-header([Content-Type application/vnd.apple.mpegurl]); }这样播放器在请求ts分片的时候会带着签名参数请求nginx或应用层再校验一次同款签名就行。唯一的代价是m3u8文件每次都是动态生成的但它的内容很小性能开销可以忽略。提示生产环境也可以用nginx的secure_link模块实现类似机制文件服务层面直接鉴权应用层只负责生成合法URL。两种思路效果差不多看你更熟悉nginx还是PHP。5.3 跨域、缓存和兼容性坑跨域问题要提前想。如果前端播放页面在www.example.comm3u8和ts文件在video.example.comhls.js跨域请求ts文件浏览器会发CORS预检。需要在nginx层对视频域名加响应头location /hls/ { add_header Access-Control-Allow-Origin *; add_header Cache-Control public, max-age86400; }m3u8文件本身不能设置Cache-Control: max-age缓存太久因为它的地址是动态签名的过期后旧缓存会失效。云服务器上如果CDN缓存了m3u8也会出现签名过期导致播放失败的问题。ts文件则可以放心缓存一天因为它们内容固定只是名字带签名的查询参数而已。还有两个兼容性问题常见一是微信内置浏览器。安卓微信的WebView对HLS支持一直不稳定建议在页面里检测到微信环境时提示用户用系统浏览器打开或者接一个带原生解析能力的播放器。这个属于产品决策技术上是绕不开的。二是播放中拖动进度条卡住。通常是因为m3u8的#EXT-X-TARGETDURATION和实际分片时长不一致或者分片序号不连续。我遇到过某次转码中断导致m3u8里混了一段缺失的分片序号播放器拉不到对应ts就卡住。排查时用grep ^video_123_ video_123.m3u8 | sort | tail看看序号是否连续不连续就重新切片。6. 生产环境实测中的几个坑和排查思路6.1 现象表格一页纸排查清单整理一下我在实际运维中碰到的坑做成表格方便大家对照现象可能原因排查命令/手段解决思路转码任务一直卡在切片中worker进程挂了或队列阻塞ps aux | grep queue:work用supervisor守护worker检查队列日志m3u8能访问但ts文件全部403nginx位置规则或防盗链拦截tail -f /var/log/nginx/error.log调整nginx location排除签名参数干扰拖动进度条后播放器卡顿分片序号缺失或时长不一致grep \.ts xx.m3u8 | wc -l对比分片记录表缺失就重新切片磁盘占用迅速增长没有执行过期清理或转码残留du -sh /data/hls/*增加定时清理失败任务加重试前清理ffmpeg进程被杀任务失败服务器内存不足dmesg -T | grep -i killed限制ffmpeg线程数或并发转码任务数手机浏览器播放黑屏视频编码或HLS兼容问题查看浏览器控制台报错统一使用H.264 AAC避免h2656.2 转码进程僵死的处理从退出码到内存限制有一次半夜所有转码任务全部失败我查日志发现ffmpeg的退出码是-9也就是进程被信号直接杀死。第一反应是看内存free -m发现交换分区都被打满了。dmesg里果然看到内核在杀进程。原因是转码高分辨率视频时ffmpeg默认线程数等于CPU核心数几个任务同时跑内存直接爆掉。解决方法是给ffmpeg加-threads 2同时把并发转码任务限制到2个。虽然转码速度慢了点但系统稳定了任务不会再半夜集体阵亡。排查思路值得记一下任何ffmpeg异常退出第一步不是看代码而是看退出码和系统日志。退出码1371289和-9都代表进程被kill优先查内存退出码1、2这类要查ffmpeg自身的报错输出日志里有具体原因。6.3 恶意请求和爬虫导致的带宽消耗体育赛事视频流量大容易被爬虫盯上。有人拿到m3u8地址后会写脚本把ts文件全部爬走带宽成本直线上升。我的对策除了签名鉴权外还在nginx层面做了请求频率限制和UA过滤。limit_req_zone $binary_remote_addr zonehls_limit:10m rate20r/s; location /hls/ { limit_req zonehls_limit burst30; if ($http_user_agent ~* (python|curl|wget|scrapy) ) { return 403; } }这里satisfy any之类的高级玩法先不提简单规则通常够用。真正要用好防盗链关键是分清“匿名可访问”和“签名的临时访问”。签名地址只发放给播放页面过期时间别设置太长我一个人习惯是1-2小时赛后再点开如果过期就重新生成。6.4 我的排查习惯最后说点运维层面的经验。这套系统跑起来之后我最少做的动作是登录服务器看日志最多做的是让程序把关键日志写到数据库表里方便在后台直接查。我给转码任务表加了一个last_log字段记录ffmpeg输出或异常堆栈。每次失败后台能看到具体的错误信息不用再去翻服务器日志文件。边调试边补这个字段的过程中我把绝大多数问题都解决在了“看到这里就知道是哪里出错”的状态。例如try { exec($cmd, $output, $returnCode); if ($returnCode ! 0) { throw new \RuntimeException(implode(\n, $output)); } } catch (\Throwable $e) { $task-last_log substr($e-getMessage(), 0, 1000); $task-save(); throw $e; }把错误信息直接落库排查效率和体验都会好很多。这个思路对任何任务型系统都适用。以上就是这套ThinkPHP体育赛事切片视频管理系统从设计到落地的全部核心内容。如果让我重新做一次我依然会选ThinkPHP做管理后台用队列调度ffmpeg把所有分片和任务状态都纳入数据库管理。唯一会更早注意的就是磁盘空间和worker进程守护这两件事越早处理运维阶段越省心。希望对你在做的项目有点帮助特别是切片和鉴权的那部分踩过一次就能体会其中的分量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑