一、 前言
本篇是EasyExcel快速入门知识,讲解如何读取Excel文件,对Excel中错误信息如空字符、必填项为空、表格格式校验做到处理 ,并给出了实际项目中示例代码;为什么要使用easyexcel;原因是相比于poi,easyexcel更加轻量级,读取写入API方便,并且在工作中占用内存较小;
官方描述如下:
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。
二、前端上传Excel文件
** **关于前端上传Excel文件分为两种情况:
** **(1)前后端分离情况下(前端使用Vue)实现文件上传。
** ** 该情况下直接借助elementUI提供的上传组件可以很好的实现上传文件,包括对上传文件前后均有回调函数,可以对各个时间节点进行操作,对文件处理非常便捷,我这里就不多赘述。
(2)前后端未分离情况下(springboot模板)实现文件上传。
该情况下可以使用input标签的type=“file”来实现文件选取,但是使用它主要有两点问题,相信大家在做项目时也会遇到:1、默认样式很丑,修改后点击无法触发文件选择的弹窗。2、如何取上传的file?直接获取input.value是fakePath路径(浏览器处于安全考虑修改为虚拟路径),因此不能通过此路径访问到文件,解析路径也比较麻烦。
(3)模板下载。
模板存在D盘img->download文件夹下,该路径通过nginx配置(8091为我的nginx地址),访问该路径实现文件下载,通过_blank实现不跳转页面。
下面代码为解决上述(2)(3)两个问题:
<body>
<div>
<a class="mini-button" iconCls="icon-download" onclick="downloadFile()">导入模板下载</a>
<a class="mini-button" onclick="showInput()">学生数据导入</a>
<input id="articleImageFile" type="file" style="display: none;" accept=".xls,.xlsx" onchange="showFileName()"/>
<span id="showFileName" class="mini-button-info"></span>
</div>
</body>
<script>
<--导入模板-->
function downloadFile(){
const url = "http://192.168.3.96:8091/download/学生数据导入模板.xls";
// 使用 window.open() 方法发起 GET 请求,跳转到下载页面
window.open(url, '_blank')
}
<--点击button后通过js实现点击上传文件input框-->
function showInput(){
deleteTemp();//删除临时表
$("#articleImageFile").click();
}
<--展示文件名称-->
function showFileName(){
var file = $("#articleImageFile")[0].files[0];
if(file == undefined || file == null){
$("#showFileName").text("")
return;
}
$("#showFileName").text(name.name)
showTips("文件上传成功,请查看导入数据后确认导入!");
}
<--提交之前可以借助以下代码实现文件类型判断,具体提交代码不再赘述,主要就是发送ajax请求,将file传递到后端-->
<--
if(file.name.substring(file.name.lastIndexOf(".")) !== '.xls' && file.name.substring(file.name.lastIndexOf(".")) !== '.xlsx'){
showTips("只能上传.xls或.xlsx文件")
return;
}
-->
</script>
三、后端使用easyExcel解析文件
(1)准备与Excel文件数据相同属性的dto,需要注意的是每一列对应的值。从0开始,注意使用
注解。
public class ExcelWgsUserInfoFileDto extends BaseDto {
@ExcelProperty(index = 0)
private String index;
@ExcelProperty(index = 1)
private String id;
@ExcelProperty(index = 2)
private String name;
//get、set方法
}
(2)controller层接收文件(可以接收多个)
/**
* 导入Excel信息
*/
@ApiOperation("导入Excel表")
@RequestMapping(value = "/toexcel",method = RequestMethod.POST)
public APIResponse toexcel(@RequestParam("file") MultipartFile file) throws Exception{
return wgsUserInfoService.loadScoreInfo(file);
}
(3)service层(最关键)
定义的变量解释:
BATCH_COUNT:集合最大缓存量,当到达最大缓存调用一次存方法。
temp:对调用了几次存函数进行计数,在判断表头、计算错误行数时使用。
isError:由于存方法中return只会跳出该方法,不会跳出整个read方法,使用它实现当出现错误后下一次1000行或者读取完成不再调用存方法。
该代码较长,我稍微做一下解释:其实使用easyExcel解析文件,最重要的是要弄清楚整个读取文件的流程是什么。在EasyExcel.read(参数1,参数2,参数3)中,最重要的是第三个参数读的监听器,该参数中重写的两个方法:
1、invoke()每读取一行调用一次,它的第一个参数ExcelWgsUserInfoDto就是我们最初定义的dto,是文件中一行的数据,做的操作为将该行数据加入到集合中,当到达集合缓冲区最大值1000时调用存数据的方法。
2、doAfterAllAnalysed()该方法是当Excel文件全部读取完成后会调用一次存数据的方法。
以上两个重写的方法主要作用是对Excel文件进行操作,拿取数据,这两个方法照着写即可,但是对于我们来说业务处理同样重要,在saveData中主要做的是业务处理(判断表格格式、存入数据库、是否有必填项为空等),存入数据库操作不多赘述,下面讲一下代码的大概思路:
进入该方法会对存入集合的数据进行遍历,取数据存入数据库,在存数据之前便是判断时间。首先进行的是表头格式的判断,根据Excel模板文件第几行为模板文字进行判断,由于我的是第二行,所以有if(temp<1&&i<2)的判断,对Excel每行必填项进行if判断,若为null,返回提示,行数根据temp和i计算得来。
Excel文件格式如下:
@Transactional
@Override
public APIResponse<String> loadScoreInfo(MultipartFile file) throws IOException {
APIResponse<String> result = new APIResponse<>();
try {
/**
* 构建一个读的工作簿对象
* @param file
* 第一个参数:文件输入流
* @param head
* 第二个参数:文件中每一行对应的实体类
* @param ReadListener
* 第三个参数:读的监听器,每读一行内容,都会调用一次该对象的invoke方法,在invoke可以操作使用读到的数据
*/
EasyExcel.read(file.getInputStream(), ExcelWgsUserInfoFileDto.class, new ReadListener<ExcelWgsUserInfoFileDto>() {
/**
* 单次缓存的数据量
*/
public static final int BATCH_COUNT = 1000;
//判断执行了几次saveData
int temp = 0;
boolean isError = false;
/**
*临时存储
*/
private List<ExcelWgsUserInfoFileDto> cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//导入数据库的明细表list
private List<WgsUserInfoTempEntity> wgsUserInfoTempList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 每第一行调一次的方法
* @param data
* @param analysisContext
*/
@Override
public void invoke(ExcelWgsUserInfoFileDto data, AnalysisContext analysisContext) {
cacheDataList.add(data);
if (cacheDataList.size() >= BATCH_COUNT && !isError) {
saveData();
temp++;
// 存储完成清理 list
cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 读取完整个文档后调用一次
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if(!isError){
saveData();
}
temp = 0;
}
/**
* 存储数据库
*/
private void saveData() {
System.out.println("进入存储文件!!");
wgsUserInfoTempList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
for (int i = 0; i < cacheDataList.size(); i++) {
//取出每一行数据
ExcelWgsUserInfoFileDto data = cacheDataList.get(i);
//表头判断
if ( i == 1 && !"学号".equals(cacheDataList.get(1).getId()) && !"姓名".equals(cacheDataList.get(1).getName()) &&
!"性别".equals(cacheDataList.get(1).getGender()) && !"手机号码".equals(cacheDataList.get(1).getMobile()) &&
!"身份证号".equals(cacheDataList.get(1).getIdCard()) && temp < 1 ) {
System.out.println("进入格式检查");
result.setMsg("请检查表格格式是否符合规范!");
result.setMsgCode(201);
isError= true;
return;
}
//因为我的表头有两行
if (temp < 1 && i < 2) {
continue;
}
if(isNull(data)){
result.setMsg("第"+(temp*BATCH_COUNT+i+1)+"行必填项为空!");
result.setMsgCode(201);
isError= true;
return;
}
WgsUserInfoTempEntity wgsUserInfoTempEntity = new WgsUserInfoTempEntity();
//数据准备,每一行的数据存入对应entity中
wgsUserInfoTempEntity.setId(data.getId());
wgsUserInfoTempEntity.setBh(data.getId());
wgsUserInfoTempList.add(wgsUserInfoTempEntity);
}
if (wgsUserInfoTempList.size() == 0) {
System.out.println("Excel表中未读取到数据");
result.setMsg("文件数据异常!");
result.setMsgCode(201);
return;
}
List<WgsUserInfoTempEntity> wgsUserInfoTempEntities = wgsUserInfoTempRepository.saveAll(wgsUserInfoTempList);
if (wgsUserInfoTempEntities.size() > 0) {
logger.info("武工商UserInfo存入临时表数据成功");
result.setMsg("文件导入成功");
result.setMsgCode(200);
} else {
result.setMsg("文件导入失败");
result.setMsgCode(201);
}
}
public Boolean isNull(ExcelWgsUserInfoFileDto excelWgsUserInfoFileDto){
if(excelWgsUserInfoFileDto.getId() == null || excelWgsUserInfoFileDto.getId().isEmpty()){
return true;
}
return false;
}
}).sheet().headRowNumber(0).doRead();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
版权归原作者 小吴很差吧 所有, 如有侵权,请联系我们删除。