Java整合FreeMarker导出Pdf文件

cnblogs 2024-06-12 09:09:00 阅读 67

引入依赖

<!--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() {

@Override

public 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;

}

})) {

@Override

public 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() {

@Override

public String getImageRootPath() {

return StringUtils.EMPTY;

}

@Override

public 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;

}

@Data

public 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;

}

}

模板及样式

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8" />

<title>测试流程表格文件</title>

<link type="text/css" rel="stylesheet" href="style/testCreatePdf.css"/>

<style/>

</head>

<body>

<div >

<div >

<div >测试流程表格文件</div>

</div>

<div >

<div >${companyName}</div>

<div ></div>

<table >

<tr>

<td >字段1</td>

<td >${field1}</td>

<td >字段2</td>

<td >${field2}</td>

<td >字段3</td>

<td >${field4}</td>

</tr>

<tr>

<td >字段4</td>

<td>${field4}</td>

<td >字段5</td>

<td>${field5}</td>

<td >字段6</td>

<td>${field6}</td>

</tr>

<tr>

<td >字段7</td>

<td colspan="5">${field7}</td>

</tr>

</table>

<table >

<thead>

<tr>

<#list modeDetailFieldList as obj >

<td>${obj}</td>

</#list>

</tr>

</thead>

<tbody>

<#list modeDetailValueList as detailValue >

<tr>

<#list detailValue as obj >

<td>${obj}</td>

</#list>

</tr>

</#list>

</tbody>

</table>

<div ></div>

<div >

<div >${remark}</div>

</div>

<div ></div>

<div >

<div >

<div >签字1:</div>

<div ></div>

<div >

<div ></div>

<div >

<img width="100%" height="100%" src="${signUrl1}" />

</div>

</div>

<div >时间:${signTime1}</div>

</div>

<div >

<div >签字2:</div>

<div ></div>

<div >

<div ></div>

<div >

<img width="100%" height="100%" src="${signUrl2}" />

</div>

</div>

<div >时间:${signTime3}</div>

</div>

<div >

<div >签字3:</div>

<div ></div>

<div >

<div ></div>

<div >

<img width="100%" height="100%" src="${signUrl3}" />

</div>

</div>

<div >时间:${signTime3}</div>

</div>

</div>

</div>

</div>

</body>

</html>

body {

width: 100%;

height: 100%;

margin: 0;

padding: 0;

}

html {

width: 100%;

height: 100%;

margin: 0;

padding: 0;

}

.app-container {

padding: 20px;

}

.app-container-top {

width:100%;

height:100px;

}

.nbsp-5 {

width: 100%;

height: 5px;

}

.nbsp-10 {

width: 100%;

height: 10px;

}

.nbsp-20 {

width: 100%;

height: 20px;

}

.nbsp-40 {

width: 100%;

height: 40px;

}

.nbsp-width {

height:300px;

float:left;

}

.nbsp-div {

width: 35%;

height: 100px;

float: left;

}

.logo-div {

width: 200px;

float: left;

height: 100px;

}

.logo-div img {

width: 200px;

height:40px;

margin: 10px 0 20px 0;

}

.qrcode-div {

width: 100px;

display: flex;

height: 100px;

float: right;

}

.qrcode-div img {

width: 100px;

object-fit: cover;

}

.title {

font-size: 30px;

height: 100px;

float: left;

text-align: center;

}

.park-label {

font-size: 20px;

height: 50px;

float: left;

}

.app-container-body {

margin-top: 5px;

}

table tr td {

height:40px;

}

.baseInfo-table {

width: 100%;

margin: 0 auto;

border: 1px solid #9D9D9D;

border-collapse: collapse;

}

.baseInfo-table tr td {

border: 1px solid #9D9D9D;

text-align: center;

}

.mode-table {

width: 100%;

margin: 10px 0 auto;

border: 1px solid #9D9D9D;

border-collapse: collapse;

}

.mode-table tr td {

border: 1px solid #9D9D9D;

text-align: center;

}

.mode-table tr td:nth-child(1) {

width: 50px;

}

.mode-table tr td:nth-child(2) {

width: 150px;

}

.mode-table tr td:nth-child(4) {

width: 80px;

}

.mode-table tr td:nth-child(8) {

width: 100px;

}

.mode-table tr td:nth-child(9) {

width: 100px;

}

.test-table {

margin-top: 10px;

width: 100%;

margin: 50px 0 auto;

border: 1px solid #9D9D9D;

border-collapse: collapse;

}

.test-table tr td {

border: 1px solid #9D9D9D;

text-align: center;

}

.test-table tr td:nth-child(1) {

width: 30px;

height: 60px;

}

.test-table tr td:nth-child(2) {

width: 120px;

}

.sign {

width: calc(100% - 80px);

margin: 40px;

}

.sign-div {

width: 50%;

height: 450px;

float: left;

text-align: left;

}

.sign-div-seal {

width: 325px;

height: 450px;

float: left;

text-align: left;

}

.sign-img {

height: 300px;

}

.sign-div-title {

text-align: left;

}

.sign-time {

margin-top: 10px;

text-align: left

}

效果图



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。