Java用itext5导出pdf前后端实现

    技术2022-07-10  141

    遇到一个需求,要将页面上展示的表格数据导出成一个pdf,查了资料决定用itext来做,记录一下前后端实现和自己遇到的问题

    前端:

    <button type="button" class="btn btn-primary" id="confirm-export">导出</button> $('#confirm-export').click(function () { var serialize = $("#processForm").serialize(); //创建一个a标签 var a = document.createElement('a'); operName = $("#merchantName").val(); //调用接口 a.href = "url?"+serialize; //下载文件命名 a.download = operName + '结算汇总.pdf'; a.click(); return false; });

    后端:

    jar包版本如下:

    <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13.1</version> </dependency <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency>

    Util工具类:

    import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import com.itextpdf.text.*; import com.itextpdf.text.pdf.*; /** * pdf工具类 * @author wangyuanchao */ public class PdfUtil extends PdfPageEventHelper{ /** * 定义静态变量,用于生成水印文件名称 */ private final static String RESULT_FILE = "C:/Users/sungm/Desktop/Test/GCX-creditreport-template.pdf"; /** * 页眉 */ public String header = "xxxx"; /** * 文档字体大小,页脚页眉最好和文本大小一致 */ public int presentFontSize = 12; /** * 文档页面大小,最好前面传入,否则默认为A4纸张 */ public Rectangle pageSize = PageSize.A4; /** * 基础字体对象 */ public BaseFont bf = null; /** * 利用基础字体生成的字体对象,一般用于生成中文文字 */ public Font fontDetail = null; // 模板 public PdfTemplate total; /** * 返回一个暂无数据的pdf文件 **/ public static void nullPdf(OutputStream out)throws MalformedURLException, IOException, DocumentException { //①建立com.lowagie.text.Document对象的实例。 Document doc = new Document(PageSize.A4,20,20,30,30); PdfWriter instance = PdfWriter.getInstance(doc, out); //③打开文档。 doc.open(); //创建字体 BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED); //字体对象 //大小为10的正常字体 Font size10font = new Font(baseFont,10,Font.NORMAL); //大小为12的粗体 Font size14bold = new Font(baseFont,12,Font.BOLD); // 准备工作结束,进行文档内容填充: // 第一页,结算汇总信息 doc.newPage(); doc.add(new Paragraph("暂无数据",size10font)); doc.close(); instance.close(); } /** * * @param str 字符串 * @param font 字体 * @param high 表格高度 * @Param alignCenter 是否水平居中 * @Param alignMidde 是否垂直居中 * @return */ public static PdfPCell mircoSoftFont(String str,Font font,int high,boolean alignCenter,boolean alignMidde){ PdfPCell pdfPCell = new PdfPCell(new Phrase(str,font)); pdfPCell.setMinimumHeight(high); // 设置可以居中 pdfPCell.setUseAscender(true); if (alignCenter){ // 设置水平居中 pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); } if (alignMidde){ // 设置垂直居中 pdfPCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); } return pdfPCell; } /** * 给pdf文件添加水印 * * @param InPdfFile * 要加水印的原pdf文件路径 * @param outPdfFile * 加了水印后要输出的路径 * @param readpicturepath * 水印图片路径 * @throws Exception */ public static void addPdfMark(String InPdfFile, String outPdfFile, String readpicturepath) throws Exception { PdfReader reader = new PdfReader(InPdfFile); int pageSize = reader.getNumberOfPages(); PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(outPdfFile)); // 插入水印 Image img = Image.getInstance(readpicturepath); img.setAbsolutePosition(0, 0); for (int i = 1; i <= pageSize; i++) { PdfContentByte under = stamp.getUnderContent(i); under.addImage(img); } stamp.close();// 关闭 File tempfile = new File(InPdfFile); if (tempfile.exists()) { tempfile.delete(); } } /** * paragraph的格式 * @param paragraph * @param doc * @throws DocumentException */ public static void geshi1(Paragraph paragraph, Document doc) throws DocumentException {// 段落的格式 paragraph.setIndentationLeft(30); paragraph.setIndentationRight(30); paragraph.setFirstLineIndent(20f); paragraph.setSpacingAfter(10f); paragraph.setSpacingBefore(10f); doc.add(paragraph); } /** * 居中无边框的cell * @param cell * @param table * @throws DocumentException */ public static void geshi2(PdfPCell cell, PdfPTable table) throws DocumentException {// 表格的格式 cell.setBorder(PdfPCell.NO_BORDER); cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER); table.addCell(cell); } // 不居中无边框的cell public static void geshi12(PdfPCell cell, PdfPTable table) throws DocumentException {// 表格的格式 cell.setBorder(PdfPCell.NO_BORDER); table.addCell(cell); } // 居中有边框的cell public static void geshi22(PdfPCell cell, PdfPTable table) throws DocumentException {// 表格的格式 cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER); table.addCell(cell); } // 居中有边框的cell public static void geshi32(PdfPCell cell, PdfPTable table) throws DocumentException {// 表格的格式 cell.setColspan(3); cell.setBorder(0); table.addCell(cell); } /** * * TODO 文档打开时创建模板 * * @see com.itextpdf.text.pdf.PdfPageEventHelper#onOpenDocument(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document) */ @Override public void onOpenDocument(PdfWriter writer, Document document) { // 共 页 的矩形的长宽高 total = writer.getDirectContent().createTemplate(50, 50); } /** * * Creates a new instance of PdfReportM1HeaderFooter 构造方法. * @param yeMei * 页眉字符串 * @param presentFontSize * 数据体字体大小 * @param pageSize */ public PdfUtil(String yeMei, int presentFontSize, Rectangle pageSize) { this.header = yeMei; this.presentFontSize = presentFontSize; this.pageSize = pageSize; } /** * * TODO 关闭每页的时候,写入页眉,写入'第几页'这几个字。 * * @see com.itextpdf.text.pdf.PdfPageEventHelper#onEndPage(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document) */ @Override public void onEndPage(PdfWriter writer, Document document) { try { if (bf == null) { bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); } if (fontDetail == null) { // 数据体字体 fontDetail = new Font(bf, presentFontSize, Font.NORMAL); } } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // 1.写入页眉 ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_CENTER, new Phrase(header, fontDetail), document.getPageSize().getRight()/2, document.getPageSize().getTop()-36, 0); // 没有需要写页脚的需求,先注释起来,后续有需求再调试 // 2.写入前半部分的 第 X页/共 // int pageS = writer.getPageNumber(); // String foot1 = "第 " + pageS + " 页"; // Phrase footer = new Phrase(foot1, fontDetail); // // 3.计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len // float len = bf.getWidthPoint(foot1, presentFontSize); // // 4.拿到当前的PdfContentByte // PdfContentByte cb = writer.getDirectContent(); // // 5.写入页脚1,x轴就是(右margin+左margin + right() -left()- len)/2.0F 再给偏移20F适合人类视觉感受,否则肉眼看上去就太偏左了 ,y轴就是底边界-20,否则就贴边重叠到数据体里了就不是页脚了;注意Y轴是从下往上累加的,最上方的Top值是大于Bottom好几百开外的。 // ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, footer, (document.rightMargin() + document.right() + document.leftMargin() - document.left() - len) / 2.0F + 20F, document.bottom() - 20, 0); // // // 6.写入页脚2的模板(就是页脚的Y页这俩字)添加到文档中,计算模板的和Y轴,X=(右边界-左边界 - 前半部分的len值)/2.0F + len , y 轴和之前的保持一致,底边界-20 // // 调节模版显示的位置 // cb.addTemplate(total, (document.rightMargin() + document.right() + document.leftMargin() - document.left()) / 2.0F + 20F, document.bottom() - 20); } /** * * TODO 关闭文档时,替换模板,完成整个页眉页脚组件 * * @see com.itextpdf.text.pdf.PdfPageEventHelper#onCloseDocument(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document) */ @Override public void onCloseDocument(PdfWriter writer, Document document) { // 7.最后一步了,就是关闭文档的时候,将模板替换成实际的 Y 值,至此,page x of y 制作完毕,完美兼容各种文档size。 total.beginText(); // 生成的模版的字体、颜色 total.setFontAndSize(bf, presentFontSize); String foot2 = " " + (writer.getPageNumber() - 1) + " 页"; // 模版显示的内容 total.showText(foot2); total.endText(); total.closePath(); } // 插入图片 public static void addpicture(PdfPTable table, Image image, String picpath, PdfPCell cell, Document doc) throws MalformedURLException, IOException, DocumentException { image = Image.getInstance(picpath); cell = new PdfPCell(image); geshi2(cell, table); doc.add(table); } //非空判断 public static String isnull(Object a) { if (a != null && a != "" && a != "null" && a.toString() != "null") { return a.toString(); } else { return ""; } } }

    生成pdfcontroller和实现方法:

    @RequestMapping("/exportSettleFile") @ResponseBody public void exportSettleFile(ExportOrderRequest request,HttpSession session,HttpServletResponse httpResponse) throws IOException, DocumentException { if (RoleType.MERCHANT.getCode().equals(request.getRole())){ User loginUser = UserUtil.getCurrentLoginUser(session); request.setMerchantName(loginUser.getCorporateName()); } List<StationBillRecord> stationBillRecords = new ArrayList<>(); List<AsOperator> asOperators = asOperatorRepository.queryByMerchantName(request.getMerchantName()); request.setMerchantId(asOperators.get(0).getMerchantId()); List<AsOrder> asOrders = orderRepository.queryOrderForExport(request); ExportSettleResponse response = buildBillRecord(asOrders); if (response.getMsg() != null){ log.error(request.getMerchantName() + request.getBillBeginDate() + "~" + request.getBillEndDate() + response.getMsg()); PdfUtil.nullPdf(httpResponse.getOutputStream()); return; } response.getBillRecordResponse().setBillDate(request.getBillBeginDate() + "~" + request.getBillEndDate()); for (StationBillRecordBO stationBillRecordBO : response.getStationBillRecordBOList()) { StationBillRecord stationBillRecord = new StationBillRecord(); BeanUtils.copyProperties(stationBillRecordBO,stationBillRecord); stationBillRecords.add(stationBillRecord); } try { generateDeepthCreditReport(response.getBillRecordResponse(),stationBillRecords,httpResponse.getOutputStream()); }catch (Exception ex){ PdfUtil.nullPdf(httpResponse.getOutputStream()); log.error("汇总导出异常:" + ex.getMessage()); } } private void generateDeepthCreditReport(SettleBillRecordResponse billRecordResponse, List<StationBillRecord> stationBillRecordList, OutputStream out) throws MalformedURLException, IOException, DocumentException { //①建立Document对象的实例。 Document doc = new Document(PageSize.A4,20,20,30,30); //②建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。 PdfWriter instance = PdfWriter.getInstance(doc, out); PdfUtil pdfUtil = new PdfUtil("xxx", 15, PageSize.A4); //③打开文档。 doc.open(); pdfUtil.onOpenDocument(instance,doc); //创建字体 BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED); //字体对象 //大小为10的正常字体 Font size10font = new Font(baseFont,10,Font.NORMAL); //大小为12的粗体 Font size12font = new Font(baseFont,12,Font.NORMAL); // 准备工作结束,进行文档内容填充: // 第一页,结算汇总信息 doc.newPage(); doc.add(new Paragraph(" ",size10font)); doc.add(new Paragraph(" ",size10font)); doc.add(new Paragraph(" ",size10font)); // 第一行标题 String title = billRecordResponse.getOperName() + "汇总结算单(" + billRecordResponse.getBillDate() + ")"; PdfPTable table = new PdfPTable(8); PdfPCell cell1 = new PdfPCell(new Phrase(title,size12font)); cell1.setMinimumHeight(50); cell1.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); cell1.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); cell1.setColspan(8); table.addCell(cell1); doc.add(table); // 第二行 PdfPTable secondRowTable = new PdfPTable(8); secondRowTable.addCell(PdfUtil.mircoSoftFont("分润模式",size10font,40,true,true)); secondRowTable.addCell(PdfUtil.mircoSoftFont("分润比例(%)",size10font,40,true,true)); secondRowTable.addCell(PdfUtil.mircoSoftFont("充电服务费(元)",size10font,40,true,true)); secondRowTable.addCell(PdfUtil.mircoSoftFont("充电电费(元)",size10font,40,true,true)); secondRowTable.addCell(PdfUtil.mircoSoftFont("结算金额(元)",size10font,40,true,true)); secondRowTable.addCell(PdfUtil.mircoSoftFont("总电量(kWh)",size10font,40,true,true)); secondRowTable.addCell(PdfUtil.mircoSoftFont("直流电量(kWh)",size10font,40,true,true)); secondRowTable.addCell(PdfUtil.mircoSoftFont("交流电量(kWh)",size10font,40,true,true)); doc.add(secondRowTable); // 第三行 PdfPTable thirdRowTable = new PdfPTable(8); //第四行 PdfPTable fourthRowTable = new PdfPTable(8); fourthRowTable.addCell(PdfUtil.mircoSoftFont("备注",size10font,40,true,true)); String remark; PdfPCell remarkCell = null; thirdRowTable.addCell(PdfUtil.mircoSoftFont(DivideModeEnum.getByCode(billRecordResponse.getDivideMode()).getMessage(),size10font,40,true,true)); //分润比例展示 if (DivideModeEnum.M1.getMessage().equals(DivideModeEnum.getByCode(billRecordResponse.getDivideMode()).getMessage())){ //按电量分成 String ratio = "直流(" + billRecordResponse.getDcChargeAttr() + ")\n" + "交流(" + billRecordResponse.getAcChargeAttr() + ")"; remark = "直流电量*直流分润系数+交流电量*交流分润系数=结算金额"; remarkCell = new PdfPCell(new Phrase(remark,size10font)); thirdRowTable.addCell(PdfUtil.mircoSoftFont(ratio,size10font,40,true,true)); } else if (DivideModeEnum.M2.getMessage().equals(DivideModeEnum.getByCode(billRecordResponse.getDivideMode()).getMessage())){ //按服务费分成 if ("0".equals(billRecordResponse.getDivideRatioElc()) || "0.00".equals(billRecordResponse.getDivideRatioElc())){ thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getDivideRatio(),size10font,40,true,true)); remark = "充电服务费*分润比例=结算金额"; remarkCell = new PdfPCell(new Phrase(remark,size10font)); }else if ("100".equals(billRecordResponse.getDivideRatioElc()) || "100.00".equals(billRecordResponse.getDivideRatioElc())){ thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getDivideRatio(),size10font,40,true,true)); remark = "充电服务费*分润比例+充电电费=结算金额"; remarkCell = new PdfPCell(new Phrase(remark,size10font)); }else { String ratio = "充电服务费(" + billRecordResponse.getDivideRatio() + ")\n" + "充电电费(" + billRecordResponse.getDivideRatioElc() + ")"; thirdRowTable.addCell(PdfUtil.mircoSoftFont(ratio,size10font,40,true,true)); remark = "充电服务费*服务费分润比例+充电电费*充电电费分润比例=结算金额"; remarkCell = new PdfPCell(new Phrase(remark,size10font)); } }else { thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getDivideRatio(),size10font,40,true,true)); remark = "(充电服务费+充电电费)*分润比例=结算金额"; remarkCell = new PdfPCell(new Phrase(remark,size10font)); } thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getServiceAmt(),size10font,40,true,true)); thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getChargeAmt(),size10font,40,true,true)); thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getBillAmt(),size10font,40,true,true)); thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getChargeEq(),size10font,40,true,true)); thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getDcChargeEq(),size10font,40,true,true)); thirdRowTable.addCell(PdfUtil.mircoSoftFont(billRecordResponse.getAcChargeEq(),size10font,40,true,true)); doc.add(thirdRowTable); remarkCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); remarkCell.setColspan(7); fourthRowTable.addCell(remarkCell); doc.add(fourthRowTable); //第五行,盖章 PdfPTable fifRowTable = new PdfPTable(8); PdfPCell gzCell1 = new PdfPCell(new Phrase("xxx\n(盖章)",size10font)); gzCell1.setMinimumHeight(50); gzCell1.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); gzCell1.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); gzCell1.setBorder(PdfCell.NO_BORDER); gzCell1.setColspan(4); fifRowTable.addCell(gzCell1); PdfPCell gzCell2 = new PdfPCell(new Phrase(billRecordResponse.getOperName() + "\n(盖章)",size10font)); gzCell2.setMinimumHeight(50); gzCell2.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); gzCell2.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); gzCell2.setBorder(PdfCell.NO_BORDER); gzCell2.setColspan(4); fifRowTable.addCell(gzCell2); doc.add(fifRowTable); pdfUtil.onEndPage(instance,doc); // 第二页 doc.newPage(); pdfUtil.onEndPage(instance,doc); //空行 doc.add(new Paragraph(" ",size10font)); doc.add(new Paragraph(" ",size10font)); doc.add(new Paragraph(" ",size10font)); PdfPTable stationTable = new PdfPTable(8); String stationTitle = billRecordResponse.getOperName() + "场站结算记录(" + billRecordResponse.getBillDate() + ")"; PdfPCell stationCell = new PdfPCell(new Phrase(stationTitle,size12font)); stationCell.setMinimumHeight(50); stationCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); stationCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); stationCell.setColspan(10); stationTable.addCell(stationCell); doc.add(stationTable); //第二行 PdfPTable stationSecondRowTable = new PdfPTable(9); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("充电站",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("充电服务费(元)",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("充电电费(元)",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("结算金额",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("订单数",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("总电量(kWh)",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("直流充电电量(kWh)",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("交流充电电量(kWh)",size10font,40,true,true)); stationSecondRowTable.addCell(PdfUtil.mircoSoftFont("优惠金额(元)",size10font,40,true,true)); doc.add(stationSecondRowTable); //循环插入场站清分信息 for (StationBillRecord stationBillRecord : stationBillRecordList) { PdfPTable tempTable = new PdfPTable(9); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getStationName(),size10font,40,true,true)); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getSrvAmt(),size10font,40,true,true)); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getElecAmt(),size10font,40,true,true)); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getSettleAmt(),size10font,40,true,true)); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getOrderNum(),size10font,40,true,true)); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getChargePq(),size10font,40,true,true)); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getZlPq(),size10font,40,true,true)); tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getJlPq(),size10font,40,true,true)); if (stationBillRecord.getDiscountAmt() != null && stationBillRecord.getDiscountAmt() != ""){ tempTable.addCell(PdfUtil.mircoSoftFont(stationBillRecord.getDiscountAmt(),size10font,40,true,true)); }else{ tempTable.addCell(PdfUtil.mircoSoftFont("",size10font,40,true,true)); } doc.add(tempTable); } doc.close(); instance.close(); }

    数据有点复杂。写pdf有点费劲,目前满足需求了,还有比如水印或者页脚页数一类的功能,有需要再补充

    参考文章: itext5: https://blog.csdn.net/u010067848/article/details/98203746

    itext2.1.5 https://blog.csdn.net/Soulmate_Min/article/details/81707918?utm_source=app

    Processed: 0.020, SQL: 9