Laravel 图片转 PDF:上传成功后,怎样拿到转换结果?

做凭证或扫描件附件功能时,“图片上传成功”和“PDF 已经可以下载”是两件事。图片保存在 Laravel 的磁盘里,只说明输入已经准备好;转换接口还可能拒绝文件、返回失败,或完成任务后给出一个需要及时下载的地址。
先让一张 PNG 走完请求、检查结果、下载和重新打开,再考虑多图合并和队列。这样遇到问题时,能分清是上传、转换还是文件保存出了错。
先确认要的是什么文件
图片转 PDF 适合把已有图片交付为 PDF。它不等于 OCR,不能据此承诺图片里的编号变成可搜索、可复制的文字。多张图片一起提交,也不能仅凭“支持多文件”就认定会自动合成一个有序的 PDF。
第一轮准备一张没有个人信息的 PNG:四个角有明显标记,中间有文字和数字。它能帮助你发现裁切、旋转和清晰度变化。先保留原图,不要在上传时为了统一尺寸而拉伸。
如果业务明确要求统一 A4、固定边距或一图一页,先把这些列为验收条件。本例的图片转换接口没有列出这些版式参数,不能自行加上 pageSize、pageMode 之类字段。需要固定模板时,应另选支持对应版式控制的生成方案。
用一条 Artisan 命令验证接口
以下代码按 Laravel 12 的 HTTP Client 写法和 ComPDF 图片转 PDF 接口组织。本文已用一张无个人信息的 PNG 核对接口,并在 Laravel 项目中运行下方命令,成功取得下载地址和 PDF 文件。这只验证单张输入的调用链,自己的图片仍应按后文检查版式和清晰度。
先在 .env 中设置控制台提供的 API key,不要提交到版本库:
COMPDF_API_KEY=替换为自己的API_key
在 config/services.php 的返回数组中增加:
'compdf' => [
'key' => env('COMPDF_API_KEY'),
],
将下面的命令加入 routes/console.php。如果文件里已有 Artisan 或 Http 导入,保留一份即可。路径参数使用服务器上可读取的 PNG 文件;它不是浏览器里的图片网址。
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Http;
Artisan::command('pdf:from-png {path}', function () {
$path = realpath($this->argument('path'));
$key = config('services.compdf.key');
if (!$key || !$path || !is_readable($path)) {
$this->error('请检查 API key 和本地文件路径。');
return 1;
}
$image = @getimagesize($path);
if (!$image || $image[2] !== IMAGETYPE_PNG) {
$this->error('本例只接收可识别的 PNG 图片。');
return 1;
}
$stream = fopen($path, 'rb');
try {
$response = Http::withHeaders(['x-api-key' => $key])
->acceptJson()
->connectTimeout(10)
->timeout(120)
->attach('file', $stream, basename($path))
->post(
'https://api-server.compdf.com/server/v2/process/img/pdf',
['language' => 2]
);
} finally {
if (is_resource($stream)) {
fclose($stream);
}
}
$response->throw();
$body = $response->json();
if ((string) data_get($body, 'code') !== '200') {
$this->error('接口未返回成功状态,请检查返回信息和账号额度。');
return 1;
}
// 分别读取任务包、文件数组或单文件结果。
$data = data_get($body, 'data', []);
$files = data_get($data, 'fileInfoDTOList');
$file = is_array($files)
? ($files[0] ?? [])
: (is_array($data) && array_is_list($data) ? ($data[0] ?? []) : $data);
$url = data_get($file, 'downloadUrl');
$taskState = data_get($data, 'taskStatus');
$fileState = data_get($file, 'status');
$failed = (int) data_get($data, 'taskFailNum', 0) > 0
|| ($fileState && $fileState !== 'success')
|| (bool) data_get($file, 'failureCode');
if ($failed || ($taskState && $taskState !== 'TaskFinish') || !$url) {
$this->error('尚未取得成功结果,请按 taskId 检查任务及失败原因。');
$this->line('taskId: '.data_get($file, 'taskId', data_get($data, 'taskId', '未返回')));
return 1;
}
$this->line('taskId: '.data_get($file, 'taskId', data_get($data, 'taskId', '未返回')));
$this->line('downloadUrl: '.$url);
return 0;
});
更新配置后运行:
php artisan config:clear
php artisan pdf:from-png /绝对路径/check.png
HTTP 非成功状态会抛出异常;连接异常和超时也会使命令失败。返回业务错误、任务未结束或缺少下载地址时,本例返回退出码 1。不要把“请求发出去了”当成转换完成。 如果服务超时,不要立即无限重试;先根据已取得的任务 ID 核查,避免重复创建任务。
代码分别处理任务包、文件数组和单文件结果,但你的账号实际响应仍需核对。若返回结构不同,先查看任务 ID、状态和错误字段,再调整解析,不能仅搜索任意一个 downloadUrl 就认定成功。

检查顺序示意:分别确认请求、任务与最终文件,不能只看一个成功状态。
下载之后,再看图片有没有被正确保留
先在浏览器打开命令输出的地址并下载 PDF。图片转 PDF 文档标明结果地址有效期为 24 小时,它适合取回产物,不适合直接作为业务系统的永久附件地址。
关闭文件后重新打开,与原 PNG 对照:
- 四个角是否都在,有没有内容被裁掉;
- 文字、细线和数字在正常阅读比例下是否清楚;
- 方向是否符合使用要求;不能用拉伸来修正横竖问题;
- 页数、页尺寸和文件大小是否符合自己的交付条件;
- 文件能否正常下载、打开,而不是扩展名叫
.pdf的错误页面。
一张图通过后,再分别用横图、竖图和较大图片做同样检查。输出满足需求,才值得接着做批量;如果不满足固定纸张或文字检索要求,应该换处理路径,而不是继续给这个接口叠加未说明的参数。
接回上传和队列时,保留这三条边界
上传与转换分开记状态。 Laravel 上传时先校验格式、大小及实际像素,再保存到私有存储。队列拿服务器文件路径执行转换;不要把前端传入的任意路径直接交给上述命令。示例只做 PNG 识别,生产上传仍需按应用资源限制设置文件和像素上限。
转换结果及时保存。 应用核对任务与输出后,把 PDF 下载到自己的存储,保存来源附件 ID、版本、任务 ID 和输出路径。下载与文件校验完成后再标记可下载,并由应用做权限检查。批量重试需要识别同一输入版本,防止同一附件产生多份无主文件。
多文件处理与合并分开。 一张图片对应一个结果时,按附件 ID 记录结果,不靠任务完成先后排序。如果确实要把多张凭证装订成一份 PDF,先定义材料顺序、检查每个转换结果,再按 PDF 合并接口的输入约定组织合并;转换接口本身不能替你决定业务页序。
先确认单张 PNG 能得到合格的 PDF,再接自己的附件、存储和权限,是这条链路里最有用的检查顺序。ComPDF 完成图片到 PDF 的转换,Laravel 负责让这个结果成为可追踪、可授权下载的业务附件;两部分分别验收,后续排障才有明确的位置。
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
推荐文章: