java實現excel的多sheet頁合並成一個sheet,完美保證格式。可以實現合並時去掉sheet頁的表頭,同時設置各個sheet之間的間隔,以及遞歸合並


1. 需求:將一個源excel的多個sheet頁,合並到目標excel的單個sheet頁中, 源excel可能需要內部自身的子sheet頁合並,合並的時候做到將源excel中的單元格是公式的值賦值到目標單元格中,同時完整的合並源excel中出現的合並單元格。

2. 代碼實現

2.1 業務table設計

 主表:源excel中需要合並到目標excel的sheet信息

CREATE TABLE `sheet_message` (
  `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `model_name` varchar(255) NOT NULL COMMENT 'excel名稱',
  `sheet_name` varchar(255) NOT NULL COMMENT 'sheet頁名稱',
  `need_merge` varchar(255) NOT NULL COMMENT '是否需要合並,需要合並為yes',
  `order_no` int(11) DEFAULT NULL COMMENT '合並的時候排序,決定哪個sheet頁需要排在最前面',
  `last_sheet_interval` int(8) DEFAULT NULL COMMENT '當前合並的sheet,與上一個sheet頁之間的間隔',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
View Code

子表:源excel中sheet需要內部合並的sheet信息

CREATE TABLE `child_sheet_message` (
  `id` bigint(8) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `parent_sheet_name` varchar(255) NOT NULL COMMENT '父sheet頁名稱,即需要將子sheet頁合並到的位置',
  `sheet_name` varchar(255) NOT NULL COMMENT '子sheet頁名稱',
  `order_no` int(8) NOT NULL COMMENT '子sheet頁合並到父sheet頁的順序',
  `title_length` int(8) DEFAULT NULL COMMENT '父子合並的表頭處理,如果合並不需要去掉表頭,填0,需要去掉填寫sheet表頭長度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
View Code

2.2 代碼service層

2.2.1 主方法mergeExcelBySheetMessage, 中間用到的mapper層省略, 用到的sql如下

2.2.1.1 取出所有需要合並sheet頁 select * from sheet_message s where s.model_name = #{fileName} and s.need_merge = 'yes' order by s.order_no

    /**
     *  傳遞 源workbook, 和 源文件名fileName
     *
     * @param sourceWorkBook 源workbook 用來獲取excel數據
     * @param fileName 源文件名稱,用來獲取數據庫中的sheet頁信息
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    @Override
    public Workbook mergeExcelBySheetMessage(Workbook sourceWorkBook, String fileName) throws IllegalAccessException, InstantiationException {

        // 創建和目標workbook相同類型的workbook
        Workbook targetWorkBook = sourceWorkBook.getClass().newInstance();
        // 創建目標sheet
        Sheet targetSheet = targetWorkBook.createSheet();

        // 取出所有的需要合並的sheet頁合集
        List<SheetMessage> needMergeSheetList = sheetMessageMapper.selectByFileName(fileName);

        // 當不需要合並的時候,直接返回源workbook
        if (needMergeSheetList == null || needMergeSheetList.isEmpty()) {
            return sourceWorkBook;
        }

        // 記錄已經合並過的sheet頁,防止重復合並
        List<String> sheetNameList = new ArrayList<>();

        // 將合並完之后的sourceWorkbook合並到targetWorkbook中
        for (SheetMessage sheetMessage : needMergeSheetList) {

            String sheetName = sheetMessage.getSheetName();

            // 與上一個合並的sheet之間的間隔
            Integer lastSheetInterval = sheetMessage.getLastSheetInterval();
            if (lastSheetInterval > 0) {
                targetSheet.createRow(targetSheet.getLastRowNum() + lastSheetInterval);
            }

            // 源sheet合並需要合並的子項
            mergeChildSheetMessage(sheetName, sourceWorkBook, sheetNameList);

            Sheet sourceSheet = sourceWorkBook.getSheet(sheetName);
            moveSourceSheetIntoTargetSheet(targetWorkBook, sourceSheet, targetSheet, 0);

        }

        return targetWorkBook;
    }

 

2.2.2 源sheet需要自身合並的子項(源excel中有的sheet頁是需要同過自身的其他sheet頁合並而來)

中間用到的mapper層,省略,用到的sql語句如下

2.2.2.1 select * from child_sheet_message c where c.parent_sheet_name = #{parentSheetName} order by c.order_no

    /**
     * 源excel中sheet頁合並需要合並的子sheet
     *
     * @param parentSheetName 需要合並的sheet頁名稱
     * @param sourceWorkBook  源excel
     * @param sheetNameList   已經合並過的sheet名稱的list集合,用來防止重復合並
     */
    private void mergeChildSheetMessage(String parentSheetName, Workbook sourceWorkBook, List<String> sheetNameList) {

        if (sheetNameList.contains(parentSheetName)) {
            return;
        }

        List<ChildSheetMessage> childSheetList = childSheetMessageMapper.selectByParentSheetMessage(parentSheetName);
        if (childSheetList == null || childSheetList.isEmpty()) {
            sheetNameList.add(parentSheetName);
            return;
        }

        // 合並
        Sheet targetSheet = sourceWorkBook.getSheet(parentSheetName);
        for (ChildSheetMessage childSheetMessage : childSheetList) {

            String sheetName = childSheetMessage.getSheetName();
            Integer titleLength = childSheetMessage.getTitleLength();

            // 遞歸合並子項的子項sheet
            mergeChildSheetMessage(sheetName, sourceWorkBook, sheetNameList);

            Sheet sourceSheet = sourceWorkBook.getSheet(sheetName);

            // 合並sheet頁
            moveSourceSheetIntoTargetSheet(sourceWorkBook, sourceSheet, targetSheet, titleLength);

        }

        // 記錄已經合並了的sheet頁名稱
        sheetNameList.add(parentSheetName);

    }

 

 

2.2.3 moveSourceSheetIntoTargetSheet 合並源sheet到目標sheet中

   /**
     * sheet頁合並
     *
     * @param targetWorkBook 目標workbook,該對象主要用來創建單元格格式
     * @param sourceSheet 源sheet
     * @param targetSheet 目標sheet
     * @param removeTitleLength 源sheet頁合並到目標sheet中需要去掉的表頭長度,如果不去掉傳遞0
     */
    private static void moveSourceSheetIntoTargetSheet(Workbook targetWorkBook, Sheet sourceSheet, Sheet targetSheet, int removeTitleLength) {

        if (sourceSheet == null) {
            return;
        }

        // 獲取目標sheet最后一行的下一行
        int targetRowNums = targetSheet.getLastRowNum();
        int physicalNumberOfRows = targetSheet.getPhysicalNumberOfRows();
        targetRowNums = physicalNumberOfRows == 0 ? 0 : targetRowNums + 1;

        // 移動 源sheet頁中的 合並單元格區域 到目標sheet頁中
        moveSourceSheetAllMergedRegionToTargetSheet(sourceSheet, targetSheet, targetRowNums, removeTitleLength);

        int sourceRowNums = sourceSheet.getLastRowNum();
        for (int i = removeTitleLength; i <= sourceRowNums; i++) {

            Row targetRow = targetSheet.createRow(targetRowNums++);
            Row sourceRow = sourceSheet.getRow(i);

            // 復制行
            copySourceRowToTargetRow(targetWorkBook, sourceRow, targetRow);
        }

    }

 

 

2.2.4 合並sheet的時候,先處理sheet頁中的合並單元格區域問題

   /**
     * 合並sheet頁中,處理源sheet頁中可能存在的 合並單元格部分;
     * 當源sheet頁在合並單元格的時候可能去掉表頭,所以也需去掉表頭的合並單元格部分
     *
     * @param sourceSheet       源sheet
     * @param targetSheet       目標sheet
     * @param targetRowNums     目標sheet的最后一行(源合並單元格的位置,需要變化到目標單元格區域,需要提供一個位置角標)
     * @param removeTitleLength 需要移除的表頭長度
     */
    private static void moveSourceSheetAllMergedRegionToTargetSheet(Sheet sourceSheet, Sheet targetSheet, int targetRowNums, int removeTitleLength) {

        int numMergedRegions = sourceSheet.getNumMergedRegions();
        for (int i = 0; i < numMergedRegions; i++) {

            CellRangeAddress mergedRegion = sourceSheet.getMergedRegion(i);

            int firstRow = mergedRegion.getFirstRow();

            // 去掉表頭的 單元格合並
            if (firstRow < removeTitleLength) {
                continue;
            }

            int lastRow = mergedRegion.getLastRow();
            int firstColumn = mergedRegion.getFirstColumn();
            int lastColumn = mergedRegion.getLastColumn();

            // 合並單元格的行需要跟隨當前單元格的行數下移
            firstRow = firstRow + targetRowNums;
            lastRow = lastRow + targetRowNums;

            CellRangeAddress cellRangeAddress = new CellRangeAddress(firstRow, lastRow, firstColumn, lastColumn);
            targetSheet.addMergedRegion(cellRangeAddress);
        }

    }

 

 

2.2.5 移動行copySourceRowToTargetRow

    /**
     * 將源行復制到目標行
     * @param targetWorkBook 目標workbook,主要用來創建單元格樣式
     * @param sourceRow 源行
     * @param targetRow 目標行
     */
    private static void copySourceRowToTargetRow(Workbook targetWorkBook, Row sourceRow, Row targetRow) {

        if (sourceRow == null) {
            return;
        }

        // 行高
        targetRow.setHeight(sourceRow.getHeight());

        int sourceCellNums = sourceRow.getLastCellNum();
        for (int i = 0; i < sourceCellNums; i++) {

            Cell targetCell = targetRow.createCell(i);
            Cell sourceCell = sourceRow.getCell(i);

            // 復制單元格
            copySourceCellToTargetCell(targetWorkBook, targetCell, sourceCell);
        }

    }

 

 

2.2.5 移動單元格

    /**
     * 移動單元格
     *
     * @param targetWorkBook 目標workbook,用來在本方法中創建單元格樣式
     * @param targetCell     目標單元格
     * @param sourceCell     源單元格
     */
    private static void copySourceCellToTargetCell(Workbook targetWorkBook, Cell targetCell, Cell sourceCell) {

        if (sourceCell == null) {
            return;
        }

        // 將源單元格的格式 賦值到 目標單元格中
        CellStyle sourceCellStyle = sourceCell.getCellStyle();
        /*
            此處由於是新建了workbook對象,只能新建 CellStyle對象,然后clone,再賦值;
            直接賦值 源CellStyle對象 會報不是同源異常
        */
        CellStyle targetCellStyle = targetWorkBook.createCellStyle();
        targetCellStyle.cloneStyleFrom(sourceCellStyle);
        targetCell.setCellStyle(targetCellStyle);

        CellType cellTypeEnum = sourceCell.getCellTypeEnum();
        switch (cellTypeEnum) {
            case STRING:
                targetCell.setCellValue(sourceCell.getStringCellValue());
                break;
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(sourceCell)) {
                    // 日期格式的值
                    targetCell.setCellValue(sourceCell.getDateCellValue());
                } else {
                    targetCell.setCellValue(sourceCell.getNumericCellValue());
                }
                break;
            case BOOLEAN:
                targetCell.setCellValue(sourceCell.getBooleanCellValue());
                break;
            case FORMULA:
                // ***為公式的情況下獲取的是單元格的數值
                targetCell.setCellValue(sourceCell.getNumericCellValue());
                break;
            case BLANK:
                break;
            case ERROR:
                targetCell.setCellValue(sourceCell.getErrorCellValue());
                break;
            case _NONE:
                break;
            default:

        }
    }

 

 

如果有意見可以留言,謝謝~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM