SpringBoot集成阿里easyexcel(二)Excel监听以及常用工具类

embedded/2024/10/19 7:30:17/

EasyExcel中非常重要的AnalysisEventListener类使用,继承该类并重写invoke、doAfterAllAnalysed,必要时重写onException方法。

Listener 中方法的执行顺序


首先先执行 invokeHeadMap() 读取表头,每一行都读完后,执行 invoke()方法 读取数据,最后是doAfterAllAnalysed() 方法。
invoke通过 AnalysisContext 对象还可以获取当前 sheet,当前行等数据。对当前获取的行的数据配合实体类字段的校验注解来实现数据校验,并返回错误提示,也可校验表头(相应index下表对应excel中的中文名称)是否正确。
doAfterAllAnalysed方法可定义在资源解析完成之后的操作 onException方法定义在数据解析出错失败时的操作,一般为当前数据行的数据无法与ExcelProperty所注解的字段类型想符合,从而导致无法继续解析流沉的错误。


一、ExcelListener监听器

java">public class ExcelListener extends AnalysisEventListener {/*** 自定义用于暂时存储data。* 可以通过实例获取该值*/private List<Object> datas = new ArrayList<>();private List<ImportErrVo> errDatas = new ArrayList<>();private Map<Integer, String> headMap = new HashMap<>();private Boolean head = false;/*** 通过 AnalysisContext 对象还可以获取当前 sheet,当前行等数据*/@Overridepublic void invoke(Object object, AnalysisContext context) {checkHead(object);//属性验证int rowNum = context.readRowHolder().getRowIndex();String errMsg;try {errMsg = EasyExcelValiHelper.validateEntity(object);} catch (Exception e) {errMsg = "解析数据出错";e.printStackTrace();}if (!StringUtils.isEmpty(errMsg)) {ImportErrVo errVo = new ImportErrVo();errVo.setLine(rowNum + 1 + "");errVo.setErrMsg(errMsg);errDatas.add(errVo);} else {//数据存储到list,供批量处理,或后续自己业务逻辑处理。datas.add(object);}//根据业务自行 dosomethingdoSomething();}/*** 根据业务自行实现该方法*/private void doSomething() {}/*** 校验表头判断是否传错** @param object*/public void checkHead(Object object) {Field[] fields = object.getClass().getDeclaredFields();if (head) {return;}for (Field f : fields) {if (f.getName().equals("serialVersionUID")) {continue;}ExcelProperty excelProperty = f.getAnnotation(ExcelProperty.class);int index = 0;if (excelProperty!=null){index= excelProperty.index();}if ((excelProperty != null && headMap.get(index)==null) || (excelProperty != null && !headMap.get(index).equals(excelProperty.value()[excelProperty.value().length-1]))) {ImportErrVo errVo = new ImportErrVo();errVo.setLine("1");errVo.setErrMsg("表头数据不对应,导入数据失败");errDatas.add(errVo);this.head = true;break;}}}/*** 解析结束的操作** @param object*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {if (head) {errDatas.removeIf(a -> !a.getLine().equals("1"));datas.clear();}/*datas.clear();解析结束销毁不用的资源*/}/*** 解析出错时的操作** @param object*/@Overridepublic void onException(Exception exception, AnalysisContext context) throws Exception {int rowIndex = context.readRowHolder().getRowIndex();int headLineNumber = context.readSheetHolder().getHeadRowNumber();if (rowIndex == headLineNumber) {Object object = null;try {object = context.currentReadHolder().excelReadHeadProperty().getHeadClazz().newInstance();} catch (Exception e) {ImportErrVo errVo = new ImportErrVo();errVo.setLine("1");errVo.setErrMsg("表头数据不对应,导入数据失败");errDatas.add(errVo);this.head = true;}if (object != null) {checkHead(object);}}if (head) {return;}String error;if (exception instanceof ExcelDataConvertException) {ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;CellData cellData = excelDataConvertException.getCellData();error = cellData.toString();} else {String eMassage = exception.getCause().toString();if (eMassage.contains("\"")) {error = eMassage.substring(eMassage.lastIndexOf(':') + 2);} else {error = eMassage.substring(eMassage.lastIndexOf(':') + 1);}}LinkedHashMap<Integer, CellData> content = (LinkedHashMap<Integer, CellData>) context.readRowHolder().getCurrentRowAnalysisResult();error = error.replaceAll("\"", "");ImportErrVo errVo = new ImportErrVo();for (Map.Entry<Integer, CellData> map : content.entrySet()) {CellDataTypeEnum type = map.getValue().getType();String c = null;if (type.equals(CellDataTypeEnum.NUMBER)) {c = map.getValue().getNumberValue().toString();} else if (type.equals(CellDataTypeEnum.STRING)) {c = map.getValue().getStringValue();} else if (type.equals(CellDataTypeEnum.BOOLEAN)) {c = map.getValue().getBooleanValue().toString();}if (error.equals(c)) {errVo.setLine(rowIndex + 1 + "");errVo.setErrMsg(headMap.get(map.getKey()) + "数据格式错误");break;}}boolean flag = true;for (ImportErrVo importErrVo : errDatas) {if (importErrVo.getLine().equals(errVo.getLine())) {importErrVo.setErrMsg(importErrVo.getErrMsg() + errVo.getErrMsg() + ";");flag = false;break;}}if (flag) {errDatas.add(errVo);}}@Overridepublic void invokeHeadMap(Map headMap, AnalysisContext context) {this.headMap = headMap;}public List<Object> getDatas() {return datas;}public void setDatas(List<Object> datas) {this.datas = datas;}public List<ImportErrVo> getErrDatas() {return errDatas;}public void setErrDatas(List<ImportErrVo> errDatas) {this.errDatas = errDatas;}}

