您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)怎么在Java項(xiàng)目中實(shí)現(xiàn)一個生成pdf文件或jpg圖片的功能,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
1、Maven導(dǎo)包
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent> <dependencies> <!-- freemarker --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency> <!-- pdf核心包 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.12</version> </dependency> <!-- 適配中文字體 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <!-- html轉(zhuǎn)pdf --> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.12</version> </dependency> <!-- pdf轉(zhuǎn)圖片 --> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.5</version> </dependency> </dependencies>
2.1、請求
@Data public class GeneratePdfReq { /** * 生成pdf文件的絕對路徑 */ @NotBlank(message = "生成pdf文件的絕對路徑不能為空") @Pattern(regexp = "^.*(\\.pdf|\\.jpg)$", message = "生成的文件必須以.pdf或.jpg結(jié)尾") private String absolutePath; /** * 使用html模板的絕對路徑 */ @NotBlank(message = "使用的模板路徑不能為空") private String templateName; /** * 渲染模板的業(yè)務(wù)數(shù)據(jù) */ private Object dataModel; /** * 水印信息 */ private WaterMarkInfo waterMarkInfo; /** * pdf文件的寬,默認(rèn)A4 */ private float width = 595; /** * pdf文件的高,默認(rèn)A4 */ private float height = 842; }
2.2、水印
@Data public class WaterMarkInfo { /** * 如果為null設(shè)置水印時會報(bào)錯 */ private String waterMark = ""; /** * 水印透明度,值越小透明度越高 */ private float opacity = 0.2F; /** * 水印字體,如果亂碼設(shè)置為本地宋體字體:fonts/simsun.ttc,1 */ private String fontName = "STSong-Light"; /** * 水印編碼格式,如果亂碼設(shè)置為:BaseFont.IDENTITY_H */ private String encoding = "UniGB-UCS2-H"; /** * 字體大小 */ private float fontSize = 24; /** * 橫坐標(biāo)在頁面寬度的百分比,左下角為原點(diǎn) */ private float x = 50; /** * 縱坐標(biāo)在頁面高度的百分比,左下角為原點(diǎn) */ private float y = 40; /** * 水印旋轉(zhuǎn)角度 */ private float rotation = 45; }
2.3、響應(yīng)
@Data public class GeneratePdfResp { /** * 生成pdf的絕對路徑 */ private String absolutePath; }
3.1、渲染freemarker模板獲取html網(wǎng)頁
@Service("freeMarkerService") @Slf4j public class FreeMarkerServiceImpl implements FreeMarkerService { @Autowired private FreeMarkerConfigurer freeMarkerConfigurer; /** * 渲染html后獲取整個頁面內(nèi)容 * * @param templatePath 模板路徑 * @param dataModel 業(yè)務(wù)數(shù)據(jù),一般以map形式傳入 * @return */ @Override public String getHtml(String templatePath, Object dataModel) { log.info("開始將模板{}渲染為html,業(yè)務(wù)數(shù)據(jù){}", templatePath, JSONUtil.toJsonPrettyStr(dataModel)); Configuration cfg = freeMarkerConfigurer.getConfiguration(); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // freemaker異常時仍舊拋出,統(tǒng)一異常處理 cfg.setClassicCompatible(true);// 不需要對null值預(yù)處理,否則需要在模板取值時判斷是否存在,不然報(bào)錯 StringWriter stringWriter = new StringWriter(); try { // 設(shè)置模板所在目錄,絕對路徑方式,不打進(jìn)jar包 // cfg.setDirectoryForTemplateLoading(new File(templatePath).getParentFile()); // Template temp = cfg.getTemplate(new File(templatePath).getName()); // 相對路徑設(shè)置模板所在目錄,模板打進(jìn)jar包,默認(rèn)就是resources目錄下的/templates目錄。 cfg.setClassForTemplateLoading(this.getClass(), "/templates"); Template temp = cfg.getTemplate(templatePath); temp.process(dataModel, stringWriter); } catch (Exception e) { log.error(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL.getDesc(), e); throw new PdfBizException(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL); } return stringWriter.toString(); } }
3.2、將html網(wǎng)頁轉(zhuǎn)pdf,并添加水印
@Service("pdfService") @Slf4j public class PdfServiceImpl implements PdfService { public static final String FONT_PATH = "fonts/simsun.ttc,1"; @Autowired private WaterMarkerService waterMarkerService; /** * html頁面內(nèi)容轉(zhuǎn)pdf,并給每頁附上水印 * * @param html html頁面內(nèi)容 * @param width pdf的寬 * @param height pdf的高 * @param waterMarkInfo 水印信息 * @return */ @Override public byte[] html2Pdf(String html, float width, float height, WaterMarkInfo waterMarkInfo) { log.info("=================開始將html轉(zhuǎn)換為pdf================="); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.html2Pdf(html, width, height, out); byte[] bytes = out.toByteArray(); // 設(shè)置水印 if (waterMarkInfo != null) { bytes = waterMarkerService.addWaterMarker(bytes, waterMarkInfo); } return bytes; } /** * html轉(zhuǎn)pdf * * @param html html頁面內(nèi)容 * @param width pdf的寬 * @param height pdf的高 * @param out 輸出流,pdf文件用此流輸出,需要pdf文檔關(guān)閉后流中才會有數(shù)據(jù) */ @Override @SneakyThrows public void html2Pdf(String html, float width, float height, OutputStream out) { @Cleanup Document document = new Document(new RectangleReadOnly(width, height)); // 默認(rèn)A4縱向 // 這里需要關(guān)閉document才能讓生成的pdf字節(jié)數(shù)據(jù)刷到輸出流中 PdfWriter writer = PdfWriter.getInstance(document, out); // 關(guān)閉可能導(dǎo)致生成的pdf顯示異常(Chrome) document.open(); // 設(shè)置字體,這里統(tǒng)一用simsun.ttc即宋體 XMLWorkerFontProvider asianFontProvider = new XMLWorkerFontProvider() { @Override public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color, boolean cached) { Font font; try { font = new Font(BaseFont.createFont(FONT_PATH, BaseFont.IDENTITY_H, BaseFont.EMBEDDED)); } catch (Exception e) { log.error(PdfErrorCode.SET_PDF_FONT_FAIL.getDesc(), e); throw new PdfBizException(PdfErrorCode.SET_PDF_FONT_FAIL); } font.setStyle(style); font.setColor(color); if (size > 0) { font.setSize(size); } return font; } }; // 生成pdf try { XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"), asianFontProvider); // 如果系統(tǒng)已經(jīng)裝有simsun.ttc字體,則不需要單獨(dú)設(shè)置字體也不需要itext-asian jar包 // XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8")); } catch (RuntimeWorkerException e) { log.error(PdfErrorCode.HTML_CONVERT2PDF_FAIL.getDesc(), e); throw new PdfBizException(PdfErrorCode.HTML_CONVERT2PDF_FAIL); } } }
添加水印實(shí)現(xiàn)類
@Service("waterMarkerService") @Slf4j public class WaterMarkerServiceImpl implements WaterMarkerService { /** * 給pdf文件每頁添加水印 * * @param source pdf文件的字節(jié)數(shù)組形式 * @param waterMarkInfo 水印信息 * @return */ @Override public byte[] addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo) { log.info("開始設(shè)置水印數(shù)據(jù){}", JSONUtil.toJsonPrettyStr(waterMarkInfo)); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.addWaterMarker(source, waterMarkInfo, out); return out.toByteArray(); } /** * 給pdf文件每頁添加水印 * * @param source pdf文件的字節(jié)數(shù)組形式 * @param waterMarkInfo 水印信息 * @param out 輸出流,pdf文件用此流輸出,需要pdf文檔關(guān)閉后流中才會有數(shù)據(jù) */ @Override @SneakyThrows public void addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo, OutputStream out) { @Cleanup PdfReader reader = new PdfReader(source); // 這里需要關(guān)閉PdfStamper才能讓生成的pdf字節(jié)數(shù)據(jù)刷到輸出流中 @Cleanup PdfStamper pdfStamper = new PdfStamper(reader, out); BaseFont font = BaseFont.createFont(waterMarkInfo.getFontName(), waterMarkInfo.getEncoding(), BaseFont.EMBEDDED); PdfGState gs = new PdfGState(); gs.setFillOpacity(waterMarkInfo.getOpacity()); // 給每頁pdf生成水印 for (int i = 1; i <= reader.getNumberOfPages(); i++) { PdfContentByte waterMarker = pdfStamper.getUnderContent(i); waterMarker.beginText(); // 設(shè)置水印透明度 waterMarker.setGState(gs); // 設(shè)置水印字體和大小 waterMarker.setFontAndSize(font, waterMarkInfo.getFontSize()); // 設(shè)置水印位置、內(nèi)容、旋轉(zhuǎn)角度 float X = reader.getPageSize(i).getWidth() * waterMarkInfo.getX() / 100; float Y = reader.getPageSize(i).getHeight() * waterMarkInfo.getY() / 100; waterMarker.showTextAligned(Element.ALIGN_CENTER, waterMarkInfo.getWaterMark(), X, Y, waterMarkInfo.getRotation()); // 設(shè)置水印顏色 waterMarker.setColorFill(BaseColor.GRAY); waterMarker.endText(); } } }
3.3、整合實(shí)現(xiàn)
@Slf4j @Service("generatePdfService") public class GeneratePdfServiceImpl implements RestService { @Autowired private FreeMarkerService freeMarkerService; @Autowired private PdfService pdfService; @Override @SneakyThrows public GeneratePdfResp service(GeneratePdfReq generatePdfReq) { log.info("開始生成pdf文件,請求報(bào)文:{}", JSONUtil.toJsonPrettyStr(generatePdfReq)); /* 1.根據(jù)freemarker模板填充業(yè)務(wù)數(shù)據(jù)獲取完整的html字符串 */ String html = freeMarkerService.getHtml(generatePdfReq.getTemplateName(), generatePdfReq.getDataModel()); /* 2.生成pdf文件(內(nèi)存) */ byte[] bytes = pdfService.html2Pdf(html, generatePdfReq.getWidth(), generatePdfReq.getHeight(), generatePdfReq.getWaterMarkInfo()); /* 3.本地保存pdf文件 */ File targetFile = new File(generatePdfReq.getAbsolutePath()); // 上級目錄不存在則創(chuàng)建 if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } // 根據(jù)不同文件名后綴生成對應(yīng)文件 if (generatePdfReq.getAbsolutePath().endsWith("pdf")) { FileUtils.writeByteArrayToFile(targetFile, bytes); } else { @Cleanup PDDocument document = PDDocument.load(bytes); PDFRenderer renderer = new PDFRenderer(document); BufferedImage bufferedImage = renderer.renderImageWithDPI(0, 150);// 只打第一頁,dpi越大圖片越高清也越耗時 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(bufferedImage, "jpg", baos); FileUtils.writeByteArrayToFile(targetFile, baos.toByteArray()); } log.info("文件本地保存完成,文件路徑:[{}]", targetFile.getAbsolutePath()); /* 4.組織返回 */ GeneratePdfResp generatePdfResp = new GeneratePdfResp(); generatePdfResp.setAbsolutePath(targetFile.getAbsolutePath()); return generatePdfResp; } }
3.4、controller
@Slf4j @RestController public class PdfController { @Autowired private RestService generatePdfService; @PostMapping(value = "/html2Pdf") public GeneratePdfResp html2Pdf(@RequestBody @Validated GeneratePdfReq req) { GeneratePdfResp resp = generatePdfService.service(req); return resp; } }
4.1、freemarker模板(html模板)
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-Style-Type" content="text/css"/> <style> body { font-family: SimSun } </style> <title>html模板</title> </head> <body> <div> <p > <span >html模板</span><br/> </p> <p>姓名:${name}</p> <p>證件號碼:${cardNo}</p> <p>日期:${date}</p> </div> </body> </html>
4.2、接口調(diào)用生成pdf
1、根據(jù)參數(shù)后綴名可以生成pdf或jpg文件,生成的pdf文件默認(rèn)為A4大小,也可以通過請求參數(shù)設(shè)置大小。
2、pdf文件會根據(jù)html模板內(nèi)容大小自動分頁。
3、如果生成圖片,多頁不會生成多張圖片,可以把高度設(shè)置大一些,最后會生成長圖。
4、水印每頁都會自動添加。
5、為了提高代碼的復(fù)用性和可維護(hù)性,工程內(nèi)渲染html模板、生成pdf文件、添加水印都有單獨(dú)的接口實(shí)現(xiàn)。
看完上述內(nèi)容,你們對怎么在Java項(xiàng)目中實(shí)現(xiàn)一個生成pdf文件或jpg圖片的功能有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。