各位看官可以關注博主個人博客,瞭解更多信息。
作者: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);
}
}
結果樣式
結尾
這次結合了前端的定時任務,後臺事務以及異步,總的來說還是一次🙅不錯的體驗。
優秀的代碼只能通過時間的沖刷纔會顯現的