接上一篇文章
注:Consts 类放到了 第三篇文章里面
**
一、优化点:
**
优化过后 直接调用 CreateWordPoiUtils 中listToArray 方法 即可;
参数为 对象集合 优化过后增加的动态数据表格 比如有两个 则 对象中 只能有两个字段,不然可能会漏数据等问题
//这是之前的 增加循环数据 需要手动添加
// 插入数据构建
ArrayList<String[]> addList = new ArrayList<>();
addList.add(new String[]{"【插入11】", "【插入12】"});
addList.add(new String[]{"【插入21】", "【插入22】"});
// 插入数据构建
ArrayList<String[]> addList1 = new ArrayList<>();
addList1.add(new String[]{"【插入11】", "【插入12】"});
addList1.add(new String[]{"【插入21】", "【插入22】"});
addList1.add(new String[]{"【插入31】", "【插入32】"});
//map的key为第几个表格
Map<Integer, List<String[]>> map1 = new HashMap<>();
map1.put(2, addList);
map1.put(3, addList1);
//优化过后 直接调用 CreateWordPoiUtils 中listToArray 方法 即可 参数为 对象集合 优化过后增加的动态数据
//表格 比如有两个 则 对象中 只能有两个字段,不然可能会漏数据等问题
//map的key为第几个表格
Map<Integer, List<String[]>> map = new HashMap<>();
map.put(2, CreateWordPoiUtils.listToArray(vos));
map.put(3, CreateWordPoiUtils.listToArray(fileVos));
二、新增功能(全部代码 懒得写)图片可插入表格
1、jar包:以前的jar包就可以去掉了,只用这个就行
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.6.0</version>
</dependency>
2、动态数据 对象类(我需要新增的表个就两个 就两个字段就好了 切记不可多)
package com.zjjw.jxtest.mode.vo;
import lombok.Data;
/**
* @author: chenjiaxiang
* @create: 2022/9/29 14:11
**/
@Data
public class TestFileVo {
private String name;
private String remark;
}
三、全新代码 下面3条 很重要!!!
1)支持插入的图片为 图片的 URL
2)支持插入的图片 为 base64。
3)目前如需插入图片 则需要 在 设置map 的 Key时 如图片为 URL 则key中必须包含 file 或 File ,如为图片为base64时,则 map的key名称 必须包含 base64 或 Base64
/**
* 生成word 工具类
*
* @author: chenjiaxiang
* @create: 2022/9/29 11:27
**/
@Slf4j
public class CreateWordPoiUtils {
private static final String FILE = "file";
private static final String FILE_F = "File";
private static final String BASE64 = "base64";
private static final String BASE64_B = "Base64";
/**
* 文件路径
*/
private static final String FOP = "/Users/chenjx/Downloads/zipceshi/cc.docx";
private static final String PATH = "/Users/chenjx/Downloads/zipceshi/";
/**
* 根据docx模版生成docx文件 适用于做测试用的 main
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
* @param filePath 输出到本地的文件路径
*/
public static void generateWord(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map, String filePath) {
InputStream inputStream = null;
//获取docx解析对象
XWPFDocument xwpfDocument = null;
FileOutputStream fileOutputStream = null;
try {
String fileLocal = String.format("%s%s.docx", filePath, UUID.randomUUID());
// 设置模板输入和结果输出
fileOutputStream = new FileOutputStream(fileLocal);
inputStream = Files.newInputStream(new File(templateName).toPath());
//获取docx解析对象
xwpfDocument = new XWPFDocument(inputStream);
// 处理所有文段数据,除了表格
handleParagraphs(xwpfDocument, insertTextMap);
// 处理表格数据
handleTable(xwpfDocument, insertTextMap, map);
//二进制OutputStream
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
//文档写入流
xwpfDocument.write(fileOutputStream);
log.info("word生成成功");
//OutputStream写入InputStream二进制流
// return new ByteArrayInputStream(baos.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (xwpfDocument != null) {
xwpfDocument.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 根据docx模版生成docx文件
*
* @param templateName 模版路径名称
* @param insertTextMap ${} 字符替换
* @param map 表格数据需循环新增 动态数据
*/
public static InputStream generateWord(String templateName, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map) {
ClassPathResource classPathResource = new ClassPathResource(templateName);
InputStream inputStream = null;
//获取docx解析对象
XWPFDocument xwpfDocument = null;
ByteArrayOutputStream baos = null;
try {
inputStream = classPathResource.getInputStream();
//获取docx解析对象
xwpfDocument = new XWPFDocument(inputStream);
// 处理所有文段数据,除了表格
handleParagraphs(xwpfDocument, insertTextMap);
// 处理表格数据
handleTable(xwpfDocument, insertTextMap, map);
//二进制OutputStream
baos = new ByteArrayOutputStream();
//文档写入流
xwpfDocument.write(baos);
log.info("word生成成功");
//OutputStream写入InputStream二进制流
return new ByteArrayInputStream(baos.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (xwpfDocument != null) {
xwpfDocument.close();
}
if (baos != null) {
baos.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 处理需要替换的数据和 需要表格新增的数据
*
* @param xwpfDocument docx
* @param insertTextMap ${} 数据
* @param map 表格数据
*/
public static void handleTable(XWPFDocument xwpfDocument, Map<String, Object> insertTextMap, Map<Integer, List<String[]>> map) {
List<XWPFTable> tables = xwpfDocument.getTables();
int tableIndex = 0;
for (XWPFTable table : tables) {
//表格坐标
tableIndex++;
List<XWPFTableRow> rows = table.getRows();
if (rows.size() > 1) {
if (isReplacement(table.getText())) {
// 替换数据
rows.stream().map(XWPFTableRow::getTableCells).forEach(tableCells -> {
tableCells.stream().filter(tableCell -> isReplacement(tableCell.getText())).forEach(tableCell -> {
List<XWPFParagraph> paragraphs = tableCell.getParagraphs();
//替换数据
paragraphs.stream().map(XWPFParagraph::getRuns).forEach(runs -> {
//获取map的key
runs.forEach(run -> {
String key = matchesKey(tableCell.getText(), insertTextMap);
if (Objects.isNull(insertTextMap.get(key))) {
run.setText("", 0);
return;
}
String string = insertTextMap.get(key).toString().replace("${", Consts.EMPTY_STRING).replace("}", Consts.EMPTY_STRING);
try {
if (key.contains(FILE) || key.contains(FILE_F)) {
run.setText("", 0);
for (String s1 : string.split(Consts.DEFAULT_SEPARATOR)) {
//url转input
//参数1:图片流数据 参数2:图片类型 参数3 图片名称 参数4:图片宽度 参数5:图片高度
InputStream inputStream = getInputStream(s1);
run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, UUID.randomUUID().toString(), Units.toEMU(128), Units.toEMU(128));
inputStream.close();
}
} else if (key.contains(BASE64) || key.contains(BASE64_B)) {
run.setText("", 0);
for (String s1 : string.split(Consts.DEFAULT_SEPARATOR)) {
//base64转input
//参数1:图片流数据 参数2:图片类型 参数3 图片名称 参数4:图片宽度 参数5:图片高度
InputStream inputStream = base64Input(s1);
run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, UUID.randomUUID().toString(), Units.toEMU(128), Units.toEMU(128));
inputStream.close();
}
} else {
run.setText(insertTextMap.get(key).toString(), 0);
}
} catch (InvalidFormatException | IOException e) {
throw new RuntimeException(e);
}
});
});
});
});
} else {
//处理新增表格数据
List<String[]> strings = map.get(tableIndex);
// 插入数据
IntStream.range(1, strings.size()).forEach(i -> table.createRow());
List<XWPFTableRow> rowList = table.getRows();
for (int i = 1; i < rowList.size(); i++) {
XWPFTableRow xwpfTableRow = rowList.get(i);
List<XWPFTableCell> tableCells = xwpfTableRow.getTableCells();
for (int j = 0; j < tableCells.size(); j++) {
XWPFTableCell xwpfTableCell = tableCells.get(j);
xwpfTableCell.setText(strings.get(i - 1)[j]);
}
}
}
}
}
}
/**
* 替换带有 ${}的数据
*
* @param wordValue 表格文本
* @param map 数据
* @return 数据
*/
public static String matchesValue(String wordValue, Map<String, Object> map) {
for (String s : map.keySet()) {
String s1 = "${" + s + "}";
if (s1.equals(wordValue)) {
wordValue = String.valueOf(map.get(s));
}
}
return wordValue;
}
/**
* 判断是否有包含需替换的数据
*
* @param text 文本
* @return boolean
*/
public static boolean isReplacement(String text) {
boolean check = false;
if (text.contains(Consts.DOLLAR)) {
check = true;
}
return check;
}
/**
* 处理文段数据
*
* @param xwpfDocument docx
* @param insertTextMap ${}数据
*/
public static void handleParagraphs(XWPFDocument xwpfDocument, Map<String, Object> insertTextMap) {
xwpfDocument.getParagraphs().forEach(paragraph -> {
String text = paragraph.getText();
if (isReplacement(text)) {
for (XWPFRun run : paragraph.getRuns()) {
// 判断带有${}的run
run.setText(matchesValue(run.text(), insertTextMap), 0);
}
}
});
}
/**
* base64转为输入流
*
* @param base64 编码
* @return 输入流
*/
public static InputStream base64Input(String base64) {
BASE64Decoder decoder = new BASE64Decoder();
InputStream inputStream;
// Base64解码
byte[] byteArr = new byte[0];
try {
byteArr = decoder.decodeBuffer(base64);
inputStream = new ByteArrayInputStream(byteArr);
} catch (IOException e) {
throw new RuntimeException(e);
}
return inputStream;
}
/**
* 【获取网络文件的输入流】
*
* @param filePath: 网络文件路径
* @return java.io.InputStream
*/
public static InputStream getInputStream(String filePath) throws IOException {
InputStream inputStream = null;
//创建URL
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();
}
return inputStream;
}
/**
* 获取map的key
*/
public static String matchesKey(String wordValue, Map<String, Object> map) {
for (String s : map.keySet()) {
String s1 = "${" + s + "}";
if (s1.equals(wordValue)) {
wordValue = s;
}
}
return wordValue;
}
/**
* 图片URL转Base64编码
*
* @param imgUrl 图片URL
* @return Base64编码
*/
public static String imageUrlToBase64(String imgUrl) {
URL url = null;
InputStream is = null;
ByteArrayOutputStream outStream = null;
HttpURLConnection httpUrl = null;
try {
url = new URL(imgUrl);
httpUrl = (HttpURLConnection) url.openConnection();
httpUrl.connect();
httpUrl.getInputStream();
is = httpUrl.getInputStream();
outStream = new ByteArrayOutputStream();
//创建一个Buffer字符串
byte[] buffer = new byte[1024];
//每次读取的字符串长度,如果为-1,代表全部读取完毕
int len = 0;
//使用输入流从buffer里把数据读取出来
while ((len = is.read(buffer)) != -1) {
//用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
outStream.write(buffer, 0, len);
}
// 对字节数组Base64编码
return encode(outStream.toByteArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (outStream != null) {
outStream.close();
}
if (httpUrl != null) {
httpUrl.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* 图片转字符串
*
* @param image 图片Buffer
* @return Base64编码
*/
public static String encode(byte[] image) {
BASE64Encoder decoder = new BASE64Encoder();
return replaceEnter(decoder.encode(image));
}
/**
* 字符替换
*
* @param str 字符串
* @return 替换后的字符串
*/
public static String replaceEnter(String str) {
String reg = "[\n-\r]";
Pattern p = Pattern.compile(reg);
Matcher m = p.matcher(str);
return m.replaceAll("");
}
/**
* 将对象集合转为一个String[] 集合
*
* @param tList 对象集合
* @param <T> 参数
* @return 数组集合
*/
public static <T> List<String[]> listToArray(List<T> tList) {
ArrayList<String[]> arrayList = new ArrayList<>();
tList.forEach(t -> {
String[] filedName = getFiledName(t);
List<String> to = Arrays.stream(filedName).map(s -> getFieldValueByName(s, t))
.map(fieldValueByName -> StringUtils.isEmpty(fieldValueByName) ? "" : String.valueOf(fieldValueByName)).collect(Collectors.toList());
arrayList.add(to.toArray(new String[]{}));
});
return arrayList;
}
/**
* 获取对象的 属性名
*
* @param o 对象名称
* @return 属性数组
*/
private static String[] getFiledName(Object o) {
Field[] fields = o.getClass().getDeclaredFields();
return Arrays.stream(fields).map(Field::getName).toArray(String[]::new);
}
/**
* 根据字段名 获取该字段值
*
* @param fieldName 字段名
* @param o 对象
* @return 值
*/
private static Object getFieldValueByName(String fieldName, Object o) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter);
return method.invoke(o);
} catch (Exception e) {
log.error("获取属性值失败!" + e, e);
}
return null;
}
}
**
四、举例子:
**
/**
* 模版文件路径
*/
private static final String FOP = "/Users/chenjx/Downloads/zipceshi/cc.docx";
private static final String PATH = "/Users/chenjx/Downloads/zipceshi/";
public static void main(String[] args) {
// 替换文本数据构建
HashMap<String, Object> insertTextMap = new HashMap<>(16);
String fileTwo = "https://img2.woyaogexing.com/2022/08/23/cc8909ab73204efd!400x400.png" +
",https://img2.woyaogexing.com/2022/08/23/cfcf821f37fa046e!400x400.jpg";
String file = "https://img2.woyaogexing.com/2022/08/23/ea7f1d004fea3f11.jpg";
String base64 = CreateWordPoiUtils.imageUrlToBase64(file);
insertTextMap.put("orgName", "单位啊啊啊--");
insertTextMap.put("base64", base64);
insertTextMap.put("deptName", "我不听");
insertTextMap.put("filePath", fileTwo);
insertTextMap.put("nameBase64", base64);
insertTextMap.put("nameFile", file);
//模拟数据查出的数据 listOne
List<TestFileVo> vos = new ArrayList<>();
TestFileVo vo1 = new TestFileVo();
vo1.setName("1");
// vo1.setRemark("1");
TestFileVo vo2 = new TestFileVo();
vo2.setName("2");
vo2.setRemark("2");
TestFileVo vo3 = new TestFileVo();
vo3.setName("3");
vo3.setRemark("3");
TestFileVo vo4 = new TestFileVo();
vo4.setName("4");
vo4.setRemark("4");
vos.add(vo1);
vos.add(vo2);
vos.add(vo3);
vos.add(vo4);
//模拟数据查出数据 listTwo
List<TestFileVo> fileVos = new ArrayList<>();
TestFileVo fileVos1 = new TestFileVo();
fileVos1.setName("5");
fileVos1.setRemark("5");
TestFileVo fileVos2 = new TestFileVo();
fileVos2.setName("6");
fileVos2.setRemark("6");
TestFileVo fileVos3 = new TestFileVo();
fileVos3.setName("7");
fileVos3.setRemark("7");
TestFileVo fileVos4 = new TestFileVo();
fileVos4.setName("8");
fileVos4.setRemark("8");
TestFileVo fileVos5 = new TestFileVo();
fileVos5.setName("9");
// fileVos5.setRemark("");
fileVos.add(fileVos1);
fileVos.add(fileVos2);
fileVos.add(fileVos3);
fileVos.add(fileVos4);
fileVos.add(fileVos5);
// // 插入数据构建
// ArrayList<String[]> addList = new ArrayList<>();
//
// addList.add(new String[]{"【插入11】", "【插入12】"});
// addList.add(new String[]{"【插入21】", "【插入22】"});
// // 插入数据构建
// ArrayList<String[]> addList1 = new ArrayList<>();
// addList1.add(new String[]{"【插入11】", "【插入12】"});
// addList1.add(new String[]{"【插入21】", "【插入22】"});
// addList1.add(new String[]{"【插入31】", "【插入32】"});
// //map的key为第几个表格
// Map<Integer, List<String[]>> map1 = new HashMap<>();
// map1.put(2, addList);
// map1.put(3, addList1);
//map的key为第几个表格
Map<Integer, List<String[]>> map = new HashMap<>();
map.put(2, CreateWordPoiUtils.listToArray(vos));
map.put(3, CreateWordPoiUtils.listToArray(fileVos));
CreateWordPoiUtils.generateWord(FOP, insertTextMap, map, PATH);
log.info("word生成成功");
}
五、效果:
模版存放位置:(可放到本地,也可放项目里,代码里有演示的)
模版:
导出效果: