【Excel_To_DB】SpringBoot+EasyPoi+Redis消息队列实现Excel批量异步导入数据库(三)

版权声明:本文为博主原创文章,转载请标明出处 https://blog.csdn.net/YangDongChuan1995/article/details/79290027

【Excel_To_DB】SpringBoot+EasyPoi+Redis消息队列实现Excel批量异步导入数据库(一)
【Excel_To_DB】SpringBoot+EasyPoi+Redis消息队列实现Excel批量异步导入数据库(二)
【Excel_To_DB】SpringBoot+EasyPoi+Redis消息队列实现Excel批量异步导入数据库(三)
【效果演示】:JavaWeb毕业设计项目-足球队管理系统(四)引入Excel_To_DB项目+源码
【码云地址】:https://gitee.com/ydc_coding

ExcelModel.java:

package com.ydc.excel_to_db.domain;

import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;

import javax.validation.constraints.Max;
import javax.validation.constraints.Pattern;
import java.util.Date;

/**
 * @Description: 为了方便扩展,这里的字段命名规则为col[列]+1[列数]
 * 例如:col1代表的就是第一列的数据,通过@Excel(name=?)注解进行区分
 * @Author: 杨东川【http://blog.csdn.net/yangdongchuan1995】
 * @Date: Created in  2018-2-6
 */
@Data
public class ExcelModel {
    // 官方文档 http://easypoi.mydoc.io/
    @Excel(name = "序号")
    @NotBlank(message = "该字段不能为空")
    @Max(value = 1000)
    private String col1;
    @Excel(name = "姓名")
    @Pattern(regexp = "[\\u4E00-\\u9FA5]{2,5}", message = "姓名中文2-5位")
    private String col2;
    @Excel(name = "性别")
    private String col3;
    @Excel(name = "出生年月", format = "yyyy.MM.dd")
    private Date col4;
    @Excel(name = "民族")
    private String col5;
    @Excel(name = "籍贯")
    private String col6;
    @Excel(name = "文化程度")
    private String col7;
    @Excel(name = "参工时间", format = "yyyy.MM")
    private Date col8;
    @Excel(name = "政治面貌")
    private String col9;
    @Excel(name = "职务")
    private String col10;
    @Excel(name = "现处室时间", format = "yyyy.MM")
    private Date col11;
    @Excel(name = "任现职位时间", format = "yyyy.MM")
    private Date col12;
    @Excel(name = "任现职级时间", format = "yyyy.MM")
    private Date col13;
    @Excel(name = "职称")
    private String col14;
    @Excel(name = "执业资格")
    private String col15;
    @Excel(name = "进局时间", format = "yyyy.MM")
    private Date col16;
    @Excel(name = "实务导师")
    private String col17;
    @Excel(name = "备注")
    private String col18;

    public String getCol2() {
        return col2;
    }
}

说明:
- 这里使用了lombok的@Data注解与easypoi的@Excel注解,还有一些其他格式校验的注解。
- 这里我把字段名设置命名规则设置为col[列]+1[列数],例如:col1代表的就是第一列的数据,通过@Excel(name=?)注解进行区分。

为什么不直接写成例如姓名-username,性别-sex这类对应的中文含义的英文字母呢?

  • 这是因为考虑到导入的数据是可变的,有可能这次需要将“全市审计人员花名册”导入数据库,下次就变换成将“审计机关全覆盖情况”导入数据库。
  • 与其每次变动都更改字段名不如直接把字段名按一定规则设置好,后期只需要通过改变@Excel注解中的值就可以了,此外对应的Mapper中的sql语句也只需要更改操作对应数据库表的字段名即可。

Lombok与EasyPoi环境依赖:

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- easypoi -->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-base</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-web</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-annotation</artifactId>
            <version>3.0.3</version>
        </dependency>

