前言

之前写的项目中,有个需求,需要导出导入Excel表格;

本来很简单的一件事,等到具体实现的时候,才发现,楼上部门给的表格很乱;

比如 Sheet A 单元簿中,公司名称在B列,那么 Sheet B 单元簿中,公司列就可能已经在 C 列上,或者直接没有公司列了 ;

或则,有的单元格有数据,有的没有数据,或者日期单元格中写的不是日期,是个字符串;

或者,同一个excel表的数据,来自多个 po 类对象,比如 A 列数据最后要封装到到 公司类 中,B 列数据最后封装到 合同类 里面 。这是最变态的;

总而言之,Excel乱的很,根本没有模板,因为这些Excel数据在写程序之前,已经存在了 ;

而给我的任务,则是导入这些表格,并且帮他们保存到数据库中,还要保持数据之间的关系;

现在摆在面前的是:别人是Excel适应程序,我面前的则是写程序兼容Excel ;

思来想去,写一个通用的工具类吧,也方便以后再遇到类似的问题,顺便展示下我实习生的能力哈;


当前支持的功能

  1. excel导入到内存中,放在一个Map<String,List<Object>>里面;

    K 是类的全限定名,V 是保存数据的list集合 ;

  2. 在读取数据的时候,可以对数据进行校验,支持正则表达式;

    通过在 po 类的字段上配置注解,进行数据的检验,不配置,则默认匹配任何数据;

  3. 如果数据校验结果有错误,则返回返回一个list集合,里面封装错误信息,便于前台显示;

    错误信息,也是用过注解,自己定义错误信息,默认错误信息为空串 ;


方法api

  1. 获取检验信息

     /**
    * 获取保存错误信息的集合
    * @return list
    */
    public List<String> getIndexErrors();

    返回一个保存哪行哪列发生错误的信息的集合;如果没有错误信息,则集合的大小为 0

  2. 导入excel表格

    /**
    * 功能:导入 excel表格,将内容保存到对应的对象的集合中 ;
    *
    * @param file 需要导入的 excel 表格
    * @param classes excel 中 数据最后封装到哪些对象中;
    */
    public Map<String, List<Object>> importExcel(File file, List<Class> classes)
    throws IOException,
    InvalidFormatException,
    IllegalAccessException,
    InstantiationException

    File 参数是要到导入的excel表格对象

    List<Class> 参数是excel表格数据来自那些类,里面保持它们的 Class 对象 ;


配置

要想工具正确的工作,还需要做一些配置的;

配置效果如下:

@FromWho(className = "cn.hyc.vo.ContractVo")
public class Contract { @ExcelVOAttribute(name = "合同ID", column = "A", CLASS = Integer.class)
private Integer contId;
@ExcelVOAttribute(name = "项目名字", column = "B")
private String itemName;
....
}
  1. @FromWho

    只有一个 String className() ; 属性,该属性使用在最基类上(Object除外),内容为最子类的全限定名;

    比如有3个类 ,A继承B,B继承C;则该注解写在C类上,里面内容为 A

  2. @ExcelVOAttribute

    /**
    * 导出到Excel中的列的名字.也是能读取识别的重要因子
    */
    String name(); /**
    * 配置列的名称,对应A,B,C,D,导出的时候,想让数据在哪一列,就写对应的列名字
    */
    String column(); /**
    * 该列是什么类型,指定列的数据类型,默认是String 类型
    */
    Class CLASS() default String.class ; /**
    * 使用正则表达式 对数据的内容进行校验。默认匹配任何数据
    */
    String regex() default "(.|\n)*"; /**
    * 错误提示信息,默认为空串
    */
    String info() default "";

如何使用(Demo)

  1. 导入

    方法参数添加的类是最子类;


    List<Class> classes = new ArrayList<>();
    // 将最终结果封装到哪一个类里面,这里就添加谁,可添加多个
    classes.add(Class.forName("cn.hyc.vo.ContractVo"));
    MyExcelUtils myExcelUtils = new MyExcelUtils();
    // 返回excel的数据,封装到各自对应的po对象里面
    Map<String, List<Object>> map = myExcelUtils.importExcel(new File("xxx.xlms"), classes);
  2. 获取错误信息


    List<String> errors = myExcelUtils.getIndexErrors();
    if (errors.size() > 0) {
    sonObject.put("result", "0");
    jsonObject.put("resultInfo", JSONObject.toJSON(errors));
    return jsonObject.toJSONString();
    }
  3. 从Map里面获取自己想要的po类数据‘

    	 List listObject = map.get("cn.hyc.vo.ContractVo");
    if (listObject.size() == 0) {
    continue;
    }
    // 根据类的全限定名获取
    List<ContractVo> list = new ArrayList<>(listObject.size());
    // 进行强转
    for (int j = 0; j < listObject.size(); j++) {
    list.add((ContractVo) listObject.get(j));
    }

