我在我的网站上使用 Coldfusion 10 和 CFWheels。基本上我的网站有一堆不同类型的表单,它们有自己的控制器和视图。对于每个表单,用户可以选择动态生成表单的 PDF 并下载。它基本上加载控制器数据,但是当它使用“pdf”参数访问视图时,它会执行以下操作,生成 PDF 并在浏览器中打开文档:
<cfdocument format="PDF" saveAsName="#formtype#_#id#.pdf">
#includePartial("/printView")#
</cfdocument>
这些 PDF 中的每一个都可以有多个页面,具体取决于添加了多少行项目。就像我在开始时所说的那样,表单有多种类型,因此它们将拥有自己的控制器和视图,并使用其打印视图生成 PDF。这些表单都是自定义的,并与诸如 shippingID 之类的 ID 相关联。所以我可以有一份包含 2 种 A 型表格和 1 种 B 型表格和 3 种 C 型表格的货件,等等。我需要做的是生成 1 个 PDF,所有表格根据货件合并在一起。因此,以我的示例为例,货件的合并 PDF 将包含 2 种 A 型表格、1 种 B 型表格和 3 种 C 型表格,全部合并。
目前我正在做的是对每个动态生成的 PDF 页面进行 http“GET”调用,将其保存到临时目录,然后在最后合并它们。
我加载货件,对于每种不同类型的表单,我执行以下操作,其中 urlPath 是生成动态 PDF 的视图的路径:
var httpService = new http();
httpService.setMethod("GET");
httpService.setUrl(urlPath);
invoice = httpService.send().getPrefix().filecontent.toByteArray();
var fullPath = "#filePath##arguments.type#_#id#.pdf";
//write files in temp directory
FileWrite(fullPath, invoice);
获取 PDF 并将其写入文件后,我将路径保存在数组中以供参考,以便循环遍历并合并数组中的所有引用文件,然后删除保存文件的临时目录。
我这样做的原因是因为控制器和视图已经设置好并即时生成单独的 PDF。如果我尝试加载(所有关联的表单)并将所有内容放在一个文件中,我将不得不添加所有相同的控制器逻辑来加载每个表单特定的内容和关联的视图,但这些对于单个页面视图已经存在。
有一个更好的方法吗?如果只有几个 PDF,它可以正常工作,但如果发货中有很多不同的表格,比如 20 个,那么它非常慢,因为我们没有 CF Enterprise,我相信 cfdocument 是单线程的。表单必须动态生成,以便它们包含最新的数据。
克里斯的更新
我添加了一些代码来显示各种形式的外观。我验证并加载了一堆其他东西,但我将其剥离以了解总体思路:
控制器/Invoices.cfc
路径可能类似于:/shipments/[shipmentkey]/invoices/[key]
public void function show(){
// load shipment to display header details on form
shipment = model("Shipment").findOne(where="id = #params.shipmentkey#");
// load invoice details to display on form
invoice = model("Invoice").findOne(where="id = #params.key#");
// load associated invoice line items to display on form
invoiceLines = model("InvoiceLine").findAll(where="invoiceId = #params.key#");
// load associated containers to display on form
containers = model("Container").findAll(where="invoiceid = #params.key#");
// load associated snumbers to display on form
scnumbers = model("Scnumber").findAll(where="invoiceid = #params.key#");
}
控制器/Permits.cfc
路径可能类似于:/shipments/[shipmentkey]/permits/[key]
public void function show(){
// load shipment to display header details on form
shipment = model("Shipment").findOne(where="id = #params.shipmentkey#");
// load permit details to display on form
permit = model("Permit").findOne(where="id = #params.key#");
// load associated permit line items to display on form
permitLines = model("PermitLine").findAll(where="permitId = #params.key#");
}
控制器/Nafta.cfc
路径可能类似于:/shipments/[shipmentkey]/naftas/[key]
public void function show(){
// load shipment to display header details on form
shipment = model("Shipment").findOne(where="id = #params.shipmentkey#");
// load NAFTA details to display on form
nafta = model("NAFTA").findOne(where="id = #params.key#");
// load associated NAFTA line items to display on form
naftaLines = model("NaftaLine").findAll(where="naftaId = #params.key#");
}
目前,我的视图基于一个名为“view”的 URL 参数,其中的值可以是“print”或“pdf”。
print - 显示打印视图,它几乎是表单的精简版本,没有网页页眉/页脚等。
pdf - 调用我粘贴在问题顶部的 cfdocument 代码,该代码使用 printView 生成 PDF。
我认为我不需要发布“show.cfm”代码,因为它只是一堆 div 和表格,显示每个特定表单的特定信息。
请记住,这些只是 3 种示例表单类型,并且有 10 多种类型可能与 1 个货件相关联,并且 PDF 需要合并。每种类型也可能在一次装运中重复多次。例如,一个货物可能包含 10 张不同的发票,其中包含 5 个许可证和 3 个北美自由贸易协定。
为了让事情稍微复杂一点,一个货件可以有两种类型:美国发往或加拿大发运,并且基于这种不同的表单类型可以与该货件相关联。因此,加拿大发票的字段与美国发票的字段完全不同,因此模型/表格不同。
目前要进行合并,我有一个控制器,它执行以下操作(请注意,我剥离了很多验证,加载其他对象以简化)
public any function displayAllShipmentPdf(shipmentId){
// variable to hold the list of full paths of individual form PDFs
formList = "";
shipment = model("shipment").findOne(where="id = #arguments.shipmentId#");
// path to temporarily store individual form PDFs for later merging
filePath = "#getTempDirectory()##shipment.clientId#/";
if(shipment.bound eq 'CA'){
// load all invoices associated to shipment
invoices = model("Invoice").findAll(where="shipmentId = #shipment.id#");
// go through all associated invoices
for(invoice in invoices){
httpService = new http();
httpService.setMethod("get");
// the following URL loads the invoice details in the Invoice controller and since I'm passing in "view=pdf" the view will display the PDF inline in the browser.
httpService.setUrl("http://mysite/shipments/#shipment.id#/invoices/#invoice.id#?view=pdf");
invoicePdf = httpService.send().getPrefix().fileContent.toByteArray();
fullPath = "#filePath#invoice_#invoice.id#.pdf";
// write the file so we can merge later
FileWrite(fullPath, invoicePdf);
// append the fullPath to the formList as reference for later merging
formList = ListAppend(formList, fullPath);
}
// the above code would be similarly repeated for every other form type (ex. Permits, NAFTA, etc.). So it would call the path with the "view=pdf" which will load the specific form Controller and display the PDF inline which we capture and create a temporary PDF file and add the path to the formList for later merging. You can see how this can be a long process as you have several types of forms associated to a shipment and there can be numerous forms of each type in the shipment and I don't want to have to repeat each form Controller data loading logic.
}else if(shipment.bound eq 'US'){
// does similar stuff to the CA except with different forms
}
// merge the PDFs in the formList
pdfService = new pdf();
// formList contains all the paths to the different form PDFs to be merged
pdfService.setSource(formList);
pdfService.merge(destination="#filePath#shipment_#shipment.id#.pdf");
// read the merged PDF
readPdfService = new pdf();
mergedPdf = readPdfService.read(source="#filePath#shipment_#shipment.id#.pdf");
// delete the temporarily created PDF files and directory
DirectoryDelete(filePath, "true");
// convert to binary to display inline in browser
shipmentPdf = toBinary(mergedPdf);
// set the response to display the merged PDF
response = getPageContext().getFusionContext().getResponse();
response.setContentType('application/pdf');
response.setHeader("Content-Disposition","filename=shipment_#shipment.id#_#dateFormat(now(),'yyyymmdd')#T#timeFormat(now(),'hhmmss')#.pdf");
response.getOutputStream().writeThrough(shipmentPdf);
}