EasyExcel实现统计结果导出导入

1、需求描述

在经营看板界面可将订单趋势图的数据导出Excel

点击"导出明细",导出Excel。

示例1:

2023-10-20~2023-11-20 全国经营分析统计.xlsx

示例2:

2、EasyExcel入门

根据需求,目标是将订单趋势图的中的数据导出Excel,数据来源于按天统计表和按小时统计表,现在关键问题是使用java语言生成Excel文档

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。

Easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便。

EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。

2.1、读Excel

java 复制代码
    
package com.jzo2o.orders.history.easyexcel.read;

import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.converters.DefaultConverterLoader;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.util.ListUtils;

import com.alibaba.fastjson.JSON;
import com.jzo2o.orders.history.easyexcel.util.TestFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

/**
* 读的常见写法
*
* @author Jiaju Zhuang
*/

@Slf4j
public class ReadTest {

    /**
* 最简单的读
* <p>1. 创建excel对应的实体对象 参照{ @link DemoData}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{ @link DemoDataListener}
* <p>3. 直接读即可
*/
    @Test
    public void simpleRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    }
    ...

2.2、写Excel

java 复制代码
    
package com.jzo2o.orders.history.easyexcel.write;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.CommentData;
import com.alibaba.excel.metadata.data.FormulaData;
import com.alibaba.excel.metadata.data.HyperlinkData;
import com.alibaba.excel.metadata.data.HyperlinkData.HyperlinkType;
import com.alibaba.excel.metadata.data.ImageData;
import com.alibaba.excel.metadata.data.ImageData.ImageType;
import com.alibaba.excel.metadata.data.RichTextStringData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.util.FileUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.merge.LoopMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;

import com.jzo2o.orders.history.easyexcel.util.TestFileUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.junit.jupiter.api.Test;

/**
* 写的常见写法
*
* @author Jiaju Zhuang
*/

public class WriteTest {
    /**
* 最简单的写
* <p>1. 创建excel对应的实体对象 参照{ @link com.alibaba.easyexcel.test.demo.write.DemoData}
* <p>2. 直接写即可
*/
    @Test
    public void simpleWrite() {
        String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
    }
    ...

2.3、web上传和下载

java 复制代码
   
package com.jzo2o.orders.history.controller.test;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.ListUtils;
import com.jzo2o.orders.history.model.dto.request.HistoryOrdersListQueryReqDTO;
import com.jzo2o.orders.history.model.dto.response.HistoryOrdersDetailResDTO;
import com.jzo2o.orders.history.model.dto.response.HistoryOrdersListResDTO;
import com.jzo2o.orders.history.service.IHistoryOrdersService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;


@Controller
@RequestMapping("/excel/test")
public class ExcelController {

/**
* 文件下载(失败了会返回一个有部分数据的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(), DemoData.class).sheet("模板").doWrite(data());
       //设置返回body无需包装标识
       response.setHeader(BODY_PROCESSED, "1");                
    }

    /**
* 文件上传
* <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(), DemoData.class, new DemoDataListener()).sheet().doRead();
        return "success";
    }

3、统计结果导出

3.1、Controller

java 复制代码
package com.jzo2o.orders.history.controller.operation;

import com.jzo2o.orders.history.model.dto.response.OperationHomePageResDTO;
import com.jzo2o.orders.history.service.OrdersStatisticsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.LocalDateTime;

/**
* 订单统计
*
* @author itcast
* @create 2023/9/21 15:15
**/
@Api(tags = "运营端 - 订单统计相关接口")
@RestController("operationOrdersStatisticsController")
@RequestMapping("/operation/orders-statistics")
public class OrdersStatisticsController {
    @Resource
    private OrdersStatisticsService ordersStatisticsService;


