java动态导出word(两种方法)

Java动态模板导出Word

​ 总结了一下我自己使用过的三种动态模板导出,两个Word导出,用的是不同的方法,一个是POI-TL,另一个是用FTL模板。

1、利用poi-tl工具进行导出word(适合较简单的模板)

​ 我要实现的功能大体是这样的,需要在导出多个部门的数据,然后每个部门中会有多条数据,我要将一个部门的数据全部动态导出到一个word文件中,每条数据占一页,生成单独的word,然后把多个文件生成放入同一个压缩包内,用户导出时下载压缩包

1.1 依赖准备

<dependency>
     <groupId>cn.afterturn</groupId>
     <artifactId>easypoi-base</artifactId>
     <version>4.4.0</version>
 </dependency>
 <dependency>
     <groupId>cn.afterturn</groupId>
     <artifactId>easypoi-web</artifactId>
     <version>4.4.0</version>
 </dependency>
 <dependency>
     <groupId>cn.afterturn</groupId>
     <artifactId>easypoi-annotation</artifactId>
     <version>4.4.0</version>
 </dependency>
 <dependency>
     <groupId>org.apache.poi</groupId>
     <artifactId>poi</artifactId>
     <version>4.1.1</version>
 </dependency>
 <dependency>
     <groupId>org.apache.poi</groupId>
     <artifactId>poi-ooxml</artifactId>
     <version>4.1.1</version>
 </dependency>

1.2 创建word模板

image-20250527154941888

按照这个形式在word中自定义你的模板,{{item}}的形式会被替换掉,要注意如果要插入图片则需要在前面加个@符号,如{{@qrCode}}

image-20250527155259040

将模板放入resource中,可以单独建立一个新的文件夹

1.3 代码实现

1.3.1 数据处理
private Map<String, Object> convertVo2Map(ExportVo vo, Map<String, List<FileUploadBaseEntity>> fileMap) throws IOException {
        Map<String, Object> map = new HashMap<>();
        map.put("region", vo.getRegion());
        map.put("depName", vo.getDepName());
        map.put("capacity", vo.getCapacity());
        map.put("type", vo.getType());
        map.put("towerNo", vo.getTowerNo());
        // 处理 towerInfrastructureList(关联栋舍),每组数据换行
        if (vo.getTowerInfrastructureList() != null && !vo.getTowerInfrastructureList().isEmpty()) {
            String formattedTowerInfrastructureList = vo.getTowerInfrastructureList().stream()
                    .map(towerInfrastructure -> towerInfrastructure.getLineName() + ", " + towerInfrastructure.getBuildingName())
                    .collect(Collectors.joining("\n")); // 每个数据换行
            map.put("towerInfrastructureList", formattedTowerInfrastructureList);
        } else {
            map.put("towerInfrastructureList", "暂无");
        }

        // 处理 towerFodderList(料号),使用逗号分隔
        if (vo.getTowerFodderList() != null && !vo.getTowerFodderList().isEmpty()) {
            String formattedTowerFodderList = vo.getTowerFodderList().stream()
                    .map(towerFodder -> towerFodder.getFodderCode().toString())
                    .collect(Collectors.joining(", "));
            map.put("fodderCode", formattedTowerFodderList);
        } else {
            map.put("fodderCode", "暂无");
        }
    	//处理图片
        List<FileUploadBaseEntity> fileList = fileMap.getOrDefault(vo.getId(), Collections.emptyList());
        if (!fileList.isEmpty()) {
            FileUploadBaseEntity img = fileList.get(0);
            byte[] imageBytes = getImageFromMinio(img.getFileId(), img.getFileName());
            map.put("qrCode", Pictures.ofBytes(imageBytes).size(300, 300).create());
        }
        return map;
    }

​ 这个方法是将从数据库中获得到的vo转换成map,然后通过map动态插入到模板中,其实用map和实体类都可以,并且实体类的更好,能够减少耦合,代码足够健壮,只是我使用的是map的方式,写起来简单,代码量少。

​ 代码并不负责,就只是将值put进map,唯一要说的就是将两个list放进map中时需要进行部分处理

//按照逗号分隔
Collectors.joining(", ")
//每个数据换行
Collectors.joining("\n")
1.3.2 Word导出
构建导出列表
//原始数据
List<ExportVo> result = buildResult3(towerInfoList, infrastructureMap, fodderMap);
//放入一个list中
List<ExportVo> exportVos = new ArrayList<>(result);
转换vo为map
List<Map<String, Object>> dataList = new ArrayList<>();
for (ExportVo vo : exportVos) {
    Map<String, Object> data = convertVo2Map(vo, fileMap);
    dataList.add(data);
}
构造渲染数据,渲染word模板
//构造渲染数据
Map<String, Object> finalData = new HashMap<>();
finalData.put("list", dataList);