二、初步检验导入参数

在实体类中可用: javax.validation.constraints下的注解对字段进行校验
EasyExcelValiHelper类(初步校验字段)

java">public class EasyExcelValiHelper {private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();public static <T> String validateEntity(T obj) throws NoSuchFieldException, SecurityException {StringBuilder result = new StringBuilder();Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);if (set != null && set.size() != 0) {for (ConstraintViolation<T> cv : set) {Field declaredField = obj.getClass().getDeclaredField(cv.getPropertyPath().toString());ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);result.append(annotation.value()[0]+cv.getMessage()).append(";");}}return result.toString();}
}

三、ExcelUti类

java">public class ExcelUtil {/*** 读取某个 sheet 的 Excel** @param excel 文件* @param head* @param sheetNo sheet 的序号 从1开始* @return Excel 数据 list*/public static List<Object> readExcel(MultipartFile excel, Class head, int sheetNo) {return readExcel(excel, head, sheetNo, 1);}/*** 读取某个 sheet 的 Excel** @param excel 文件* @param head* @param sheetNo sheet 的序号 从1开始* @return Excel 数据 list*/public static List<Object> readExcel(MultipartFile excel, Class head, int sheetNo, ExcelListener excelListener) {ExcelReader reader = getReader(excel, head,excelListener);if (reader == null) {return null;}ReadSheet readSheet = EasyExcel.readSheet(sheetNo).build();reader.read(readSheet);// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的reader.finish();return excelListener.getDatas();}/*** 读取某个 sheet 的 Excel** @param excel 文件* @param head* @param sheetNo sheet 的序号 从1开始* @param headLineNum 表头行数,默认为1* @return Excel 数据 list*/public static List<Object> readExcel(MultipartFile excel,Class head, int sheetNo, int headLineNum) {ExcelListener excelListener = new ExcelListener();ExcelReader reader = getReader(excel, head,excelListener);if (reader == null) {return null;}ReadSheet readSheet = EasyExcel.readSheet(sheetNo).headRowNumber(headLineNum).build();reader.read(readSheet);// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的reader.finish();return excelListener.getDatas();}/*** 读取某个 sheet 的 Excel** @param excel 文件* @param head* @param sheetNo sheet 的序号 从1开始* @param headLineNum 表头行数,默认为1* @return Excel 数据 list*/public static List<Object> readExcel(MultipartFile excel,Class head, int sheetNo, int headLineNum,ExcelListener excelListener) {ExcelReader reader = getReader(excel, head,excelListener);if (reader == null) {return null;}ReadSheet readSheet = EasyExcel.readSheet(sheetNo).headRowNumber(headLineNum).build();reader.read(readSheet);// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的reader.finish();return excelListener.getDatas();}/*** 返回 ExcelReader** @param excel 需要解析的 Excel 文件* @param excelListener new ExcelListener()*/private static ExcelReader getReader(MultipartFile excel, Class head, AnalysisEventListener excelListener){String filename = excel.getOriginalFilename();if (filename == null || (!filename.toLowerCase().endsWith(".xls") && !filename.toLowerCase().endsWith(".xlsx"))) {return null;}InputStream inputStream;try {inputStream = new BufferedInputStream(excel.getInputStream());ExcelReader excelReader =  EasyExcel.read(inputStream,head,excelListener).build();return excelReader;} catch (IOException e) {e.printStackTrace();}return null;}/*** 判断你一个类是否存在某个属性(字段)** @param fieldName 字段* @param obj   类对象* @return true:存在,false:不存在, null:参数不合法*/public static Boolean isExistField(String fieldName, Object obj) {if (obj == null || StringUtils.isEmpty(fieldName)) {return null;}//获取这个类的所有属性Field[] fields = obj.getClass().getDeclaredFields();boolean flag = false;//循环遍历所有的fieldsfor (int i = 0; i < fields.length; i++) {if (fields[i].getName().equals(fieldName)) {flag = true;break;}}return flag;}private static ExcelReader getReaderNoHead(MultipartFile excel, AnalysisEventListener excelListener){String filename = excel.getOriginalFilename();if (filename == null || (!filename.toLowerCase().endsWith(".xls") && !filename.toLowerCase().endsWith(".xlsx"))) {return null;}InputStream inputStream;try {inputStream = new BufferedInputStream(excel.getInputStream());ExcelReader excelReader =  EasyExcel.read(inputStream,excelListener).build();return excelReader;} catch (IOException e) {e.printStackTrace();}return null;}/*** 新增或修改合并策略map* @param strategyMap* @param key* @param index*/private static void fillStrategyMap(Map<String, List<RowRangeVo>> strategyMap, String key, int index) {List<RowRangeVo> rowRangeDtoList = strategyMap.get(key) == null ? new ArrayList<>() : strategyMap.get(key);boolean flag = false;for (RowRangeVo dto : rowRangeDtoList) {//分段list中是否有end索引是上一行索引的,如果有,则索引+1if (dto.getEnd() == index) {dto.setEnd(index + 1);flag = true;}}//如果没有,则新增分段if (!flag) {rowRangeDtoList.add(new RowRangeVo(index, index + 1));}strategyMap.put(key, rowRangeDtoList);}
}