下面开始贴从上传Excel至返回第一层操作结果的代码及业务逻辑说明(Excel数据格式校验结果)
importExcel.html中上传方法js:

    layui.use('upload', function () {
        var $ = layui.jquery
            , upload = layui.upload;
        //拖拽上传
        upload.render({
            elem: '#test10'
            , url: '/doImport/'
            , method: 'post' //默认就是post
            , accept: 'file'
            , size: 20 * 1024
            , before: function (obj) { //obj参数包含的信息,跟 choose回调完全一致,可参见上文。
                layer.load(); //上传loading
            }
            , done: function (res, index, upload) {
                layer.closeAll('loading'); //关闭loading
                //假设code=0代表上传成功
                if (res.code == 0) {
                    // 0 : 上传文件格式通过
                    if (res.data.code == 0) {
                        //询问框
                        layer.confirm(res.data.msg, {
                            icon: 6,
                            title: '恭喜您,数据上传成功!',
                            btn: ['需要', '不需要'] //按钮
                        }, function () {
                            checkUndoSize();
                        }, function () {
                            layer.confirm('真的不需要查看数据同步结果吗?', {
                                icon: 3,
                                time: 10000, //20s后自动关闭
                                btn: ['查看结果', '是的,不需要']
                            }, function () {
                                checkUndoSize();
                            }, function () {
                                layer.msg('不开心...', {icon: 5});
                            });
                        });
                    } else { // 50X: 上传文件格式未通过(报异常时)
                        layer.alert(res.data.msg, {icon: 5});
                    }
                } else {
                    layer.alert("当前系统有误,请尝试与管理员联系", {icon: 5});
                }
            }
            , error: function (index, upload) {
                layer.closeAll('loading'); //关闭loading
                layer.alert("当前系统有误,请尝试与管理员联系", {icon: 5});
            }
        });
    });

说明:设置文件类型(accept:file)为普通文件,大小为20M(size:20*1024),上传前展示出加载层(layer.load()),这里把后台传递回前台的数据进行了封装,如下图:
Result.java:

package com.ydc.excel_to_db.result;
/**
 * @Description: 封装返回给前台的数据
 */
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    /**
     * @Description: 成功时候的调用
     * @Param: [data]
     * @Retrun: com.ydc.excel_to_db.result.Result<T>
     */
    public static <T> Result<T> success(T data) {
        return new Result<T>(data);
    }

    /**
     * @Description: 失败时候的调用
     * @Param: [codeMsg]
     * @Retrun: com.ydc.excel_to_db.result.Result<T>
     */
    public static <T> Result<T> error(CodeMsg codeMsg) {

        return new Result<T>(codeMsg);
    }

    private Result(T data) {
        this.data = data;
    }

    private Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Result(CodeMsg codeMsg) {
        if (codeMsg != null) {
            this.code = codeMsg.getCode();
            this.msg = codeMsg.getMsg();
        }
    }


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

另外对需要返回消息的数据也进行封装,如下图:

package com.ydc.excel_to_db.result;
/**
 * @Description: 封装需要返回的异常信息及自定义信息
 * @Author: 杨东川【http://blog.csdn.net/yangdongchuan1995】
 * @Date: Created in  2018-2-6
 */
public class CodeMsg {

    private int code;
    private String msg;
    public static CodeMsg FilE_ERROR = new CodeMsg(501, "上传文件类型异常");
    public static CodeMsg Excel_FORMAT_ERROR = new CodeMsg(502, "上传Excel表格格式有误<br>或者<br> 上传Excel数据为空");
    public static CodeMsg SERVER_ERROR = new CodeMsg(503, "服务端异常,请您联系管理员查看服务器日志");

    private CodeMsg(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * @Description: 返回用户自定义的消息(非异常信息)
     * @Param: [msg]
     * @Retrun: com.ydc.excel_to_db.result.CodeMsg
     */
    public static CodeMsg userDefined(String msg) {
        return new CodeMsg(0, msg);
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }


    @Override
    public String toString() {
        return "CodeMsg [code=" + code + ", msg=" + msg + "]";
    }


}

后台返回数据的各种情况可以参考:【Excel_To_DB】SpringBoot+EasyPoi+Redis消息队列实现Excel批量异步导入数据库(一)

后台对应的代码:
ExcelToDBController.java

