| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- <?php
- namespace App\Services;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Process;
- use Illuminate\Support\Str;
- /**
- * PDF合并工具类
- * 支持pdfunite(生产环境)和qpdf(本地开发)
- */
- class PdfMerger
- {
- private string $mergeTool;
- private bool $isProduction;
- public function __construct()
- {
- $this->isProduction = app()->environment('production');
- $this->mergeTool = $this->detectMergeTool();
- }
- /**
- * 检测系统中可用的PDF合并工具
- */
- private function detectMergeTool(): string
- {
- // 生产环境优先使用pdfunite
- if ($this->isProduction) {
- if ($this->commandExists('pdfunite')) {
- return 'pdfunite';
- }
- }
- // 本地开发环境使用qpdf
- if ($this->commandExists('qpdf')) {
- return 'qpdf';
- }
- // 备选:尝试pdfunite
- if ($this->commandExists('pdfunite')) {
- return 'pdfunite';
- }
- throw new \Exception('未找到可用的PDF合并工具(pdfunite或qpdf)');
- }
- /**
- * 检查命令是否存在
- */
- private function commandExists(string $command): bool
- {
- // 【修复】异步环境中PATH可能不完整,尝试绝对路径
- $fullPaths = [
- "/opt/homebrew/bin/{$command}",
- "/usr/bin/{$command}",
- "/usr/local/bin/{$command}",
- ];
- Log::debug("检查命令是否存在: {$command}", [
- 'checking_absolute_paths' => $fullPaths
- ]);
- // 首先尝试绝对路径
- foreach ($fullPaths as $path) {
- if (file_exists($path) && is_executable($path)) {
- Log::debug("找到命令(绝对路径): {$command} -> {$path}");
- return true;
- }
- }
- // 如果绝对路径都不行,再尝试which命令
- Log::debug("绝对路径未找到,尝试which命令: {$command}");
- $output = Process::run("which {$command}");
- $result = $output->successful();
- Log::debug("which命令结果", [
- 'command' => $command,
- 'successful' => $result,
- 'output' => $output->output(),
- 'error' => $output->errorOutput()
- ]);
- return $result;
- }
- /**
- * 合并多个PDF文件
- *
- * @param array $pdfPaths PDF文件路径数组
- * @param string $outputPath 输出文件路径
- * @return bool 合并是否成功
- * @throws \Exception
- */
- public function merge(array $pdfPaths, string $outputPath): bool
- {
- // 验证输入文件
- foreach ($pdfPaths as $path) {
- if (!file_exists($path)) {
- throw new \Exception("PDF文件不存在: {$path}");
- }
- }
- // 确保输出目录存在
- $outputDir = dirname($outputPath);
- if (!is_dir($outputDir)) {
- mkdir($outputDir, 0755, true);
- }
- Log::info('开始合并PDF', [
- 'tool' => $this->mergeTool,
- 'input_count' => count($pdfPaths),
- 'output_path' => $outputPath
- ]);
- try {
- switch ($this->mergeTool) {
- case 'pdfunite':
- return $this->mergeWithPdfunite($pdfPaths, $outputPath);
- case 'qpdf':
- return $this->mergeWithQpdf($pdfPaths, $outputPath);
- default:
- throw new \Exception("不支持的合并工具: {$this->mergeTool}");
- }
- } catch (\Exception $e) {
- Log::error('PDF合并失败', [
- 'tool' => $this->mergeTool,
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString()
- ]);
- throw $e;
- }
- }
- /**
- * 使用pdfunite合并PDF
- * pdfunite file1.pdf file2.pdf ... output.pdf
- */
- private function mergeWithPdfunite(array $pdfPaths, string $outputPath): bool
- {
- $command = 'pdfunite ' . implode(' ', array_map('escapeshellarg', $pdfPaths)) . ' ' . escapeshellarg($outputPath);
- Log::debug('执行pdfunite命令', ['command' => $command]);
- $output = Process::run($command);
- if ($output->successful()) {
- Log::info('pdfunite合并成功', ['output_path' => $outputPath]);
- return true;
- }
- Log::error('pdfunite合并失败', [
- 'exit_code' => $output->exitCode(),
- 'output' => $output->output(),
- 'error' => $output->errorOutput()
- ]);
- return false;
- }
- /**
- * 使用qpdf合并PDF
- * qpdf --empty --pages file1.pdf file2.pdf -- -- output.pdf
- */
- private function mergeWithQpdf(array $pdfPaths, string $outputPath): bool
- {
- // 构建qpdf命令
- $pagesArg = implode(' ', array_map('escapeshellarg', $pdfPaths));
- $command = "qpdf --empty --pages {$pagesArg} -- -- " . escapeshellarg($outputPath);
- Log::debug('执行qpdf命令', ['command' => $command]);
- $output = Process::run($command);
- if ($output->successful()) {
- Log::info('qpdf合并成功', ['output_path' => $outputPath]);
- return true;
- }
- Log::error('qpdf合并失败', [
- 'exit_code' => $output->exitCode(),
- 'output' => $output->output(),
- 'error' => $output->errorOutput()
- ]);
- return false;
- }
- /**
- * 获取当前使用的合并工具
- */
- public function getMergeTool(): string
- {
- return $this->mergeTool;
- }
- /**
- * 检查环境是否支持PDF合并
- */
- public function isSupported(): bool
- {
- return in_array($this->mergeTool, ['pdfunite', 'qpdf']);
- }
- }
|