package com.glodon.estate.jrzhj.lhlt.service.common.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpUtil;
import com.glodon.estate.jrzhj.lhlt.service.common.pojo.Export2WordDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.Units;
import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.usermodel.*;
import org.jetbrains.annotations.NotNull;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;
/**
* Word文档工具类
* 用于生成带有页眉和内容的Word文档
* 此版本用poi编写, 基本可用,但是轮兼容性还是不如末班引擎.
*
* @author chengzhengbo
* @date 2025/03/22 13:59
*/
@Slf4j
public class WordUtilV2 {
/**
* 字体样式
*/
private static final class FontStyle {
static final String SONGTI = "宋体";
static final int HEADER_TITLE_SIZE_36 = 36;
static final int HEADER_TEXT_SIZE_24 = 28;
static final int HEADER_TEXT_SIZE_20 = 24;
}
/**
* 生成Word文档
*
* @param dto 数据
* @param file 输出file
* @throws Exception 处理异常
*/
public static void toWord(Export2WordDTO dto, File file) throws Exception {
// 创建文档
XWPFDocument doc = new XWPFDocument();
clearTable(doc);
// 设置页眉
addHeader(doc, dto);
// 添加文档内容
addDocumentContent(doc, dto);
// 保存文档
try (FileOutputStream out = new FileOutputStream(file)) {
doc.write(out);
}
}
private static void clearTable(XWPFDocument doc) {
// 清理文档起始处可能存在的默认段落
while (!doc.getParagraphs().isEmpty()) {
doc.removeBodyElement(0);
}
// 清理文档起始处可能存在的默认表格
List<XWPFTable> tables = doc.getTables();
while (!tables.isEmpty()) {
doc.removeBodyElement(doc.getPosOfTable(tables.get(0)));
tables = doc.getTables();
}
}
/**
* 添加页眉
*/
private static void addHeader(XWPFDocument doc, Export2WordDTO dto) {
// 设置页面边距,确保页眉有足够空间显示
CTSectPr sectPr = doc.getDocument().getBody().addNewSectPr();
CTPageMar pageMar = sectPr.addNewPgMar();
pageMar.setTop(BigInteger.valueOf(1440)); // 上边距 1 英寸
pageMar.setRight(BigInteger.valueOf(1440)); // 右边距 1 英寸
pageMar.setBottom(BigInteger.valueOf(1440)); // 下边距 1 英寸
pageMar.setLeft(BigInteger.valueOf(1440)); // 左边距 1 英寸
pageMar.setHeader(BigInteger.valueOf(1440)); // 页眉距离 0.5 英寸
// 创建页眉
XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT);
// 添加页眉标题
if (dto.getHeaderTitle() != null) {
XWPFParagraph titlePara = header.createParagraph();
titlePara.setAlignment(ParagraphAlignment.CENTER);
XWPFRun titleRun = titlePara.createRun();
titleRun.setText(dto.getHeaderTitle());
titleRun.setFontFamily(FontStyle.SONGTI);
// POI中字号是Word中的一半
titleRun.setFontSize(FontStyle.HEADER_TITLE_SIZE_36 / 2);
titleRun.setBold(true);
}
// 添加页眉文本
if (dto.getHeaderText() != null) {
XWPFParagraph textPara = header.createParagraph();
XWPFRun textRun = textPara.createRun();
textRun.setText(dto.getHeaderText());
textRun.setFontFamily(FontStyle.SONGTI);
textRun.setFontSize(FontStyle.HEADER_TEXT_SIZE_20 / 2);
// 添加制表符
textPara.setAlignment(ParagraphAlignment.LEFT);
CTTabs tabs = textPara.getCTP().getPPr().addNewTabs();
CTTabStop tabStop = tabs.addNewTab();
tabStop.setVal(STTabJc.RIGHT);
tabStop.setPos(BigInteger.valueOf(9000));
// 添加页码
textRun = textPara.createRun();
textRun.addTab();
textRun.setText("第");
textRun.setFontFamily(FontStyle.SONGTI);
textRun.setFontSize(FontStyle.HEADER_TEXT_SIZE_20 / 2);
// 插入页码域
textRun.getCTR().addNewFldChar().setFldCharType(STFldCharType.BEGIN);
textRun = textPara.createRun();
textRun.setText(" PAGE ");
textRun.getCTR().addNewFldChar().setFldCharType(STFldCharType.END);
textRun = textPara.createRun();
textRun.setText("页");
textRun.setFontFamily(FontStyle.SONGTI);
textRun.setFontSize(FontStyle.HEADER_TEXT_SIZE_20 / 2);
}
}
/**
* 添加文档内容
*/
private static void addDocumentContent(XWPFDocument doc, Export2WordDTO dto) throws Exception {
// 创建表格
XWPFTable table = doc.createTable();
setTableStyle(table);
table.removeRow(0);
// 添加前3行并合并单元格
addTableRow(table, dto.getRow1(), true);
addTableRow(table, dto.getRow2(), true);
addTableRow(table, dto.getRow3(), true);
// 添加中间5行
addTableRow(table, dto.getRow4(), false);
addTableRow(table, dto.getRow5(), false);
addTableRow(table, dto.getRow6(), false);
addTableRow(table, dto.getRow7(), false);
addTableRow(table, dto.getRow8(), false);
// 添加检查情况标题行
addTitleRow(table, dto.getRow9());
// 添加检查综述
addSectionTitle(table, dto.getRow10());
addEmptyRow(table);
// 添加安全问题
addSectionTitle(table, dto.getRow11());
addProblemContent(table, dto.getAqjcDataList());
// 添加质量问题
addSectionTitle(table, dto.getRow12());
addProblemContent(table, dto.getZljcDataList());
}
/**
* 设置表格样式
*/
private static void setTableStyle(XWPFTable table) {
// 设置表格宽度
table.setWidth("100%");
// 设置表格边框
table.setBottomBorder(XWPFTable.XWPFBorderType.SINGLE, 4, 0, "auto");
table.setTopBorder(XWPFTable.XWPFBorderType.SINGLE, 4, 0, "auto");
table.setLeftBorder(XWPFTable.XWPFBorderType.SINGLE, 4, 0, "auto");
table.setRightBorder(XWPFTable.XWPFBorderType.SINGLE, 4, 0, "auto");
table.setInsideHBorder(XWPFTable.XWPFBorderType.SINGLE, 4, 0, "auto");
table.setInsideVBorder(XWPFTable.XWPFBorderType.SINGLE, 4, 0, "auto");
// 设置表格与上下文的间距为 0
CTTblPr tableProperties = table.getCTTbl().getTblPr() == null ? table.getCTTbl().getTblPr() : table.getCTTbl().addNewTblPr();
if (!tableProperties.isSetTblCellMar()) {
tableProperties.addNewTblCellMar();
}
CTTblCellMar tblCellMar = tableProperties.getTblCellMar();
if (tblCellMar.getTop() == null) {
tblCellMar.addNewTop();
}
tblCellMar.getTop().setW(BigInteger.ZERO);
}
/**
* 添加表格行
*/
private static void addTableRow(XWPFTable table, String[] rowData, boolean merge) {
XWPFTableRow row = getXwpfTableRow(table, rowData);
// 合并单元格
if (merge) {
mergeCells(row, 1);
}
}
private static XWPFTableRow getXwpfTableRow(XWPFTable table) {
return getXwpfTableRow(table, new String[]{"", "", "", ""});
}
@NotNull
private static XWPFTableRow getXwpfTableRow(XWPFTable table, String[] rowData) {
XWPFTableRow row = table.createRow();
setParagraph(row, rowData[0], 0, true);
setParagraph(row, rowData[1], 1, false);
setParagraph(row, rowData[2], 2, true);
setParagraph(row, rowData[3], 3, false);
row.setHeight(500);
return row;
}
@NotNull
private static XWPFTableRow getXwpfTableRowNotBold(XWPFTable table, String[] rowData) {
XWPFTableRow row = table.createRow();
setParagraph(row, rowData[0], 0, false);
setParagraph(row, rowData[1], 1, false);
setParagraph(row, rowData[2], 2, false);
setParagraph(row, rowData[3], 3, false);
row.setHeight(500);
return row;
}
private static void setParagraph(XWPFTableRow row, String text, int i, boolean blod) {
XWPFTableCell cell = row.getCell(i);
if (cell == null) {
cell = row.createCell();
}
cell.setWidth(getWidth(i));
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0);
paragraph.setAlignment(ParagraphAlignment.CENTER);
// 去除段落的前后间距
paragraph.setSpacingBefore(0);
paragraph.setSpacingAfter(0);
// 设置段落的缩进为 0
paragraph.setIndentationLeft(0);
paragraph.setIndentationRight(0);
XWPFRun run = paragraph.createRun();
run.setText(text);
run.setBold(blod);
}
private static String getWidth(int i) {
switch (i) {
case 0:
case 2:
return "1100";
case 1:
return "4400";
case 3:
default:
return "2200";
}
}
/**
* 合并单元格
*/
private static void mergeCells(XWPFTableRow row, int fromCell) {
int size = row.getTableICells().size();
if (size <= 1) {
return;
}
for (int i = fromCell; i <= 3; i++) {
XWPFTableCell cell = row.getCell(i);
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr == null) {tcPr = cell.getCTTc().addNewTcPr();}
CTHMerge hMerge = tcPr.addNewHMerge();
if (i == fromCell) {
hMerge.setVal(STMerge.RESTART);
} else {
hMerge.setVal(STMerge.CONTINUE);
}
}
}
/**
* 添加标题行
*/
private static void addTitleRow(XWPFTable table, String[] rowData) {
XWPFTableRow row = getXwpfTableRow(table, rowData);
mergeCells(row, 0);
// 设置标题样式
XWPFTableCell cell = row.getCell(0);
XWPFParagraph para = cell.getParagraphs().get(0);
XWPFRun run = para.createRun();
run.setFontFamily(FontStyle.SONGTI);
run.setFontSize(FontStyle.HEADER_TEXT_SIZE_24 / 2);
run.setBold(true);
// 设置居中对齐
para.setAlignment(ParagraphAlignment.CENTER);
}
/**
* 添加章节标题
*/
private static void addSectionTitle(XWPFTable table, String[] rowData) {
XWPFTableRow row = getXwpfTableRow(table, rowData);
mergeCells(row, 0);
// 设置样式
XWPFTableCell cell = row.getCell(0);
XWPFParagraph para = cell.getParagraphs().get(0);
XWPFRun run = para.createRun();
run.setFontFamily(FontStyle.SONGTI);
run.setFontSize(FontStyle.HEADER_TEXT_SIZE_20 / 2);
run.setBold(true);
XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0);
paragraph.setAlignment(ParagraphAlignment.LEFT);
}
/**
* 添加空行
*/
private static void addEmptyRow(XWPFTable table) {
XWPFTableRow row = getXwpfTableRow(table);
mergeCells(row, 0);
// 设置最小高度
row.setHeight(2500);
}
/**
* 添加问题内容
*/
private static void addProblemContent(XWPFTable table, List<Export2WordDTO.ProblemData> problemList) throws Exception {
if (CollUtil.isEmpty(problemList)) {
addEmptyRow(table);
return;
}
for (Export2WordDTO.ProblemData problem : problemList.stream().filter(item -> CollUtil.isNotEmpty(item.getContext())).collect(Collectors.toList())) {
if (problem.isImg()) {
addImageRow(table, problem.getContext());
} else {
addTextRow(table, problem.getContext());
}
}
}
/**
* 添加图片行
*/
private static void addImageRow(XWPFTable table, List<String> imageUrls) throws Exception {
XWPFTableRow row = getXwpfTableRow(table);
mergeCells(row, 0);
XWPFTableCell cell = row.getCell(0);
while (!cell.getParagraphs().isEmpty()) {
cell.removeParagraph(0);
}
// 创建两列布局的段落
XWPFParagraph para = cell.addParagraph();
para.setAlignment(ParagraphAlignment.LEFT);
XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0);
// 去除段落的前后间距
paragraph.setSpacingBefore(0);
paragraph.setSpacingAfter(0);
// 设置段落的缩进为 0
paragraph.setIndentationLeft(0);
paragraph.setIndentationRight(0);
// 添加图片
for (int i = 0; i < Math.min(2, imageUrls.size()); i++) {
XWPFRun run = para.createRun();
byte[] imageData = downloadAndProcessImage(imageUrls.get(i));
if (imageData.length > 0) {
run.addPicture(
new ByteArrayInputStream(imageData),
getPictureType(imageUrls.get(i)),
imageUrls.get(i),
Units.toEMU(Units.pixelToPoints(285)),
Units.toEMU(Units.pixelToPoints(240))
);
// 如果不是最后一张图片,添加适当的间距
if (i < Math.min(2, imageUrls.size()) - 1) {
run.setText(" ");
}
}
}
}
/**
* 添加文本行
*/
private static void addTextRow(XWPFTable table, List<String> textList) {
XWPFTableRow row = getXwpfTableRowNotBold(table, new String[]{"","","",""});
mergeCells(row, 0);
XWPFTableCell cell = row.getCell(0);
cell.removeParagraph(0);
// 添加文本内容
for (String text : textList) {
// 创建新的段落
XWPFParagraph paragraph = cell.addParagraph();
paragraph.setAlignment(ParagraphAlignment.LEFT);
XWPFRun run = paragraph.createRun();
run.setFontFamily(FontStyle.SONGTI);
run.setFontSize(FontStyle.HEADER_TEXT_SIZE_20 / 2);
run.setText(text);
// 设置段落属性
CTPPr pPr = paragraph.getCTP().getPPr();
if (pPr == null) {
pPr = paragraph.getCTP().addNewPPr();
}
CTSpacing spacing = pPr.isSetSpacing() ? pPr.getSpacing() : pPr.addNewSpacing();
spacing.setBefore(BigInteger.ZERO);
spacing.setAfter(BigInteger.ZERO);
}
}
/**
* 下载并处理图片
*/
private static byte[] downloadAndProcessImage(String imageUrl) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpUtil.download(imageUrl, outputStream, true);
return outputStream.toByteArray();
} catch (Exception e) {
log.error("下载图片失败: {}", e.getMessage());
return new byte[0];
}
}
/**
* 获取图片类型
*/
private static int getPictureType(String imageUrl) {
if (imageUrl.toLowerCase().endsWith(".png")) {
return Document.PICTURE_TYPE_PNG;
} else if (imageUrl.toLowerCase().endsWith(".jpg") || imageUrl.toLowerCase().endsWith(".jpeg")) {
return Document.PICTURE_TYPE_JPEG;
} else {
return Document.PICTURE_TYPE_JPEG; // 默认使用JPEG
}
}
}