yinshuang

  • 个人主页
所有文章 友链 关于我

这似乎是首纯音乐,请尽情的欣赏它吧!

yinshuang

  • 个人主页

EasyPoi实现word模板导出

阅读数:516次 2024-01-25
字数统计: 1.4k字   |   阅读时长≈ 7分

前言

       对于复杂的word模板导出场景,业界比较常用的方案是将word模板转化成xml格式,通过FreeMarker,Thymeleaf,Moustache等模板引擎生成xml最终导出word;上面的方案虽然能实现复杂的模板逻辑,但是如果业务方需要频繁修改模板样式,那么xml修改模板样式也是极为麻烦的且很不直观,这里我推荐一款国产开源的框架EasyPoi,直接在word中使用模板语法就能实现动态解析导出,所见即所得,对于需要频繁调整word模板样式的场景是非常适用的。

1.引入依赖

这里我推荐一个IDEA插件maven-search,方便快速搜索和复制maven依赖
对于word模板导出的场景,引入easypoi-base依赖即可

1
2
3
4
5
6
7
8
9
10
11
<!-- 依赖版本号 -->
<properties>
<easypoi.version>4.4.0</easypoi.version>
</properties>

<!-- 项目依赖 -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>${easypoi.version}</version>
</dependency>

2.模板语法介绍

  • 三目运算 {{test ? obj:obj2}}
  • 字符串长度 {{le:() > 8 ? obj1 : obj2}}
  • 判空 {{le:(obj) > 0 ? obj : -}} (官方没有专门的判空语法,这里是替代方案)
  • 格式化时间 {{fd:(obj;yyyy-MM-dd)}}
  • 格式化数值 {{fn:(obj;#,##0.00)}}
  • 遍历表格 {{$fe: list t.item }} 下面是示例{{}}内容将被遍历
名称数值占比
{ {$fe: list t.itemfn:(t.value;#,##0.00)fn:(t.ratio;#,##0.00%) } }

其中list是map的k的名称 v是List<T>,item, value, ratio 是T的成员属性

更多用法见官方文档

3.集成工具类

工具类包含了导出word文件和web接口response导出,两种导出方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import cn.afterturn.easypoi.word.WordExportUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Map;

/**
* @author yinshuang
* @date 2023/12/12 17:50
* @description WordTemplateUtil
* @copyright(c) chinacscs all rights reserved
*/
@Slf4j
public class WordTemplateUtil {

/**
* 模板文件路径
*/
private static final String TEMPLATE_PATH = "templates";

/**
* word临时文件路径
*/
private static final String DOCX_TMP_PATH = "./docxTmp/";

/**
* docx后缀
*/
private static final String DOCX_SUFFIX = ".docx";

/**
* 根据模板生成word文件
*
* @param dataMap 组装数据Map
* @param templateName docx模板文件
* @param filename 生成文件名
*/
public static File createWordFile(Map<String, Object> dataMap, String templateName, String filename) {
String templateUrl = TEMPLATE_PATH + templateName;
String outfileName = DOCX_TMP_PATH + filename + DOCX_SUFFIX;
File outFile = new File(outfileName);
try {
if (!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
XWPFDocument doc = WordExportUtil.exportWord07(templateUrl, dataMap);
OutputStream fo = Files.newOutputStream(outFile.toPath());
doc.write(fo);
fo.close();
} catch (Exception e) {
log.error("生成docx文件[{}]失败!", outfileName);
log.error("生成docx报错详细日志:{}", ExceptionUtil.stacktraceToString(e));
}
return outFile;
}

public static void exportWord(Map<String, Object> dataMap, String templateName, String filename, HttpServletResponse response) {
// 设置word文件导出header
setResponse(response, filename);
String templateUrl = TEMPLATE_PATH + templateName;
try (OutputStream os = response.getOutputStream()) {
XWPFDocument doc = WordExportUtil.exportWord07(templateUrl, dataMap);
doc.write(os);
} catch (Exception e) {
log.error("生成docx文件["+ filename + "]失败!");
log.error("生成docx报错详细日志:{}", ExceptionUtil.stacktraceToString(e));
}
}

private static void setResponse(HttpServletResponse response, String filename) {
String filenameEncoder = URLUtil.encode(filename) + DOCX_SUFFIX;
String header = StrUtil.format("attachment;filename={};filename*=utf-8''{}", filenameEncoder, filenameEncoder);
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", header);
}
}

4.实战场景

  • 包含文字表格和图片的报告模板案例
    模板样例

  • 用EasyPoi模板语法填充后的报告模板
    模板填充

  • 样例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    import cn.afterturn.easypoi.entity.ImageEntity;
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.date.DatePattern;
    import cn.hutool.core.date.DateUtil;
    import lombok.Data;
    import lombok.experimental.FieldNameConstants;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;

    import java.io.File;
    import java.math.BigDecimal;
    import java.util.*;

    /**
    * @author yinshuang
    * @date 2024/1/25 10:31
    * @description EasyPoiWordTemplateTest
    * @copyright(c) chinacscs all rights reserved
    */
    @Slf4j
    public class EasyPoiWordTemplateTest {

    public static final String FILENAME_SUFFIX = "投前调查报告";
    public static final String TEMPLATE_PATH = "/docx/invest_report.docx";
    @Test
    @DisplayName("easypoi按模板导出word报告")
    public void testCreateReport() {
    // 组装报告数据
    Map<String, Object> dataMap = getDataMap();
    // 报告名称格式:企业名称 + 报告类型后缀 + 日期
    String companyName = (String) dataMap.get(CompanyBasicInfoVO.Fields.companyName);
    String today = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
    String filename = companyName + FILENAME_SUFFIX + today;
    // 根据模板生成文件
    File reportFile = WordTemplateUtil.createWordFile(dataMap, TEMPLATE_PATH, filename);
    }

    private Map<String, Object> getDataMap() {
    Map<String, Object> dataMap = new HashMap<>(16);
    // 企业基本信息
    CompanyBasicInfoVO companyBasicInfo = getCompanyBasicInfo();
    // 前十大股东信息
    List<CompanySharehdInfoVO> sharehdInfos = getSharehdInfoList();
    // 交易员电子签
    ImageEntity traderImage = getTraderImage();

    dataMap.put("today", new Date());
    dataMap.putAll(BeanUtil.beanToMap(companyBasicInfo));
    dataMap.put("sharehdlist", sharehdInfos);
    dataMap.put("traderSignature", traderImage);
    return dataMap;
    }

    private ImageEntity getTraderImage() {
    ImageEntity traderImage = new ImageEntity();
    traderImage.setType(ImageEntity.URL);
    traderImage.setUrl("picture/img1.jpg");
    traderImage.setWidth(65);
    traderImage.setHeight(40);
    traderImage.setLocationType(ImageEntity.EMBED);
    return traderImage;
    }

    private CompanyBasicInfoVO getCompanyBasicInfo() {
    CompanyBasicInfoVO companyBasicInfo = new CompanyBasicInfoVO();
    companyBasicInfo.setCompanyName("万科企业股份有限公司");
    companyBasicInfo.setReportDt(DateUtil.parse("2023-03-31"));
    companyBasicInfo.setShareCapital(new BigDecimal("109.95"));
    companyBasicInfo.setFirstSharehdName("深圳市地铁集团有限公司");
    companyBasicInfo.setFirstSharehdRatio(new BigDecimal("0.2718"));
    return companyBasicInfo;
    }

    private List<CompanySharehdInfoVO> getSharehdInfoList() {
    List<CompanySharehdInfoVO> sharehdInfos = new ArrayList<>(10);
    CompanySharehdInfoVO sharehdInfo = new CompanySharehdInfoVO();
    sharehdInfo.setSharehdRank("1");
    sharehdInfo.setSharehdName("深圳市地铁集团有限公司");
    sharehdInfo.setSharehdNum(new BigDecimal("32.43"));
    sharehdInfo.setSharehdRatio(new BigDecimal("0.2718"));
    sharehdInfos.add(sharehdInfo);
    return sharehdInfos;
    }

    @Data
    @FieldNameConstants
    public static class CompanyBasicInfoVO {
    /**
    * 企业名称
    */
    private String companyName;

    /**
    * 财务指标最新报告期
    */
    private Date reportDt;

    /**
    * 实收资本 (亿元)
    */
    private BigDecimal shareCapital;

    /**
    * 实际控制人
    */
    private String controllerName;

    /**
    * 第一大股东名称
    */
    private String firstSharehdName;

    /**
    * 第一大股东持股比例
    */
    private BigDecimal firstSharehdRatio;
    }

    @Data
    public static class CompanySharehdInfoVO {
    /**
    * 排名
    */
    private String sharehdRank;

    /**
    * 股东名称
    */
    private String sharehdName;

    /**
    * 持股数量(亿股)
    */
    private BigDecimal sharehdNum;

    /**
    * 持股比例(%)
    */
    private BigDecimal sharehdRatio;

    /**
    * 实控人
    */
    private String affilParty;
    }
    }

    企业样例数据来源
    其中模板Url和图片Url的路径地址都是默认指向resources目录下

  • word模板导出效果
    导出结果

赏

谢谢你请我吃糖果

微信
  • 本文作者: yinshuang
  • 本文链接: https://yinshuang007.github.io/2024/01/25/EasyPoi实现word模板导出/
  • 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!
  • EasyPoi
  • word模板导出

扫一扫,分享到微信

UV快速入门
阿里TransmittableThreadLocal实践
Issue Page
Error: Not Found Project
登录 码云
支持Markdown的样式
Powered by Giteement
© 2021-2025 yinshuang
GitHub:hexo-theme-yilia-plus by Litten
本站总访问量517次 | 本站访客数413人
  • 所有文章
  • 友链
  • 关于我

tag:

  • BitoAI
  • AWS CodeWhisperer
  • CodeGeeX
  • Mapstruct Plus
  • Hutool
  • Jenkins
  • Pipline
  • 企业微信机器人
  • Arthus
  • Mysql-json
  • ApplicationRunner
  • uv
  • python
  • pip
  • conda
  • Java函数式编程
  • poi-tl
  • Hutool-StopWatch
  • 工厂模式
  • 策略模式
  • 模板模式
  • 设计模式
  • ThreadLocal
  • TransmittableThreadLocal
  • JetCache
  • Caffeine
  • Redis
  • Gitlab webhook
  • EasyPoi
  • word模板导出

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia-plus根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • poi-tl模板引擎生成word统计图实践

    2025-05-10

    #poi-tl

  • UV快速入门

    2025-04-29

    #uv#python#pip#conda

  • EasyPoi实现word模板导出

    2024-01-25

    #EasyPoi#word模板导出

  • 阿里TransmittableThreadLocal实践

    2023-11-20

    #ThreadLocal#TransmittableThreadLocal

  • 阿里缓存框架JetCache实践

    2023-08-12

    #JetCache#Caffeine#Redis

  • 工厂+策略+模板模式消除代码中的if else

    2023-08-01

    #工厂模式#策略模式#模板模式#设计模式

  • Java类型转换框架MapstructPlus使用分享

    2023-07-19

    #Mapstruct Plus

  • AI编程辅助工具分享

    2023-07-09

    #BitoAI#AWS CodeWhisperer#CodeGeeX

  • Hutool常用工具类整理

    2023-07-04

    #Hutool

  • SpringBoot中使用mysql8的json类型存储json数据实现CRUD操作

    2023-06-11

    #Mysql-json

  • SpringBoot启动常用的初始化加载数据方法

    2023-05-31

    #ApplicationRunner

  • Jenkins流水线任务配置(企业微信机器人通知)

    2023-02-25

    #Jenkins#Pipline#企业微信机器人

  • Jenkins联和Gitlab配置webhook实现push触发自动化部署

    2023-02-20

    #Jenkins#Gitlab webhook

  • 任务耗时统计工具类分享

    2023-02-18

    #Hutool-StopWatch

  • Java应用诊断利器-Arthus

    2023-02-12

    #Arthus

  • TreeUtil组装树工具类分享

    2023-02-05

    #Java函数式编程

  • 个人博客
  • GitHub
  • 码云
主要涉及技术:
Java后端开发


联系QQ:875038467

很惭愧

只做了一点微小的工作
谢谢大家