阿里巴巴(Alibaba)EasyExcel之自定义

背景

应项目的要求,改了订单导出的功能,项目上顺便又改造了导出的导出的框架,从原本的流读取,换成的现在的开源项目EasyExcel。再后来,发现我做的导出少了原本的单元格合并。。。。。。。

效果图

阿里巴巴(Alibaba)EasyExcel之自定义_第1张图片
效果如上,就是部分公共信息是合并单元格显示的,到了商品上又不是合并,总价上又是合并的

EasyExcel介绍

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
对于合并单元格,官网是这样的写的:
阿里巴巴(Alibaba)EasyExcel之自定义_第2张图片
这样写的比较笼统,不太容易看得懂,但是最后一句很关键:“合并策略可以自己写”

EasyExcel官网地址:https://alibaba-easyexcel.github.io/index.html

实战

由于项目需要合并的复杂程度,这里我们的合并策略肯定要自己写的,但是我百度一下发现。。。。。全是把官网内容拷过来的,就没有一个人去实战的吗?
最后,好在这位老哥帮我充分理解了如何自定义策略:【转】利用阿里研发的easyexcel导入导出excel,避免OOM,并对excel加密保护

下面我们来通过我的操作来一步步讲解如何自定义合并策略
1.合并策略的编写,在如官网所描述的,你要做合并单元格,那么你就得先提供一个合并策略,共用的合并策略有以下两种:LoopMergeStrategy(循环合并),OnceAbsoluteMergeStrategy(一次性合并)
我这里拿一次性合并(OnceAbsoluteMergeStrategy)的源码来说一下:

public class OnceAbsoluteMergeStrategy extends AbstractMergeStrategy {
    private int firstRowIndex;
    private int lastRowIndex;
    private int firstColumnIndex;
    private int lastColumnIndex;

    public OnceAbsoluteMergeStrategy(int firstRowIndex, int lastRowIndex, int firstColumnIndex, int lastColumnIndex) {
        if (firstRowIndex >= 0 && lastRowIndex >= 0 && firstColumnIndex >= 0 && lastColumnIndex >= 0) {
            this.firstRowIndex = firstRowIndex;
            this.lastRowIndex = lastRowIndex;
            this.firstColumnIndex = firstColumnIndex;
            this.lastColumnIndex = lastColumnIndex;
        } else {
            throw new IllegalArgumentException("All parameters must be greater than 0");
        }
    }

    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
        if (cell.getRowIndex() == this.firstRowIndex && cell.getColumnIndex() == this.firstColumnIndex) {
            CellRangeAddress cellRangeAddress = new CellRangeAddress(this.firstRowIndex, this.lastRowIndex, this.firstColumnIndex, this.lastColumnIndex);
            sheet.addMergedRegionUnsafe(cellRangeAddress);
        }

    }
}

首先,我们要重构类方法和重构merge方法,重构类方法一般是用来做数据处理和数据采集,将采集到的数据供merge方法使用。好像这里,它就是将要合并的行数以及列记录下来,再拿去merge去判断是否需要合并。
然后,merge方法是在写excel表时执行的,所以它这里入参有sheet(哪个sheet页)和cell(单元格),cell这里有重点说一下,EasyExcel写excel表时是一个一个单元格去写的,如这次进merge写的是A1单元格,下次进merge写的是A2单元格,所以merge是每一个单元格写的时候都会执行一次的。(merge会执行多次哦,别写循环了,很容易死循环的。

说完源码,来看看我写的合并策略

public class OrderAbsoluteMergeStrategy extends AbstractMergeStrategy {

    private List<Integer> colMergeList;
    //<行号,往下数量>
    private Map<Integer, Long> map = new HashMap<>();


    public OrderAbsoluteMergeStrategy(List<?> data, Class clazz) throws Excel4JException {
        List<OrderExportDto> list = (List<OrderExportDto>) data;
        int currentRowNo = list.size() + 1;
        if (list.size() == 0) {
            return;
        }
        if (colMergeList == null) {
            colMergeList = UtilsMy.getColMerge(clazz);
        }
        if (colMergeList.size() == 0) {
            return;
        }
        Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(OrderExportDto::getOrderNo, Collectors.counting()));
        String lastOrderNo = "";
        for (int i = 0; i < list.size(); i++) {
            String orderNo = list.get(i).getOrderNo();
            if (orderNo.equals(lastOrderNo)) {
                lastOrderNo = orderNo;
                continue;
            }
            Long aLong = collect.get(orderNo);
            if (aLong > 1) {
                map.put(currentRowNo - list.size() + i, aLong - 1);
            }
            lastOrderNo = orderNo;
        }
    }


    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, Integer integer) {
        if (map.containsKey(cell.getRowIndex()) && colMergeList.contains(cell.getColumnIndex())){
            Long b = map.get(cell.getRowIndex());
            Integer a= cell.getRowIndex();
            Integer column = cell.getColumnIndex();
            CellRangeAddress cellRangeAddress = new CellRangeAddress(a, a + b.intValue(), column, column);
            sheet.addMergedRegionUnsafe(cellRangeAddress);
        }
    }
}

