批量打印的难点: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); } }
下载