五、controller中的使用(导入时解析成列表数据并获得错误提示)

java">    @PostMapping("/import")@ApiOperation("导入XX信息")public ResponseResult<?> importProject(@RequestParam("file") MultipartFile file)  throws Exception{List<ProjectExpView> list = new ArrayList<>(1);List<ImportErrVo> errMsgList = new ArrayList<>(1);ExcelListener excelListener = new ExcelListener();Object Object1 = ExcelUtil.readExcel(file,ProjectExpView.class,0,excelListener);errMsgList = excelListener.getErrDatas();list = (List<ProjectExpView>) Object1;projectService.importProject(list);if(null!=errMsgList&&errMsgList.size()>0){return ResponseResult.error(HttpStatus.NOT_IMPLEMENTED,"部分导入成功:成功"+(null==list?0:list.size())+"条,失败"+errMsgList.size()+"条",errMsgList);}return ResponseResult.importSuccess();}

参考:https://blog.csdn.net/her_he/article/details/125297884?spm=1001.2014.3001.5502
该作者主页还有其他easyExcel相关,感兴趣可以看看


http://www.ppmy.cn/embedded/120036.html

相关文章

2024 Fortinet OT工业安全高峰论坛成功举办

9月10日&#xff0c;“2024年Fortinet OT工业安全高峰论坛”于广州圆满闭幕。盛会紧扣“工业安全新行动&#xff0c;智驭AI新时代”主题&#xff0c;汇聚全球OT领域精英、技术先锋及安全领域翘楚&#xff0c;共谋OT现代化浪潮下的安全新篇章。通过多维度视角、深层次对话、鲜活…

C++中string的使用

文章目录 string类对象的常见构造string类对象的容量操作size() / length()&#xff1a;返回字符串的长度&#xff08;字符数&#xff09;。capacity()&#xff1a;返回当前字符串分配的容量&#xff08;即在重新分配内存前可以保存的字符数&#xff09;。检查是否为空&#xf…

基本控制结构2

顺序结构 程序按照语句的书写次序顺序执行。 选择结构 判断选择结构又称条件分支结构&#xff0c;是一种基本的程序结构类型。 在程序设计中&#xff0c;当需要进行选择、判断和处理的时候&#xff0c;就要用到条件分支结构。 条件分支结构的语句一般包括if语句、if–else…

git clone或repo init 时报错:fatal: 协议错误:错误的行长度 xxx

执行repo init或git clone时报错:protocol error: bad line length 或协议错误:错误的行长度 系统版本:Ubuntu20.04 repo version v2.47 repo launcher version 2.45 git version 2.25.1 报错信息 fatal: 协议错误:错误的行长度 948 fatal: 远端意外挂断了 repo: err…

从0学习React(4)---更新组件状态setState

在上篇文章中&#xff0c;我们讲了React中的一些基础&#xff0c;包括组件的种类以及state的使用。上篇文章的结尾&#xff0c;我们讲到了如何更新组件的状态&#xff08;使用setState&#xff09;。但是我没有讲的很详细&#xff0c;这篇文章我们详细的讲一下React中如何更新组…

Qt Linguist手册-翻译员

翻译人员 Qt Linguist 是为 Qt 应用程序添加翻译的工具。一旦安装了 Qt&#xff0c;就可以像开发主机上的其他应用程序一样启动 Qt Linguist。 Qt Linguist 主窗口包含一个菜单栏和以下视图&#xff1a; 上下文 (F6) 用于从上下文列表中选择要翻译的字符串。字符串 (F7) 用于…

选择租用徐州服务器机柜的作用有哪些?

企业为了线上网络业务&#xff0c;通常都会选择租用服务器来确保网络的稳定性&#xff0c;企业选择服务器租用和托管业务后&#xff0c;同时也需要租用服务器机柜来进行放置所使用的服务器&#xff0c;对于机柜企业可以选择租用徐州机柜&#xff0c;下面就来聊一下选择租用徐州…

IvorySQL 3.4 来了

9 月 26 日&#xff0c;IvorySQL 3.4 发版。本文将带大家快速了解新版本特性。 IvorySQL 3.4 发版说明 IvorySQL 3.4 基于 PostgreSQL 16.4&#xff0c;修复了多个问题&#xff0c;并增强多项功能。 PostgreSQL 16.4 的变更 在未经授权时防止 pg_dump 执行&#xff0c;并引入一…