Day28 工作台与 Apache POI 导出报表

Day28 - 工作台与 Apache POI 导出报表

一、工作台

1.1 功能概述

工作台模块为管理端首页提供概览数据,包含四个接口:

接口 路径 返回内容
今日数据查询 /admin/workspace/businessData 营业额、有效订单数、完成率、客单价、新增用户数
订单管理数据 /admin/workspace/overviewOrders 各状态订单数量(待接单、待派送、已完成、已取消、全部)
菜品总览 /admin/workspace/overviewDishes 启售数量、停售数量
套餐总览 /admin/workspace/overviewSetmeals 启售数量、停售数量

1.2 实现方式

工作台代码以”代码导入”方式引入,共三个核心类:

  • WorkSpaceController:接收请求,今日数据查询时自动计算当天的开始和结束时间

  • WorkspaceService / WorkspaceServiceImpl:调用各 Mapper 获取统计数据

1
2
3
// Controller 中自动计算当天时间范围
LocalDateTime begin = LocalDateTime.now().with(LocalTime.MIN);
LocalDateTime end = LocalDateTime.now().with(LocalTime.MAX);

1.3 工作台依赖的 Mapper 方法

工作台大量调用 day11 引入的通用 sumByMap(Map)countByMap(Map) 方法。由于我 day11 的报表实现与讲义不同(自己用了 turnoverStatisticsordersStatistics 等专用方法),这些通用方法在代码仓库中缺失,需要补全后工作台才能正常编译。

补全的方法分布:

Mapper 方法 作用
OrderMapper Double sumByMap(Map map) 按动态条件(status + 时间范围)求营业额
OrderMapper Integer countByMap(Map map) 按动态条件统计订单数量
UserMapper Integer countByMap(Map map) 按动态条件统计用户数量
DishMapper Integer countByMap(Map map) 按动态条件(status + categoryId)统计菜品数量
SetmealMapper Integer countByMap(Map map) 按动态条件统计套餐数量

这些方法全部使用 <where> + <if> 动态 SQL,Service 层通过往 Map 里放不同的 key 来灵活控制查询条件,一个 Mapper 方法应对多种查询场景。例如 getOrderOverView 中对同一个 countByMap 传 5 次不同的 status,分别查出各状态订单数。


二、Apache POI

2.1 概述

Apache POI 是一个处理 Microsoft Office 文件格式的开源 Java 库,一般用于读写 Excel 文件。常见应用场景包括导出交易明细、业务报表、批量导入数据等。

项目通过 Maven 引入两个依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>

2.2 核心 API

POI 操作 Excel 的核心类都在 org.apache.poi.xssf.usermodel 包下,层级关系为 工作簿 → Sheet 页 → 行 → 单元格

对应 Excel 概念 常用方法
XSSFWorkbook Excel 文件(工作簿) new XSSFWorkbook(inputStream) 基于模板创建;getSheet("Sheet1") 获取 Sheet;write(out) 写入输出流;close() 关闭
XSSFSheet Sheet 页 getRow(i) 获取指定行;createRow(0) 创建行
XSSFRow getCell(j) 获取单元格;createCell(j) 创建单元格;setCellValue(value) 设置值
XSSFCell 单元格 getStringCellValue() 获取文本值

注意:行号和列号均从 0 开始计数。getRow(3) 获取的是第 4 行,getCell(2) 获取的是第 3 列。


三、导出运营数据报表

3.1 需求与接口设计

导出最近 30 天的运营数据为 Excel 文件。接口特点:

  • GET 请求,不需要任何请求参数(后端计算近 30 天范围)

  • 无返回数据,本质是文件下载——通过输出流将 Excel 写到客户端浏览器

  • 需要提前设计好 Excel 模板文件,放到 resources/template/ 目录下

3.2 实现步骤

1
2
3
4
1. 设计 Excel 模板文件(含表头、格式)
2. 查询近 30 天的运营数据(复用 WorkspaceService.getBusinessData)
3. 基于模板创建 Excel 对象,将数据写入对应单元格
4. 通过 HttpServletResponse 输出流下载到客户端

3.3 关键代码

Controller 层

