问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

使用 POI-TL 和 JFreeChart 动态生成 Word 报告

创作时间:
作者:
@小白创作中心

使用 POI-TL 和 JFreeChart 动态生成 Word 报告

引用
CSDN
1.
https://blog.csdn.net/mr_wanter/article/details/145512508

在开发过程中,我们经常需要生成包含动态数据和图表的Word报告。本文将介绍如何结合POI-TL和JFreeChart,实现动态生成Word报告的功能,并分享一些实际开发中的踩坑经验。

前言

在开发过程中,我们经常需要生成包含动态数据和图表的Word报告。本文将介绍如何结合POI-TL和JFreeChart,实现动态生成Word报告的功能,并分享一些实际开发中的踩坑经验。

Word生成方案:

  1. freemarker+ftl
  2. pot-tl模板替换
  3. poi硬编码

一、需求背景

在之前的文章中,我们已经介绍了如何使用模板替换、复杂表格和图片插入等功能。此次的需求是生成一个包含统计图的Word报告,统计图需要根据动态数据生成。面临的主要问题包括:

  1. 选择Word生成方案:如何在Word中动态插入数据和图表?
  2. 图片插入方案:如何将生成的统计图插入到Word中?
  3. 生成统计图表方案:如何根据数据动态生成统计图?

二、方案分析

  1. POI硬编码
    直接使用Apache POI硬编码生成Word文档,虽然可行,但代码复杂且难以维护,因此不推荐。

  2. FreeMarker + FTL
    FreeMarker可以实现文本替换和图片插入,理论上符合需求。但FTL模板的维护较为繁琐,尤其是在处理复杂表格和图片时。

  3. POI-TL+ JFreeChart
    POI-TL是一个基于Apache POI的模板引擎,支持文本替换、图片插入等功能。结合JFreeChart生成统计图,可以很好地满足需求。

三、 POI-TL + JFreeChart 实现

关于JFreeChart请移步使用JFreeChart创建动态图表:从入门到实战

3.1 Maven 依赖

首先,需要在项目中引入POI-TL和JFreeChart的依赖。注意POI和POI-TL的版本需要对应,否则可能会出现NoSuchMethod等错误。

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-excelant</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.jfree</groupId>
    <artifactId>jfreechart</artifactId>
    <version>1.5.3</version>
</dependency>
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.10.0</version>
</dependency>

3.3 word模板设置

在Word模板中,使用占位符标记需要替换的内容。对于图片,需要在占位符前加@,例如:

  • 替换文本:时间 -> ${date}
  • 插入图片:${@dailyOnlinePic}
  • 条件判断:${?isTrue} 条件内容 ${/isTrue}

3.2 实现代码

以下是核心实现代码:

private static final String TEMPLATE_PATH = "classpath:template/report.docx";
private static final String OUTPUT_DIR = "D:/data/upload/analysis/";
private static final String PIC_DIR = OUTPUT_DIR + "pic/";

