当前位置: 首页 > news >正文

Laravel 动态生成 PDF:基于 KnpSnappy 实现多公司页眉页脚差异化配置

引言:为什么需要个性化 PDF 方案?

在多公司业务场景中,生成的 PDF 文件(如对账单、律师函)往往需要根据公司类型展示不同的页眉(如企业 Logo)和页脚(如联系方式)。本文基于barryvdh/laravel-snappy(封装自 KnpSnappy)和wkhtmltopdf工具,实现「按公司动态配置页眉页脚」的 PDF 生成方案,适用于需要差异化文档样式的业务系统。

一、技术栈与核心依赖

框架:Laravel(任意 5.5 + 版本均可)
PDF 生成工具:wkhtmltopdf(底层渲染引擎)
Laravel 扩展:barryvdh/laravel-snappy(封装 KnpSnappy,简化 PDF 操作)
核心能力:通过 HTML 模板定义页眉页脚,根据公司类型动态切换。

二、安装与配置

1、安装包

composer require barryvdh/laravel-snappy

2、发布配置

php artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"

3、配置 wkhtmltopdf 路径

在 config/snappy.php 中设置 pdf.wrapper 的路径:

'pdf' => ['enabled' => true,'binary'  => '/usr/local/bin/wkhtmltopdf', // 根据实际路径修改'timeout' => false,'options' => [],'env'     => [],
],

三、创建 PDF 服务类

为避免控制器逻辑臃肿,将 PDF 生成逻辑封装为PdfService,核心功能包括:模板匹配、路径生成、动态页眉页脚配置。

