• 【工具】导入导出 Excel



    前言

    之前写的项目中,有个需求,需要导出导入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 "(.|
      )*";
      
          /**
           * 错误提示信息,默认为空串 
           */
          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 积分…

  • 相关阅读:
    AppScan安全问题解决方案
    WPF方法 获取控件在屏幕的位置和自动控件位置
    WPF获取鼠标点击位置和API使用
    system.windows.controls
    .net 获取项目的根目录
    InvokeRequired
    控件取图片和矩形框 小测试
    mariadb 设置远程访问
    代码生成器 链接
    分享个网站
  • 原文地址:https://www.cnblogs.com/young-youth/p/11665601.html
Copyright © 2020-2023  润新知