本博客是本身在學習和工做途中的積累與總結,僅供本身參考,也歡迎你們轉載,轉載時請註明出處,請尊重他人努力成果,謝謝。
http://www.cnblogs.com/king-xg/p/6369291.html
css
前提準備:html
1. 項目中至少須要引入的jar包,注意版本:
css3
a) core-renderer.jargit
b) freemarker-2.3.16.jarweb
c) iText-2.0.8.jarjson
d) iTextAsian.jar 瀏覽器
上代碼:
app
註釋: 此類爲自定義的Tag類的基類,在action中怎麼放的數據,在ftl中就怎麼取數據,簡潔明瞭。jsp
1. 自定義Tag類的基類ide
/** * 通用的生成pdf預覽和生成打印的html文件 * * @author xg君 * */ public abstract class PDFTag extends BodyTagSupport { private static final long serialVersionUID = 1L; // 標籤屬性變量 private String json = ""; private String tempDir = ""; // 非標籤屬性變量 private Map<String, Object> rootMap = new HashMap<String, Object>(); private String templateStr = null; private String freemarkereConfigurationBeanName = null; private String fileName = null; private String basePath = null; private String fileEncoding = "utf-8"; @Override public int doStartTag() throws JspException { setConfigParams(); WebApplicationContext application = WebApplicationContextUtils.getWebApplicationContext(pageContext .getServletContext()); Map<String, Object> map = parseJSON2Map(json); doServiceStart(); rootMap.putAll(map); if (freemarkereConfigurationBeanName == null) { try { throw new CstuException("FreemarkereConfigurationBeanName不能爲空!"); } catch (CstuException e) { e.printStackTrace(); } } Configuration cptFreemarkereConfiguration = (Configuration) application .getBean(freemarkereConfigurationBeanName); try { if (templateStr == null) { throw new CstuException("模板文件不能爲空!"); } Template template = cptFreemarkereConfiguration.getTemplate(templateStr); if (basePath == null) { throw new CstuException("文件的基本路徑(父路徑)不能爲空!"); } File htmlPath = new File(tempDir + File.separator + basePath); if (!htmlPath.exists()) { htmlPath.mkdirs(); } if (fileName == null) { throw new CstuException("生成的html文件名不能爲空!"); } File htmlFile = new File(htmlPath, File.separator + fileName); if (!htmlFile.exists()) { htmlFile.createNewFile(); } BufferedWriter out = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(htmlFile), fileEncoding)); template.process(rootMap, out); out.flush(); doServiceDoing(); // 顯示在頁面 template.process(rootMap, pageContext.getResponse().getWriter()); } catch (Exception e) { e.printStackTrace(); } doServiceEnd(); return SKIP_BODY; } /** * 啓用配置的參數 */ public abstract void setConfigParams(); /** * 業務處理方法-開始 填充數據 * * @return */ public abstract void doServiceStart(); /** * 業務處理方法-執行中 備用,可空實現,若rootMap中存在雙份數據則可在此處填充判斷條件 * * @return */ public abstract void doServiceDoing(); /** * 業務處理方法-結束 清空rootMap並調用垃圾回收,也可空實現 * * @return */ public abstract void doServiceEnd(); /** * 將元素放入rootMap中 */ public void putKV(String key, Object value) { rootMap.put(key, value); } /** * 將map放入rootMap中 * * @param m */ public void putMap(Map m) { rootMap.putAll(m); } public void clear() { rootMap.clear(); rootMap = null; } /** * 移除元素 * * @param key * @return */ public Object remove(String key) { return rootMap.remove(key); } public static Map<String, Object> parseJSON2Map(String jsonStr) { Map<String, Object> map = new HashMap<String, Object>(); JSONObject json = JSONObject.fromObject(jsonStr); for (Object k : json.keySet()) { Object v = json.get(k); if (v instanceof JSONArray) { List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); Iterator<JSONObject> it = ((JSONArray) v).iterator(); while (it.hasNext()) { JSONObject json2 = it.next(); list.add(parseJSON2Map(json2.toString())); } map.put(k.toString(), list); } else { map.put(k.toString(), v); } } return map; } public String getJson() { return json; } public void setJson(String json) { this.json = json; } public String getTempDir() { return tempDir; } public void setTempDir(String tempDir) { this.tempDir = tempDir; } public String getTemplateStr() { return templateStr; } public void setTemplateStr(String templateStr) { this.templateStr = templateStr; } public String getFreemarkereConfigurationBeanName() { return freemarkereConfigurationBeanName; } public void setFreemarkereConfigurationBeanName(String freemarkereConfigurationBeanName) { this.freemarkereConfigurationBeanName = freemarkereConfigurationBeanName; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getBasePath() { return basePath; } public void setBasePath(String basePath) { this.basePath = basePath; } public String getFileEncoding() { return fileEncoding; } public void setFileEncoding(String fileEncoding) { this.fileEncoding = fileEncoding; } }
註釋: setConfigParams方法是用於調用接口定義的配置參數的方法,如:templateStr,basePath等,doServiceStart,doServiceDoing和doServiceEnd等方法用於處理業務邏輯,好比個人需求是作出合同在一個頁面顯示,要分頁,要加水印,但生成的pdf樣式與預覽的是不一樣的,因此我加了一個doServiceDoing中給rootMap添加判斷條件,這樣就能一個flt文件做出兩種效果(預覽和打印),固然若是預覽和打印要同樣的效果,doServiceDoing方法能夠空實現。這四個方法總結一下就是:
1. setConfigParams : 配置參數
2. doServiceStart : 填充數據/條件
3. doServiceDoing : 填充數據/條件,到這裏是個分界線,此方法以前,rootMap數據先進入html再進入瀏覽器(預覽),此方法以後,rootMap數據會再次進入html文件,結束,因此此處可寫判斷
4. doServiceEnd : 無關緊要,我仍是寫上了,萬一哪天的數據集太大,此處即可以在數據填充完後,清理掉,節省內存空間
2. PDFTag的子類
/** * 用戶自定義PDFTag類 * * @author xg君 * */ public class ViewPDFTag extends PDFTag { private static final long serialVersionUID = 4528567203497016087L; private String prjNature = ""; private String bookName = ""; private String prjCode = ""; /** * 用戶自定義的配置參數 */ public PDFConfigurationInterface pDFConfigurationInterface = new PDFConfigurationInterface() { @Override public void configTemplateStr() { // 自定義的業務參數 if (prjNature.equalsIgnoreCase("2") || prjNature.equalsIgnoreCase("1")) { setTemplateStr("wj-project-print.ftl"); } } @Override public void configFreemarkereConfigurationBeanName() { setFreemarkereConfigurationBeanName("cptFreemarkereConfiguration"); } @Override public void configFileName() { // 填入html文件 setFileName(prjCode + ".html"); } @Override public void configFileEncoding() { // 默認utf-8 } @Override public void configBasePath() { setBasePath("html_pdf"); } }; @Override public void doServiceStart() { putKV("prjNature", prjNature); putKV("bookName", bookName); putKV("flag", "0"); } @Override public void doServiceDoing() { putKV("flag", "1"); } @Override public void doServiceEnd() { clear(); System.gc(); } public String getPrjNature() { return prjNature; } public void setPrjNature(String prjNature) { this.prjNature = prjNature; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getPrjCode() { return prjCode; } public void setPrjCode(String prjCode) { this.prjCode = prjCode; } @Override public void setConfigParams() { pDFConfigurationInterface.configTemplateStr(); pDFConfigurationInterface.configFreemarkereConfigurationBeanName(); pDFConfigurationInterface.configFileName(); pDFConfigurationInterface.configBasePath(); pDFConfigurationInterface.configFileEncoding(); } }
註釋: 1. PDFConfigurationInterface 是自定義的標籤參數配置接口,子類必須定義一個該接口的實現類的成員變量或定義一個成員變量內部類,並在setConfigParams方法中調用使其生效。
2. 其實這個setConfigParams方法能夠不用寫,的用反射就能在後面所有自動執行,可是考慮到有些參數是默認的,能夠不用設置,因此還寫了setConfigParams方法
3. 子類的成員變量接收在tld文件中配置的屬性。
3. 自定義的標籤參數配置接口
/** * PdfTag類的配置 * * @author xg君 * */ public interface PDFConfigurationInterface { /** * 設置模板名稱 */ void configTemplateStr(); /** * 設置配置的FreemarkereConfigurationBean的名稱 */ void configFreemarkereConfigurationBeanName(); /** * 設置生成的html文件名稱 */ void configFileName(); /** * 設置生成的html文件的基本路徑(父目錄) */ void configBasePath(); /** * 設置文件編碼,默認utf-8 */ void configFileEncoding(); }
4. 自定義異常類
/** * 自定義異常類 * * @author Administrator * */ public class CstuException extends Exception { private static final long serialVersionUID = 4266461814747405870L; public CstuException(String msg) { super(msg); } }
5. tld文件配置
<tag> <name>print</name> <tagclass>com.iris.taglib.web.PreviewPDFTag</tagclass> <bodycontent>JSP</bodycontent> <attribute> <name>json</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>prjNature</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>bookName</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>tempDir</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>prjCode</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
6. action( 在action中,存在request域中的數據是怎麼放入的,那麼在*.ftl文件中就怎麼取)
/** * 處理PDF導出 * */ @Namespace("/export") @Results({ @Result(name = "exceltemplate", location = "/WEB-INF/content/pdf/export-pdf.jsp"), @Result(name = "exprotPdf2", location = "/WEB-INF/content/project/project/export/export-pdf2.jsp") }) public class ExportPdfAction extends ActionSupport { private static final long serialVersionUID = -5454188364706173477L; @Value("${tempDir}") private String tempDir; @Value("${pdfFont}") private String pdfFont; @Value("${staticResRootDir}") private String staticResRootDir; @Value("${staticResRootDir2}") private String staticResRootDir2; @Value("${WaterMarkImgDir}") private String waterMarkImgDir; @Autowired private ProjectService projectService; @Autowired private PersonService personService; @Autowired private ConstDictionaryService constDictionaryService; @Autowired private FdPlanDetailService fdPlanDetailService; @Autowired private ServiceFactory serviceFactory; @Action("exprotPdf2") public String exprotPdf2() { String prjCode = Struts2Utils.getParameter("prjCode"); prjCode = Struts2Utils.decodeDesString(prjCode); Project project = projectService.getProjectById(Long.parseLong(prjCode)); Map<String, String> baseInfo = new HashMap<String, String>();
// 此處將數據放入baseInfo 的map中。並返回給頁面
return "exprotPdf2"; } public List<Map<String, String>> getMembers(String xmlData) throws Exception { return list; } /** * 爲字符串添加指定字符 * * @param num * @param splitStr * @param str * @return */ public String addStr(int num, String splitStr, String str) { StringBuffer sb = new StringBuffer(); String temp = str; int len = str.length(); while (len > 0) { if (len < num) { num = len; } sb.append(temp.substring(0, num)).append(splitStr); temp = temp.substring(num); len = temp.length(); } return sb.toString(); } /** * 兩個數字/英文 * * @param str * @param num * @return 最終索引 */ public int getEndIndex(String str, double num) { int idx = 0; int count = 0; double val = 0.00; // 判斷是不是英文/數字 for (int i = 0; i < str.length(); i++) { if ((str.charAt(i) >= 'A' && str.charAt(i) <= 'Z') || (str.charAt(i) >= 'a' && str.charAt(i) <= 'z') || Character.isDigit(str.charAt(i))) { val += 0.50; } else { val += 1.00; } count = i + 1; if (val >= num) { idx = i; break; } } if (idx == 0) { idx = count; } return idx; } /** * 下載pdf文件 * * @return */ @Action("downLoad") public String downLoad() { String prjCode = Struts2Utils.getParameter("id"); String basePath = "html_pdf"; Project project = projectService.getProjectById(prjCode); String zhTitle = project.getZhTitle(); FileOutputStream fos = null; // html // 經過自定義標籤生成的html轉成pdf
......
// 添加水印
// 拿到pdf File pdfFile = new File(wm_pdf); BufferedOutputStream out = null; FileInputStream in = null; try { in = new FileInputStream(pdfFile); HttpServletResponse response = Struts2Utils.getResponse(); response.reset(); String fileName = zhTitle + ".pdf"; String fileName2 = URLEncoder.encode(fileName, "UTF-8"); String agent = Struts2Utils.getRequest().getHeader("USER-AGENT"); // IE if (null != agent && -1 != agent.indexOf("MSIE")) { fileName2 = new String(fileName.getBytes("GBK"), "ISO8859-1"); } else if (null != agent && -1 != agent.indexOf("Mozilla")) { fileName2 = new String(fileName.getBytes("UTF-8"), "ISO8859-1"); } response.setCharacterEncoding("UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName2 + "\""); response.setContentType(FileContentTypes.getContentType(zhTitle + ".pdf")); out = new BufferedOutputStream(response.getOutputStream()); byte[] buffer = new byte[16 * 1024]; int len = 0; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e1) { e1.printStackTrace(); } try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } // 清理殘留文件 // if (htmlFile.exists()) { // htmlFile.delete(); // } File temp_file = new File(pdfPath); if (temp_file.exists()) { temp_file.delete(); } if (pdfFile.exists()) { pdfFile.delete(); } return null; }
public BigDecimal checkNumber(String number) { // 初始化爲6位小數 DecimalFormat df = new DecimalFormat("0.000000"); String num = df.format(Double.parseDouble(number)); BigDecimal bd = new BigDecimal(num); String val = bd.toString(); val = val.replaceAll("^(0+)", ""); val = val.replaceAll("(0+)$", ""); int idx = val.indexOf("."); int len = val.substring(idx + 1).length(); if (len < 2) { if (len == 0 && idx == 0) { bd = new BigDecimal("0.00"); } else { bd = new BigDecimal(val).setScale(2); } } else { bd = new BigDecimal(val).setScale(len); } return bd; } private String replaceStr(String str, String reVal) { Pattern pattern = Pattern.compile("^" + reVal + "+|" + reVal + "+$"); Matcher matcher = pattern.matcher(str); return matcher.replaceAll(""); } /** * 添加水印 */ private void waterMark(BufferedOutputStream bos, String input, String waterMarkName, String imagePath) { try { PdfReader reader = new PdfReader(input); PdfStamper stamper = new PdfStamper(reader, bos); int total = reader.getNumberOfPages() + 1; PdfContentByte content; BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); PdfGState gs = new PdfGState(); for (int i = 1; i < total; i++) { content = stamper.getOverContent(i);// 在內容上方加水印 content.setGState(gs); content.beginText(); Image image = Image.getInstance(imagePath); image.setAbsolutePosition(-30, 200); image.scalePercent(80); content.addImage(image); content.endText(); } stamper.close(); } catch (Exception e) { e.printStackTrace(); } } }
7. ftl文件(模板文件)
補充:ftl文件是經過取得標籤傳入的數據,經過freemarker表達式進行取值操做,關於freemarker表達式的使用,後期將抽空補上
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>打印預覽</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
</head>
<#if "${flag}" = "0">
<body style="background:#ffffff;font-size:10px;">
</#if>
<#if "${flag}" = "1">
<body>
</#if>
<#if "${flag}" = "1">
<div class="top">
<!-- <a id="printbt" href="#" class="grey_but mw50" style="float: right;margin-right: 10px;margin-top: 2px;">打印</a> -->
<a id="importbt" href="#" class="grey_but mw50" style="float: right;margin-right: 10px;margin-top: 2px;">導出pdf</a>
</div>
</#if>
<#if "${flag}" = "1">
<div style="width: 1000px;height: 50px;margin-left: auto;margin-right:auto;"></div>
</#if>
<div id="p_content" class="print_content">
<#if "${flag}" = "0">
<div class="table_block" style="width: 680px;margin-top:0px">
<#else>
<div class="table_block" style="width: 1000px;margin-top:0px">
</#if>
<img style="width: 900px;height: 900px;position: absolute;top:-100px" src="${baseInfo.waterMarkImg}" />
<table width="90%" border="0" cellspacing="0" cellpadding="0" style="margin-left: auto;margin-right:auto;border-color: #000000;border-collapse: collapse;table-layout:fixed;">
<tr>
<td colspan="5" style="border-bottom:none;border-top: none;border-left: none;border-right: none;" class="title">${bookName}</td>
</tr>
<tr class="text">
<td>xxxx</td>
<td>${xxxx.xxxx}</td>
<#if "${xxxx.xxxx}" = "1">
<td>xxxx/xxxx</td>
<td colspan="2">${xxxx.xxxx}/${xxxx.xxxx!''}</td>
</#if>
<#if "${xxxx.xxxx}" = "2">
<td>xxxx</td>
<td colspan="2">${xxxx.xxxx}/${xxxx.xxxx}</td>
</#if>
</tr>
<tr class="text">
<td>xxxx</td>
<td>${xxxx.xxxx}</td>
<td>xxxx</td>
<td colspan="2">${xxxx.xxxx}</td>
</tr>
<tr class="text">
<td>xxxx</td>
<td>${xxxx.xxxx}</td>
<td>xxxx</td>
<td colspan="2">${xxxx.xxxx}</td>
</tr>
<tr class="text">
<td>xxxx</td>
<td colspan="4">${xxxx}</td>
</tr>
<tr>
<td class="middle" colspan="5">xxxx</td>
</tr>
<#if "${xxxx.xxxx}" = "1">
<tr class="text">
<td></td>
<td>xxxx</td>
<td>xxxx</td>
<td>xxxx</td>
<td>xxxx</td>
</tr>
<#list xxxxas xxxx>
<tr class="text">
<td>${xxxx.xxxx}</td>
<td>${xxxx.xxxx_p}</td>
<td>${xxxx.xxxx}</td>
<td>${xxxx}</td>
<td>${xxxx}</td>
</tr>
</#list>
</#if>
<#if "${xxxx}" = "2">
<tr class="text">
<td>xxxx</td>
<td>xxxx</td>
<td>xxxx</td>
<td>xxxx</td>
<td>xxxx</td>
</tr>
<#list projectMember as member>
<tr class="text">
<td>${xxxx.xxxx_p}</td>
<td>${xxxx.xxxx}</td>
<td>${xxxx.xxxx}</td>
<td>${xxxx.xxxx}</td>
<td>${xxxx.xxxx}</td>
</tr>
</#list>
</#if>
<tr>
<td colspan="5" style="padding: 5px;border-bottom: none;">
<div>xxxx:</div>
<div>1.xxxx;</div>
<div>2.xxxx;</div>
<div>3.xxxx;</div>
<div>4.xxxx;</div>
<div>5.xxxx。</div>
</td>
</tr>
<tr style="height:180px;padding:0px">
<td colspan="5" style="border:none ;margin:0px;padding:0px;height:180px;">
<table width="100%" border="0" style="border-collapse: collapse;table-layout:fixed; <#if "${flag}" = "0">word-break:break-strict;</#if>height:180px;">
<tr>
<td style="padding: 0px;">
<table class="innerTB" width="100%" border="0" style="border-collapse: collapse;table-layout:fixed; <#if "${flag}" = "0">word-break:break-strict;</#if>">
<tr>
<td>xxxx(xxxx):</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td style="text-align: right;">年 月 日</td>
</tr>
<tr>
<td> </td>
</tr>
</table>
</td>
<td style="padding: 0px;">
<table class="innerTB" width="100%" border="0" style="border-collapse: collapse;table-layout:fixed; <#if "${flag}" = "0">word-break:break-strict;</#if>">
<tr>
<td>xxxx(xxxx)</td>
</tr>
<tr>
<td>xxxx:</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>(xxxx)</td>
</tr>
<tr>
<td style="text-align: right;">年 月 日</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>
</body>
</html>
預覽效果:
打印效果:
打印的話僅需在預覽頁面添加一段js代碼:
window.print();這是打印整個頁面
固然也能夠打印指定區域,須要用到css3的@media print
@media print {
.noprint {
display: none
}
}
在不打印區域添加<div class="noprint"></div>便可,主要是noprint樣式.
2017-07-11 更新補充bug或侷限性:
(1). 項目後期需求更改將放入各類類型參數,所以在後臺將數據轉成json,將會比較花時間,致使客戶端響應時間延長
解決方案1(推薦): 在自定義標籤中,經過pageContext內置對象,獲取Request實例,map = (Map<String, Object>) pageContext.getRequest().getAttribute(dataKey);便可獲取數據對象,而後放入rootMap中,共、供ftl標籤使用,即後臺正常將數據放入request域且不用轉換成json對象
解決方案2(不推薦): 採用jackson工具,將對象轉成json字符串,僅能解決數據轉換問題,並不能減小客戶端響應時間
(2) 補充,如下jar包 一樣可以生產PDF
1) flying-saucer-pdf-text5-9.0.2.jar 2) flying-saucer-core-9.0.2.jar 3) flying-saucer-log4j-9.0.2.jar 4) itextpdf-5.3.4iris.jar