Remember to get the download status once

  • Requirements: The front-end sends a download request, and when the back-end pushes the file, it needs to give the front-end a signal that the download is complete

  • Technology selection: springmvc js jsp

  • train of thought

  1. After the download is complete, return a view (ModelAndView), and use the view to pass parameters

    //此为初步构想代码
    protected ModelAndView download(HttpServletRequest req,HttpServletResponse resp,
                                   String attribute){
          
          
        ServletOutputStream outStream;
        HSSFWorkbook workbook;
        ModelAndView mav ;
        //RESULT_PAGE 专门展示后端返回值的jsp(/pages/result.jsp)
        //AmluConstants 常量类,存放各种参数
        //AmluConstants.REQUEST_RESULT_INFO 需要向request中写返回值时,key的名称
        //ResultInfoManager 返回信息组装类,根据异常类型返回相应提示语
        try{
          
          
            //工作簿对象生成
            workbook = getWorkbook(attribute);
            //抛出文件生成异常
            if(workbook==null){
          
          
    			request.setAttribute(AmluConstants.REQUEST_RESULT_INFO,
                             ResultInfoManager.getWorkbookErrorInfo(e));
                mav = new ModelAndView(RESULT_PAGE);
                return mav;
            }
            //文件推送
            resp.reset();
            resp.setContentType("application/msexcel;charset=utf-8");
            resp.setHeader("Content-Disposition","attachment;filename="+
                          URLEncoder.encode(fileName,"UTF-8")+".xls");
            outStream = response.getOutputStream();
            workbook.write(outStream);
            outStream.flush();
            //完成状态推送
            mav = new ModelAndView();
            mav.addObject("message","success");
            return mav;
        }catch(Exception e){
          
          
            logger.error(e);
    		req.setAttribute(AmluConstants.REQUEST_RESULT_INFO,
                                 ResultInfoManager.getDownloadErrorInfo(e));
            mav = new ModelAndView(RESULT_PAGE);
            return mav;
        }finally{
          
          
            if(outStream!=null){
          
          
                outStream.close();
            }
        }
    }
    

    Ideally, if an exception is thrown, it will jump to the RESULT_PAGE page and display an error message. If it is in a normal state, it can be passed to the front-end information [message:success], but through Debug, it can be found that the above writing will cause the returned view to be null. The reasons are as follows:

    When the controller method parameter contains HttpServletResponse response, the view is empty after the method is processed

    Reference document: ModelAndView is null in springmvc interceptor

  2. Set the response header to the response to refresh the page

    response.setHeader("refresh","1");//每秒刷新一次
    

    This method is written incorrectly, because the original intention is to refresh the page only once, and there is no need to refresh repeatedly, but it will not take effect except for the reason of the writing, because the download file needs to modify the response header (Content-Disposition), when downloading When the response header is modified again after completion, the modification will become invalid (it may be impossible to modify the response header after the response is sent, that is, after the file stream is pushed)

    More ways to write response headers:

    Reference document: setHeader usage in response

  3. The return type of the download method is void, write the value to the request, and jump to a specific page to read the value and display the information

    There are two ways to jump to other pages, redirection and request forwarding

    Redirection will tell the client what the target URL is and return a response. After receiving the response, the client will send a request again to the target URL just obtained, so there are two request requests in the middle. If you write a value to the request, retry The value in the request will be lost after orientation.

    protected void download(HttpServletRequest req,HttpServletResponse resp,
                                   String attribute){
          
          
        ServletOutputStream outStream;
        HSSFWorkbook workbook;
        try{
          
          
            //工作簿对象生成
            workbook = getWorkbook(attribute);
            //抛出文件生成异常
            if(workbook==null){
          
          
    			throw new NullArgumentException("未获取到对应模板文件");
            }
            //文件推送
            resp.reset();
            resp.setContentType("application/msexcel;charset=utf-8");
            resp.setHeader("Content-Disposition","attachment;filename="+
                          URLEncoder.encode(fileName,"UTF-8")+".xls");
            outStream = response.getOutputStream();
            workbook.write(outStream);
            outStream.flush();
            //完成状态推送
            req.setAttribute(AmluConstants.REQUEST_RESULT_INFO,
                                 "下载成功");
            req.getRequestDispatcher(RESULT_PAGE).forward(req,resp);
        }catch(Exception e){
          
          
            logger.error(e);
    		req.setAttribute(AmluConstants.REQUEST_RESULT_INFO,
                                 ResultInfoManager.getDownloadErrorInfo(e));
            //采用请求转发进行错误页面跳转
            req.getRequestDispatcher(RESULT_PAGE).forward(req,resp);
        }finally{
          
          
            if(outStream!=null){
          
          
                outStream.close();
            }
        }
    }
    

    This method is effective, but the fly in the ointment is that the page jumps after the download is complete, which is very inhumane. Is it possible to pass a signal to the front end after the download request is completed normally?

  4. The front-end value transfer attempt after the download is completed based on the above method (method 3)

    4.1 Attempt to read cookies

    The back-end code only needs to be modified//The code after the status push is completed

    //更新完后,设定cookie,用于页面判断更新完成后的标志
    Cookie status = new Cookie("downloadStatus","success");
    status.setMaxAge(600);
    //添加cookie操作必须在写出文件前,如果写在后面,随着数据量增大时cookie无法写入
    response.addCookie(status);
    
    var timer1 = setInterval(refreshPage,1500);
    function refeshPage(){
     if(getCookie("downloadStatus")=="success"){
        clearInterval(timer1);//每隔一秒的判断操作停止
        delCookie("updateStatus");//删除cookie
        windows.location.reload();//页面刷新
     }
    }
    function getCookie(){
    <%
     String targetName="downloadStatus";
     request.getCookies();
     Cookie[] cookies=request.getCookies();
     if(cookies!=null){
     	for(int i=0;i<cookies.length;i++){
     		Cookie cookie = cookies[i];
     		if(cookie.getName().equals(targetName))){
              	%>
     			return "success";
     			<%
              }
          }
      }
    %>
    }
    //此处省略delCookie()具体写法
    

    Some problems were found in the actual operation of the above jsp code:

    ​ (1) When the cookie is repeatedly queried, the result of each query is the same as the first result, even after a new cookie is added in the background, and the latest cookie can be obtained after refreshing the jsp page. This makes the author draw some conclusions: the code in the script fragment will not be repeated multiple times. This may be related to the implementation principle of jsp.

    ​ (2) After refreshing the page and getting the latest cookie, execute delCookie() to delete the target cookie. This process can be realized. It is also the conclusion in (1) above: the code in the script fragment can be executed once.

    Similar cases: why this JSP program can only be executed once

    4.2 Try the value of EL expression (${ xxx })

    The back-end code only needs to be modified//The code after the status push is completed

    request.setAttribute("downloadStatus","success");
    
    var timer1 = setInterval(refreshPage,1500);
    function refreshPage(){
    	var downloadStatus=$("#downloadStatus").val();
    	if(downloadStatus=="success"){
    		<%request.removeAttribute("downloadStatus")%>
    		windows.location.reload();//页面刷新
    	}
    }
    
    <body>
    	<input type="hidden" id="downloadStatus" value="${ downloadStatus }"/>
    </body>
    

    This method is still invalid, and the real-time parameters in the request cannot be obtained in real time, and the reason is unknown. 【digging】

    4.3 Try to call the asynchronous request to query real-time parameters in a loop

    //这次将参数写在了session中
    HttpSession session = req.getSession();
    session.setAttribute("downloadStatus_37","success");
    //参数key详细化,以便后续此种方法大量复用
    
    //控制层增加方法
    @RequestMapping(params="method=queryStatus")
    @ResponseBody
    public Object queryStatus(String name,HttpServletRequest req){
          
          
        return req.getSession().getAttribute(name);
    }
    
    var timer1;
    function queryStatus(){
    	var url = "${pageContext.request.contextPath}/budgtBusDiff.htm?method=queryStatus";
    	$.ajax({
    		type:"get",
    		url:url,
    		data: "name=downloadStatus_37",
    		dataType:"json",
    		cache:false,
    		success:function(msg){
    			if(msg=='success'){
    				//销毁对应的参数
    				<%request.getSession().removeAttribute("downloadStatus_37")%>
    				window.location.reload();//页面刷新,恢复初始下载状态
    			}
    		},
    		error:function(msg){
    				console.log("ajax call failed:queryStatus()");
    				console.log(msg);
    		},
    		complete:function(msg){}
    	})
    }
    function download_onclick(){
    	DOWNLAODFORM.submit();
    	$('#background').show();//生成背景遮罩
    	timer1=setInterval(queryStatus,3000);//提交下载请求后再开始查询状态
    }
    

    This method is feasible.

    Reference document: simple implementation of front-end display of back-end processing progress

  • Other ideas:

    Can there be more ways to deal with the requirement of "download progress awareness"?

    We know that the progress bar of browser file download is mostly displayed according to the content-length field of the response header. There are also many plug-in components to choose from on the Internet. This method of progress display is not suitable, because from the user's point of view, waiting for the query result and waiting for the file to be pushed are both part of "waiting", and there is no indistinguishable difference.

    If you want to perceive the query progress of the sql statement, what should you do? I have two ideas:

    ​ (1) First count the amount of data, give the specific query time by a specific algorithm function, and pass the remaining query time to the front end, but this method needs to know the total amount of data, if multiple queries are required, the total amount of data is not good to give;

    ​ (2) Embed points in the program in advance, and update the "download progress parameter" after a certain part of the code is executed. This method is cumbersome, but it will be much simpler if you can use the support of the framework.

    For message push, WebSocket can also be used to actively push messages to the front end.

    Other reference articles:

    Websocket advanced download progress monitoring_哔哩哔哩_

    Display a dynamic progress bar when downloading files (front-end easyUI, background java)

Guess you like

Origin blog.csdn.net/Ka__ze/article/details/118763950