美文网首页
java excel导入获取实时进度

java excel导入获取实时进度

作者: 东本三月 | 来源:发表于2019-10-25 17:50 被阅读0次

    1.需求

    • 对于成千上万数据量的excel导入,后台处理耗时长,体验差.需要实时展示当前导入的进度,提高使用体验

    2.实现思路

    • 采用多线程进行实现
    • 在导入开始执行前,生成一个uuid和进度对象,储存到静态Map中
    • 使用一个新线程执行导入,导入执行时,将导入的进度信息放到uuid对应的对象里
    • 将uuid返回给前端.请求结束
      -前端循环发请求,从后台获取uuid对应的进度对象,将展示到页面上

    3.页面实现效果(仅供参考)

    正在导入
    导入完成

    4.定义用于存储导入进度的对象

    • 将进度的一些常用信息进行定义,同时声明一个静态Map,用于存储所有进度信息
    /**
     * 用于存储学生信息导入的进度信息
     * @author authstr
     * @time 2019年10月24日16:56:21
     */
    public class ImportAsynInfo {
    
        //用于存储所有的导入进度信息
        public static Map<String,ImportAsynInfo> allAsynInfo=new HashMap<String,ImportAsynInfo>();
    
        //提示信息或 异常信息
        private String msg;
        //数据总数
        private Integer totality=0;
        //已处理的数据条数
        private Integer doneSum=0;
        //失败的数据条数
        private Integer errorSum=0;
        //成功的数据条数
        private Integer successSum=0;
        //错误文件的路径
        public String errorFilePath;
        //导入是否结束
        public  Boolean isEnd= false;
    
        /**
         * 创建一个进度信息,并获取对应的uuid
         * @return
         */
        public static String  createAsynInfo(){
            ImportAsynInfo asynInfo=new ImportAsynInfo();
            String uuid=UUID.randomUUID().toString().replace("-","");
            allAsynInfo.put(uuid,asynInfo);
            return uuid;
        }
    
        /**
         * 通过uuid获取进度信息
         * @param uuid
         * @return
         */
        public static ImportAsynInfo getAsynInfo(String uuid){
            return allAsynInfo.get(uuid);
        }
    
        /**
         * 通过uuid删除对应的进度信息
         * @param uuid
         * @return
         */
        public static void deleteAsynInfo(String uuid){
            allAsynInfo.remove(uuid);
        }
    
        /**
         * uuid对应的进度 已处理的数据条数+1
         * @param uuid
         */
        public static void doneSumAddOne(String uuid){
            ImportAsynInfo asynInfo= getAsynInfo(uuid);
            asynInfo.setDoneSum(asynInfo.getDoneSum()+1);
        }
    
        /**
         * uuid对应的进度 失败的数据条数+1
         * @param uuid
         */
        public static void errorSumAddOne(String uuid){
            ImportAsynInfo asynInfo= getAsynInfo(uuid);
            asynInfo.setErrorSum(asynInfo.getErrorSum()+1);
        }
    
        /**
         * uuid对应的进度 成功的数据条数+1
         * @param uuid
         */
        public static void successSumAddOne(String uuid){
            ImportAsynInfo asynInfo= getAsynInfo(uuid);
            asynInfo.setSuccessSum(asynInfo.getSuccessSum()+1);
        }
    
        public String getMsg() {
            return msg;
        }
        public void setMsg(String msg) {
            this.msg = msg;
        }
        public Integer getTotality() {
            return totality;
        }
        public void setTotality(Integer totality) {
            this.totality = totality;
        }
        public Integer getDoneSum() {
            return doneSum;
        }
        public void setDoneSum(Integer doneSum) {
            this.doneSum = doneSum;
        }
        public Integer getErrorSum() {
            return errorSum;
        }
        public void setErrorSum(Integer errorSum) {
            this.errorSum = errorSum;
        }
        public Integer getSuccessSum() {
            return successSum;
        }
        public void setSuccessSum(Integer successSum) {
            this.successSum = successSum;
        }
        public String getErrorFilePath() {
            return errorFilePath;
        }
        public void setErrorFilePath(String errorFilePath) {
            this.errorFilePath = errorFilePath;
        }
        public Boolean getEnd() {
            return isEnd;
        }
        public void setEnd(Boolean end) {
            isEnd = end;
        }
    
    }
    

    5.Controller层开启线程进行导入

    • 获取线程池,通过线程池来启动线程来执行导入,并将uuid传入
    • 文件需要通过输入流来传入,直接传文件对象,可能无法读取到文件
    • 后面定义一个接口,来向前端返回指定uuid对应的进度对象.并对进度对象进行清理
    @RestController
    @RequestMapping("student_import/v1")
    public class StudentImportController extends AbstractAPIController {
        @Autowired
        StudentImportServiceImpl studentImportService;
    
        private ExecutorService executor = Executors.newCachedThreadPool() ;
    
        //下载导入模板
        @RequestMapping("/excelExport")
        public void excelExport(HttpServletResponse response) {
            studentImportService.excelExport(response);
        }
    
         //数据导入处理
        @RequestMapping("/save_excel_auto_studentno")
        public Map saveExcelStudentno(HttpServletResponse response, @RequestParam("file") MultipartFile file){
            Map m = new HashMap<>();
            String uuid=ImportAsynInfo.createAsynInfo();
            try {
                final InputStream  inputStream = file.getInputStream();
                executor.submit(new Runnable(){
                    @Override
                    public void run() {
                        try {
                            studentImportService.saveExcel_auto_studentno(response, inputStream,uuid);
                        }catch(Exception e) {
                            e.printStackTrace();
                            ImportAsynInfo.getAsynInfo(uuid).setMsg(e.getMessage());
                            ImportAsynInfo.getAsynInfo(uuid).setEnd(true);
                            throw new Exception("无法进行导入!");
                        }
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
            m.put("uuid",uuid);
            return m;
        }
       //下载导入的错误文件
        @RequestMapping("downloadErrorExcel")
        public void downloadErrorExcel(HttpServletResponse response, String fileName){
            studentImportService.downloadErrorExcel(response, fileName);
        }
    
        //获取导入的进度
        @RequestMapping("get_import_plan")
        public Map get_import_plan(String uuid) {
            Map m = new HashMap<>();
            ImportAsynInfo asynInfo=ImportAsynInfo.getAsynInfo(uuid);
            //如果导入结束,复制进度对象进行返回,将储存的进度对象删除
            if(asynInfo!=null&&asynInfo.getEnd()){
                ImportAsynInfo newAsynInfo=new ImportAsynInfo();
                newAsynInfo.setEnd(asynInfo.getEnd());
                newAsynInfo.setMsg(asynInfo.getMsg());
                newAsynInfo.setErrorFilePath(asynInfo.getErrorFilePath());
                newAsynInfo.setTotality(asynInfo.getTotality());
                newAsynInfo.setDoneSum(asynInfo.getDoneSum());
                newAsynInfo.setErrorSum(asynInfo.getErrorSum());
                newAsynInfo.setSuccessSum(asynInfo.getSuccessSum());
                ImportAsynInfo.deleteAsynInfo(uuid);
                asynInfo=newAsynInfo;
            }
            m.put("data",asynInfo);
            return m;
        }
    

    6. service进行执行导入

    • 在导入过程中,设置导入进度信息
    • 其他业务相关代码已省略
    @Service
    public class StudentImportServiceImpl extends AbstractService implements StudentImportService {
    
        @Override
        public void excelExport(HttpServletResponse response) {
               //导入模板下载   略  
         }
    
        @Override
        public void downloadErrorExcel(HttpServletResponse response, String fileName) {
              //下载错误文件   略  
        }
    
    
        @Transactional
        @Override
        public Map<String, Object> saveExcel(HttpServletResponse response, InputStream inputStream,String uuid) {
           //其他代码...
           //获取excel导入数据数量后
           ImportAsynInfo.getAsynInfo(uuid).setTotality( 数量 );
           //其他代码...
          for (int i = 0; i < 数量; i++) {
               //其他代码...
               //在一条数据处理结束后
               ImportAsynInfo.doneSumAddOne(uuid);
               //其他代码...
               if(数据有错误){
                   //其他代码...
                   ImportAsynInfo.errorSumAddOne(uuid);
               }else{
                    //其他代码...
                    ImportAsynInfo.successSumAddOne(uuid);
               }
           }
            //其他代码...
            //错误文件创建后
           ImportAsynInfo.getAsynInfo(uuid).setErrorFilePath(errorFileName);
            //其他代码...
            //导入完成后
            ImportAsynInfo.getAsynInfo(uuid).setEnd(true);
      }
    }
    

    7.显示进度条页面

    • 在网上没找到方便的可以显示多种颜色的进度条,这里曲线救国,采用ECharts的饼图来显示进度信息(具体样式可以根据需求调整)
    • 一些js与样式 已略过
    <!doctype html>
    <html>
    <head>
            <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
            <meta http-equiv="content-type" content="text/html;charset=utf-8">
            <meta content="always" name="referrer">
            <script src="/.../echarts/echarts.min.js"></script>
    </head>
    <body>
        <div class="J_conWarp g-lr-mg g-warning-box">
            <h4 class="g-title"> <a href="javascript:history.back()" style="font-size: 16px; color: black; " >< 返回</a></h4>
            <div class="J_containerWarp">
                <div  class="container-fluid g-t-mg2">
                    <div class="row" >
                        <div class="col-xs-12" >
                                <div id="import_file">
                                    <form class="form-horizontal" role="form" id="excelExportForm">
                                    </form>
                                    <form class="form-horizontal" role="form" id="downloadErrorExcelForm">
                                        <input type="hidden" name="fileName" id="fileName" value="" />
                                    </form>
                                    <form class="form-horizontal" role="form" id="form1">
                                        <div class="form-group ">
                                            <label class="col-xs-2 control-label"><em class="text-red">*</em>导入文件</label>
                                            <div class="col-xs-10">
                                                <input type="file" id="file" name="file" class="form-control" style="width:400px" accept=".xls" />
                                            </div>
                                        </div>
    
                                    </form>
                                    <div  class="form-group ">
                                        <label class="col-xs-2 control-label"></label>
                                        <a class="gbn gbn-m" href="javascript:" onclick="E.excelExport();" >下载导入模板</a>
                                        <a class="gbn gbn-m" href="javascript:" onclick="E.saveExcel();" >导入</a>
                                        <a class="gbn gbn-m gbn-red" href="javascript:" onclick="history.back();" >返回</a>
                                    </div>
                                </div>
    
                                <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
                                <div id="main" style="width:100%;height:550px;"></div>
                                <div  id="downloadErrorExcelA"class="col-xs-12" style="height:150px;width:100%;display:none">
                                <div class="col-xs-4"></div>
                                <div  class="form-group ">
                                    <a class="gbn gbn-m gbn-red"   href="javascript:" onclick="E.downloadErrorExcel();" >下载错误文件</a>
                                    <a id="quxiao" class="gbn gbn-m"   href="javascript:" onclick="E.quxiao();" >取消</a>
                                </div>
                            </div>
                        </div>
    
                    </div>
    
                </div>
            </div>
    
        </div>
    </body>
    
    <script type="text/javascript">
        window.onload = window.onresize = function() {
          $(".J_containerWarp").height($(window).height() - 60);
          $(".J_containerWarp").niceScroll({});
        }
        $.ajaxSettings.async=true;
        var uuid=null;
        var setInterval_id=null;
        var myChart = echarts.init(document.getElementById('main'));
        var option = {
            title : {
                text: '正在进行导入中...',
                subtext: '当前进度',
                x:'center'
            },
            tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
            },
            legend: {
                orient: 'vertical',
                left: 'right',
                data: ['导入成功','导入失败','未处理']
            },
            series : [
                {
                    name: '导入进度',
                    type: 'pie',
                    // radius : '55%',
                    radius: ['50%', '70%'],
                    center: ['50%', '60%'],
                    data:[
                        {value:0, name:'导入成功'},
                        {value:0, name:'导入失败'},
                        {value:100, name:'未处理'}
                    ],
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    },
                    color: ['#2ECC71','#E67E22','#BDC3C7'],
                }
            ]
        };
        var E = {
            excelExport : function() {
                $("form[id=excelExportForm]").attr("action",
                        "${request.contextPath}/student_import/v1/excelExport");
                $("#excelExportForm").submit();
            },
            saveExcel : function() {
                var file = $("#file").val();
                if(!file){
                    Message.error("导入的文件不能为空");
                    return ;
                }
                //循环获取进度信息
                setInterval_id=setInterval(E.getAsynInfo,500);
                myChart.setOption(option);
                var index = layer.load(1);
                var formData = new FormData();
                //隐藏导入区域和错误文件下载区域,显示进度条区域
                $("#import_file").hide();
                $("#main").show();
                $("#downloadErrorExcelA").hide();
                formData.append('file', $('#file')[0].files[0]);
                 $.ajax({
                        type : "POST",
                        url : "${request.contextPath}/student_import/v1/save_excel",
                        data : formData,
                        async: false,
                        cache: false,
                        contentType: false,
                        processData: false,
                        success : function(o) {
                            if(o.code==1){
                                //设置uuid
                                uuid=o.uuid;
                            } else {
                                Message.error(o.msg);
                            }
                    }
                });
            },
            downloadErrorExcel : function() {
                $("form[id=downloadErrorExcelForm]").attr("action",
                        "${request.contextPath}/student_import/v1/downloadErrorExcel");
                $("#downloadErrorExcelForm").submit();
            },
            getAsynInfo:function(){
                //如果uuid存在,进行获取数据
                if(uuid!=null){
                    $.post("${request.contextPath}/student_import/v1/get_plan",{"uuid":uuid},function(o){
                        console.log(o);
                        //如果获取到了数据
                        if(o.code==1&&o.data!=null){
                            // 使用指定的配数据显示图表。
                            option.title.subtext="当前进度   [共"+o.data.totality+"]条";
                            option.series[0].data[0].value=o.data.successSum;
                            option.series[0].data[1].value=o.data.errorSum;
                            option.series[0].data[2].value=o.data.totality-o.data.doneSum;
                            myChart.setOption(option);
                            //如果导入结束了
                            if(o.data.isEnd){
                                option.title.text="导入完成";
                                myChart.setOption(option);
                                clearInterval(setInterval_id);
                                //如果有错误数据,展示错误文件的下载
                                if(o.data.totality>0&&o.data.errorSum>0){
                                    $("#fileName").val(o.data.errorFilePath);
                                    $("#downloadErrorExcelA").show();
                                }
                                //如果导入中出现的异常
                                if(o.data.msg!=null){
                                    $("#import_file").show();
                                    $("#main").hide();
                                    Message.error(o.data.msg);
                                }else{
                                    Message.success("导入结束,"+o.data.successSum+"条数据导入成功,"+o.data.errorSum+"条数据导入失败");
                                }
                            }
                        }else{
                            Message.error(o.msg);
                        }
                    });
                }
            },
            quxiao:function () {
                $("#import_file").show();
                $("#main").hide();
                $("#downloadErrorExcelA").hide();
                uuid=null;
            }
        }
    
    </script>
    
    </html>
    

    8.还未完成的功能

    • 对线程进行处理和关闭
    • 增加取消导入功能
      • 导入进度对象中,增加一个 是否取消 的标识
      • 在导入的每次循环中,判断这个标识,如果true,跳出整个导入方法,并回滚事务
    • 增加中断导入功能
      • 导入进度对象中,增加一个 是否中断 的标识
      • 在导入的每次循环中,判断这个标识,如果true,直接提交事务,将之前的错误数据信息和之后还未处理的数据合并成一个excel给前端,然后跳出导入方法

    相关文章

      网友评论

          本文标题:java excel导入获取实时进度

          本文链接:https://www.haomeiwen.com/subject/buwsvctx.html