SpringBoot如何實現一個實時更新的進度條

各位看官可以關注博主個人博客,瞭解更多信息。
作者:Surpasser
鏈接地址:https://surpass.org.cn

前言

博主近期接到一個任務,大概內容是:導入excel表格批量修改狀態,期間如果發生錯誤則所有數據不成功,爲了防止重複提交,做一個類似進度條的東東。

那麼下面我會結合實際業務對這個功能進行分析和記錄。

正文

思路

前端使用bootstrap,後端使用SpringBoot分佈式到註冊中心,原先的想法是導入表格後異步調用修改數據狀態的方法,然後每次計算修改的進度然後存放在session中,前臺jquery寫定時任務訪問獲取session中的進度,更新進度條進度和百分比。但是這存在session在服務間不共享,跨域問題。那麼換一個方式存放,存放在redis中,前臺定時任務直接操作獲取redis的數據。

實施

進度條

先來看一下bootstrap的進度條

<div class="progress progress-striped active">
    <div class="progress-bar progress-bar-success" role="progressbar"
         aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
         style="width: 40%;">
        40%
    </div>
</div>

進度條更新主要更新style="width: 40%;"的值即可,div裏面的40%可以省略,無非時看着明確。

可以考慮將進度條放入彈出層。

定時任務

//點擊確認導入執行此方法
function bulkImportChanges() {
    //獲取批量操作狀態文件
    var files = $("#importChanges").prop("files");
    var changesFile = files[0];
    var formData = new FormData();
    formData.append("importFile",changesFile);
    $.ajax({
        type : 'post',
        url : "/risk/bulk***es",
        data : formData,
        processData : false,      //文件ajax上傳要加這兩個的,要不然上傳不了
        contentType : false,      //
        success : function(obj) {
            //導入成功
            if (obj.rspCode == "00") {
                //定時任務獲取redis導入修改進度
                var progress = "";
                var timingTask = setInterval(function(){
                    $.ajax({
                        type: 'post',
                        url: "/risk/t***k",
                        dataType : 'json',
                        success: function(result) {
                            progress = result.value;
                            if (progress != "error"){
                                var date = progress.substring(0,6);
                                //這裏更新進度條的進度和數據
                                $(".progress-bar").width(parseFloat(date)+"%");
                                $(".progress-bar").text(parseFloat(date)+"%");
                            }
                        }
                    });
                    //導入修改完成或異常(停止定時任務)
                    if (parseInt(progress)==100 || progress == "error") {
                        //清除定時執行
                        clearInterval(timingTask);
                        $.ajax({
                            type: 'post',
                            url: "/risk/de***ess",
                            dataType : 'json',
                            success: function(result) {
                                $("#bulkImportChangesProcessor").hide();
                                if (parseInt(progress) == 100) {
                                    alert("批量導入修改狀態成功");
                                }
                                if (progress == "error") {
                                    alert("批量導入修改狀態失敗");
                                }
                                //獲取最新數據
                                window.location.href="/risk/re***ByParam";
                            }
                        });
                    }
                }, 1000)
            }else {
                $("#bulkImportChangesProcessor").hide();
                alert(obj.rspMsg);
                window.location.href="/risk/re***ByParam";
            }
        }
    });
}

解釋:點擊確認導入文件後成功後開啓定時任務每一秒(一千毫秒)訪問一次後臺獲取redis存放的進度,返回更新進度條,如果更新完成或者更新失敗(根據後臺返回的數據決定)則停止定時任務顯示相應的信息並刷新頁面。獲取最新數據。

後臺控制層

