【工具】导入导出 Excel

版权声明:转载请给出原文链接 https://blog.csdn.net/youngyouth/article/details/86510724


前言

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

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

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

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

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

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

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

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

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


当前支持的功能

工期很紧张,没有那些时间给我慢慢研究,开发导入当初已经用了三四天时间;

但是导出真的很简单,相对于导入来说,看下 poi 文档就可以写出来,以后有时间,再更下。

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

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

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

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

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

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


方法api

类名为 MyExcelUtils ,方法为非 static 的,因为方法跑在服务器端,多个浏览器可能并发访问,使用静态方法,可能导致保存错误信息的集合中的错误信息乱掉 ;

  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的一个工具类,但是那个工具类,写的不完善,,好多东西都没做判断,。。但是也给予我指导了;

    那个地址为:https://blog.csdn.net/lk_blog/article/details/8007777

    /**
         * 导出到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三个类。

类名 excel列 — 字段 excel列 — 字段
Aaa A — name B — age
Bbb D — time E — money
Ccc E — 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 积分…

猜你喜欢

转载自blog.csdn.net/youngyouth/article/details/86510724