文章目录
可能最近和easyexcel比较有缘,上个月倒腾了导出,这个月就轮到了导入了~~
Excel读取实际上是很复杂的,比如问卷场景,涉及多级表头、不固定的列、各类格式(时间、数值等等),但是不用怕,这些在 官方网站上都能找到案例!
本文主要讲解web版不创建对象的导入excel通用版,解决了表格内容不固定和各个导入业务模块复用性!
背景知识介绍
本篇项目框架介绍:SpringBoot + MybatisPlus + EasyExcel + Knife4j
有数据对象读方式
创建类实现ReadListener
实现步骤为:
- 定义数据对象
DemoData
,与Excel中row的每一列对应上 - 定义监听器
implements ReadListener
,将每一行映射到List<DemoData>
中 - 根据业务,存储
List<DemoData>
无额外实现类,since: 3.0.0-beta1
划重点:easyexcel版本需要在3.0.0-beta1及以上!!!
如下代码为:官方举例,其中DemoData为定义的数据对象,与Excel中的列对应。
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
// 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
for (DemoData demoData : dataList) {
log.info("读取到一条数据{}", JSON.toJSONString(demoData));
}
})).sheet().doRead();
不创建数据对象读方式
实现步骤为:
- 定义监听器
extends AnalysisEventListener<Map<Integer, String>>
- 定义全局变量
Map<Integer, String> cachedDataList
,将读取的每一行数据存储在该变量中 - 根据业务,存储
cachedDataList
实践
思路
- 定义抽象监听器
AbstractNoModelDataListener
实现AnalysisEventListener<Map<Integer, String>>
,构造函数中将业务处理接口及业务上下文参数初始化 - 定义抽象业务处理类
AbstractImportInterface
,作为业务处理统一入口 - 定义具体业务处理类实现
AbstractImportInterface
抽象监听类
package com.lizzy.common.excel.utils;
import java.util.List;
import java.util.Map;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
/**
* EasyExcel不创建数据对象的读
*/
@Slf4j
public class AbstractNoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
private List<Map<Integer, String>> cachedDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
private AbstractImportInterface importInterface;
private Map<String, Object> params;
/**
* 初始化业务处理的service层和其他业务参数
* @param importInterface 处理业务的service层
* @param params 其他业务参数
*/
public AbstractNoModelDataListener(AbstractImportInterface importInterface, Map<String, Object> params) {
this.importInterface = importInterface;
this.params = params;
}
// 默认跳过第一行(通常情况下第一行为标题栏)
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
// 通过 AnalysisContext 对象还可以获取当前 sheet,当前行等数据
// 若有跳过行数需求,可写个钩子进行处理
// context.getCurrentRowNum()
log.debug("解析到一条数据:{}", data.toString());
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
cachedDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
importInterface.save(params, cachedDataList);
log.info("存储数据库成功!");
}
}
抽象业务处理类
package com.lizzy.common.excel.utils;
import java.util.List;
import java.util.Map;
/**
* 导入excel时,抽象业务处理层
*/
public interface AbstractImportInterface {
/**
* 业务层存储excel数据逻辑
* @param params 业务上下文参数
* @param data 表格row数据
*/
void save(Map<String, Object> params, List<Map<Integer, String>> data);
}
具体业务处理类
因涉及项目私密性,此处为伪代码,请各位看官自行实现。
package com.lizzy.service;
import com.lizzy.common.excel.utils.AbstractImportInterface;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class UserImportService implements AbstractImportInterface {
@Autowired
IUserService iUserService;
@Override
public void save(Map<String, Object> params, List<Map<Integer, String>> data) {
// 获取上下文参数
final String key = String.valueOf(params.get("key"));
// 业务判断逻辑,此处伪代码,请自行实现
iUserService.save(key, data);
}
}
web整合
说明:ResponseVo
和ResultUtil
为项目定义的通用返回对象,可自行替换!
package com.lizzy.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.lizzy.common.ResponseVo;
import com.lizzy.common.excel.utils.AbstractNoModelDataListener;
import com.lizzy.common.util.ResultUtil;
import com.lizzy.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
/**
* 平台端-问卷数据管理
*
*/
@RestController
@RequestMapping(value = { "/test" })
@Validated
@Api(tags = "测试数据")
@SuppressWarnings({ "unchecked" })
@Slf4j
public class TestController {
@Autowired
IUserService iUserService;
@ApiOperation(value = "导入数据")
@PostMapping(value = "/importData")
public ResponseVo<?> importData(
@ApiParam(value = "key", required = true) @Valid @RequestParam String key,
@RequestParam(value = "file", required = false) MultipartFile file) {
try {
Map<String, Object> params = new HashMap<String, Object>();
params.put("key", key);
// 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
// EasyExcel.read(file.getInputStream()).sheet().doReadSync();
EasyExcel.read(file.getInputStream(), new AbstractNoModelDataListener(iUserService, params))
.sheet().doRead();
return ResultUtil.success();
} catch (IOException e) {
log.info("导入数据excel失败!", e.getMessage());
return ResultUtil.error("导入数据excel失败!");
}
}
}
后记
记录一次通用导入模块诞生记,欢迎大家踊跃提出问题,一起学习,一起进步!!!