电商网站订烟平台官网,电子商务网站建设与策划,怎么样做网站代理商,外贸网站源码多语言如果想直接看怎么写的可以跳转到 解决方法 节#xff01;
需求描述
目前我们系统导出文件时#xff0c;都是通过表单提交后#xff0c;接收文件流自动下载。但由于在表单提交时没有相关调用前和调用后的回调函数#xff0c;所以我们存在的问题#xff0c;假如导出数据需…如果想直接看怎么写的可以跳转到 解决方法 节
需求描述
目前我们系统导出文件时都是通过表单提交后接收文件流自动下载。但由于在表单提交时没有相关调用前和调用后的回调函数所以我们存在的问题假如导出数据需要10秒这期间前台依然可以操作用户超过3秒没收到反馈会重复点击多次导致后台查询压力过大卡死。因此要对功能做以下修改
用户点击下载时弹出加载框提示如果用户有相同条件的数据正在导出需要弹出提示“文件正在下载请稍后”避免用户开了多个窗口点击
系统现状
使用的技术框架jQuery 1.11.3注意版本号EasyUI后端用的Struts2SpringHibernateJDK8 前端请求下载的逻辑是通过iframe跳转接收到后端传回的二进制文件流触发浏览器的自动下载来完成的。 前后端代码如下
htmlbodyform idtheForm2 nametheForm2 methodPOST enctypemultipart/form-datadiv idform-data-request-param styledisplay: none;/div/formiframe idoIframe nameoIframe frameborder0 width100% height100% styledisplay: none; srcc:out value${pageContext.request.contextPath} //pages/globals/blank.jsp/iframe/body
/htmlscriptexportExcel: function() {var requestParamForm $(#form-data-request-param);$(#form-data-request-param).empty();let inputHiddenDataArr [];let rqParams {};rqParams[cond.beginDate] 2024-05-20; // 入参1rqParams[cond.endDate] 2024-05-21; // 入参2rqParams[cond.other] Y; // 入参3for (let rqName in rqParams) {inputHiddenDataArr.push(input typehidden name rqName value rqParams[rqName] /);}$(inputHiddenDataArr.join()).appendTo(requestParamForm);let sTarget oIframe;let sFormName theForm2;let sUri actionUri /exportExcel.shtml;let form document.forms[sFormName];form.target sTarget;form.action sUri;// 无法监听到返回所以也没有做加载框form.submit();}
/scriptController(businessAction)
Scope(prototype)
public class BusinessAction extends Struts2Action {Resourceprivate BusinessService service;private Cond cond;public String exportExcel() throws Exception {// download方法的源码就不贴了, 内部逻辑是设置response的头信息Content-dispositionattachment; filenamexxx和Content-Typeapplication/octet-stream, 再通过输出流写出FileUtils.download(ServletActionContext.getResponse(), this.service.export(this.getDownloadDir(), this.cond, this.getSessionBean()));return null;}// 省略其他逻辑
}public class BusinessServiceImpl extends BusinessService {ExportLog(serviceNode 导出Excel)public File export(String downloadDir, Cond cond, SessionBean sessionBean) throws Exception {// ...省略查询等数据组装File file new File(downloadDir, PC DateUtils.formatDate(new Date(), yyyyMMddHHmmss) .xls);return file;}
}处理思路及过程
需要添加和移除加载框还有展示后端的错误信息就得用ajax后端返回的是文件流需要确认jQuery的ajax是否支持下载文件流如果不用文件流服务器生成文件后返回下载链接到前台也行但生成的文件在另外一个机器中不在tomcat目录下用户无法直接访问所以还是采用返回文件流的方式不考虑异步导出因为对于系统的改动比较大需要引入延时框架或中间件效益不高 因此决定后台依然返回文件流前端用ajax请求如果判断是文件流则下载不是则弹出错误提示
过程
使用jQuery的$.ajax一直都无法正常下载文件后来查了一些文章表示jQuery的$.ajax会把文件流的内容返回为字符串需要生成Blob对象后下载使用以下两种写法结果下载了打开文件会显示损坏
添加了xhrFields: { responseType: blob }jQuery3.x可正常使用1.11.x版本使用报错 Uncaught DOMException: Failed to read the responseText property from XMLHttpRequest: The value is only accessible if the objects responseType is or text (was blob). 注意换了3.0版本后可以接收到blob对象但项目中好多地方用到了jQuery不敢轻易升级版本 将dataType/responseType设置为blob也无效依然接收到字符串类型估计是$.ajax将接收到的数据都先序列化成字符串了
折腾了好久决定不用jQuery的$.ajax了用原生的XMLHttpRequest查找它的写法来请求结果终于正常接收到后端返回的Blob对象了
解决方法
asyncDownloadFile: function(requestUrl, requestData, successCallback, beforeSendCallback, completeCallback, errorCallback) {var formData new FormData();for (var key in requestData) {formData.append(key, requestData[key]);}var xhr new XMLHttpRequest();xhr.open(POST, requestUrl, true);//定义responseTypeblob, 是读取文件成功的关键这样设置可以解决下载文件乱码的问题xhr.responseType blob;xhr.onload function() {var data this.response;// 如果不是流信息, 说明有报错if (response.type.indexOf(text/plain) 0) {showMessage(data);}// 非文本内容, 后台返回了文件流, 在此处理var disposition decodeURI(xhr.getResponseHeader(Content-Disposition)),mimeTypexhr.getResponseHeader(Content-Type)//通过Content-Type获取后端的文件名var filename getFilenameFromDisposition(disposition);saveAsFile(data, filename, mimeType);};xhr.onerror function() {if (typeof errorCallback function) {errorCallback();}$.messager.alert(提示, 下载失败, 请联系管理员);};xhr.onloadend function() {$.messager.progress(close);if (typeof completeCallback function) {completeCallback();}};xhr.send(formData);
},
/** 解析文本内容*/
showMessage: function(data) {var reader new FileReader();reader.readAsText(data,UTF-8);reader.onload function() {var res JSON.parse(reader.result);$.messager.alert(提示, res.ajaxError ? res.ajaxError : 服务器异常, 请联系管理员);}
},
/** 通过disposition获取文件流的文件名 */
getFilenameFromDisposition: function (disposition){var filename;if (disposition disposition.indexOf(attachment) ! -1) {var filenameRegex /filename[^;\n]*(([]).*?\2|[^;\n]*)/;var matches filenameRegex.exec(disposition);if (matches ! null matches[1]) {filename matches[1].replace(/[]/g, );}}return filename;
},
/** 保存文件到本地 */
saveAsFile: function (data, filename, mimeType) {//兼容ieif (msSaveOrOpenBlob in navigator) {var blob new Blob([data], { type: mimeType });window.navigator.msSaveOrOpenBlob(blob, filename);} else {var blob new Blob([data], { type: mimeType });var url window.URL.createObjectURL(blob);var link document.createElement(a);document.body.appendChild(link);link.style.display none;link.download filename;link.href url;link.click();window.URL.revokeObjectURL(url);//手动释放blobURL,避免内存溢出document.body.removeChild(link);}
}jQuery3.x的写法
$.ajax({type: POST,url: 请求地址,xhrFields: {responseType: blob},data: requestDatasuccess: function(response,status,xhr) {if (response.type.indexOf(text/plain) 0) {showMessage(response);// 复用上面代码块的方法return;}// 复用上面代码块的方法var fileName getFilenameFromDisposition(xhr.getResponseHeader(Content-Disposition)); // 设置下载的文件名saveAsFile(response, fileName, xhr.getResponseHeader(Content-Type));},error: function(jqXHR, textStatus, errorThrown) {console.error(Error downloading file:, textStatus, errorThrown);}
});总结及反思
留意版本问题在这个需求上耗费的时间主要集中在使用了不同版本的写法结果大家都忽略了标注上自己的jQuery版本导致相同的用法在低版本下无效后台返回指定内容类型后台注意区分返回文件流和文本的头信息contentType的返回在我们系统会通过Struts的拦截器类将异常信息使用contentTypetext/plain文件流用的application/octet-stream写到response的头信息中
参考链接
Ajax处理文件流下载 使用XMLHttpRequest处理文件流下载