/**
	 * 退單管理批量修改狀態導入文件
	 * @param importFile
	 * @return
	 */
	@ResponseBody
	@RequestMapping("/bulk***es")
	public Map<String,Object> bulk***es(MultipartFile importFile){
    	log.info("退單管理批量修改狀態導入文件,傳入參數:"+importFile);
		Map<String,Object> map = new HashMap<>();
		List<Bulk***esEntity> fromExcel = null;
		try{
            //使用工具類導入轉成list
			String[] header = {"sy***um","t***mt","ha***ult","re***nd","sy***nd","r**k"};
			fromExcel = importExcelUtil.importDataFromExcel(importFile, header, BulkImportChangesEntity.class);
			if (fromExcel.size()==0){
				map.put("rspCode","99");
				map.put("rspMsg","導入數據不能爲空");
				return map;
			}
		}catch (Exception e){
			map.put("rspCode","99");
			map.put("rspMsg","導入操作表失敗,請注意數據列格式");
			return map;
		}
		try {
			//這裏會對list集合中的數據進行處理

			log.info("調用服務開始,參數:"+JSON.toJSONString(fromExcel));
			//String url = p4_zuul_url+"/***/ri***eat/bu***nges";
			String url = p4_zuul_url+"/***-surpass/ri***eat/bu***nges";
			String result = HttpClientUtil.doPost(url,JSON.toJSONString(fromExcel));
			log.info("調用服務結束,返回數據:"+result);
			if (result != null){
				map = JSONObject.parseObject(result, Map.class);
				log.info("批量修改狀態導入:"+JSON.toJSONString(map));
			}
		}catch (Exception e){
			map.put("rspCode","99");
			map.put("rspMsg","導入操作表失敗");
			log.info("bu***es exception",e);
			return map;
		}
		return map;
	}

	/**
	 * 獲取退單管理批量修改狀態導入文件進度條進度
	 * @return
	 */
	@ResponseBody
	@RequestMapping("/t***sk")
	public Map<String,Object> t***sk(){
		Map<String,Object> map = new HashMap<>();
        //獲取redis值
		String progress = HttpClientUtil.doGet(
				p4_zuul_url + "/" + p4_redis + "/redis***ler/get?key=progressSchedule");
		if (progress != null){
			map = JSONObject.parseObject(progress, Map.class);
			log.info("進度條進度:"+JSON.toJSONString(map));
			map.put("progressSchedule",progress);
		}else {
			HttpClientUtil.doGet(
					p4_zuul_url + "/" + p4_redis + "/redis***ler/del?key=progressSchedule");
		}
		return map;
	}

	/**
	 * 清除redis進度條進度
	 * @return
	 */
	@ResponseBody
	@RequestMapping("/de***ess")
	public Map<String,Object> de***ess(){
		Map<String,Object> map = new HashMap<>();
		String progress = HttpClientUtil.doGet(
				p4_zuul_url + "/" + p4_redis + "/redis***ler/del?key=progressSchedule");
		if (progress != null){
			map = JSONObject.parseObject(progress, Map.class);
			log.info("返回數據:"+JSON.toJSONString(map));
		}
		return map;
	}

導入時調用第一個bulk***es方法,定時任務調用t***sk方法,導入完成或發生錯誤調用de***ess方法刪除redis數據,避免佔用資源。

服務層

@Async//開啓異步
	@Transactional(rollbackFor = Exception.class)//事務回滾級別
	@Override
	public void bulkImportChanges(List<BulkImportChangesParam> list) {
		//初始化進度
		Double progressBarSchedule = 0.0;
		redisClient.set("progressSchedule", progressBarSchedule + "");//存redis
		try {
			for (int i = 1; i <= list.size(); i++) {
				RiskRetreatEntity entity = riskRetreatMapper.selectRetreatListBySysRefNum(list.get(i-1).getSysRefNum());
				if (entity == null){
					//查詢結果爲空直接進行下次循環不拋出
					continue;
				}
				//實體封裝
                ···
                //更新
				riskRetreatMapper.updateRetreatByImport(entity);
				//計算修改進度並存放redis保存(1.0 / list.size())爲一條數據進度
				progressBarSchedule = (1.0 / list.size()) * i*100;
				redisClient.set("progressSchedule", progressBarSchedule+"");
				if (i==list.size()){
					redisClient.set("progressSchedule", "100");
				}
			}
		}catch (Exception e){
			//當發生錯誤則清除緩存直接拋出回滾
			redisClient.set("progressSchedule","error");
			log.info("導入更新錯誤,回滾");
			log.info("bulkImportChanges exception:",e);
			throw e;
		}
	}

每更新一條數據存放進度,當發生錯誤則進行回滾。如果開啓異步則需要在啓動類添加註解@EnableAsync

@EnableAsync
···//其他註解
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

結果樣式

結尾

這次結合了前端的定時任務,後臺事務以及異步,總的來說還是一次🙅‍不錯的體驗。

優秀的代碼只能通過時間的沖刷纔會顯現的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章