需求
这次接到的任务是,将客户的资料动态写入到pdf文档中并下载。大体效果如下。
需求分析
划重点,需求主要的是动态填充数据(譬如用户有三张银行卡,那么就需要循环插入数据)、并且导出后的文档格式是PDF。所以难度是在动态填充里面,其次是PDF,至于用户名这种固定的,直接占位符替换即可。如果直接在PDF进行数据填充,是否可以呢?答案是可以,但是动态填充的话,涉及到格式以及样式问题,就不好实现了。那咋整呢,我们可以曲线一下,先把数据填充到Excel模板,然后再把Excel文件转成PDF。所以整体可以拆到两步。
(ps:可能有些同学会问,直接整个Excel自己手写是否可以呢,也可以,但还是样式问题,不如模板 填充的形式更方便快捷。主要是自己懒得写样式)
1-找到支持动态插入数据到Excel的框架。
2-找Excel转PDF的框架。
EXCEL动态插入数据技术方案
1.Apache POI
Apache POI 是一个开源的 Java 库,用于读取和写入 Microsoft Office 格式的文件,例如 Excel(.xls 和 .xlsx)、Word(.doc 和 .docx)以及 PowerPoint(.ppt 和 .pptx)文件。
优点
.功能强大提供了丰富的 API,能够满足对 Office 文件的各种操作需求,如读写、格式设置、公式计算等。
支持多种文件格式(.xls、.xlsx、.doc、.docx、.ppt、.pptx),几乎涵盖了所有常见的 Microsoft Office 文件类型。
完全基于 Java 编写,不依赖于外部的 Microsoft Office 软件。这意味着它可以在没有安装 Microsoft Office 的环境中运行,减少了对环境的依赖。
缺点
.不能根据模板插入数据,尤其是循环插入的情况下。
2.esaypoi
也是个开源框架,主要是支持根据占位符替换数据,且可以动态插入数据。完美。
但是,这玩意有个比较坑的地方,多个fe (循环)标签。就会出现莫名其妙的BUG,,会报【for each 当中存在空字符串,请检查模板】。整整花了2天的时间,后面实在没办法。直接放弃。
3.阿里的esayExcel
免费开源,容易上手,但它对单元格的合并很不友好。加上升级框架对于现有项目的改动很大,然后放弃、、。(项目使用的是springbootcloud 架构)
4.Jxls
JXLS是一个轻量级的Java库,主要用于基于模板的Excel报表生成。它通过在Excel模板中使用特定的标记或注释来定义数据的输出格式和布局,从而避免了编写大量重复且易出错的代码,特别适合处理复杂的Excel导出需求,如固定样式、合并单元格和动态列等。xlsx文件的模板如下
.其中第一个注释必须写在第一个单元格,内容是【jx:area(lastCell=“K6”)】,其中K6代表整体区域的最后一个位置,可相应的修改
.第二个注释【jx:each(items=“data”,var=“cell”,lastCell=“K5”)】代表将data里的列表数据进行循环,每一个为cell对象,生效范围一直到K5位置
.${name} 这种就是单纯的占位符替换了。
写代码的时候,
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "xxxxxx");
List<xxxxx> data= new ArrayList();
map.put("data",data);
代码中的name 就是对应的 ${name}了。
data 对应的就是循环里面的data
我这里只是用到了部分功能、其他的功能使用建议自行查询。
模板内容讲解完毕后,就可以上代码了。
首先是jxls的pom.xml
<!-- jxls begin -->
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls</artifactId>
<version>2.8.0</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-poi</artifactId>
<version>2.8.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-jexcel</artifactId>
<version>1.0.9</version>
</dependency>
<!--根据jxls-cor-1.0.6修改,支持poi4.x版本-->
<dependency>
<groupId>net.sf.jxls</groupId>
<artifactId>jxls-core</artifactId>
<version>1.0.4</version>
</dependency>
工具类
import org.jxls.common.Context;
import org.jxls.transform.Transformer;
import org.jxls.util.JxlsHelper;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
public class JxlsUtils{
private static final String TEMPLATE_PATH="jxls-template";
public static void exportExcel(InputStream is, OutputStream os, Map<String, Object> model) throws IOException {
Context context = new Context();
if (model != null) {
for (String key : model.keySet()) {
context.putVar(key, model.get(key));
}
}
JxlsHelper jxlsHelper = JxlsHelper.getInstance();
Transformer transformer = jxlsHelper.createTransformer(is, os);
//JexlExpressionEvaluator evaluator = (JexlExpressionEvaluator)transformer.getTransformationConfig().getExpressionEvaluator();
//Map<String, Object> funcs = new HashMap<>();
// funcs.put("utils", new JxlsUtils()); //添加自定义功能
// evaluator.getJexlEngine().setFunctions(funcs);
jxlsHelper.processTemplate(context, transformer);
}
public static void exportExcel(File xls, File out, Map<String, Object> model) throws FileNotFoundException, IOException {
exportExcel(new FileInputStream(xls), new FileOutputStream(out), model);
}
public static void exportExcel(String templateName, OutputStream os, Map<String, Object> model) throws FileNotFoundException, IOException {
File template = getTemplate(templateName);
if(template!=null){
exportExcel(new FileInputStream(template), os, model);
}
}
//获取jxls模版文件
public static File getTemplate(String name){
String templatePath = JxlsUtils.class.getClassLoader().getResource(TEMPLATE_PATH).getPath();
File template = new File(templatePath, name);
if(template.exists()){
return template;
}
return null;
}
// 日期格式化
public String dateFmt(Date date, String fmt) {
if (date == null) {
return "";
}
try {
SimpleDateFormat dateFmt = new SimpleDateFormat(fmt);
return dateFmt.format(date);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
// if判断
public Object ifelse(boolean b, Object o1, Object o2) {
return b ? o1 : o2;
}
}
excel转pdf技术方案
.Spire ,功能强大,直接几行代码转DF, 但是免费版只能转3页,超过3页会出现提示。。。
.aspose 功能强大,上手简单,虽然收费但是网上免费资源较多(自行体会)。
自然而然得选择了aspose
上代码
附上asposepom.xml(ps:这个jar需要自己下载,然后放到项目里面引用的。)
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-cells</artifactId>
<version>8.5.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/../file/lib/aspose-cells-8.5.2.jar</systemPath>
</dependency>
附上导出代码——将上面的excel转换成PDF
Map<String, Object> map = new HashMap<String, Object>();
//设置参数,excel是模板填充
map.put("services",service);
// excel 路径
URL url=new URL("http://xxxxxxxx);
String fileName = URLEncoder.encode("文件名.pdf", CharsetUtil.UTF_8);
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.setContentType("application/octet-stream;charset=UTF-8");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
InputStream inputStream1 = url.openStream();
log.info("开始导出excel============================时间为:"+DateUtil.format(new Date(),"yyyy-MM-dd HH:ss:mm"));
JxlsUtils.exportExcel(inputStream1,outputStream,map);
log.info("结束导出excel============================时间为:"+DateUtil.format(new Date(),"yyyy-MM-dd HH:ss:mm"));
log.info("开始导出pdf============================时间为:"+DateUtil.format(new Date(),"yyyy-MM-dd HH:ss:mm"));
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ServletOutputStream responseOutputStream = response.getOutputStream();
Excel2Pdf.excel2pdf(inputStream,responseOutputStream);
log.info("结束导出pdf============================时间为:"+DateUtil.format(new Date(),"yyyy-MM-dd HH:ss:mm"));
responseOutputStream.flush();
responseOutputStream.close();
inputStream.close();
inputStream1.close();
outputStream.flush();
outputStream.close();
补上 Excel2Pdf 工具类
import com.aspose.cells.License;
import com.aspose.cells.SaveFormat;
import com.aspose.cells.Workbook;
import javax.servlet.ServletOutputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
*
* @version 1.0.0
* @ClassName Excel2Pdf
* @Description
* @createTime 2022年10月23日
*/
public class Excel2Pdf {
public static boolean getLicense() {
boolean result = false;
try {
InputStream is = Excel2Pdf.class.getClassLoader().getResourceAsStream("xlsxlicense.xml"); // license.xml应放在..WebRootWEB-INFclasses路径下
License aposeLic = new License();
aposeLic.setLicense(is);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public static void excel2pdf(ByteArrayInputStream inputStream, ServletOutputStream responseOutputStream) {
if (!getLicense()) { // 验证License 若不验证则转化出的pdf文档会有水印产生
return;
}
try {
Workbook wb = new Workbook(inputStream);// 原始excel路径
wb.save(responseOutputStream, SaveFormat.PDF);
} catch (Exception e) {
e.printStackTrace();
}
}
}
以下是 xlsxlicense.xml![]()
<License>
<Data>
<Products>
<Product>Aspose.Total for Java</Product>
<Product>Aspose.Words for Java</Product>
</Products>
<EditionType>Enterprise</EditionType>
<SubscriptionExpiry>20991231</SubscriptionExpiry>
<LicenseExpiry>20991231</LicenseExpiry>
<SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>
</Data>
<Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>
</License>
至此,收工,有问题欢迎留言讨论。