占位符导入模板excel, 再导出xlsx

发布时间 2023-10-19 19:58:51作者: HeavenTang
1、引入包
`<dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>ooxml-schemas</artifactId>
        <version>1.1</version>
    </dependency>

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

    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>3.8</version>
    </dependency>`
2、工具类
`package com.ly.education.trainingResource.server.utils;

import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.format.CellFormatType;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.aspectj.weaver.ast.Test;
import org.springframework.core.io.ClassPathResource;

import java.io.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 使用一个已经存在的Excel作为模板,可以对当前的模板Excel进行修改操作,
 * 然后重新输出为流,或者存入文件系统当中。
 *
 *
 * @Description: excel模板操作
 *
 * */
public class ExcelTemplate {
	private String path;

	private Workbook workbook;

	private Sheet[] sheets;

	private Sheet sheet;

	private Throwable ex;

	private List<Cell> cellList = null;

	private Pattern doublePattern = Pattern.compile("^[0-9]+[.]{0,1}[0-9]*[dD]{0,1}$");

	/**
	 * 通过模板Excel的路径初始化
	 * */
	public ExcelTemplate(String path) {
		this.path = path;
		init();
	}

	public ExcelTemplate(InputStream is) {
		init(is);
	}

	private void init(){

		ClassPathResource classPathResource = new ClassPathResource(path);

//        File file = new File(path);
//        if (file.exists() && (path == null
//                || (!path.endsWith(".xlsx") && !path.endsWith(".xls")))) {
//            ex = new IOException("错误的文件格式");
//        } else{
		try (InputStream is = classPathResource.getInputStream()){
			workbook = WorkbookFactory.create(is);
			sheets = new Sheet[workbook.getNumberOfSheets()];
			for(int i = 0;i < sheets.length;i++){
				sheets[i] = workbook.getSheetAt(i);
			}
			if(sheets.length > 0) {
				sheet = sheets[0];
			}
			sheet.setForceFormulaRecalculation(true);
		} catch (EncryptedDocumentException e) {
			ex = e;
		} catch (IOException | InvalidFormatException e) {
			ex = e;
		}
//        }
	}

private void init(InputStream is){
    try {
        workbook = WorkbookFactory.create(is);
        sheets = new Sheet[workbook.getNumberOfSheets()];
        for(int i = 0;i < sheets.length;i++){
            sheets[i] = workbook.getSheetAt(i);
        }
        if(sheets.length > 0) {
            sheet = sheets[0];
        }
        sheet.setForceFormulaRecalculation(true);
    } catch (EncryptedDocumentException e) {
        ex = e;
    } catch (IOException | InvalidFormatException e) {
        ex = e;
    }
}

private boolean initSheet(int sheetNo){
    if(!examine() || sheetNo < 0 || sheetNo > workbook.getNumberOfSheets() - 1) {
        return false;
    }
    int sheetNum = workbook.getNumberOfSheets();
    sheets = new Sheet[sheetNum];
    for(int i = 0;i < sheetNum;i++){
        if(i == sheetNo) {
            sheet = workbook.getSheetAt(i);
        }
        sheets[i] = workbook.getSheetAt(i);
    }
    sheet = workbook.getSheetAt(sheetNo);
    sheet.setForceFormulaRecalculation(true);
    return true;
}

/**
 * 验证模板是否可用
 * @return true-可用 false-不可用
 * */
public boolean examine(){
    if(ex == null && workbook != null) {
        return true;
    }
    return false;
}

private boolean examineSheetRow(int index){
    if(index < 0 || index > sheet.getLastRowNum()) {
        return false;
    }
    return true;
}

/**
 * 使用一个已经存在的row作为模板,
 * 从sheet[sheetNo]的toRowNum行开始插入这个row模板的副本
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param fromRowStartIndex 模板row区域的开始索引
 * @param fromRowEndIndex 模板row区域的结束索引
 * @param toRowIndex 开始插入的row索引值
 * @param copyNum 复制的数量
 * @param delRowTemp 是否删除模板row区域
 * @return int 插入的行数量
 * @throws IOException
 * */
public int addRowByExist(int sheetNo,int fromRowStartIndex, int fromRowEndIndex,int toRowIndex, int copyNum,boolean delRowTemp)
        throws IOException {
    LinkedHashMap<Integer, LinkedList<String>> map = new LinkedHashMap<>();
    for(int i = 1;i <= copyNum;i++){
        map.put(i,new LinkedList<>());
    }
    return addRowByExist(sheetNo,fromRowStartIndex,fromRowEndIndex,toRowIndex,map,delRowTemp);
}

/**
 * 使用一个已经存在的row作为模板,
 * 从sheet[sheetNo]的toRowNum行开始插入这个row模板的副本,
 * 并且使用areaValue从左至右,从上至下的替换掉
 * row区域中值为 ${} 的单元格的值
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param fromRowIndex 模板行的索引
 * @param toRowIndex 开始插入的row索引
 * @param areaValues 替换模板row区域的${}值
 * @return int 插入的行数量
 * @throws IOException
 * */
public int addRowByExist(int sheetNo,int fromRowIndex, int toRowIndex,
                         LinkedHashMap<Integer,LinkedList<String>> areaValues)
        throws IOException {
    return addRowByExist(sheetNo,fromRowIndex,fromRowIndex,toRowIndex,areaValues,true);
}

/**
 * 使用一个已经存在的行区域作为模板,
 * 从sheet的toRowNum行开始插入这段行区域,
 * areaValue会从左至右,从上至下的替换掉row区域
 * 中值为 ${} 的单元格的值
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param fromRowStartIndex 模板row区域的开始索引
 * @param fromRowEndIndex 模板row区域的结束索引
 * @param toRowIndex 开始插入的row索引
 * @param areaValues 替换模板row区域的${}值
 * @param delRowTemp 是否删除模板row区域
 * @return int 插入的行数量
 * @throws IOException
 * */
public int addRowByExist(int sheetNo,int fromRowStartIndex, int fromRowEndIndex,int toRowIndex,
                         LinkedHashMap<Integer,LinkedList<String>> areaValues, boolean delRowTemp)
        throws IOException {
    exception();
    if(!examine()
            || !initSheet(sheetNo)
            || !examineSheetRow(fromRowStartIndex)
            || !examineSheetRow(fromRowEndIndex)
            || fromRowStartIndex > fromRowEndIndex) {
        return 0;
    }
    int areaNum;List<Row> rows = new ArrayList<>();
    if(areaValues != null){
        int n = 0,f = areaValues.size() * (areaNum = (fromRowEndIndex - fromRowStartIndex + 1));
        // 在插入前腾出空间,避免新插入的行覆盖原有的行
        shiftAndCreateRows(sheetNo,toRowIndex,f);
        // 读取需要插入的数据
        for (Integer key:areaValues.keySet()){
            List<Row> temp = new LinkedList<>();
            // 插入行
            for(int i = 0;i < areaNum;i++){
                int num = areaNum * n + i;
                Row toRow = sheet.getRow(toRowIndex + num);
                Row row;
                if(toRowIndex >= fromRowEndIndex) {
                    row = copyRow(sheetNo,sheet.getRow(fromRowStartIndex + i),sheetNo,toRow,true,true);
                } else {
                    row = copyRow(sheetNo,sheet.getRow(fromRowStartIndex + i + f),sheetNo,toRow,true,true);
                }
                temp.add(row);
            }
            // 使用传入的值覆盖${}或者N${}
            replaceMark(temp,areaValues.get(key));
            rows.addAll(temp);
            n++;
        }
        if(delRowTemp){
            if(toRowIndex >= fromRowEndIndex) {
                removeRowArea(sheetNo,fromRowStartIndex,fromRowEndIndex);
            } else {
                removeRowArea(sheetNo,fromRowStartIndex + f,fromRowEndIndex + f);
            }
        }
    }
    return rows.size();
}

/**
 * 使用一个已经存在的列区域作为模板,
 * 从sheet的toColumnIndex列开始插入这段列区域,
 * areaValue会从上至下,从左至右的替换掉列区域
 * 中值为 ${} 的单元格的值
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param fromColumnStartIndex 模板列区域的开始索引
 * @param fromColumnEndIndex 模板列区域的结束索引
 * @param toColumnIndex 开始插入的列索引
 * @param areaValues 替换模板列区域的${}值
 * @param delColumnTemp 是否删除模板列区域
 * @return int 插入的列数量
 * @throws IOException
 * */
public int addColumnByExist(int sheetNo,int fromColumnStartIndex, int fromColumnEndIndex,int toColumnIndex,
                            LinkedHashMap<Integer,LinkedList<String>> areaValues, boolean delColumnTemp)
        throws IOException{
    exception();
    if(!examine()
            || !initSheet(sheetNo)
            || fromColumnStartIndex > fromColumnEndIndex
            || toColumnIndex < 0) {
        return 0;
    }
    // 合并区域的列的数量
    int areaNum;
    List<Integer> n = new ArrayList<>();
    n.add(0);
    if(areaValues != null){
        int f = areaValues.size() * (areaNum = (fromColumnEndIndex - fromColumnStartIndex + 1));
        // 创建空白的列
        shiftAndCreateColumns(sheetNo,toColumnIndex-1,f);
        // 获取所有合并区域
//            List<CellRangeAddress> crds = sheet.getMergedRegions();
			//获取合并单元格的总数,并循环每一个合并单元格,
			int sheetMergeCount  = sheet.getNumMergedRegions();


        // 读取需要插入的数据
        for (Integer key:areaValues.keySet()){
            for(int i = 0;i < areaNum;i++){
                // 获取插入的位置
                int position = toColumnIndex + n.get(0) * areaNum + i;
                // 插入的列的位置是在复制区域之后
                if(toColumnIndex >= fromColumnStartIndex) {
                    copyColumn(sheetNo,fromColumnStartIndex + i,sheetNo,position,true);
                }
                // 插入的列的位置是在复制区域之前
                else {
                    copyColumn(sheetNo,fromColumnStartIndex + i + f,sheetNo,position,true);
                }
            }
            // 复制源列的合并区域到新添加的列
            if(sheetMergeCount>0) {
                for (int i = 0; i < sheetMergeCount; i++) {
                    CellRangeAddress crd = sheet.getMergedRegion(i);
                    // 列偏移量
                    int offset = toColumnIndex - fromColumnStartIndex + areaNum * n.get(0);
                    // 合并区域的宽度
                    int rangeAreaNum = crd.getLastColumn() - crd.getFirstColumn() + 1;
                    // 原合并区域的首列
                    int firstColumn = crd.getFirstColumn();
                    // 需要添加的合并区域首列
                    int addFirstColumn = firstColumn + offset;
                    // 根据插入的列的位置是在复制区域之前还是之后
                    // firstColumn和addFirstColumn分配不同的值
                    firstColumn = toColumnIndex >= fromColumnStartIndex ? firstColumn : firstColumn - f;
                    addFirstColumn = toColumnIndex >= fromColumnStartIndex ? addFirstColumn : toColumnIndex + areaNum * n.get(0);
                    if(firstColumn >= fromColumnStartIndex && firstColumn < fromColumnEndIndex){
                        if ((firstColumn + rangeAreaNum - 1) > fromColumnEndIndex) {
                            rangeAreaNum = fromColumnEndIndex - firstColumn + 1;
                        }
                        if(rangeAreaNum > areaNum){
                            mergedRegion(sheetNo,
                                    crd.getFirstRow(),
                                    crd.getLastRow(),
                                    addFirstColumn,
                                    addFirstColumn + areaNum - 1);
                        }
                        else {
                            mergedRegion(sheetNo,
                                    crd.getFirstRow(),
                                    crd.getLastRow(),
                                    addFirstColumn,
                                    addFirstColumn + rangeAreaNum - 1);
                        }
                    }
                }
            }
            /*if(crds != null){
                crds.forEach(crd -> {
                    // 列偏移量
                    int offset = toColumnIndex - fromColumnStartIndex + areaNum * n.get(0);
                    // 合并区域的宽度
                    int rangeAreaNum = crd.getLastColumn() - crd.getFirstColumn() + 1;
                    // 原合并区域的首列
                    int firstColumn = crd.getFirstColumn();
                    // 需要添加的合并区域首列
                    int addFirstColumn = firstColumn + offset;
                    // 根据插入的列的位置是在复制区域之前还是之后
                    // firstColumn和addFirstColumn分配不同的值
                    firstColumn = toColumnIndex >= fromColumnStartIndex ? firstColumn : firstColumn - f;
                    addFirstColumn = toColumnIndex >= fromColumnStartIndex ? addFirstColumn : toColumnIndex + areaNum * n.get(0);
                    if(firstColumn >= fromColumnStartIndex && firstColumn < fromColumnEndIndex){
                        if ((firstColumn + rangeAreaNum - 1) > fromColumnEndIndex) {
                            rangeAreaNum = fromColumnEndIndex - firstColumn + 1;
                        }
                        if(rangeAreaNum > areaNum){
                            mergedRegion(sheetNo,
                                    crd.getFirstRow(),
                                    crd.getLastRow(),
                                    addFirstColumn,
                                    addFirstColumn + areaNum - 1);
                        }
                        else {
                            mergedRegion(sheetNo,
                                    crd.getFirstRow(),
                                    crd.getLastRow(),
                                    addFirstColumn,
                                    addFirstColumn + rangeAreaNum - 1);
                        }
                    }
                });
            }*/
            // 填充${}
            List<String> fillValues = new ArrayList<>(areaValues.get(key));
            if (fillValues == null || fillValues.size() == 0){
                n.replaceAll(i -> i + 1);
                continue;
            }
            List<Cell> needFillCells;
            initCellList(sheetNo);
            needFillCells = cellList;
            // 获取所有的值为${}单元格
            needFillCells = needFillCells.stream().filter(c -> {
//                    if(c != null && c.getCellTypeEnum() == CellType.STRING){
					if(c != null && c.getCellType() == Cell.CELL_TYPE_STRING){
						if ("${}".equals(c.getStringCellValue()) || "N${}".equals(c.getStringCellValue())) {
							return true;
						}
					}
					return false;
				}).collect(Collectors.toList());
				if (needFillCells == null){
					n.replaceAll(i -> i + 1);
					continue;
				}
				// 所有的${}单元格按照列从小到大,行从小到大的顺序排序
				needFillCells.sort((c1,c2) -> {
					if (c1 == null && c2 == null) {
						return 0;
					}
					if (c1 == null) {
						return 1;
					}
					if (c2 == null) {
						return -1;
					}
					if(c1.getColumnIndex() > c2.getColumnIndex()) {
						return 1;
					} else if(c1.getColumnIndex() < c2.getColumnIndex()) {
						return -1;
					} else {
						if(c1.getRowIndex() > c2.getRowIndex()) {
							return 1;
						} else if(c1.getRowIndex() < c2.getRowIndex()) {
							return -1;
						} else {
							return 0;
						}
					}
				});
				needFillCells
						.stream()
						.filter(c -> {
							if(c == null) {
								return false;
							}
							// 筛选出当前需要填充的单元格
							return c.getColumnIndex() >= toColumnIndex + areaNum * n.get(0)
									&& c.getColumnIndex() <= toColumnIndex + areaNum * (n.get(0) + 1);
						}).forEach(c -> {
					if(fillValues.size() > 0){
						// 设置为列的首行,再移除掉首行的值
						String value = fillValues.stream().filter(Objects::nonNull).findFirst().orElse("");
						if (doublePattern.matcher(value == null ? "": value).find()){
							c.setCellValue(Double.parseDouble(value));
						}
						else {
							c.setCellValue(value);
						}
						CellStyle cellStyle = c.getCellStyle();
						cellStyle.setWrapText(true);
						c.setCellStyle(cellStyle);
						fillValues.remove(0);
					}
				});
				n.replaceAll(i -> i + 1);
			}
			if(delColumnTemp){
				if(toColumnIndex >= fromColumnStartIndex) {
					removeColumnArea(sheetNo,fromColumnStartIndex,fromColumnEndIndex);
				} else {
					removeColumnArea(sheetNo,fromColumnStartIndex + f,fromColumnEndIndex + f);
				}
			}
		}
		return n.get(0);
	}

/**
 * 使用一个已经存在的列区域作为模板,
 * 从sheet的toColumnIndex列开始插入这段列区域
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param fromColumnStartIndex 模板列区域的开始索引
 * @param fromColumnEndIndex 模板列区域的结束索引
 * @param toColumnIndex 开始插入的列索引
 * @param copyNum 复制数量
 * @param delColumnTemp 是否删除模板列区域
 * @return int 插入的列数量
 * @throws IOException
 * */
public int addColumnByExist(int sheetNo,int fromColumnStartIndex, int fromColumnEndIndex,int toColumnIndex,
                            int copyNum, boolean delColumnTemp)
        throws IOException{
    LinkedHashMap<Integer, LinkedList<String>> map = new LinkedHashMap<>();
    for(int i = 1;i <= copyNum;i++){
        map.put(i,new LinkedList<>());
    }
    return addColumnByExist(sheetNo,fromColumnStartIndex,fromColumnEndIndex,toColumnIndex,map,delColumnTemp);
}

/**
 * 填充Excel当中的变量
 *
 * @param fillValues 填充的值
 * @return int 受影响的变量数量
 * @throws IOException
 **/
public int fillVariable(Map<String,String> fillValues) throws IOException {
    return fillVariable(0,fillValues);
}

/**
 * 填充Excel当中的变量
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param fillValues 填充的值
 * @return int 受影响的变量数量
 * @throws IOException
 **/
public int fillVariable(int sheetNo,Map<String,String> fillValues)
        throws IOException {
    exception();
    if(!examine()
            || sheetNo < 0
            || sheetNo > sheets.length - 1
            || fillValues == null
            || fillValues.size() == 0) {
        return 0;
    }
    // 验证${}格式
    Pattern pattern = Pattern.compile("(\\$\\{[^\\}]+})");
    // 把所有的${}按Cell分类,也就是说如果一个Cell中存在两个${},
    // 这两个变量的Cell应该一样
    Map<Cell,Map<String,String>> cellVal = new HashMap<>();
    List<Integer> ns = new ArrayList<>();
    ns.add(0);
    fillValues.forEach((k,v) ->{
        // 找到变量所在的单元格
        Cell cell = findCells(sheetNo,s -> {
            if(s == null || "".equals(s)) {
                return false;
            }
            Matcher matcher = pattern.matcher(s);
            while(matcher.find()){
                String variable = matcher.group(1);
                if(variable != null
                        && formatParamCode(variable).equals(k.trim())) {
                    return true;
                }
            }
            return false;
        }).stream().findFirst().orElse(null);
        if(cell != null){
            Map<String,String> cellValMap = cellVal.get(cell);
            if(cellValMap == null) {
                cellValMap = new HashMap<>();
            }
            cellValMap.put(k,v);
            cellVal.put(cell,cellValMap);
            ns.replaceAll(n -> n + 1);
        }
    });
    cellVal.forEach((k,v) -> {
        String cellValue = k.getStringCellValue();
        String value = composeMessage(cellValue,v);
        Matcher matcher = doublePattern.matcher(value == null ? "": value);
        if (matcher.find()){
            k.setCellValue(Double.parseDouble(value));
        }
        else {
            k.setCellValue(value);
        }
        CellStyle cellStyle = k.getCellStyle();
        cellStyle.setWrapText(true);
        k.setCellStyle(cellStyle);
    });
    return ns.get(0);
}

/**
 * 指定cell插入图片
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param imageBytes 图片字节流
 * @param imgType 图片类型(举例:png 传入 Workbook.PICTURE_TYPE_PNG作为参数)
 * @param startRow 开始行
 * @param endRow 结束行
 * @param startCol 开始列
 * @param endCol 结束列
 * */
public void insertPicture(int sheetNo,byte[] imageBytes,int imgType,int startRow,int endRow,int startCol,int endCol) throws IOException {
    exception();
    if (!initSheet(sheetNo)) {
        return;
    }

    Drawing patriarch = sheet.createDrawingPatriarch();
    ClientAnchor anchor = null;

    if (sheet instanceof XSSFSheet) {
        anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) startCol, startRow, (short) endCol, endRow);
    } else {
        anchor = new HSSFClientAnchor(0, 0, 0, 0, (short) startCol, startRow, (short) endCol, endRow);
    }
    anchor.setAnchorType(ClientAnchor.MOVE_AND_RESIZE);

    patriarch.createPicture(anchor, workbook.addPicture(imageBytes, imgType));
    if (sheet instanceof  XSSFSheet) {
        List<XSSFShape> shapes = ((XSSFDrawing)patriarch).getShapes();
        for (XSSFShape shape : shapes) {
            XSSFPicture picture = (XSSFPicture) shape;
            picture.getPreferredSize();
        }
    }
    else {
        List<HSSFShape> list = ((HSSFSheet)sheet).getDrawingPatriarch().getChildren();
        for (HSSFShape shape : list) {
            if (shape instanceof HSSFPicture) {
                HSSFPicture picture = (HSSFPicture) shape;
//                    picture.getClientAnchor();
					picture.getAnchor();
					picture.getPictureData();
				}
			}
		}
	}

/**
 * 根据行坐标和列坐标定位到单元格,填充单元格
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param rowIndex 行坐标
 * @param columnIndex 列坐标
 * @param value 填充的值
 * @return boolean 是否成功
 * @throws IOException
 **/
public boolean fillByCoordinate(int sheetNo,int rowIndex,int columnIndex,String value)
        throws IOException {
    exception();
    if(!initSheet(sheetNo)) {
        return false;
    }
    Row row = sheet.getRow(rowIndex);
    if(row == null) {
        return false;
    }
    Cell cell = row.getCell(columnIndex);
    if(cell == null) {
        return false;
    }
    if (doublePattern.matcher(value == null ? "": value).find()){
        cell.setCellValue(Double.parseDouble(value));
    }
    else{
        cell.setCellValue(value);
    }
    return true;
}

/**
 * 根据断言predicate查找sheet当中符合条件的cell
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param predicate 筛选的断言
 * @return List<Cell> 符合条件的Cell
 * */
public List<Cell> findCells(int sheetNo,Predicate<String> predicate){
    Objects.requireNonNull(predicate);
    initCellList(sheetNo);
    return cellList.stream()
            .map(c -> {
                if(c != null && c.getCellType() == Cell.CELL_TYPE_STRING) {
                    return c.getStringCellValue();
                }
                return null;
            })// Cell流转换为String流
            .filter(predicate)
            .map(s -> cellList.stream().filter(c -> {
                if(c != null && c.getCellType() == Cell.CELL_TYPE_STRING
                        && s.equals(c.getStringCellValue())) {
                    return true;
                }
                return false;
            }).findFirst().orElse(null))// String流重新转换位Cell流
            .filter(c -> c != null)
            .collect(Collectors.toList());
}

/**
 * 根据断言predicate查找sheet当中符合条件的Row
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param predicate 筛选的断言
 * @return List<Row> 符合条件的Row
 * */
public List<Row> findRows(int sheetNo,Predicate<Row> predicate){
    if(!examine() || !initSheet(sheetNo)) {
        return null;
    }
    List<Row> rows = new ArrayList<>();
    for(int i = sheet.getFirstRowNum();i <= sheet.getLastRowNum();i++){
        Row row = sheet.getRow(i);
        if(predicate.test(row)) {
            rows.add(row);
        }
    }
    return rows;
}

/**
 * 提取变量中的值,比如 formatParamCode("${1234}"),
 * 会得到结果1234
 *
 * @param paramCode 需要提取的字符串
 * @return String
 * */
private String formatParamCode(String paramCode){
    if(paramCode == null) {
        return "";
    }
    return paramCode.replaceAll("\\$", "")
            .replaceAll("\\{", "")
            .replaceAll("\\}", "");
}

/**
 * 使用paramData当中的值替换data当中的变量
 *
 * @param data 需要提取的字符串
 * @param paramData 需要替换的值
 * @return String
 * */
private String composeMessage(String data, Map<String,String> paramData){
    String regex = "\\$\\{(.+?)\\}";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(data == null ? "": data);
    StringBuffer msg = new StringBuffer();
    while (matcher.find()) {
        String key = matcher.group(1);// 键名
        String value = paramData.get(key);// 键值
        if(value == null) {
            value = "";
        } else {
            value = value.replaceAll("\\$", "\\\\\\$");
        }
        matcher.appendReplacement(msg, value);
    }
    matcher.appendTail(msg);
    return msg.toString();
}

/**
 * 计算excel公式中的单元格的列和行加上数值后的结果
 *
 * @param isColumn 是否是行的计算
 * @param value 原始值
 * @param addNum 添加的数量
 * @return String
 * */
private String addRowOrColumnIndex(boolean isColumn,String value,int addNum){
    value = value == null ? "" : value;
    if(isColumn){
        if (!Pattern.compile("^[A-Z]+$").matcher(value).find()) {
            return value;
        }
        char[] cs = value.toCharArray();
        int cardinal = 0;
        // 组合转换为数字
        for (int i = cs.length - 1; i >= 0; i--) {
            cardinal += Math.pow(26,cs.length - 1 - i) * (cs[i] - 64);
        }
        // 加上添加后的数值
        cardinal += addNum;
        // 不能为0
        cardinal = cardinal <= 0 ? 1 : cardinal;
        // 是否需要向前借一位
        boolean borrowBit = false;
        Stack<Character> stack = new Stack<>();
        // 数字转换为组合
        while (true && cardinal > 0){
            int mode = cardinal % 26;
            // 如果到达了第一位
            if(cardinal >= 1 && cardinal < 26){
                // 是否需要借位
                if (borrowBit) {
                    mode -= 1;
                }
                // 首位借位之后必须大于0才能添加
                if (mode > 0) {
                    stack.add((char)(mode + 64));
                }
                break;
            }
            cardinal -= mode;
            cardinal /= 26;
            if (borrowBit){
                if (mode != 0) {
                    mode -= 1;
                }
                // 如果借位的时候,发现本身也为0,需要向前再借位
                else{
                    mode = 25;
                    borrowBit = true;
                    stack.add((char)(mode + 64));
                    continue;
                }
            }
            if (mode == 0){
                mode = 26;
                borrowBit = true;
            }
            else {
                borrowBit = false;
            }
            stack.add((char)(mode + 64));
        }
        int size = stack.size();
        char[] chars = new char[size];
        for (int j = size - 1; j >= 0; j--) {
            chars[size - 1 - j] = stack.get(j);
        }
        return new String(chars);
    }
    else {
        if(!Pattern.compile("^[0-9]+$").matcher(value).find()) {
            return value;
        }
        try{
            int intValue = Integer.parseInt(value);
            intValue += addNum;
            if (intValue <= 0) {
                return "1";
            }
            return Integer.toString(intValue);
        }catch (NumberFormatException e){
            return value;
        }
    }
}

/**
 * 修改公式里面单元格参数的坐标
 *
 * @param formula 公式
 * @param index 第几个单元格参数
 * @param rowAddNum 给行添加的数量
 * @param columnAddNum 给列添加的数量
 * @return String
 * */
private String composeFormula(String formula,int index,
                              int rowAddNum,int columnAddNum){
    String regex = "[A-Z]+[0-9]+";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(formula == null ? "" : formula);
    List<String> valueList = new LinkedList<>();
    String oldFormula = formula;
    while(matcher.find()){
        String value = matcher.group();
        valueList.add(value);
        formula = formula.replaceFirst(value,"@&");
    }
    if (index >= 0 && index < valueList.size()){
        String value = valueList.get(index);
        Matcher columnMatcher = Pattern.compile("[A-Z]+").matcher(value);
        String newValue = value;
        if (columnMatcher.find()){
            String columnIndex = columnMatcher.group();
            String rowIndex = value.replaceAll(columnIndex,"");
            columnIndex = addRowOrColumnIndex(true,columnIndex,columnAddNum);
            rowIndex = addRowOrColumnIndex(false,rowIndex,rowAddNum);
            newValue = columnIndex + rowIndex;
        }
        valueList.set(index,newValue);
    }
    String[] spilts = formula.split("@&");
    if (spilts.length == 0){
        if (valueList.size() == 1) {
            return valueList.get(0);
        }
        return oldFormula;
    }
    StringBuffer newFormula = new StringBuffer();
    int position = 0;
    for (int i = 0; i < spilts.length; i++) {
        newFormula.append(spilts[i]);
        if (position < valueList.size()){
            newFormula.append(valueList.get(position++));
        }
    }
    return newFormula.toString();
}

/**
 * 获取单元格里面公式的变量数量
 * 例如公式 SUM(AP40:AV40),含有两个单元格变量 AP40和AV40,
 * 使用此方法会返回2
 *
 * @param cell 需要操作的单元格
 * @return int 单元格变量的数量
 * */
public int getFormulaVariableNum(Cell cell){
    if (cell == null || cell.getCellType() != Cell.CELL_TYPE_FORMULA) {
        return 0;
    }
    String formula = cell.getCellFormula();
    Matcher matcher = Pattern.compile("[A-Z]+[0-9]+").matcher(formula == null ? "" : formula);
    int count = 0;
    while(matcher.find()){
        count++;
    }
    return count;
}

/**
 * 修改单元格的公式的参数
 * excel的所有列按照如下规则分布,
 * A,B,C,D...Z,AA,AB...AZ,BA,BB...BZ...以此类推,
 * 你可以看成是一个关于A,B,C...Z的排列组合问题
 *
 * 举例:
 * 单元格cell的公式为 SUM(AP40:AV40) 是求单元格 AP40 到 AV40的单元格的和,
 * 其中AP40中的AP表示单元格的列坐标,40表示横坐标,AV40类推。
 * 如果使用方法 composeCellFormula(cell,0,2,5),则cell的公式会修改为 SUM(AU42:AV40)
 *
 * @param cell 需要修改的单元格
 * @param index 第几个单元格参数
 * @param rowAddNum 给行添加的数量
 * @param columnAddNum 给列添加的数量
 * @return String
 * */
public void composeCellFormula(Cell cell,int index,
                               int rowAddNum,int columnAddNum){
    if (cell == null || cell.getCellType() != Cell.CELL_TYPE_FORMULA) {
        return;
    }
    if (cell instanceof HSSFCell) {
        throw new IllegalArgumentException("4.1.1及之前的版本的POI的处理xls文件的公式单元格有bug,建议换成xlsx文件," +
                "或者去官网查看哪个版本修复了这个bug(https://bz.apache.org/bugzilla/show_bug.cgi?id=64517),换成此版本POI。" +
                "如果引用的POI版本已经是修复了此BUG的版本的POI,可以删掉这个异常提示!");
    }
    String formula = cell.getCellFormula();
    cell.setCellFormula(composeFormula(formula,index,rowAddNum,columnAddNum));
}

// 初始化cellList
private void initCellList(int sheetNo){
    cellList = new ArrayList<>();
    if(examine() && !initSheet(sheetNo)) {
        return;
    }
    int rn = sheet.getLastRowNum();
    for(int i = 0;i <= rn;i++){
        Row row = sheet.getRow(i);
        if(row != null){
            short cn = row.getLastCellNum();
            for (int j = 0;j < cn;j++){
                cellList.add(row.getCell(j));
            }
        }
    }
}

/**
 * 替换掉所有行区域中的所有 ${} 标记
 * valueList对rows中${}替换的顺序是:
 * 从左至右,从上到下
 *
 * @param rows 行区域
 * @param vl 替换的值
 * */
private void replaceMark(List<Row> rows,List<String> vl){
    if (rows == null || vl == null) {
        return;
    }
    List<String> valueList = new ArrayList<>(vl);
    rows.forEach(r -> {
        if(r != null){
            r.forEach(c -> {
                if (c != null){
                    if (c.getCellType() == Cell.CELL_TYPE_STRING){
                        if("${}".equals(Optional.ofNullable(c.getStringCellValue()).orElse("").trim())){
                            if(valueList == null) {
                                return;
                            }
                            String value = valueList.stream().filter(Objects::nonNull).findFirst().orElse(null);
                            c.setCellValue(value);
                            CellStyle cellStyle = c.getCellStyle();
                            cellStyle.setWrapText(true);
                            c.setCellStyle(cellStyle);
                            if(value != null) {
                                valueList.remove(valueList.indexOf(value));
                            }
                        }
                        else if("N${}".equals(Optional.ofNullable(c.getStringCellValue()).orElse("").trim())){
                            if(valueList == null) {
                                return;
                            }
                            String value = valueList.stream().filter(Objects::nonNull).findFirst().orElse(null);
                            Matcher matcher = doublePattern.matcher(value == null ? "" : value);
                            if (matcher.find()){
                                c.setCellValue(Double.parseDouble(value));
                                CellStyle cellStyle = c.getCellStyle();
                                cellStyle.setWrapText(true);
                                c.setCellStyle(cellStyle);
                                if(value != null) {
                                    valueList.remove(valueList.indexOf(value));
                                }
                            }
                            else {
                                throw new IllegalArgumentException("N${} 所替换的内容只能为数字,非法参数\"" + value + "\"");
                            }
                        }
                    }
                }
            });
        }
    });
}

/**
 * 复制Row到sheet中的另一个Row
 *
 * @param fromSheetNo 复制的行所在的sheet
 * @param fromRow 需要复制的行
 * @param toSheetNo 粘贴的行所在的sheet
 * @param toRow 粘贴的行
 * @param copyValueFlag 是否需要复制值
 * @param needMerged 是否需要合并单元格
 */
private Row copyRow(int fromSheetNo,Row fromRow, int toSheetNo,Row toRow, boolean copyValueFlag,boolean needMerged) {
    if(fromSheetNo < 0 || fromSheetNo > workbook.getNumberOfSheets()
            || toSheetNo < 0 || toSheetNo > workbook.getNumberOfSheets()) {
        return null;
    }
    if (fromRow == null) {
        return null;
    }
    if(toRow == null){
        Sheet sheet = workbook.getSheetAt(toSheetNo);
        if(sheet == null) {
            return null;
        }
        toRow = sheet.createRow(fromRow.getRowNum());
        if(toRow == null) {
            return null;
        }
    }
    // 设置高度
    toRow.setHeight(fromRow.getHeight());
    // 遍历行中的单元格
    for(Cell c:fromRow){
        Cell newCell = toRow.createCell(c.getColumnIndex());
        copyCell(c, newCell, copyValueFlag);
    }
    // 如果需要合并
    if(needMerged){
        Sheet fromSheet = workbook.getSheetAt(fromSheetNo);
        Sheet toSheet = workbook.getSheetAt(toSheetNo);
        // 遍历行当中的所有的合并区域
//            List<CellRangeAddress> crds = fromSheet.getMergedRegions();

        //获取合并单元格的总数,并循环每一个合并单元格,
        int sheetMergeCount  = fromSheet.getNumMergedRegions();
        if(sheetMergeCount>0){
            for (int i = 0; i < sheetMergeCount; i++) {
                CellRangeAddress crd = sheet.getMergedRegion(i);
                // 如果当前合并区域的首行为复制的源行
                if(crd.getFirstRow() == fromRow.getRowNum()) {
                    // 创建对应的合并区域
                    CellRangeAddress newCellRangeAddress = new CellRangeAddress(
                            toRow.getRowNum(),
                            (toRow.getRowNum() + (crd.getLastRow() - crd.getFirstRow())),
                            crd.getFirstColumn(),
                            crd.getLastColumn());
                    // 添加合并区域
                    safeMergedRegion(toSheetNo,newCellRangeAddress);
                }
            }
        }

    }
    return toRow;
}

/**
 * 复制sheet中列的另一列
 *
 * @param fromSheetNo 复制的行所在的sheet
 * @param fromColumnIndex 需要复制的行索引
 * @param toSheetNo 粘贴的行所在的sheet
 * @param toColumnIndex 粘贴的行
 * @param copyValueFlag 是否需要复制值
 */
private void copyColumn(int fromSheetNo,int fromColumnIndex,int toSheetNo,
                        int toColumnIndex,boolean copyValueFlag) {
    if(fromSheetNo < 0 || fromSheetNo > workbook.getNumberOfSheets()
            || toSheetNo < 0 || toSheetNo > workbook.getNumberOfSheets()) {
        return;
    }
    Sheet fromSheet = workbook.getSheetAt(fromSheetNo);
    Sheet toSheet = workbook.getSheetAt(toSheetNo);
    for(int i = 0;i <= fromSheet.getLastRowNum();i++){
        Row fromRow = fromSheet.getRow(i);
        Row toRow = toSheet.getRow(i);
        if(fromRow == null) {
            continue;
        }
        if(toRow == null) {
            toRow = toSheet.createRow(i);
        }
        if(toRow == null) {
            continue;
        }
        // 设置高度
        toRow.setHeight(fromRow.getHeight());
        Cell srcCell = fromRow.getCell(fromColumnIndex);
        Cell distCell = toRow.getCell(toColumnIndex);
        if(srcCell == null) {
            continue;
        }
        if(distCell == null) {
            distCell = toRow.createCell(toColumnIndex);
        }
        // 设置列宽
        toSheet.setColumnWidth(toColumnIndex,fromSheet.getColumnWidth(fromColumnIndex));
        copyCell(srcCell,distCell,copyValueFlag);
    }
}

/**
 * 复制Cell到sheet中的另一个Cell
 *
 * @param srcCell 需要复制的单元格
 * @param distCell 粘贴的单元格
 * @param copyValueFlag true则连同cell的内容一起复制
 */
private void copyCell(Cell srcCell, Cell distCell, boolean copyValueFlag) {
    if (srcCell == null || distCell == null) {
        return;
    }

    // 获取源单元格的样式
    CellStyle srcStyle = srcCell.getCellStyle();
    // 复制样式
    distCell.setCellStyle(srcStyle);

    // 复制评论
    if(srcCell.getCellComment() != null) {
        distCell.setCellComment(srcCell.getCellComment());
    }
    // 不同数据类型处理
    int srcCellType = srcCell.getCellType();
    if(copyValueFlag) {
        if(srcCellType == Cell.CELL_TYPE_NUMERIC) {
            if(DateUtil.isCellDateFormatted(srcCell)) {
                distCell.setCellValue(srcCell.getDateCellValue());
            } else {
                distCell.setCellValue(srcCell.getNumericCellValue());
            }
        } else if(srcCellType == Cell.CELL_TYPE_STRING) {
            distCell.setCellValue(srcCell.getRichStringCellValue());
        } else if(srcCellType == Cell.CELL_TYPE_BLANK) {

        } else if(srcCellType == Cell.CELL_TYPE_BOOLEAN) {
            distCell.setCellValue(srcCell.getBooleanCellValue());
        } else if(srcCellType == Cell.CELL_TYPE_ERROR) {
            distCell.setCellErrorValue(srcCell.getErrorCellValue());
        } else if(srcCellType == Cell.CELL_TYPE_FORMULA) {
            distCell.setCellFormula(srcCell.getCellFormula());
        } else {
        }
    }
    /*CellType srcCellType = srcCell.getCellTypeEnum();
    if(copyValueFlag) {
        if(srcCellType == CellType.NUMERIC) {
            if(DateUtil.isCellDateFormatted(srcCell)) {
                distCell.setCellValue(srcCell.getDateCellValue());
            } else {
                distCell.setCellValue(srcCell.getNumericCellValue());
            }
        } else if(srcCellType == CellType.STRING) {
            distCell.setCellValue(srcCell.getRichStringCellValue());
        } else if(srcCellType == CellType.BLANK) {

        } else if(srcCellType == CellType.BOOLEAN) {
            distCell.setCellValue(srcCell.getBooleanCellValue());
        } else if(srcCellType == CellType.ERROR) {
            distCell.setCellErrorValue(srcCell.getErrorCellValue());
        } else if(srcCellType == CellType.FORMULA) {
            distCell.setCellFormula(srcCell.getCellFormula());
        } else {
        }
    }*/
}

/**
 * 合并单元格区域,本方法是安全的操作,在出现合并冲突的时候,
 * 分割合并区域,然后最大限度的合并冲突区域
 *
 * 使用此方法而不是采用addMergedRegion()和
 * addMergedRegionUnsafe()合并单元格区间,
 * 因为此方法会自行解决合并区间冲突,避免报错或者生成
 * 无法打开的excel
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param firstRow 开始行
 * @param lastRow 结束行
 * @param firstCol 开始列
 * @param lastCol 结束列
 * */
public void mergedRegion(int sheetNo,int firstRow, int lastRow, int firstCol, int lastCol){
    if(firstRow > lastRow || firstCol > lastCol) {
        return;
    }
    CellRangeAddress address = new CellRangeAddress(firstRow,lastRow,firstCol,lastCol);
    safeMergedRegion(sheetNo,address);
}

/**
 * 合并单元格区域,本方法是安全的操作,在出现合并冲突的时候,
 * 分割合并区域,然后最大限度的合并冲突区域
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param rangeAddress 合并的单元格区域
 * */
private void safeMergedRegion(int sheetNo,CellRangeAddress rangeAddress){
    if(!examine() || !initSheet(sheetNo) || rangeAddress == null) {
        return;
    }
    // 获取所有合并的区域
/*    List<CellRangeAddress> crds = sheet.getMergedRegions();
    if(crds == null) {
        return;
    }*/
    //获取合并单元格的总数,并循环每一个合并单元格,
    int sheetMergeCount = sheet.getNumMergedRegions();
    if(sheetMergeCount<=0){
        return;
    }

    // 获取描述单元格区域的坐标,
    // 在首行和首列,坐标等于行编号,
    // 在末行和末列,坐标等于行编号加1
    int firstRow = rangeAddress.getFirstRow();
    int lastRow = rangeAddress.getLastRow() + 1;
    int firstColumn = rangeAddress.getFirstColumn();
    int lastColumn = rangeAddress.getLastColumn() + 1;
    // 查找冲突的单元格区域
    CellRangeAddress conflictRange = null;
    Boolean checkFlagConflit = false;
    for (int i = 0; i < sheetMergeCount; i++) {
        if(checkFlagConflit){
            continue;
        }
        CellRangeAddress crd = sheet.getMergedRegion(i);
        // 获取单元格区域的坐标
        int cFirstRow = crd.getFirstRow();
        int cLastRow = crd.getLastRow() + 1;
        int cFirstColumn = crd.getFirstColumn();
        int cLastColumn = crd.getLastColumn()  + 1;
        // 每个合并单元格区域看成一个长方形
        // 计算两个长方形中心的X坐标的距离
        float xDistance = (float)(lastColumn + firstColumn)/2
                - (float)(cLastColumn + cFirstColumn)/2;
        // 每个合并单元格区域看成一个长方形
        // 计算两个长方形中心的Y坐标的距离
        float yDistance = (float)(lastRow + firstRow)/2
                - (float)(cLastRow + cFirstRow)/2;
        // 获取距离的绝对值
        xDistance = xDistance >= 0 ? xDistance : -xDistance;
        yDistance = yDistance >= 0 ? yDistance : -yDistance;
        // 如果两个合并区域相交了,返回true
        if(xDistance < ((float)(lastColumn - firstColumn)/2 + (float)(cLastColumn - cFirstColumn)/2)
                && yDistance < ((float)(lastRow - firstRow)/2 + (float)(cLastRow - cFirstRow)/2)) {
            checkFlagConflit = true;
            conflictRange = crd;
        }
    }

    /*CellRangeAddress conflictRange = crds.stream()
            .filter(crd -> {
                // 获取单元格区域的坐标
                int cFirstRow = crd.getFirstRow();
                int cLastRow = crd.getLastRow() + 1;
                int cFirstColumn = crd.getFirstColumn();
                int cLastColumn = crd.getLastColumn()  + 1;
                // 每个合并单元格区域看成一个长方形
                // 计算两个长方形中心的X坐标的距离
                float xDistance = (float)(lastColumn + firstColumn)/2
                        - (float)(cLastColumn + cFirstColumn)/2;
                // 每个合并单元格区域看成一个长方形
                // 计算两个长方形中心的Y坐标的距离
                float yDistance = (float)(lastRow + firstRow)/2
                        - (float)(cLastRow + cFirstRow)/2;
                // 获取距离的绝对值
                xDistance = xDistance >= 0 ? xDistance : -xDistance;
                yDistance = yDistance >= 0 ? yDistance : -yDistance;
                // 如果两个合并区域相交了,返回true
                if(xDistance < ((float)(lastColumn - firstColumn)/2 + (float)(cLastColumn - cFirstColumn)/2)
                        && yDistance < ((float)(lastRow - firstRow)/2 + (float)(cLastRow - cFirstRow)/2)) {
                    return true;
                }
                return false;
            })
            .findFirst()
            .orElse(null);*/
    // 如果没有查找到冲突的区域,直接合并
    if(conflictRange == null){
        if(examineRange(rangeAddress)) {
            sheet.addMergedRegion(rangeAddress);
        }
    }
    // 如果合并区域冲突了,分离新增的合并区域
    List<CellRangeAddress> splitRangeAddr = splitRangeAddress(conflictRange,rangeAddress);
    if(splitRangeAddr != null) {
        splitRangeAddr.forEach(sra -> safeMergedRegion(sheetNo,sra));
    }
}

/**
 * 如果插入的目标合并区域target和sheet中已存在的合并区域source冲突,
 * 把target分割成多个合并区域,这些合并区域都不会和source冲突
 *
 * @param source 已经存在的合并单元格区域
 * @param target 新增的合并单元格区域
 * @return target分离之后的合并单元格列表
 * */
private List<CellRangeAddress> splitRangeAddress(CellRangeAddress source,CellRangeAddress target){
    List<CellRangeAddress> splitRangeAddr = null;
    if(source == null || target == null) {
        return null;
    }
    // 获取source区域的坐标
    int sFirstRow = source.getFirstRow();
    int sLastRow = source.getLastRow() + 1;
    int sFirstColumn = source.getFirstColumn();
    int sLastColumn = source.getLastColumn() + 1;
    // 获取target区域的坐标
    int tFirstRow = target.getFirstRow();
    int tLastRow = target.getLastRow() + 1;
    int tFirstColumn = target.getFirstColumn();
    int tLastColumn = target.getLastColumn() + 1;

    while(true){
        if(splitRangeAddr == null) {
            splitRangeAddr = new ArrayList<>();
        }
        // 如果target被切分得无法越过source合并区域,退出循环
        if(tFirstRow >= sFirstRow && tLastRow <= sLastRow
                && tFirstColumn >= sFirstColumn && tLastColumn <= sLastColumn) {
            break;
        }
        // 只考虑Y坐标,当source的最大Y坐标sLastRow在开区间(tFirstRow,tLastRow)
        if(sLastRow > tFirstRow && sLastRow < tLastRow){
            CellRangeAddress address =
                    new CellRangeAddress(sLastRow,tLastRow - 1,tFirstColumn,tLastColumn - 1);
            tLastRow = sLastRow;
            if(examineRange(address)) {
                splitRangeAddr.add(address);
            }
        }
        // 只考虑Y坐标,当source的最小Y坐标sFirstRow在开区间(tFirstRow,tLastRow)
        if(sFirstRow > tFirstRow && sFirstRow < tLastRow){
            CellRangeAddress address =
                    new CellRangeAddress(tFirstRow,sFirstRow - 1,tFirstColumn,tLastColumn - 1);
            tFirstRow = sFirstRow;
            if(examineRange(address)) {
                splitRangeAddr.add(address);
            }
        }
        // 只考虑X坐标,当source的最小X坐标sFirstColumn在开区间(tFirstColumn,tLastColumn)
        if(sFirstColumn > tFirstColumn && sFirstColumn < tLastColumn){
            CellRangeAddress address =
                    new CellRangeAddress(tFirstRow,tLastRow - 1,tFirstColumn,sFirstColumn - 1);
            tFirstColumn = sFirstColumn;
            if(examineRange(address)) {
                splitRangeAddr.add(address);
            }
        }
        // 只考虑X坐标,当source的最大X坐标sLastColumn在开区间(tFirstColumn,tLastColumn)
        if(sLastColumn > tFirstColumn && sLastColumn < tLastColumn){
            CellRangeAddress address =
                    new CellRangeAddress(tFirstRow,tLastRow - 1,sLastColumn,tLastColumn - 1);
            tLastColumn = sLastColumn;
            if(examineRange(address)) {
                splitRangeAddr.add(address);
            }
        }
    }
    return splitRangeAddr;
}

// 检查合并区域
private boolean examineRange(CellRangeAddress address){
    if(address == null || !examine()) {
        return false;
    }
    int firstRowNum = address.getFirstRow();
    int lastRowNum = address.getLastRow();
    int firstColumnNum = address.getFirstColumn();
    int lastColumnNum = address.getLastColumn();
    if(firstRowNum == lastRowNum && firstColumnNum == lastColumnNum) {
        return false;
    }
    return true;
}

private void exception() throws EncryptedDocumentException, IOException {
    if(ex != null){
        if(ex instanceof EncryptedDocumentException) {
            throw new EncryptedDocumentException("无法读取的加密文件");
        } else if(ex instanceof IOException) {
            throw new IOException(ex);
        } else {
            return;
        }
    }
}

/**
 * 把sheet[sheetNo]当中所有的行从startRow位置开始,
 * 全部下移moveNum数量的位置,并且在腾出的空间当中创建新行
 *
 * 应该使用本方法而不是采用sheet.shiftRows()和sheet.createRow(),
 * 主要是因为插入一段行的时候会进行如下步骤:
 * 第一:使用shiftRows腾出空间
 * 第二:使用createRow(position)从position开始创建行
 * 但是这样,后面下移的行的合并单元格会部分消失,
 * 并且新创建的行的合并单元格并没有消失,这是因为sheet当中的
 * 大于position的CellRangeAddress并没有跟着下移。
 * 而使用本方法下移并且在中间自动插入行,新插入的行不会含有任何合并单元格,
 * 并且原来的合并单元格也不会消失。
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param startRow 移动的Row区间的起始位置
 * @param moveNum 移动的行数
 * */
public synchronized void shiftAndCreateRows(int sheetNo,int startRow,int moveNum){
    if(!examine() || !initSheet(sheetNo))
        return;

    // 复制当前需要操作的sheet到一个临时的sheet
    Sheet tempSheet = workbook.cloneSheet(sheetNo);
    // 获取临时sheet在workbook当中的索引
    int tempSheetNo = workbook.getSheetIndex(tempSheet);
    // 得到临时sheet的第一个row的索引
    int firstRowNum = tempSheet.getFirstRowNum();
    // 得到临时sheet的最后一个row的索引
    int lastRowNum = tempSheet.getLastRowNum();
    if(!clearSheet(sheetNo)){
        return;
    }
    if (startRow <= lastRowNum){
        for(int i= firstRowNum;i <= lastRowNum - firstRowNum + moveNum + 1;i++) {
            sheet.createRow(i);
        }
    }
    else {
        for(int i= firstRowNum;i <= startRow + moveNum + 1;i++) {
            sheet.createRow(i);
        }
    }
    for(int i= firstRowNum;i <= lastRowNum;i++){
        if(i < startRow) {
            copyRow(tempSheetNo,tempSheet.getRow(i),sheetNo,sheet.getRow(i),true,true);
        }
        // 到达需要插入的索引的位置,需要留出moveNum空间的行
        else {
            copyRow(tempSheetNo,tempSheet.getRow(i),sheetNo,sheet.getRow(i + moveNum),true,true);
        }
    }
    settingColumnWidth(tempSheetNo,sheetNo);
    // 删除临时的sheet
    workbook.removeSheetAt(tempSheetNo);
}

/**
 * 把sheet[sheetNo]当中所有的列从startColumn位置开始,
 * 全部右移moveNum数量的位置,并且在腾出的空间当中创建新列
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param startColumn 移动的列区间的起始位置
 * @param moveNum 移动的列数
 * */
public synchronized void shiftAndCreateColumns(int sheetNo,int startColumn,int moveNum){
    if(!examine() || !initSheet(sheetNo))
        return;

    // 复制当前需要操作的sheet到一个临时的sheet
    Sheet tempSheet = workbook.cloneSheet(sheetNo);
    // 获取临时sheet在workbook当中的索引
    int tempSheetNo = workbook.getSheetIndex(tempSheet);
    // 得到临时sheet的第一个row的索引
    int firstRowNum = tempSheet.getFirstRowNum();
    // 得到临时sheet的最后一个row的索引
    int lastRowNum = tempSheet.getLastRowNum();

    if(!clearSheet(sheetNo)){
        return;
    }

    for(int i = firstRowNum;i <= lastRowNum;i++){
        Row row = tempSheet.getRow(i);
        if(row != null){
            int addNum = row.getLastCellNum() + moveNum;
            for(int j = 0;j < moveNum;j++){
                row.createCell(addNum);
            }
            for(int j = 0;j <= row.getLastCellNum();j++){
                if(j <= startColumn) {
                    copyColumn(tempSheetNo,j,sheetNo,j,true);
                } else {
                    copyColumn(tempSheetNo,j,sheetNo,j + moveNum,true);
                }
            }
        }
    }
    /*List<CellRangeAddress> crds = tempSheet.getMergedRegions();
    if(crds == null) {
        return;
    }*/
    //获取合并单元格的总数,并循环每一个合并单元格,
    int sheetMergeCount = sheet.getNumMergedRegions();
    if(sheetMergeCount<=0){
        return;
    }
    for (int i = 0; i < sheetMergeCount; i++) {
        CellRangeAddress crd = sheet.getMergedRegion(i);
        int firstColumn;
        int lastColumn;
        if((lastColumn = crd.getLastColumn()) <= startColumn) {
            safeMergedRegion(sheetNo,crd);
        } else if((firstColumn = crd.getFirstColumn()) <= startColumn){
            if(lastColumn > startColumn){
                CellRangeAddress range = new CellRangeAddress(crd.getFirstRow(),crd.getLastRow(),firstColumn,startColumn);
                if(examineRange(range)) {
                    safeMergedRegion(sheetNo,range);
                }
                range = new CellRangeAddress(crd.getFirstRow(),crd.getLastRow(),
                        startColumn + moveNum + 1,lastColumn + moveNum);
                if(examineRange(range)) {
                    safeMergedRegion(sheetNo,range);
                }
            }
        }
        else if(firstColumn > startColumn){
            CellRangeAddress range = new CellRangeAddress(crd.getFirstRow(),crd.getLastRow(),
                    firstColumn + moveNum,lastColumn + moveNum);
            if(examineRange(range)) {
                safeMergedRegion(sheetNo,range);
            }
        }
    }

    /*crds.forEach(crd -> {
        int firstColumn;
        int lastColumn;
        if((lastColumn = crd.getLastColumn()) <= startColumn) {
            safeMergedRegion(sheetNo,crd);
        } else if((firstColumn = crd.getFirstColumn()) <= startColumn){
            if(lastColumn > startColumn){
                CellRangeAddress range = new CellRangeAddress(crd.getFirstRow(),crd.getLastRow(),firstColumn,startColumn);
                if(examineRange(range)) {
                    safeMergedRegion(sheetNo,range);
                }
                range = new CellRangeAddress(crd.getFirstRow(),crd.getLastRow(),
                        startColumn + moveNum + 1,lastColumn + moveNum);
                if(examineRange(range)) {
                    safeMergedRegion(sheetNo,range);
                }
            }
        }
        else if(firstColumn > startColumn){
            CellRangeAddress range = new CellRangeAddress(crd.getFirstRow(),crd.getLastRow(),
                    firstColumn + moveNum,lastColumn + moveNum);
            if(examineRange(range)) {
                safeMergedRegion(sheetNo,range);
            }
        }
    });*/
    // 删除临时的sheet
    workbook.removeSheetAt(tempSheetNo);
}

/**
 * 移除掉行区域
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param startRow 起始行
 * @param endRow 结束行
 * */
public synchronized void removeRowArea(int sheetNo,int startRow,int endRow){
    if(!examine() || !initSheet(sheetNo) || startRow > endRow)
        return;

    // 复制当前需要操作的sheet到一个临时的sheet
    Sheet tempSheet = workbook.cloneSheet(sheetNo);
    // 获取临时sheet在workbook当中的索引
    int tempSheetNo = workbook.getSheetIndex(tempSheet);
    // 得到临时sheet的第一个row的索引
    int firstRowNum = tempSheet.getFirstRowNum();
    // 得到临时sheet的最后一个row的索引
    int lastRowNum = tempSheet.getLastRowNum();
    // 清空sheet
    if(!clearSheet(sheetNo)){
        return;
    }

    int delNum = endRow - startRow + 1;
    for(int i = firstRowNum;i <= lastRowNum;i++){
        Row fromRow = tempSheet.getRow(i);
        Row toRow =  sheet.createRow(i);
        if(i < startRow) {
            copyRow(tempSheetNo,fromRow,sheetNo,toRow,true,false);
        } else {
            copyRow(tempSheetNo,tempSheet.getRow(i + delNum),sheetNo,toRow,true,false);
        }
    }
    /*List<CellRangeAddress> crds = tempSheet.getMergedRegions();
    if(crds == null) {
        return;
    }*/
    //获取合并单元格的总数,并循环每一个合并单元格,
    int sheetMergeCount = sheet.getNumMergedRegions();
    if(sheetMergeCount<=0){
        return;
    }
    for (int i = 0; i < sheetMergeCount; i++) {
        CellRangeAddress crd = sheet.getMergedRegion(i);
        if(crd != null){
            int firstMergedRow = crd.getFirstRow();
            int lastMergedRow = crd.getLastRow();
            int firstMergedColumn = crd.getFirstColumn();
            int lastMergedClolunm = crd.getLastColumn();
            if(lastMergedRow < startRow) {
                safeMergedRegion(sheetNo,crd);
            } else if(lastMergedRow >= startRow){
                if(lastMergedRow <= endRow){
                    if(firstMergedRow < startRow){
                        mergedRegion(sheetNo,firstMergedRow,startRow - 1,firstMergedColumn,lastMergedClolunm);
                    }
                }
                else if(lastMergedRow > endRow){
                    if(firstMergedRow < startRow){
                        mergedRegion(sheetNo,firstMergedRow,lastMergedRow - delNum,firstMergedColumn,lastMergedClolunm);
                    }
                    else if(firstMergedRow >= startRow && firstMergedRow <= endRow){
                        mergedRegion(sheetNo,endRow + 1 - delNum,lastMergedRow - delNum,firstMergedColumn,lastMergedClolunm);
                    }
                    else if(firstMergedRow > endRow){
                        mergedRegion(sheetNo,firstMergedRow - delNum,lastMergedRow - delNum,firstMergedColumn,lastMergedClolunm);
                    }
                }
            }
        }
    }
    /*crds.forEach(crd -> {
        if(crd != null){
            int firstMergedRow = crd.getFirstRow();
            int lastMergedRow = crd.getLastRow();
            int firstMergedColumn = crd.getFirstColumn();
            int lastMergedClolunm = crd.getLastColumn();
            if(lastMergedRow < startRow) {
                safeMergedRegion(sheetNo,crd);
            } else if(lastMergedRow >= startRow){
                if(lastMergedRow <= endRow){
                    if(firstMergedRow < startRow){
                        mergedRegion(sheetNo,firstMergedRow,startRow - 1,firstMergedColumn,lastMergedClolunm);
                    }
                }
                else if(lastMergedRow > endRow){
                    if(firstMergedRow < startRow){
                        mergedRegion(sheetNo,firstMergedRow,lastMergedRow - delNum,firstMergedColumn,lastMergedClolunm);
                    }
                    else if(firstMergedRow >= startRow && firstMergedRow <= endRow){
                        mergedRegion(sheetNo,endRow + 1 - delNum,lastMergedRow - delNum,firstMergedColumn,lastMergedClolunm);
                    }
                    else if(firstMergedRow > endRow){
                        mergedRegion(sheetNo,firstMergedRow - delNum,lastMergedRow - delNum,firstMergedColumn,lastMergedClolunm);
                    }
                }
            }
        }
    });*/
    settingColumnWidth(tempSheetNo,sheetNo);
    // 删除临时的sheet
    workbook.removeSheetAt(tempSheetNo);
}

/**
 * 移除掉列区域
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @param startCol 起始列
 * @param endCol 结束列
 * */
public synchronized void removeColumnArea(int sheetNo,int startCol,int endCol){
    if(!examine() || !initSheet(sheetNo) || startCol > endCol)
        return;

    // 复制当前需要操作的sheet到一个临时的sheet
    Sheet tempSheet = workbook.cloneSheet(sheetNo);
    // 获取临时sheet在workbook当中的索引
    int tempSheetNo = workbook.getSheetIndex(tempSheet);
    // 得到临时sheet的第一个row的索引
    int firstRowNum = tempSheet.getFirstRowNum();
    // 得到临时sheet的最后一个row的索引
    int lastRowNum = tempSheet.getLastRowNum();

    if(!clearSheet(sheetNo)){
        return;
    }

    for(int i = firstRowNum;i <= lastRowNum;i++){
        Row row = tempSheet.getRow(i);
        if(row != null){
            for(int j = 0;j < row.getLastCellNum();j++){
                // 到达删除区间之前正常复制
                if(j < startCol) {
                    copyColumn(tempSheetNo,j,sheetNo,j,true);
                }
                // 到达删除区间后,跳过区间长度复制
                else {
                    copyColumn(tempSheetNo,j + endCol - startCol + 1,sheetNo,j,true);
                }
            }
        }
    }
    /*List<CellRangeAddress> crds = tempSheet.getMergedRegions();
    if(crds == null) {
        return;
    }*/
    //获取合并单元格的总数,并循环每一个合并单元格,
    int sheetMergeCount = sheet.getNumMergedRegions();
    if(sheetMergeCount<=0){
        return;
    }
    for (int i = 0; i < sheetMergeCount; i++) {
        CellRangeAddress crd = sheet.getMergedRegion(i);
        if (crd != null) {
            int delColNum = endCol - startCol + 1;
            int firstMergedRow = crd.getFirstRow();
            int lastMergedRow = crd.getLastRow();
            int firstMergedColumn = crd.getFirstColumn();
            int lastMergedClolunm = crd.getLastColumn();
            if(lastMergedClolunm < startCol) {
                safeMergedRegion(sheetNo,crd);
            } else if(lastMergedClolunm >= startCol){
                if(lastMergedClolunm <= endCol){
                    if(firstMergedColumn < startCol){
                        mergedRegion(sheetNo,firstMergedRow,lastMergedRow,firstMergedColumn,startCol - 1);
                    }
                }
                else if(lastMergedClolunm > endCol){
                    if(firstMergedColumn < startCol){
                        mergedRegion(sheetNo,firstMergedRow,lastMergedRow,firstMergedColumn,lastMergedClolunm - delColNum);
                    }
                    else if(firstMergedColumn >= startCol && firstMergedColumn <= endCol){
                        mergedRegion(sheetNo,firstMergedRow,lastMergedRow,endCol + 1 - delColNum,lastMergedClolunm - delColNum);
                    }
                    else if(firstMergedColumn > endCol){
                        mergedRegion(sheetNo,firstMergedRow,lastMergedRow,firstMergedColumn - delColNum,lastMergedClolunm -delColNum);
                    }
                }
            }
        }
    }
    /*crds.forEach(crd -> {
        int delColNum = endCol - startCol + 1;
        int firstMergedRow = crd.getFirstRow();
        int lastMergedRow = crd.getLastRow();
        int firstMergedColumn = crd.getFirstColumn();
        int lastMergedClolunm = crd.getLastColumn();
        if(lastMergedClolunm < startCol) {
            safeMergedRegion(sheetNo,crd);
        } else if(lastMergedClolunm >= startCol){
            if(lastMergedClolunm <= endCol){
                if(firstMergedColumn < startCol){
                    mergedRegion(sheetNo,firstMergedRow,lastMergedRow,firstMergedColumn,startCol - 1);
                }
            }
            else if(lastMergedClolunm > endCol){
                if(firstMergedColumn < startCol){
                    mergedRegion(sheetNo,firstMergedRow,lastMergedRow,firstMergedColumn,lastMergedClolunm - delColNum);
                }
                else if(firstMergedColumn >= startCol && firstMergedColumn <= endCol){
                    mergedRegion(sheetNo,firstMergedRow,lastMergedRow,endCol + 1 - delColNum,lastMergedClolunm - delColNum);
                }
                else if(firstMergedColumn > endCol){
                    mergedRegion(sheetNo,firstMergedRow,lastMergedRow,firstMergedColumn - delColNum,lastMergedClolunm -delColNum);
                }
            }
        }
    });*/
    // 删除临时的sheet
    workbook.removeSheetAt(tempSheetNo);
}

private void settingColumnWidth(int sourceSheetNo,int sheetNo){
    if(sourceSheetNo < 0 || sourceSheetNo > workbook.getNumberOfSheets() ||
            sheetNo < 0 || sheetNo > workbook.getNumberOfSheets()) {
        return;
    }
    List<Row> rows = new ArrayList<>();
    for(int i = sheet.getFirstRowNum();i <= sheet.getLastRowNum();i++){
        Row row = sheet.getRow(i);
        if(row != null) {
            rows.add(row);
        }
    }
    Row maxColumnRow = rows.stream().max((r1,r2) -> {
        if (r1 == null && r2 == null) {
            return 0;
        }
        if (r1 == null) {
            return 1;
        }
        if (r2 == null) {
            return -1;
        }
        if (r1.getLastCellNum() == r2.getLastCellNum()) {
            return 0;
        }
        if (r1.getLastCellNum() > r2.getLastCellNum()) {
            return 1;
        } else {
            return -1;
        }
    }).filter(r -> r != null).orElse(null);
    if(maxColumnRow != null){
        int maxColumn = maxColumnRow.getLastCellNum();
        for (int i = 0; i < maxColumn; i++) {
            workbook.getSheetAt(sheetNo).setColumnWidth(i,workbook.getSheetAt(sourceSheetNo).getColumnWidth(i));
        }
    }
}

/**
 * 清除掉sheet,清除不是删除,只是会清除所有
 * 的列的值和和合并单元格
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @return boolean true-成功 false-失败
 * */
public synchronized boolean clearSheet(int sheetNo){
    if(!examine()) {
        return false;
    }
    int sheetNum;
    if(sheetNo < 0 || sheetNo > (sheetNum = workbook.getNumberOfSheets())) {
        return false;
    }

    for(int i = 0;i < sheetNum;i++){
        if(i == sheetNo){
            String sheetName = workbook.getSheetName(i);
            workbook.removeSheetAt(i);
            workbook.createSheet(sheetName);
        }
        if(i > sheetNo){
            int offset = i - sheetNo;
            String sheetName = workbook.getSheetName(i-offset);
            Sheet newSheet = workbook.cloneSheet(i-offset);
            workbook.removeSheetAt(i-offset);
            workbook.setSheetName(workbook.getSheetIndex(newSheet),sheetName);
        }
    }
    if(!initSheet(sheetNo)) {
        return false;
    }
    return true;
}

/**
 * 存储Excel
 *
 * @param path 存储路径
 * @throws IOException
 */
public void save(String path) throws
        IOException {
    exception();
    if(!examine())
        return;
    try (FileOutputStream fos = new FileOutputStream(path)){
        workbook.write(fos) ;
    }
}

/**
 * 返回Excel的字节数组
 *
 * @return byte[]
 */
public byte[] getBytes(){
    if(!examine())
        return null;
    try(ByteArrayOutputStream ops = new ByteArrayOutputStream()){
        workbook.write(ops);
        return ops.toByteArray();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 返回Workbook
 *
 * @return Workbook
 * @throws IOException
 * */
public Workbook getWorkbook()
        throws IOException {
    exception();
    return workbook;
}

/**
 * @return sheet的数量
 * */
public int getSheetNum(){
    return workbook.getNumberOfSheets();
}

/**
 * 返回sheet的行数量
 *
 * @param sheetNo 需要操作的Sheet的编号
 * @return int 行数量
 * */
public int getSheetRowNum(int sheetNo){
    if(!examine() || !initSheet(sheetNo))
        return 0;
    return sheets[sheetNo].getLastRowNum();
}

/**
 * 设置excel的缩放率
 *
 * @param zoomFz 缩放率分子
 * @param zoomFm 缩放率分母
 * */
public void setZoom(int zoomFz, int zoomFm){
    if(!examine() || !initSheet(workbook.getSheetIndex(sheet)))
        return;
    for (int i = 0; i < sheets.length; i++) {
        //设置放大属性(Zoom被明确为一个分数,例如下面的75%使用3作为分子,4作为分母)
//            sheets[i].setZoom(3,4);
			sheets[i].setZoom(zoomFz,zoomFm);
		}
	}

@Override
public boolean equals(Object o){
    if(o == null)
        return false;
    if(o == this)
        return true;
    if(!(o instanceof ExcelTemplate))
        return false;
    if(examine() ^ ((ExcelTemplate)o).examine())
        return false;
    return Objects.equals(path,((ExcelTemplate)o).path);
}

@Override
public int hashCode(){
    int hash = Objects.hashCode(path);
    return hash >>> 16 ^ hash;
}

@Override
public String toString(){
    return "ExcelTemplate from " + path + " is " +
            (examine() ? "effective" : "invalid");
}
}

`

3、使用