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

来源:互联网 发布:0.32双丝并绕数据 编辑:程序博客网 时间:2024/05/16 10:25

要解决的问题:导出文件超时。

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

由于系统中需要执行导出的操作较多,因此将导出方法封装成了一个服务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