后端:使用easyExcel实现解析Excel文件读取数据。前端:Excel模板下载、前端上传文件

一、 前言

        本篇是EasyExcel快速入门知识,讲解如何读取Excel文件,对Excel中错误信息如空字符、必填项为空、表格格式校验做到处理 ,并给出了实际项目中示例代码;为什么要使用easyexcel;原因是相比于poi,easyexcel更加轻量级,读取写入API方便,并且在工作中占用内存较小;

        官方描述如下:

        Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。

二、前端上传Excel文件

        关于前端上传Excel文件分为两种情况:

        (1)前后端分离情况下(前端使用Vue)实现文件上传。

                该情况下直接借助elementUI提供的上传组件可以很好的实现上传文件,包括对上传文件前后均有回调函数,可以对各个时间节点进行操作,对文件处理非常便捷,我这里就不多赘述。

        (2)前后端未分离情况下(springboot模板)实现文件上传。

                该情况下可以使用input标签的type=“file”来实现文件选取,但是使用它主要有两点问题,相信大家在做项目时也会遇到:1、默认样式很丑,修改后点击无法触发文件选择的弹窗。2、如何取上传的file?直接获取input.value是fakePath路径(浏览器处于安全考虑修改为虚拟路径),因此不能通过此路径访问到文件,解析路径也比较麻烦。

        (3)模板下载。

                模板存在D盘img->download文件夹下,该路径通过nginx配置(8091为我的nginx地址),访问该路径实现文件下载,通过_blank实现不跳转页面。

                下面代码为解决上述(2)(3)两个问题:


<body>
<div>
<a class="mini-button" iconCls="icon-download" onclick="downloadFile()">导入模板下载</a>
<a class="mini-button" onclick="showInput()">学生数据导入</a>
<input id="articleImageFile" type="file" style="display: none;" accept=".xls,.xlsx" onchange="showFileName()"/>
<span id="showFileName" class="mini-button-info"></span>
</div>
</body>
<script>
<--导入模板-->
    function downloadFile(){
        const url = "http://192.168.3.96:8091/download/学生数据导入模板.xls";
        // 使用 window.open() 方法发起 GET 请求,跳转到下载页面
        window.open(url, '_blank')
    }
<--点击button后通过js实现点击上传文件input框-->
    function showInput(){
        deleteTemp();//删除临时表
        $("#articleImageFile").click();
    }
<--展示文件名称-->
    function showFileName(){
        var file = $("#articleImageFile")[0].files[0];
        if(file == undefined || file == null){
            $("#showFileName").text("")
            return;
        }
        $("#showFileName").text(name.name)
        showTips("文件上传成功,请查看导入数据后确认导入!");
    }
<--提交之前可以借助以下代码实现文件类型判断,具体提交代码不再赘述,主要就是发送ajax请求,将file传递到后端-->
<--
  if(file.name.substring(file.name.lastIndexOf(".")) !== '.xls' && file.name.substring(file.name.lastIndexOf(".")) !== '.xlsx'){
            showTips("只能上传.xls或.xlsx文件")
            return;
        }
-->
</script>

三、后端使用easyExcel解析文件

        (1)准备与Excel文件数据相同属性的dto,需要注意的是每一列对应的值。从0开始,注意使用

注解。