实现思路(该工具类可正确的一个大前提)

无论你多个单元簿,里面的列怎么变换,怎么增删,但是你那个列的名字是不变的;(这是大前提,如果没有这个前提,则本工具将只读取和配置 name 的属性一样的列)

比如,公司名称数据那列,无论你一会放在A列,一会放在B列,但是你的那一列名字总归是 公司名称

从这个入手;

比如现在有5列,分类来自Aaa Bbb Ccc三个类。

AaaA — nameB — age
BbbD — timeE — money
CccE — nickname

按照上面的分配;去配置Aaa Bbb Ccc三个类 ;

仅演示配置 Aaa

@FromWho(className = "Aaa")
public class Aaa{ @ExcelVOAttribute(name = "姓名", column = "A", CLASS = Integer.class)
private String name;
@ExcelVOAttribute(name = "年龄", column = "B")
private String age;
....
}

工具内部的实现原理就是:先加载那些,传进来的Class对象,通过反射,获取其所有的子段,包括父类的字段,直到最基类;

拿到字段以后,只获取那些标注了特定注解的字段;

然后,获取注解中的内容:

比如加载 Aaa 先获取其头上标注的 @FromWho 的内容,知道这个类的字段上面的列名字都是来自 Aaa ,然后读取 Aaa 的字段,获取使用了 @ExcelVOAttribute 的内容,知道 在 excel姓名 列的内容最后封装到 Aaaname 字段上 ,知道 在 excel年龄 列的内容最后封装到 Aaaage 字段上 ;以此类推

关系绑定代码如下:

/**
* 将配置中配置的类的有注解字段,加载进 map 里面,K-V K是字段 V是注解的值,也就是基类名称 ;
* <p>
* 配置各项映射关系
*
* @param classes
* @return
*/
private void setFieldMapping(List<Class> classes) { Class clazz = null;
for (int i = 0; i < classes.size(); i++) {
clazz = classes.get(i);
int j = 0;
FromWho fromWho = (FromWho) clazz.getAnnotation(FromWho.class);
String className = fromWho.className();
Field[] allFields = clazz.getDeclaredFields(); for (Field field : allFields) {
if (field.isAnnotationPresent(ExcelVOAttribute.class)) {
String column = field.getAnnotation(ExcelVOAttribute.class).column();
String indexName = field.getAnnotation(ExcelVOAttribute.class).name();
String regex = field.getAnnotation(ExcelVOAttribute.class).regex();
String info = field.getAnnotation(ExcelVOAttribute.class).info();
int count = getColumnIndex(column);
cellMaxNum = Math.max(count, cellMaxNum);
indexNameClassMap.put(indexName, className);
indexNameFieldMap.put(indexName, field);
indexNameRegex.put(field, regex);
indexNameErrorInfo.put(field, info);
}
}
if (clazz.getSuperclass() != null
&& !clazz.getSuperclass().equals(Object.class)) {
List<Class> list = new ArrayList<>();
list.add(clazz.getSuperclass());
setFieldMapping(list);
}
}
}

涉及到的关系如下:

   /**
* 将字段进行分拣保持在其中,按照其在excel的列索引;
*/
private Map<Integer, String> indexClassMap = new HashMap<>(); /**
* 字段与列索引之间的关系
*/
private Map<String, Field> indexNameFieldMap = new HashMap<>(); /**
* 保存配置文件的 class 对象
*/
private Map<String, Class> name4class = new HashMap<>(); /**
* 列名字与类的映射关系
*/
private Map<String, String> indexNameClassMap = new HashMap<>(); /**
* 列与校验规则的映射关系
*/
private Map<Field, String> indexNameRegex = new HashMap<>();
/**
* 错误配置信息
*/
private Map<Field, String> indexNameErrorInfo = new HashMap<>();
/**
* 列内容的错误信息
*/
private List<String> indexErrors = new ArrayList<>();
/**
* 最大单元格数
*/
private int cellMaxNum = 0;
/**
* 列索引与字段的映射
*/
private Map<Integer, String> indexFieldMap = new HashMap<>();

后记

具体实现的代码太长了,一个导入 500 多行,就不放上来,我将它们封为一个 jar 包了;

下载地址 :Excel工具类 jar 包

我自己的东西,我竟然不能设置为免费下载,最低 1 积分…

05-11 18:08