最近项目有一个导出报表文件的需求,我脑中闪过第一念头就是导出pdf(产品经理没有硬性规定导出excel还是pdf文件),于是赶紧上网查看相关的资料,直到踩了无数的坑把功能做出来了才知道其实导出excel的api更方便,网上的相关博客也更多,不过坑也踩完了,这一次就来把代码和收获整理和分享一下。
导出pdf模板的局限性
在看到需求之前,我先去网上去搜了一波,发现网上很大一部分博客都是将如何导出pdf模板的,就是先制作一张pdf模板,把固定不变的地方先写好,把需要改变的地方留白并设置参数,然后在代码里为参数赋值就行了,这种方式很简单,代码量也很少,但是!!!这种方式只适合导出格式和内容是固定的文件,而我们的需求是导出一张以产品名称为列,产品属性为行的报表,产品数量不定,这很显然就不能用模板的方式,只好老老实实把报表的数据从头到尾一一导出来。
使用iText导出pdf表格
iText是一种生成PDF报表的Java组件,先把jar包下下来,maven依赖如下:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.0.6</version>
</dependency>
按照惯例,先来生一个“Hello World”的文件,代码如下
public class TestPdf {
public static void main(String[] args) throws Exception { TestPdf pdf = new TestPdf();
String filename = "D:/Program Files/pdfTest/testTable3.pdf";
pdf.createPDF(filename);
System.out.println("打印完成"); }
public void createPDF(String filename) throws IOException {
Document document = new Document(PageSize.A4);
try {
PdfWriter.getInstance(document, new FileOutputStream(filename));
document.addTitle("example of PDF");
document.open();
document.add(new Paragraph("Hello World!"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
} finally {
document.close();
}
}
}
这个没什么可说的,照着写就行了,不过我们导出的pdf大多数是以表格的形式,所以我就直接切入主题了,这里要用到一个很关键的类com.itextpdf.text.pdf.PDFPTable,先导出一张两行两列的表格
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{
PdfPTable table = new PdfPTable(2);//生成一个两列的表格
PdfPCell cell;
int size = 15;
cell = new PdfPCell(new Phrase("one"));
cell.setFixedHeight(size);//设置高度
table.addCell(cell);
cell = new PdfPCell(new Phrase("two"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("three"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("four"));
cell.setFixedHeight(size);
table.addCell(cell);
return table;
} public void createPDF(String filename) throws IOException {
Document document = new Document(PageSize.A4);
try {
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
document.addTitle("example of PDF");
document.open();
PdfPTable table = createTable(writer);
document.add(table);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
} finally {
document.close();
}
}
效果如下图
现在把我们的需求变得更苛刻一点,再增加一行,格子数为1,文字水平和垂直居中对齐,占两行高度,先贴代码再做解释
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{
PdfPTable table = new PdfPTable(2);//生成一个两列的表格
PdfPCell cell;
int size = 15;
cell = new PdfPCell(new Phrase("one"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("two"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("three"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("four"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("five"));
cell.setColspan(2);//设置所占列数
cell.setFixedHeight(size*2);//设置高度
cell.setHorizontalAlignment(Element.ALIGN_CENTER);//设置水平居中
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//设置垂直居中
table.addCell(cell);
return table;
}
这里有一个很坑的地方,就是每一行的长度要和初始化表格的长度相等,即要把整行给占满,否则后面的都不会打印出来,所以要用到setColspan()方法来合并列,接下来还有一个就是合并行的的方法setRowspan()。我们在导出pdf报表的实际操作中经常会有这样的需求
所以合并行的方法显得尤为重要,那就尝试一下用这个方法来合并行,
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{
PdfPTable table = new PdfPTable(2);//生成一个两列的表格
PdfPCell cell;
int size = 20;
cell = new PdfPCell(new Phrase("one"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("two"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("three"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("four"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("five"));
cell.setColspan(1);//设置所占列数
cell.setRowspan(2);//合并行
cell.setFixedHeight(size*2);//设置高度
cell.setHorizontalAlignment(Element.ALIGN_CENTER);//设置水平居中
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//设置垂直居中
table.addCell(cell);
cell = new PdfPCell(new Phrase("six"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("seven"));
cell.setFixedHeight(size);
table.addCell(cell);
return table;
}
效果如下
事实上很简单,在原来的代码上将“five”这一行长度由2变为1,加上setRowspan(2)方法,再加上两行长度为1的cell就是上面的效果了。看起来也没有多复杂是吧,现在我们在jar包上用了较新的版本itextpdf-5.0.6.jar,然而在这之前还有一种老版本的jar包itext-2.1.7.jar,依赖如下:
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
用了这个jar包后发现效果是一样的,那我为什么要提这个版本呢,在后面我们会讲打到。事实上,在实现行合并的还有一种方法就是table中再套一个table,代码如下:
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{
PdfPTable table = new PdfPTable(2);//生成一个两列的表格
PdfPCell cell;
int size = 20;
cell = new PdfPCell(new Phrase("one"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("two"));
cell.setFixedHeight(size);
cell.setColspan(2);
table.addCell(cell);
cell = new PdfPCell(new Phrase("three"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("four"));
cell.setFixedHeight(size);
cell.setColspan(2);
table.addCell(cell);
cell = new PdfPCell(new Phrase("five"));
cell.setColspan(1);//设置所占列数
//cell.setRowspan(2);//合并行
cell.setFixedHeight(size*2);//设置高度
cell.setHorizontalAlignment(Element.ALIGN_CENTER);//设置水平居中
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//设置垂直居中
table.addCell(cell);
PdfPTable table2 = new PdfPTable(1);//新建一个table
cell = new PdfPCell(new Phrase("six"));
cell.setFixedHeight(size);
table2.addCell(cell);
cell = new PdfPCell(new Phrase("seven"));
cell.setFixedHeight(size);
table2.addCell(cell);
cell = new PdfPCell(table2);//将table放到cell中
table.addCell(cell);//将cell放到外层的table中去
return table;
}
效果和刚才是完全一样的,这种方法的关键就是将新建的table放到cell中,然后把cell放到外层table中去。这种方法明显比刚才的麻烦很多,我之所以拿出来是因为我在用老版本的jar包中的合并行的方法突然不起作用了,然后临时想了这个办法出来,至于为什么方法失效报错我到现在还没弄明白。。。
在表格中加入单选框/复选框
之所以提这个,在我自己项目中就有这样的需求,而且在网上这种资料非常少,我这也是在官网上看到的,遂记录一下,先看一下单选框怎么加,代码如下
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{
PdfPTable table = new PdfPTable(2);//生成一个两列的表格
PdfPCell cell;
int size = 20;
cell = new PdfPCell(new Phrase("one"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("two"));
cell.setFixedHeight(size);
cell.setColspan(2);
table.addCell(cell);
cell = new PdfPCell(new Phrase("three"));
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase("four"));
cell.setFixedHeight(size);
cell.setColspan(2);
table.addCell(cell);
cell = new PdfPCell(new Phrase("five"));
cell.setColspan(1);//设置所占列数
//cell.setRowspan(2);//合并行
cell.setFixedHeight(size*2);//设置高度
cell.setHorizontalAlignment(Element.ALIGN_CENTER);//设置水平居中
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//设置垂直居中
table.addCell(cell);
PdfPTable table2 = new PdfPTable(1);//新建一个table
cell = new PdfPCell(new Phrase("six"));
cell.setFixedHeight(size);
table2.addCell(cell);
cell = new PdfPCell(new Phrase("seven"));
cell.setFixedHeight(size);
table2.addCell(cell);
cell = new PdfPCell(table2);//将table放到cell中
table.addCell(cell);//将cell放到外面的table中去
Phrase phrase1 = new Phrase();
Chunk chunk1 = new Chunk(" YES");
Chunk chunk2 = new Chunk(" NO");
phrase1.add(chunk1);
phrase1.add(chunk2);
cell = new PdfPCell(phrase1);
cell.setColspan(2);
table.addCell(cell);
//增加两个单选框
PdfFormField radiogroup=PdfFormField.createRadioButton(writer, true);
radiogroup.setFieldName("salesModel");
Rectangle rect1 = new Rectangle(110, 722, 120, 712);
Rectangle rect2 = new Rectangle(165, 722, 175, 712);
RadioCheckField radio1 = new RadioCheckField(writer, rect1, null, "self-support" ) ;
RadioCheckField radio2 = new RadioCheckField(writer, rect2, null, "cooprate") ;
radio2.setChecked(true);
PdfFormField radiofield1 = radio1.getRadioField();
PdfFormField radiofield2 = radio2.getRadioField();
radiogroup.addKid(radiofield1);
radiogroup.addKid(radiofield2);
writer.addAnnotation(radiogroup);
return table;
}
效果如图
这个最烦的地方就是自己要去一点点试单选框的参数来调整位置,就是 Rectangle rect1 = new Rectangle()里面的参数,然后想让哪个radio被选中就用setChecked(true)方法就OK了
解决中文字体的问题
在之前的例子中我用的都是英文字,但是中文字体是一个不得不解决的问题,根据我去网上搜集到的资料大抵有以下几个方法:
- 使用windows系统自带的字体,这种事最省事,也是最简单的,代码如下
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{
PdfPTable table = new PdfPTable(2);//生成一个两列的表格
PdfPCell cell;
int size = 20;
Font font = new Font(BaseFont.createFont("C:/Windows/Fonts/SIMYOU.TTF",BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED));
cell = new PdfPCell(new Phrase("显示中文",font));
cell.setFixedHeight(size);
cell.setColspan(2);
table.addCell(cell);
return table;
} - 使用itext-asian.jar中的中文字体,代码如下:
Font font = new Font(BaseFont.createFont( "STSongStd-Light" ,"UniGB-UCS2-H",BaseFont.NOT_EMBEDDED));
在这里可能会报Font 'STSongStd-Light' with 'UniGB-UCS2-H' is not recognized.的错,网上都说是语言包中的包名有问题,但是我把itexitextpdf包的版本从5.0.6改成5.4.3就解决了,我把语言包解压后发现路径是对的,应该是后面的版本已经把语言包中的包名解决了,那为什么5.0.6的版本不行呢,反正我到现在还没找到原因,如果有谁知道的话欢迎留言告知。
3.使用自己下载的资源字体。这种方法可以说是最麻烦的,但是也是最有效的。麻烦是因为自己要去下载字体文件,但是效果确是最好的,能够应对各种系统,各种状况,下面会提到,先看一下我的文件路径
然后是代码
Font font = new Font(BaseFont.createFont( "/simsun.ttf",BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED));
其实跟第一个是差不多的,只是一个是读系统的文件,一个是读项目自己的文件。
在springboot中导出pdf文件
前面做了这么多铺垫,终于来到我们最关键的地方了,前面的东西都是放到java项目中去跑的,没有太多实际用处,在web项目中用到才算真正的应用,假设我们有一个产品类,我们的需求就是把产品数据导成pdf文件,代码如下:
产品Product类
public class Product {
private String productName;
private String productCode;
private float price;
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getProductCode() {
return productCode;
}
public void setProductCode(String productCode) {
this.productCode = productCode;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
} public Product(String productName, String productCode, float price) {
super();
this.productName = productName;
this.productCode = productCode;
this.price = price;
} }
接下来是controller
//打印pdf
@RequestMapping("printPdf")
public ModelAndView printPdf() throws Exception{
Product product1 = new Product("产品一","cp01",120);
Product product2 = new Product("产品一","cp01",120);
Product product3 = new Product("产品一","cp01",120);
List<Product>products = new ArrayList<>();
products.add(product1);
products.add(product2);
products.add(product3);
Map<String, Object> model = new HashMap<>();
model.put("sheet", products);
return new ModelAndView(new ViewPDF(), model);
}
然后是ViewPDF类
public class ViewPDF extends AbstractPdfView { @Override
protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,
HttpServletRequest request, HttpServletResponse response) throws Exception { String fileName = new Date().getTime()+"_quotation.pdf"; // 设置response方式,使执行此controller时候自动出现下载页面,而非直接使用excel打开
response.setCharacterEncoding("UTF-8");
response.setContentType("application/pdf");
response.setHeader("Content-Disposition","filename=" + new String(fileName.getBytes(), "iso8859-1"));
List<Product> products = (List<Product>) model.get("sheet");
PdfUtil pdfUtil = new PdfUtil();
pdfUtil.createPDF(document, writer, products);
}
}
在这里有两个值得注意的地方:
1. 仔细观察这个类,看父类的bulidPdfDocument(),发现其中的document要求的是这个com.lowagie.text.Document版本的,这就非常坑了,意味着之前的itextpdf-5.0.6.jar不能再用了,只能用老版本的(改名之前的)itext-2.1.7.jar了。
2. 如果不想要这种直接在浏览器中预览的效果,而是直接下载文件的话就把response.setHeader()方法里面的参数改成下面的,相当于加了一个attachment,以附件形式下载
response.setHeader("Content-Disposition","attachment;filename=" + new String(fileName.getBytes(), "iso8859-1"));
最后是PdfUtil类
public class PdfUtil { public void createPDF(Document document,PdfWriter writer, List<Product> products) throws IOException {
//Document document = new Document(PageSize.A4);
try {
document.addTitle("sheet of product");
document.addAuthor("scurry");
document.addSubject("product sheet.");
document.addKeywords("product.");
document.open();
PdfPTable table = createTable(writer,products);
document.add(table);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
} finally {
document.close();
}
}
public static PdfPTable createTable(PdfWriter writer,List<Product> products) throws IOException, DocumentException {
PdfPTable table = new PdfPTable(3);//生成一个两列的表格
PdfPCell cell;
int size = 20;
Font font = new Font(BaseFont.createFont("C://Windows//Fonts//simfang.ttf", BaseFont.IDENTITY_H,
BaseFont.NOT_EMBEDDED));
for(int i = 0;i<products.size();i++) {
cell = new PdfPCell(new Phrase(products.get(i).getProductCode(),font));//产品编号
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase(products.get(i).getProductName(),font));//产品名称
cell.setFixedHeight(size);
table.addCell(cell);
cell = new PdfPCell(new Phrase(products.get(i).getPrice()+"",font));//产品价格
cell.setFixedHeight(size);
table.addCell(cell);
}
return table;
}
}
这同时也意味着如果项目要打包部署到linux服务器上去的话,前两种的中文解决办法都不好使了,只能下载中文字体资源文件,然而这其中又有一个坑,打包后文件路径读取不到。
所以要以加载资源文件的形式读取文件,代码如下
BaseFont baseFont = BaseFont.createFont(new ClassPathResource("/simsun.ttf").getPath(), BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
效果如下图:
在这里既可以下载也可以打印。
到这里我这踩了一路的坑都已经说完了,如果之后遇到新的问题我也持续更新这篇博客的。总的来说导出pdf真的是一件很繁琐的事情,还是那句话如果能导excel的话就尽量导excel吧。。。
史上最全的springboot导出pdf文件的更多相关文章
-
史上最全的maven pom.xml文件教程详解
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
-
Vscode+Picgo+github+Markdown Preview Enhanced实现Markdown一键上传图床以及导出pdf文件
目录 安装Vscode 安装及配置Picgo插件 安装Markdown Preview Enhance 安装Vscode 安装Vscode(不解释了) 安装及配置Picgo插件 在github中新建仓 ...
-
SpringBoot面试题 (史上最全、持续更新、吐血推荐)
文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...
-
Office在线预览及PDF在线预览的实现方式史上最全大集合
Office在线预览及PDF在线预览的实现方式大集合 一.服务器先转换为PDF,再转换为SWF,最后通过网页加载Flash预览 微软方:利用Office2007以上版本的一个PDF插件SaveAsPD ...
-
markdown写ppt (史上最全)
文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...
-
史上最全的 Java 新手问题汇总
史上最全的 Java 新手问题汇总 Java是目前最流行的编程语言之一——它可以用来编写Windows程序或者是Web应用,移动应用,网络程序,消费电子产品,机顶盒设备,它无处不在. 有超过30亿 ...
-
JVM史上最全实践优化没有之一
JVM史上最全优化没有之一 1.jvm的运行参数 1.1 三种参数类型 1.1.1 -server与-clinet参数 2.1 -X参数 2.1.1 -Xint.-Xcomp.-Xmixed 3.1 ...
-
spring + spring mvc + tomcat 面试题(史上最全)
文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...
-
JVM 内存溢出 实战 (史上最全)
文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...
随机推荐
-
Android画图Path的使用
/** * Paint类介绍 * * Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色, * 样式等绘制信息,指定了如何绘制文本 ...
-
javascript中window.open()与window.location.href的区别
window.open("www.baidu.com"); 只是表示打开这个页面,并不是打开并刷新baidu.com window.location.href="www. ...
-
548 - Tree (UVa OJ)
Tree You are to determine the value of the leaf node in a given binary tree that is the terminal nod ...
-
POJ 2250(LCS最长公共子序列)
compromise Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u Descri ...
-
设计模式——单例设计模式(C++实现)
#ifndef SINGLETONHOLDER_INC #define SINGLETONHOLDER_INC template<class T> class SingletonHolde ...
-
GIT入门笔记(18)- 标签创建和管理
git tag <name>用于新建一个标签,默认为HEAD,也可以指定一个commit id: git tag -a <tagname> -m "blablabla ...
-
网盘资源分享:你不知道的JavaScript(上)
链接:https://pan.baidu.com/s/1UEBetOr2Z94oEeu5VsQYXQ 提取码:etts 复制这段内容后打开百度网盘手机App,操作更方便哦
-
redis的主从机制 master&;slave
转载自:https://www.cnblogs.com/qwangxiao/p/9733480.html 一:master&slave的解释? master&slave就是主从复制,主 ...
-
自学Zabbix14.1 二次开发API
点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix14.1 二次开发API Zabbix API我们可以做很多,自己开发web界面. ...
-
Linux掉电处理
在嵌入式设备中,掉电处理一直是一项比较麻烦的工作,在具有Linux系统的设备中,系统的种种数据的处理更是增加掉电处理的难度.现在做以下几点总结,再遇到类似问题可以做个参考. 1,系统启动的处理 在系统 ...