20190622_系统中的Excel导入导出功能

问题

在一般的后台管理系统中都少不了导入导出功能,而且很多都是以Excel的格式进行操作。在之前的项目中用的是前辈们提供的一个工具类。总结一下这个工具类可以帮你从Excel中解析出你要的数据,也可以把你的数据转化成Excel,功能比较基础和纯粹。但是从一个完成的导入导出逻辑来说,我们要做的远不止这些东西,比如在我们的项目中,完整的导入导出应该是这样的:

  • 导入:
    1. 用户选择文件,点击导入按钮
    2. 前端页面展示目前的导入进度,总条数,成功条数,错误条数
    3. 导入完毕,展示包含错误数据的错误文件的下载链接
    4. 用户下载错误文件,查看每条数据导入错误的原因,然后修改数据
    5. 重新导入
    6. 导入应该支持Excel 03版本,07版本
    7. 导入性能应该尽量高
    8. 导入最大应该支持100万数据的导入,不能出现OOM
  • 导出:
    1. 用户点击导出按钮
    2. 弹出对话框让用户选择需要导出的字段
    3. 用户点击确定,开始导出
    4. 前端页面显示导出进度,总条数,当前条数
    5. 导出完毕,直接下载文件到本地电脑
    6. 导出应该支持Excel 03版本,07版本
    7. 导出性能应该尽量高
    8. 导出最大应该支持100万数据的导出,不能出现OOM

基于上面这些问题,我们原来的一个简单的工具类是没办法解决的,只能另寻他路。

引入EasyExcel

几经寻找,找到了这个阿里巴巴开源的项目,看了一下文档,下载下来把我需要的几个功能都试了一下,感觉不错。不管从可用性,稳定性,性能以及学习成本上都比我们原来的工具类强很多,引入EasyExcel之后我们可以解决上面导入导出场景中的6,7,8问题,就是说针对Excel本身的这些操作我们可以不用操心了,完全交给EasyExcel就可以了,我们自己要去解决剩下的问题。

设计导入模式

其实我们大部分的导入场景的过程都是一样的,基本上分为以下几步:

  1. 从Excel解析出数据,转换为Java对象
  2. 遍历Java对象,处理每一个Java对象,通常就是简单的插入数据库
  3. 导入完毕,得到导入的结果:总条数,成功条数,错误条数,错误数据列表

所以这个地方我们可以设计一个模板模式+策略模式,这样可以将导入过程统一起来,把公共的步骤提供默认实现,每个业务只需要实现自己不同的地方,就是实现怎么去处理每一个Java对象。

简单写一下伪代码:
导入模板接口:

  interface ImportTemplateInterface{
    /**
    *默认方法,实现导入模板过程
    */
    default String import(InputStream ins){
        //任务id,返回给客户端用户查询导入进度
       String taskId = UUID.random().toString();
       //异步执行
        new Thread(
          new Runable(){
               public void run(){
                 updateProgress(taskId,START);
                 //利用easyExcel读取数据
                 List modelList = easyExcel.read(ins);
                //错误数据列表
                 List errorList = new ArrayList<>();
                 updateProgress(taskId,totalCount++);
                 modelList.foreach(model -> {
                       try{
                          //处理数据
                          importItem(model);
                          updateProgress(taskId,successCount++);
                        }catch(Exception e){
                            //加入到错误列表
                            errorList.add(toErrorModel(model));
                            updateProgress(taskId,errorCount++);
                       }
                 });
                updateProgress(END);
               }
          }
        ).start()
        return taskId;
      }

     /**
      *处理数据方法,由具体实现类去实现
      */
      void importItem(T model);

     /**
      *更新进度方法,默认实现类可以提供默认实现,具体实现类可以覆盖
      */
      void updateProgress(String taskId,int count);

  }

模板类的默认实现:

abstract class DefaultImportTemplate implements ImportTemplateInterface {
      @Resource
      private Redis redis;
      
      /**
      *抽象方法,处理数据,由子类提供实现
       */     
      abstract void importItem(T model);

      /**
      *默认的更新进度实现
      */
      void updateProgress(String taskId,int count){
        //进度存储到redis
        redis.update(taskId,count);
    }

    /**
    *默认的获取流程进度的方法
    */
     void getProgress(String taskId){
        redis.get(taskId);
      }

    /**
    *默认的获取错误数据的方法
    */
    List getErrorList(String taskId){
         redis.getErrorList(taskId);
    }
}

业务类的实现:

class UserImportTemplate extends DefaultImportTemplate {
    @Resource
    private UserDao userDao;

    /**
    *用户导入实现处理数据的方法,插入用户
    */
    void importItem(User model){
        userDao.insert(model);
    }
}

设计导出模式

其实导出跟导入一样也可以使用模板模式+策略模式,在导出的过程里面,主要需要业务自己实现的是获取数据的方法和获取表头的方法,其他的都可以在模板里面做好。这里可以没有默认实现,因为获取数据和获取表头都是和业务强相关的没法提供默认实现。伪代码思路基本上跟导入是一样的,不写了。

导出的动态表头设计

我们系统有点特殊的地方,就是导出的字段是可选的,那这个该怎么办呢?

设计一个注解

注解有index,value两个基础属性,index表示导出时表头字段的顺序,value表示表头的显示名称,把注解加在需要导出的实体类的字段上。

获取所有可选表头

前端传一个参数表示是哪个模块的导出操作,后端根据参数找到该模块导出的实体类,然后利用反射找出实体类中所有加了上面那个注解的字段,然后封装成表头对象,columnKey是指字段的名字,columnName是指表头显示名称,就是上面注解的value。然后把这些可选表头全部返回给前端,给用户选择。

根据选择的表头获取数据

用户在页面上勾选的表头时候提交到后端,后端获取了表头之后,使用EasyExcel的创建表头功能创建表头,这一步比较简单,就是把我们自己的表头对象转换成EasyExcel的格式,主要用到index和columnName两个属性。然后查询出需要导出的数据,因为我们查询出来的对象里面是所有属性都有值的,如果直接使用EasyExcel导出的话,会把所有数据都会导出,这样就会出现,表头是选择的那么多,但是每一行后面的数据会多出那些没有选择的列。因此,我们在从数据库获取数据之后,我们还需要利用表头中的columnKey的值进行反射获取对象的属性值,我们只获取用户选择的那些columnKey的值,然后组装成EasyExcel中需要的那种格式,然后就可以导出了。在这个过程中我们的表头和我们的内容顺序要保持一致,要不然会出现内容和表头对不上,所以我们在创建表头和内容是都要先对提交上来的可选表头按index排序,这样就OK了。用文字描述可能有点抽象,其实自己去实现的话,没有说的那么麻烦。如果你的导出表头是固定的那就很简单了,只需要使用EasyExcel的注解,然后直接查出数据,直接导出就可以了。EasyExcel提供了根据模型注解导出,也可以自己组装数据和表头,所以我们利用后面这个特点就可以实现动态表头的导出功能。
这个地方还有一点要注意的是,你要使用反射来自己获取属性值的话,那日期类型的数据你要自己格式化一下,要不然导出到Excel里面格式是不固定的,也许不是你想要的格式。

好了,写了这么多,感觉就是一个简单的导入导出功能,如果是需要做到系统里面的导入导出比较统一实现的话,可以参考一下,这样其他的伙伴在开发的时候就比较简单了,不用每个人都把这些做一遍。

你可能感兴趣的:(20190622_系统中的Excel导入导出功能)