EasyExcel web上传下载功能实现

目录

1、依赖引入和poi版本对应关系

2、web下载

2.1、实体对象注解方式

2.1.1、实体对象

2.1.2、接口代码

 2.2、非实体对象注解方式

3、web上传

3.1、实体对象方式

3.1.1、实体对象对代码

3.1.2、接口代码

3.1.3、监听器

3.2、非实体对象方式

3.2.1、接口代码

3.2.2、监听器代码

3.3、根据路径读取


官方文档链接:EasyExcel · 语雀

1、依赖引入和poi版本对应关系

easyExcel版本与poi版本对应关系,这个是官方文档提供的说明

版本

poi依赖版本 (支持范围)

jdk版本支持范围

备注

3.1.0+

4.1.2 (4.1.2 - 5.2.2)

jkd8 - jdk17

推荐使用,会更新的版本

3.0.0-beta1 - 3.0.5

4.1.2 (4.1.2 - 5.2.2)

jkd8 - jdk11

不推荐项目新引入此版本,除非超级严重bug,否则不再更新

2.0.0-beta1-2.2.11

3.17 (3.17 - 4.1.2)

jdk6 - jdk11

不推荐项目新引入此版本,除非是jdk6否则不推荐使用,除非超级严重bug,否则不再更新

1+版本

3.17 (3.17 - 4.1.2)

jdk6 - jdk11

不推荐项目新引入此版本,超级严重bug,也不再更新

依赖引入

注意: 3+版本的的easyexcel,使用poi 5+版本时,需要手动排除:poi-ooxml-schemas

 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>easyexcel</artifactId>
     <version>3.1.0</version>
     <exclusions>
         <exclusion>
             <artifactId>poi-ooxml-schemas</artifactId>
             <groupId>org.apache.poi</groupId>
         </exclusion>
     </exclusions>
</dependency>

2、web下载

2.1、实体对象注解方式

2.1.1、实体对象

@Getter
@Setter
@EqualsAndHashCode
public class DownloadData {
    @ExcelProperty(value = "字符串标题", index = 1)
    private String string;
    @ExcelProperty(value = "日期标题", index = 2)
    private Date date;
    @ExcelProperty(value = "数字标题", index = 3)
    private Double doubleData;
}
@ExcelProperty注解value用于指定表头名字,index用于指定列下标

2.1.2、接口代码

    /**
     * 文件下载(失败了会返回一个有部分数据的Excel)
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DownloadData}
     * <p>
     * 2. 设置返回的 参数
     * <p>
     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
     */
    @GetMapping("download")
    public void download(HttpServletResponse response) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
    }

    /**
     * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
     *
     * @since 2.1.1
     */
    @GetMapping("downloadFailedUsingJson")
    public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            // 这里需要设置不关闭流
            EasyExcel.write(response.getOutputStream(), DownloadData.class).autoCloseStream(Boolean.FALSE).sheet("模板")
                .doWrite(data());
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = MapUtils.newHashMap();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }
    }

 2.2、非实体对象注解方式

这种方式需要提供方法用于获取表头数据和内容数据

public void downloadDistributeTemplate(HttpServletResponse response) {
        // 表头数据封装
		List<List<String>> data = new ArrayList<>();
		List<String> rowOne = new ArrayList<>();
		rowOne.add("填写说明:只能分发一个产品下的设备,且最多 10000 个");
		List<String> rowTwo = new ArrayList<>();
		rowTwo.add("ProductKey");
		rowTwo.add("a1gMnLAmZ0F");
		List<String> rowThree = new ArrayList<>();
		rowThree.add("DeviceName");
		List<String> rowFour = new ArrayList<>();
		rowFour.add("sajdfhladJSDAJLFCjsdbca");
		data.add(rowOne);
		data.add(rowTwo);
		data.add(rowThree);
		data.add(rowFour);
		try {
			response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
			response.setCharacterEncoding("utf-8");
			// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
			String fileName = URLEncoder.encode("设备分发模板", "UTF-8").replaceAll("\\+", "%20");
			response.setHeader("Access-Control-Expose-Headers", "content-disposition");
			response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

			// 这里需要设置不关闭流
			EasyExcel.write(response.getOutputStream()).autoCloseStream(Boolean.FALSE).sheet("模板")
					.registerWriteHandler(new OnceAbsoluteMergeStrategy(0, 0, 0, 5))
					.registerWriteHandler(new AbstractColumnWidthStyleStrategy() {
						@Override
						protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
							Sheet sheet = writeSheetHolder.getSheet();
							sheet.setColumnWidth(cell.getColumnIndex(), 3000);
						}
					})
					.doWrite(data);
		} catch (Exception e) {
			// 重置response
			response.reset();
			response.setContentType("application/json");
			response.setCharacterEncoding("utf-8");
			throw ApiResult.resultCodeException(new AResultCode("0-90002", "下载文件失败"));
		}
    }

