EasyExcel导入

更多使用参考:easyExcel官网


业务需求:1、导入用户账号(手机号)进行修改用户名称,2、用户如果存在,判断用户姓名是否与库中一致,不一致修改库中用户姓名,3、记录导入失败数据(前端显示),以备修改后再次导入。


代码实现:


一、controller层:

/**
 * 修改名称导入
 * @return
 */
@ApiOperation(value = "修改名称导入", notes = "修改名称导入")
@PostMapping("/updateName")
public R importCustomerExcel(@RequestParam("file") MultipartFile file) {
   String fileName = file.getOriginalFilename();
   String suffixName = "";
   if (StrUtil.isNotBlank(fileName)) {
      suffixName = fileName.substring(fileName.lastIndexOf("."));
   }
   if (!".xlsx".equals(suffixName) && !".xls".equals(suffixName)){
      return R.failed("文件格式不对,请导入excel文件!");
   }
   cpCustomerService.importCustomerExcel(file);
   // 导入失败的数据
   List<ImportCustomerExcelModel> failData = ImportCustomerExcelListener.failList();
   return R.ok(failData);
}


二、service:

/**
 * 用户名称修改导入
 * @param file
 * @return
 */
void importCustomerExcel(MultipartFile file);
@Override
public void importCustomerExcel(MultipartFile file) {
   try (InputStream inputStream = file.getInputStream()){
      EasyExcel.read(inputStream, CpCustomer.class, new ImportCustomerExcelListener(cpCustomerService)).doReadAll();
   } catch (IOException e) {
      LOGGER.error("读取文件出现错误,请重新导入!{}", file);
   }
}


EasyExcel.read()解释:


EasyExcel.read(inputStream, CpCustomer.class,new ImportCustomerExcelListener(cpCustomerService)).doReadAll();


1、inputStream:需要读取的文件流。


2、CpCustomer.class:excel导入的字段对应的实体。


3、ImportCustomerExcelListener(cpCustomerService):数据处理监听器,主要逻辑处理都在这里面;由于我需要service去查库判断,所以我这里传了一个cpCustomerService。


4、doReadAll():读取全部sheet。


三、ImportCustomerExcelListener监听器:

/**
 * 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
 */
