1.需求
- 對(duì)于成千上萬(wàn)數(shù)據(jù)量的excel導(dǎo)入,后臺(tái)處理耗時(shí)長(zhǎng),體驗(yàn)差.需要實(shí)時(shí)展示當(dāng)前導(dǎo)入的進(jìn)度,提高使用體驗(yàn)
2.實(shí)現(xiàn)思路
- 采用多線程進(jìn)行實(shí)現(xiàn)
- 在導(dǎo)入開始執(zhí)行前,生成一個(gè)uuid和進(jìn)度對(duì)象,儲(chǔ)存到靜態(tài)Map中
- 使用一個(gè)新線程執(zhí)行導(dǎo)入,導(dǎo)入執(zhí)行時(shí),將導(dǎo)入的進(jìn)度信息放到uuid對(duì)應(yīng)的對(duì)象里
- 將uuid返回給前端.請(qǐng)求結(jié)束
-前端循環(huán)發(fā)請(qǐng)求,從后臺(tái)獲取uuid對(duì)應(yīng)的進(jìn)度對(duì)象,將展示到頁(yè)面上
3.頁(yè)面實(shí)現(xiàn)效果(僅供參考)

正在導(dǎo)入

導(dǎo)入完成
4.定義用于存儲(chǔ)導(dǎo)入進(jìn)度的對(duì)象
- 將進(jìn)度的一些常用信息進(jìn)行定義,同時(shí)聲明一個(gè)靜態(tài)Map,用于存儲(chǔ)所有進(jìn)度信息
/**
* 用于存儲(chǔ)學(xué)生信息導(dǎo)入的進(jìn)度信息
* @author authstr
* @time 2019年10月24日16:56:21
*/
public class ImportAsynInfo {
//用于存儲(chǔ)所有的導(dǎo)入進(jìn)度信息
public static Map<String,ImportAsynInfo> allAsynInfo=new HashMap<String,ImportAsynInfo>();
//提示信息或 異常信息
private String msg;
//數(shù)據(jù)總數(shù)
private Integer totality=0;
//已處理的數(shù)據(jù)條數(shù)
private Integer doneSum=0;
//失敗的數(shù)據(jù)條數(shù)
private Integer errorSum=0;
//成功的數(shù)據(jù)條數(shù)
private Integer successSum=0;
//錯(cuò)誤文件的路徑
public String errorFilePath;
//導(dǎo)入是否結(jié)束
public Boolean isEnd= false;
/**
* 創(chuàng)建一個(gè)進(jìn)度信息,并獲取對(duì)應(yīng)的uuid
* @return
*/
public static String createAsynInfo(){
ImportAsynInfo asynInfo=new ImportAsynInfo();
String uuid=UUID.randomUUID().toString().replace("-","");
allAsynInfo.put(uuid,asynInfo);
return uuid;
}
/**
* 通過(guò)uuid獲取進(jìn)度信息
* @param uuid
* @return
*/
public static ImportAsynInfo getAsynInfo(String uuid){
return allAsynInfo.get(uuid);
}
/**
* 通過(guò)uuid刪除對(duì)應(yīng)的進(jìn)度信息
* @param uuid
* @return
*/
public static void deleteAsynInfo(String uuid){
allAsynInfo.remove(uuid);
}
/**
* uuid對(duì)應(yīng)的進(jìn)度 已處理的數(shù)據(jù)條數(shù)+1
* @param uuid
*/
public static void doneSumAddOne(String uuid){
ImportAsynInfo asynInfo= getAsynInfo(uuid);
asynInfo.setDoneSum(asynInfo.getDoneSum()+1);
}
/**
* uuid對(duì)應(yīng)的進(jìn)度 失敗的數(shù)據(jù)條數(shù)+1
* @param uuid
*/
public static void errorSumAddOne(String uuid){
ImportAsynInfo asynInfo= getAsynInfo(uuid);
asynInfo.setErrorSum(asynInfo.getErrorSum()+1);
}
/**
* uuid對(duì)應(yīng)的進(jìn)度 成功的數(shù)據(jù)條數(shù)+1
* @param uuid
*/
public static void successSumAddOne(String uuid){
ImportAsynInfo asynInfo= getAsynInfo(uuid);
asynInfo.setSuccessSum(asynInfo.getSuccessSum()+1);
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Integer getTotality() {
return totality;
}
public void setTotality(Integer totality) {
this.totality = totality;
}
public Integer getDoneSum() {
return doneSum;
}
public void setDoneSum(Integer doneSum) {
this.doneSum = doneSum;
}
public Integer getErrorSum() {
return errorSum;
}
public void setErrorSum(Integer errorSum) {
this.errorSum = errorSum;
}
public Integer getSuccessSum() {
return successSum;
}
public void setSuccessSum(Integer successSum) {
this.successSum = successSum;
}
public String getErrorFilePath() {
return errorFilePath;
}
public void setErrorFilePath(String errorFilePath) {
this.errorFilePath = errorFilePath;
}
public Boolean getEnd() {
return isEnd;
}
public void setEnd(Boolean end) {
isEnd = end;
}
}
5.Controller層開啟線程進(jìn)行導(dǎo)入
- 獲取線程池,通過(guò)線程池來(lái)啟動(dòng)線程來(lái)執(zhí)行導(dǎo)入,并將uuid傳入
- 文件需要通過(guò)輸入流來(lái)傳入,直接傳文件對(duì)象,可能無(wú)法讀取到文件
- 后面定義一個(gè)接口,來(lái)向前端返回指定uuid對(duì)應(yīng)的進(jìn)度對(duì)象.并對(duì)進(jìn)度對(duì)象進(jìn)行清理
@RestController
@RequestMapping("student_import/v1")
public class StudentImportController extends AbstractAPIController {
@Autowired
StudentImportServiceImpl studentImportService;
private ExecutorService executor = Executors.newCachedThreadPool() ;
//下載導(dǎo)入模板
@RequestMapping("/excelExport")
public void excelExport(HttpServletResponse response) {
studentImportService.excelExport(response);
}
//數(shù)據(jù)導(dǎo)入處理
@RequestMapping("/save_excel_auto_studentno")
public Map saveExcelStudentno(HttpServletResponse response, @RequestParam("file") MultipartFile file){
Map m = new HashMap<>();
String uuid=ImportAsynInfo.createAsynInfo();
try {
final InputStream inputStream = file.getInputStream();
executor.submit(new Runnable(){
@Override
public void run() {
try {
studentImportService.saveExcel_auto_studentno(response, inputStream,uuid);
}catch(Exception e) {
e.printStackTrace();
ImportAsynInfo.getAsynInfo(uuid).setMsg(e.getMessage());
ImportAsynInfo.getAsynInfo(uuid).setEnd(true);
throw new Exception("無(wú)法進(jìn)行導(dǎo)入!");
}
}
});
} catch (IOException e) {
e.printStackTrace();
}
m.put("uuid",uuid);
return m;
}
//下載導(dǎo)入的錯(cuò)誤文件
@RequestMapping("downloadErrorExcel")
public void downloadErrorExcel(HttpServletResponse response, String fileName){
studentImportService.downloadErrorExcel(response, fileName);
}
//獲取導(dǎo)入的進(jìn)度
@RequestMapping("get_import_plan")
public Map get_import_plan(String uuid) {
Map m = new HashMap<>();
ImportAsynInfo asynInfo=ImportAsynInfo.getAsynInfo(uuid);
//如果導(dǎo)入結(jié)束,復(fù)制進(jìn)度對(duì)象進(jìn)行返回,將儲(chǔ)存的進(jìn)度對(duì)象刪除
if(asynInfo!=null&&asynInfo.getEnd()){
ImportAsynInfo newAsynInfo=new ImportAsynInfo();
newAsynInfo.setEnd(asynInfo.getEnd());
newAsynInfo.setMsg(asynInfo.getMsg());
newAsynInfo.setErrorFilePath(asynInfo.getErrorFilePath());
newAsynInfo.setTotality(asynInfo.getTotality());
newAsynInfo.setDoneSum(asynInfo.getDoneSum());
newAsynInfo.setErrorSum(asynInfo.getErrorSum());
newAsynInfo.setSuccessSum(asynInfo.getSuccessSum());
ImportAsynInfo.deleteAsynInfo(uuid);
asynInfo=newAsynInfo;
}
m.put("data",asynInfo);
return m;
}
6. service進(jìn)行執(zhí)行導(dǎo)入
- 在導(dǎo)入過(guò)程中,設(shè)置導(dǎo)入進(jìn)度信息
- 其他業(yè)務(wù)相關(guān)代碼已省略
@Service
public class StudentImportServiceImpl extends AbstractService implements StudentImportService {
@Override
public void excelExport(HttpServletResponse response) {
//導(dǎo)入模板下載 略
}
@Override
public void downloadErrorExcel(HttpServletResponse response, String fileName) {
//下載錯(cuò)誤文件 略
}
@Transactional
@Override
public Map<String, Object> saveExcel(HttpServletResponse response, InputStream inputStream,String uuid) {
//其他代碼...
//獲取excel導(dǎo)入數(shù)據(jù)數(shù)量后
ImportAsynInfo.getAsynInfo(uuid).setTotality( 數(shù)量 );
//其他代碼...
for (int i = 0; i < 數(shù)量; i++) {
//其他代碼...
//在一條數(shù)據(jù)處理結(jié)束后
ImportAsynInfo.doneSumAddOne(uuid);
//其他代碼...
if(數(shù)據(jù)有錯(cuò)誤){
//其他代碼...
ImportAsynInfo.errorSumAddOne(uuid);
}else{
//其他代碼...
ImportAsynInfo.successSumAddOne(uuid);
}
}
//其他代碼...
//錯(cuò)誤文件創(chuàng)建后
ImportAsynInfo.getAsynInfo(uuid).setErrorFilePath(errorFileName);
//其他代碼...
//導(dǎo)入完成后
ImportAsynInfo.getAsynInfo(uuid).setEnd(true);
}
}
7.顯示進(jìn)度條頁(yè)面
- 在網(wǎng)上沒找到方便的可以顯示多種顏色的進(jìn)度條,這里曲線救國(guó),采用ECharts的餅圖來(lái)顯示進(jìn)度信息(具體樣式可以根據(jù)需求調(diào)整)
- 一些js與樣式 已略過(guò)
<!doctype html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta content="always" name="referrer">
<script src="/.../echarts/echarts.min.js"></script>
</head>
<body>
<div class="J_conWarp g-lr-mg g-warning-box">
<h4 class="g-title"> <a href="javascript:history.back()" style="font-size: 16px; color: black; " >< 返回</a></h4>
<div class="J_containerWarp">
<div class="container-fluid g-t-mg2">
<div class="row" >
<div class="col-xs-12" >
<div id="import_file">
<form class="form-horizontal" role="form" id="excelExportForm">
</form>
<form class="form-horizontal" role="form" id="downloadErrorExcelForm">
<input type="hidden" name="fileName" id="fileName" value="" />
</form>
<form class="form-horizontal" role="form" id="form1">
<div class="form-group ">
<label class="col-xs-2 control-label"><em class="text-red">*</em>導(dǎo)入文件</label>
<div class="col-xs-10">
<input type="file" id="file" name="file" class="form-control" style="width:400px" accept=".xls" />
</div>
</div>
</form>
<div class="form-group ">
<label class="col-xs-2 control-label"></label>
<a class="gbn gbn-m" href="javascript:" onclick="E.excelExport();" >下載導(dǎo)入模板</a>
<a class="gbn gbn-m" href="javascript:" onclick="E.saveExcel();" >導(dǎo)入</a>
<a class="gbn gbn-m gbn-red" href="javascript:" onclick="history.back();" >返回</a>
</div>
</div>
<!-- 為 ECharts 準(zhǔn)備一個(gè)具備大?。▽捀撸┑?DOM -->
<div id="main" style="width:100%;height:550px;"></div>
<div id="downloadErrorExcelA"class="col-xs-12" style="height:150px;width:100%;display:none">
<div class="col-xs-4"></div>
<div class="form-group ">
<a class="gbn gbn-m gbn-red" href="javascript:" onclick="E.downloadErrorExcel();" >下載錯(cuò)誤文件</a>
<a id="quxiao" class="gbn gbn-m" href="javascript:" onclick="E.quxiao();" >取消</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
window.onload = window.onresize = function() {
$(".J_containerWarp").height($(window).height() - 60);
$(".J_containerWarp").niceScroll({});
}
$.ajaxSettings.async=true;
var uuid=null;
var setInterval_id=null;
var myChart = echarts.init(document.getElementById('main'));
var option = {
title : {
text: '正在進(jìn)行導(dǎo)入中...',
subtext: '當(dāng)前進(jìn)度',
x:'center'
},
tooltip : {
trigger: 'item',
formatter: "{a} <br/> : {c} (u0z1t8os%)"
},
legend: {
orient: 'vertical',
left: 'right',
data: ['導(dǎo)入成功','導(dǎo)入失敗','未處理']
},
series : [
{
name: '導(dǎo)入進(jìn)度',
type: 'pie',
// radius : '55%',
radius: ['50%', '70%'],
center: ['50%', '60%'],
data:[
{value:0, name:'導(dǎo)入成功'},
{value:0, name:'導(dǎo)入失敗'},
{value:100, name:'未處理'}
],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
color: ['#2ECC71','#E67E22','#BDC3C7'],
}
]
};
var E = {
excelExport : function() {
$("form[id=excelExportForm]").attr("action",
"${request.contextPath}/student_import/v1/excelExport");
$("#excelExportForm").submit();
},
saveExcel : function() {
var file = $("#file").val();
if(!file){
Message.error("導(dǎo)入的文件不能為空");
return ;
}
//循環(huán)獲取進(jìn)度信息
setInterval_id=setInterval(E.getAsynInfo,500);
myChart.setOption(option);
var index = layer.load(1);
var formData = new FormData();
//隱藏導(dǎo)入?yún)^(qū)域和錯(cuò)誤文件下載區(qū)域,顯示進(jìn)度條區(qū)域
$("#import_file").hide();
$("#main").show();
$("#downloadErrorExcelA").hide();
formData.append('file', $('#file')[0].files[0]);
$.ajax({
type : "POST",
url : "${request.contextPath}/student_import/v1/save_excel",
data : formData,
async: false,
cache: false,
contentType: false,
processData: false,
success : function(o) {
if(o.code==1){
//設(shè)置uuid
uuid=o.uuid;
} else {
Message.error(o.msg);
}
}
});
},
downloadErrorExcel : function() {
$("form[id=downloadErrorExcelForm]").attr("action",
"${request.contextPath}/student_import/v1/downloadErrorExcel");
$("#downloadErrorExcelForm").submit();
},
getAsynInfo:function(){
//如果uuid存在,進(jìn)行獲取數(shù)據(jù)
if(uuid!=null){
$.post("${request.contextPath}/student_import/v1/get_plan",{"uuid":uuid},function(o){
console.log(o);
//如果獲取到了數(shù)據(jù)
if(o.code==1&&o.data!=null){
// 使用指定的配數(shù)據(jù)顯示圖表。
option.title.subtext="當(dāng)前進(jìn)度 [共"+o.data.totality+"]條";
option.series[0].data[0].value=o.data.successSum;
option.series[0].data[1].value=o.data.errorSum;
option.series[0].data[2].value=o.data.totality-o.data.doneSum;
myChart.setOption(option);
//如果導(dǎo)入結(jié)束了
if(o.data.isEnd){
option.title.text="導(dǎo)入完成";
myChart.setOption(option);
clearInterval(setInterval_id);
//如果有錯(cuò)誤數(shù)據(jù),展示錯(cuò)誤文件的下載
if(o.data.totality>0&&o.data.errorSum>0){
$("#fileName").val(o.data.errorFilePath);
$("#downloadErrorExcelA").show();
}
//如果導(dǎo)入中出現(xiàn)的異常
if(o.data.msg!=null){
$("#import_file").show();
$("#main").hide();
Message.error(o.data.msg);
}else{
Message.success("導(dǎo)入結(jié)束,"+o.data.successSum+"條數(shù)據(jù)導(dǎo)入成功,"+o.data.errorSum+"條數(shù)據(jù)導(dǎo)入失敗");
}
}
}else{
Message.error(o.msg);
}
});
}
},
quxiao:function () {
$("#import_file").show();
$("#main").hide();
$("#downloadErrorExcelA").hide();
uuid=null;
}
}
</script>
</html>
8.還未完成的功能
- 對(duì)線程進(jìn)行處理和關(guān)閉
- 增加取消導(dǎo)入功能
- 導(dǎo)入進(jìn)度對(duì)象中,增加一個(gè) 是否取消 的標(biāo)識(shí)
- 在導(dǎo)入的每次循環(huán)中,判斷這個(gè)標(biāo)識(shí),如果true,跳出整個(gè)導(dǎo)入方法,并回滾事務(wù)
- 增加中斷導(dǎo)入功能
- 導(dǎo)入進(jìn)度對(duì)象中,增加一個(gè) 是否中斷 的標(biāo)識(shí)
- 在導(dǎo)入的每次循環(huán)中,判斷這個(gè)標(biāo)識(shí),如果true,直接提交事務(wù),將之前的錯(cuò)誤數(shù)據(jù)信息和之后還未處理的數(shù)據(jù)合并成一個(gè)excel給前端,然后跳出導(dǎo)入方法