EasyExcel 实现数据的导入
项目环境:
jdk 17+element-plus +vue 3
1.前端使用element-plus 部署文件的提交部件,:http-request绑定一个向前端发送数据的一个方法。
<el-dialog v-model="importExcelDialogVisible" title="导入线索Excel" width="55%" center>
<el-upload ref="uploadRef" method="post"
:http-request="uploadFile" :auto-upload="false" :limit="1">
<template #trigger>
<el-button type="primary">选择Excel文件</el-button>
仅支持后缀名为.xls或.xlsx的文件
</template>
<br />
<br />
<div>重要提示:</div>
<ul>
<li>上传仅支持后缀名为.xls或.xlsx的文件;</li>
<li>给定Excel文件的第一行将视为字段名;</li>
<li>请确认您的文件大小不超过50MB;</li>
<li>日期值以文本形式保存,必须符合yyyy-MM-dd格式;</li>
<li>日期时间以文本形式保存,必须符合yyyy-MM-dd HH:mm:ss的格式;</li>
</ul>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importExcelDialogVisible = false">关 闭</el-button>
<el-button class="ml-3" type="success" @click="submitUpload">上 传</el-button>
</span>
</template>
</el-dialog>
submitUpload 实现文件提交出发el-upload 的submit()方法
submitUpload() {
this.$refs.uploadRef.submit();
},
uploadFile(files) {
// console.log(files)
let fileName = files.file.name;
// 判断是不是excel文件
if (fileName.endsWith(".xlsx") || fileName.endsWith(".xls")) {
// 提交文件到后台
let formData = new FormData();
formData.append("file", files.file);
doPost("/api/clue/importExcel", formData).then((resp) => {
if (resp.data.code === 200) {
// 隐藏弹框
this.importExcelDialogVisible = false;
// 刷新(父组件传递给子组件一个局部刷新方法)
this.reload();
messageTitle("导入文件成功!", "success");
} else {
messageTitle("导入文件失败!", "error");
}
});
} else {
messageTitle("文件类型不正确!", "warning");
}
},
2.后端引入easyExcel 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.3</version>
</dependency>
界面层处理前端的请求:
@PostMapping("/api/clue/importExcel")
public R importClues(MultipartFile file) throws IOException {
System.out.println(file);
boolean read = service.readExcel(file.getInputStream());
return read?R.OK():R.FAIL();
}
业务逻辑层处理:
@Override
public boolean readExcel(InputStream inputStream) {
try {
EasyExcel.read(inputStream, TClue.class, new UploadExcelListener(clueMapper)).sheet().doRead();
return true;
} catch (Exception e) {
return false;
}
}
easyExcel 读取数据的核心监听器的处理。(查看官网即可)
package com.dqw.config.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.dqw.domain.pojo.TClue;
import com.dqw.domain.util.JsonUtils;
import com.dqw.mapper.TClueMapper;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
*
*/
@Slf4j
public class UploadExcelListener implements ReadListener<TClue> {
private TClueMapper tClueMapper;
public UploadExcelListener() {
}
public UploadExcelListener(TClueMapper tClueMapper) {
this.tClueMapper = tClueMapper;
}
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<TClue> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(TClue tClue, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JsonUtils.BeantoString(tClue));
cachedDataList.add(tClue);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
tClueMapper.save(cachedDataList);
log.info("存储数据库成功!");
}
}
处理excel表中何类对应的字段
采用@ExcelProperty
@ExcelProperty(value = "活动ID")
private Integer activityId;
/**
* 姓名
*/
@ExcelProperty(value = "姓名")
private String fullName;
/**
* 称呼
*/
@ExcelProperty(value = "称呼",converter = AppellationConverter.class)
private Integer appellation;
负责人 所属活动 姓名 称呼 手机号 微信号 QQ号 邮箱 年龄 职业 年收入 地址 是否贷款 意向状态 意向产品 线索状态 线索来源 线索描述 下次联系时间
1 46 王杰 先生 13700000000 13700000000 230989432 wangjie@163.com 32 工程师 10 北京亦庄 需要 有意向 比亚迪e2 未联系 车展会 近期在看车 2023/11/27 20:33:25
7 47 张怡然 女士 13700000001 13700000001 28 8 河北廊坊 不需要 有意向 海豚 将来联系 网络广告 通过打电话获取的线索 2023/11/30 10:33:51
12 7 张翔宇 先生 13876903226 13876903226 1298094321 26 9 天津和平 需要 意向不明 秦PLUS EV 需要条件 视频直播 有购车意向,需要跟踪 2023/11/15 10:30:00
19 46 王世坤 先生 13700000000 13700000000 209836613 wangjie@163.com 32 工程师 10 北京亦庄 不需要 意向不明 秦PLUS DM-i 未联系 视频直播 近期在看车 2023/12/27 9:20:21
21 47 张珊珊 女士 13700000001 13700000001 28 8 河北廊坊 需要 无意向 汉DM 将来联系 视频直播 通过打电话获取的线索 2023/11/30 13:33:51
生
女士
看上图:称呼是非数据的字符串类型,但是接受数据的实体类是该字段是Integer类型,因此需要进行数据转换。
package com.dqw.config.converter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.dqw.ServerApplication;
import com.dqw.domain.constants.Constants;
import com.dqw.domain.pojo.TDicValue;
import java.util.List;
public class AppellationConverter implements Converter<Integer> {
@Override
public Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
// 读取excel数据时 执行该转换器
// 1. 读取该值
String stringValue = cellData.getStringValue();
// 2. 通过该值 获取对应的id (根据自己的需求实现数据的转化)
List<TDicValue> tDicValues = (List<TDicValue>) ServerApplication.CACHEMAP.get(Constants.APPELLATION);
for (TDicValue tDicValue : tDicValues) {
if (tDicValue.getTypeValue().equals(stringValue)) {
return tDicValue.getId();
}
}
return -1;
}
}
对注解2 那块代码进行解释:
由于称呼信息经常被单独请求调用查询,为了避免多次没必要的数据库IO,设置了一个定时任务每隔一定的间隔时间就从数据库中获取数据并且将数据存入内存中,每次访问数据时都会优先出内存中获取数据,而不是从数据库中读取,加快了访问速度和减少了磁盘读写次数。
上述代码经实验没有任何错误,仅此记录。