呼和浩特做网站的,朝阳网站建设 慈云寺,免费的行情网站ifind是,深圳市 交易建设中心网站最近公司有个需求#xff0c;就是想根据一个模板生成一个pdf文档#xff0c;当即我就想到了freemarker这个远古老东西#xff0c;毕竟freemarker在模板渲染方面还是非常有优势的。
准备依赖#xff1a; dependencygroupIdorg.springframework.boot/gr…最近公司有个需求就是想根据一个模板生成一个pdf文档当即我就想到了freemarker这个远古老东西毕竟freemarker在模板渲染方面还是非常有优势的。
准备依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-freemarker/artifactId/dependencydependencygroupIdcom.itextpdf/groupIdartifactIdhtml2pdf/artifactIdversion3.0.5/version/dependency!--pdf 支持中文(默认不支持)--dependencygroupIdcom.itextpdf/groupIdartifactIditext-asian/artifactIdversion5.2.0/version/dependencydependencygroupIdcom.itextpdf/groupIdartifactIditextpdf/artifactIdversion5.5.13/version/dependency我这里不想选freemarker版本直接用spring集成的省事。 配置一下freemarker的配置
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;import java.io.IOException;Component
org.springframework.context.annotation.Configuration
public class FreemarkerConfig {// 我这里为了省事不想创建那么多的Configuration而且创建Configuration太多不好Bean(name cfg)public Configuration freemarkerConfigurer() throws IOException {// 选择版本不同版本对不同的模板语法或者模板转换也会有差异如果你css 样式比较新建议选高版本准没错Configuration cfg new Configuration(Configuration.VERSION_2_3_22);// 选择你存放模板的位置final ClassPathResource classPathResource new ClassPathResource(templates);cfg.setDirectoryForTemplateLoading(classPathResource.getFile());cfg.setDefaultEncoding(UTF-8);// 模板异常处理cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);return cfg;}
}然后我们准备下我们的ftl模板【freemarker的模板文件】----pdf.ftl freemarker框架类似于beetl、thymeleaf、jsp、Velocity等模板引擎 JSP就不用说了吧基本上开发Java的基本上都会了解开发过
!doctype html
html langen
headmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, minimum-scale1.0meta http-equivX-UA-Compatible contentieedgetitleDocument/title
/head
style.logo {width: 320px;height: 80px;}.user-info {padding: 10px 10px;background: RGB(221, 235, 247);}.label {width: 150px;padding: 0 20px 0 0;text-align: left;}.time {width: 100px;margin-right: 10px;text-align: center;}.label, .time {display: inline-block;}.range-time {margin: 20px 0;}.table-data {width: 100%;}table{border-collapse: collapse;}.header th {text-align: left;height: 50px;}.divider-line {margin: 20px 0;height: 3px;background: #000;}.desc {padding: 15px 10px;}.date {width: 100px;}.fee {width: 50px;}.name {width: 140px;}.name, .fee, .date {padding: 0 10px;}.download-date {text-align: right;}.bottom-footer-tip {width: 100%;margin-top: 300px;font-size: 12px;transform: scale(.9);}
/style
bodydiv classdownload-dateDownload on 2022/2/2/div
div classrange-timespan classtime${startTime}/spanspanto/spanspan classtime${endTime}/span
/divtable classtable-datatr classheaderthDate/ththName/ththdesc/ththfee/th/trtr classdivider-lineth/thth/thth/thth/th/tr#list list as itemtrtd classdate${item.date}/tdtd classname${item.name}/tdtd classdescdiv${item.desc}/div/tdtd classfee${item.fee}/td/tr/#listtr classdivider-lineth/thth/thth/thth/th/tr
/table
/body
/html这里ftl的语法我就不多做解释了我这里附上freemarker的官方文档感兴趣的自己去学习一下。 然后准备下我们的代码处理逻辑
首先是PDF实体数据
import lombok.Data;import java.util.List;Data
public class PDFData {private String logo;private String name;private String address;private String startTime;private String endTime;private ListTableData list;
}然后是关联table数据 import lombok.Data;Data
public class TableData {private String date;private String desc;private String name;private String fee;
}然后我们处理我们处理逻辑的代码
import com.alibaba.fastjson.JSONObject;
import com.example.web.pojo.TableData;
import com.example.web.pojo.PDFData;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.layout.property.TextAlignment;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;Component
public class FreemarkerExecution {// 准备下字体文件private static String FONT ./src/main/resources/templates/AlibabaPuHuiTi-3-65-Medium.ttf;// 后续转pdf时的配置private static ConverterProperties converterProperties new ConverterProperties();private static String base64LogoData null;{FontProvider dfp new DefaultFontProvider();//添加字体库dfp.addFont(FONT);//设置解析属性converterProperties.setFontProvider(dfp);converterProperties.setCharset(utf-8);try {// 有一个logo处理因为一般服务器渲染的话一般建议将部分图片处理成base64然后放进来或者大家看看其他的方式base64LogoData imgToBase64(new FileInputStream(./src/main/resources/templates/logo.png));} catch (FileNotFoundException e) {e.printStackTrace();}}Resource(name cfg)private Configuration configuration;// 普通处理逻辑public void converterHTML() {// 这个会将处理后的html语法输出至 命令行窗口可以手动创建一个html文件然后把结果复制进去直接打开查看try (Writer out new OutputStreamWriter(System.out)) {// 获取数据final PDFData pdfData getData();Template temp configuration.getTemplate(pdf.ftl);// 直接写出文件temp.process(pdfData, out);} catch (IOException e) {e.printStackTrace();} finally {}}private void writeToPDF(Template template, MapString, Object dataModel) {try {final File file new File(D:/pdf/test.html);template.process(dataModel, new OutputStreamWriter(new FileOutputStream(file)));final File pdfFile new File(D:/pdf/test.pdf);HtmlConverter.convertToPdf(file, pdfFile, converterProperties);PdfReader reader new PdfReader(new File(D:/pdf/test.pdf));PdfWriter writer new PdfWriter(new FileOutputStream(D:/pdf/test_1.pdf));PdfDocument pdfDocument new PdfDocument(reader, writer);// 页大小final PageSize pageSize pdfDocument.getDefaultPageSize();// 页数final int numberOfPages pdfDocument.getNumberOfPages();for (int i 1; i numberOfPages; i) {PdfPage page pdfDocument.getPage(i);final PdfDocument pdfDoc page.getDocument();final Document document new Document(pdfDoc);final Paragraph paragraph new Paragraph(Page i).setFont(PdfFontFactory.createFont(FONT)).setFontColor(new DeviceRgb(0, 0, 0)).setFixedPosition(i, 0, 10, pageSize.getWidth()).setFontSize(10).setTextAlignment(TextAlignment.CENTER);document.add(paragraph);}pdfDocument.close();reader.close();writer.close();} catch (Exception e) {e.printStackTrace();}}private PDFData getData() throws FileNotFoundException {PDFData data new PDFData();data.setName(重生之我是蔡徐坤);data.setAddress(蔡徐坤蔡徐坤喜欢唱跳rap篮球);data.setStartTime(01-Feb-22);data.setEndTime(28-Feb-22);data.setLogo(data:image/png;base64, base64LogoData);final LocalDateTime nowTime LocalDateTime.now();final ListTableData arr new ArrayList();for (int i 0; i 10; i) {final TableData tableData new TableData();tableData .setDesc(i % 2 0 ? 交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大 : 交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大交会毫不我交会毫不我哒哒哒哒哒哒多多多多多多多交会毫不我电话电话大);tableData .setName(蔡徐坤 i);tableData .setFee(1000 i );tableData .setDate(nowTime.plusDays(-i).format(DateTimeFormatter.ofPattern(YYYY/MM/dd)));arr.add(tableData);}data.setList(arr);return data;}private String imgToBase64(InputStream inputStream) {byte[] data null;try {data new byte[inputStream.available()];inputStream.read(data);inputStream.close();} catch (IOException e) {e.printStackTrace();}return Base64.getEncoder().encodeToString(data);}
}这里会又有一个问题出现就是我们一般处理PDF的时候数据不可能一次性处理到内存中因为我们服务器内存等问题假如我们有100w数据肯定不能一次性查出来由此我们就需要批量处理这里我们可以将模板拆分开重复的数据放一个模板文件然后后续进行模板的组装。
首先我将一个ftl模板文件拆成了三个 header.ftl,content.ftl,footer.ftl,然后再由一个主的核心ftl模板来组装这几个模板。 思路准备上述模板文件然后渲染模板后继续解析成ftl模板文件然后读取选然后的ftl模板文件然后转成html最后通过html文件处理成pdf文件。 这里content.ftl是批量的数据因为不能一次读取大量数据所以这里content.ftl要单独处理一下。 Main.ftl
!doctype html
html langen
headmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, minimum-scale1.0meta http-equivX-UA-Compatible contentieedgetitleDocument/title
/head
style.logo {width: 320px;height: 80px;}.user-info {padding: 10px 10px;background: RGB(221, 235, 247);}.label {width: 150px;padding: 0 20px 0 0;text-align: left;}.time {width: 100px;margin-right: 10px;text-align: center;}.label, .time {display: inline-block;}.range-time {margin: 20px 0;}.table-data {width: 100%;}table {border-collapse: collapse;}.header th {text-align: left;height: 50px;}.divider-line {margin: 20px 0;height: 3px;background: #000;}.desc {padding: 15px 10px;}.date {width: 100px;}.name {width: 50px;}.fee{width: 140px;}.fee, .name , .date {padding: 0 10px;}.download-date {text-align: right;}.bottom-footer-tip {width: 100%;margin-top: 300px;font-size: 12px;transform: scale(.9);}
/style
body${headerPath}
table classtable-datatr classheaderthDate/ththDesc/ththName/ththFee/th/trtr classdivider-lineth/thth/thth/thth/th/tr#list contentPathList as content${content}/#listtr classdivider-lineth/thth/thth/thth/th/tr
/table
${footerPath}#--#include header.ftl--
#--#include content.ftl--
#--#include footer.ftl--/body
/htmlheader.ftl
img src${logo} classlogo
div classdownload-dateDownload on 2022/2/2/div
div classuser-infodivdiv classlabelUser:/divspan${name}/span/divdivdiv classlabelAddress:/divspan${address}/span/div
/divdiv classrange-timespan classtime${startTime}/spanspanto/spanspan classtime${endTime}/span
/divcontent.ftl
#list list as itemtrtd classdate${item.date}/tdtd classdescdiv${item.desc}/div/tdtd classfee${item.fee}/tdtd classname${item.name}/td/tr/#list
footer.ftl
#list list as itemtrtd classdate${item.date}/tdtd classdescdiv${item.desc}/div/tdtd classfee${item.fee}/tdtd classname${item.name}/td/tr/#list
核心处理逻辑 void allTemplatesWriteToPDF() {// 所有子模板final ListString ftlNameList new ArrayList();try {// 读取对应的模板文件final Template template getTemplate(content.ftl);final Template headerTemplate getTemplate(header.ftl);final Template footerTemplate getTemplate(footer.ftl);final PDFData data getData();// 先处理头部和尾部headerTemplate.process(data, new FileWriter(D:/pdf/content/header.ftl));footerTemplate.process(data, new FileWriter(D:/pdf/content/footer.ftl));// mock 模拟数据库查询10次for (int i 0; i 10; i) {// 组装10条数据算上10次循环一共100条数据final PDFData pdfData getData();String fileName D:/pdf/content/content i .ftl;final FileWriter writer new FileWriter(fileName);// 存储最后框架模板的数据这里是存储了freemarker include 语法连接所有的需要组装的数据模板名称ftlNameList.add(#include \ fileName.substring(fileName.lastIndexOf(/) 1, fileName.lastIndexOf(.)) .ftl \/);// 生成对应的模板文档template.process(pdfData, writer);writer.flush();writer.close();}// 获取所有子模板final Configuration cdf new Configuration(Configuration.VERSION_2_3_22);cdf.setDirectoryForTemplateLoading(new File(D:/pdf/content));cdf.setDefaultEncoding(UTF-8);cdf.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);final Template mainTemplate getTemplate(main.ftl);final HashMapObject, Object map new HashMap();map.put(headerPath, #include \ header.ftl\/);map.put(contentPathList, ftlNameList);map.put(footerPath, #include \ footer.ftl\/);mainTemplate.process(map, new FileWriter(D:/pdf/content/main.ftl));final Template cdfTemplate cdf.getTemplate(main.ftl);cdfTemplate.process(null, new FileWriter(D:/pdf/content/main.html));final File pdfFile new File(D:/pdf/test_new.pdf);HtmlConverter.convertToPdf(new File(D:/pdf/content/main.html), pdfFile, converterProperties);PdfReader reader new PdfReader(pdfFile);PdfWriter writer new PdfWriter(new FileOutputStream(D:/pdf/test_new_1.pdf));PdfDocument pdfDocument new PdfDocument(reader, writer);// 页大小final PageSize pageSize pdfDocument.getDefaultPageSize();// 页数final int numberOfPages pdfDocument.getNumberOfPages();// 这里是处理页脚数据for (int i 1; i numberOfPages; i) {PdfPage page pdfDocument.getPage(i);final PdfDocument pdfDoc page.getDocument();final Document document new Document(pdfDoc);final Paragraph paragraph new Paragraph(Page i).setFont(PdfFontFactory.createFont(FONT)).setFontColor(new DeviceRgb(0, 0, 0)).setFixedPosition(i, 0, 10, pageSize.getWidth()).setFontSize(10).setTextAlignment(TextAlignment.CENTER);document.add(paragraph);}pdfDocument.close();reader.close();writer.close();} catch (IOException | TemplateException e) {e.printStackTrace();}}private Template getTemplate(String name) throws IOException {return configuration.getTemplate(name);}OK大概就这样剩下的大家自己去玩吧 解散