EasyExcel 使用小结
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。以下是在使用 EasyExcel 的一些小结,参考内容以官方说明文档为主,总结了使用过程中一些经验和遇到的问题,相较于官方文档并不全面,谨作为记录,具体使用请参照官方使用说明。
引入EasyExcel依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>${easyexcel.version}</version> </dependency>
最新版本(2021/01/25)2.2.6 版本查看:https://github.com/alibaba/easyexcel/releases
EasyExcel 官方使用说明
官方的使用说明:https://www.yuque.com/easyexcel/doc/easyexcel
使用记录
使用方式为 Web 中的读取,Web 中上传文件,对数据进行处理后在 Web 中写出。
上传文件:
上传多个文件
<div class="item"> <form id="form" method="post" action="upload" enctype="multipart/form-data"> <div class="upload-box"> 选择文件 <input type="file" name="uploadFile" id="fileUps" accept=".xls,.xlsx" class="upload" multiple="multiple"> </div> <input type="submit" value="提交" class="upload-box submit"/> </form> <div id="files-box"></div> <p>报价单汇总(只提交报价单)</p> <p>只能上传excel格式文件!</p> </div>
Controller:
@PostMapping("upload") @ResponseBody public void upload(@RequestParam("uploadFile") MultipartFile[] uploadFileM, HttpServletResponse response) { mergeQuotesService.MergeQuotes(uploadFileM, response); }
读文件:
for (MultipartFile file : uploadFileM) { if (file.getOriginalFilename().contains("报价单")) { // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 try { // 标题占两行,从第三行开始读取 EasyExcel.read(file.getInputStream(), Quotes.class, new QuotesListener(quotesResultDto)).sheet().headRowNumber(2).doRead(); } catch (Exception e) { e.printStackTrace(); } /** * 原始数据整理 */ quotesResultDto.feedBack().forEach(quotes -> removeRepeat.put(quotes.getDrawing() + quotes.getVersion(), new QuotesMergeDto(quotes))); } }
对象:
Qutote(部分):
@Data @ContentRowHeight(12) @HeadFontStyle(fontHeightInPoints = 10, fontName = "微软雅黑") @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 9) public class Quotes { /** * Module Name */ @ColumnWidth(12) @ExcelProperty(value = {"加工件", "Module Name"}, index = 0) private String moduleName; /** * SWC Module */ @ColumnWidth(10) @ExcelProperty(value = {"加工件", "SWC Module"}, index = 1) private String module; /** * Drawing No. */ @ColumnWidth(18) @ExcelProperty(value = {"加工件", "Drawing No."}, index = 2) private String drawing;
... }
因为此实体类还参与了后续的写文件,因此添加了很多自定义样式的注解,若无此需求则不需要添加,如:
@Data public class DemoData { private String string; private Date date; private Double doubleData; }
监听器:
1 public class QuotesListener extends AnalysisEventListener<Quotes> { 2 3 4 List<Quotes> list = new ArrayList<>(); 5 6 private Quotes quotes; 7 8 @Autowired 9 QuotesResultDAO quotesResult; 10 11 public QuotesListener() { 12 quotes = new Quotes(); 13 } 14 15 public QuotesListener(QuotesResultDAO quotesResult) { 16 this.quotesResult = quotesResult; 17 } 18 19 /** 20 * 这个每一条数据解析都会来调用 21 * 22 * @param quotes 23 * @param analysisContext 24 */ 25 @Override 26 public void invoke(Quotes quotes, AnalysisContext analysisContext) { 27 list.add(quotes); 28 } 29 30 31 /** 32 * 所有数据解析完成了 才会来调用 33 * 34 * @param analysisContext 35 */ 36 @Override 37 public void doAfterAllAnalysed(AnalysisContext analysisContext) { 38 quotesResult.save(list); 39 } 40 41 }
持久层:
1 @Repository 2 public class QuotesResultDAO { 3 4 List<Quotes> list = new ArrayList<>(); 5 6 /** 7 * 存储 Excel 中读取的数据 8 * 9 * @param quotes 10 */ 11 public void save(List<Quotes> quotes) { 12 list.addAll(quotes); 13 } 14 15 /** 16 * 返回列表 17 * 18 * @return 19 */ 20 public List<Quotes> feedBack() { 21 /** 22 * 在第一次返回数据时清空 list 中的内容,防止数据留存 23 */ 24 List<Quotes> temp = new ArrayList<>(); 25 temp.addAll(list); 26 list.clear(); 27 return temp; 28 } 29 30 }
数据处理(读取&写出):
1 @Service 2 @RequiredArgsConstructor 3 public class MergeQuotesServiceImpl implements MergeQuotesService { 4 5 private final QuotesResultDto quotesResultDto; 6 7 // 报价单合并 8 public void MergeQuotes(MultipartFile[] uploadFileM, HttpServletResponse response) { 9 10 // 用于对唯一ID进行去重 11 Map<String, QuotesMergeDto> removeRepeat = new HashMap<>(); 12 // 最终合并结果列表 13 List<QuotesMergeDto> result = new ArrayList<>(); 14 15 16 for (MultipartFile file : uploadFileM) { 17 if (file.getOriginalFilename().contains("报价单")) { 18 // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 19 try { 20 // 标题占两行,从第三行开始读取 21 EasyExcel.read(file.getInputStream(), Quotes.class, new QuotesListener(quotesResultDto)).sheet().headRowNumber(2).doRead(); 22 } catch (Exception e) { 23 e.printStackTrace(); 24 } 25 26 /** 27 * 原始数据整理 28 */ 29 quotesResultDto.feedBack().forEach(quotes -> removeRepeat.put(quotes.getDrawing() + quotes.getVersion(), new QuotesMergeDto(quotes))); 30 } 31 } 32 33 // 最终的去重数据 34 removeRepeat.forEach((k, v) -> result.add(v)); 35 36 // 报价单输出 37 MergeResultWrite(result, response); 38 } 39 40 41 /** 42 * 报价单输出 43 * 44 * @param list 45 */ 46 public void MergeResultWrite(List<QuotesMergeDto> list, HttpServletResponse response) { 47 48 // 这里注意 使用swagger 会导致各种问题,请直接用浏览器或者用postman 49 response.setContentType("application/vnd.ms-excel"); 50 response.setCharacterEncoding("utf-8"); 51 // 这里URLEncoder.encode可以防止中文乱码 当然和easyExcel没有关系 52 try { 53 54 String fileName = URLEncoder.encode("汇总报价单", "UTF-8").replaceAll("\\+", "%20"); 55 response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); 56 // Excel 写出合并报价单 57 EasyExcel.write(response.getOutputStream(), QuotesMergeDto.class).sheet("sheet1") 58 .doWrite(list); 59 // System.out.println("报价单已合并"); 60 } catch (Exception e) { 61 e.printStackTrace(); 62 } 63 64 } 65 66 }
写文件:
@Data @ContentRowHeight(12) @HeadFontStyle(fontHeightInPoints = 10) @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 9) public class QuotesMergeDto { /** * Drawing No. */ @ColumnWidth(18) @ExcelProperty(value = {"Drawing No."}, index = 0) private String drawing; /** * Version No. */ @ColumnWidth(16) @ExcelProperty(value = {"Version No."}, index = 1) private String version; /** * Material */ @ColumnWidth(16) @ExcelProperty(value = {"Material"}, index = 2) private String material; /** * Surface Treatment */ @ColumnWidth(25) @ExcelProperty(value = {"Surface Treatment"}, index = 3) private String surface; /** * 零件类型 */ @ColumnWidth(15) @ExcelProperty(value = {"零件类型"}, index = 4) private String type;
}
在文件写出时,可以按照需求设置样式,最后传入的 list 并不需要与实体类的类型对应,只会按照实体类的格式进行写出,index 的值对应 列表对象中元素的位置。
其它操作
按照序号读取特定表单:
1 ExcelReader excelReader = null; 2 try { 3 excelReader = EasyExcel.read(file.getInputStream()).build(); 4 5 // 读取右侧第三个 sheet 6 ReadSheet readSheet2 = 7 EasyExcel.readSheet(2).head(Workmanship.class). 8 registerReadListener(new SummaryListener(summaryResultDto)).headRowNumber(5).build(); 9 excelReader.read(readSheet2); 10 } catch (Exception e) { 11 e.printStackTrace(); 12 } finally { 13 if (excelReader != null) { 14 // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 15 excelReader.finish(); 16 } 17 }
使用场景:Excel 中有多个 sheet,按照需要读取所需 sheet。
写入多个 sheet:
1 int number = 0; // 表单的编号 2 try { 3 String returnName = URLEncoder.encode("报价单+价格", "UTF-8").replaceAll("\\+", "%20"); 4 response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + returnName + ".xlsx"); 5 excelWriter = EasyExcel.write(response.getOutputStream()).build(); 6 for (MultipartFile file : files) { 7 if (file.getOriginalFilename().contains("报价单")) { 8 // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 9 try { 10 EasyExcel.read(file.getInputStream(), Quotes.class, new QuotesListener(quotesResultDto)).sheet().headRowNumber(2).doRead(); 11 } catch (Exception e) { 12 System.out.println(file.getOriginalFilename() + "读取错误"); 13 e.printStackTrace(); 14 } 15 16 /** 17 * 报价单表内数据 18 */ 19 quoteList = quotesResultDto.feedBack(); 20 List<QuotesMergePlusPriceDto> list = new ArrayList<>(); 21 22 quoteList.forEach(quotes -> { 23 24 try { 25 // 父类赋值到子类 26 list.add(FatherToChild.getQuotesMergePlusPriceDto(new QuotesMergePlusPriceDto(), quotes, prices.get(quotes.getDrawing() + "-" + quotes.getVersion()))); 27 28 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } 32 }); 33 34 35 String sheetName = file.getOriginalFilename(); 36 WriteSheet writeSheet = EasyExcel.writerSheet(number, sheetName).head(QuotesMergePlusPriceDto.class).build(); 37 number++; 38 39 excelWriter.write(list, writeSheet); 40 41 } 42 43 } 44 } catch (Exception e) { 45 e.printStackTrace(); 46 47 } finally { 48 // 千万别忘记finish 会帮忙关闭流 49 if (excelWriter != null) { 50 excelWriter.finish(); 51 } 52 }
编号对应颜色:
单元格填充颜色:
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 9)
字体颜色:
@ContentFontStyle(color = 10, fontHeightInPoints = 11)