定义asyncExportFile服务,通过异步下载导出文件
2017-07-14 00:43
447 查看
要解决的问题:导出文件超时。
解决思路:异步下载方式进行导出。先生成下载任务,然后轮询文件名,生成文件名的时候,再执行下载。
由于系统中需要执行导出的操作较多,因此将导出方法封装成了一个服务asyncExportFile,分别注入到各个需要执行下载任务的controller中。
(1)执行下载任务的asyncExportFile服务:
备注:因为想在controller中得知任务执行结果,然后给按钮上文字修改,所以后面用了promise。这样里面的3个函数都必须定义为promise对象。 生成下载任务和轮询文件名生成结果这2个会失败,就定义了reject。下载的时候,location.href和window.open很快,而且没办法拿到返回值,所以直接按成功处理。下载方法执行后,就直接resolve()。
(2)向后端发请求查询数据和执行结果的asyncExportFileService服务:
备注:
因为window.open下载的新窗口总是被拦截。所以后面改为location.href下载。location.href下载的时候,因为拿到文件下载,整个过程时间很短,所以用户感知不到页面跳转。
(3)页面中使用这个服务:
controller中引入:'$timeout','asyncExportFile'。
html中:
<div class="col-sm-2 text-right">
<button type="button" class="btn btn-default" ng-disabled="!exportFlag" ng-click="exportChannelSku();">
{{exportText}}
</button>
</div>
controller中:
$scope.exportFlag和$scope.exportText是为了提示用户文件正在下载,让按钮置灰,文案改变。最初要设置初始值。
$scope.exportFlag = true;
$scope.exportText = '导出渠道选品';
(4)后面QA测出用户连续点击两次会报400错误。
前端顶多设置这次在下载没执行完的时候,按钮不能点击,并提示正在导出中。没办法不让用户点两次。因为同一页面,这个item导出后,用户也可以选择其它item导出。关于函数节流和去抖,时间也不好设置。
后面自己去试了下,打开两个浏览器,同时点导出也会报400。
因为后端没有对生成下载任务的方法进行并发控制。最简单的,可以写一个manager的,一个任务执行完了,再去执行另一个任务。或者加sychronized锁。
函数节流(throttle),函数去抖(debounce)参看:http://www.cnblogs.com/fsjohnhuang/p/4147810.html
解决思路:异步下载方式进行导出。先生成下载任务,然后轮询文件名,生成文件名的时候,再执行下载。
由于系统中需要执行导出的操作较多,因此将导出方法封装成了一个服务asyncExportFile,分别注入到各个需要执行下载任务的controller中。
(1)执行下载任务的asyncExportFile服务:
'use strict'; /** * @ngdoc service * @name adminApp.asyncExportFile * @author wqq */ angular.module('adminApp') .service('asyncExportFile', ['$timeout', '$q', 'toastr', 'asyncExportFileService', function ($timeout, $q, Toastr, asyncExportFileService) { // var exportingFile = false; var service = { export: function (options) { // if(exportingFile === true){ // return; // 有正在流程中的任务在执行,就不再向后端发请求 // } // exportingFile = true; return $q(function (resolve, reject) { // console.log("入参", options); Toastr.info("文件导出中"); var type = options.type; var param = options.param; genTask(type, param).then(resolve, reject); }); // 用$q,可以实现promise传回执行结果。但是下面要用到的3个函数,必须都用promise实现。resolve里面可以加参数,传给外面页面。 function genTask(type, param) { return $q(function (resolve, reject) { // console.log("传给genTask服务的参数", JSON.stringify(param)); // JSON.stringify(param); var genTaskPromise = asyncExportFileService.genTask(type, JSON.stringify(param)); genTaskPromise.then(function (rst) { var taskId = rst.taskId; return queryTask(taskId); // 必须return,才能在下面then的时候知道执行结果 }, function () { // console.log("需要重新生成任务,此处让用户重新点击"); // exportFileFlag = false; Toastr.error("导出失败"); reject(); }).then(resolve, reject);
//genTaskPromise执行完成,queryTask(taskId);也执行完成, genTaskPromise.then才执行完成,才执行genTaskPromise.then().then()里的resolve。 }) }; function queryTask(taskId) { var taskId = taskId; // 方案一:放最外面也可以,避免闭包内变量提升导致找不到taskId。 return $q(function (resolve, reject) { // var taskId = taskId; //报错:会找不到taskId。原因:变量提升,闭包内变量变成最前面的,外面同名的排在了后面,所以找不到外面的,即便是参数中的,本质是作用域链的问题。 // var taskId2 = taskId; //方案二:可以换个不同的名字。 // console.log("queryTask---taskId22", taskId2); var queryTaskPromise = asyncExportFileService.queryTask(taskId); queryTaskPromise.then(function (rst) { if (!rst.file) { // console.log("轮询等待task任务"); $timeout(function () { queryTask(taskId); }, 2000); } else { var file = rst.file; return downloadTask(taskId, file); } }, function () { // console.log("未知taskId,需要重新生成任务,此处让用户重新点击"); // exportFileFlag = false; Toastr.error("导出失败"); reject(); }).then(resolve, reject); }) }; function downloadTask(taskId, file) { return $q(function (resolve, reject) { // console.log("下载文件时候传入的参数", taskId, file) asyncExportFileService.downloadTask(taskId, file); // $timeout(function () { exportingFile = false; }, 2000); //为了减少对服务端请求压力,可以对同一浏览器设置 一次下载完成2s后,才能再下载 resolve(); }) }; } }; return service; }]);
备注:因为想在controller中得知任务执行结果,然后给按钮上文字修改,所以后面用了promise。这样里面的3个函数都必须定义为promise对象。 生成下载任务和轮询文件名生成结果这2个会失败,就定义了reject。下载的时候,location.href和window.open很快,而且没办法拿到返回值,所以直接按成功处理。下载方法执行后,就直接resolve()。
(2)向后端发请求查询数据和执行结果的asyncExportFileService服务:
'use strict'; angular.module('adminApp') .service('asyncExportFileService', ['$q','common',function($q, Common){ var genTask = '/xhr/file/asyncDownload/genTask.json'; var queryTask = '/xhr/file/asyncDownload/queryTask.json'; var downloadTask = '/xhr/file/asyncDownload/downloadTask.json'; var service = { genTask: function(type, param){ var defer = $q.defer(); var params = { type: type, param: param }; Common.post(Common.contextPath + genTask, params).success(function(res) { if(res.code == 200) { defer.resolve(res.data); } else{ defer.reject(); } }).error(function() { defer.reject(); }); return defer.promise; }, queryTask: function(taskId){ var defer = $q.defer(); var params = { taskId: taskId }; Common.post(Common.contextPath + queryTask, params).success(function(res) { if(res.code == 200) { defer.resolve(res.data); } else{ defer.reject(); } }).error(function() { defer.reject(); }); return defer.promise; }, downloadTask:function(taskId, file){ console.log("下载文件时候传进来的参数",taskId,file) var params = { taskId: taskId, file: file }; //window.open(Common.contextPath + downloadTask + '?' + $.param(params)); location.href = Common.contextPath + downloadTask + '?' + $.param(params); } }; return service; }])
备注:
因为window.open下载的新窗口总是被拦截。所以后面改为location.href下载。location.href下载的时候,因为拿到文件下载,整个过程时间很短,所以用户感知不到页面跳转。
(3)页面中使用这个服务:
controller中引入:'$timeout','asyncExportFile'。
html中:
<div class="col-sm-2 text-right">
<button type="button" class="btn btn-default" ng-disabled="!exportFlag" ng-click="exportChannelSku();">
{{exportText}}
</button>
</div>
controller中:
$scope.exportChannelSku = function () { var params = { param: { channelId: channelId, firstCategory: $scope.search.cateId, priceStatus: $scope.search.status }, // 原本的导出参数 type: 2 // 导出类型 } $scope.exportFlag = false; $scope.exportText = '导出渠道选品中...'; asyncExportFile.export(params).then(function () { $scope.exportFlag = true; $scope.exportText = '导出渠道选品'; console.log("导出成功"); }); };
$scope.exportFlag和$scope.exportText是为了提示用户文件正在下载,让按钮置灰,文案改变。最初要设置初始值。
$scope.exportFlag = true;
$scope.exportText = '导出渠道选品';
(4)后面QA测出用户连续点击两次会报400错误。
前端顶多设置这次在下载没执行完的时候,按钮不能点击,并提示正在导出中。没办法不让用户点两次。因为同一页面,这个item导出后,用户也可以选择其它item导出。关于函数节流和去抖,时间也不好设置。
后面自己去试了下,打开两个浏览器,同时点导出也会报400。
因为后端没有对生成下载任务的方法进行并发控制。最简单的,可以写一个manager的,一个任务执行完了,再去执行另一个任务。或者加sychronized锁。
函数节流(throttle),函数去抖(debounce)参看:http://www.cnblogs.com/fsjohnhuang/p/4147810.html
相关文章推荐
- WebClient.DownloadFile(线程机制,异步下载文件)
- AjaxFileUpload实现文件异步上传(AjaxFileUpload.js文件下载及参数介绍)
- 配置tftp服务以及开发板中通过tftp下载文件
- android开发步步为营之67:使用android开源项目android-async-http异步下载文件
- WebClient.DownloadFile(线程机制,异步下载文件)
- Spring Boot 微服务之间通过FeignClient进行大文件下载:
- 九、AsyncFileUpload——异步上传文件
- input file 文件上传下载 查询数据库数据并导出Excel
- 文件上传下载——通过struts的FormFile上传单个excel文件
- ubuntu下配置tftp服务以及开发板中通过tftp下载文件
- 文件上传下载——通过struts的FormFile上传单个excel文件
- winform通过web异步上传下载服务器文件
- Magcodes.WeiChat——通过CsvFileResult以及DataAnnotations实现导出CSV文件
- ubuntu下配置tftp服务以及开发板中通过tftp下载文件
- 文件上传下载——通过ahxu扩展后的组件DiskFileUploadEx上传单个excel
- vs2008中定义dll,通过def文件导出接口
- 直接客户端浏览器下载文件,不必通过回传服务器再下载(例如将table内容导出excel)
- c# winform 通过web服务下载文件
- 数据写入到csv文件或者通过浏览器导出到下载文件
- Java 通过SMB服务远程下载文件及zip包中的文件