先给大家一个效果图:
左侧是word模板,右侧是生成后的word文档。
在工作中经常用到会有一些生成统计报告、请假等word的功能需求,小编之前做了一些报表的生成,使用过freemarker和poi,但是使用freemarker生成word有一些麻烦的点:
- 需要先将模板word转化为xml,而且在模板word中写好的占位符${obj}也会在转化为xml后被拆分开,还需要人工处理一次
- 在表格循环的时候,需要使用freemarker的<#list list as item>标签进行遍历
- 统计图表(饼图、折线图、柱状图等),图片适配度地,操作麻烦,图表需要人工修改大量xml中的标签已达到动态修改图表数据的效果,而word转为xml时,图片是已base64进行存储,在模板替换中我们需要将图片的base64码替换为占位符
这就意味着使用freemarker需要我们预先编写好word模板再转化为xml,再对xml进行freemaker的标签、占位符等的处理后才能进行word生成,而且图表支持度很低。
这里小编使用easypoi+jfree的形式进行word生成,使用jfree生成统计图表(饼图、折线图、柱状图等)图片,使用easypoi进行占位符替换、表格循环、图片插入已达到根据word模板生成word的效果。通过这个方式生成word只需要预先编写好word模板就可以进行word生成。
但这个解决方案有一种缺点就是只支持07以后的word,也就是后缀为.docx的word文档。
下面我就以Spring Boot项目为例举一个例子:
开发工具:IntelliJ IDEA
JDK:1.8
以及项目目录结构:
1、添加依赖:
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jcommon</artifactId>
<version>1.0.24</version>
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.0</version>
</dependency>
2、编写jfreeutil工具类
作为例子,我只封装了将图片转为字节数组和根据具有生成饼状图Image实体的两个方法,各位自行参考
import cn.afterturn.easypoi.entity.ImageEntity;
import lombok.extern.slf4j.Slf4j;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.data.general.DefaultPieDataset;
import org.springframework.util.Assert;
import java.awt.*;
import java.io.*;
import java.util.Map;
/**
* @author 何昌杰
*
* 参考API博客 javascript:void(0)
*/
@Slf4j
public class JfreeUtil {
private static String tempImgPath="D:\\tempJfree.jpeg";
/**
* 将图片转化为字节数组
* @return 字节数组
*/
private static byte[] imgToByte(){
File file = new File(tempImgPath);
byte[] buffer = null;
try {
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
byte[] b = new byte[1000];
int n;
while ((n = fis.read(b)) != -1) {
bos.write(b, 0, n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
} catch (IOException e) {
log.error(e.getMessage());
}
//删除临时文件
file.delete();
return buffer;
}
public static ImageEntity pieChart(String title, Map<String, Integer> datas, int width, int height) {
//创建主题样式
StandardChartTheme standardChartTheme = new StandardChartTheme("CN");
//设置标题字体
standardChartTheme.setExtraLargeFont(new Font("宋体", Font.BOLD, 20));
//设置图例的字体
standardChartTheme.setRegularFont(new Font("宋体", Font.PLAIN, 15));
//设置轴向的字体
standardChartTheme.setLargeFont(new Font("宋体", Font.PLAIN, 15));
//设置主题样式
ChartFactory.setChartTheme(standardChartTheme);
//根据jfree生成一个本地饼状图
DefaultPieDataset pds = new DefaultPieDataset();
datas.forEach(pds::setValue);
//图标标题、数据集合、是否显示图例标识、是否显示tooltips、是否支持超链接
JFreeChart chart = ChartFactory.createPieChart(title, pds, true, false, false);
//设置抗锯齿
chart.setTextAntiAlias(false);
PiePlot plot = (PiePlot) chart.getPlot();
plot.setNoDataMessage("暂无数据");
//忽略无值的分类
plot.setIgnoreNullValues(true);
plot.setBackgroundAlpha(0f);
//设置标签阴影颜色
plot.setShadowPaint(new Color(255,255,255));
//设置标签生成器(默认{0})
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}({1})/{2}"));
try {
ChartUtils.saveChartAsJPEG(new File(tempImgPath), chart, width, height);
} catch (IOException e1) {
log.error("生成饼状图失败!");
}
ImageEntity imageEntity = new ImageEntity(imgToByte(), width, height);
Assert.notNull(imageEntity.getData(),"生成饼状图对象失败!");
return imageEntity;
}
}
关于jfree的一些API接口,和图形图表属性设置。
注:@Slf4j注解需要添加lombok的依赖
3、编写wordutil工具类
import cn.afterturn.easypoi.word.WordExportUtil;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.util.Assert;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Map;
/**
* @author 何昌杰
*/
public class WordUtil {
/**
* 导出word
* <p>第一步生成替换后的word文件,只支持docx</p>
* <p>第二步下载生成的文件</p>
* <p>第三步删除生成的临时文件</p>
* 模版变量中变量格式:{{foo}}
*
* @param templatePath word模板地址
* @param temDir 生成临时文件存放地址
* @param fileName 文件名
* @param params 替换的参数
*/
public static void exportWord(String templatePath, String temDir, String fileName, Map<String, Object> params) {
Assert.notNull(templatePath, "模板路径不能为空");
Assert.notNull(temDir, "临时文件路径不能为空");
Assert.notNull(fileName, "导出文件名不能为空");
Assert.isTrue(fileName.endsWith(".docx"), "word导出请使用docx格式");
if (!temDir.endsWith("/")) {
temDir = temDir + File.separator;
}
File dir = new File(temDir);
if (!dir.exists()) {
dir.mkdirs();
}
try {
XWPFDocument doc = WordExportUtil.exportWord07(templatePath, params);
String tmpPath = temDir + fileName;
FileOutputStream fos = new FileOutputStream(tmpPath);
doc.write(fos);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、编写word模板
关于word中的更多表达式各位可参考easypoi文档的3节内容。
5、编写测试类
import java.util.ArrayList;
import java.util.HashMap;
import cn.afterturn.easypoi.entity.ImageEntity;
import com.springclouddemo.freemarkerdemo.utils.JfreeUtil;
import com.springclouddemo.freemarkerdemo.utils.WordUtil;
/**
* @author 何昌杰
*/
public class WordDemo1 {
public static void main(String[] args) {
HashMap<String, Object> map = new HashMap<>(4);
//模拟饼状图数据
HashMap<String, Integer> datas = new HashMap<>(3);
datas.put("一号",10);
datas.put("二号",20);
datas.put("三号",40);
ImageEntity imageEntity = JfreeUtil.pieChart("测试",datas, 500, 300);
map.put("picture", imageEntity);
//模拟其它普通数据
map.put("username", "张三");
map.put("date", "2019-10-10");
map.put("desc", "测试");
map.put("boo", true);
//模拟表格数据
ArrayList<HashMap<String, String>> list = new ArrayList<>(2);
HashMap<String, String> temp = new HashMap<>(3);
temp.put("sn","1");
temp.put("name","第一个人");
temp.put("age","23");
list.add(temp);
temp = new HashMap<>(3);
temp.put("sn","2");
temp.put("name","第二个人");
temp.put("age","24");
list.add(temp);
map.put("personlist",list);
//word模板相对路径、word生成路径、word生成的文件名称、数据源
WordUtil.exportWord("template/demo1.docx", "D:/", "生成文件.docx", map);
}
}
6、运行测试
生成后的word文档