主要思路是用线程池池去处理上传任务,并计算上传进度,将进度保存到session中。前端通过一个定时器按固定时间调用获取进度条的百分比,更新进度条进度。
前端相关代码:
<script type="text/javascript" src="${pageContext.request.contextPath }/static/scripts/easyui/jquery.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath }/static/scripts/easyui/jquery.easyui.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath }/static/scripts/easyui/easyui-lang-zh_CN.js"></script>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath }/static/scripts/easyui/themes/default/easyui.css"> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath }/static/scripts/easyui/themes/icon.css"> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath }/static/styles/common.css"> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath }/static/scripts/easyui/themes/demo.css">
<div id="progressDlg" class="easyui-dialog" title="执行进度" style="width: 600px;hight:400px;" data-options="iconCls:'icon-save',closed:true,resizable:true,modal:true,collapsible:true, buttons: '#progressDlgBtns'"> <div style="padding: 10px 60px 20px 60px"> <div id="p" class="easyui-progressbar" style="width:400px;"></div> </div> </div> <div id="progressDlgBtns" style="width: 600px;"> <a href="javascript:void(0)" class="easyui-linkbutton" style="padding: 4px;" οnclick="javascript:$('#progressDlg').dialog('close')">关闭</a> </div>
相关的js代码:
//展示进度条 var timerId; function showCheckProgress(){ $('#progressDlg').dialog('open'); //想要修改进度条的颜色去css文件中去修改 $('#p').progressbar({ value : 0, //设置进度条值 默认0 text : '{value}%' //设置进度条百分比模板 默认 {value}% //在value改变的时候触发 /*onChange : function (newValue, oldValue) { console.log('新:' + newValue + ',旧:' + oldValue); }, */ }); timerId = window.setInterval(getCheckProgress,1000); } //通过post请求得到进度 function getCheckProgress(){ var progressUrl = $('#getProgressUrl').val(); //使用JQuery从后台获取JSON格式的数据 $.ajax({ type:"post",//请求方式 url:progressUrl,//发送请求地址 timeout:3000,//超时时间:30秒 dataType:"json",//设置返回数据的格式 //请求成功后的回调函数 data为json格式 success:function(data){ if(data.progressValue>=100){ window.clearInterval(timerId); $('#dg').datagrid('load'); $('#importBtn').css('display','inline-block'); $('#showProgress').css('display','none'); } $('#p').progressbar('setValue',data.progressValue); }, //请求出错的处理 error:function(){ window.clearInterval(timerId); //alert("请求出错"); } }); }
文件上传界面:enctype="multipart/form-data"
<div id="importDlg" class="easyui-dialog" title="批量导入" style="width:400px;padding:30px 70px 50px 70px"> <form id="importForm" method="post" enctype="multipart/form-data"> <div style="margin-bottom:20px"> <input id="uploadFile" class="easyui-filebox" name="uploadFile" data-options="prompt:'请选择要导入的文件……'" style="width:100%"> </div> <div> <a href="#" id="doImportBtn" class="easyui-linkbutton" style="width:100%">上传</a> <a href="#" id="cancelImportBtn" class="easyui-linkbutton" style="width:100%">取消</a> </div> </form> </div>
springmvc中对上传文件的通用属性配置:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize"><value>2097152</value></property> <property name="defaultEncoding"><value>UTF-8</value></property> </bean>
后台java代码:
/** * 批量导入贷中记录 */ @RequestMapping(value = "/action/import") @ResponseBody public Result importList(HttpServletRequest request, HttpServletResponse response, @RequestParam MultipartFile uploadFile) { HttpSession session = request.getSession(); session.setAttribute("progressValue", 0.0); Result result = new Result(); try { String fileName = uploadFile.getOriginalFilename(); long size = uploadFile.getSize();//文件大小,字节 long maxSize = 2048000;//2M String fileType = StringUtils.split(fileName,".")[1]; if(!StringUtils.equals(fileType, "xlsx") && !StringUtils.equals(fileType, "xls")){ result.setCode(0); result.setMsg("请选择xlsx或xls格式的文件"); return result; } if(size > maxSize){ result.setCode(0); result.setMsg("批量导入的文件大小不能超过2M"); return result; } String batchNo = "DZ" + DateUtil.getCurrentDateTime(); List<RcsCreditManageJob> jobList = parseExcel( uploadFile.getInputStream(),session,batchNo); if(jobList!=null && jobList.size()>3000){ result.setCode(0); result.setMsg("单次校验记录数不能超过3000"); return result; } //先全部批量插入数据库 rcsCreditManageJobServiceImpl.batchAdd(jobList); result = rcsCreditManageJobServiceImpl.batchCheckCreditIn(session,jobList,batchNo); } catch (Exception e) { result.setCode(0); result.setMsg("批量校验操作失败!"); log.error("【批量校验操作异常】:"+ e.getMessage(),e); } return result; }
这里使用了多个线程,共同消费jobList中任务,每个Runnable中都传入了相同的对象progress来保存共同的信息,如批次号,总任务数,完成的任务数等。
/** * 批量校验 */ @Override public Result batchCheckCreditIn(HttpSession session,List<RcsCreditManageJob> jobList,String batchNo) { Result result = new Result(); try { int totalSize = jobList.size(); TaskProgress progress = new TaskProgress(totalSize,batchNo); for (RcsCreditManageJob job : jobList) { taskExecutor.execute(new dzCheckTask(progress,job,session)); } result.setCode(1); result.setMsg("批量校验申请提交完毕!"); } catch (Exception e) { e.printStackTrace(); result.setCode(0); result.setMsg("批量校验申请提交失败!"); } return result; }
/** * 执行校验的任务 */ private class dzCheckTask implements Runnable { private RcsCreditManageJob job; private TaskProgress progress; private HttpSession session; public dzCheckTask(TaskProgress progress,RcsCreditManageJob job,HttpSession session) { this.job = job; this.progress = progress; this.session=session; } public void run() { try { EngineCreditInDto ec = new EngineCreditInDto(); ec.setName(job.getUserName());//用户名称 ec.setMobile(job.getMobile());//手机号 ec.setIdCard(job.getCertid());//身份证号 ec.setCurrTime(DateUtil.getCurrentPrettyDateTime());//当前时间 ec.setCallType(job.getCallType()); riskServiceFacade.evaluateCreditInCheck(ec); //根据表数据 Map<String, Object> paramMap = new HashMap<String, Object>(); paramMap.put("execTime", DateUtil.getCurrentDateTime()); paramMap.put("certid", ec.getIdCard()); paramMap.put("batchNo", progress.getTaskNo()); rcsCreditManageJobMapper.updateExcuTimeByMap(paramMap); //更新进度 session.setAttribute("progressValue", progress.computeProgress()); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } }
/** * 任务进度实体 */ public class TaskProgress { private int totalSize;//任务总数 private int completeSize;//任务完成数 private String taskNo;//任务批次 public double taskProgress;//任务进度 public TaskProgress(int totalSize, String taskNo) { this.totalSize = totalSize; this.taskNo = taskNo; } /** * 用同步代码块实现 * * @param money */ public double computeProgress() { synchronized (this) { BigDecimal b1 = new BigDecimal(Double.toString(this.getTotalSize())); //没调用一次,完成一笔 int newCompleteSize = this.getCompleteSize() + 1; this.setCompleteSize(newCompleteSize); BigDecimal b2 = new BigDecimal(Double.toString(newCompleteSize)); double taskProgress = b2.divide(b1,2,RoundingMode.HALF_UP).multiply(new BigDecimal("100")).doubleValue(); this.setTaskProgress(taskProgress); return taskProgress; } } public int getTotalSize() { return totalSize; } public void setTotalSize(int totalSize) { this.totalSize = totalSize; } public int getCompleteSize() { return completeSize; } public void setCompleteSize(int completeSize) { this.completeSize = completeSize; } public String getTaskNo() { return taskNo; } public void setTaskNo(String taskNo) { this.taskNo = taskNo; } public double getTaskProgress() { return taskProgress; } public void setTaskProgress(double taskProgress) { this.taskProgress = taskProgress; } }
spring mvc中配置线程池的bean
<!-- 异步线程池--> <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <!-- 核心线程数 --> <property name="corePoolSize" value="10" /> <!-- 最大线程数 --> <property name="maxPoolSize" value="30" /> <!-- 队列最大长度 >=mainExecutor.maxSize --> <property name="queueCapacity" value="3000" /> <!-- 线程池维护线程所允许的空闲时间 --> <property name="keepAliveSeconds" value="300" /> <!-- 线程池对拒绝任务(无线程可用)的处理策略 --> <property name="rejectedExecutionHandler"> <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> </property> </bean>
最后是相应获取进度的请求,从session中获取最新的进度:
/** * 得到处理进度 */ @RequestMapping(value = "/action/getProgressValue") @ResponseBody public Map getProgressValue( HttpSession session) { double progress = (double) session.getAttribute("progressValue"); Map map = new HashMap(); map.put("progressValue", progress); return map; }
比较粗糙的实现,但是能跑能跳,本博客只是自己的工作记录之用,实现方式未必优雅。
如你有更好的方法,欢迎指教。