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)

此处参考:https://my.oschina.net/u/3267498/blog/4680918

posted @ 2021-01-25 16:24  宁任翃  阅读(907)  评论(0编辑  收藏  举报