模版文件
jar包
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version>
</dependency>
可能会出现poi jar包冲突 如出现 将老版本的poi排掉就好 ,也可能出现 log4j 版本低 用下面这个就行
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
代码
package com.util;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.HyperlinkTextRenderData;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.deepoove.poi.util.PoitlIOUtils;
import com.zjjw.platform.core.exception.BusinessRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.springframework.core.io.ClassPathResource;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* POI-TL 进行转换 支持多图片及循环增加表格
* <p>
* 使用方式:
* 参数传入类型 Map<String,Object>
* 模版 纯文本 为:
* {{name}} 则map.put("name",params) name 是自己定义的名字 下面的都是自己定义名字,只是示例
* <p>
* 图片为:
* {{@imgUrl}} 只要是 图片的 都需在参数前 加 @ 符号 这个只支持单张图片!!! map.put("imgUrl",params)
* <p>
* 循环表格:表格示例:
* {{list}} 用户名称 | 年龄 | 图片
* [user] | [age] | [?img1][@#this][/img1]
* list 为表格定义配置 需在配置文件配置,已提供默认表格配置 和 自定义配置方法 如一个文档中有 多处 需要循环表格 {{list}} 不可重复
* 示例:
* String avatar = "http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png";
* List<PictureRenderData> imgList = Arrays.stream(avatar.split(",")).map(s -> Pictures.ofStream(getInputStream(s))
* .size(50, 50).create()).collect(Collectors.toList());
* List<Map<String, Object>> par = new ArrayList<>();
* for (int i = 0; i <= 1; i++) {
* Map<String, Object> map1 = new HashMap<>(16);
* map1.put("user", "测试一下吧");
* map1.put("age", "123");
* map1.put("img1", imgList);
* par.add(map1);
* }
* map.put("list", par);
* 其中 getInputStream(s) 方法是 获取url文件流方法,这里也可填入文件流 或 Pictures. 按自己需求来
* <p>
* 空白处循环添加图片:
* {{?imga}}
* {{@#this}}
* {{#table}}
* {{/imga}}
* <p>
* {{#table}} 占位符
* <p>
* 放入map方式如上
* <p>
* 支持 删除表格的 第一行 也就是表头 removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*
* @author: chenjiaxiang
* @create: 2023/9/12 10:47
**/
@Slf4j
public class PoiTlToWordUtils {
/**
* 导出文件到本地
*
* @param map 数据集合
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版地址
* @param outPath 输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportWordToLocality(Map<String, Object> map, List<String> nameList, String filePath, String outPath, List<String> removeTags) {
exportFile(map, nameList, filePath, outPath, null, removeTags);
}
/**
* 导出文件到本地,无循环表格
*
* @param map 数据集合
* @param filePath 模版地址
* @param outPath 输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportWordToLocality(Map<String, Object> map, String filePath, String outPath, List<String> removeTags) {
exportFile(map, null, filePath, outPath, null, removeTags);
}
/**
* 导出文件到本地
*
* @param map 数据集合
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版地址
* @param outPath 输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
* @param configure 自定义配置文件
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportWordToLocality(Map<String, Object> map, List<String> nameList, String filePath, String outPath, Configure configure, List<String> removeTags) {
exportFile(map, nameList, filePath, outPath, configure, removeTags);
}
/**
* 导出文件到本地,无循环表格
*
* @param map 数据集合
* @param filePath 模版地址
* @param outPath 输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
* @param configure 自定义配置文件
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportWordToLocality(Map<String, Object> map, String filePath, String outPath, Configure configure, List<String> removeTags) {
exportFile(map, null, filePath, outPath, configure, removeTags);
}
/**
* 浏览器下载
*
* @param response response
* @param map 数据信息
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版文件地址
* @param fileName 文件名称
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, List<String> nameList, String filePath, String fileName, List<String> removeTags) {
exportFile(response, map, nameList, filePath, fileName, null, removeTags);
}
/**
* 浏览器下载,无循环表格
*
* @param response response
* @param map 数据信息
* @param filePath 模版文件地址
* @param fileName 文件名称
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, String filePath, String fileName, List<String> removeTags) {
exportFile(response, map, null, filePath, fileName, null, removeTags);
}
/**
* 浏览器下载 支持自定义配置文件
*
* @param response response
* @param map 数据信息
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版文件地址
* @param fileName 文件名称
* @param configure 自定义配置文件
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, List<String> nameList, String filePath, String fileName, Configure configure, List<String> removeTags) {
exportFile(response, map, nameList, filePath, fileName, configure, removeTags);
}
/**
* 浏览器下载,无循环表格,支持自定义配置文件
*
* @param response response
* @param map 数据信息
* @param filePath 模版文件地址
* @param fileName 文件名称
* @param configure 自定义配置文件
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static void exportBrowser(HttpServletResponse response, Map<String, Object> map, String filePath, String fileName, Configure configure, List<String> removeTags) {
exportFile(response, map, null, filePath, fileName, configure, removeTags);
}
/**
* 文件生成返回 InputStream
*
* @param map 数据集合
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版地址
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static InputStream exportFileInputStream(Map<String, Object> map, List<String> nameList, String filePath, List<String> removeTags) {
return exportFile(map, nameList, filePath, null, removeTags);
}
/**
* 文件生成返回 InputStream ,无循环表格
*
* @param map 数据集合
* @param filePath 模版地址
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static InputStream exportFileInputStream(Map<String, Object> map, String filePath, List<String> removeTags) {
return exportFile(map, null, filePath, null, removeTags);
}
/**
* 文件生成返回 InputStream
*
* @param map 数据集合
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版地址
* @param configure 自定义配置文件
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static InputStream exportFileInputStream(Map<String, Object> map, List<String> nameList, String filePath, Configure configure, List<String> removeTags) {
return exportFile(map, nameList, filePath, configure, removeTags);
}
/**
* 文件生成返回 InputStream ,无循环表格
*
* @param map 数据集合
* @param filePath 模版地址
* @param configure 自定义配置文件
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
public static InputStream exportFileInputStream(Map<String, Object> map, String filePath, Configure configure, List<String> removeTags) {
return exportFile(map, null, filePath, configure, removeTags);
}
/**
* 导出文件到本地
*
* @param map 数据集合
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版地址
* @param outPath 输出地址及文件名称 需要后缀 ,如 /xx/xx/xx.docx
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
private static void exportFile(Map<String, Object> map, List<String> nameList, String filePath, String outPath, Configure configure, List<String> removeTags) {
XWPFTemplate template = null;
BufferedOutputStream bos = null;
OutputStream out = null;
try {
// 获取Word模板,模板存放路径在项目的resources目录下
template = common(filePath, map, nameList, configure);
out = new ByteArrayOutputStream();
bos = new BufferedOutputStream(out);
template.write(bos);
bos.flush();
out.flush();
removeTags(template, removeTags);
template.writeAndClose(Files.newOutputStream(Paths.get(outPath)));
} catch (Exception e) {
log.info("[生成word文件]-失败,{}", e.getMessage());
throw new BusinessRuntimeException(e.getMessage());
} finally {
closeStream(template);
closeStream(bos);
closeStream(out);
}
}
/**
* 浏览器下载
*
* @param response response
* @param map 数据信息
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版文件地址
* @param fileName 文件名称
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
private static void exportFile(HttpServletResponse response, Map<String, Object> map, List<String> nameList, String filePath, String fileName, Configure configure, List<String> removeTags) {
XWPFTemplate template = null;
BufferedOutputStream bos = null;
OutputStream out = null;
try {
// 获取Word模板,模板存放路径在项目的resources目录下
template = common(filePath, map, nameList, configure);
// 浏览器端下载 设置响应
setResponse(response, fileName);
out = response.getOutputStream();
bos = new BufferedOutputStream(out);
removeTags(template, removeTags);
template.write(bos);
bos.flush();
out.flush();
PoitlIOUtils.closeQuietlyMulti(template, bos, out);
} catch (Exception e) {
log.info("[生成word文件]-失败,{}", e.getMessage());
throw new BusinessRuntimeException(e.getMessage());
} finally {
closeStream(template);
closeStream(bos);
closeStream(out);
}
}
/**
* 文件生成返回 InputStream
*
* @param map 数据集合
* @param nameList 循环增加表格的标识名称集合
* @param filePath 模版地址
* @param removeTags 需要去处的表格第一行的标签 word中定义为 {{xxx}} 集合中传入 xxx
*/
private static InputStream exportFile(Map<String, Object> map, List<String> nameList, String filePath, Configure configure, List<String> removeTags) {
XWPFTemplate template = null;
ByteArrayOutputStream bos = null;
try {
// 获取Word模板,模板存放路径在项目的resources目录下
template = common(filePath, map, nameList, configure);
bos = new ByteArrayOutputStream();
removeTags(template, removeTags);
template.write(bos);
return new ByteArrayInputStream(bos.toByteArray());
} catch (Exception e) {
log.info("[生成word文件]-失败,{}", e.getMessage());
throw new BusinessRuntimeException(e.getMessage());
} finally {
closeStream(template);
closeStream(bos);
}
}
private static XWPFTemplate common(String filePath, Map<String, Object> map, List<String> nameList, Configure configure) {
//获取模版文件的文件流
XWPFTemplate template;
InputStream ins = null;
try {
ins = getTemplate(filePath);
//如果有循环添加的数据表格的话 则绑定
template = CollectionUtils.isNotEmpty(nameList) ? XWPFTemplate.compile(ins, Objects.nonNull(configure) ? configure : getConfig(nameList)).render(map)
: XWPFTemplate.compile(ins).render(map);
} finally {
closeStream(ins);
}
return template;
}
private static <T extends Closeable> void closeStream(T ins) {
if (ins != null) {
try {
ins.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 绑定循环表格标识
*
* @param nameList 标识集合
* @return 配置
*/
private static Configure getConfig(List<String> nameList) {
//使用行循环插件
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
ConfigureBuilder builder = Configure.builder();
nameList.forEach(name -> builder.bind(name, policy));
return builder.build();
}
/**
* 【获取网络文件的输入流】
*
* @param filePath: 网络文件路径
* @return java.io.InputStream
*/
public static InputStream getInputStream(String filePath) {
InputStream inputStream = null;
//创建URL
try {
URL url = new URL(filePath);
//试图连接并取得返回状态码
URLConnection urlconn = url.openConnection();
urlconn.connect();
HttpURLConnection httpconn = (HttpURLConnection) urlconn;
int httpResult = httpconn.getResponseCode();
if (httpResult == HttpURLConnection.HTTP_OK) {
inputStream = urlconn.getInputStream();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return inputStream;
}
/**
* 获取模版文件流信息
*
* @param filePath 模版路径
* @return 流
*/
public static InputStream getTemplate(String filePath) {
// 获取Word模板,模板存放路径在项目的resources目录下
ClassPathResource classPathResource = new ClassPathResource(filePath);
try {
return classPathResource.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 放入 response
*
* @param response 响应
* @param fileName 文件名称
*/
private static void setResponse(HttpServletResponse response, String fileName) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String filePoiName;
try {
filePoiName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + filePoiName + ".docx");
}
/**
* 去处表格标签
*
* @param template word
* @param removeTags 标签集合
*/
private static void removeTags(XWPFTemplate template, List<String> removeTags) {
if (CollectionUtils.isNotEmpty(removeTags)) {
traverseTables(template.getXWPFDocument(), removeTags);
}
}
/**
* 根据标签去除该表格的 第一行
*
* @param document 文件
* @param removeTags 标签集合
*/
private static void traverseTables(XWPFDocument document, List<String> removeTags) {
List<XWPFTable> tables = document.getTables();
// 递归处理嵌套表格
tables.forEach(table -> {
containsMarkerInFirstRow(table, removeTags);
traverseNestedTables(table, removeTags);
});
}
/**
* 递归处理嵌套表格
*
* @param table 表格
* @param removeTags 需要去处的表格标签
*/
private static void traverseNestedTables(XWPFTable table, List<String> removeTags) {
table.getRows().forEach(row -> row.getTableCells().forEach(cell -> traverseTables(cell, removeTags)));
}
/**
* 传入cell 进行判断当前行是否包含
*/
private static void traverseTables(XWPFTableCell cell, List<String> removeTags) {
cell.getTables().forEach(table -> {
// 判断是否包含标签 是的话则去除表格表头
containsMarkerInFirstRow(table, removeTags);
traverseNestedTables(table, removeTags);
});
}
/**
* 判断是否包含标签 是的话则去除表格表头
*
* @param table 表格
* @param removeTags 需要去处的表格信息
*/
private static void containsMarkerInFirstRow(XWPFTable table, List<String> removeTags) {
List<XWPFTableRow> rows = table.getRows();
int a = 0;
if (!rows.isEmpty()) {
for (XWPFTableRow row : rows) {
if (removeTags.stream().anyMatch(removeTag -> row.getTable().getText().contains(String.format("{{%s}}", removeTag)) && rows.indexOf(row) == 0)) {
table.removeRow(0);
a = 1;
}
if (a > 0) {
break;
}
}
}
}
private static final String OUT_PATH = "/Users/chenjx/Downloads/zipceshi/0911/";
private static final String FOP = "/file/elTest123.docx";
public static void main(String[] args) {
Map<String, Object> m = new HashMap<>();
m.put("baseName", "爱就是打卡上");
m.put("imgUrl", Pictures.ofStream(getInputStream("http://img.crcz.com/allimg/202003/27/1585280268112506.jpg"))
.size(50, 50).create());
//try {
String avatar = "http://img.crcz.com/allimg/202003/27/1585280268105939.jpg," +
"http://img.crcz.com/allimg/202003/27/1585280268458675.jpg," +
"http://img.crcz.com/allimg/202003/26/1585192258743890-lp.jpg";
List<PictureRenderData> imgList = Arrays.stream(avatar.split(",")).map(s -> Pictures.ofStream(getInputStream(s))
.size(50, 50).create()).collect(Collectors.toList());
List<Map<String, Object>> par = new ArrayList<>();
for (int i = 0; i <= 1; i++) {
Map<String, Object> map = new HashMap<>(16);
map.put("user", "测试一下吧");
map.put("pag", "123");
map.put("img1", imgList);
par.add(map);
}
m.put("imgs", imgList);
m.put("img", imgList);
m.put("list", par);
m.put("list1", par);
m.put("list3", par);
m.put("imga", imgList);
m.put("mo", new HyperlinkTextRenderData("测试超链接", "http://www.baidu.com"));
List<Map<String, Object>> par1 = new ArrayList<>();
for (int i = 0; i <= 2; i++) {
Map<String, Object> map = new LinkedHashMap<>(16);
map.put("test1", "测试一下吧");
map.put("test2", "123");
map.put("test3", imgList);
par1.add(map);
}
exportWordToLocality(m, Arrays.asList("list", "list1", "list3"), FOP, OUT_PATH + UUID.randomUUID() + ".docx", Arrays.asList("remove_tit", "remove_tit1"));
}
}