    /**
* 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
*
* @since 2.1.1
*/
@GetMapping("downloadStatistics")
    @ApiOperation("导出统计数据")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "minTime", value = "开始时间", required = true, dataTypeClass = LocalDateTime.class),
            @ApiImplicitParam(name = "maxTime", value = "结束时间", required = true, dataTypeClass = LocalDateTime.class)
    })
    public void downloadStatistics(@RequestParam("minTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime minTime,
                                   @RequestParam("maxTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime maxTime) throws IOException {
        ordersStatisticsService.downloadStatistics(minTime, maxTime);
    }
}

3.2、Service

通过EasyExcelUtil更改excel的样式

java 复制代码
 /**
* 导出统计数据
*
* @param minTime 开始时间
* @param maxTime 结束时间
*/
@Override
public void downloadStatistics(LocalDateTime minTime, LocalDateTime maxTime) throws IOException {
    //校验查询时间
if (LocalDateTimeUtil.between(minTime, maxTime, ChronoUnit.DAYS) > 365) {
        throw new ForbiddenOperationException("查询时间区间不能超过一年");
    }

    HttpServletResponse response = ResponseUtils.getResponse();
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    //设置返回body无需包装标识
response.setHeader(BODY_PROCESSED, "1");
    try {
        //如果查询日期是同一天,则按小时导出数据
if (LocalDateTimeUtil.beginOfDay(maxTime).equals(minTime)) {
            downloadHourStatisticsData(response, minTime);
        } else {
            //如果查询日期不是同一天,则按日导出数据
downloadDayStatisticsData(response, minTime, maxTime);
        }

    } catch (Exception e) {
        // 重置response,默认失败了会返回一个有部分数据的Excel
response.reset();
        response.setContentType("application/json");
        Map<String, String> map = MapUtils.newHashMap();
        map.put("status", "failure");
        map.put("message", "下载文件失败" + e.getMessage());
        response.getWriter().println(JSON.toJSONString(map));
    }
}

private void downloadDayStatisticsData(HttpServletResponse response, LocalDateTime minTime, LocalDateTime maxTime) throws IOException {
        //模板文件路径
String templateFileName = "static/day_statistics_template.xlsx";

        //转换时间格式,拼接下载文件名称
String fileNameMinTimeStr = LocalDateTimeUtil.format(minTime, DatePattern.NORM_DATE_PATTERN);
        String fileNameMaxTimeStr = LocalDateTimeUtil.format(maxTime, DatePattern.NORM_DATE_PATTERN);
        String fileName = fileNameMinTimeStr + "~" + fileNameMaxTimeStr + " 全国经营分析统计.xlsx";
        // 这里URLEncoder.encode可以防止中文乱码
fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
        String currentTime = LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy/MM/dd HH:mm:ss");

        //根据id区间查询按天统计数据,并转为map,key为日期,value为日期对应统计数据
String minTimeDayStr = LocalDateTimeUtil.format(minTime, DatePattern.PURE_DATE_PATTERN);
        String maxTimeDayStr = LocalDateTimeUtil.format(maxTime, DatePattern.PURE_DATE_PATTERN);
        //查询按天统计表
List<StatDay> statDayList = statDayService.queryListByIdRange(Long.valueOf(minTimeDayStr), Long.valueOf(maxTimeDayStr));
        //转成List<StatisticsData>
List<StatisticsData> statisticsDataList = BeanUtils.copyToList(statDayList, StatisticsData.class);

        //按月份切分统计数据, 有几个月list中就有几条记录
List<ExcelMonthData> excelMonthDataList = cutDataListByMonth(statisticsDataList);

        // 生成CellWriteHandler对象,在向单元格写数据时会调用它的afterCellDispose方法
 //  getSpecialHandleDataInfo()方法找到需要格式化处理的行索引,返回CellWriteHandler对象
EasyExcelUtil easyExcelUtil = getSpecialHandleDataInfo(excelMonthDataList);

        try (ExcelWriter excelWriter = EasyExcel
                //注意,服务器上以jar包运行,只能使用下面第2种方式,第1种方式只能在本地运行成功
//                .write(fileName, StatisticsData.class)
.write(response.getOutputStream(), StatisticsData.class)
                //注意,服务器上以jar包运行,只能使用下面第3种方式,前2种方式只能在本地运行成功
//                .withTemplate(templateFileName)
//                .withTemplate(FileUtil.getInputStream(templateFileName))
.withTemplate(FileUtil.class.getClassLoader().getResourceAsStream(templateFileName))
                .autoCloseStream(Boolean.FALSE)
                .registerWriteHandler(easyExcelUtil).build()) {
            // 按天统计,选择第1个sheet,把sheet设置为不需要头
WriteSheet writeSheet = EasyExcel.writerSheet(0).needHead(Boolean.FALSE).build();

            //构建填充数据,map的key对应模板文件中的{}中的名称
Map<String, Object> map = MapUtils.newHashMap();
            map.put("startTime", fileNameMinTimeStr);
            map.put("endTime", fileNameMaxTimeStr);
            map.put("currentTime", currentTime);


            //写入填充数据
excelWriter.fill(map, writeSheet);
            //向单元格式依次写入数据
for (ExcelMonthData excelMonthData : excelMonthDataList) {
                MonthElement monthElement = new MonthElement(excelMonthData.getMonth());
                excelWriter.write(List.of(monthElement), writeSheet); //月份
excelWriter.write(excelMonthData.getStatisticsDataList(), writeSheet); //每天的数据
excelWriter.write(List.of(excelMonthData.getMonthAggregation()), writeSheet); //该天的汇总数据
}

            excelWriter.finish();
        }
    }
相关推荐
我该如何取个名字3 分钟前
Mac配置Java的环境变量
java·开发语言·macos
kkkkatoq8 分钟前
Java中的锁
java·开发语言
界面开发小八哥21 分钟前
「Java EE开发指南」用MyEclipse开发EJB 3无状态会话Bean(二)
java·ide·java-ee·eclipse·myeclipse
LCY13327 分钟前
spring security +kotlin 实现oauth2.0 认证
java·spring·kotlin
soulermax29 分钟前
数字ic后端设计从入门到精通2(含fusion compiler, tcl教学)
java·linux·服务器
我的代码永没有bug35 分钟前
day1-小白学习JAVA---JDK安装和环境变量配置(mac版)
java·学习·macos
王有品1 小时前
Spring MVC 一个简单的多文件上传
java·spring·mvc
Johnny Lnex1 小时前
JVM之经典垃圾回收器
java
那就摆吧1 小时前
数据结构-栈
android·java·c语言·数据结构
du fei1 小时前
C# 单例模式
java·单例模式·c#