【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中的数据取出来。