java动态导出word(两种方法)_java导出word
Java动态模板导出Word
总结了一下我自己使用过的三种动态模板导出,两个Word导出,用的是不同的方法,一个是POI-TL,另一个是用FTL模板。
1、利用poi-tl工具进行导出word(适合较简单的模板)
我要实现的功能大体是这样的,需要在导出多个部门的数据,然后每个部门中会有多条数据,我要将一个部门的数据全部动态导出到一个word文件中,每条数据占一页,生成单独的word,然后把多个文件生成放入同一个压缩包内,用户导出时下载压缩包
1.1 依赖准备
cn.afterturn easypoi-base 4.4.0 cn.afterturn easypoi-web 4.4.0 cn.afterturn easypoi-annotation 4.4.0 org.apache.poi poi 4.1.1 org.apache.poi poi-ooxml 4.1.1
1.2 创建word模板
按照这个形式在word中自定义你的模板,{{item}}的形式会被替换掉,要注意如果要插入图片则需要在前面加个@符号,如{{@qrCode}}
将模板放入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导出(适合比较复杂的模板)
新建模板
需要先定义一个模板,其中**KaTeX parse error: Double superscript at position 10: {(mat)!\' \'̲}**这种都是需要填充的数据,…{(mat)}**,那么当业务代码中,变量值为空的时候,此时导出word时就会报错。所以我们需要设置默认值。如果想要插入图片,那么先插入一个图片进去,后续修改ftl文件。
生成XML文件
重命名为ftl
解决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>
这里是为了将固定图片换成动态插入图片
接下来防止遍历的都是同一个图片
接下来需要添加遍历
最后把变量名做统一修改,你是什么就改成什么
最后要注意的一个点,防止刚才修改时把图片那里的修改了
如此,我们的模板就生成成功了,具体的业务代码就不叙述了,和上面poi-tl的差不多
18103497)]
[外链图片转存中…(img-Um7yZ7f2-1748418103497)]
最后把变量名做统一修改,你是什么就改成什么
[外链图片转存中…(img-bcv7wcK9-1748418103498)]
最后要注意的一个点,防止刚才修改时把图片那里的修改了
[外链图片转存中…(img-5JnFKEci-1748418103498)]
如此,我们的模板就生成成功了,具体的业务代码就不叙述了,和上面poi-tl的差不多