This is the result I summarized when I met such needs in my work. When I finished the needs, I took it as a work note to avoid forgetting in the future. Of course, it is the best way to help others.
Prerequisite preparation:
1. At least the jar packages that need to be introduced in the project, pay attention to the version:
a) core-renderer.jar
b) freemarker-2.3.16.jar
c) iText-2.0.8.jar
d) iTextAsian.jar
Upper Code:
Note: This class is the base class of the custom Tag class. It is concise and clear about how to put data in action and how to get data in ftl.
1. Base class of custom Tag class
/** * Generating general-purpose pdf preview and generating printed html files * * @author xg Gentleman * */ public abstract class PDFTag extends BodyTagSupport { private static final long serialVersionUID = 1L; // Tag Attribute Variable private String json = ""; private String tempDir = ""; // Unlabeled attribute variables 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()); doServiceStart(); String ctx = (String) pageContext.getAttribute("ctx"); rootMap.put("ctx", ctx); Map<String, Object> map = parseJSON2Map(json); rootMap.putAll(map); if (freemarkereConfigurationBeanName == null) { try { throw new CstuException("FreemarkereConfigurationBeanName Can not be empty!"); } catch (CstuException e) { e.printStackTrace(); } } Configuration cptFreemarkereConfiguration = (Configuration) application .getBean(freemarkereConfigurationBeanName); try { if (templateStr == null) { throw new CstuException("Template files cannot be empty!"); } Template template = cptFreemarkereConfiguration.getTemplate(templateStr); if (basePath == null) { throw new CstuException("Basic path of file(Parent path)Can not be empty!"); } File htmlPath = new File(tempDir + File.separator + basePath); if (!htmlPath.exists()) { htmlPath.mkdirs(); } if (fileName == null) { throw new CstuException("Generated html File name cannot be empty!"); } 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(); // Display on the page template.process(rootMap, pageContext.getResponse().getWriter()); } catch (Exception e) { e.printStackTrace(); } doServiceEnd(); return SKIP_BODY; } /** * Configure basic parameters, such as */ public abstract void setConfigParams(); /** * Business Processing Method - Start Filling Data * * @return */ public abstract void doServiceStart(); /** * Business Processing Method - Standby in execution, empty implementation. If there are two copies of data in rootMap, you can fill in the judgment condition here. * * @return */ public abstract void doServiceDoing(); /** * Business Processing Method - End Clearing rootMap and Call Garbage Recycling, or Empty Implementation * * @return */ public abstract void doServiceEnd(); /** * Place elements in rootMap */ public void putKV(String key, Object value) { rootMap.put(key, value); } /** * Put map into rootMap * * @param m */ public void putMap(Map m) { rootMap.putAll(m); } public void clear() { rootMap.clear(); rootMap = null; } /** * Removing Elements * * @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; } }
Note: The setConfigParams method is used to invoke the configuration parameters defined by the interface, such as template Str, basePath, doService Start, doService Doing and doService End, etc. to process business logic. For example, my requirement is to make a contract to display on a page, paging, watermarking, but the generated pdf style is different from the preview, so I added one. Adding a judgment condition to rootMap in each doService Doing makes it possible for an flt file to produce two effects (preview and print). Of course, if preview and print are the same, the doService Doing method can be implemented empty. These four methods are summarized as follows:
1. setConfigParams: Configuration parameters
2. doService Start: Fill in data / conditions
3. doService Doing: Fill in the data / condition, and here is a dividing line. Before this method, the rootMap data first enters html and then enters the browser (preview). After this method, the rootMap data enters the html file again and ends, so judgment can be written here.
4. doServiceEnd: Yes or no, I wrote that in case the data set is too large on one day, it can be cleaned up after filling up the data and save memory space.
2. Subclasses of PDFTag
/** * User-defined PDFTag class * * @author xg Gentleman * */ public class ViewPDFTag extends PDFTag { private static final long serialVersionUID = 4528567203497016087L; private String prjNature = ""; private String bookName = ""; private String prjCode = ""; /** * User-defined configuration parameters */ public PDFConfigurationInterface pDFConfigurationInterface = new PDFConfigurationInterface() { @Override public void configTemplateStr() { // transverse,portrait if (prjNature.equalsIgnoreCase("2") || prjNature.equalsIgnoreCase("1")) { setTemplateStr("wj-project-print.ftl"); } } @Override public void configFreemarkereConfigurationBeanName() { setFreemarkereConfigurationBeanName("cptFreemarkereConfiguration"); } @Override public void configFileName() { // Fill html file setFileName(prjCode + ".html"); } @Override public void configFileEncoding() { // default 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(); } }
Note: PDF Configuration Interface is a custom tag parameter configuration interface. A subclass must define a member variable of an implementation class of the interface or an internal class of a member variable, and call it into effect in the setConfigParams method.
Membership variables of subclasses receive properties that are configured in the tld file.
3. Custom Label Parameter Configuration Interface
/** * PdfTag Class configuration * * @author xg Gentleman * */ public interface PDFConfigurationInterface { /** * Setting template name */ void configTemplateStr(); /** * Set the name of the configured FreemarkereConfiguration Bean */ void configFreemarkereConfigurationBeanName(); /** * Set the name of the generated html file */ void configFileName(); /** * Set the basic path (parent directory) of the generated html file */ void configBasePath(); /** * Set file encoding, default utf-8 */ void configFileEncoding(); }
4. Customize exception classes
/** * Custom exception class * * @author Administrator * */ public class CstuException extends Exception { private static final long serialVersionUID = 4266461814747405870L; public CstuException(String msg) { super(msg); } }
5. tld file configuration
<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
/** * Processing PDF export * */ @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.put("tempDir", tempDir); // Item number baseInfo.put("prjCode", prjCode); // Project type String prjNature = project.getPrjNature(); baseInfo.put("prjNature", prjNature); // Watermark Name Format:watermark+"-"+prjNature baseInfo.put("waterMarkImg", waterMarkImgDir + File.separator + "watermark-" + prjNature + ".png"); // Person in charge Person person = personService.getPerson(project.getPsnCode()); String zhName = person.getZhName(); baseInfo.put("zhName", addStr(9, "<br/>", zhName)); // Item number String prjNo = project.getPrjNo(); baseInfo.put("prjNo", prjNo); // Project source ConstDictionary cd = constDictionaryService.findCdByCategoryCode("project_from", project.getGrantNo()); String project_from = cd.getzh_cn_caption(); baseInfo.put("project_from", addStr(9, "<br/>", project_from)); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // Starting and ending years String startDate = sdf.format(project.getStartDate()); String endDate = sdf.format(project.getEndDate()); String startEndDate = startDate + "~" + endDate; baseInfo.put("startEndDate", startEndDate); // Item category--Funding category String grantName = project.getGrantName(); baseInfo.put("grantName", addStr(9, "<br/>", grantName)); // Contract amount String totalAmt = project.getTotalAmt().toString(); BigDecimal totalAmt_ = checkNumber(totalAmt); baseInfo.put("totalAmt", totalAmt_.toString()); // entry name String zhTitle = project.getZhTitle(); baseInfo.put("zhTitle", addStr(38, "<br/>", zhTitle)); List<Map<String, String>> ps = null; try { ps = getMembers(project.getPrjXml()); } catch (Exception e) { e.printStackTrace(); } String bookName = ""; // Judging Project Type Map<String, Object> itemMap = new HashMap<String, Object>(); if (prjNature.equalsIgnoreCase("1")) { bookName = "First Contract"; // Obtain fdPlanDetail List<Map<String, Object>> list = fdPlanDetailService.getFdPlanDetailsByPrjCode(Long.parseLong(prjCode)); // test /* * Map<String, Object> test = new HashMap<String, Object>(); test.put("itemValue", "6"); * test.put("itemName", "Travel expenses; test.put("remark", "xxxxx"); list.add(test); */ for (Map<String, Object> m : list) { String key = (String) m.get("ITEMNAME"); BigDecimal itemValue = (BigDecimal) m.get("ITEMVALUE"); BigDecimal proportion = new BigDecimal(0.00); if (itemValue != null && totalAmt_.compareTo(new BigDecimal("0.00")) != 0) { proportion = itemValue.divide(totalAmt_, 6, BigDecimal.ROUND_HALF_EVEN); } if (itemValue == null) { itemValue = new BigDecimal("0.00"); } proportion = checkNumber(proportion.toString()); Map<String, Object> data = new HashMap<String, Object>(); // Adding proportion data.put("proportion", proportion.toString()); // Specification of testing amount BigDecimal amt = checkNumber(itemValue.toString()); data.put("itemValue", amt.toString()); // remark String remark = (String) m.get("REAMRK"); data.put("remark", remark == null ? "" : remark); itemMap.put(key, data); } } else if (prjNature.equalsIgnoreCase("2")) { bookName = "Second Contract"; } Map<String, Object> map = new HashMap<String, Object>(); map.put("baseInfo", baseInfo); map.put("projectMember", ps); map.put("itemMap", itemMap); map.put("psCount", ps.size()); map.put("psSum", 25 - ps.size()); String json = JSONObject.fromObject(map).toString(); Struts2Utils.getRequest().setAttribute("jsonData", json); Struts2Utils.getRequest().setAttribute("prjNature", prjNature); Struts2Utils.getRequest().setAttribute("bookName", bookName); Struts2Utils.getRequest().setAttribute("tempDir", tempDir); Struts2Utils.getRequest().setAttribute("prjCode", prjCode); return "exprotPdf2"; } public List<Map<String, String>> getMembers(String xmlData) throws Exception { List<Map<String, String>> list = new ArrayList<Map<String, String>>(); Document doc = DocumentHelper.parseText(xmlData); Node ps = doc.selectSingleNode("/data/project/persons"); List<Node> psList = ps.selectNodes("person"); String totalAmt = doc.selectSingleNode("/data/project/basic_info/total_amt").getText(); for (Node person : psList) { Map<String, String> map = new HashMap<String, String>(); Node fund_proportion = person.selectSingleNode("fund_proportion"); String fund_proportion_text = ""; if (fund_proportion == null) { map.put("proportion", "0.00"); map.put("fpAmt", "0.00"); } else { fund_proportion_text = fund_proportion.getText(); BigDecimal fp = new BigDecimal(fund_proportion_text); fp = fp.multiply(new BigDecimal("0.01")); fp = checkNumber(fp.toString()); map.put("proportion", fp.toString()); BigDecimal fdAmt_ = fp.multiply(new BigDecimal(totalAmt)); fdAmt_ = checkNumber(fdAmt_.toString()); map.put("fpAmt", fdAmt_.toString()); } Node psn_name = person.selectSingleNode("psn_name"); String psn_name_text = psn_name.getText(); map.put("zhName_p", addStr(9, "<br/>", psn_name_text)); Node psn_work = person.selectSingleNode("psn_work"); String psn_work_text = ""; if (psn_work != null) { psn_work_text = psn_work.getText(); } map.put("work", addStr(9, "<br/>", psn_work_text)); Node dept_code_name = person.selectSingleNode("dept_code_name"); String dept_code_name_text = ""; if (dept_code_name != null) { dept_code_name_text = dept_code_name.getText(); } map.put("deptName", addStr(9, "<br/>", dept_code_name_text)); Node psn_type_name = person.selectSingleNode("psn_type_name"); String psn_type_name_text = ""; if (psn_type_name != null) { psn_type_name_text = psn_type_name.getText(); } map.put("psnTypeName", psn_type_name_text); list.add(map); } return list; } /** * Adds a specified character to a string * * @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(); } /** * Two Numbers/English * * @param str * @param num * @return Final index */ public static int getEndIndex(String str, double num) { int idx = 0; int count = 0; double val = 0.00; // Is it English?/number 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; } /** * Download pdf file * * @return */ @Action("downLoad") public String downLoad() { String prjCode = Struts2Utils.getParameter("prjCode"); String basePath = "html_pdf"; Project project = projectService.getProjectById(Long.parseLong(prjCode)); String zhTitle = project.getZhTitle(); FileOutputStream fos = null; // html File htmlFile = new File(tempDir + File.separator + basePath + File.separator + prjCode + ".html"); String pdfPath = tempDir + File.separator + basePath + File.separator + zhTitle + ".pdf"; try { fos = new FileOutputStream(pdfPath); String url = htmlFile.toURI().toURL().toString(); ITextRenderer renderer = new ITextRenderer(); renderer.setDocument(url); ITextFontResolver fontResolver = renderer.getFontResolver(); fontResolver.addFont(pdfFont + File.separator + "SimSun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); renderer.layout(); renderer.createPDF(fos); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } // Add watermark String wartermark = ""; if (project.getPrjNature().equalsIgnoreCase("1")) { // transverse wartermark = "CTGU Notice of Establishment of Horizontal Scientific Research Projects"; } else if (project.getPrjNature().equalsIgnoreCase("2")) { // portrait wartermark = "CTGU Notice of Vertical Scientific Research Project Establishment"; } String wm_pdf = tempDir + File.separator + "wm_" + project.getZhTitle() + ".pdf"; try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(wm_pdf)); waterMark(bos, pdfPath, wartermark, staticResRootDir2 + File.separator + waterMarkImgDir + File.separator + "watermark-" + project.getPrjNature() + ".png"); } catch (Exception e2) { e2.printStackTrace(); } // Get 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(); } } } // Clean up residual documents // 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) { // Initialized to 6 decimal digits 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(""); } /** * Add watermark */ 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);// Watermarking over Content 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 file (template file)
<?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>Print preview</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
......
</head>
<body>
</body>
</html>
Preview effect:
Printing effect:
Over the weekend, I'll upload a separate demo to github, and you can ask me if you have any questions.