SpringBoot系列之集成EasyExcel导入合并行数据

x33g5p2x  于2022-03-18 转载在 Spring  
字(9.7k)|赞(0)|评价(0)|浏览(1240)

最近在做Excel导入功能,是一种一对多的数据,涉及到合并单元格的,考虑到使用poi去学,要自己去做处理,所以,看看有什么开源的框架,找到两个合适的框架,一个是easypoi是能支持这种的,这个框架提供了特定注解;还有一种是EasyExcel,阿里开源的,不过功能相对没easypoi齐全,比如这种合并单元格数据导入,就没有特定的注解,不过通过搜索资料,是可以实现的,不过要自己写工具类做处理,工具类整理自网上教程

不过在官网看到EasyExcel更专注于性能和易用,虽然功能支持不如easypoi,所以还是想尝试使用一下这个框架。所以,业务比较复杂,可以使用easypoi,业务简单点可以使用easyexcel,图来自EasyExcel官网:

加上easyexcel配置:

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>easyexcel</artifactId>
  4. <version>2.2.10</version>
  5. </dependency>

加上ExcelProperty注解,加上value和index,index是excel对应的列

  1. package com.example.easyexcel.model.dto;
  2. import com.alibaba.excel.annotation.ExcelProperty;
  3. import lombok.Data;
  4. import org.hibernate.validator.constraints.Length;
  5. import javax.validation.constraints.NotBlank;
  6. @Data
  7. public class UserExcelDto {
  8. @ExcelProperty(value = "序号",index = 0)
  9. @NotBlank(message = "序号必须填!")
  10. private String seq;
  11. @ExcelProperty(value = "用户名" ,index = 1)
  12. @NotBlank(message = "用户名必须填!")
  13. private String name;
  14. @ExcelProperty(value = "密码", index = 2)
  15. @Length(min = 0 , max = 8 , message = "密码最多8位!")
  16. private String password;
  17. @ExcelProperty(value = "描述",index = 3)
  18. private String addressName;
  19. @ExcelProperty(value = "邮政编码",index = 4)
  20. private String code;
  21. }
  1. package com.common.excel;
  2. import cn.hutool.core.util.ReflectUtil;
  3. import cn.hutool.core.util.StrUtil;
  4. import cn.hutool.json.JSONUtil;
  5. import com.alibaba.excel.context.AnalysisContext;
  6. import com.alibaba.excel.event.AnalysisEventListener;
  7. import com.alibaba.excel.metadata.CellExtra;
  8. import lombok.extern.slf4j.Slf4j;
  9. import java.util.ArrayList;
  10. import java.util.List;
  11. @Slf4j
  12. public class EasyExcelListener<T> extends AnalysisEventListener<T> {
  13. private List<T> datas;
  14. private Integer rowIndex;
  15. private List<CellExtra> extraMergeInfoList;
  16. public EasyExcelListener(Integer rowIndex) {
  17. this.rowIndex = rowIndex;
  18. datas = new ArrayList<>();
  19. extraMergeInfoList = new ArrayList<>();
  20. }
  21. @Override
  22. public void invoke(T data, AnalysisContext context) {
  23. // 是否忽略空行数据,因为自己要做数据校验,所以还是加上,可以根据业务情况使用
  24. context.readWorkbookHolder().setIgnoreEmptyRow(false);
  25. ReflectUtil.invoke(data, "setIndex", StrUtil.toString(context.readRowHolder().getRowIndex()));
  26. datas.add(data);
  27. }
  28. @Override
  29. public void doAfterAllAnalysed(AnalysisContext context) {
  30. log.info("所有数据解析完成!");
  31. }
  32. @Override
  33. public void extra(CellExtra extra, AnalysisContext context) {
  34. switch (extra.getType()) {
  35. case MERGE:
  36. if (extra.getRowIndex() >= rowIndex) {
  37. extraMergeInfoList.add(extra);
  38. }
  39. break;
  40. default:
  41. }
  42. }
  43. public List<T> getData() {
  44. return datas;
  45. }
  46. public List<CellExtra> getExtraMergeInfoList() {
  47. return extraMergeInfoList;
  48. }
  49. }
  1. EasyExcel.read(file.getInputStream(), UserExcelDto.class, easyExcelListener)
  2. .extraRead(CellExtraTypeEnum.MERGE)
  3. .sheet(sheetNo)
  4. .headRowNumber(headRowNumber)
  5. .doRead();
  6. System.out.println(JSONUtil.toJsonPrettyStr(easyExcelListener.getData()));
  7. List<CellExtra> extraMergeInfoList = easyExcelListener.getExtraMergeInfoList();