这里用到了两个策略

OnceAbsoluteMergeStrategy用于合并指定的单元格,如这里合并的是第一行第一个到第六个单元格

AbstractColumnWidthStyleStrategy用于设置单元格宽度

3、web上传

3.1、实体对象方式

3.1.1、实体对象对代码

@Getter
@Setter
@EqualsAndHashCode
public class UploadData {
    private String string;
    private Date date;
    private Double doubleData;
}

3.1.2、接口代码

    @Autowired
    private UploadDAO uploadDAO;
     /**
     * 文件上传
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link UploadData}
     * <p>
     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
     * <p>
     * 3. 直接读即可
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();
        return "success";
    }

注意:有个很重要的点 UploadDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring(如service和mapper)可以构造方法传进去

3.1.3、监听器

自定义一个监听器,去实现ReadListener接口即可,这里ReadListener传入的泛型是跟表头对象的实体对象

public class UploadDataListener implements ReadListener<UploadData> {
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    private List<UploadData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private UploadDAO uploadDAO;

    public UploadDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        uploadDAO = new UploadDAO();
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param uploadDAO
     */
    public UploadDataListener(UploadDAO uploadDAO) {
        this.uploadDAO = uploadDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(UploadData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        uploadDAO.save(cachedDataList);
        log.info("存储数据库成功!");
    }
}

3.2、非实体对象方式

这里跟实体对象方式差不多,只是在读取的时候read()方法不传入实体对象class,监听器的泛型对象使用Map

3.2.1、接口代码

@PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(),  new DeviceDistributeListener()).sheet().doRead();
        return "success";
    }

3.2.2、监听器代码

public class DeviceDistributeListener implements ReadListener<Map<Integer, String>> {

    private List<String> deviceNameList;

    private String productKey;

    /**
     * 添加deviceName标识
     * */
    private boolean flag = false;

    @Override
    public void invoke(Map<Integer, String> row, AnalysisContext analysisContext) {
        // row是行数据对象
        String data = row.get(0);
        if ("ProductKey".equals(data)) {
            productKey = row.get(1);
            return;
        }
        if ("DeviceName".equals(data)) {
            flag = true;
            deviceNameList = new ArrayList<>();
            return;
        }
        if (flag) {
            deviceNameList.add(data);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }

    public List<String> getDeviceNameList() {
        return deviceNameList;
    }

    public String getProductKey() {
        return productKey;
    }
}

3.3、根据路径读取

部分成熟的业务或者公司会有自己的文件服务器,因此在进行web上传时往往会先把文件上传到文件服务器中,然后将文件的id保存下来,当需要解析的时候就是根据这个文件id获取到文件的url地址,此时获取该文件的输入流就需要使用new URL(path).openStream()这个方法

具体的service代码如下

try {
					EasyExcel.read(new URL(path).openStream(), new DeviceDistributeListener()).sheet().doRead();
				} catch (Exception e) {
					log.error("解析excel失败", e);
					throw ApiResult.resultCodeException(new AResultCode("0-90003", "解析excel失败"));
				}

其中path为根据文件id从文件服务器获取的文件url地址

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值