RuoYi/ExcelUtil修改
描述
使用ruoyi框架时,需要使用到导入、导出功能,涉及到ExcelUtil工具类,但是在导入具体实现中,遇到问题
具体问题:
- 实体类字段,对应数据库表。
- 添加完了注解,没有使用注解中的
readConverterExp(@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知"),readConverterExp转换不正确也会导致问题) - 但是在导时老是,表格中有的列有值,但是导入到数据库表中时却没有值
原因
- 排除实体类字段值与数据库值不一致
- 排除注解问题
- 最后想到,第二步中注解中
readConverterExp转换不正确会导致表有值但是数据库没有值,是不是数据库的列名读取时又问题(和看到的不一致,导致在工具类中读取到的和注解中的name对不上),于是将表格中的值copy出来,发现问题:注解name中的值是"xx",但是表格中是"xx "或者" xx"(表格列太多了很多缩在一起,并且只是一个空格,看不出来)
解决
发现原因:表格中的列值和注解name对不上,可能会有差距(空格等)
- 在系统中设计一个功能,到处具体的
规范模版下载给用户使用,再用该模版导入数据 - 在代码中处理,想办法在读取时,
去除掉前后空格
我的需求是,导入数据,分析数据,不可能给人家一个模版用,所以采用修改代码
具体代码:修改ExcelUtil中的importExcel方法
java
/**
* 对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个sheet
Sheet 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-1
int 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)
{
//修改点2
Excel 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分支下提交