您的位置:首页 > 其它

定义asyncExportFile服务,通过异步下载导出文件

2017-07-14 00:43 447 查看
要解决的问题:导出文件超时。

解决思路:异步下载方式进行导出。先生成下载任务,然后轮询文件名,生成文件名的时候,再执行下载。

由于系统中需要执行导出的操作较多,因此将导出方法封装成了一个服务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   
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: