# 苍穹外卖 day12
# 课程内容
- 工作台
- Apache POI
- 导出运营数据 Excel 报表
功能实现:工作台、数据导出
工作台效果图:
数据导出效果图:
在数据统计页面点击数据导出:生成 Excel 报表
# 工作台
# 需求分析和设计
# 产品原型
工作台是系统运营的数据看板,并提供快捷操作入口,可以有效提高商家的工作效率。
工作台展示的数据:
原型图:
名词解释:
- 营业额:已完成订单的总金额
- 有效订单:已完成订单的数量
- 订单完成率:有效订单数 / 总订单数 * 100%
- 平均客单价:营业额 / 有效订单数
- 新增用户:新增用户的数量
# 接口设计
通过上述原型图分析,共包含 6 个接口。
接口设计:
- 今日数据接口
- 订单管理接口
- 菜品总览接口
- 套餐总览接口
- 订单搜索(已完成)
- 各个状态的订单数量统计(已完成)
1). 今日数据的接口设计
2). 订单管理的接口设计
3). 菜品总览的接口设计
4). 套餐总览的接口设计
# 代码开发
# Controller 层
添加 WorkSpaceController.java
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package com.sky.controller.admin;
import com.sky.result.Result; import com.sky.service.WorkspaceService; import com.sky.vo.BusinessDataVO; import com.sky.vo.DishOverViewVO; import com.sky.vo.OrderOverViewVO; import com.sky.vo.SetmealOverViewVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.LocalTime;
@RestController @RequestMapping("/admin/workspace") @Slf4j @Api(tags = "工作台相关接口") public class WorkSpaceController {
@Autowired private WorkspaceService workspaceService;
@GetMapping("/businessData") @ApiOperation("工作台今日数据查询") public Result<BusinessDataVO> businessData(){ LocalDateTime begin = LocalDateTime.now().with(LocalTime.MIN); LocalDateTime end = LocalDateTime.now().with(LocalTime.MAX);
BusinessDataVO businessDataVO = workspaceService.getBusinessData(begin, end); return Result.success(businessDataVO); }
@GetMapping("/overviewOrders") @ApiOperation("查询订单管理数据") public Result<OrderOverViewVO> orderOverView(){ return Result.success(workspaceService.getOrderOverView()); }
@GetMapping("/overviewDishes") @ApiOperation("查询菜品总览") public Result<DishOverViewVO> dishOverView(){ return Result.success(workspaceService.getDishOverView()); }
@GetMapping("/overviewSetmeals") @ApiOperation("查询套餐总览") public Result<SetmealOverViewVO> setmealOverView(){ return Result.success(workspaceService.getSetmealOverView()); } }
|
# Service 层接口
添加 WorkspaceService.java
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
| package com.sky.service;
import com.sky.vo.BusinessDataVO; import com.sky.vo.DishOverViewVO; import com.sky.vo.OrderOverViewVO; import com.sky.vo.SetmealOverViewVO; import java.time.LocalDateTime;
public interface WorkspaceService {
BusinessDataVO getBusinessData(LocalDateTime begin, LocalDateTime end);
OrderOverViewVO getOrderOverView();
DishOverViewVO getDishOverView();
SetmealOverViewVO getSetmealOverView();
}
|
# Service 层实现类
添加 WorkspaceServiceImpl.java
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
| package com.sky.service.impl;
import com.sky.constant.StatusConstant; import com.sky.entity.Orders; import com.sky.mapper.DishMapper; import com.sky.mapper.OrderMapper; import com.sky.mapper.SetmealMapper; import com.sky.mapper.UserMapper; import com.sky.service.WorkspaceService; import com.sky.vo.BusinessDataVO; import com.sky.vo.DishOverViewVO; import com.sky.vo.OrderOverViewVO; import com.sky.vo.SetmealOverViewVO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.HashMap; import java.util.Map;
@Service @Slf4j public class WorkspaceServiceImpl implements WorkspaceService {
@Autowired private OrderMapper orderMapper; @Autowired private UserMapper userMapper; @Autowired private DishMapper dishMapper; @Autowired private SetmealMapper setmealMapper;
public BusinessDataVO getBusinessData(LocalDateTime begin, LocalDateTime end) {
Map map = new HashMap(); map.put("begin",begin); map.put("end",end);
Integer totalOrderCount = orderMapper.countByMap(map);
map.put("status", Orders.COMPLETED); Double turnover = orderMapper.sumByMap(map); turnover = turnover == null? 0.0 : turnover;
Integer validOrderCount = orderMapper.countByMap(map);
Double unitPrice = 0.0;
Double orderCompletionRate = 0.0; if(totalOrderCount != 0 && validOrderCount != 0){ orderCompletionRate = validOrderCount.doubleValue() / totalOrderCount; unitPrice = turnover / validOrderCount; }
Integer newUsers = userMapper.countByMap(map);
return BusinessDataVO.builder() .turnover(turnover) .validOrderCount(validOrderCount) .orderCompletionRate(orderCompletionRate) .unitPrice(unitPrice) .newUsers(newUsers) .build(); }
public OrderOverViewVO getOrderOverView() { Map map = new HashMap(); map.put("begin", LocalDateTime.now().with(LocalTime.MIN)); map.put("status", Orders.TO_BE_CONFIRMED);
Integer waitingOrders = orderMapper.countByMap(map);
map.put("status", Orders.CONFIRMED); Integer deliveredOrders = orderMapper.countByMap(map);
map.put("status", Orders.COMPLETED); Integer completedOrders = orderMapper.countByMap(map);
map.put("status", Orders.CANCELLED); Integer cancelledOrders = orderMapper.countByMap(map);
map.put("status", null); Integer allOrders = orderMapper.countByMap(map);
return OrderOverViewVO.builder() .waitingOrders(waitingOrders) .deliveredOrders(deliveredOrders) .completedOrders(completedOrders) .cancelledOrders(cancelledOrders) .allOrders(allOrders) .build(); }
public DishOverViewVO getDishOverView() { Map map = new HashMap(); map.put("status", StatusConstant.ENABLE); Integer sold = dishMapper.countByMap(map);
map.put("status", StatusConstant.DISABLE); Integer discontinued = dishMapper.countByMap(map);
return DishOverViewVO.builder() .sold(sold) .discontinued(discontinued) .build(); }
public SetmealOverViewVO getSetmealOverView() { Map map = new HashMap(); map.put("status", StatusConstant.ENABLE); Integer sold = setmealMapper.countByMap(map);
map.put("status", StatusConstant.DISABLE); Integer discontinued = setmealMapper.countByMap(map);
return SetmealOverViewVO.builder() .sold(sold) .discontinued(discontinued) .build(); } }
|
# Mapper 层
在 SetmealMapper 中添加 countByMap 方法定义
1 2 3 4 5 6
|
Integer countByMap(Map map);
|
在 SetmealMapper.xml 中添加对应 SQL 实现
1 2 3 4 5 6 7 8 9 10 11
| <select id="countByMap" resultType="java.lang.Integer"> select count(id) from setmeal <where> <if test="status != null"> and status = #{status} </if> <if test="categoryId != null"> and category_id = #{categoryId} </if> </where> </select>
|
在 DishMapper 中添加 countByMap 方法定义
1 2 3 4 5 6
|
Integer countByMap(Map map);
|
在 DishMapper.xml 中添加对应 SQL 实现
1 2 3 4 5 6 7 8 9 10 11
| <select id="countByMap" resultType="java.lang.Integer"> select count(id) from dish <where> <if test="status != null"> and status = #{status} </if> <if test="categoryId != null"> and category_id = #{categoryId} </if> </where> </select>
|
# 功能测试
可以通过如下方式进行测试:
接下来我们使用上述两种方式分别测试。
# 接口文档测试
启动服务,访问 http://localhost:8080/doc.html,进入工作台相关接口
注意:
使用 admin 用户登录重新获取 token,在全局参数设置中添加,防止 token 失效。
1). 今日数据查询
2). 菜品总览查询
3). 订单管理数据查询
4). 套餐总览查询
# 前后端联调测试
启动 nginx, 访问 http://localhost,进入工作台
进入开发者模式,分别查看今日数据、订单管理、菜品总览、套餐总览
1). 今日数据查询
2). 订单管理数据查询
3). 菜品总览查询
4). 套餐总览查询
# Apache POI
# 介绍
Apache POI 是一个处理 Miscrosoft Office 各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对 Miscrosoft Office 各种文件进行读写操作。
一般情况下,POI 都是用于操作 Excel 文件。
Apache POI 的应用场景:
银行网银系统导出交易明细
各种业务系统导出 Excel 报表
批量导入业务数据
# 入门案例
Apache POI 既可以将数据写入 Excel 文件,也可以读取 Excel 文件中的数据,接下来分别进行实现。
Apache POI 的 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>
|
# 将数据写入 Excel 文件
1). 代码开发
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
| package com.sky.test;
import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream;
public class POITest {
public static void write() throws Exception{ XSSFWorkbook excel = new XSSFWorkbook(); XSSFSheet sheet = excel.createSheet("itcast");
XSSFRow row1 = sheet.createRow(0); row1.createCell(1).setCellValue("姓名"); row1.createCell(2).setCellValue("城市");
XSSFRow row2 = sheet.createRow(1); row2.createCell(1).setCellValue("张三"); row2.createCell(2).setCellValue("北京");
XSSFRow row3 = sheet.createRow(2); row3.createCell(1).setCellValue("李四"); row3.createCell(2).setCellValue("上海");
FileOutputStream out = new FileOutputStream(new File("D:\\itcast.xlsx")); excel.write(out);
out.flush(); out.close(); excel.close(); } public static void main(String[] args) throws Exception { write(); } }
|
2). 实现效果
在 D 盘中生成 itcast.xlsx 文件,创建名称为 itcast 的 Sheet 页,同时将内容成功写入。
# 读取 Excel 文件中的数据
1). 代码开发
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
| package com.sky.test;
import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream;
public class POITest {
public static void read() throws Exception{ FileInputStream in = new FileInputStream(new File("D:\\itcast.xlsx")); XSSFWorkbook excel = new XSSFWorkbook(in); XSSFSheet sheet = excel.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int i = 0; i <= lastRowNum; i++) { XSSFRow titleRow = sheet.getRow(i); XSSFCell cell1 = titleRow.getCell(1); String cellValue1 = cell1.getStringCellValue(); XSSFCell cell2 = titleRow.getCell(2); String cellValue2 = cell2.getStringCellValue();
System.out.println(cellValue1 + " " +cellValue2); }
in.close(); excel.close(); }
public static void main(String[] args) throws Exception { read(); } }
|
2). 实现效果
将 itcast.xlsx 文件中的数据进行读取
# 导出运营数据 Excel 报表
# 需求分析和设计
# 产品原型
在数据统计页面,有一个数据导出的按钮,点击该按钮时,其实就会下载一个文件。这个文件实际上是一个 Excel 形式的文件,文件中主要包含最近 30 日运营相关的数据。表格的形式已经固定,主要由概览数据和明细数据两部分组成。真正导出这个报表之后,相对应的数字就会填充在表格中,就可以进行存档。
原型图:
导出的 Excel 报表格式:
业务规则:
- 导出 Excel 形式的报表文件
- 导出最近 30 天的运营数据
# 接口设计
通过上述原型图设计对应的接口。
注意:
# 代码开发
# 实现步骤
1). 设计 Excel 模板文件
2). 查询近 30 天的运营数据
3). 将查询到的运营数据写入模板文件
4). 通过输出流将 Excel 文件下载到客户端浏览器
# Controller 层
根据接口定义,在 ReportController 中创建 export 方法:
1 2 3 4 5 6 7 8 9
|
@GetMapping("/export") @ApiOperation("导出运营数据报表") public void export(HttpServletResponse response){ reportService.exportBusinessData(response); }
|
# Service 层接口
在 ReportService 接口中声明导出运营数据报表的方法:
1 2 3 4 5
|
void exportBusinessData(HttpServletResponse response);
|
# Service 层实现类
在 ReportServiceImpl 实现类中实现导出运营数据报表的方法:
提前将资料中的运营数据报表模板.xlsx 拷贝到项目的 resources/template 目录中
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
|
public void exportBusinessData(HttpServletResponse response) { LocalDate begin = LocalDate.now().minusDays(30); LocalDate end = LocalDate.now().minusDays(1); BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(begin,LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX)); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx"); try { XSSFWorkbook excel = new XSSFWorkbook(inputStream); XSSFSheet sheet = excel.getSheet("Sheet1");
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()); 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(7 + i); 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){ e.printStackTrace(); } }
|
# 功能测试
直接使用前后端联调测试。
进入数据统计
点击数据导出:
Excel 报表下载成功