前言
这段时间做了excel 数据导入,用的阿里巴巴的easyExcel 的代码。下面是官网和githab 代码地址。需要将github 上的代码拉下来,方便查看demo.关于Easyexcel | Easy ExcelEasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目,在尽可能节约内存的情况下支持读写百M的Excel。https://easyexcel.opensource.alibaba.com/docs/current/
GitHub - alibaba/easyexcel: 快速、简洁、解决大文件内存溢出的java处理Excel工具快速、简洁、解决大文件内存溢出的java处理Excel工具. Contribute to alibaba/easyexcel development by creating an account on GitHub.https://github.com/alibaba/easyexcel
仔细查看上面的代码,模仿上面的代码就可以写出来了。
操作步骤
一,创建对象
对象不能使用@Accessors(chain = true) 注解,不然会出现读取不到数据的情况
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}
二,随便创建一个简单的类,不需要任何配置。
只需要根据官网demo来实现或者继承他们自己的内部类即可,创建的类放哪里都行
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
}
三,了解继承类的各个方法
3.1 invokeHead 方法是用来读取表头的。
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {}
使用invokeHead 方法需要我们先定义哪几行是表头,下面代码中的.headRowNumber(3) 可以定义哪几行是表头。3待办前三行都是表头,invokeHead 方法会读取前三行,我们可以根据invokeHead 来校验表头
EasyExcel.read(file.getInputStream(), YclJzLqJcVo.class, yclJzLqExcelDataListener).sheet().headRowNumber(3).doRead();
3.2 invoke 是用来读取数据内容的,在invokeHead 方法运行完后就会运行invoke 方法。来读取数据内容。
@Override
public void invoke(YclJzLqJcVo data, AnalysisContext context) {}
3.3 所有数据解析完成了 都会来调用
@Override
public void doAfterAllAnalysed(AnalysisContext context) {}
四,导入并且返回异常数据提示信息
4.1 实体类设置
@ExcelProperty(index = 1, value = "进场日期")
@ExcelProperty注解中的index 属性代表该字段读取的excel 上的第几列,index =1 代表该字段读取excel 上的第二列,index =0 读取第一列。
package easttrans.pitchdatacore.domain.core;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author guankong
* @since 2022-10-26
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="改性沥青模板映射类", description="")
public class YclGxLqJcVo extends CommonVo implements Serializable {
private static final long serialVersionUID=1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ExcelProperty(index = 0, value = "序号")
@ApiModelProperty(value = "序号")
private Integer serialNum;
@ExcelProperty(index = 1, value = "进场日期")
@ApiModelProperty(value = "进场日期")
private Date enterDate;
@ExcelProperty(index = 2, value = "品牌及标号")
@ApiModelProperty(value = "品牌及标号")
private String brand;
@ExcelProperty(index = 3, value = "堆放地点")
@ApiModelProperty(value = "堆放地点")
private String place;
@ExcelProperty(index = 4, value = "用途")
@ApiModelProperty(value = "用途面层")
private String ratio;
@ExcelProperty(index = 5, value = "质保单编号")
@ApiModelProperty(value = "质保单编号")
private String qaNum;
@ExcelProperty(index = 6, value = "同批数量(t)")
@ApiModelProperty(value = "同批数量(t)")
private Double count;
@ExcelProperty(index = 7, value = "委托单或任务单编号")
@ApiModelProperty(value = "委托单或任务单编号")
private String taskNum;
@ExcelProperty(index = 8, value = "针入度0.1mm")
@ApiModelProperty(value = "针入度0.1mm")
private Double zrd;
}
4.2 拦截器设置
这里AnalysisEventListener<YclJzLqJcVo> 可以设置成泛型AnalysisEventListener<T>
@Slf4j
public final class YclJzLqExcelDataListener extends AnalysisEventListener<YclJzLqJcVo> {
private final YclLqJcVo yclLqJcVo;
private YclLqJcService yclLqJcService;
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
private List<YclJzLqJcVo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 存储异常提醒消息
*/
private Map<String, Object> result = new HashMap<>();
/**
* excel表头是否正确,false 有误,true 正确。
*/
private boolean format = false;
/**
* 头信息
*/
Map<Integer, String> headMap = new HashMap<>();
/**
* 返回提示语
*/
public List<ImportTips> tips = new ArrayList<>();
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param
*/
public YclJzLqExcelDataListener(YclLqJcService yclLqJcService,YclLqJcVo yclLqJcVo) {
this.yclLqJcService = yclLqJcService;
this.yclLqJcVo = yclLqJcVo;
}
/**
* 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) {
log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
int col = 0,row =0;
String title = "";
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
col = excelDataConvertException.getColumnIndex();
row = excelDataConvertException.getRowIndex();
title = this.headMap.get(col);
}
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
}
/**
* 这里会一行行的返回头
*
* @param headMap
* @param context
*/
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
// 如果想转成 Map<Integer,String>
// 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener
// 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换
Map<Integer, String> integerStringMap = ConverterUtils.convertToStringMap(headMap, context);
String s = integerStringMap.get(0);
if (s != null && s.equals(Constants.JZ_Asp_Mob_Ins_Account)){
//如果表头包含该内容,则为true
format = true;
}
}
@Override
public void invoke(YclJzLqJcVo data, AnalysisContext context) {
result.put("format",format);
if (!format){
//format 为false 时
throw new ExcelAnalysisStopException(Constants.formatTemplateNoTrue);
}
//查询改性沥青数据库,判断该数据库中的报告编号是否与cachedDataList 中的报告编号重复,重复不导入
LambdaQueryWrapper<YclLqJcVo> yclJzLqJcVoLambdaQueryWrapper = new LambdaQueryWrapper<>();
yclJzLqJcVoLambdaQueryWrapper.eq(YclLqJcVo::getReportNum,data.getReportNum());
yclJzLqJcVoLambdaQueryWrapper.eq(YclLqJcVo::getType,Constants.JZLQ_TYPE);
List<YclLqJcVo> list = yclLqJcService.list(yclJzLqJcVoLambdaQueryWrapper);
if (list.size()>0){
//表示有数据,该数据不能新增进数据库
saveTips(context.readRowHolder().getRowIndex(),Constants.Duplicate_Import_Information,tips);
return;
}
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
yclLqJcService.saveBatch(collect);
log.info("存储数据库成功!");
}
/**
* 返回数据
* @return 返回读取的数据集合
**/
public Map<String,Object> getResult(){
return result;
}
/**
* 设置读取的数据集合
* @param result 设置读取的数据集合
**/
public void setResult(Map<String,Object> result) {
this.result = result;
}
}
4.3 controller 层设置
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
@ApiOperation(value = "沥青进场检验台账导入数据", notes = "沥青进场检验台账导入数据")
public EastTransResult importExcel(@RequestParam("file") MultipartFile file, YclLqJcVo yclLqJcVo) throws IOException {
EastTransResult<Object> eastTransResult = new EastTransResult<>();
//两个判断,第一个是表头,第二个是内容(哪一行的数据不对)
//type 等于2 为改性沥青,先判断第一行导入模板的表头是否准确
YclGxLqExcelDataListener yclGxLqExcelDataListener = new YclGxLqExcelDataListener(yclLqJcService, yclLqJcVo);
EasyExcel.read(file.getInputStream(), YclGxLqJcVo.class, yclGxLqExcelDataListener).sheet().headRowNumber(3).doRead();
Map<String, Object> result = yclGxLqExcelDataListener.getResult();
boolean format = (boolean) result.get("format");
eastTransResult = new EastTransResult(200, null, "请求成功");
if (!format) {
//表头错误
eastTransResult = new EastTransResult(403, null, "该模板不正确");
}
}
return eastTransResult;
}
返回的数据是 Map<String, Object> result ,可以看下listener 类里面的result 是怎么定义的,先创建了一个
private Map<String, Object> result = new HashMap<>();
然后设置了该result 的get,set 方法。
public Map<String,Object> getResult(){ return result; } public void setResult(Map<String,Object> result) { this.result = result; }
然后可以在listener 类里面result.put("key","val");
后面在controller 中获取该类listener 的getResult 方法就可以获取这个map 了。