ColdFusion/CFWheels объединяет несколько PDF-файлов в разных контроллерах

Я использую Coldfusion 10 и CFWheels для своего сайта. В основном на моем сайте есть куча разных типов форм со своими собственными контроллерами и представлениями. Для каждой формы у пользователя есть возможность динамически создать PDF-файл формы и загрузить его. В основном он загружает данные контроллера, но когда он попадает в представление с параметром «pdf», он делает следующее, что создает PDF и открывает документ в браузере:

<cfdocument format="PDF" saveAsName="#formtype#_#id#.pdf">
  #includePartial("/printView")#
</cfdocument>

Каждый из этих PDF-файлов может иметь несколько страниц в зависимости от того, сколько позиций добавлено. Как я сказал в начале, существует несколько типов форм, поэтому у них будет свой собственный контроллер и представления, а также генерация PDF с их представлениями для печати. Все эти формы настроены и связаны вместе с идентификатором, например, shippingID. Таким образом, у меня может быть одна поставка, содержащая 2 формы типа A и 1 форму типа B, 3 формы типа C и т. д. Что мне нужно сделать, так это создать 1 PDF-файл со всеми формами, объединенными вместе, на основе поставки. Итак, в моем примере объединенный PDF-файл для отправки будет содержать 2 формы типа A, 1 форму типа B и 3 формы типа C, все они объединены.

В настоящее время я делаю HTTP-вызов «GET» для каждой из динамически сгенерированных страниц PDF, сохраняю их во временном каталоге, а затем объединяю их в конце.

Я загружаю посылку и для каждого типа формы делаю следующее, где 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 является однопоточным. Формы должны генерироваться динамически, чтобы они содержали самые последние данные.

ОБНОВЛЕНИЕ для Криса

Я добавил немного кода, чтобы показать, как могут выглядеть различные формы. Я проверяю и загружаю кучу других вещей, но я урезал их, чтобы получить общее представление:

controllers/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#");
}

controllers/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#");
}

В настоящее время мое представление основано на параметре URL, называемом «просмотр», где значения могут быть «печать» или «pdf».

print — отображает представление для печати, которое представляет собой урезанную версию формы без верхних/нижних колонтитулов веб-страницы и т. д.

pdf - вызывает код cfdocument, который я вставил в начало вопроса, который использует printView для создания PDF.

Я не думаю, что мне нужно публиковать код «show.cfm», так как это будет просто набор разделов и таблиц, отображающих конкретную информацию для каждой конкретной рассматриваемой формы.

Имейте в виду, что это только 3 примера типов форм, и существует более 10 типов, которые могут быть связаны с 1 отправкой, и PDF-файлы необходимо будет объединить. Каждый тип может повторяться несколько раз в пределах одной отправки. Например, отгрузка может содержать 10 различных счетов-фактур с 5 разрешениями и 3 НАФТА.

Чтобы немного усложнить ситуацию, посылка может иметь 2 типа: для отправки в США или для отправки в Канаду, и в зависимости от этого к отправке могут быть привязаны различные типы форм. Таким образом, в счете-фактуре для Канады будут совершенно другие поля, чем в счете-фактуре для США, поэтому модели/таблицы будут другими.

В настоящее время для слияния у меня есть контроллер, который делает что-то вроде следующего (обратите внимание, что я удалил много проверок, загрузив другие объекты для упрощения)

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);
}

person yaki33    schedule 02.09.2015    source источник
comment
Можете ли вы обновить свой вопрос, чтобы он содержал соответствующий код, скажем, для двух форм, которые необходимо объединить в одну? У меня есть несколько идей о том, как я могу помочь, но я предпочитаю говорить конкретно, а не в общих чертах.   -  person Chris Peters    schedule 03.09.2015
comment
P. S. Если вы используете provides и настраиваете свои маршруты для приема .[format], вам не нужно писать собственную логику, чтобы решить, отображать ли PDF в представлении. Посмотрите этот скринкаст: vimeo.com/channels/cfwheels/17933706   -  person Chris Peters    schedule 03.09.2015
comment
Я обновил вопрос, чтобы включить некоторый код. Надеюсь, это поможет найти лучшее решение. Спасибо!   -  person yaki33    schedule 03.09.2015
comment
Привет, Крис, просто интересно, была ли у тебя возможность взглянуть на мой обновленный код. Для меня это становится все более серьезной проблемой, потому что сервер начинает зависать, если в посылке несколько PDF-файлов. Спасибо   -  person yaki33    schedule 21.09.2015


Ответы (1)


См.: https://forums.adobe.com/thread/1121909 ... ". .. Standard Edition Adobe ограничивает функции PDF в один поток, ... Developer работает как Enterprise, поэтому ваша среда разработки будет выхватывать PDF-файлы, но ваш рабочий сервер CF Standard будет задыхаться.

Кроме того, похоже, у вас нет проблем с одним или двумя PDF-файлами. У меня есть CF Enterprise, и он отлично генерировал pdf-файлы — несколько секунд, а затем из ниоткуда pdf-файлы начали занимать 4 минуты. В другом комментарии в упомянутом выше посте Adobe предлагается проверить /etc/hosts, с которым CF связывается сам (?????). Немного покопавшись, я обнаружил, что Windows\system32\drivers\etc\hosts были обновлены за день до того, как пользователи обнаружили, что время ожидания pdf-файлов истекло. IP-адрес был изменен на другой IP-адрес интрасети, а имя сервера было именем DNS-сервера. Я изменил значение обратно на 127.0.0.1 localhost и вуаля, pdf-файлы начали отображаться в обычное время.

person gordon    schedule 10.01.2017