public class ImportCustomerExcelListener extends AnalysisEventListener<CpCustomer> {
   private static final Logger LOGGER = LoggerFactory.getLogger(ImportCustomerExcelListener.class);
   /**
    * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
    */
   private static final int BATCH_COUNT = 5;
   List<CpCustomer> list = new ArrayList<>();
   /**
    * 错误数据
    */
   private static List<ImportCustomerExcelModel> errList = new ArrayList<>();
   /**
    *添加失败集合数据
    */
   public static List<ImportCustomerExcelModel> failList(){
      List<ImportCustomerExcelModel> listerror = new ArrayList<>();
      listerror.addAll(errList);
      errList.clear();
      return listerror;
   }
   /**
    * 头信息
    */
   Map<Integer, String> headMap = new HashMap<>();
   /**
    * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
    */
   private CpCustomerService cpCustomerService;
   public ImportCustomerExcelListener() {
      // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
   }
   /**
    * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
    *
    * @param cpCustomerService
    */
   public ImportCustomerExcelListener(CpCustomerService cpCustomerService) {
      this.cpCustomerService = cpCustomerService;
   }
   /**
    * 这个每一条数据解析都会来调用
    *
    * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
    * @param context
    */
   @Override
   public void invoke(CpCustomer data, AnalysisContext context) {
      updateCustomerName(data);
   }
   /**
    * 修改用户名称
    * @param data
    */
   public void updateCustomerName(CpCustomer data) {
      if (!Validator.isMobile(data.getPhoneNum())){
         failDataOperation(data, "手机号格式错误");
         throw new ExcelAnalysisStopException(data.getCustomerName()+"的手机号格式错误,错误手机号:"+data.getPhoneNum());
      }
//    if (!LegalUtils.isNameLegal(data.getCustomerName())) {
//       failDataOperation(data, "姓名格式错误(中文2-4位)");
//       throw new ExcelAnalysisStopException(data.getCustomerName()+":姓名应该是中文2-4位,请重新输入!");
//    }
      CpCustomer cpCustomer = cpCustomerService.getOne(Wrappers.<CpCustomer>query().eq("phone_num", data.getPhoneNum()));
      Optional.ofNullable(cpCustomer).ifPresent(customer -> {
         LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
         // 避免名字中间存在空格
         String newName = "";
         String oldName = "";
         if (StrUtil.isNotBlank(data.getCustomerName())) {
            newName = data.getCustomerName().replace(" ", "");
         }
         if (StrUtil.isNotBlank(cpCustomer.getCustomerName())) {
            oldName = cpCustomer.getCustomerName().replace(" ", "");
         }
         if (!newName.equals(oldName)) {
            data.setId(cpCustomer.getId());
            list.add(data);
         }
         LOGGER.info("需要修改的数据:{}", list);
      });
      // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
      if (list.size() >= BATCH_COUNT) {
         // 添加到数据库
         //saveData();
         // 修改数据库数据
         updateData();
         // 存储完成清理 list
         list.clear();
      }
   }
   /**
    * 所有数据解析完成了 都会来调用
    *
    * @param context
    */
   @Override
   public void doAfterAllAnalysed(AnalysisContext context) {
      // 这里也要保存/修改数据,确保最后遗留的数据也存储到数据库
      //saveData();
      if (CollUtil.isNotEmpty(list)) {
         // 修改数据
         updateData();
         LOGGER.info("所有数据解析完成!");
      }else {
         LOGGER.info("没有需要修改的数据");
      }
   }
   /**
    * 加上存储数据库(判断数据库中是否存在该数据)
    */
   private void saveData() {
      LOGGER.info("{}条数据,开始存储数据库!", list.size());
      cpCustomerService.saveBatch(list);
      LOGGER.info("存储数据库成功!");
   }
   @Override
   public void onException(Exception exception, AnalysisContext context) {
      LOGGER.error("解析失败:{}", exception.getMessage());
      // 以下为导入内容格式转换检测:如:时间格式“yyyy-MM-dd HH:mm:ss”
      // 如果是某一个单元格的转换异常 能获取到具体行号;如果要获取头的信息 配合invokeHeadMap使用
      int row = 0;
      int col = 0;
      String title = "";
      if (exception instanceof ExcelDataConvertException) {
         ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
         col = excelDataConvertException.getColumnIndex();
         row = excelDataConvertException.getRowIndex();
         LOGGER.error("第{}行,第{}列解析异常,数据为:{}", row, col , excelDataConvertException.getCellData());
         title = this.headMap.get(col);
         LOGGER.info("出错标题列名称:{}", title);
      }
      // 此处抛出异常则--停止导入  不抛出异常则--继续往下导入
//    throw new ExcelAnalysisStopException(exception.getMessage()+(row==0?"":", 出错行:"+row)+("".equals(title)?"": "出错列:"+title));
   }
   @Override
   public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
      this.headMap = headMap;
   }
   /**
    * 修改数据库
    */
   public void updateData() {
      LOGGER.info("{}条数据,开始修改数据库信息!", list.size());
      try {
         cpCustomerService.updateBatchById(list);
      }catch (Exception e){
         LOGGER.error("以下数据修改过程中出现错误,请重新导入:{}", list);
         list.forEach(data -> failDataOperation(data, "修改过程中出现错误,请重新导入!"));
         throw new ExcelAnalysisStopException("以下数据修改过程中出现错误,请重新导入:"+list);
      }
      LOGGER.info("修改数据库成功!");
   }
   /**
    * 失败信息操作公共方法
    * @param data
    */
   private void failDataOperation(CpCustomer data, String message) {
      ImportCustomerExcelModel model = new ImportCustomerExcelModel();
      BeanUtil.copyProperties(data, model);
      model.setMessage(message);
      errList.add(model);
   }
}


以上的监听器中有我业务处理的逻辑,官网的介绍请看这里:https://www.yuque.com/easyexcel/doc/read

上一篇:Struts2中使用Velocity的resource.loader配置问题详解


下一篇:DC学院学习笔记(十九):聚类算法(k均值、DBSCAN)