引入依赖
<!--Freemarker wls--><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.30</version></dependency><dependency><groupId>com.itextpdf.tool</groupId><artifactId>xmlworker</artifactId><version>5.5.11</version></dependency><!-- 支持中文 --><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency><!-- 支持css样式渲染 --><dependency><groupId>org.xhtmlrenderer</groupId><artifactId>flying-saucer-pdf-itext5</artifactId><version>9.1.18</version></dependency><dependency><groupId>gui.ava</groupId><artifactId>html2image</artifactId><version>2.0.1</version></dependency>
代码示例
/*** @author alin* @date 2024-06-11*/
@Slf4j
public class TestCreatePdf {public static void main(String[] args) throws Exception {generatePdfUrl();}/*** 生成pdf** @return*/public static String generatePdfUrl() throws Exception {// 构造参数Model model = assembleData();return createPdfAndUpload(beanToMap(model), UUID.randomUUID() + ".pdf", "testCreatePdf.html", "testCreatePdf.css");}/*** 创建pdf并上传/输出* * @param data* @param fileName 文件名* @param templateFileName 模板文件名, html模板文件* @param cssPath css文件路径* @return* @throws Exception*/private static String createPdfAndUpload(Map<String, Object> data, String fileName, String templateFileName, String cssPath) throws Exception {ByteArrayOutputStream outputStream = new ByteArrayOutputStream();try {// 根据模板生成html字符串String pdf = createHtmlStr(data, templateFileName);// 通过html字符串生成pdf文件generatePdf(pdf, outputStream, cssPath);} catch (Exception e) {return null;}// 输出/上传至指定位置FileOutputStream out = new FileOutputStream("d:/testPdf/" + fileName);out.write(outputStream.toByteArray());return "d:/testPdf/" + fileName;}/*** bean转map* * @param bean* @return*/public static Map<String, Object> beanToMap(Object bean) {Class<?> clazz = bean.getClass();return Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toMap(Field::getName,field -> {try {field.setAccessible(true);return field.get(bean);} catch (IllegalAccessException e) {// 处理异常return null;}}));}/*** 模板生成html字符串** @param data 数据* @param templateFileName 模板文件名* @throws Exception 捕获异常*/public static String createHtmlStr(Map<String, Object> data, String templateFileName) throws Exception {// 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);// 指定FreeMarker模板文件的位置cfg.setClassForTemplateLoading(TestCreatePdf.class, "/template");// 获取模板文件Template template = cfg.getTemplate(templateFileName, "UTF-8");StringWriter stringWriter = new StringWriter();BufferedWriter writer = new BufferedWriter(stringWriter);template.process(data, writer);String htmlStr = stringWriter.toString();writer.flush();writer.close();return htmlStr;}/*** 通过html字符串生成pdf文件** @param htmlStr* @param out* @param cssPath* @throws IOException* @throws DocumentException*/public static void generatePdf(String htmlStr, OutputStream out, String cssPath) throws IOException, DocumentException {Document document = new Document(PageSize.A3);PdfWriter writer = PdfWriter.getInstance(document, out);document.open();// html内容解析HtmlPipelineContext htmlContext = new HtmlPipelineContext(new CssAppliersImpl(new XMLWorkerFontProvider() {@Overridepublic Font getFont(String fontName, String encoding,float size, final int style) {Font font = null;if (fontName == null) {//字体BaseFont bf;try {bf = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);font = new Font(bf, size, style);} catch (Exception e) {log.error("getFont", e);}}return font;}})) {@Overridepublic HtmlPipelineContext clone()throws CloneNotSupportedException {HtmlPipelineContext context = super.clone();try {ImageProvider imageProvider = this.getImageProvider();context.setImageProvider(imageProvider);} catch (Exception e) {log.error("clone", e);}return context;}};// 图片解析htmlContext.setImageProvider(new AbstractImageProvider() {@Overridepublic String getImageRootPath() {return StringUtils.EMPTY;}@Overridepublic Image retrieve(String src) {if (StringUtils.isEmpty(src)) {return null;}try {int pos = src.indexOf("base64,");try {if (src.startsWith("data") && pos > 0) {byte[] img = Base64.decode(src.substring(pos + 7));return Image.getInstance(img);} else if (src.startsWith("http")) {return Image.getInstance(src);}} catch (Exception ex) {log.error("retrieve", ex);return null;}return null;} catch (Throwable e) {log.error("retrieve", e);}return super.retrieve(src);}});htmlContext.setAcceptUnknown(true).autoBookmark(true).setTagFactory(Tags.getHtmlTagProcessorFactory());// css解析CSSResolver cssResolver = new StyleAttrCSSResolver();InputStream cssInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(cssPath);CssFile cssfile = XMLWorkerHelper.getCSS(cssInputStream);cssResolver.addCss(cssfile);HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer));Pipeline<?> pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);XMLWorker worker = new XMLWorker(pipeline, true);XMLParser parser = new XMLParser(true, worker, StandardCharsets.UTF_8);try (InputStream inputStream = new ByteArrayInputStream(htmlStr.getBytes())) {parser.parse(inputStream, StandardCharsets.UTF_8);}document.close();}/*** 组装数据** @return* @throws Exception*/private static Model assembleData() throws Exception {TestCreatePdf.Model model = new TestCreatePdf.Model();model.setCompanyName("公司名称");model.setField1("字段一");model.setField2("字段二");model.setField3("字段三");model.setField4("字段四");model.setField5("字段五");model.setField6("字段六");model.setField7("字段七");model.setRemark("备注~~~~~~~~~");model.setSignUrl1("D:/testPdf/test.png");model.setSignUrl2("D:/testPdf/test.png");model.setSignUrl3("D:/testPdf/test.png");model.setSignTime1("2024-04-28 17:08:52");model.setSignTime2("2024-04-28 17:08:52");model.setSignTime3("2024-04-28 17:08:52");List<Object> modeDetailFieldList = Lists.newArrayList();modeDetailFieldList.add("表头一");modeDetailFieldList.add("表头二");modeDetailFieldList.add("表头三");modeDetailFieldList.add("表头四");modeDetailFieldList.add("表头五");modeDetailFieldList.add("表头六");model.setModeDetailFieldList(modeDetailFieldList);List<List<Object>> modeDetailValueList = Lists.newArrayList();for (int i = 1; i < 6; i++) {List<Object> valueList = Lists.newArrayList();valueList.add("表头一值--" + i);valueList.add("表头二值--" + i);valueList.add("表头三值--" + i);valueList.add("表头四值--" + i);valueList.add("表头五值--" + i);valueList.add("表头六值--" + i);modeDetailValueList.add(valueList);}model.setModeDetailValueList(modeDetailValueList);return model;}@Datapublic static class Model {/*** companyName*/private String companyName;/*** 字段1*/private String field1;/*** 字段2*/private String field2;/*** 字段3*/private String field3;/*** 字段4*/private String field4;/*** 字段5*/private String field5;/*** 字段6*/private String field6;/*** 字段7*/private String field7;/*** 备注*/private String remark;/*** 图片地址(base64结构)*/private String imgBase64;/*** signUrl1*/private String signUrl1;/*** signUrl2*/private String signUrl2;/*** signUrl3*/private String signUrl3;/*** signTime1*/private String signTime1;/*** signTime2*/private String signTime2;/*** signTime3*/private String signTime3;/*** 表格字段名称*/private List<Object> modeDetailFieldList;/*** 表格字段值*/private List<List<Object>> modeDetailValueList;}}
结果