更多使用参考:easyExcel官网
业务需求:1、导入用户账号(手机号)进行修改用户名称,2、用户如果存在,判断用户姓名是否与库中一致,不一致修改库中用户姓名,3、记录导入失败数据(前端显示),以备修改后再次导入。
代码实现:
一、controller层:
/**
* 修改名称导入
* @return
*/
@ApiOperation(value = "修改名称导入", notes = "修改名称导入")
@PostMapping("/updateName")
public R importCustomerExcel(@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename();
String suffixName = "";
if (StrUtil.isNotBlank(fileName)) {
suffixName = fileName.substring(fileName.lastIndexOf("."));
}
if (!".xlsx".equals(suffixName) && !".xls".equals(suffixName)){
return R.failed("文件格式不对,请导入excel文件!");
}
cpCustomerService.importCustomerExcel(file);
// 导入失败的数据
List<ImportCustomerExcelModel> failData = ImportCustomerExcelListener.failList();
return R.ok(failData);
}
二、service:
/**
* 用户名称修改导入
* @param file
* @return
*/
void importCustomerExcel(MultipartFile file);
@Override
public void importCustomerExcel(MultipartFile file) {
try (InputStream inputStream = file.getInputStream()){
EasyExcel.read(inputStream, CpCustomer.class, new ImportCustomerExcelListener(cpCustomerService)).doReadAll();
} catch (IOException e) {
LOGGER.error("读取文件出现错误,请重新导入!{}", file);
}
}
EasyExcel.read()解释:
EasyExcel.read(inputStream, CpCustomer.class,new ImportCustomerExcelListener(cpCustomerService)).doReadAll();
1、inputStream:需要读取的文件流。
2、CpCustomer.class:excel导入的字段对应的实体。
3、ImportCustomerExcelListener(cpCustomerService):数据处理监听器,主要逻辑处理都在这里面;由于我需要service去查库判断,所以我这里传了一个cpCustomerService。
4、doReadAll():读取全部sheet。
三、ImportCustomerExcelListener监听器:
/**
* 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
*/
public class ImportCustomerExcelListener extends AnalysisEventListener<CpCustomer> {
private static final Logger LOGGER = LoggerFactory.getLogger(ImportCustomerExcelListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<CpCustomer> list = new ArrayList<>();
/**
* 错误数据
*/
private static List<ImportCustomerExcelModel> errList = new ArrayList<>();
/**
*添加失败集合数据
*/
public static List<ImportCustomerExcelModel> failList(){
List<ImportCustomerExcelModel> listerror = new ArrayList<>();
listerror.addAll(errList);
errList.clear();
return listerror;
}
/**
* 头信息
*/
Map<Integer, String> headMap = new HashMap<>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private CpCustomerService cpCustomerService;
public ImportCustomerExcelListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param cpCustomerService
*/
public ImportCustomerExcelListener(CpCustomerService cpCustomerService) {
this.cpCustomerService = cpCustomerService;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(CpCustomer data, AnalysisContext context) {
updateCustomerName(data);
}
/**
* 修改用户名称
* @param data
*/
public void updateCustomerName(CpCustomer data) {
if (!Validator.isMobile(data.getPhoneNum())){
failDataOperation(data, "手机号格式错误");
throw new ExcelAnalysisStopException(data.getCustomerName()+"的手机号格式错误,错误手机号:"+data.getPhoneNum());
}
// if (!LegalUtils.isNameLegal(data.getCustomerName())) {
// failDataOperation(data, "姓名格式错误(中文2-4位)");
// throw new ExcelAnalysisStopException(data.getCustomerName()+":姓名应该是中文2-4位,请重新输入!");
// }
CpCustomer cpCustomer = cpCustomerService.getOne(Wrappers.<CpCustomer>query().eq("phone_num", data.getPhoneNum()));
Optional.ofNullable(cpCustomer).ifPresent(customer -> {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
// 避免名字中间存在空格
String newName = "";
String oldName = "";
if (StrUtil.isNotBlank(data.getCustomerName())) {
newName = data.getCustomerName().replace(" ", "");
}
if (StrUtil.isNotBlank(cpCustomer.getCustomerName())) {
oldName = cpCustomer.getCustomerName().replace(" ", "");
}
if (!newName.equals(oldName)) {
data.setId(cpCustomer.getId());
list.add(data);
}
LOGGER.info("需要修改的数据:{}", list);
});
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
// 添加到数据库
//saveData();
// 修改数据库数据
updateData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存/修改数据,确保最后遗留的数据也存储到数据库
//saveData();
if (CollUtil.isNotEmpty(list)) {
// 修改数据
updateData();
LOGGER.info("所有数据解析完成!");
}else {
LOGGER.info("没有需要修改的数据");
}
}
/**
* 加上存储数据库(判断数据库中是否存在该数据)
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
cpCustomerService.saveBatch(list);
LOGGER.info("存储数据库成功!");
}
@Override
public void onException(Exception exception, AnalysisContext context) {
LOGGER.error("解析失败:{}", exception.getMessage());
// 以下为导入内容格式转换检测:如:时间格式“yyyy-MM-dd HH:mm:ss”
// 如果是某一个单元格的转换异常 能获取到具体行号;如果要获取头的信息 配合invokeHeadMap使用
int row = 0;
int col = 0;
String title = "";
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
col = excelDataConvertException.getColumnIndex();
row = excelDataConvertException.getRowIndex();
LOGGER.error("第{}行,第{}列解析异常,数据为:{}", row, col , excelDataConvertException.getCellData());
title = this.headMap.get(col);
LOGGER.info("出错标题列名称:{}", title);
}
// 此处抛出异常则--停止导入 不抛出异常则--继续往下导入
// throw new ExcelAnalysisStopException(exception.getMessage()+(row==0?"":", 出错行:"+row)+("".equals(title)?"": "出错列:"+title));
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
}
/**
* 修改数据库
*/
public void updateData() {
LOGGER.info("{}条数据,开始修改数据库信息!", list.size());
try {
cpCustomerService.updateBatchById(list);
}catch (Exception e){
LOGGER.error("以下数据修改过程中出现错误,请重新导入:{}", list);
list.forEach(data -> failDataOperation(data, "修改过程中出现错误,请重新导入!"));
throw new ExcelAnalysisStopException("以下数据修改过程中出现错误,请重新导入:"+list);
}
LOGGER.info("修改数据库成功!");
}
/**
* 失败信息操作公共方法
* @param data
*/
private void failDataOperation(CpCustomer data, String message) {
ImportCustomerExcelModel model = new ImportCustomerExcelModel();
BeanUtil.copyProperties(data, model);
model.setMessage(message);
errList.add(model);
}
}
以上的监听器中有我业务处理的逻辑,官网的介绍请看这里:https://www.yuque.com/easyexcel/doc/read