Java生成pdf文件或jpg图片
|字数总计:2.3k|阅读时长:10分钟|阅读量:
个人博客
在一些业务场景中,需要生成pdf
文件或者jpg
图片,有时候还需要带上水印。我们可以事先用freemarker
定义好html
模板,然后把模板转换成pdf
或jpg
文件。
同时freemarker
模板还支持变量的定义,在使用时可以填充具体的业务数据。
1、Maven导包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency> <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> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.12</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.5</version> </dependency> </dependencies>
|
2、接口定义
2.1、请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Data public class GeneratePdfReq {
@NotBlank(message = "生成pdf文件的绝对路径不能为空") @Pattern(regexp = "^.*(\\.pdf|\\.jpg)$", message = "生成的文件必须以.pdf或.jpg结尾") private String absolutePath;
@NotBlank(message = "使用的模板路径不能为空") private String templateName;
private Object dataModel;
private WaterMarkInfo waterMarkInfo;
private float width = 595;
private float height = 842; }
|
2.2、水印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @Data public class WaterMarkInfo {
private String waterMark = "";
private float opacity = 0.2F;
private String fontName = "STSong-Light";
private String encoding = "UniGB-UCS2-H";
private float fontSize = 24;
private float x = 50;
private float y = 40;
private float rotation = 45; }
|
2.3、响应
1 2 3 4 5 6 7
| @Data public class GeneratePdfResp {
private String absolutePath; }
|
3、应用代码
3.1、渲染freemarker
模板获取html
网页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Service("freeMarkerService") @Slf4j public class FreeMarkerServiceImpl implements FreeMarkerService { @Autowired private FreeMarkerConfigurer freeMarkerConfigurer;
@Override public String getHtml(String templatePath, Object dataModel) { log.info("开始将模板{}渲染为html,业务数据{}", templatePath, JSONUtil.toJsonPrettyStr(dataModel)); Configuration cfg = freeMarkerConfigurer.getConfiguration(); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setClassicCompatible(true); StringWriter stringWriter = new StringWriter(); try {
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网页转pdf,并添加水印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| @Service("pdfService") @Slf4j public class PdfServiceImpl implements PdfService { public static final String FONT_PATH = "fonts/simsun.ttc,1";
@Autowired private WaterMarkerService waterMarkerService;
@Override public byte[] html2Pdf(String html, float width, float height, WaterMarkInfo waterMarkInfo) { log.info("=================开始将html转换为pdf================="); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.html2Pdf(html, width, height, out); byte[] bytes = out.toByteArray(); if (waterMarkInfo != null) { bytes = waterMarkerService.addWaterMarker(bytes, waterMarkInfo); } return bytes; }
@Override @SneakyThrows public void html2Pdf(String html, float width, float height, OutputStream out) { @Cleanup Document document = new Document(new RectangleReadOnly(width, height)); PdfWriter writer = PdfWriter.getInstance(document, out); document.open(); 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; } };
try { XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"), asianFontProvider);
} catch (RuntimeWorkerException e) { log.error(PdfErrorCode.HTML_CONVERT2PDF_FAIL.getDesc(), e); throw new PdfBizException(PdfErrorCode.HTML_CONVERT2PDF_FAIL); } } }
|
添加水印实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| @Service("waterMarkerService") @Slf4j public class WaterMarkerServiceImpl implements WaterMarkerService {
@Override public byte[] addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo) { log.info("开始设置水印数据{}", JSONUtil.toJsonPrettyStr(waterMarkInfo)); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.addWaterMarker(source, waterMarkInfo, out); return out.toByteArray(); }
@Override @SneakyThrows public void addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo, OutputStream out) { @Cleanup PdfReader reader = new PdfReader(source); @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()); for (int i = 1; i <= reader.getNumberOfPages(); i++) { PdfContentByte waterMarker = pdfStamper.getUnderContent(i); waterMarker.beginText(); waterMarker.setGState(gs); waterMarker.setFontAndSize(font, waterMarkInfo.getFontSize()); 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()); waterMarker.setColorFill(BaseColor.GRAY); waterMarker.endText(); } } }
|
3.3、整合实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| @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文件,请求报文:{}", JSONUtil.toJsonPrettyStr(generatePdfReq));
String html = freeMarkerService.getHtml(generatePdfReq.getTemplateName(), generatePdfReq.getDataModel());
byte[] bytes = pdfService.html2Pdf(html, generatePdfReq.getWidth(), generatePdfReq.getHeight(), generatePdfReq.getWaterMarkInfo());
File targetFile = new File(generatePdfReq.getAbsolutePath()); if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); }
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); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(bufferedImage, "jpg", baos); FileUtils.writeByteArrayToFile(targetFile, baos.toByteArray()); } log.info("文件本地保存完成,文件路径:[{}]", targetFile.getAbsolutePath());
GeneratePdfResp generatePdfResp = new GeneratePdfResp(); generatePdfResp.setAbsolutePath(targetFile.getAbsolutePath()); return generatePdfResp; } }
|
3.4、controller
1 2 3 4 5 6 7 8 9 10 11 12
| @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、应用
4.1、freemarker
模板(html
模板)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <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 style="margin:0pt; orphans:0; text-align:center; widows:0"> <span style="font-family:SimSun; font-size:16pt">html模板</span><br/> </p> <p>姓名:${name}</p> <p>证件号码:${cardNo}</p> <p>日期:${date}</p> </div> </body> </html>
|
4.2、接口调用生成pdf
5、说明
- 根据参数后缀名可以生成pdf或jpg文件,生成的pdf文件默认为A4大小,也可以通过请求参数设置大小。
- pdf文件会根据html模板内容大小自动分页。
- 如果生成图片,多页不会生成多张图片,可以把高度设置大一些,最后会生成长图。
- 水印每页都会自动添加。
- 为了提高代码的复用性和可维护性,工程内渲染html模板、生成pdf文件、添加水印都有单独的接口实现。
参考链接
代码地址