目录

一、需求描述

二、简单实现

1、引入pom依赖

2、创建监听器

3、创建实体类

4、具体使用

三、加入校验功能

1、自定义注解

2、自定义校验

3、调整后的实体类

4、监听器

5、最终监听器:

四、总结


一、需求描述

公司业务中需要用户导入Excel数据,并且每次几万条,要求速度快,用户操作方便灵活,有些非必填字段的数据可以为空。

当然目前有很多成熟的框架都可以实现该功能,在这里就不详细的描述我们是如何选择这些框架的过程,我们选择的是阿里的easyExcel框架,号称操作简单、省内存、性能稳定等。导入功能本身并没什么复杂的,这里主要想分享下我在导入Excel数据的过程中的校验操作(列头字段的校验、数据类型的校验),因为很多时候用户在导入的时候不依照模板进行,列头字段不正确、乱序,数据不该为空时候为空等等...

二、简单实现

1、引入pom依赖

<!--easyexcel-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>

2、创建监听器

@Slf4j
public class AtbDataListener extends AnalysisEventListener<AtbExcelVo> {

    /**
     * 批处理阈值
     */
    private static final int BATCH_COUNT = 20;
    @Getter
    List<AtbExcelVo> list = new ArrayList<>(BATCH_COUNT);

    @Override
    public void invoke(AtbExcelVo atbExcelVo, AnalysisContext analysisContext) {
        list.add(atbExcelVo);
        if (list.size() >= BATCH_COUNT) {
            list.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

3、创建实体类

@Data
@Setter
@Getter
@EqualsAndHashCode
public class AtbExcelVo implements Serializable {
   private static final long serialVersionUID = 1L;
   @ExcelProperty(value ="序号")
   private String id;
   @ExcelProperty(value = "订单号")
   private String orderId;
   @ExcelProperty(value = "约定交期")
   private String deliveryTime;
   @ExcelProperty(value = "客户编号")
   private String cusCode;
   @ExcelProperty(value = "客户简称")
   private String shortName;
   @ExcelProperty(value = "规格")
   private String seriNum;
   @ExcelProperty(value = "客户规格")
   private String cusSeriNum;
   @ExcelProperty(value = "产品编号")
   private String prodCode;
   @ExcelProperty(value = "产品名称")
   private String ProdName;
   @ExcelProperty(value = "数量")
   private String prodCount;
   @ExcelProperty("单位")
   private String unit;
   @ExcelProperty("单价")
   private String unitPrice;
   @ExcelProperty("订单日期")
   private String orderTime;
   @ExcelProperty("待完成工序")
   private String unCompleteProcess;
   @ExcelProperty("订单备注")
   private String orderMark;

}

4、具体使用

public boolean importOrder(MultipartFile importFile) {

    List<AtbExcelVo> list = EasyExcel.read(importFile.getInputStream(), AtbExcelVo.class, new AtbDataListener()).doReadAllSync();
}

小结:其实到目前为止导入功能基本可以实现了,只要将用户Excel的数据能够转化成特定list,后面基本都好操作了。但是这里仅仅是对用户的无误数据能够导入了,但是如果用户的数据存在很多问题,那么我们将如何来进行校验并让用户调整后继续导入?我能够想到的就是从实体上添加注解实现字段的校验。

三、加入校验功能

1、自定义注解

/**
 * 导入Excel数据进行校验的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelValid {
    String message() default "导入有未填入的字段";
}

2、自定义校验

public class ExcelImportValid {
    /**
     * Excel导入字段校验
     * @param object 校验的JavaBean 其属性须有自定义注解
     */
    public static void valid(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            //设置可访问
            field.setAccessible(true);
            //属性的值
            Object fieldValue = null;
            try {
                fieldValue = field.get(object);
            } catch (IllegalAccessException e) {
                throw new ContractException(ResponseStatusEnum.FAILED.getCode(),
                        field.getAnnotation(ExcelValid.class).message());
            }
            //是否包含必填校验注解
            boolean isExcelValid = field.isAnnotationPresent(ExcelValid.class);
            if (isExcelValid && Objects.isNull(fieldValue)) {
                throw new ContractException(ResponseStatusEnum.FAILED.getCode(),field.getAnnotation(ExcelValid.class).message());
            }

        }
    }
}

3、调整后的实体类

@Data
@Setter
@Getter
@EqualsAndHashCode
public class AtbExcelVo implements Serializable {
   private static final long serialVersionUID = 1L;
   @ExcelProperty(value ="序号")
   @ExcelValid(message = "【序号】列不能有空数据!")
   private String id;
   @ExcelProperty(value = "订单号")
   private String orderId;
   @ExcelProperty(value = "约定交期")
   @ExcelValid(message = "【约定交期】列不能有空数据!")
   private String deliveryTime;
   @ExcelProperty(value = "客户编号")
   private String cusCode;
   @ExcelProperty(value = "客户简称")
   private String shortName;
   @ExcelProperty(value = "规格")
   @ExcelValid(message = "【规格】列不能有空数据!")
   private String seriNum;
   @ExcelProperty(value = "客户规格")
   private String cusSeriNum;
   @ExcelProperty(value = "产品编号")
   @ExcelValid(message = "【产品编号】列不能有空数据!")
   private String prodCode;
   @ExcelProperty(value = "产品名称")
   private String ProdName;
   @ExcelProperty(value = "数量")
   @ExcelValid(message = "【数量】列不能有空数据!")
   private String prodCount;
   @ExcelProperty("单位")
   private String unit;
   @ExcelProperty("单价")
   private String unitPrice;
   @ExcelProperty("订单日期")
   @ExcelValid(message = "【订单日期】列不能有空数据!")
   private String orderTime;
   @ExcelProperty("待完成工序")
   private String unCompleteProcess;
   @ExcelProperty("订单备注")
   private String orderMark;

}

4、监听器

@Slf4j
public class AtbListener extends AnalysisEventListener<AtbExcelVo> {

