先给大家一个效果图:

JAVA report java report工具 word_JAVA report

左侧是word模板,右侧是生成后的word文档。

 

在工作中经常用到会有一些生成统计报告、请假等word的功能需求,小编之前做了一些报表的生成,使用过freemarker和poi,但是使用freemarker生成word有一些麻烦的点:

  1. 需要先将模板word转化为xml,而且在模板word中写好的占位符${obj}也会在转化为xml后被拆分开,还需要人工处理一次
  2. 在表格循环的时候,需要使用freemarker的<#list list as item>标签进行遍历
  3. 统计图表(饼图、折线图、柱状图等),图片适配度地,操作麻烦,图表需要人工修改大量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

以及项目目录结构:

JAVA report java report工具 word_JAVA report_02

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模板

JAVA report java report工具 word_word生成_03

关于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文档

JAVA report java report工具 word_java_04