入参是需要导出的数据data,导出的类clazz,因为是自定义的合并列,我们无法做到直接告诉它哪写列是需要合并的,所以我们用了自定义注解的方式去获取需要合并的类,通过UtilsMy.getColMerge方法可以获取到需要合并列的一个集合。然后,再通过对数据集合的排序,筛选(需求是相同订单号的合并,商品分开展示),最终形成 <行号,往下合并多少行> map。merge方法里面判断,这一行这一列是否需要合并,如果需要则合并,往下合并a+b行。

补充部分代码:
UtilsMy.getColMerge方法:

public class UtilsMy {
    public static List<Integer> getColMerge(Class<?> clz) throws Excel4JException {
        List<Integer> list = new ArrayList<>();
        List<Field> fields = new ArrayList();

        for(Class clazz = clz; clazz != Object.class; clazz = clazz.getSuperclass()) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        }
        //字段的反射顺序和编写时的顺序是一样的
        int relay=0;
        boolean jump=false;
        for(int i=0;i<fields.size();i++){
            Field field = fields.get(i);
            if (field.isAnnotationPresent(ExcelField.class) || field.isAnnotationPresent(ExcelProperty.class)) {
                if (field.isAnnotationPresent(ExcelFieldMy.class)) {
                    ExcelFieldMy ermy = (ExcelFieldMy) field.getAnnotation(ExcelFieldMy.class);
                    if (ermy.isMerge()) {
                        list.add(i - relay);
                    }
                }
                jump = true;
            }
            if(!jump){
                relay++;
            }
        }
        return list;
    }
}

实体类(@ExcelFieldMy 为需要合并的列):

    @ExcelProperty(value = "店铺名称", index = 3)@ExcelFieldMy(isMerge = true)
    private String storeName;
    @ExcelProperty(value = "店铺属性", index = 4)@ExcelFieldMy(isMerge = true)
    private String isEshop;
    @ExcelProperty(value = "业务类型", index = 5)@ExcelFieldMy(isMerge = true)
    private String businessType;
    @ExcelProperty(value = "订单来源", index = 6)@ExcelFieldMy(isMerge = true)
    private String orderSource;
    @ExcelProperty(value = "订单属性", index = 7)@ExcelFieldMy(isMerge = true)
    private String goodsTypeNum;
    @ExcelProperty(value = "订单类型", index = 8)@ExcelFieldMy(isMerge = true)
    private String orderType;
    @ExcelProperty(value = "订单编号", index = 9)@ExcelFieldMy(isMerge = true)
    private String orderNo;
    @ExcelProperty(value = "订单状态", index = 10)@ExcelFieldMy(isMerge = true)
    private String serviceStatus;
    @ExcelProperty(value = "物流方式", index = 11)@ExcelFieldMy(isMerge = true)
    private String deliveryType;
    @ExcelProperty(value = "配送方式", index = 12)@ExcelFieldMy(isMerge = true)
    private String distributionType;
    @ExcelProperty(value = "商品名称", index = 13)
    private String productName;
    @ExcelProperty(value = "商品SPU编码",index = 14)
    private String productCode;
    @ExcelProperty(value = "商品编码(sku)", index = 15)
    private String skuCode;
    @ExcelProperty(value = "SPU编码备注",index = 16)
    private String realProductCode;
    @ExcelProperty(value = "SKU编码备注",index = 17)
    private String realSkuCode;
    @ExcelProperty(value = "规格", index = 18)
    private String skuInfo;
    @NumberFormat("0.00")
    @ExcelProperty(value = "单类商品原金额", index = 19)
    private Double detOriPrice;

2.注册合并策略
合并策略写完了,后面就简单了,我们只需要将合并策略注册上次就好了
核心代码:

	/**
	 * @Title: 合并的写
	 * @Author wuxiangen
	 * @param fileName
	 * @param sheetName
	 * @param excludeColumnFiledNames
	 * @param dataList
	 * @param clazz
	 */
	public static <T extends EasyBaseModel> void mergeWrite(String fileName, String sheetName, Set<String> excludeColumnFiledNames,
															List<T> dataList, Class<T> clazz, AbstractMergeStrategy abstractMergeStrategy) {
		checkSheetName(sheetName);
		checkFileName(fileName);
		checkDataList(dataList);
		checkClazz(clazz);
		if (CollectionUtils.isEmpty(excludeColumnFiledNames)) {
			EasyExcel.write(fileName, clazz).sheet(sheetName).registerWriteHandler(abstractMergeStrategy).doWrite(dataList);
		} else {
			EasyExcel.write(fileName, clazz).excludeColumnFiledNames(excludeColumnFiledNames).sheet(sheetName).registerWriteHandler(abstractMergeStrategy).doWrite(dataList);
		}
	}

具体的工具类上传到csdn上,请自取。没有分的,可以评论留取邮箱地址,我看到后会第一时间发送给您。
https://download.csdn.net/download/a81579261/12037078

你可能感兴趣的:(Java)