当前位置: 首页 > 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.lryc.cn/news/583417.html

相关文章:

  • Java零基础笔记08(Java编程核心:面向对象编程高级 {继承、多态})
  • uniapp小程序无感刷新token
  • Docker 高级管理--容器通信技术与数据持久化
  • [论文阅读] 软件工程 | 一篇关于开源许可证管理的深度综述
  • 图像处理中的模板匹配:原理与实现
  • LabVIEW前面板颜色修改
  • 利用编码ai工具cursor写单元测试
  • MCP快速入门—快速构建自己的服务器
  • 如何使用Pytest进行测试?
  • Android-重学kotlin(协程基础)新学习总结
  • MATLAB基于voronoi生成三维圆柱形
  • 2025年人工智能、虚拟现实与交互设计国际学术会议
  • Matlab-Simulink之步长
  • PlantUML 在 IDEA 中文版中的安装与使用指南
  • VR重现红军过雪山:一场穿越时空的精神洗礼​
  • VR/AR在HMI中的创新应用:远程协作与维修的沉浸式体验
  • 【图像处理基石】图像超分辨率有哪些研究进展值得关注?
  • 【SQL】使用UPDATE修改表字段的时候,遇到1054 或者1064的问题怎么办?
  • git上传大文件启用LFS git报错 the remote end hung up unexpectedly
  • ReactNative【实战系列教程】我的小红书 6 -- 购物(含商品搜索、商品分类、商品列表)
  • 【RidgeUI AI+系列】猜密码游戏
  • 2025快手创作者中心发布视频python实现
  • Python 项目快速部署到 Linux 服务器基础教程
  • Android 系统默认代码,如何屏蔽相册分享功能
  • cJSON数组操作函数使用指南
  • AJAX 学习
  • Go语言高级面试必考:切片(slice)你真的掌握了吗?
  • 11.7 ChatGPT奖励模型完全解读:RLHF核心技术深度剖析与Hugging Face实战
  • 从传统到智能:地质灾害风险评估、易发性分析与灾后重建;AI大语言模型DeepSeek、ChatGPT、GIS、Python和机器学习深度融合
  • 李宏毅NLP-9-语音转换