1
2
3
4
@GetMapping("/export")
public void exportExcel(HttpServletResponse response) {
reportService.exportExcel(response);
}

返回类型为 void,不返回 JSON,直接操作 HttpServletResponse 的输出流。

Service 层核心实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public void exportExcel(HttpServletResponse response) {
LocalDate begin = LocalDate.now().minusDays(30);
LocalDate end = LocalDate.now().minusDays(1);

// 查询概览数据,复用工作台的 getBusinessData
BusinessDataVO businessData = workspaceService.getBusinessData(
LocalDateTime.of(begin, LocalTime.MIN),
LocalDateTime.of(end, LocalTime.MAX));

// 获取模板输入流
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("template/运营数据报表模板.xlsx");

try {
XSSFWorkbook excel = new XSSFWorkbook(in); // 基于模板创建工作簿
XSSFSheet sheet = excel.getSheet("Sheet1"); // 获取 Sheet 页

// 填充概览数据(第2行日期、第4-5行汇总指标)
sheet.getRow(1).getCell(1).setCellValue("日期: " + begin + " 至 " + end);

XSSFRow row = sheet.getRow(3);
row.getCell(2).setCellValue(businessData.getTurnover());
row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
row.getCell(6).setCellValue(businessData.getNewUsers());

row = sheet.getRow(4);
row.getCell(2).setCellValue(businessData.getValidOrderCount());
row.getCell(4).setCellValue(businessData.getUnitPrice());

// 循环填充30天明细数据(从第8行开始)
for (int i = 0; i < 30; i++) {
LocalDate date = begin.plusDays(i);
businessData = workspaceService.getBusinessData(
LocalDateTime.of(date, LocalTime.MIN),
LocalDateTime.of(date, LocalTime.MAX));
row = sheet.getRow(i + 7);
row.getCell(1).setCellValue(date.toString());
row.getCell(2).setCellValue(businessData.getTurnover());
row.getCell(3).setCellValue(businessData.getValidOrderCount());
row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
row.getCell(5).setCellValue(businessData.getUnitPrice());
row.getCell(6).setCellValue(businessData.getNewUsers());
}

// 通过输出流下载到客户端
ServletOutputStream out = response.getOutputStream();
excel.write(out);
out.flush();
out.close();
excel.close();

} catch (IOException e) {
throw new RuntimeException(e);
}
}

3.4 两个关键点

获取模板输入流——通过 ClassLoader 从 classpath 读取模板文件,而非磁盘绝对路径:

1
2
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("template/运营数据报表模板.xlsx");

前提是模板文件已放在 src/main/resources/template/ 目录下,编译后会出现在 classpath 中。

获取输出流——通过 HttpServletResponse 获取浏览器的输出流:

1
2
ServletOutputStream out = response.getOutputStream();
excel.write(out);

不需要设置响应头,浏览器会直接弹出文件下载。

3.5 模板文件的结构

模板 运营数据报表模板.xlsx 是提前设计好的 Excel 文件,包含固定的表头和格式。数据写入时需要严格对应行列号

行号(0 基) 内容
1 日期范围(第2列)
3 概览:营业额(第3列)、完成率(第5列)、新增用户(第7列)
4 概览:有效订单(第3列)、客单价(第5列)
7 ~ 36 30 天明细:日期、营业额、有效订单、完成率、客单价、新增用户

今日总结

模块 核心要点
工作台 管理端首页概览,4 个接口分别查今日营业数据、各状态订单数、菜品/套餐启售停售数
通用 Mapper 方法 sumByMap(Map) / countByMap(Map) 用动态 SQL 灵活组合条件,工作台和导出报表都依赖它们;由于 day11 自实现方式不同,需补全这些方法
Apache POI Java 操作 Excel 的库,核心类层级:XSSFWorkbookXSSFSheetXSSFRowXSSFCell,行列号从 0 开始
导出报表 GET 请求无参数无返回值,本质是文件下载;步骤为设计模板 → 查询数据 → 写入 Excel → 输出流下载
模板输入流 this.getClass().getClassLoader().getResourceAsStream("template/xxx.xlsx") 从 classpath 读取模板
浏览器输出流 response.getOutputStream() 获取输出流,excel.write(out) 写入后关闭资源