Ubuntu环境下Swift/Vapor生成PDF并返回前端的最优方案咨询
我之前在Vapor项目里折腾过Ubuntu环境下的PDF生成需求,结合不同场景给你推荐几个靠谱的方案,你可以根据自己的需求选:
方案1:原生Swift库直接生成PDF(适合简单场景)
如果你的PDF内容结构不复杂(比如简单文本、基础表格),用纯Swift库是最省心的,不需要依赖外部工具,完全集成在Vapor项目里。推荐用SwiftPdf,它是跨平台的,完美支持Linux。
步骤:
- 先在
Package.swift里添加依赖:
dependencies: [ .package(url: "https://github.com/OpenSwiftUI/SwiftPdf.git", from: "0.1.0"), // 别忘了你的Vapor依赖 ], targets: [ .executableTarget( name: "App", dependencies: [ .product(name: "Vapor", package: "vapor"), .product(name: "SwiftPdf", package: "SwiftPdf") ] ), // 其他target... ]
- 在路由里实现PDF生成和返回:
import Vapor import SwiftPdf func routes(_ app: Application) throws { app.get("generate-simple-pdf") { req async throws -> Response in // 构建PDF内容 let document = PDFDocument { PDFPage { PDFText("Vapor PDF 生成示例", font: .boldSystemFont(ofSize: 24)) .frame(maxWidth: .infinity, alignment: .center) .padding(.bottom, 20) PDFTable(columns: 3) { PDFTableRow { PDFTableCell(PDFText("ID")) PDFTableCell(PDFText("商品名称")) PDFTableCell(PDFText("价格")) } PDFTableRow { PDFTableCell(PDFText("1")) PDFTableCell(PDFText("笔记本电脑")) PDFTableCell(PDFText("¥5999")) } PDFTableRow { PDFTableCell(PDFText("2")) PDFTableCell(PDFText("无线鼠标")) PDFTableCell(PDFText("¥99")) } } .border(width: 1) } } // 渲染PDF为Data let pdfData = try document.render() // 构造响应,设置正确的Content-Type和下载文件名 let response = Response() response.headers.contentType = .pdf response.headers.add(name: .contentDisposition, value: "attachment; filename=\"simple-report.pdf\"") response.body = .init(data: pdfData) return response } }
优点:纯Swift代码,无外部依赖,部署简单;缺点:复杂布局(比如多列、自定义样式)的灵活性不如HTML。
方案2:HTML转PDF(适合复杂布局,首推)
如果需要生成带复杂样式、多元素的PDF(比如报表、合同),HTML/CSS的布局能力比原生PDF库强太多。这里推荐用wkhtmltopdf这个命令行工具,Ubuntu上可以直接安装,然后在Vapor里调用外部进程生成PDF。
步骤:
- 在Ubuntu服务器上安装
wkhtmltopdf:
sudo apt update sudo apt install wkhtmltopdf
- 在Vapor中实现HTML渲染和PDF生成:
你可以直接拼接HTML字符串,或者用Vapor的Leaf模板引擎渲染更复杂的页面(推荐用Leaf,维护起来更方便)。
import Vapor func routes(_ app: Application) throws { app.get("generate-complex-pdf") { req async throws -> Response in // 用Leaf渲染HTML(假设你已经配置了Leaf) let context = [ "title": "复杂报表", "items": [ ["id": "1", "name": "台式机", "price": "¥8999"], ["id": "2", "name": "显示器", "price": "¥1299"] ] ] let htmlContent = try req.view.render("report", context).renderAsString() // 创建临时文件存HTML let tempHtmlPath = req.application.tempDirectory.appending(path: "temp_\(UUID().uuidString).html") try htmlContent.write(to: tempHtmlPath, atomically: true, encoding: .utf8) // 调用wkhtmltopdf生成PDF let tempPdfPath = req.application.tempDirectory.appending(path: "temp_\(UUID().uuidString).pdf") let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/wkhtmltopdf") process.arguments = [tempHtmlPath.path, tempPdfPath.path] try process.run() process.waitUntilExit() // 检查生成是否成功 guard process.terminationStatus == 0 else { throw Abort(.internalServerError, reason: "PDF生成失败") } // 读取PDF数据 let pdfData = try Data(contentsOf: tempPdfPath) // 清理临时文件(可选,也可以让系统自动清理) try? FileManager.default.removeItem(at: tempHtmlPath) try? FileManager.default.removeItem(at: tempPdfPath) // 返回响应 let response = Response() response.headers.contentType = .pdf response.headers.add(name: .contentDisposition, value: "attachment; filename=\"complex-report.pdf\"") response.body = .init(data: pdfData) return response } }
优点:布局灵活,熟悉HTML/CSS就能快速实现复杂PDF;缺点:依赖外部命令行工具,需要确保服务器上安装了wkhtmltopdf。
方案3:Gotenberg服务(适合高并发/多场景需求)
如果你的服务有高并发需求,或者需要支持多种PDF生成方式(比如Office转PDF、URL转PDF),可以用Gotenberg这个专门的PDF生成服务。它基于Chromium,用Docker部署很方便,Vapor只需要调用它的API即可。
步骤:
- 用Docker部署Gotenberg:
docker run --rm -p 3000:3000 gotenberg/gotenberg:7
(生产环境可以配置持久化、负载均衡等)
- 在Vapor中调用Gotenberg的API:
import Vapor func routes(_ app: Application) throws { app.get("generate-pdf-gotenberg") { req async throws -> Response in // 生成HTML内容(或用Leaf渲染) let htmlContent = """ <!DOCTYPE html> <html> <head> <style> body { font-family: "Microsoft YaHei", sans-serif; } .header { text-align: center; padding: 20px; border-bottom: 1px solid #ddd; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th { background-color: #f5f5f5; } th, td { border: 1px solid #ddd; padding: 10px; } </style> </head> <body> <div class="header"> <h1>Gotenberg生成的PDF</h1> </div> <table> <tr><th>ID</th><th>产品</th><th>价格</th></tr> <tr><td>1</td><td>耳机</td><td>¥399</td></tr> <tr><td>2</td><td>键盘</td><td>¥299</td></tr> </table> </body> </html> """ // 构造表单数据发送给Gotenberg let formPart = FormDataPart( name: "file", filename: "index.html", contentType: .html, data: Data(htmlContent.utf8) ) let formData = try FormDataEncoder().encode([formPart]) // 调用Gotenberg API let gotenbergResponse = try await req.client.post(URI(string: "http://localhost:3000/convert/html")) { request in request.headers.contentType = .multipartFormData request.body = .init(data: formData) } guard gotenbergResponse.status == .ok else { throw Abort(.internalServerError, reason: "Gotenberg生成PDF失败") } // 返回PDF给客户端 let response = Response() response.headers.contentType = .pdf response.headers.add(name: .contentDisposition, value: "attachment; filename=\"gotenberg-report.pdf\"") response.body = gotenbergResponse.body return response } }
优点:解耦PDF生成逻辑,支持高并发和多种生成方式,可横向扩展;缺点:需要额外部署和维护Gotenberg服务。
内容的提问来源于stack exchange,提问作者Philip Pegden
相关产品推荐
相关产品推荐

