SpringBoot+Apache POI导出word

使用SpringBoot+Apache POI导出Word的流程

1、依赖配置

pom.xml中添加Apache POI依赖:

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.3</version>
        </dependency>
2、工具类

这里提供了导出用到的工具类,可以仔细看一下具体内容:

public class WordUtil {

    /**
     * 合并横向单元格(跨列合并)
     * @param table   目标表格
     * @param row     行号(从0开始)
     * @param fromCol 起始列号(从0开始)
     * @param toCol   结束列号(从0开始)
     */
    public static void mergeCellsHorizontal(XWPFTable table, int row, int fromCol, int toCol) {
        for (int colIndex = fromCol; colIndex <= toCol; colIndex++) {
            XWPFTableCell cell = table.getRow(row).getCell(colIndex);
            if (cell == null) continue; // 防止空指针异常

            if (colIndex == fromCol) {
                // 起始列标记为合并起点
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
            } else {
                // 后续列标记为合并延续
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
            }
        }
    }

    /**
     * 合并纵向相同的数据
     *
     * @param table 要合并单元格的表格
     * @param col   要合并的列索引
     * 注意:
     *      1、该方法会修改表格的结构,可能会导致行的高度发生变化。
     *      2、该方法只是根据相邻单元getCellValue()值相同进行合并
     */
    public static void mergeVerticalCells(XWPFTable table, int col) {
        int rowCount = table.getNumberOfRows();
        if (rowCount < 2) {
            return; // 如果表格行数小于2,无需合并
        }

        String previousValue = getCellValue(table.getRow(0).getCell(col));
        int startRow = 0;

        for (int rowIndex = 1; rowIndex < rowCount; rowIndex++) {
            String currentValue = getCellValue(table.getRow(rowIndex).getCell(col));

            if (!currentValue.equals(previousValue)) {
                if (rowIndex - 1 > startRow) {
                    mergeCellsVertically(table, col, startRow, rowIndex - 1);
                }
                startRow = rowIndex;
                previousValue = currentValue;
            }
        }

        if (rowCount - 1 > startRow) {
            mergeCellsVertically(table, col, startRow, rowCount - 1);
        }
    }