    /**
     * @Description: 异步接收上传的Excel文件并传递至Service层
     * @Param: [file]
     * @Retrun: com.ydc.excel_to_db.result.Result<com.ydc.excel_to_db.result.CodeMsg>
     */
    @PostMapping("/doImport")
    @ResponseBody
    public Result<CodeMsg> doImport(@RequestParam("file") MultipartFile file) {
        CodeMsg codeMsg = importService.verfiyExcel(file);
        // 返回封装好的结果集
        return Result.success(codeMsg);
    }

ImportServiceImpl.java

    /**
     * @Description: 
     * 1.校验上传的文件类型及其对应的数据
     * 2.将通过(1)的数据转换为实体对象集合
     * 3.清空redis中的部分旧数据cleanCache()
     * 4.将对象集合传入cacheAndPublish()中
     * 5.封装本次数据校验结果并返回
     * @Param: [file]
     * @Retrun: com.ydc.excel_to_db.result.CodeMsg
     */
    @Override
    public CodeMsg verfiyExcel(MultipartFile file) {
        // 截取原始文件名里的类型名(最后一个“.”后的数据)
        String fileName = file.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf("."), fileName.length());
        // 判断是否属于Excel表格的类型,不属于则直接返回“上传文件类型异常”(CodeMsg.FilE_ERROR)
        if (!".xls".equals(suffix) && !".xlsx".equals(suffix)) {
            return CodeMsg.FilE_ERROR;
        }
        ImportParams importParams = new ImportParams();
        // 对上传的数据进行处理,详情信息请看-com.ydc.excel_to_db.handler.ExcelModelHandler;
//        IExcelDataHandler<ExcelModel> handler = new ExcelModelHandler();
//        handler.setNeedHandlerFields(new String[] {"姓名"});
//        importParams.setDataHanlder(handler);
        // 是否需要验证
        importParams.setNeedVerfiy(true);
        try {
            ExcelImportResult<ExcelModel> result = ExcelImportUtil.importExcelMore(file.getInputStream(), ExcelModel.class,
                    importParams);
            // 当结果中通过校验的数据(result.getList())为空时
            // 直接返回“上传Excel表格格式有误<br>或者<br> 上传Excel数据为空”(CodeMsg.Excel_FORMAT_ERROR)
            if (result.getList().size() == 0 || result.getList().get(0) == null) {
                return CodeMsg.Excel_FORMAT_ERROR;
            }
            // 清空redis中的部分旧数据
            cleanCache();
            // 将参数result中的部分数据存入redis中,并把格式校验成功的数据发布至对应频道中
            cacheAndPublish(result);
            int succSize = result.getList().size();
            int failSize = result.getFailList().size();
            String msg = "在Excel数据格式校验环节中,共获得有效数据" + (succSize + failSize) + "条</br>其中," + succSize
                    + "条数据通过格式校验," + failSize + "条数据未通过格式校验 </br> 是否需要查看完整数据同步结果信息?";
            return CodeMsg.userDefined(msg);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        // 当以上过程中抛出异常时,返回"服务端异常,请您联系管理员查看服务器日志"(CodeMsg.SERVER_ERROR)
        return CodeMsg.SERVER_ERROR;
    }

    /**
     * @Description: 清空redis中的部分旧数据
     * @Param: []
     * @Retrun: void
     */
    @Override
    @Transactional
    public void cleanCache() {
        List<String> keyList = new ArrayList<>(10);
        keyList.add(Constant.failToDBKey);
        keyList.add(Constant.succSizeTempKey);
        keyList.add(Constant.failListKey);
        keyList.add(Constant.failSizeKey);
        keyList.add(Constant.succSizeKey);
        redisDao.cleanCache(keyList);
    }