public void getWord(String curDistCode) {
    try {
        // 获取模板文件
        File file = ResourceUtils.getFile(TEMPLATE_PATH);
        // 构建模板替换的数据
        Map<String, Object> dataMap = buildTemplateData(curDistCode);
        // 生成最终文件路径
        String fileName = UUIDUtil.genUUID32() + ".docx";
        String filePath = OUTPUT_DIR + fileName;
        // 使用POI-TL渲染模板并保存
        try (XWPFTemplate template = XWPFTemplate.compile(file, Configure.newBuilder().buildGramer("${", "}").build())
                .render(dataMap)) {
            template.writeToFile(filePath);
        }
        //上传文件返回附件id
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private Map<String, Object> buildTemplateData(String curDistCode) throws IOException {
    Map<String, Object> dataMap = new HashMap<>();
    // 设置日期和在线总数
    dataMap.put("date", LocalDate.now());
    // 查询设备数据
    dataMap.put("onlineTotal", 100);
    // 生成每日在线图表
    DefaultCategoryDataset dailyData = buildDailyDataset(stationByTime);
    String dailyPicFile = generateChart(dailyData, "监测站总在线数", "小时", "数量", dailyOnlineTxtEnum);
    //图片插入就用Pictures类构建PictureBuilder,可查看Pictures其他方法,比如插入base64等
    dataMap.put("dailyOnlinePic", Pictures.ofStream(new FileInputStream(dailyPicFile), PictureType.JPEG).size(600, 200).create());
    return dataMap;
}

// 构建每日在线图表的数据集
private DefaultCategoryDataset buildDailyDataset(List<FireStationByTimeDTO> stationByTime) {
    DefaultCategoryDataset dataset = new DefaultCategoryDataset();
    stationByTime.forEach(t -> dataset.addValue(t.getOnlineNum(), "", t.getTime()));
    return dataset;
}

// 生成图表并保存为图片
private String generateChart(DefaultCategoryDataset dataset, String title, String xAxisLabel, String yAxisLabel, DailyOnlineTxtEnum style) throws IOException {
    // 设置全局字体(支持中文)
    StandardChartTheme chartTheme = new StandardChartTheme("CN");
    chartTheme.setExtraLargeFont(new Font("宋体", Font.PLAIN, 14)); // 标题字体
    chartTheme.setLargeFont(new Font("宋体", Font.PLAIN, 14));     // 图例字体
    chartTheme.setRegularFont(new Font("宋体", Font.PLAIN, 12));   // 轴标签字体
    ChartFactory.setChartTheme(chartTheme);
    // 创建图表
    JFreeChart chart = ChartFactory.createLineChart(title, xAxisLabel, yAxisLabel, dataset);
    setChartStyle(chart, style);
    // 保存图表为图片
    String picFile = PIC_DIR + UUIDUtil.genUUID32() + ".png";
    //int numberOfCategories = dataset.getColumnCount();
    //int width = Math.max(800, numberOfCategories * 50); // 每个类别宽度为50,最小宽度为800
    ChartUtils.saveChartAsPNG(new File(picFile), chart, 1200, 400);
    return picFile;
}

// 设置图表样式
private void setChartStyle(JFreeChart chart) {
    CategoryPlot plot = chart.getCategoryPlot();
    chart.setBackgroundPaint(Color.WHITE);
    plot.setBackgroundPaint(Color.WHITE);
    plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
    plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
    // 设置第一条折线的粗细
    plot.getRenderer().setSeriesStroke(0, new BasicStroke(5.0f));
    // 根据样式设置折线颜色
    plot.getRenderer().setSeriesPaint(0, Color.RED);
}

踩坑

  1. 插入图片如何占位
    与常规文本替换不同,图片插入需要在占位符前加@,例如
    ${@dailyOnlinePic}

    {{@pic}}

  2. 统计图中文乱码
    JFreeChart默认不支持中文,需要通过设置全局字体解决:

// 设置全局字体(支持中文)
StandardChartTheme chartTheme = new StandardChartTheme("CN");
chartTheme.setExtraLargeFont(new Font("宋体", Font.PLAIN, 14)); // 标题字体
chartTheme.setLargeFont(new Font("宋体", Font.PLAIN, 14));     // 图例字体
chartTheme.setRegularFont(new Font("宋体", Font.PLAIN, 12));   // 轴标签字体
ChartFactory.setChartTheme(chartTheme);

3.统计图横坐标…
如果生成的图片宽度不够,横坐标可能会显示不全。可以通过增加图片宽度解决,插入时等比例缩放:

ChartUtils.saveChartAsPNG(new File(picFile), chart, 1200, 400);
dataMap.put("dailyOnlinePic", 
Pictures.ofStream(new FileInputStream(dailyPicFile), PictureType.JPEG).size(600, 200).create());

或动态计算需要生成的图片宽度:

int numberOfCategories = dataset.getColumnCount();
int width = Math.max(800, numberOfCategories * 50); // 每个类别宽度为50,最小宽度为800
  1. 模板条件判断
    POI-TL支持复杂的条件判断,例如结合逻辑运算符(&&、||)和比较运算符(==、!=、>、< 等)。
    例如:
{{?isStudent}}
  {{?age > 18}}
    您是一名成年学生。
  {{/age > 18}}
{{/isStudent}}

此次实验boolean好用,字符串==匹配失败。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号