    /**
     * 跨行合并单元格
     *
     * @param table 要合并单元格的表格
     * @param col   要合并的列索引
     * @param fromRow 从哪一行开始合并单元格
     * @param toRow   合并到哪一行
     */
    public static void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
        for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
            XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
            if (rowIndex == fromRow) {
                // The first merged cell is set with RESTART merge value
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
            } else {
                // Cells which join (merge) the first one, are set with CONTINUE
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
            }
        }
    }

    /**
     * 获取单元格的文本值
     *
     * @param cell 单元格
     * @return 单元格的文本值
     */
    public static String getCellValue(XWPFTableCell cell) {
        List<XWPFParagraph> paragraphs = cell.getParagraphs();
        if (paragraphs != null && !paragraphs.isEmpty()) {
            return paragraphs.get(0).getText();
        }
        return "";
    }

    /**
     * 设置字体颜色
     *
     * @param cell 单元格
     * @param textPart1 第一部分文本
     * @param textPart2 第二部分文本
     * @param isRed 是否红色
     * @return 单元格的文本值
     */
    public static void setCellTextWithColor(XWPFTableCell cell, String textPart1, String textPart2, boolean isRed) {
        XWPFParagraph paragraph = !cell.getParagraphs().isEmpty() ? cell.getParagraphArray(0) : cell.addParagraph();
        paragraph.setAlignment(ParagraphAlignment.CENTER);

        // 第一部分文本(属性:)
        XWPFRun run1 = paragraph.createRun();
        if (isRed) {
            run1.setColor("FF0000"); // 设置红色
        }
        run1.setText(textPart1);

        // 第二部分文本(实际属性值或"属性值:")
        XWPFRun run2 = paragraph.createRun();
        run2.setText(textPart2);
    }

    /**
     * 设置表格单元格文本并居中对齐
     * @param cell 表格单元格
     * text 文本内容
     * **/
    public static void setCellTextAndAlignment(XWPFTableCell cell, String text) {
        // 在单元格中创建段落
        XWPFParagraph paragraph = !cell.getParagraphs().isEmpty() ? cell.getParagraphArray(0) : cell.addParagraph();
        // 设置段落对齐方式为居中
        paragraph.setAlignment(ParagraphAlignment.CENTER);
        // 创建运行并设置文本
        XWPFRun run = paragraph.createRun();
        run.setText(text);
    }

    /**
     * 修改表格的样式
     *
     * @param table 表格
     */
    public static void tableStyle(XWPFTable table) {
        // 获取表格属性对象
        CTTblPr tblPr = table.getCTTbl().getTblPr();
        if (tblPr == null) {
            tblPr = table.getCTTbl().addNewTblPr();
        }
        if (!tblPr.isSetJc()) {
            tblPr.addNewJc();  // 确保存在对齐属性节点
        }
        CTJcTable jc = tblPr.getJc();
        jc.setVal(STJcTable.CENTER);

        // 设置表格宽度和自适应布局
        CTTblWidth tblWidth = tblPr.addNewTblW();
        tblWidth.setType(STTblWidth.DXA);
        tblWidth.setW(BigInteger.valueOf(8300));  // 设置固定宽度(单位:DXA)

        //设置单元格和其文本样式
        for (XWPFTableRow row : table.getRows()) {
            for (XWPFTableCell cell : row.getTableCells()) {
                // 获取单元格的属性
                if (cell.getCTTc().getTcPr() == null) {
                    cell.getCTTc().addNewTcPr();
                }
                CTTcMar cellMar = cell.getCTTc().getTcPr().isSetTcMar()
                        ? cell.getCTTc().getTcPr().getTcMar()
                        : cell.getCTTc().getTcPr().addNewTcMar();

                // 设置内边距
                cellMar.addNewLeft().setW("100");
                cellMar.addNewRight().setW("100");
                cellMar.addNewTop().setW("100");
                cellMar.addNewBottom().setW("100");

                // 设置单元格垂直居中
                cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);

                // 遍历单元格内的所有段落
                for (XWPFParagraph paragraph : cell.getParagraphs()) {
                    // 设置水平居中
                    paragraph.setAlignment(ParagraphAlignment.CENTER);

                    // 段后0磅
                    paragraph.setSpacingAfter(0);

                    // 3. 设置行距为多倍行距1(关键步骤)
                    CTSpacing spacing = paragraph.getCTP().getPPr().isSetSpacing()
                            ? paragraph.getCTP().getPPr().getSpacing()
                            : paragraph.getCTP().getPPr().addNewSpacing();

                    // 多倍行距1(相当于Word里的1倍行距)
                    spacing.setLineRule(STLineSpacingRule.Enum.forInt(LineSpacingRule.AUTO.getValue())); // AUTO = 多倍行距
                    spacing.setLine(BigInteger.valueOf(240)); // 240 = 1倍行距(标准值)

                    //设置文本样式
                    for (XWPFRun run : paragraph.getRuns()) {
                        // 设置字体为宋体(需确认字体名称)
                        run.setFontFamily("宋体");
                        // 设置字号为12磅
                        run.setFontSize(12);
                    }
                }
            }
        }
    }

    /**
     * 插入一级标题并修改其样式
     *
     * @param doc,text word文档和对应的内容
     */
    public static void addContentParagraphTitle(XWPFDocument doc, String text) {
        XWPFParagraph para = doc.createParagraph();
        para.setSpacingBetween(1.0f);   //行间距1.0
        XWPFRun run = para.createRun();
        run.setFontFamily("宋体");
        run.setFontSize(16);
        run.setText(text);
    }

    /**
     * 插入正文文本并修改其样式
     *
     * @param doc,text word文档和对应的内容
     */
    public static void addContentParagraphText(XWPFDocument doc, String text) {
        XWPFParagraph para = doc.createParagraph();
        para.setIndentationFirstLine(500);  //首航缩进2个空格
        para.setSpacingBetween(1.0f);   //行间距1.0
        XWPFRun run = para.createRun();
        run.setFontFamily("宋体");
        run.setFontSize(14);
        run.setText(text);
    }


}
3、导出接口内容