    /**
     * @Description: 将参数result中的部分数据存入redis中,并把格式校验成功的数据发布至对应频道中
     * @Param: [result]
     * @Retrun: void
     */
    @Override
    @Transactional
    public void cacheAndPublish(ExcelImportResult<ExcelModel> result) {
        // 通过校验的数据
        List<ExcelModel> successList = result.getList();
        // 未通过校验的数据
        List<ExcelModel> failList = result.getFailList();
        int succSize = successList.size();
        int failSize = failList.size();
        // 将未通过校验的数据存入redis中
        redisDao.setStringKey(Constant.failListKey, failList);
        redisDao.setStringKey(Constant.failSizeKey, failSize);
        // 将通过校验的数据存入redis中
        redisDao.setStringKey(Constant.succSizeKey, succSize);
        // succSizeTempKey 用于判断消息队列中未消费数据的大小
        redisDao.setStringKey(Constant.succSizeTempKey, succSize);
//        redisDao.setStringKey(Constant.successListKey,successList);
        // 发布消息
        redisDao.publish(Constant.receiveList, successList);
    }

说明:
1. 校验上传的文件类型及其对应的数据
2. 将通过(1)的数据转换为实体对象集合
3. 清空redis中的部分旧数据cleanCache()
4. 将对象集合传入cacheAndPublish()中
5. 封装本次数据校验结果并返回

其中,cacheAndPublish()的作用:
- 将通过校验的数据大小(size)存入redis中
- 将队列中未消费数据的大小(size)存入redis中
- 将未通过校验的数据存入redis中
- 将未通过校验的数据大小(size)存入redis中
- 将通过校验的数据当做消息,发布至对应的频道中

以上就是第一层操作及返回对应格式校验结果的代码.
下面是前端中,js轮询机制持续查询redis中未消费数据大小的代码:
importExcel.html:

    // 检测未被消费的数据大小
    function checkUndoSize() {
        layer.load();
        $.ajax({
            url: "/getUndoSize",
            type: "GET",
            success: function (data) {
                if (data.code == 0) {
                    var result = data.data;
                    if (result > 0) {//继续轮询
                        layer.closeAll();
                        layer.msg("当前仍有" + result + "条数据未处理,请稍等...");
                        setTimeout(function () {
                            checkUndoSize();
                        }, 1000);
                    } else {
                        layer.closeAll('loading'); //关闭loading
                        layer.confirm("数据同步已完成,是否立即查看结果?", {icon: 1, btn: ["立即查看", "稍等"]},
                            function () {
                                layer.closeAll();
                                toResultHtml();
                            },
                            function () {
                                layer.msg("五秒后再次提醒...", {icon: 6});
                                setTimeout(function () {
                                    checkUndoSize();
                                }, 5000);
                            });
                    }
                } else {
                    layer.msg(data.msg);
                }
            },
            error: function () {
                layer.msg("客户端请求有误");
            }
        });
    }

ExcelToDBController.java:

    /**
     * @Description: 异步获取当前消息队列中未被消费的队列大小
     * @Param: []
     * @Retrun: com.ydc.excel_to_db.result.Result<java.lang.Long>
     */
    @GetMapping("/getUndoSize")
    @ResponseBody
    public Result<Long> getUndoSize() {
        return Result.success(importService.getTempSize(Constant.succSizeTempKey));
    }

ImportServiceImpl.java:

    /**
     * @Description: 根据key值,返回redis中对应的结果
     * @Param: [key]
     * @Retrun: long
     */
    @Override
    public long getTempSize(String key) {
        return redisDao.getStringValue(key, long.class);
    }

这里涉及一个概念:ack机制——消息确认机制(Acknowledge)

首先来看RabbitMQ的ack机制:
- Publisher把消息通知给Consumer,如果Consumer已处理完任务,那么它将向Broker发送ACK消息,告知某条消息已被成功处理,可以从队列中移除。如果Consumer没有发送回ACK消息,那么Broker会认为消息处理失败,会将此消息及后续消息分发给其他Consumer进行处理(redeliver flag置为true)。
- 这种确认机制和TCP/IP协议确立连接类似。不同的是,TCP/IP确立连接需要经过三次握手,而RabbitMQ只需要一次ACK。
- 值的注意的是,RabbitMQ当且仅当检测到ACK消息未发出且Consumer的连接终止时才会将消息重新分发给其他Consumer,因此不需要担心消息处理时间过长而被重新分发的情况。

那这里怎么去模仿实现这样一个ack机制呢?
因为我们这里是对一个整体(Excel中通过校验的数据)进行操作,我们并不需要知道每一条数据是否消费的情况,只需要知道这个整体中仍未被消费的数据的大小(size),从而通过这个大小(size)来进行下一步的操作(继续轮询或者跳转至同步结果页面)。

具体实现的代码:
在ImportServiceImpl中的cacheAndPublish方法里,将通过校验的数据大小存入redis中(key为Constant.succSizeTempKey)

        // succSizeTempKey 用于判断消息队列中未消费数据的大小
        redisDao.setStringKey(Constant.succSizeTempKey, succSize);

在Receiver中的receiveSingle方法里,每消费一条信息就执行一次-1操作

        // 加上-1,其实也就是做减1操作
        redisDao.incrOrDecr(Constant.succSizeTempKey, -1);

以及上面importExcel.html中的checkUndoSize方法里的轮询机制,ajax异步获取redis中未被消费的数据的大小(size)。

第二层操作(数据同步数据库的结果)的业务逻辑及对应代码:
importExcel.html:

    // 跳转至同步结果页面
    function toResultHtml() {
        var index = layer.open({
            type: 2,
            content: '/toResult',
            area: ['320px', '195px'],
            maxmin: true
        });
        layer.full(index);
    }

ExcelToDBController.java:

