RuoYi/ExcelUtil修改(导入excel表时,表中字段没有映射上数据库表字段)
RuoYi/ExcelUtil修改
- 描述
- 原因
- 解决
描述
使用ruoyi框架时,需要使用到导入、导出功能,涉及到ExcelUtil
工具类,但是在导入具体实现中,遇到问题
具体问题:
- 实体类字段,对应数据库表。
- 添加完了注解,没有使用注解中的
readConverterExp
(@Excel
(name = “用户性别”, readConverterExp = “0=男,1=女,2=未知”),readConverterExp转换不正确也会导致问题) - 但是在导时老是,表格中有的列有值,但是导入到数据库表中时却没有值
原因
- 排除实体类字段值与数据库值不一致
- 排除注解问题
- 最后想到,第二步中注解中
readConverterExp
转换不正确会导致表有值但是数据库没有值,是不是数据库的列名读取时又问题(和看到的不一致,导致在工具类中读取到的和注解中的name
对不上),于是将表格中的值copy出来,发现问题:注解name中的值是“xx”
,但是表格中是“xx ”或者“ xx”
(表格列太多了很多缩在一起,并且只是一个空格,看不出来)
解决
发现原因:表格中的列值和注解name对不上,可能会有差距(空格等)
- 在系统中设计一个功能,到处具体的
规范模版下载
给用户使用,再用该模版导入数据 - 在代码中处理,想办法在读取时,
去除掉前后空格
我的需求是,导入数据,分析数据,不可能给人家一个模版用,所以采用修改代码
具体代码:修改ExcelUtil
中的importExcel
方法
/*** 对excel表单指定表格索引名转换成list** @param sheetName 表格索引名* @param titleNum 标题占用行数* @param is 输入流* @return 转换后集合*/public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception{this.type = Type.IMPORT;this.wb = WorkbookFactory.create(is);List<T> list = new ArrayList<T>();// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheetSheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);if (sheet == null){throw new IOException("文件sheet不存在");}boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);Map<String, List<PictureData>> pictures = null;if (isXSSFWorkbook){pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);}else{pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);}// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1int rows = sheet.getLastRowNum();if (rows > 0){// 定义一个map用于存放excel列的序号和field.Map<String, Integer> cellMap = new HashMap<String, Integer>();// 获取表头Row heard = sheet.getRow(titleNum);for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++){Cell cell = heard.getCell(i);if (StringUtils.isNotNull(cell)){String value = this.getCellValue(heard, i).toString();//修改点1// 去除表头值前后空格,提高匹配容错性String trimmedValue = StringUtils.trim(value);cellMap.put(trimmedValue, i);}else{cellMap.put(null, i);}}// 有数据时才处理 得到类的所有field.List<Object[]> fields = this.getFields();Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();for (Object[] objects : fields){//修改点2Excel attr = (Excel) objects[1];String fieldName = attr.name();Integer column = cellMap.get(fieldName);// 如果直接匹配失败,尝试处理表头中的特殊情况if (column == null && cellMap.size() > 0){// 遍历cellMap查找可能匹配的表头(如包含空格的情况)for (Map.Entry<String, Integer> entry : cellMap.entrySet()){String headerName = entry.getKey();if (headerName != null && StringUtils.trim(headerName).equals(fieldName)){column = entry.getValue();break;}}}if (column != null){fieldsMap.put(column, objects);}}for (int i = titleNum + 1; i <= rows; i++){// 从第2行开始取数据,默认第一行是表头.Row row = sheet.getRow(i);// 判断当前行是否是空行if (isRowEmpty(row)){continue;}T entity = null;for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()){Object val = this.getCellValue(row, entry.getKey());// 如果不存在实例则新建.entity = (entity == null ? clazz.newInstance() : entity);// 从map中得到对应列的field.Field field = (Field) entry.getValue()[0];Excel attr = (Excel) entry.getValue()[1];// 取得类型,并根据对象类型设置值.Class<?> fieldType = field.getType();if (String.class == fieldType){String s = Convert.toStr(val);if (s.matches("^\\d+\\.0$")){val = StringUtils.substringBefore(s, ".0");}else{String dateFormat = field.getAnnotation(Excel.class).dateFormat();if (StringUtils.isNotEmpty(dateFormat)){val = parseDateToStr(dateFormat, val);}else{val = Convert.toStr(val);}}}else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))){val = Convert.toInt(val);}else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))){val = Convert.toLong(val);}else if (Double.TYPE == fieldType || Double.class == fieldType){val = Convert.toDouble(val);}else if (Float.TYPE == fieldType || Float.class == fieldType){val = Convert.toFloat(val);}else if (BigDecimal.class == fieldType){val = Convert.toBigDecimal(val);}else if (Date.class == fieldType){if (val instanceof String){val = DateUtils.parseDate(val);}else if (val instanceof Double){val = DateUtil.getJavaDate((Double) val);}}else if (Boolean.TYPE == fieldType || Boolean.class == fieldType){val = Convert.toBool(val, false);}if (StringUtils.isNotNull(fieldType)){String propertyName = field.getName();if (StringUtils.isNotEmpty(attr.targetAttr())){propertyName = field.getName() + "." + attr.targetAttr();}if (StringUtils.isNotEmpty(attr.readConverterExp())){val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());}else if (StringUtils.isNotEmpty(attr.dictType())){if (!sysDictMap.containsKey(attr.dictType() + val)){String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());sysDictMap.put(attr.dictType() + val, dictValue);}val = sysDictMap.get(attr.dictType() + val);}else if (!attr.handler().equals(ExcelHandlerAdapter.class)){val = dataFormatHandlerAdapter(val, attr, null);}else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)){StringBuilder propertyString = new StringBuilder();List<PictureData> images = pictures.get(row.getRowNum() + "_" + entry.getKey());for (PictureData picture : images){byte[] data = picture.getData();String fileName = FileUtils.writeImportBytes(data);propertyString.append(fileName).append(SEPARATOR);}val = StringUtils.stripEnd(propertyString.toString(), SEPARATOR);}ReflectUtils.invokeSetter(entity, propertyName, val);}}list.add(entity);}}return list;}
//修改点1
在获取Excel表头值并存入映射表时,增加了trim()
操作,自动去除表头值前后的空格
,确保表头名称标准化。
//修改点2
增强了字段映射逻辑,如果直接匹配失败,会遍历所有表头进行容错处理,通过trim()后比较表头名称,实现了对带空格表头的智能匹配。
这两处修改共同作用,使得即使Excel表格中的表头前后包含空格(如"工单编号 "
),也能正确匹配到实体类中定义的@Excel(name = "工单编号")
注解的字段,有效提高了Excel导入功能的健壮性和用户体验。
仓库地址
不清楚可以看仓库,dev分支下提交