这里是完整的接口内容:

@RestController
@RequestMapping("/export")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ExportWordController {
    @PostMapping("/word")
    public void genera(HttpServletResponse response) throws IOException {
        try {
            // 1. 设置 CORS 头(根据实际前端地址调整)
            response.setHeader("Access-Control-Allow-Origin", "http://localhost:5173");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");

            // 2. 设置下载头(编码文件名)
            String encodedFileName = "";
            encodedFileName = URLEncoder.encode("word导出.docx", "UTF-8").replace("+", "%20");

            response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName);
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");

            // 3. 生成文档
            XWPFDocument doc = new XWPFDocument();

            //文本内容
            setText(doc);

            //表格内容
            setTable(doc);

            //图片内容
            setImage(doc);

            // 4. 写入响应流
            OutputStream out = response.getOutputStream();
            doc.write(out);
        } catch (Exception e) {
            response.sendError(500, "文件生成失败: " + e.getMessage());
        }
    }

    public void setText(XWPFDocument doc){
        // 主标题(宋体、三号、居中)
        XWPFParagraph titlePara = doc.createParagraph();
        titlePara.setSpacingBetween(1.0f);   //行间距1.0
        titlePara.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun titleRun = titlePara.createRun();
        titleRun.setFontFamily("宋体");
        titleRun.setFontSize(18);
        titleRun.setText("word标题");

        WordUtil.addContentParagraphTitle(doc, "一、文本");//一级标题
        WordUtil.addContentParagraphText(doc, "这是一段文字描述");//正文
    }

    public void setTable(XWPFDocument doc){

        //构建表格内容
        List<StudentClass> list = new ArrayList<>();

        //添加数据
        list.add(new StudentClass("一班", List.of(new Student("张三"), new Student("李四"))));
        list.add(new StudentClass("二班", List.of(new Student("王五"), new Student("赵六"))));

        WordUtil.addContentParagraphTitle(doc, "二、表格"); // 一级标题

        //创建表格
        XWPFTable table = doc.createTable();
        XWPFTableRow tableHeader = table.getRow(0);  // 获取第一行(默认已有一行)
        WordUtil.setCellTextAndAlignment(tableHeader.getCell(0), "班级");
        WordUtil.setCellTextAndAlignment(tableHeader.addNewTableCell(), "学生");

        // 遍历班级列表
        for (StudentClass studentClass : list) {
            // 获取班级名称
            String primaryObjective = studentClass.getClassName();
            // 获取班级下的学生列表
            List<Student> students = studentClass.getStudents();
            // 对于每个一级目标,遍历其所有二级目标
            for (Student student : students) {
                XWPFTableRow row = table.createRow();  //创建新行
                row.getCell(0).setText(primaryObjective); // 班级名称
                row.getCell(1).setText(student.getStudentName()); // 设置学生姓 名
            }
        }
        WordUtil.mergeVerticalCells(table, 0); //合并第一列(班级列)
        WordUtil.tableStyle(table); //设置表格样式
    }

    public void setImage(XWPFDocument doc) throws IOException, InvalidFormatException {
        WordUtil.addContentParagraphTitle(doc, "三、图片"); // 一级标题
        String imagePath = "img.png";
        // 获取图片输入流
        InputStream pic = new FileInputStream(imagePath);

        // 设置图片宽度为4厘米,高度为3厘米
        int widthCm = 4;
        int heightCm = 3;

        int widthEMU = Math.round(widthCm * 360000); // 将厘米转换为EMU
        int heightEMU = Math.round(heightCm * 360000);

        XWPFParagraph paragraph = doc.createParagraph();
        XWPFRun run = paragraph.createRun();
        run.addPicture(pic, Document.PICTURE_TYPE_PNG, imagePath, widthEMU, heightEMU);

        // 关闭输入流
        pic.close();
    }
}
接下来直接运行就可以了

这里是运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值