    /**
     * @Description: 跳转至同步结果页面
     * @Param: []
     * @Retrun: java.lang.String
     */
    @GetMapping("/toResult")
    public String toResult() {
        return "importResult";
    }

importResult.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
    <title>Excel 数据同步</title>
    <link rel="stylesheet" type="text/css" th:href="@{/layui/css/layui.css}"/>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="echartsMain" style="width: 1918px; height: 904px;"></div>
</body>

<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery.min.js}"></script>
<script th:src="@{/echarts/js/echarts.js}"></script>
<script th:src="@{/echarts/js/dark.js}"></script>
<script>
    // 使用刚指定的配置项和数据显示图表。
    var myChart = echarts.init(document.getElementById('echartsMain'), 'dark');
    myChart.showLoading();
    $.ajax({
        url: "/getResultData",
        type: "GET",
        success: function (data) {
            myChart.hideLoading();
            buildEcharts(data);
        }
    })

    function buildEcharts(data) {
        var resultData = data.data;
        myChart.setOption({
            title: {
                text: '数据同步结果统计图',
                subtext: 'Excel表格导入数据库',
                x: 'center',
                textStyle: {
                    fontWeight: 'bold',
                    fontSize: 25
                },
                subtextStyle: {
                    fontSize: 16
                }

            },
            tooltip: {
                trigger: 'item',
                formatter: "{a} {b} :{d}%",
                textStyle: {
                    fontWeight: 'bold',
                    fontSize: 14
                }
            },
            legend: {
                itemWidth: 100,
                itemHeight: 30,
                orient: 'vertical',
                left: 'left',
                data: ['通过', '未通过'],
                textStyle: {
                    padding: 5,
                    fontSize: 20
                }
            },
            toolbox: {
                show: true,
                orient: 'vertical',
                left: 'right',
                top: 'center',
                feature: {
                    dataView: {readOnly: false},
                    restore: {},
                    saveAsImage: {}
                }
            },
            grid: [
                {x: '7.5%', y: '65%', width: '88%', height: '30%'},
            ],
            xAxis: [
                {
                    gridIndex: 0,
                    type: 'category',
                    axisTick: {
                        alignWithLabel: true
                    },
                    axisLabel: {
                        show: true,
                        textStyle: {
                            fontSize: '15'
                        }
                    },
                    data: ['数据格式校验结果', ' ', '数据导入数据库结果']
                },

            ],
            yAxis: [
                {gridIndex: 0, name: '学科', show: false},
            ],
            series: [
                {
                    name: '数据同步结果概览',
                    type: 'pie',
                    //roseType:'radius',
                    radius: '45%',
                    center: ['50%', '33%'],
                    data: [
                        {value: resultData.succToDBSize, name: '通过'},
                        {value: resultData.failToDBSize + resultData.failSize, name: '未通过'}
                    ],
                    label: {
                        normal: {
                            formatter: '{a} :{b} {c}条',
                            textStyle: {
                                color: '#ffffff',
                                fontSize: 14
                            }
                        }
                    },
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                },
                {
                    name: '数据格式校验结果',
                    type: 'pie',
                    //roseType:'radius',
                    radius: '22%',
                    center: ['22%', '81%'],
                    data: [
                        {value: resultData.succSize, name: '通过'},
                        {value: resultData.failSize, name: '未通过'}
                    ],
                    label: {
                        normal: {
                            formatter: '{c}条',
                            textStyle: {
                                color: '#ffffff',
                                fontSize: 14
                            }
                        }
                    },
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                },
                {
                    name: '数据导入数据库结果',
                    type: 'pie',
                    //roseType:'radius',
                    radius: '22%',
                    center: ['80%', '81%'],
                    data: [
                        {value: resultData.succToDBSize, name: '通过'},
                        {value: resultData.failToDBSize, name: '未通过'}
                    ],
                    label: {
                        normal: {
                            formatter: '{c}条',
                            textStyle: {
                                color: '#ffffff',
                                fontSize: 14
                            }
                        }
                    },
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                },

            ], color: ['#8dc1a9', '#dd6b66']
        })
        myChart.on('click', function (params) {
            // 也就是从上往下,从左往右 第二个图
            if (params.seriesIndex == 1) {
                layer.msg("正在导出格式校验未通过的数据,请稍等...", {icon: 1});
                window.location.href = "/doExport?failTypeName=format";
            } else if (params.seriesIndex == 2) { //第三个图
                layer.msg("正在导出同步数据库未通过的数据,请稍等...", {icon: 1});
                window.location.href = "/doExport?failTypeName=db";
            } else {
                layer.alert("点击左右子图表可分别下载对应未通过的数据....", {icon: 6});
            }
        });
    }
</script>
</html>

ExcelToDBController.java:

    /**
     * @Description: 异步获取同步结果页面中饼状图所需的数据
     * @Param: []
     * @Retrun: com.ydc.excel_to_db.result.Result<com.ydc.excel_to_db.vo.ExcelModelVo>
     */
    @GetMapping("/getResultData")
    @ResponseBody
    public Result<ExcelModelVo> getResultData() {
        ExcelModelVo resultData = importService.getResultData();
        return Result.success(resultData);
    }

ImportServiceImpl.java:

    /**
     * @Description: 获取同步结果页面中饼状图所需的数据
     * @Param: []
     * @Retrun: com.ydc.excel_to_db.vo.ExcelModelVo 封装的值对象
     */
    @Override
    public ExcelModelVo getResultData() {
        // 获取格式校验失败的数据大小
        Long failSize = redisDao.getStringValue(Constant.failSizeKey, long.class);
        // 获取格式校验成功的数据大小
        Long succSize = redisDao.getStringValue(Constant.succSizeKey, long.class);
        // 获取导入数据库失败的数据大小
        Long failToDBSize = redisDao.getListSize(Constant.failToDBKey);
        return new ExcelModelVo(succSize, failSize, failToDBSize);
    }

ExcelModelVo.java

package com.ydc.excel_to_db.vo;
import lombok.Data;

/**
 * @Description: 值对象
 * @Author: 杨东川【http://blog.csdn.net/yangdongchuan1995】
 * @Date: Created in  2018-2-6
 */