EasyExcel Merge默认数据读取,这种情况,那些非合并的数据,比如姓名,密码这些数据,只会在第一个附上,框架之后的读取,不能读取到

  1. [
  2. {
  3. "code": "510000",
  4. "password": "********",
  5. "name": "user1",
  6. "addressName": "广州天河区",
  7. "seq": "1"
  8. },
  9. {
  10. "code": "510001",
  11. "addressName": "广州天河区"
  12. },
  13. {
  14. "code": "510002",
  15. "password": "***",
  16. "name": "user2",
  17. "addressName": "广州天河区",
  18. "seq": "2"
  19. }
  20. ]

所以借助网上一个工具类实现,具体参考ExcelMergeHelper

工具类,思路也是读取excel数据,然后读取ExcelProperty注解,注意必须加上index,通过反射机制读取数据

  1. package com.example.easyexcel.core.excel;
  2. import com.alibaba.excel.EasyExcel;
  3. import com.alibaba.excel.annotation.ExcelProperty;
  4. import com.alibaba.excel.enums.CellExtraTypeEnum;
  5. import com.alibaba.excel.metadata.CellExtra;
  6. import com.alibaba.excel.util.CollectionUtils;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import java.lang.reflect.Field;
  10. import java.util.List;
  11. public class ExcelMergeHelper<T> {
  12. private static final Logger LOGGER = LoggerFactory.getLogger(ExcelMergeHelper.class);
  13. /**
  14. * 返回解析后的List
  15. *
  16. * @param: fileName 文件名
  17. * @param: clazz Excel对应属性名
  18. * @param: sheetNo 要解析的sheet
  19. * @param: headRowNumber 正文起始行
  20. * @return java.util.List<T> 解析后的List
  21. */
  22. public List<T> getList(String fileName, Class<T> clazz, Integer sheetNo, Integer headRowNumber) {
  23. UserEasyExcelListener<T> listener = new UserEasyExcelListener<>(headRowNumber);
  24. try {
  25. EasyExcel.read(fileName, clazz, listener).extraRead(CellExtraTypeEnum.MERGE).sheet(sheetNo).headRowNumber(headRowNumber).doRead();
  26. } catch (Exception e) {
  27. LOGGER.error(e.getMessage());
  28. }
  29. List<CellExtra> extraMergeInfoList = listener.getExtraMergeInfoList();
  30. if (CollectionUtils.isEmpty(extraMergeInfoList)) {
  31. return listener.getData();
  32. }
  33. List<T> data = explainMergeData(listener.getData(), extraMergeInfoList, headRowNumber);
  34. return data;
  35. }
  36. /**
  37. * 处理合并单元格
  38. *
  39. * @param data 解析数据
  40. * @param extraMergeInfoList 合并单元格信息
  41. * @param headRowNumber 起始行
  42. * @return 填充好的解析数据
  43. */
  44. public List<T> explainMergeData(List<T> data, List<CellExtra> extraMergeInfoList, Integer headRowNumber) {
  45. //循环所有合并单元格信息
  46. extraMergeInfoList.forEach(cellExtra -> {
  47. int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNumber;
  48. int lastRowIndex = cellExtra.getLastRowIndex() - headRowNumber;
  49. int firstColumnIndex = cellExtra.getFirstColumnIndex();
  50. int lastColumnIndex = cellExtra.getLastColumnIndex();
  51. //获取初始值
  52. Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, data);
  53. //设置值
  54. for (int i = firstRowIndex; i <= lastRowIndex; i++) {
  55. for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
  56. setInitValueToList(initValue, i, j, data);
  57. }
  58. }
  59. });
  60. return data;
  61. }
  62. /**
  63. * 设置合并单元格的值
  64. *
  65. * @param filedValue 值
  66. * @param rowIndex 行
  67. * @param columnIndex 列
  68. * @param data 解析数据
  69. */
  70. public void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
  71. T object = data.get(rowIndex);
  72. for (Field field : object.getClass().getDeclaredFields()) {
  73. //提升反射性能,关闭安全检查
  74. field.setAccessible(true);
  75. ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
  76. if (annotation != null) {
  77. if (annotation.index() == columnIndex) {
  78. try {
  79. field.set(object, filedValue);
  80. break;
  81. } catch (IllegalAccessException e) {
  82. LOGGER.error("设置合并单元格的值异常:"+e.getMessage());
  83. }
  84. }
  85. }
  86. }
  87. }
  88. /**
  89. * 获取合并单元格的初始值
  90. * rowIndex对应list的索引
  91. * columnIndex对应实体内的字段
  92. *
  93. * @param firstRowIndex 起始行
  94. * @param firstColumnIndex 起始列
  95. * @param data 列数据
  96. * @return 初始值
  97. */
  98. private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
  99. Object filedValue = null;
  100. T object = data.get(firstRowIndex);
  101. for (Field field : object.getClass().getDeclaredFields()) {
  102. //提升反射性能,关闭安全检查
  103. field.setAccessible(true);
  104. ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
  105. if (annotation != null) {
  106. if (annotation.index() == firstColumnIndex) {
  107. try {
  108. filedValue = field.get(object);
  109. break;
  110. } catch (IllegalAccessException e) {
  111. LOGGER.error("设置合并单元格的初始值异常:"+e.getMessage());
  112. }
  113. }
  114. }
  115. }
  116. return filedValue;
  117. }
  118. }

