更多使用参考:easyExcel官网

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

代码实现:

一、controller层:

  1. /**
  2. * 修改名称导入
  3. * @return
  4. */
  5. @ApiOperation(value = "修改名称导入", notes = "修改名称导入")
  6. @PostMapping("/updateName")
  7. public R importCustomerExcel(@RequestParam("file") MultipartFile file) {
  8. String fileName = file.getOriginalFilename();
  9. String suffixName = "";
  10. if (StrUtil.isNotBlank(fileName)) {
  11. suffixName = fileName.substring(fileName.lastIndexOf("."));
  12. }
  13. if (!".xlsx".equals(suffixName) && !".xls".equals(suffixName)){
  14. return R.failed("文件格式不对,请导入excel文件!");
  15. }
  16. cpCustomerService.importCustomerExcel(file);
  17. // 导入失败的数据
  18. List<ImportCustomerExcelModel> failData = ImportCustomerExcelListener.failList();
  19. return R.ok(failData);
  20. }

二、service:

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

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监听器:

  1. /**
  2. * 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
  3. */
  4. public class ImportCustomerExcelListener extends AnalysisEventListener<CpCustomer> {
  5. private static final Logger LOGGER = LoggerFactory.getLogger(ImportCustomerExcelListener.class);
  6. /**
  7. * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
  8. */
  9. private static final int BATCH_COUNT = 5;
  10. List<CpCustomer> list = new ArrayList<>();
  11. /**
  12. * 错误数据
  13. */
  14. private static List<ImportCustomerExcelModel> errList = new ArrayList<>();
  15. /**
  16. *添加失败集合数据
  17. */
  18. public static List<ImportCustomerExcelModel> failList(){
  19. List<ImportCustomerExcelModel> listerror = new ArrayList<>();
  20. listerror.addAll(errList);
  21. errList.clear();
  22. return listerror;
  23. }
  24. /**
  25. * 头信息
  26. */
  27. Map<Integer, String> headMap = new HashMap<>();
  28. /**
  29. * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
  30. */
  31. private CpCustomerService cpCustomerService;
  32. public ImportCustomerExcelListener() {
  33. // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
  34. }
  35. /**
  36. * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
  37. *
  38. * @param cpCustomerService
  39. */
  40. public ImportCustomerExcelListener(CpCustomerService cpCustomerService) {
  41. this.cpCustomerService = cpCustomerService;
  42. }
  43. /**
  44. * 这个每一条数据解析都会来调用
  45. *
  46. * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
  47. * @param context
  48. */
  49. @Override
  50. public void invoke(CpCustomer data, AnalysisContext context) {
  51. updateCustomerName(data);
  52. }
  53. /**
  54. * 修改用户名称
  55. * @param data
  56. */
  57. public void updateCustomerName(CpCustomer data) {
  58. if (!Validator.isMobile(data.getPhoneNum())){
  59. failDataOperation(data, "手机号格式错误");
  60. throw new ExcelAnalysisStopException(data.getCustomerName()+"的手机号格式错误,错误手机号:"+data.getPhoneNum());
  61. }
  62. // if (!LegalUtils.isNameLegal(data.getCustomerName())) {
  63. // failDataOperation(data, "姓名格式错误(中文2-4位)");
  64. // throw new ExcelAnalysisStopException(data.getCustomerName()+":姓名应该是中文2-4位,请重新输入!");
  65. // }
  66. CpCustomer cpCustomer = cpCustomerService.getOne(Wrappers.<CpCustomer>query().eq("phone_num", data.getPhoneNum()));
  67. Optional.ofNullable(cpCustomer).ifPresent(customer -> {
  68. LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
  69. // 避免名字中间存在空格
  70. String newName = "";
  71. String oldName = "";
  72. if (StrUtil.isNotBlank(data.getCustomerName())) {
  73. newName = data.getCustomerName().replace(" ", "");
  74. }
  75. if (StrUtil.isNotBlank(cpCustomer.getCustomerName())) {
  76. oldName = cpCustomer.getCustomerName().replace(" ", "");
  77. }
  78. if (!newName.equals(oldName)) {
  79. data.setId(cpCustomer.getId());
  80. list.add(data);
  81. }
  82. LOGGER.info("需要修改的数据:{}", list);
  83. });
  84. // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
  85. if (list.size() >= BATCH_COUNT) {
  86. // 添加到数据库
  87. //saveData();
  88. // 修改数据库数据
  89. updateData();
  90. // 存储完成清理 list
  91. list.clear();
  92. }
  93. }
  94. /**
  95. * 所有数据解析完成了 都会来调用
  96. *
  97. * @param context
  98. */
  99. @Override
  100. public void doAfterAllAnalysed(AnalysisContext context) {
  101. // 这里也要保存/修改数据,确保最后遗留的数据也存储到数据库
  102. //saveData();
  103. if (CollUtil.isNotEmpty(list)) {
  104. // 修改数据
  105. updateData();
  106. LOGGER.info("所有数据解析完成!");
  107. }else {
  108. LOGGER.info("没有需要修改的数据");
  109. }
  110. }
  111. /**
  112. * 加上存储数据库(判断数据库中是否存在该数据)
  113. */
  114. private void saveData() {
  115. LOGGER.info("{}条数据,开始存储数据库!", list.size());
  116. cpCustomerService.saveBatch(list);
  117. LOGGER.info("存储数据库成功!");
  118. }
  119. @Override
  120. public void onException(Exception exception, AnalysisContext context) {
  121. LOGGER.error("解析失败:{}", exception.getMessage());
  122. // 以下为导入内容格式转换检测:如:时间格式“yyyy-MM-dd HH:mm:ss”
  123. // 如果是某一个单元格的转换异常 能获取到具体行号;如果要获取头的信息 配合invokeHeadMap使用
  124. int row = 0;
  125. int col = 0;
  126. String title = "";
  127. if (exception instanceof ExcelDataConvertException) {
  128. ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
  129. col = excelDataConvertException.getColumnIndex();
  130. row = excelDataConvertException.getRowIndex();
  131. LOGGER.error("第{}行,第{}列解析异常,数据为:{}", row, col , excelDataConvertException.getCellData());
  132. title = this.headMap.get(col);
  133. LOGGER.info("出错标题列名称:{}", title);
  134. }
  135. // 此处抛出异常则--停止导入 不抛出异常则--继续往下导入
  136. // throw new ExcelAnalysisStopException(exception.getMessage()+(row==0?"":", 出错行:"+row)+("".equals(title)?"": "出错列:"+title));
  137. }
  138. @Override
  139. public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
  140. this.headMap = headMap;
  141. }
  142. /**
  143. * 修改数据库
  144. */
  145. public void updateData() {
  146. LOGGER.info("{}条数据,开始修改数据库信息!", list.size());
  147. try {
  148. cpCustomerService.updateBatchById(list);
  149. }catch (Exception e){
  150. LOGGER.error("以下数据修改过程中出现错误,请重新导入:{}", list);
  151. list.forEach(data -> failDataOperation(data, "修改过程中出现错误,请重新导入!"));
  152. throw new ExcelAnalysisStopException("以下数据修改过程中出现错误,请重新导入:"+list);
  153. }
  154. LOGGER.info("修改数据库成功!");
  155. }
  156. /**
  157. * 失败信息操作公共方法
  158. * @param data
  159. */
  160. private void failDataOperation(CpCustomer data, String message) {
  161. ImportCustomerExcelModel model = new ImportCustomerExcelModel();
  162. BeanUtil.copyProperties(data, model);
  163. model.setMessage(message);
  164. errList.add(model);
  165. }
  166. }

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