likes
comments
collection
share

EasyExcel常用业务场景及代码实现

作者站长头像
站长
· 阅读数 8

前言

EasyExcel 是阿里巴巴开源的一款 Excel 处理工具,EasyExcel 使用起来快速简便,性能高效。本文将基于日常开发中的业务场景来介绍 EasyExcel 的应用及代码实现。限于篇幅及文章主旨,本文不会对 EasyExcel 的基本 API 作讲解,因为可在官方文档查阅。 EasyExcel 官方文档:easyexcel.opensource.alibaba.com/docs/curren…


导出Excel

导出 Excel,就是将数据导出为 Excel 格式文件,也称为写入 Excel。它有以下应用场景:

  • 导出 Excel 到本地
  • Web 下载 Excel
  • Excel 导出任务

导出Excel到本地

导出 Excel 到本地是最常用的场景,在业务中通常是根据给定的 Excel 模版导出数据,而数据则通过数据库读取。 ExcelUtil 中导出 Excel 到本地的方法,如下示例:

public class ExcelUtil {
    /**
     * 写入Excel
     * @param pathName 路径
     * @param head 表头
     * @param data 数据
     */
    public static void writeExcel(String pathName, Class<?> head, List<?> data) {

        EasyExcel.write(pathName).head(head).sheet("Sheet1").doWrite(data);
    }
}

Web下载Excel

Web 下载 Excel 是指实时地生成 Excel 并通过 Web 网络下载,同样地生成的 Excel 也是根据给定的模版要求导出特定数据; 实际上,Web 下载 Excel 会占用网络资源,业务量大时对服务器压力也不小,所以通常是以 Excel 导出任务的方式替代这样的业务场景。 ExcelUtil 中 Web 下载 Excel 的方法称为 downloadExcel,如下示例:

public class ExcelUtil {
    /**
     * 下载Excel
     * @param response HTTP Response
     * @param head 表头
     * @param data 数据
     */
    public static void downloadExcel(HttpServletResponse response, Class<?> head, List<?> data) throws IOException {
        try {
            ExcelTypeEnum xlsx = ExcelTypeEnum.XLSX;
            String fileName = URLEncoder.encode(FileUtil.randomName(xlsx.getValue()), "UTF-8").replaceAll("\\+", "%20");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
            EasyExcel.write(outputStream).head(head).autoCloseStream(autoCloseStream).sheet("Sheet1").doWrite(data);
        } catch (IOException e) {
            e.printStackTrace();
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().println(JSON.toJSONString(ApiResponse.result(ApiResult.WEB_DOWNLOAD_ERROR)));
        }
    }
}

Excel导出任务

Excel 导出任务,就是先创建需要的导出任务,等待调度服务完成 Excel 导出,之后再手动下载需要的 Excel; 调度服务完成 Excel 导出,这个过程实际上可导出到本地服务器,或上传到对象存储服务(如 OSS); 当需要上传到对象存储服务时,通过流的方式导出 Excel 并上传。 ExcelUtil 中流式导出 Excel 的方法如下示例:

public class ExcelUtil {
    /**
     * 写入Excel
     * @param outputStream 流
     * @param head 表头
     * @param data 数据
     * @param autoCloseStream 自动关闭流
     */
    public static void writeExcel(OutputStream outputStream, Class<?> head, List<?> data, Boolean autoCloseStream) {

        EasyExcel.write(outputStream).head(head).autoCloseStream(autoCloseStream).sheet("Sheet1").doWrite(data);
    }
}

在完成流式导出 Excel 后,此时数据以OutputStream形式存在,当需要上传至对象存储服务时,则要把OutputStream转为InputStream,伪代码如下:

// 假设这里是Excel数据
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

// 将OutputStream的内容转为InputStream
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());

之后,就可以调用对象存储服务的上传方法,如下:

/**
 * 阿里云OSS工具类
 */
public class AliOSSUtil {
    public static void upload(String fileName, InputStream inputStream) {

        OSS ossClient = new OSSClientBuilder().build(AliOSSConfig.ENDPOINT, AliOSSConfig.ACCESS_KEY_ID, AliOSSConfig.ACCESS_KEY_SECRET);
        PutObjectResult putObjectResult = ossClient.putObject(AliOSSConfig.BUCKET_NAME, fileName, inputStream);
        ossClient.shutdown();
    }
}

导入Excel

导入 Excel,就是解析 Excel 文件数据 ,也称为读取 Excel,有以下场景:

  • 从本地导入 Excel 文件
  • Web 上传 Excel

从本地导入Excel文件