对数据进行处理:

  1. List<UserExcelDto> data = new ExcelMergeHelper().explainMergeData(easyExcelListener.getData(), extraMergeInfoList, headRowNumber);

封装后的数据,这种数据,我们就可以进行业务处理,过程相对比较麻烦

  1. [
  2. {
  3. "code": "510000",
  4. "password": "********",
  5. "name": "user1",
  6. "addressName": "广州天河区",
  7. "seq": "1"
  8. },
  9. {
  10. "code": "510001",
  11. "password": "********",
  12. "name": "user1",
  13. "addressName": "广州天河区",
  14. "seq": "1"
  15. },
  16. {
  17. "code": "510002",
  18. "password": "***",
  19. "name": "user2",
  20. "addressName": "广州天河区",
  21. "seq": "2"
  22. }
  23. ]
  1. package com.common.excel;
  2. import com.alibaba.excel.annotation.ExcelProperty;
  3. import com.alibaba.excel.metadata.CellExtra;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import java.lang.reflect.Field;
  7. import java.util.List;
  8. public class ExcelMergeHelper<T> {
  9. private static final Logger LOGGER = LoggerFactory.getLogger(ExcelMergeHelper.class);
  10. /**
  11. * 处理合并单元格
  12. *
  13. * @param data 解析数据
  14. * @param extraMergeInfoList 合并单元格信息
  15. * @param headRowNumber 起始行
  16. * @return 填充好的解析数据
  17. */
  18. public List<T> explainMergeData(List<T> data, List<CellExtra> extraMergeInfoList, Integer headRowNumber) {
  19. //循环所有合并单元格信息
  20. extraMergeInfoList.forEach(cellExtra -> {
  21. int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNumber;
  22. int lastRowIndex = cellExtra.getLastRowIndex() - headRowNumber;
  23. int firstColumnIndex = cellExtra.getFirstColumnIndex();
  24. int lastColumnIndex = cellExtra.getLastColumnIndex();
  25. //获取初始值
  26. Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, data);
  27. //设置值
  28. for (int i = firstRowIndex; i <= lastRowIndex; i++) {
  29. for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
  30. setInitValueToList(initValue, i, j, data);
  31. }
  32. }
  33. });
  34. return data;
  35. }
  36. /**
  37. * 设置合并单元格的值
  38. *
  39. * @param filedValue 值
  40. * @param rowIndex 行
  41. * @param columnIndex 列
  42. * @param data 解析数据
  43. */
  44. public void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
  45. if (!(data.size()>rowIndex))
  46. return;
  47. T object = data.get(rowIndex);
  48. for (Field field : object.getClass().getDeclaredFields()) {
  49. //提升反射性能,关闭安全检查
  50. field.setAccessible(true);
  51. ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
  52. if (annotation != null) {
  53. if (annotation.index() == columnIndex) {
  54. try {
  55. field.set(object, filedValue);
  56. break;
  57. } catch (IllegalAccessException e) {
  58. LOGGER.error("设置合并单元格的值异常:"+e.getMessage());
  59. }
  60. }
  61. }
  62. }
  63. }
  64. /**
  65. * 获取合并单元格的初始值
  66. * rowIndex对应list的索引
  67. * columnIndex对应实体内的字段
  68. *
  69. * @param firstRowIndex 起始行
  70. * @param firstColumnIndex 起始列
  71. * @param data 列数据
  72. * @return 初始值
  73. */
  74. private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
  75. Object filedValue = null;
  76. T object = data.get(firstRowIndex);
  77. for (Field field : object.getClass().getDeclaredFields()) {
  78. //提升反射性能,关闭安全检查
  79. field.setAccessible(true);
  80. ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
  81. if (annotation != null) {
  82. if (annotation.index() == firstColumnIndex) {
  83. try {
  84. filedValue = field.get(object);
  85. break;
  86. } catch (IllegalAccessException e) {
  87. LOGGER.error("设置合并单元格的初始值异常:"+e.getMessage());
  88. }
  89. }
  90. }
  91. }
  92. return filedValue;
  93. }
  94. }

本博客代码例子可以在GitHub找到下载链接

相关文章