@Data
public class ExcelModelVo {
    // 格式校验成功的数据大小
    private long succSize;
    // 格式校验失败的数据大小
    private long failSize;
    // 导入数据库失败的数据大小
    private long failToDBSize;
    // 导入数据库成功的数据大小;
    private long succToDBSize;


    public ExcelModelVo(long succSize, long failSize, long failToDBSize) {
        this.succSize = succSize;
        this.failSize = failSize;
        this.failToDBSize = failToDBSize;
        // 校验成功的数据大小 - 导入数据库失败的数据大小 =成功导入的数据大小
        this.succToDBSize = succSize - failToDBSize;
    }
}

说明:这里单独为同步数据结果封装了值对象,方便前台获取各种对应的数据,此外还在echarts图表上添加了对应的点击事件,点击触发下载未通过校验的数据或同步数据库未通过的数据;

        myChart.on('click', function (params) {
            // 也就是从上往下,从左往右 第二个图
            if (params.seriesIndex == 1) {
                layer.msg("正在导出格式校验未通过的数据,请稍等...", {icon: 1});
                window.location.href = "/doExport?failTypeName=format";
            } else if (params.seriesIndex == 2) { //第三个图
                layer.msg("正在导出同步数据库未通过的数据,请稍等...", {icon: 1});
                window.location.href = "/doExport?failTypeName=db";
            } else {
                layer.alert("点击左右子图表可分别下载对应未通过的数据....", {icon: 6});
            }
        });

另一篇关于Echarts的文章: 《SoloBug - bug管理系统》-Echarts+Ajax实现图表数据异步加载

最后贴一下导出功能后台对应的代码:
ExcelToDBController.java

 /**
     * @Description: 导出Excel文件
     * @Param: response
     * @Param: failTypeName 失败类型的名称,例如:格式错误(format)/导入数据库失败(db)/excel模板下载(excelDemo)
     * @Retrun: void
     */
    @GetMapping("/doExport")
    public void doExport(HttpServletResponse response, @RequestParam("failTypeName") String failTypeName) {
        try {
            // 设置响应输出的头类型
            response.setHeader("content-Type", "application/vnd.ms-excel");
            // 导出文件名称
            String exportExcelName;
            if ("format".equals(failTypeName)) {
                exportExcelName = "数据格式校验失败的数据";
            } else if ("db".equals(failTypeName)) {
                exportExcelName = "导入数据库失败的数据";
            } else {
                exportExcelName = "Excel数据格式模板";
            }
            // 下载文件的默认名称
            response.setHeader("Content-Disposition", "attachment;filename=" +
                    URLEncoder.encode(exportExcelName, "UTF-8") + ".xls");
            ExportParams exportParams = new ExportParams();
            /* exportParams.setDataHanlder(null); 设置handler来处理特殊数据 */
            // 根据失败类型的名称(failTypeName),获取对应的List
            List<ExcelModel> failList = importService.getFailList(failTypeName);
            // 导出Excel
            Workbook workbook = ExcelExportUtil.exportExcel(exportParams, ExcelModel.class, failList);
            workbook.write(response.getOutputStream());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

ImportServiceImpl.java

    /**
     * @Description: 根据不同的失败类型的名称(failTypeName), 返回对应的数据
     * @Param: [failTypeName]
     * @Retrun: java.util.List<com.ydc.excel_to_db.domain.ExcelModel>
     */
    @Override
    public List<ExcelModel> getFailList(String failTypeName) {
        if ("format".equals(failTypeName)) {
            return redisDao.getStringListValue(Constant.failListKey, ExcelModel.class);
        } else if ("db".equals(failTypeName)) {
            return redisDao.getListValue(Constant.failToDBKey, ExcelModel.class);
        } else {
            return new ArrayList<ExcelModel>();
        }
    }

说明:将之前存入Redis中的数据取出来。

猜你喜欢

转载自blog.csdn.net/YangDongChuan1995/article/details/79290027
今日推荐