学习EasyExcel的基本处理以及AnalysisEventListener的实现

2年前 (2022) 程序员胖胖胖虎阿
465 0 0

最近使用EasyExcel,实现每一个类型在一个sheet上,合并成一个excel,写一点心得

导出

导出主要配置在entity上,主要的是ExcelProperty的value 如果相同的名称会合并,以此来实现字段多种样式组合

@Data
@EqualsAndHashCode(callSuper = false)
//一些表头基本配置
@ContentStyle(horizontalAlignment= HorizontalAlignment.CENTER)
@HeadStyle(horizontalAlignment= HorizontalAlignment.CENTER)
@ContentFontStyle(fontHeightInPoints=12)
@HeadFontStyle(fontHeightInPoints=12)
public class demo implements Serializable{
	//第一行标题 因为相同,会合并单元格 
    @ExcelProperty(index = 0, value = {"第一行标题","name1名称"})
    @ColumnWidth(16)
    private String name1;
    
    @ExcelProperty(index = 1, value = {"第一行标题","name2名称"})
    @ColumnWidth(8)
    private String name2;
    
}
    @ApiOperation(value = "导出【excel】", httpMethod = "GET")
    @GetMapping(path = "/download")
    public void download(@ApiParam("导出查询对象") Dto dto, HttpServletResponse response) throws IOException {
        try {
            String fileName = URLEncoder.encode("表名-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()), "UTF-8");
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
            ExcelWriter excelWriter = EasyExcelFactory.write(response.getOutputStream()).build();
            excelWriter.write(service1.list(dto), EasyExcelFactory.writerSheet(0, "name1").head(Entity1.class).build());
            excelWriter.write(service2.list(dto),  EasyExcelFactory.writerSheet(1, , "name2").head(Entity2.class).build());
            excelWriter.write(service3.list(dto), EasyExcelFactory.writerSheet(2, , "name3").head(Entity3.class).build());
            excelWriter.write(service4.list(dto), EasyExcelFactory.writerSheet(3,, "name4").head(Entity4.class).build());
            excelWriter.write(service5.list(dto), EasyExcelFactory.writerSheet(4, , "name5").head(Entity5.class).build());
            excelWriter.write(service6.list(dto), EasyExcelFactory.writerSheet(5, , "name6").head(Entity6.class).build());
            excelWriter.finish();
        } catch (Exception e) {
			e.printStackTrace();
        }
    }

导入

导入就比较繁琐

1、excel设计
  • 格式处理:文本就是文本,时间就是时间,数字就是数字,设定好格式
  • 设定好规则,如下图可以控制输入和进行提示,注意 下拉框 用序列,字段用’,'隔开
    学习EasyExcel的基本处理以及AnalysisEventListener的实现
2、导入Converter配置

用于解决数据输入输出时的转换,比如输入名称,转换成编码

public class AreaConverter implements Converter<String> {

    private static List<ConverterDto> areaConverterDtos = null;

    public AreaConverter() {
        ConverterService service = SpringApplicationUtils.getBean(ConverterService.class);
        if (CollectionUtil.isEmpty(areaConverterDtos)) {
            areaConverterDtos = service.areaListByLevel();
        }
    }

    @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    //导入
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        for (ConverterDto dto : areaConverterDtos) {
            if (dto.getName().contains(cellData.getStringValue()) ) {
                return dto.getCode();
            }
        }
        return "-1";
    }

    @Override
    //导出
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        for (ConverterDto dto : areaConverterDtos) {
            if (value.equals(dto.getCode())) {
                return new CellData(dto.getName());
            }
        }
        return new CellData("未知");
    }
}
@Component
//通过上下文获取到实例bean
public class SpringApplicationUtils  implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringApplicationUtils.applicationContext == null) {
            SpringApplicationUtils.applicationContext = applicationContext;
        }
    }

    /**
     * 通过name获取 Bean.
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    /**
     * 获取指定类型的所有bean实例
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
        return getApplicationContext().getBeansOfType(clazz);
    }
}
3、实现AnalysisEventListener

AnalysisEventListener功能很强大

    @Override
    public void invoke(Dto dto, AnalysisContext context) {
		//每一条数据都会循环,可以实现空数据,重复数据的检查
		//在外层增加list,就可以实现数据临时的缓存
    }
    
      @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
	//通过变量list 也可以实现检查,并且能实现批量存储
    }

个人不太这个模块的定性,一方面,它可以处理空数据的检查,重复数据的检查等一些数据筛查工作。另一方面,也可以做数据的存储,如果在此做数据存储,那么数据的转换也是可以在这里完成的。
我没更加深入的理解,就先实现了用途,如开始所说,我需要在一个excel中,导入多个sheet并且类型不一,每个类型还要存入多个表,如果不使用listener,代码会异常臃肿难看。
贴下并不完整代码:

public class ExcelListener extends AnalysisEventListener<ExcelBaseModel> {

    Service service;

    Map<Integer, String> checkNullMap;

    List<FmmpXmxx> list1 = new ArrayList<>();

    List<Object> list2 = new ArrayList<>();

    public ExcelListener() {
        service = SpringApplicationUtils.getBean(Service.class);
        checkNullMap = new HashMap<>();
    }
    
	//new的时候带进来 checkNullMap 查空字符,其实其他参数也可以带进来,所以非常灵活
    public ExcelListener(Map<Integer, String> checkNullMap) {
        service = SpringApplicationUtils.getBean(Service.class);
        this.checkNullMap = checkNullMap;
    }

    @Override
    public void invoke(ExcelBaseModel dto, AnalysisContext context) {
        this.checkNull(context);
        this.buildEntity(dto);
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (!list1.isEmpty()) {
            this.checkIdIsExist(context); //去重一方面插入数据本身重复问题,另一方面,每个sheet之间重复问题,还有excel和数据库的重复
            //很多人会先在list到达一定数量后,在invoke做保存,然后在这里做剩下的数据保存,我因为业务不同没有做此处理,并且个人更加倾向保存逻辑都在该方法下完成。可以通过list下标实现
            service.saveBatch(list1);
            service.saveBatchZb(list2);
        }
    }

    public void checkNull(AnalysisContext context) {
        Map<Integer, Cell> cellMap = context.readSheetHolder().getCellMap();
        checkNullMap.keySet().forEach(colNum -> {
            if (!cellMap.containsKey(colNum)) {
                int row = context.readRowHolder().getRowIndex() + 1;
                throw new ServiceException(400, context.readSheetHolder().getSheetName() + ":第" + row + "行 " + checkNullMap.get(colNum) + "为空,请核实");
            }
        });
    }

    private void checkIdIsExist(AnalysisContext context) {
        HashSet<String> baseIds = service.list().stream().map(FmmpXmxx::getId).collect(Collectors.toCollection(HashSet::new));
        HashSet<String> targetIds = list1.stream().map(FmmpXmxx::getId).collect(Collectors.toCollection(HashSet::new));
        if (targetIds.size() != list1.size()) {
            throw new ServiceException(400, context.readSheetHolder().getSheetName() + ": " + checkNullMap.get(0) + " 有重复");
        }
        list1.forEach(index -> {
            if (baseIds.contains(index.getId())) {
                throw new ServiceException(400, context.readSheetHolder().getSheetName() + ":第" + context.readRowHolder().getRowIndex() + 1 + "行  " + checkNullMap.get(0) + " 已经存在");
            }
        });
    }

    private void buildEntity(ExcelBaseModel dto) {
        //dto处理后插入list 这里dto是做了处理的,不展示逻辑代码了
        list1.add(dto);
        list2.add(dto);
    }
}
4、业务逻辑

这里就会显得比较简洁,毕竟真正的处理都在listener里了

	 @Override
    @Transactional //在这里配置事务,保证业务上出错全部回滚
    public boolean excel2Database(MultipartFile file) {
        Map<Integer, String> checkNullMap = new HashMap<>(5);
        checkNullMap.put(0, "字段名1-默认是主键名,判断重复");
        checkNullMap.put(1, "字段名2");
        checkNullMap.put(2, "字段名3");
        checkNullMap.put(7, "字段名4");
        checkNullMap.put(8, "字段名5");
        try {
            EasyExcelFactory.read(file.getInputStream()).registerReadListener(new ExcelListener(checkNullMap)).head(Entity1.class).sheet(0).doReadSync();
            EasyExcelFactory.read(file.getInputStream()).registerReadListener(new ExcelListener(checkNullMap)).head(Entity2.class).sheet(1).doReadSync();
            EasyExcelFactory.read(file.getInputStream()).registerReadListener(new ExcelListener(checkNullMap)).head(Entity3.class).sheet(2).doReadSync();
            EasyExcelFactory.read(file.getInputStream()).registerReadListener(new ExcelListener(checkNullMap)).head(Entity4.class).sheet(3).doReadSync();
            EasyExcelFactory.read(file.getInputStream()).registerReadListener(new ExcelListener(checkNullMap)).head(Entity5.class).sheet(4).doReadSync();
            EasyExcelFactory.read(file.getInputStream()).registerReadListener(new ExcelListener(checkNullMap)).head(Entity6.class).sheet(5).doReadSync();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

相关文章

暂无评论

暂无评论...