    /**
     * 批处理阈值
     */
    private static final int BATCH_COUNT = 20;
    @Getter
    List<AtbExcelVo> list = new ArrayList<>(BATCH_COUNT);

    @Override
    public void invoke(AtbExcelVo atbExcelVo, AnalysisContext analysisContext) {
        log.info("数据校验:"+atbExcelVo);
        ExcelImportValid.valid(atbExcelVo);
        list.add(atbExcelVo);
        if (list.size() >= BATCH_COUNT) {
            log.info("已经解析"+list.size()+"条数据");
            list.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
  
}

小结:到这里为止目前能对字段下的数据进行校验了,但是还是不能实现对列头字段的校验,这里需要在监听器上进行调整,具体的调整如下:

5、最终监听器:

@Slf4j
public class AtbListener extends AnalysisEventListener<AtbExcelVo> {

    /**
     * 批处理阈值
     */
    private static final int BATCH_COUNT = 20;
    @Getter
    List<AtbExcelVo> list = new ArrayList<>(BATCH_COUNT);

    @Override
    public void invoke(AtbExcelVo atbExcelVo, AnalysisContext analysisContext) {
        log.info("数据校验:"+atbExcelVo);
        ExcelImportValid.valid(atbExcelVo);
        list.add(atbExcelVo);
        if (list.size() >= BATCH_COUNT) {
            log.info("已经解析"+list.size()+"条数据");
            list.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("解析到一条表头数据:{}", JSON.toJSONString(headMap));
        List<String> voHead = new ArrayList<>();
        Field[] fields = AtbExcelVo.class.getDeclaredFields();
        for (int i = 1; i < fields.length; i++) {
            ExcelProperty myField2 = fields[i].getAnnotation(ExcelProperty.class);
            voHead.add(myField2.value()[0]);
        }
        int i=0;
        for(Integer key : headMap.keySet()){
            log.info("表头数据校验:"+headMap.get(key));
            if(headMap.get(key) == null ){
                throw new ContractException("1","第"+(i+1)+"列表头格式不正确!");
            }
            if(i == voHead.size()){
                throw new ContractException("1","上传文件表头超过模板列数,正确表头为:"+voHead);
            }
            if(!headMap.get(key).equals(voHead.get(i))){
                throw new ContractException("1", "第"+(i+1)+"列【"+headMap.get(key)+"】附近表头模板异常!\n正确表头顺序为:\n"+voHead+ "!");
            }
            i++;
        }
    }
}

四、总结

到此导入功能就实现了,本文中用到的自定义异常ContractException没有分享,这里如果实现没有自定义异常的直接抛出Exception也行。导入的关键点还是在于监听器的使用,不管是列头的校验还是数据的校验都离不开监听器。

官方文档:读Excel | Easy Excel

GitHub 加速计划 / ea / easyexcel
31.64 K
7.47 K
下载
快速、简洁、解决大文件内存溢出的java处理Excel工具
最近提交(Master分支:3 个月前 )
c42183df Bugfix 3 个月前
efa7dff6 * 重新加回 `commons-io` 3 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