更多使用参考:easyExcel官网
业务需求:1、导入用户账号(手机号)进行修改用户名称,2、用户如果存在,判断用户姓名是否与库中一致,不一致修改库中用户姓名,3、记录导入失败数据(前端显示),以备修改后再次导入。
代码实现:
一、controller层:
/*** 修改名称导入* @return*/value="修改名称导入", notes="修改名称导入") ("/updateName") (publicRimportCustomerExcel( ("file") MultipartFilefile) { StringfileName=file.getOriginalFilename(); StringsuffixName=""; if (StrUtil.isNotBlank(fileName)) { suffixName=fileName.substring(fileName.lastIndexOf(".")); } if (!".xlsx".equals(suffixName) &&!".xls".equals(suffixName)){ returnR.failed("文件格式不对,请导入excel文件!"); } cpCustomerService.importCustomerExcel(file); // 导入失败的数据List<ImportCustomerExcelModel>failData=ImportCustomerExcelListener.failList(); returnR.ok(failData); }
二、service:
/*** 用户名称修改导入* @param file* @return*/voidimportCustomerExcel(MultipartFilefile); publicvoidimportCustomerExcel(MultipartFilefile) { try (InputStreaminputStream=file.getInputStream()){ EasyExcel.read(inputStream, CpCustomer.class, newImportCustomerExcelListener(cpCustomerService)).doReadAll(); } catch (IOExceptione) { 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可以构造方法传进去*/publicclassImportCustomerExcelListenerextendsAnalysisEventListener<CpCustomer> { privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(ImportCustomerExcelListener.class); /*** 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收*/privatestaticfinalintBATCH_COUNT=5; List<CpCustomer>list=newArrayList<>(); /*** 错误数据*/privatestaticList<ImportCustomerExcelModel>errList=newArrayList<>(); /***添加失败集合数据*/publicstaticList<ImportCustomerExcelModel>failList(){ List<ImportCustomerExcelModel>listerror=newArrayList<>(); listerror.addAll(errList); errList.clear(); returnlisterror; } /*** 头信息*/Map<Integer, String>headMap=newHashMap<>(); /*** 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。*/privateCpCustomerServicecpCustomerService; publicImportCustomerExcelListener() { // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 } /*** 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来** @param cpCustomerService*/publicImportCustomerExcelListener(CpCustomerServicecpCustomerService) { this.cpCustomerService=cpCustomerService; } /*** 这个每一条数据解析都会来调用** @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context*/publicvoidinvoke(CpCustomerdata, AnalysisContextcontext) { updateCustomerName(data); } /*** 修改用户名称* @param data*/publicvoidupdateCustomerName(CpCustomerdata) { if (!Validator.isMobile(data.getPhoneNum())){ failDataOperation(data, "手机号格式错误"); thrownewExcelAnalysisStopException(data.getCustomerName()+"的手机号格式错误,错误手机号:"+data.getPhoneNum()); } // if (!LegalUtils.isNameLegal(data.getCustomerName())) {// failDataOperation(data, "姓名格式错误(中文2-4位)");// throw new ExcelAnalysisStopException(data.getCustomerName()+":姓名应该是中文2-4位,请重新输入!");// }CpCustomercpCustomer=cpCustomerService.getOne(Wrappers.<CpCustomer>query().eq("phone_num", data.getPhoneNum())); Optional.ofNullable(cpCustomer).ifPresent(customer-> { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); // 避免名字中间存在空格StringnewName=""; StringoldName=""; 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了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif (list.size() >=BATCH_COUNT) { // 添加到数据库//saveData();// 修改数据库数据updateData(); // 存储完成清理 listlist.clear(); } } /*** 所有数据解析完成了 都会来调用** @param context*/publicvoiddoAfterAllAnalysed(AnalysisContextcontext) { // 这里也要保存/修改数据,确保最后遗留的数据也存储到数据库//saveData();if (CollUtil.isNotEmpty(list)) { // 修改数据updateData(); LOGGER.info("所有数据解析完成!"); }else { LOGGER.info("没有需要修改的数据"); } } /*** 加上存储数据库(判断数据库中是否存在该数据)*/privatevoidsaveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); cpCustomerService.saveBatch(list); LOGGER.info("存储数据库成功!"); } publicvoidonException(Exceptionexception, AnalysisContextcontext) { LOGGER.error("解析失败:{}", exception.getMessage()); // 以下为导入内容格式转换检测:如:时间格式“yyyy-MM-dd HH:mm:ss”// 如果是某一个单元格的转换异常 能获取到具体行号;如果要获取头的信息 配合invokeHeadMap使用introw=0; intcol=0; Stringtitle=""; if (exceptioninstanceofExcelDataConvertException) { ExcelDataConvertExceptionexcelDataConvertException= (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)); } publicvoidinvokeHeadMap(Map<Integer, String>headMap, AnalysisContextcontext) { this.headMap=headMap; } /*** 修改数据库*/publicvoidupdateData() { LOGGER.info("{}条数据,开始修改数据库信息!", list.size()); try { cpCustomerService.updateBatchById(list); }catch (Exceptione){ LOGGER.error("以下数据修改过程中出现错误,请重新导入:{}", list); list.forEach(data->failDataOperation(data, "修改过程中出现错误,请重新导入!")); thrownewExcelAnalysisStopException("以下数据修改过程中出现错误,请重新导入:"+list); } LOGGER.info("修改数据库成功!"); } /*** 失败信息操作公共方法* @param data*/privatevoidfailDataOperation(CpCustomerdata, Stringmessage) { ImportCustomerExcelModelmodel=newImportCustomerExcelModel(); BeanUtil.copyProperties(data, model); model.setMessage(message); errList.add(model); } }