<?php
namespace App\Services;use Barryvdh\Snappy\PdfWrapper;
use Illuminate\Support\Facades\Storage;
use Exception;class PdfService
{private $pdf;private $config;public function __construct(PdfWrapper $pdf){$this->pdf = $pdf;$this->config = config('pdf');}public function generatePdf($data, $type){try {// 获取模板配置$templateConfig = $this->getTemplateConfig($type);// 生成文件路径$filePath = $this->generateFilePath($templateConfig['filename_en']);// 生成PDF$this->pdf->loadView($templateConfig['view'], ['data' => $data])->setPaper('a4')->setOrientation('portrait')->setOption('encoding', 'UTF-8')->setOption('header-html', view('qa_header.' . $data['company_type'] . '_header')->render())->setOption('footer-html', view('qa_footer.' . $data['company_type'] . '_footer')->render())->setOptions($this->getCommonOptions());$this->pdf->save(storage_path("app/public/{$filePath}"));return asset("storage/{$filePath}");} catch (Exception $e) {\Log::error('PDF生成失败: ' . $e->getMessage(), ['data' => $data,'type' => $type]);throw new Exception('PDF生成失败,请稍后重试');}}private function getTemplateConfig($type){$templates = $this->config['templates'];if (!isset($templates[$type])) {throw new Exception('无效的PDF类型');}return $templates[$type];}private function generateFilePath($filename){$datePath = 'uploads/coa/' . date("Ymd") . "/";$fullPath = "public/{$datePath}";if (!Storage::exists($fullPath)) {Storage::makeDirectory($fullPath);}return $datePath . "{$filename}_{$this->getTimestamp()}.pdf";}private function getTimestamp(){return time() . '_' . date("Hms");}private function getCommonOptions(){return ['header-spacing' => 10,'footer-spacing' => 10,'margin-top' => 30,'margin-bottom' => 40,'enable-local-file-access' => true,'disable-smart-shrinking' => true,'page-offset' => 0,'header-line' => true,'footer-line' => true];}
}

四、模板配置与视图设计

1. 模板配置文件(config/pdf.php)

定义 PDF 类型与模板的映射关系,便于统一管理:

<?php
return ['templates' => [1 => ['view' => 'qa.billing','filename' => '对账单','filename_en' => 'billing'],2 => ['view' => 'qa.lawyer','filename' => '律师函','filename_en' => 'lawyer'],3 => ['view' => 'qa.repayment','filename' => '分期还款协议','filename_en' => 'repayment'],4 => ['view' => 'qa.demand_letter','filename' => '催款函','filename_en' => 'demand_letter'],]
];

2. 页脚视图(按公司类型区分)

示例:resources/views/qa_footer/company_a_footer.blade.php(A 公司页脚)

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body {font-family: Arial, sans-serif;font-size: 12px;}</style>
</head>
<body>
<table cellspacing="0" border="0" cellpadding="0"><tr><td style="height: 20px;line-height: 20px"><b class="footer_en">company name.</b></td></tr><tr><td style="height: 20px;line-height: 20px"><b class="footer_en">Tel:</b><span class="footer_en">+1 909 ;</span><b class="footer_en">Email:</b><span class="footer_en">sales@.com</span></td></tr><tr><td style="height: 20px;line-height: 20px"><b class="footer_en">Website:</b><span class="footer_en">www.com</span></td></tr>
</table>
</body>
</html>

3. 页眉视图(按公司类型区分)

示例:resources/views/qa_header/company_a_header.blade.php(A 公司页眉)

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body {font-family: Arial, sans-serif;font-size: 12px;}</style>
</head>
<body>
<div style="height: 50px"><img src="{{ asset('assets/img/logos/company.png') }}" alt=""  style="height: 50px" /></div></body>
</html>

五、控制器调用示例

控制器仅负责接收参数、准备数据,具体生成逻辑委托给PdfService:

<?php
namespace App\Http\Controllers;use App\Services\PdfService;
use Illuminate\Http\Request;
use Exception;class PdfController extends Controller
{private $pdfService;// 依赖注入PdfService(Laravel自动解析)public function __construct(PdfService $pdfService){$this->pdfService = $pdfService;}// 生成PDF接口public function generate(Request $request){// 1. 验证参数$request->validate(['type' => 'required|integer|in:1,2,3,4', // 仅允许配置的类型'corp_id' => 'required|integer',          // 公司ID(用于获取company_type)]);// 2. 准备业务数据(示例)$data = ['company_type' => $this->getCompanyType($request->corp_id), // 获取公司类型(如company_a)'order_no' => 'ORD20231001001','amount' => 10000.00,// ... 其他业务字段];// 3. 调用服务生成PDFtry {$pdfUrl = $this->pdfService->generatePdf($data, $request->type);// 4. 更新业务记录(示例:保存PDF地址到数据库)$this->updatePdfUrl($request->corp_id, $pdfUrl);return response()->json(['success' => true,'pdf_url' => $pdfUrl]);} catch (Exception $e) {return response()->json(['success' => false,'message' => $e->getMessage()], 500);}}// 从公司ID获取类型(实际业务中从数据库查询)private function getCompanyType($corpId){// 示例:corp_id=1对应company_a,=2对应company_breturn $corpId == 1 ? 'company_a' : 'company_b';}// 更新业务记录的PDF地址private function updatePdfUrl($corpId, $pdfUrl){// 实际业务中更新对应模型// 如:Order::where('corp_id', $corpId)->update(['pdf_url' => $pdfUrl]);}
}

六、常见问题与解决方案

1、中文乱码

确保wkhtmltopdf安装了中文字体(如SimHei、WenQuanYi Micro Hei)。
视图中指定字体:font-family: SimHei, Arial, sans-serif;。

2、图片无法加载

启用enable-local-file-access选项(已在getCommonOptions中配置)。
使用绝对路径:{{ public_path(‘assets/img/logos/company_a.png’) }}。

3、页眉页脚位置偏移

调整margin-top和margin-bottom值,预留足够空间。
避免页眉页脚内容过长导致溢出。

总结

本文通过「服务类封装 + 配置驱动 + 动态视图」的方式,实现了多公司 PDF 的个性化生成。核心优势在于:
**可扩展性:**新增公司类型只需添加对应页眉页脚视图,无需修改核心逻辑。
**可维护性:**模板配置集中管理,样式调整仅需修改 HTML 视图。
**稳定性:**完善的错误处理和日志记录,便于排查问题。
该方案适用于 SaaS 平台、多租户系统等需要差异化文档样式的场景,可根据实际业务扩展更多功能(如水印、动态页码等)。

http://www.dtcms.com/a/270652.html

相关文章:

  • C++如何进行性能优化?
  • 安卓设备信息查看器 - 源码编译
  • PlantUML 在 IDEA 中文版中的安装与使用指南
  • Vim 编辑器常用操作详解(新手快速上手指南)
  • LKT4304稳定可靠高兼容性国产安全加密芯片
  • JAVA观察者模式demo【设计模式系列】
  • WebSocket实现多人实时在线聊天
  • 【TCP/IP】2. 计算机网络与因特网体系结构
  • 两张图片对比clip功能
  • 后端id设置long类型时,传到前端,超过19位最后两位为00
  • 解锁DevOps潜力:如何选择合适的CI/CD工作流工具
  • 【Linux | 网络】socket编程 - 使用UDP实现服务端向客户端提供简单的服务
  • python:ImportError: cannot import name ‘ParameterSource‘ from ‘click.core‘
  • Linux 中的 .bashrc 是什么?配置详解
  • ESP32的OTA升级详解:2. OTA低层组件app_update介绍
  • 增强检索知识库系统1
  • 模型内部进行特征提取时,除了“减法”之外,还有哪些技术
  • 线程池与并发工具:优化多线程执行!
  • [特殊字符]【跨数据库支持】SQL 秒转 ArkTS 实体!HarmonyOS 开发者的数据库适配神器 gotool.top
  • Node.Js是什么?
  • AI+智慧园区 | 事件处置自动化——大模型重构园区治理逻辑
  • 【图像处理基石】如何检测到画面中的ppt并对其进行增强?
  • 洛谷 P1104 生日---排序
  • Android Studio 2024,小白入门喂饭级教程
  • 滑动窗口的初步了解
  • 记录一下:成功部署k8s集群(部分)
  • 【音视频】TS协议介绍
  • 搭建商城系统
  • 【Java】【力扣】3.无重复字符的最长字串
  • Flutter基础(前端教程⑧-数据模型)