public class ExcelWgsUserInfoFileDto extends BaseDto {
    @ExcelProperty(index = 0)
    private String index;
    @ExcelProperty(index = 1)
    private String id;
    @ExcelProperty(index = 2)
    private String name;
//get、set方法
}

        (2)controller层接收文件(可以接收多个)

    /**
     * 导入Excel信息
     */
    @ApiOperation("导入Excel表")
    @RequestMapping(value = "/toexcel",method = RequestMethod.POST)
    public APIResponse toexcel(@RequestParam("file") MultipartFile file) throws Exception{
        return wgsUserInfoService.loadScoreInfo(file);
    }

        (3)service层(最关键)

                定义的变量解释:

                        BATCH_COUNT:集合最大缓存量,当到达最大缓存调用一次存方法。

                        temp:对调用了几次存函数进行计数,在判断表头、计算错误行数时使用。

                        isError:由于存方法中return只会跳出该方法,不会跳出整个read方法,使用它实现当出现错误后下一次1000行或者读取完成不再调用存方法。

                该代码较长,我稍微做一下解释:其实使用easyExcel解析文件,最重要的是要弄清楚整个读取文件的流程是什么。在EasyExcel.read(参数1,参数2,参数3)中,最重要的是第三个参数读的监听器,该参数中重写的两个方法:

        1、invoke()每读取一行调用一次,它的第一个参数ExcelWgsUserInfoDto就是我们最初定义的dto,是文件中一行的数据,做的操作为将该行数据加入到集合中,当到达集合缓冲区最大值1000时调用存数据的方法。

        2、doAfterAllAnalysed()该方法是当Excel文件全部读取完成后会调用一次存数据的方法。

        以上两个重写的方法主要作用是对Excel文件进行操作,拿取数据,这两个方法照着写即可,但是对于我们来说业务处理同样重要,在saveData中主要做的是业务处理(判断表格格式、存入数据库、是否有必填项为空等),存入数据库操作不多赘述,下面讲一下代码的大概思路:

        进入该方法会对存入集合的数据进行遍历,取数据存入数据库,在存数据之前便是判断时间。首先进行的是表头格式的判断,根据Excel模板文件第几行为模板文字进行判断,由于我的是第二行,所以有if(temp<1&&i<2)的判断,对Excel每行必填项进行if判断,若为null,返回提示,行数根据temp和i计算得来。

          Excel文件格式如下:

 @Transactional
    @Override
    public APIResponse<String> loadScoreInfo(MultipartFile file) throws IOException {
        APIResponse<String> result = new APIResponse<>();
        try {
            /**
             * 构建一个读的工作簿对象
             * @param file
             * 第一个参数:文件输入流
             * @param head
             * 第二个参数:文件中每一行对应的实体类
             * @param ReadListener
             * 第三个参数:读的监听器,每读一行内容,都会调用一次该对象的invoke方法,在invoke可以操作使用读到的数据
             */
            EasyExcel.read(file.getInputStream(), ExcelWgsUserInfoFileDto.class, new ReadListener<ExcelWgsUserInfoFileDto>() {
                /**
                 * 单次缓存的数据量
                 */
                public static final int BATCH_COUNT = 1000;
                //判断执行了几次saveData
                int temp = 0;
                boolean isError = false;
                /**
                 *临时存储
                 */
                private List<ExcelWgsUserInfoFileDto> cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                //导入数据库的明细表list
                private List<WgsUserInfoTempEntity> wgsUserInfoTempList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

                /**
                 * 每第一行调一次的方法
                 * @param data
                 * @param analysisContext
                 */
                @Override
                public void invoke(ExcelWgsUserInfoFileDto data, AnalysisContext analysisContext) {
                    cacheDataList.add(data);
                    if (cacheDataList.size() >= BATCH_COUNT && !isError) {
                        saveData();
                        temp++;
                        // 存储完成清理 list
                        cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                    }
                }

                /**
                 * 读取完整个文档后调用一次
                 * @param analysisContext
                 */
                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                    if(!isError){
                        saveData();
                    }
                    temp = 0;
                }

                /**
                 * 存储数据库
                 */
                private void saveData() {
                    System.out.println("进入存储文件!!");
                    wgsUserInfoTempList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                    for (int i = 0; i < cacheDataList.size(); i++) {
                        //取出每一行数据
                        ExcelWgsUserInfoFileDto data = cacheDataList.get(i);
//表头判断
                        if ( i == 1 && !"学号".equals(cacheDataList.get(1).getId()) && !"姓名".equals(cacheDataList.get(1).getName()) &&
                                !"性别".equals(cacheDataList.get(1).getGender()) && !"手机号码".equals(cacheDataList.get(1).getMobile()) &&
                                !"身份证号".equals(cacheDataList.get(1).getIdCard()) && temp < 1 ) {
                            System.out.println("进入格式检查");
                            result.setMsg("请检查表格格式是否符合规范!");
                            result.setMsgCode(201);
                            isError= true;
                            return;
                        }
//因为我的表头有两行
                        if (temp < 1 && i < 2) {
                            continue;
                        }
                        if(isNull(data)){
                            result.setMsg("第"+(temp*BATCH_COUNT+i+1)+"行必填项为空!");
                            result.setMsgCode(201);
                            isError= true;
                            return;
                        }
                        WgsUserInfoTempEntity wgsUserInfoTempEntity = new WgsUserInfoTempEntity();
//数据准备,每一行的数据存入对应entity中
                        wgsUserInfoTempEntity.setId(data.getId());
                        wgsUserInfoTempEntity.setBh(data.getId());
                        wgsUserInfoTempList.add(wgsUserInfoTempEntity);
                    }
                    if (wgsUserInfoTempList.size() == 0) {
                        System.out.println("Excel表中未读取到数据");
                        result.setMsg("文件数据异常!");
                        result.setMsgCode(201);
                        return;
                    }
                    List<WgsUserInfoTempEntity> wgsUserInfoTempEntities = wgsUserInfoTempRepository.saveAll(wgsUserInfoTempList);
                    if (wgsUserInfoTempEntities.size() > 0) {
                        logger.info("武工商UserInfo存入临时表数据成功");
                        result.setMsg("文件导入成功");
                        result.setMsgCode(200);
                    } else {
                        result.setMsg("文件导入失败");
                        result.setMsgCode(201);
                    }
                }
                public Boolean isNull(ExcelWgsUserInfoFileDto excelWgsUserInfoFileDto){
                    if(excelWgsUserInfoFileDto.getId() == null ||         excelWgsUserInfoFileDto.getId().isEmpty()){
                        return true;
                    }
                    return false;
                }
            }).sheet().headRowNumber(0).doRead();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>