从本地导入 Excel 文件是比较的基础业务场景,通常是将本地 Excel 文件按照数据规则读取出来后入库保存; 更常见的业务场景,是Web上传Excel

Web上传Excel

Web 上传 Excel 是更为常见的业务场景,通常是在管理后台上传 Excel 文件,然后服务端根据规则读取数据,之后入库保存。 ExcelUtil 中读取 Excel 的方法如下示例:

public class ExcelUtil {
    /**
     * 读取Excel
     * @param inputStream 流
     * @param head 表头
     * @return 数据
     */
    public static <T> List<T> readExcel(InputStream inputStream, Class<?> head) {
        ExcelListener<T> listener = new ExcelListener<>();
        EasyExcel.read(inputStream, head, listener).sheet().doRead();
        return listener.getRows();
    }
}

controller层中,我们可以很方便地利用 Spring 的MultipartFile来获取文件的数据流并调用EasyExcel读取其中的数据,无需将数据流转为本地文件再处理,避免占用存储空间。 代码调用如下示例:

@Api(tags = "文件处理")
@RestController
@RequestMapping("/admin-service")
public class FileHandleController {

    @ApiOperation("上传Excel")
    @PostMapping("/uploadExcel")
    public ApiResponse<ApiResult> uploadExcel(@RequestParam("file") MultipartFile file) {

        try {
            List<OrderHead> list = ExcelUtil.readExcel(file.getInputStream(), OrderHead.class);
            // ......
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ApiResponse.success();
    }
}

导入Excel分批入库

在导入 Excel 的业务中,时常会处理大量数据的 Excel 表格; 然而在数据量大的时候一次性批量插入数据库会产生性能问题,所以通常是将大量数据分批次入库。 EasyExcel 中提供了一个PageReadListener的监听器,可以用于分批读取。 ExcelUtil 中分批读取 Excel 的方法如下示例:

public class ExcelUtil {
    /**
     * 读取Excel
     * 可以分批次读取,再入库保存
     * @param inputStream 流
     * @param head 表头
     * @param listener 监听器
     */
    public static <T> void readExcel(InputStream inputStream, Class<?> head, PageReadListener<T> listener) {

        EasyExcel.read(inputStream, head, listener).sheet().doRead();
    }
}

PageReadListener

PageReadListener用于实现数据的分批处理,默认是每个批次100条数据,也可以在构造函数中指定; PageReadListener的构造函数中至少要传入一个Consumer,也就是需要定义一个如何消费的函数; 在invoke方法中(每读取一条都会回调),当缓存的数据量达到指定的阈值(BATCH_COUNT),就执行Consumer; 当所有数据都已读取完,则会调用doAfterAllAnalysed方法,如果有存余的缓存数据,那么执行ConsumerPageReadListener源码如下:

public class PageReadListener<T> implements ReadListener<T> {
    public static int BATCH_COUNT = 100;
    private List<T> cachedDataList;
    private final Consumer<List<T>> consumer;
    private final int batchCount;

    public PageReadListener(Consumer<List<T>> consumer) {
        this(consumer, BATCH_COUNT);
    }

    public PageReadListener(Consumer<List<T>> consumer, int batchCount) {
        this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        this.consumer = consumer;
        this.batchCount = batchCount;
    }

    public void invoke(T data, AnalysisContext context) {
        this.cachedDataList.add(data);
        if (this.cachedDataList.size() >= this.batchCount) {
            this.consumer.accept(this.cachedDataList);
            this.cachedDataList = ListUtils.newArrayListWithExpectedSize(this.batchCount);
        }
    }
    
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (CollectionUtils.isNotEmpty(this.cachedDataList)) {
            this.consumer.accept(this.cachedDataList);
        }
    }
}

代码调用

service层中我们调用ExcelUtil#readExcel(InputStream inputStream, Class<?> head, PageReadListener<T> listener)方法分批读取 Excel 数据,这里需要一个PageReadListener; 而前面讲,PageReadListener需要传入一个消费函数,来定义如何消费每个批次的数据,对于业务来讲就是将每个批次的数据执行入库操作,这里就是调用dao层的代码。 代码调用如下示例:

/**
 * Excel分批读取入库接口
 * 可以实现分批次读取Excel数据再入库
 */
public interface ExcelService {

    default <T> void saveData(InputStream inputStream, Class<?> head, int batchCount) {

        PageReadListener<T> listener = new PageReadListener<T>(dataList -> {
            System.out.println(dataList.toString());
            // excelMapper.saveData(dataList);
        }, batchCount);
        ExcelUtil.readExcel(inputStream, head, listener);
    }
}
转载自:https://juejin.cn/post/7333936808789032972
评论
请登录