java excel導(dǎo)入獲取實(shí)時(shí)進(jìn)度

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)入方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容