批量打印實現

批量打印的難點: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>&nbsp;</td>    <td>&nbsp;</td>    <td>&nbsp;</td>    <td>&nbsp;</td>    <td>170.00</td></tr><tr>    <td>主體金</td>    <td>&nbsp;</td>    <td>&nbsp;</td>    <td>&nbsp;</td>    <td>&nbsp;</td>    <td>25.50</td></tr><tr>    <td>合計</td>    <td>&nbsp;</td>    <td>&nbsp;</td>    <td>&nbsp;</td>    <td>&nbsp;</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);
	}

}

下載

        http://download.csdn.net/detail/class__horse/9713450

相關文章
相關標籤/搜索