一、功能描述

  1. EasyExcel是对Alibaba提供的ExcelPOI的一个二次开发,对其进行了再度的封装<br /> SprngBoot中集成EasyExcel功能,通过对象导入、解析到数据库;导出到本地。<br /> 官方文档:[https://www.yuque.com/easyexcel/doc/quickstart](https://www.yuque.com/easyexcel/doc/quickstart)

二、集成EasyExcel

1、导入依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>easyexcel</artifactId>
  4. <version>3.0.5</version>
  5. </dependency>

2、创建实体类(上传、下载通用)

  1. 1EasyExcel中提供了通过对象进行Excel数据表的解析与下载<br /> 2@ExcelProperty("字段名称")<br /> 该注解用于将Excel文件中标题进行匹配,格式要求必须遵循,不能同时使用indexname进行匹配。<br /> 3@ExcelIngore <br /> 该注解用于忽略某些字段,比如创建时间,只需要在数据库中存在即可。
  1. @ApiModel("数据解析实体类")
  2. @Data
  3. public class NucleateExcelDto {
  4. @ExcelProperty("序号")
  5. private Integer id;
  6. @ExcelProperty("工作单位")
  7. private String unitName;
  8. @ExcelProperty("姓名")
  9. private String name;
  10. @ExcelProperty("身份证号码")
  11. private String cardId;
  12. @ExcelProperty("联系方式")
  13. private String phone;
  14. @ExcelProperty("最后一次核酸检测时间")
  15. private Date lastCollectTime;
  16. @ExcelProperty("核酸检测结果")
  17. private String collectResult;
  18. @ExcelIgnore
  19. private Date createTime;
  20. public NucleateExcelDto(){
  21. this.createTime = new Date();
  22. }
  23. }

三、EasyExcel导入数据库

1、创建Listener监听器

该监听器中主要用来对文件进行读取、通过缓存容器来设定临时容器,里面可自定义调用入库方法。
需要注意的是:调用三次存储方法,需要加入非空判断逻辑(防止读取到Excel后面的空行情况)。

  1. package com.example.epidemic.lisenter;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.alibaba.excel.context.AnalysisContext;
  4. import com.alibaba.excel.read.listener.ReadListener;
  5. import com.alibaba.excel.util.ListUtils;
  6. import com.example.epidemic.dto.NucleateExcelDto;
  7. import com.example.epidemic.service.ExcelService;
  8. import lombok.extern.slf4j.Slf4j;
  9. import java.util.List;
  10. /**
  11. * @ClassName:
  12. * @Author: 挽风
  13. * @Date: 2022
  14. * @Copyright: 2022 by 挽风
  15. * @Description:
  16. **/
  17. @Slf4j
  18. public class UploadNucleateDataListener implements ReadListener<NucleateExcelDto> {
  19. // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
  20. /**
  21. * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
  22. */
  23. private static final int BATCH_COUNT = 100;
  24. /**
  25. * 缓存的数据
  26. */
  27. private List<NucleateExcelDto> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
  28. /**
  29. * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
  30. */
  31. private ExcelService excelService;
  32. // public () {
  33. // // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
  34. // demoDAO = new N;
  35. // }
  36. /**
  37. * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
  38. *
  39. * @param excelService
  40. */
  41. public UploadNucleateDataListener(ExcelService excelService) {
  42. this.excelService = excelService;
  43. }
  44. /**
  45. * 这个每一条数据解析都会来调用
  46. *
  47. * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
  48. * @param context
  49. */
  50. @Override
  51. public void invoke(NucleateExcelDto data, AnalysisContext context) {
  52. log.info("解析到一条数据:{}", data.toString());
  53. // 这里需要进行非空判断,防止读取Excel格式中数据为空
  54. if (!StrUtil.isEmpty(data.getCardId())){
  55. cachedDataList.add(data);
  56. }
  57. // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
  58. if (cachedDataList.size() >= BATCH_COUNT) {
  59. saveData();
  60. // 存储完成清理 list
  61. cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
  62. }
  63. }
  64. /**
  65. * 所有数据解析完成了 都会来调用
  66. *
  67. * @param context
  68. */
  69. @Override
  70. public void doAfterAllAnalysed(AnalysisContext context) {
  71. // 这里也要保存数据,确保最后遗留的数据也存储到数据库
  72. excelService.saveNucleateExcelDto(cachedDataList);
  73. log.info("所有数据解析完成!");
  74. }
  75. /**
  76. * 加上存储数据库
  77. */
  78. private void saveData() {
  79. log.info("{}条数据,开始存储数据库!", cachedDataList.size());
  80. // 此处用来调用service层进行数据存储
  81. excelService.saveNucleateExcelDto(cachedDataList);
  82. log.info("存储数据库成功!");
  83. }
  84. }

2、Api接口中调用上传文件

Api层中主要用来控制请求转发,直接调用Service层中的方法即可

  1. /**
  2. * 上传Excel数据报表接口
  3. * @param file excel文件
  4. * @return
  5. */
  6. @PostMapping("/uploadNucleateExcel")
  7. @ApiOperation(value = "上传报表数据Excel", notes = "上传报表文件Excel数据")
  8. public R uploadNucleateExcel(@RequestParam("file")MultipartFile file){
  9. return reportService.addNucleateExcelFile(file);
  10. }

3、Service层中调用EasyExcel读取文件

ServiceImpl中实现Service接口层中的方法、、

  1. /**
  2. * 上传Excel数据报表接口
  3. * @param file excel文件
  4. * @return
  5. */
  6. @Override
  7. @Transactional(rollbackFor = Exception.class)
  8. public R addNucleateExcelFile(MultipartFile file){
  9. try{
  10. // 此处调用,传入文件流、上传Dto实体、创建上传监听器
  11. EasyExcel.read(file.getInputStream(), NucleateExcelDto.class,
  12. new UploadNucleateDataListener(excelService)).sheet().doRead();
  13. }catch (Exception ignored){
  14. throw new EpidemicException(NucleateDataEnum.UPLOAD_DATA_FAIL.getMsg(), NucleateDataEnum.UPLOAD_DATA_FAIL.getCode());
  15. }
  16. return R.ok();
  17. }

四、EasyExcel导出数据库

Api层中定义Get类型的请求api接口,在其中调用Service层方法即可。
前端调用该Api层接口,Service层中直接将文件下载到浏览器。
注意:autoCloseStream(Boolean.TRUE)必须要设置为TRUE,否则会导致下载文件Excel无法打开,WPS可以打开的情况。

  1. /**
  2. * 下载报表数据Excel
  3. * @param downloadExcelDto id集合参数封装
  4. * @return
  5. */
  6. @Override
  7. @Transactional(rollbackFor = Exception.class)
  8. public R downloadNucleateExcel(DownloadExcelDto downloadExcelDto) {
  9. try {
  10. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  11. response.setCharacterEncoding("utf-8");
  12. // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
  13. String name = "重点人群报备"; // 文件名称
  14. String fileName = URLEncoder.encode(name, "UTF-8").replaceAll("\\+", "%20");
  15. response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
  16. // 这里需要设置不关闭流
  17. EasyExcel.write(response.getOutputStream(), NucleateExcelDto.class).autoCloseStream(Boolean.TRUE).sheet("模板")
  18. .doWrite(getNucleateExcelData(downloadExcelDto.getIds(), downloadExcelDto.getUnitName()));
  19. } catch (Exception e) {
  20. exceptionResp(response);
  21. }
  22. Map<String, Object> map = MapUtils.newHashMap();
  23. map.put("status", "true");
  24. map.put("message", "文件下载成功");
  25. return R.ok(map);
  26. }