//加载模板
ClassPathResource resource = new ClassPathResource("dynamic/template/QRExportTemplate1.docx");
XWPFTemplate tp = XWPFTemplate.compile(resource.getInputStream()).render(finalData);
XWPFDocument doc = tp.getXWPFDocument();
写入本地文件
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String departmentFileName = "料塔二维码" + dateStr + counter++ + ".docx";
File departmentFile = new File(tempDir, departmentFileName);

//写出文件
try (FileOutputStream out = new FileOutputStream(departmentFile)) {
    doc.write(out);
}

2、利用FTL模板进行word导出(适合比较复杂的模板)

新建模板

image-20250527164946938

需要先定义一个模板,其中**KaTeX parse error: Double superscript at position 10: {(mat)!' '̲}**这种都是需要填充的数据,…{(mat)}**,那么当业务代码中,变量值为空的时候,此时导出word时就会报错。所以我们需要设置默认值。如果想要插入图片,那么先插入一个图片进去,后续修改ftl文件。

生成XML文件

image-20250527165603607

重命名为ftl

image-20250527165704667

解决word转xml占位符、变量值被分离的问题

转换处理的ftl文件不可以直接使用,需要进行转换一下,转换代码如下

public static void main(String[] args) {
    String urlSrc= "C:\\Users\\73108\\Desktop\\archiveCardSrc.ftl";
    String urlTarget= "C:\\Users\\73108\\Desktop\\archiveCard.ftl";
    //文件读取-FileReader
    //默认UTF-8编码,可以在构造中传入第二个参数作为编码
    FileReader fileReader = new FileReader(urlSrc);
    //从文件中读取每一行数据
    List<String> strings = fileReader.readLines();
    //文件追加-FileAppender
    //destFile – 目标文件
    //capacity – 当行数积累多少条时刷入到文件
    //isNewLineMode – 追加内容是否为新行
    FileAppender appender = new FileAppender(FileUtil.newFile(urlTarget), 16, true);
    //遍历得到每一行数据
    for (String string : strings) {
        //判断每一行数据中不包含'$'的数据先添加进新文件
        if (!string.contains("$")) {
            appender.append(string);
            continue;
        }
        //如果一行数据中包含'$'变量符将替换为'#$'
        string = string.replaceAll("\\$", "#\\$");
        //然后以'#'切割成每一行(数组),这样一来'$'都将在每一行的开头
        String[] ss = string.split("#");
        // 同一行的数据写到同一行,文件追加自动换行了(最后的完整数据)
        StringBuilder sb = new StringBuilder();
        //遍历每一行(数组ss)
        for (int i = 0; i < ss.length; i++) {
            //暂存数据
            String s1 = ss[i];
            //将不是以'$'开头的行数据放进StringBuilder
            if (!s1.startsWith("$")) {
                sb.append(s1);
                continue;
            }
            //被分离的数据一般都是'${'这样被分开
            //匹配以'$'开头的变量找到'}' 得到索引位置
            int i1 = s1.lastIndexOf("}");
            //先切割得到这个完整体
            String substr = s1.substring(0, i1 + 1);
            //把变量追加到StringBuilder
            sb.append(substr.replaceAll("<[^>]+>", ""));
            //再将标签数据追加到StringBuilder包裹变量
            sb.append(s1.substring(i1 + 1));
        }
        appender.append(sb.toString());
    }
    appender.flush();
    appender.toString();
    System.out.println("success");
}
修改ftl文件图片相关代码
//修改binData,并设置图片名,item.eraNo和item.pic都是变量
<w:binData w:name="wordml://${item.eraNo}.png" xml:space="preserve">${(item.pic)!'ant'}</w:binData>

这里是为了将固定图片换成动态插入图片

image-20250527170022125

接下来防止遍历的都是同一个图片

image-20250528151346304

接下来需要添加遍历

在这里插入图片描述

image-20250528151544583

最后把变量名做统一修改,你是什么就改成什么

image-20250528151632377

最后要注意的一个点,防止刚才修改时把图片那里的修改了
在这里插入图片描述

如此,我们的模板就生成成功了,具体的业务代码就不叙述了,和上面poi-tl的差不多

18103497)]

[外链图片转存中…(img-Um7yZ7f2-1748418103497)]

最后把变量名做统一修改,你是什么就改成什么

[外链图片转存中…(img-bcv7wcK9-1748418103498)]

最后要注意的一个点,防止刚才修改时把图片那里的修改了

[外链图片转存中…(img-5JnFKEci-1748418103498)]

如此,我们的模板就生成成功了,具体的业务代码就不叙述了,和上面poi-tl的差不多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值