Execel文档批量替换标签实现方案

问题背景

需求:俺现网班级作为维度,批量导出每个班级学员的数据,excel的个数在1k左右,每一张表的人数在90左右。导出总耗时在10小时左右。

代码编写完成并导出现网数据后,发现导出的标题错了。

解决方案

1.通过修改代码,重新导出。(耗时在10h)

2.通过java 代码实现excel标签替换。(耗时在10分钟)

代码实现

依赖

java 复制代码
    implementation "org.apache.poi:poi:5.2.3"
    implementation "org.apache.poi:poi-ooxml:5.2.3"

代码

其中当文件中只有旧的标签且其他数据不存在时,会直接报错,需要我们手动处理即可。

template为我们新模板的样式文件,sourseDir为旧excel的文件夹。outputDir为新文件的生成位置。

根据样式的实际行数修改readTemplateData中的循环行数。removeRows方法中设置旧excel中标签的起止行数。(索引从0开始)

java 复制代码
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ExecelUtios {
    public static void main(String[] args) {
        String templatePath = "C:\\Users\\fjhb\\Desktop\\test111\\template.xlsx";
        String sourceDir = "C:\\Users\\fjhb\\Desktop\\教育学院\\4考勤\\2023";
        String outputDir = "C:\\Users\\fjhb\\Desktop\\教育学院\\4考勤最总\\2023\\";

        try {
            // 读取模板数据(内容和样式)
            TemplateData templateData = readTemplateData(templatePath);
            List<String> errorFileName = new ArrayList<>();
            File dir = new File(sourceDir);
            File[] files = dir.listFiles((d, name) ->
                    name.toLowerCase().endsWith(".xls") || name.toLowerCase().endsWith(".xlsx"));

            if (files == null || files.length == 0) {
                System.out.println("目录中没有Excel文件");
                return;
            }

            System.out.println("开始处理 " + files.length + " 个文件...");

            for (File file : files) {
                try {
                    processFile(file, templateData, outputDir);
                    System.out.println("✓ 已处理: " + file.getName());
                } catch (Exception e) {
                    System.err.println("✗ 处理失败: " + file.getName() + " - " + e.getMessage());
                    errorFileName.add(file.getName());
                }
            }

            System.out.println("处理完成! 成功处理 " + files.length + " 个文件");
            if(!errorFileName.isEmpty()) {
                System.out.println("执行错误,需要手动处理的文件为下(因为文件没有内容只有标题):");
                for (String s : errorFileName) {
                    System.err.println(s);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 读取模板数据(包含样式、内容和合并单元格)
    private static TemplateData readTemplateData(String templatePath) throws IOException {
        try (InputStream is = new FileInputStream(templatePath);
             Workbook templateWorkbook = WorkbookFactory.create(is)) {

            Sheet sheet = templateWorkbook.getSheetAt(0);
            List<RowData> rows = new ArrayList<>();
            List<CellRangeAddress> mergedRegions = new ArrayList<>();

            // 读取前三行
            for (int i = 0; i < 3 && i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row != null) {
                    rows.add(new RowData(row, templateWorkbook));
                }
            }

            // 读取前三行的合并单元格区域
            for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
                CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
                if (mergedRegion.getLastRow() < 3) { // 只处理前三行的合并
                    mergedRegions.add(mergedRegion);
                }
            }

            return new TemplateData(rows, mergedRegions, templateWorkbook);
        }
    }

    // 处理单个文件
    private static void processFile(File file, TemplateData templateData, String outputDir)
            throws IOException {

        try (InputStream is = new FileInputStream(file);
             Workbook workbook = WorkbookFactory.create(is)) {

            Sheet sheet = workbook.getSheetAt(0);

            // 1. 删除原有的合并区域(前三行)
            removeMergedRegionsInRange(sheet, 0, 2);

            // 2. 删除原有的前两行
            removeRows(sheet, 0, 1);

            // 3. 插入模板行(带样式)
            insertTemplateRows(sheet, templateData, workbook);

            // 4. 确保输出目录存在
            File outDir = new File(outputDir);
            if (!outDir.exists()) outDir.mkdirs();

            // 5. 保存文件
            String outputPath = outputDir + File.separator + file.getName();
            try (OutputStream os = new FileOutputStream(outputPath)) {
                workbook.write(os);
            }
        }
    }

    // 删除指定行范围内的合并区域
    private static void removeMergedRegionsInRange(Sheet sheet, int startRow, int endRow) {
        for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
            CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
            if (mergedRegion.getFirstRow() >= startRow && mergedRegion.getLastRow() <= endRow) {
                sheet.removeMergedRegion(i);
            }
        }
    }

    // 删除指定行范围
    private static void removeRows(Sheet sheet, int startRow, int endRow) {
        // 删除行内容
        for (int i = startRow; i <= endRow; i++) {
            Row row = sheet.getRow(i);
            if (row != null) {
                sheet.removeRow(row);
            }
        }

        // 移动行
        if (endRow < sheet.getLastRowNum()) {
            sheet.shiftRows(endRow + 1, sheet.getLastRowNum(), -(endRow - startRow + 1));
        }
    }

    // 插入模板行(带样式)
    private static void insertTemplateRows(Sheet sheet, TemplateData templateData, Workbook targetWorkbook) {
        if (templateData.rows.isEmpty()) return;

        // 移动现有行
        sheet.shiftRows(0, sheet.getLastRowNum(), templateData.rows.size(), true, true);

        // 创建新行并应用模板
        for (int i = 0; i < templateData.rows.size(); i++) {
            Row newRow = sheet.createRow(i);
            templateData.rows.get(i).applyTo(newRow, targetWorkbook, templateData.sourceWorkbook);
        }

        // 添加合并区域
        for (CellRangeAddress mergedRegion : templateData.mergedRegions) {
            sheet.addMergedRegion(mergedRegion);
        }
    }

    // 模板数据容器
    static class TemplateData {
        final List<RowData> rows;
        final List<CellRangeAddress> mergedRegions;
        final Workbook sourceWorkbook;

        public TemplateData(List<RowData> rows, List<CellRangeAddress> mergedRegions, Workbook sourceWorkbook) {
            this.rows = rows;
            this.mergedRegions = mergedRegions;
            this.sourceWorkbook = sourceWorkbook;
        }
    }

    // 行数据容器
    static class RowData {
        private final List<CellData> cells = new ArrayList<>();

        public RowData(Row sourceRow, Workbook sourceWorkbook) {
            if (sourceRow != null) {
                for (Cell cell : sourceRow) {
                    cells.add(new CellData(cell, sourceWorkbook));
                }
            }
        }

        public void applyTo(Row targetRow, Workbook targetWorkbook, Workbook sourceWorkbook) {
            for (CellData cellData : cells) {
                cellData.applyTo(targetRow, targetWorkbook, sourceWorkbook);
            }
        }
    }

    // 单元格数据容器(包含样式)
    static class CellData {
        private final int columnIndex;
        private final CellStyle sourceStyle;
        private final Object value;
        private final CellType cellType;
        private final Workbook sourceWorkbook;

        public CellData(Cell sourceCell, Workbook sourceWorkbook) {
            this.columnIndex = sourceCell.getColumnIndex();
            this.sourceStyle = sourceCell.getCellStyle();
            this.sourceWorkbook = sourceWorkbook;
            this.cellType = sourceCell.getCellType();

            switch (cellType) {
                case STRING:
                    value = sourceCell.getStringCellValue();
                    break;
                case NUMERIC:
                    value = sourceCell.getNumericCellValue();
                    break;
                case BOOLEAN:
                    value = sourceCell.getBooleanCellValue();
                    break;
                case FORMULA:
                    value = sourceCell.getCellFormula();
                    break;
                case BLANK:
                    value = "";
                    break;
                default:
                    value = null;
            }
        }

        public void applyTo(Row targetRow, Workbook targetWorkbook, Workbook sourceWorkbook) {
            Cell newCell = targetRow.createCell(columnIndex);

            // 复制单元格值
            setCellValue(newCell, value, cellType);

            // 复制单元格样式(深度复制)
            if (sourceStyle != null) {
                CellStyle newStyle = targetWorkbook.createCellStyle();
                copyCellStyleDeep(newStyle, sourceStyle, targetWorkbook, sourceWorkbook);
                newCell.setCellStyle(newStyle);
            }
        }

        private void setCellValue(Cell cell, Object value, CellType cellType) {
            if (value == null) return;

            switch (cellType) {
                case STRING:
                    cell.setCellValue((String) value);
                    break;
                case NUMERIC:
                    cell.setCellValue((Double) value);
                    break;
                case BOOLEAN:
                    cell.setCellValue((Boolean) value);
                    break;
                case FORMULA:
                    cell.setCellFormula((String) value);
                    break;
                case BLANK:
                    cell.setBlank();
                    break;
                default:
            }
        }

        // 深度复制单元格样式(支持.xls和.xlsx)
        private void copyCellStyleDeep(CellStyle newStyle, CellStyle sourceStyle,
                                       Workbook targetWorkbook, Workbook sourceWorkbook) {

            // 复制基本样式属性
            newStyle.setAlignment(sourceStyle.getAlignment());
            newStyle.setVerticalAlignment(sourceStyle.getVerticalAlignment());
            newStyle.setBorderTop(sourceStyle.getBorderTop());
            newStyle.setBorderBottom(sourceStyle.getBorderBottom());
            newStyle.setBorderLeft(sourceStyle.getBorderLeft());
            newStyle.setBorderRight(sourceStyle.getBorderRight());
            newStyle.setTopBorderColor(sourceStyle.getTopBorderColor());
            newStyle.setBottomBorderColor(sourceStyle.getBottomBorderColor());
            newStyle.setLeftBorderColor(sourceStyle.getLeftBorderColor());
            newStyle.setRightBorderColor(sourceStyle.getRightBorderColor());
            newStyle.setFillPattern(sourceStyle.getFillPattern());

            // 复制背景色
            if (sourceStyle.getFillBackgroundColor() > 0) {
                newStyle.setFillBackgroundColor(sourceStyle.getFillBackgroundColor());
            }

            // 复制前景色
            if (sourceStyle.getFillForegroundColor() > 0) {
                newStyle.setFillForegroundColor(sourceStyle.getFillForegroundColor());
            }

            // 复制其他属性
            newStyle.setDataFormat(sourceStyle.getDataFormat());
            newStyle.setWrapText(sourceStyle.getWrapText());
            newStyle.setIndention(sourceStyle.getIndention());
            newStyle.setRotation(sourceStyle.getRotation());
            newStyle.setHidden(sourceStyle.getHidden());
            newStyle.setLocked(sourceStyle.getLocked());
            newStyle.setShrinkToFit(sourceStyle.getShrinkToFit());

            // 复制字体
            Font sourceFont = sourceWorkbook.getFontAt(sourceStyle.getFontIndex());
            Font newFont = targetWorkbook.createFont();
            copyFontDeep(newFont, sourceFont, targetWorkbook, sourceWorkbook);
            newStyle.setFont(newFont);
        }

        // 深度复制字体样式
        private void copyFontDeep(Font newFont, Font sourceFont,
                                  Workbook targetWorkbook, Workbook sourceWorkbook) {

            newFont.setBold(sourceFont.getBold());
            newFont.setColor(sourceFont.getColor());
            newFont.setFontHeight(sourceFont.getFontHeight());
            newFont.setFontHeightInPoints(sourceFont.getFontHeightInPoints());
            newFont.setFontName(sourceFont.getFontName());
            newFont.setItalic(sourceFont.getItalic());
            newFont.setStrikeout(sourceFont.getStrikeout());
            newFont.setTypeOffset(sourceFont.getTypeOffset());
            newFont.setUnderline(sourceFont.getUnderline());
            newFont.setCharSet(sourceFont.getCharSet());
        }
    }

}

批量执行即可。

执行效果为下:

相关推荐
num_killer5 小时前
小白的Langchain学习
java·python·学习·langchain
期待のcode5 小时前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐5 小时前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
a程序小傲6 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红6 小时前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥6 小时前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v6 小时前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地6 小时前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209256 小时前
Guava Cache 原理与实战
java·后端·spring
yangminlei6 小时前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot