Laravel执行Ghostscript进程致浏览器卡顿的解决方案咨询
解决Laravel 10中PDF压缩页面卡顿及进程终止问题
一、核心问题分析
你当前使用Process->run()属于同步阻塞执行,会导致HTTP请求一直等待Ghostscript进程完成,因此页面会卡顿直到压缩结束。同时,由于进程依附于当前请求进程,页面刷新时原请求进程已脱离前端控制,无法直接终止。
二、优化方案:异步处理+进程管理
1. 改用异步执行避免页面卡顿
将同步的run()替换为异步的start(),同时存储进程ID(PID)以便后续控制:
use Illuminate\Support\Facades\Cache; use Symfony\Component\Process\Process; $cmd = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -sOutputFile={$output} {$input}"; // 用fromShellCommandline避免文件名含空格时的命令分割错误 $process = Process::fromShellCommandline($cmd); $process->setTimeout(3600); $process->start(); // 将PID存入缓存,关联用户会话标识 $processKey = 'pdf_compress_' . session()->getId(); Cache::put($processKey, $process->getPid(), 3600);
2. 前端轮询+进程终止功能
- 状态查询接口:创建接口供前端轮询压缩状态:
public function checkStatus() { $processKey = 'pdf_compress_' . session()->getId(); $pid = Cache::get($processKey); if (!$pid) { return response()->json(['status' => 'finished']); } // 检查进程是否存活 $isRunning = Process::isRunning($pid); if (!$isRunning) { Cache::forget($processKey); return response()->json(['status' => 'finished']); } return response()->json(['status' => 'running']); }
- 终止进程接口:用户刷新或主动终止时,调用接口结束进程:
public function terminateProcess() { $processKey = 'pdf_compress_' . session()->getId(); $pid = Cache::get($processKey); if ($pid && Process::isRunning($pid)) { // 根据系统类型执行终止命令 if (PHP_OS_FAMILY === 'Windows') { Process::fromShellCommandline("taskkill /F /PID {$pid}")->run(); } else { Process::fromShellCommandline("kill {$pid}")->run(); } Cache::forget($processKey); return response()->json(['success' => true]); } return response()->json(['success' => false]); }
- 前端逻辑:页面加载后轮询状态,刷新前触发终止接口:
// 轮询压缩状态 let pollInterval = setInterval(async () => { const res = await fetch('/pdf-compress/check-status'); const data = await res.json(); if (data.status === 'finished') { clearInterval(pollInterval); // 处理压缩完成后的逻辑,比如提示下载 } }, 1000); // 页面刷新/关闭时终止进程 window.addEventListener('beforeunload', async () => { await fetch('/pdf-compress/terminate', { method: 'POST' }); clearInterval(pollInterval); });
3. 更稳健的长耗时处理:队列异步执行
若PDF压缩耗时较长,建议用Laravel队列彻底解耦HTTP请求与压缩进程:
- 创建压缩任务:
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 Symfony\Component\Process\Process; class CompressPdf implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $inputPath; protected $outputPath; public function __construct(string $inputPath, string $outputPath) { $this->inputPath = $inputPath; $this->outputPath = $outputPath; } public function handle() { $cmd = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -sOutputFile={$this->outputPath} {$this->inputPath}"; $process = Process::fromShellCommandline($cmd); $process->setTimeout(3600); $process->run(); } }
- 触发队列任务:
use App\Jobs\CompressPdf; // 触发任务后立即返回响应,压缩在后台执行 CompressPdf::dispatch($input, $output); return response()->json(['status' => 'started']);
队列方案下,用户刷新页面不影响后台进程;若需终止,可在任务中记录PID到数据库/缓存,通过接口匹配任务ID终止对应进程。
三、注意事项
- 确保服务器具备Ghostscript执行权限,且
$input、$output路径有读写权限。 - 缓存存储PID时,用用户会话或唯一请求ID关联,避免不同用户进程冲突。
- 队列处理需配置好Laravel队列驱动(如Redis、Database),并启动队列worker。
内容的提问来源于stack exchange,提问作者Toni Joe
相关产品推荐
相关产品推荐

