easyExcel 3.x以上版本导入数据后,再把错误信息导出,外加自定义RGB背景色
由于项目中用的easypoi导入的数据量大了,会导致OOM的问题,所以要求更换为easyExcel框架做导入。话不多说,这里只做一个导入的示例,还有把遇到的一些问题列出来,大家根据业务需要随机应变。文章参考了其他大佬写的博客,这里把参考的大佬博客列出来:
官方文档:https://easyexcel.opensource.alibaba.com/docs/3.0.x
https://blog.csdn.net/qq_36978700/article/details/123425954
https://blog.csdn.net/qq_29834241/article/details/133786536
https://blog.csdn.net/wjs_007/article/details/135118539
https://www.cnblogs.com/mike-mei/p/17732227.html
//我的项目用的是gradle implementation ('com.alibaba:easyexcel:3.0.5') //maven com.alibaba easyexcel 3.0.5 //可能会用到alibaba的fastjson implementation 'com.alibaba:fastjson:1.2.83'
@PostMapping("/import") public JSONResult importExcel(@RequestParam(name = "file") MultipartFile file, HttpServletResponse response) { try { //实现easyExcel的解析对象 DemoDataListener demoDataListener = new DemoDataListener(); //读取excel文件 EasyExcel.read(file.getInputStream(), DemoData.class, demoDataListener).sheet().doRead(); List
@Data public class DemoData { @ExcelProperty(index = 0)//index可不写,表示读取的列下标 private String title; @ExcelProperty(index = 1) private String createTime; @ExcelProperty(index = 2) private BigDecimal price; @ExcelProperty(index = 3) private String pname; @ExcelProperty(index = 4) private String spec; @ExcelProperty(index = 5) private String failReason; }
@Slf4j public class DemoDataListener implements ReadListener { /** * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 100; /** * 错误数据上限,达到上限则停止解析数据 */ private static final int ERROR_COUNT = 100; /** * 缓存的数据 */ private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); /** * 失败的数据 */ private List> failDataList = new ArrayList<>(); private DemoDao dao; public DemoDataListener() { // TODO 实际使用过程中可通过构造参数把dao层的对象传进来,写入数据(下面注释的构造参数) } /** public DemoDataListener(DemoDao dao) { // TODO 实际使用过程中可通过构造参数把dao层的对象传进来,写入数据 this.dao = dao; } */ public List> getFailDataList() { return failDataList; } @Override public void onException(Exception exception, AnalysisContext context) throws Exception { log.error("解析数据异常!", exception); // if (failDataList.size() > 0) { // exportErrorDataToExcel(); // } ReadListener.super.onException(exception, context); } @Override public void invokeHead(Map> headMap, AnalysisContext context) { ReadListener.super.invokeHead(headMap, context); } /** * 这个每一条数据解析都会来调用 * * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(DemoData data, AnalysisContext context) { //去除空行,有些空行有格式但没有数据,也会被读取 if (lineNull(data)) { return; } log.info("解析到一条数据:{}", JSON.toJSONString(data)); //TODO 这里做数据校验,失败的数据放到failDataList中 if (StringUtils.isBlank(data.getPname())) { Map map = new HashMap<>(1); data.setFailReason("第"+context.readRowHolder().getRowIndex()+"行:商品名称不能为空"); map.put("data", data); failDataList.add(map); } // if (failDataList.size() > ERROR_COUNT) { // throw new RuntimeException("错误数据过多,停止解析,请检查数据"); // } //校验数据完成后,添加到缓存中 cachedDataList.add(data); // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (cachedDataList.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } private boolean lineNull(Object line) { if (line instanceof String) { return StringUtils.isEmpty((String) line); } try { Set fields = Arrays.stream(line.getClass().getDeclaredFields()).filter(f -> f.isAnnotationPresent(ExcelProperty.class)).collect(Collectors.toSet()); for (Field field : fields) { field.setAccessible(true); if (field.get(line) != null) { return false; } } return true; } catch (Exception ignored) { log.error(ignored.getMessage(), ignored); } return true; } @Override public void extra(CellExtra extra, AnalysisContext context) { ReadListener.super.extra(extra, context); } @Override public void doAfterAllAnalysed(AnalysisContext context) { } @Override public boolean hasNext(AnalysisContext context) { return ReadListener.super.hasNext(context); } /** * 加上存储数据库 */ private void saveData() { log.info("{}条数据,开始存储数据库!", cachedDataList.size()); //TODO 这里执行存储数据库的代码逻辑 // demoDAO.save(cachedDataList); log.info("存储数据库成功!"); } }
@Slf4j public class CustomRgbCellStyle extends AbstractCellStyleStrategy { private List contentWriteCellStyleList; public CustomRgbCellStyle(WriteCellStyle contentWriteCellStyle) { if (contentWriteCellStyle != null) { this.contentWriteCellStyleList = ListUtils.newArrayList(contentWriteCellStyle); } } @Override public void setHeadCellStyle(CellWriteHandlerContext context) { Boolean head = context.getHead(); // 设置标题头样式,这里的判断可不要 if (head) { log.info("afterCellCreate ====> {}", head); // 获取和创建CellStyle WriteCellData> cellData = context.getFirstCellData(); CellStyle originCellStyle = cellData.getOriginCellStyle(); if (Objects.isNull(originCellStyle)) { originCellStyle = context.getWriteWorkbookHolder().getWorkbook().createCellStyle(); } // 设置背景颜色 ((XSSFCellStyle) originCellStyle).setFillForegroundColor(new XSSFColor(new java.awt.Color(186, 12, 47), new DefaultIndexedColorMap())); originCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 重点!!! 由于在FillStyleCellWriteHandler,会把OriginCellStyle和WriteCellStyle合并,会已WriteCellStyle样式为主,所有必须把WriteCellStyle设置的背景色清空 // 具体合并规则请看WriteWorkbookHolder.createCellStyle方法 WriteCellStyle writeCellStyle = cellData.getWriteCellStyle(); writeCellStyle.setFillForegroundColor(null); // 重点!!! 必须设置OriginCellStyle cellData.setOriginCellStyle(originCellStyle); } } @Override public void setContentCellStyle(CellWriteHandlerContext context) { if (stopProcessing(context) || CollectionUtils.isEmpty(contentWriteCellStyleList)) { return; } WriteCellData> cellData = context.getFirstCellData(); if (context.getRelativeRowIndex() == null || context.getRelativeRowIndex() <= 0) { WriteCellStyle.merge(contentWriteCellStyleList.get(0), cellData.getOrCreateStyle()); } else { WriteCellStyle.merge( contentWriteCellStyleList.get(context.getRelativeRowIndex() % contentWriteCellStyleList.size()), cellData.getOrCreateStyle()); } } protected boolean stopProcessing(CellWriteHandlerContext context) { return context.getFirstCellData() == null; } }
到此结束,如有疑问,欢迎留言!