批量打印的難點:html
一、文本顯示的樣式;java
二、不一樣瀏覽器打印的兼容性linux
三、帳單個數上萬時,服務端的性能(起碼要保證不能把內存消耗完,致使內存溢出)。數據庫
對應的處理方案:apache
一、打印樣式經過html顯示;瀏覽器
二、採用pd4ml將html轉化爲pdf,處理不一樣瀏覽器打印兼容的問題;緩存
三、每次處理的帳單個數,根據服務器當前的內存使用狀況,計算其合適的批量處理大小;服務器
四、利用pdfbox,合併html轉化後的pdf。app
流程:dom
一、從數據庫查出數據,並組裝成單個帳單;每30個帳單寫入一次pdf;
二、將全部的小的pdf合成一個單獨的pdf;
三、將單獨的pdf在網頁上顯示
效果圖:
代碼
(一)批量打印工具類
package com.hyn.util; import java.awt.Insets; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.StringReader; import java.net.URLEncoder; import java.security.InvalidParameterException; import java.util.List; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.multipdf.PDFMergerUtility; import org.zefer.pd4ml.PD4Constants; import org.zefer.pd4ml.PD4ML; /** * html轉pdf工具類 * * @author liyulin * @version 1.0 2016年12月16日 下午2:15:29 */ public final class Html2PdfUtil { private final static Log logger = LogFactory.getLog(Html2PdfUtil.class); public final static String getHtmlHead(){ StringBuilder html = new StringBuilder(); html.append("<html>") .append(" <head>") .append(" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>") .append(" <style>") .append(" body{font-family:SongTi_GB2312;height: auto;color:#000000;}") .append(" .mtop10 li{text-align: left;}") .append(" .t_right li{text-align:right !important;margin-right: 3em;}") .append(" .page_break{page-break-after: always;}") .append(" .tablePrint td {padding-top:50px; font-size: 18px;}") .append(" .tablePrint h2 {color: #000000;}") .append(" .bordered{border-bottom: 1px solid #000000 !important;border: solid #000 1px;}") .append(" .bordered td {height: 22px; border-left: 1px solid #000; border-top: 1px solid #000; padding: 10px !important; text-align: left;background-color:#ffffff;}") .append(" .trbg{color: #dd4929;background-color: #ffffff;}") .append(" .info{margin: 25px 20px 0;}") .append(" .bold{font-weight: bold !important;}") .append(" table.info-list-02 {width: 100%;background: #eeeeee;border-radius: 3px;border-bottom: 1px solid #dddfe1;font-size: 12px !important;}") .append(" .t_center {text-align: center !important;margin:0px;}") .append(" .t_right {text-align: right !important;}") .append(" .f14 {font-size: 14px !important;}") .append(" table.info-list {width: 100%;font-size: 12px;}") .append(" table.tableBordered {border-spacing: 0;width: 100%;}") .append(" .tableBordered {border: solid #666 1px;border-top: none;}") .append(" table.info-list tr td {padding: 5px 2px;}") .append(" .tableBordered td, .tableBordered th {height: 30px;border-left: 1px solid #666;border-top: 1px solid #666;padding: 10px;text-align: center;}") .append(" .mtop10 {margin-top: 10px !important;}") .append(" ul, li {list-style: none;margin: 0px;padding: 0px;}") .append(" </style>") .append(" </head>") .append(" <body>") .append(" <font face=\"SongTi_GB2312\">"); return html.toString(); } /** * 組裝html * * @param template * @return */ public final static String getHtml(String template){ StringBuilder html = new StringBuilder(); html.append(getHtmlHead()) .append(template) .append(getHtmlTail()); return html.toString(); } /** * 獲取html尾部 * * @param template * @return */ public final static String getHtmlTail(){ StringBuilder html = new StringBuilder(); html.append(" </font>") .append(" </body>") .append("</html>"); return html.toString(); } /** * 將html轉pdf(此方式在Sring對象過大的狀況下可能致使heap溢出) * * @param response * @param template * @throws InvalidParameterException * @throws IOException */ @Deprecated public final static void html2pdf(HttpServletResponse response, String template) throws InvalidParameterException, IOException{ response.setContentType("application/pdf;charset=UTF-8"); response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(PrintConfig.PDF_NAME, "UTF-8")); ServletOutputStream outputStream = response.getOutputStream(); PD4ML pd4ml = new PD4ML(); pd4ml.setPageSize(PD4Constants.A4); pd4ml.setHtmlWidth(1073); pd4ml.setPageInsets(new Insets(15, 10, 10, 10)); pd4ml.enableDebugInfo(); pd4ml.monitorProgress(new ProgressMeter()); pd4ml.useTTF(PrintConfig.FONT_PACKAGE, true); pd4ml.setDefaultTTFs("SongTi_GB2312", "SongTi_GB2312", "SongTi_GB2312"); String html = getHtml(template); pd4ml.render(new StringReader(html), outputStream); outputStream.close(); } /** * 瀏覽器打開方式 * * @param isAttachment * @return */ private final static String getOpenType(boolean isAttachment){ if(isAttachment){ return PrintConfig.ATTACHMENT; } else { return PrintConfig.INLINE; } } //======================================================= /** * 將html寫入pdf * * @param html * @param outFile * @throws InvalidParameterException * @throws IOException */ public final static void writePdf(String html, File outFile) throws InvalidParameterException, IOException{ PD4ML pd4ml = new PD4ML(); pd4ml.setPageSize(PD4Constants.A4); pd4ml.setHtmlWidth(1073); pd4ml.setPageInsets(new Insets(15, 10, 10, 10)); pd4ml.enableDebugInfo(); pd4ml.useTTF(PrintConfig.FONT_PACKAGE, true); pd4ml.setDefaultTTFs("SongTi_GB2312", "SongTi_GB2312", "SongTi_GB2312"); pd4ml.enableImgSplit(false); pd4ml.monitorProgress(new ProgressMeter()); OutputStream outputStream = FileUtils.openOutputStream(outFile); pd4ml.render(new StringReader(html), outputStream); outputStream.close(); } private final static void mergePdf(List<File> pdfs, File mergedPdf) throws IOException{ PDFMergerUtility mergePdf = new PDFMergerUtility(); for(File pdf:pdfs){ mergePdf.addSource(pdf); } mergePdf.setDestinationFileName(mergedPdf.getAbsolutePath()); mergePdf.mergeDocuments(MemoryUsageSetting.setupTempFileOnly()); } private final static void showPdf(HttpServletResponse response, File pdf, boolean isAttachment) throws IOException{ // 設置response的Header response.setContentType("application/pdf;charset=UTF-8"); response.setHeader("Content-Disposition", getOpenType(isAttachment)+";filename=" + URLEncoder.encode(PrintConfig.PDF_NAME, PrintConfig.UTF_8)); response.setContentLength((int) pdf.length()); // 瀏覽器緩存20分鐘 response.setHeader("Cache-Control", "max-age="+PrintConfig.CLIENT_PDF_CACHE_20MINUTES); ServletOutputStream outputStream = response.getOutputStream(); FileInputStream fis = FileUtils.openInputStream(pdf); byte[] buffer = new byte[PrintConfig.BUFFERED_SIZE_10M]; while(fis.read(buffer, 0, PrintConfig.BUFFERED_SIZE_10M) != -1){ outputStream.write(buffer, 0, PrintConfig.BUFFERED_SIZE_10M); } outputStream.flush(); outputStream.close(); fis.close(); } /** * 合併,並在網頁顯示PDF * * @param tmpBaseDirUrl 臨時文件夾 * @param htmlFiles 全部生成的html * @param response * @throws InvalidParameterException * @throws IOException */ public final static void showMergePDF(String tmpBaseDirUrl, List<File> pdfFiles, HttpServletResponse response) throws InvalidParameterException, IOException{ // 二、pdf合成 logger.debug("2.一、pdf合成:"+tmpBaseDirUrl); File mergedPdf = new File(tmpBaseDirUrl + File.separator + PrintConfig.PDF_NAME); Html2PdfUtil.mergePdf(pdfFiles, mergedPdf); // 三、瀏覽器文件流輸出 logger.debug("2.二、瀏覽器文件流輸出:"+tmpBaseDirUrl); Html2PdfUtil.showPdf(response, mergedPdf, false); // 四、刪除全部文件 logger.debug("2.三、刪除全部文件:"+tmpBaseDirUrl); FileUtils.deleteDirectory(new File(tmpBaseDirUrl)); } }
(二)進度類
package com.hyn.util; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.zefer.pd4ml.PD4ProgressListener; /** * pdf處理進度 * * @author liyulin * @version 1.0 2016年12月15日 下午1:52:53 */ public final class ProgressMeter implements PD4ProgressListener { private Log logger = LogFactory.getLog(getClass()); public void progressUpdate(int messageID, int progress, String note, long msec) { String tick = String.format("spend:%7d", msec); String progressString = String.format("%3d", progress); String step = ""; switch (messageID) { case CONVERSION_BEGIN: step = "conversion begin"; break; case HTML_PARSED: step = "html parsed"; break; case DOC_TREE_BUILT: step = "document tree structure built"; break; case HTML_LAYOUT_IN_PROGRESS: step = "layouting..."; break; case HTML_LAYOUT_DONE: step = "layout done"; break; case TOC_GENERATED: step = "TOC generated"; break; case DOC_OUTPUT_IN_PROGRESS: step = "generating PDF..."; break; case NEW_SRC_DOC_BEGIN: step = "proceed to new source document"; break; case CONVERSION_END: step = "done."; break; } logger.debug(tick + " " + progressString + "% " + step + " " + note);; } }
(三)常量類
package com.hyn.util; /** * 打印配置 * * @author liyulin * @version 1.0 2016年12月15日 下午6:55:48 */ public final class PrintConfig { /** 打印時的臨時文件夾 */ public final static String BASE_DIR_URL = System.getProperty("java.io.tmpdir"); /** 網頁顯示 */ public final static String INLINE = "inline"; /** 下載 */ public final static String ATTACHMENT = "attachment"; /** utf-8編碼 */ public final static String UTF_8 = "UTF-8"; /** 緩存大小10M */ public final static int BUFFERED_SIZE_10M = 1024 * 1024 * 10; /** 帳單一次性寫入的大小 */ public final static int WRITE_BILL_BUFFERED_SIZE = 30; /** SONGTI.TTF存放路徑 */ public final static String FONT_PACKAGE = "java:com/hyn/fonts"; /** pdf文件名稱 */ public final static String PDF_NAME = "批量打印.pdf"; /** 瀏覽器pdf緩存時間20分鐘 */ public final static long CLIENT_PDF_CACHE_20MINUTES = 60 * 20; }
(四)servlet
package com.hyn.servlet; import java.io.File; import java.io.IOException; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.hyn.util.Html2PdfUtil; import com.hyn.util.PrintConfig; /** * 批量打印servlet * * @author liyulin * @version 1.0 2016年12月16日 下午2:18:48 */ @WebServlet("/html2PdfServlet") public class Html2PdfServlet extends HttpServlet { private final static long serialVersionUID = 1L; private final static Log logger = LogFactory.getLog(Html2PdfServlet.class); private final static String billTemplate = "<div class=\"info posrelative page_break\"><div><div class=\"overflow\" style=\"height: 60px;\"><img style=\"max-width: 200px; max-height: 60px;height: 60px;border:0;\" src=\"http://cdn.linlile.com.cn:8086/propertyCommpany/payBillPrintCompanyIcon_5027938.png\" onerror=\"this.parentNode.parentNode.remove();\"/></div><h3 class=\"t_center\" style=\"font-size: 24px;\"><u>A小區</u>(小區) <u>2016</u>年 <u>11</u>月份<u>物業費</u>(帳單)收費通知單</h3><table class=\"info-list mtop10 f14\" border=\"0\"><tr><td colspan=\"3\">房號:1-101</td><td class=\"t_right\" colspan=\"3\" style=\"padding-right: 6em;\">戶名:</td></tr></table><table class=\"tableBordered\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><thead><tr><th width=\"25%\">項目</th><th>__月讀數</th><th>__月讀數</th><th width=\"22%\">面積(㎡)/ 用量(m³、度)</th><th width=\"100\">標準單價(元)</th><th width=\"120\">應交合計</th></tr></thead> <tr> <td>管理費</td> <td> </td> <td> </td> <td> </td> <td> </td> <td>170.00</td></tr><tr> <td>主體金</td> <td> </td> <td> </td> <td> </td> <td> </td> <td>25.50</td></tr><tr> <td>合計</td> <td> </td> <td> </td> <td> </td> <td> </td> <td>195.50</td></tr><tr><td colspan=\"6\"><ul class=\"mtop10\"><li>一、根據《半山半海花園物業服務收費標準》所示,物業費標準:3.8元/㎡/月;水費標準:按市政階梯標準收費。</li><li>二、本次收取<u>2016</u>年<u>11</u>月<u>1</u>日起至<u>2016</u>年<u>11</u>月<u>30</u>日物業費用; <u>2016</u>年<u>09</u>月 <u>11</u>日起至<u>2016</u>年<u>10</u>月 <u>10</u>日產生的水費;費用合計<u>195.50</u>。</li><li>三、請於<u>1970</u>年<u>01</u>月<u>15</u>日前至樓村花園物業服務中心財務室繳納物業費 /水電費用。</li><li>四、若有疑問,可隨時致電諮詢:0755-27118338。</li></ul> </td></tr></table> <ul class=\"mtop10 t_right\"><li>小曾06物業</li><li><u>2016</u>年<u>12</u>月<u>12</u>日</li></ul></div> </div>"; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 一、組裝html,並生成pdf String tmpBaseDirUrl = ""; // linux環境上臨時目錄url後面可能沒有File.separator if(PrintConfig.BASE_DIR_URL.endsWith(File.separator)){ tmpBaseDirUrl = PrintConfig.BASE_DIR_URL + UUID.randomUUID().toString(); } else { tmpBaseDirUrl = PrintConfig.BASE_DIR_URL + File.separator + UUID.randomUUID().toString(); } logger.debug("一、組裝html,並生成pdf:"+tmpBaseDirUrl); System.err.println(tmpBaseDirUrl); List<File> pdfFiles = generatePDFForBills(tmpBaseDirUrl); // 二、合併,並在網頁顯示PDF logger.debug("二、合併,並在網頁顯示PDF:"+tmpBaseDirUrl); Html2PdfUtil.showMergePDF(tmpBaseDirUrl, pdfFiles, response); } /** * 組裝html,並生成pdf * * @param tmpBaseDirUrl * @return * @throws IOException * @throws InvalidParameterException */ private List<File> generatePDFForBills(String tmpBaseDirUrl) throws InvalidParameterException, IOException{ List<File> pdfFiles = new ArrayList<File>(); StringBuilder templates = new StringBuilder(); int billSize = 102; for(int i=0; i<billSize; i++){ templates.append(billTemplate); if (isNeedWrite(i, billSize)) { String fileUrl = tmpBaseDirUrl +File.separator+pdfFiles.size()+".pdf"; File pdfFile = new File(fileUrl); String html = Html2PdfUtil.getHtml(templates.toString()); Html2PdfUtil.writePdf(html, pdfFile); pdfFiles.add(pdfFile); templates.delete(0, templates.length()); } } return pdfFiles; } /** * 判斷是否須要寫入 * * @param index * @param size * @return */ private boolean isNeedWrite(int index, int size){ return (index % PrintConfig.WRITE_BILL_BUFFERED_SIZE == 0) || (index % PrintConfig.WRITE_BILL_BUFFERED_SIZE != 0 && index == size - 1); } }
下載