Java项目苍穹外卖:接口开发(5)

数据统计

图形报表

Apache ECharts

介绍

Apache ECharts是一款基于Javascript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。

官网地址:https://echarts.apache.org/zh/index.html

效果:通过直观的图表来展示数据。

入门案例

Apache Echarts官方提供的快速入门:https://echarts.apache.org/handbook/zh/get-started/

总结:使用Echarts,重点在于研究当前图表所需的数据格式。通常是需要后端提供符合格式要求的动态数据,然后响应给前端来展示图表。

注意:需要官网下载echarts.js文件,并通过<script src="echarts.js"></script>引入才能使用。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ECharts</title>
<!-- 引入刚刚下载的 ECharts 文件 -->
<script src="echarts.js"></script>
</head>
<body>
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));

// 指定图表的配置项和数据
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
};

// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</body>
</html>

营业额统计

产品原型

业务规则:

  • 营业额指订单状态为已完成的订单金额合计。
  • 基于可视化报表的折线图展示营业额数据,X轴为日期,Y轴为营业额。
  • 根据时间选择区间,展示每天的营业额数据。
接口设计

代码开发

根据接口定义设计对应的VO

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sky.vo;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TurnoverReportVO implements Serializable {
//日期,以逗号分隔,例如:2022-10-01,2022-10-02,2022-10-03
private String dateList;

//营业额,以逗号分隔,例如:406.0,1520.0,75.0
private String turnoverList;
}

根据接口定义创建ReportController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.sky.controller.admin;

//数据统计相关接口
@RestController()
@RequestMapping("/admin/report")
@Api(tags = "数据统计相关接口")
@Slf4j
public class ReportController {
@Autowired
private ReportService reportService;

//营业额统计
@GetMapping("turnoverStatistics")
@ApiOperation("营业额统计")
public Result<TurnoverReportVO> turnoverStatistics(
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
log.info("营业额数据统计:{},{}", begin, end);
return Result.success(reportService.getTurnoverStatistics(begin, end));
}
}

创建ReportService接口,声明getTurnover方法。

1
2
3
4
5
6
7
package com.sky.service;

@Service
public interface ReportService {
//统计指定时间区间内的营业额数据
TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end);
}

创建ReportServiceImpl实现类,实现getTurnover方法。

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
package com.sky.service.impl;

@Service
@Slf4j
public class ReportServiceImpl implements ReportService {
@Autowired
private OrderService orderService;
@Autowired
private OrderMapper orderMapper;

//统计指定时间区间内的营业额数据
@Override
public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) {
//当前集合用于存放从begin到end范围内的每天的日期
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);

/* if(begin.isAfter(end)) {//我自己加的,如果begin大于end,则不合法
return null;
}*/

while(!begin.equals(end)) {
//日期计算,计算指定日期的后一天对应的日期
begin = begin.plusDays(1);
dateList.add(begin);
}

//存放每天的营业额
List<Double> turnoverList = new ArrayList<>();
for(LocalDate date : dateList) {
//查询Date日期对应的营业额数据,营业额是指:状态为“已完成”的订单金额合计
//LocalTime.MIN是00:00,LocalTime.MAX是23:59:59.999999999
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);

//select sum(amount) from orders where order_time > beginTime and order_time <endTime and status = 5
Map map = new HashMap();
map.put("begin", beginTime);
map.put("end", endTime);
map.put("status", Orders.COMPLETED);
Double turnover = orderMapper.sumByMap(map);//如果没查找到营业额数据返回null
turnover = turnover == null ? 0 : turnover;
turnoverList.add(turnover);
}

return TurnoverReportVO.builder()
.dateList(StringUtils.join(dateList, ","))//用import org.apache.commons.lang3.StringUtils;包下的StringUtils
.turnoverList(StringUtils.join(turnoverList, ","))
.build();
}
}

OrderMapper接口声明sumByMap方法。

1
2
//根据动态条件统计营业额数据
Double sumByMap(Map map);

OrderMapper.xml文件中编写动态SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="sumByMap" resultType="java.lang.Double">
select sum(amount) from orders
<where>
<if test="begin != null">
and order_time &gt; #{begin}
</if>
<if test="end != null">
and order_time &lt; #{end}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>

用户统计

产品原型

业务规则:

  • 基于可视化报表的折线图展示用户数据,X轴为日期,Y轴为用户数。
  • 根据时间选择区间,展示每天的用户总量和新增用户量数据。
接口设计

代码开发

根据用户统计接口的返回结果设计VO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.sky.vo;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserReportVO implements Serializable {
//日期,以逗号分隔,例如:2022-10-01,2022-10-02,2022-10-03
private String dateList;

//用户总量,以逗号分隔,例如:200,210,220
private String totalUserList;

//新增用户,以逗号分隔,例如:20,21,10
private String newUserList;
}

根据接口定义,在ReportController中创建userStatistics方法。

1
2
3
4
5
6
7
8
9
//用户统计
@GetMapping("userStatistics")
@ApiOperation("用户统计")
public Result<UserReportVO> userStatistics(
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
log.info("用户统计数据:{},{}", begin, end);
return Result.success(reportService.getUserStatistics(begin, end));
}

ReportService接口中声明getUserStatistics方法。

1
2
//统计指定时间区间内的用户数据
UserReportVO getUserStatistics(LocalDate begin, LocalDate end);

ReportServiceImpl实现类中实现getUserStatistics方法。

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
@Autowired
private UserMapper userMapper;

//统计指定时间区间内的用户数据
@Override
public UserReportVO getUserStatistics(LocalDate begin, LocalDate end) {
//存放从begin到end之间的每天对应的日期
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
while(!begin.equals(end)) {
begin = begin.plusDays(1);
dateList.add(begin);
}

//存放每天的新增用户数量 select count(id) from user where create_time < ? and create_time > ?
List<Integer> newUserList = new ArrayList<>();
//存放每天的总用户数量 select count(id) from user where create_time < ?
List<Integer> totalUserList = new ArrayList<>();

for(LocalDate date : dateList) {
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);

Map map = new HashMap();
map.put("end", endTime);
//总用户数量
Integer totalUser = userMapper.countByMap(map);

map.put("begin", beginTime);
//新增用户数量
Integer newUser = userMapper.countByMap(map);

totalUserList.add(totalUser);
newUserList.add(newUser);
}

return UserReportVO.builder()
.dateList(StringUtils.join(dateList, ","))
.totalUserList(StringUtils.join(totalUserList, ","))
.newUserList(StringUtils.join(newUserList, ","))
.build();
}

ReportServiceImpl实现类中创建私有方法getUserCount。(这个用不用都可以)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 根据时间区间统计用户数量
* @param beginTime
* @param endTime
* @return
*/
private Integer getUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
Map map = new HashMap();
map.put("begin",beginTime);
map.put("end", endTime);
return userMapper.countByMap(map);
}
/* //如果使用该方法,可以调用这个方法统计用户数量。
//新增用户数量 select count(id) from user where create_time > ? and create_time < ?
Integer newUser = getUserCount(beginTime, endTime);
//总用户数量 select count(id) from user where create_time < ?
Integer totalUser = getUserCount(null, endTime);
*/

UserMapper接口中声明countByMap方法。

1
2
//根据动态条件统计用户数量
Integer countByMap(Map map);

UserMapper.xml文件中编写动态SQL

1
2
3
4
5
6
7
8
9
10
11
<select id="countByMap" resultType="java.lang.Integer">
select count(id) from user
<where>
<if test="begin != null">
and create_time &gt; #{begin}
</if>
<if test="end != null">
and create_time &lt; #{end}
</if>
</where>
</select>

订单统计

产品原型

业务规则:

  • 有效订单指状态为 “已完成” 的订单。
  • 基于可视化报表的折线图展示订单数据,X轴为日期,Y轴为订单数量。
  • 根据时间选择区间,展示每天的订单总数和有效订单数。
  • 展示所选时间区间内的有效订单数、总订单数、订单完成率,订单完成率 = 有效订单数 / 总订单数 * 100%
接口设计

代码开发

根据订单统计接口的返回结果设计VO

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
package com.sky.vo;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderReportVO implements Serializable {
//日期,以逗号分隔,例如:2022-10-01,2022-10-02,2022-10-03
private String dateList;

//每日订单数,以逗号分隔,例如:260,210,215
private String orderCountList;

//每日有效订单数,以逗号分隔,例如:20,21,10
private String validOrderCountList;

//订单总数
private Integer totalOrderCount;

//有效订单数
private Integer validOrderCount;

//订单完成率
private Double orderCompletionRate;
}

ReportController中根据订单统计接口创建orderStatistics方法。

1
2
3
4
5
6
7
8
9
//订单统计
@GetMapping("ordersStatistics")
@ApiOperation("订单统计")
public Result<OrderReportVO> ordersStatistics(
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
log.info("用户统计数据:{},{}", begin, end);
return Result.success(reportService.getOrdersStatistics(begin, end));
}

ReportService接口中声明getOrderStatistics方法。

1
2
//统计指定时间区间内的订单数据
OrderReportVO getOrdersStatistics(LocalDate begin, LocalDate end);

ReportServiceImpl实现类中实现getOrderStatistics方法。

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
//统计指定时间区间内的订单数据
@Override
public OrderReportVO getOrdersStatistics(LocalDate begin, LocalDate end) {
//存放从begin到end之间的每天对应的日期
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
while(!begin.equals(end)) {
begin = begin.plusDays(1);
dateList.add(begin);
}

//存放每天的订单总数
List<Integer> orderCountList = new ArrayList<>();
List<Integer> validOrderCountList = new ArrayList<>();

//遍历dateList集合,查询每天的有效订单数和订单总数
for(LocalDate date : dateList) {
//查询每天的订单总数 select count(id) from orders where create_time > ? and create_time < ?
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
Integer orderCount = getOrderCount(beginTime, endTime, null);

//查询每天的有效订单数 select count(id) from orders where create_time > ? and create_time < ? and status = 5
Integer validOrderCount = getOrderCount(beginTime, endTime, Orders.COMPLETED);

orderCountList.add(orderCount);
validOrderCountList.add(validOrderCount);
}

//计算时间区间内的订单总数量
Integer totalOrderCount = orderCountList.stream().reduce(Integer::sum).get();

//计算时间区间内的有效订单数量
Integer validOrderCount = validOrderCountList.stream().reduce(Integer::sum).get();

Double orderCompletionRate = 0.0;
if(totalOrderCount != 0){
//计算订单完成率
orderCompletionRate = validOrderCount.doubleValue() / totalOrderCount;
}

return OrderReportVO.builder()
.dateList(StringUtils.join(dateList, ","))
.orderCountList(StringUtils.join(orderCountList, ","))
.validOrderCountList(StringUtils.join(validOrderCountList, ","))
.totalOrderCount(totalOrderCount)
.validOrderCount(validOrderCount)
.orderCompletionRate(orderCompletionRate)
.build();
}

ReportServiceImpl实现类中提供私有方法getOrderCount

1
2
3
4
5
6
7
8
//根据条件统计订单数量
private Integer getOrderCount(LocalDateTime begin, LocalDateTime end, Integer status) {
Map map = new HashMap();
map.put("begin", begin);
map.put("end", end);
map.put("status", status);
return orderMapper.countByMap(map);
}

OrderMapper接口中声明countByMap方法。

1
2
//根据动态条件统计订单数量
Integer countByMap(Map map);

OrderMapper.xml文件中编写动态SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="countByMap" resultType="java.lang.Integer">
select count(id) from orders
<where>
<if test="begin != null">
and order_time &gt; #{begin}
</if>
<if test="end != null">
and order_time &lt; #{end}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>

销量排名Top10

产品原型

业务规则:

  • 根据时间选择区间,展示销量前10的商品(包括菜品和套餐)。
  • 基于可视化报表的柱状图降序展示商品销量。
  • 此处的销量为商品销售的份数。
接口设计

代码开发

根据销量排名接口的返回结果设计VO

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sky.vo;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SalesTop10ReportVO implements Serializable {
//商品名称列表,以逗号分隔,例如:鱼香肉丝,宫保鸡丁,水煮鱼
private String nameList;

//销量列表,以逗号分隔,例如:260,215,200
private String numberList;
}

ReportController中根据销量排名接口创建top10方法。

1
2
3
4
5
6
7
8
9
//销售排名top10
@GetMapping("/top10")
@ApiOperation("销售排名top10")
public Result<SalesTop10ReportVO> top10(
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
log.info("销售排名top10:{},{}", begin, end);
return Result.success(reportService.getSalesTop10(begin, end));
}

ReportService接口中声明getSalesTop10方法。

1
2
//统计指定时间区间内的销量排名前10
SalesTop10ReportVO getSalesTop10(LocalDate begin, LocalDate end);

ReportServiceImpl实现类中实现getSalesTop10方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//统计指定时间区间内的销量排名前10
@Override
public SalesTop10ReportVO getSalesTop10(LocalDate begin, LocalDate end) {
LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);

List<GoodsSalesDTO> salesTop10 = orderMapper.getSalesTop10(beginTime, endTime);
List<String> names = salesTop10.stream().map(GoodsSalesDTO::getName).collect(Collectors.toList());
String nameList = StringUtils.join(names, ",");

List<Integer> numbers = salesTop10.stream().map(GoodsSalesDTO::getNumber).collect(Collectors.toList());
String numberList = StringUtils.join(numbers, ",");

return SalesTop10ReportVO.builder()
.nameList(nameList)
.numberList(numberList)
.build();
}

OrderMapper接口中声明getSalesTop10方法。

1
2
//统计指定时间区间内的销量排名前10
List<GoodsSalesDTO> getSalesTop10(LocalDateTime begin, LocalDateTime end);

OrderMapper.xml文件中编写动态SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getSalesTop10" resultType="com.sky.dto.GoodsSalesDTO">
select od.name, sum(od.number) number
from order_detail od, orders o
where od.order_id = o.id and o.status = 5
<if test="begin != null">
and o.order_time &gt; #{begin}
</if>
<if test="end != null">
and o.order_time &lt; #{end}
</if>
group by od.name
order by number desc
limit 0,10
</select>

Excel报表

工作台

产品原型

工作台是系统运营的数据看板,并提供快捷操作入口,可以有效提高商家的工作效率。

工作台展示的数据:

  • 今日数据
  • 订单管理
  • 菜品总览
  • 套餐总览
  • 订单信息

名词解释:

  • 营业额:已完成订单的总金额。
  • 有效订单:已完成订单的数量。
  • 订单完成率:有效订单数 / 总订单数 * 100%。
  • 平均客单价:营业额 / 有效订单数。
  • 新增用户:新增用户的数量。

接口设计

1.今日数据接口。

2.订单管理接口。

3.菜品总览接口。

4.套餐总览接口。

5.订单搜索(已完成)。

6.各个状态的订单数量统计(已完成)。

代码开发

1.WorkSpaceController

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
package com.sky.controller.admin;

//工作台
@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());
}
}

2.WorkspaceService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.sky.service;

public interface WorkspaceService {

//根据时间段统计营业数据
BusinessDataVO getBusinessData(LocalDateTime begin, LocalDateTime end);

//查询订单管理数据
OrderOverViewVO getOrderOverView();

//查询菜品总览
DishOverViewVO getDishOverView();

//查询套餐总览
SetmealOverViewVO getSetmealOverView();
}

3.WorkspaceServiceImpl

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
package com.sky.service.impl;

@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();
}
}

4.DishMapper

1
2
//根据条件统计菜品数量
Integer countByMap(Map map);

5.DishMapper.xml

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>

6.SetmealMapper

1
2
//根据条件统计套餐数量
Integer countByMap(Map map);

7.SetmealMapper.xml

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>

Apache POI

介绍

Apache POI是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用POIJava程序中对Miscrosoft Office各种文件进行读写操作。

一般情况下,POI都是用于操作Excel文件。

Apache POI的应用场景:

  • 银行网银系统导出交易明细。
  • 各种业务系统导出Excel报表。
  • 批量导入业务数据。
入门案例

Apache POImaven坐标。

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
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
package com.sky.test;

//使用POI操作Excel文件
public class POITest {
//通过POI创建Excel文件并且写入文件内容
public static void write() throws Exception {
//在内存中创建一个Sheet页
XSSFWorkbook excel = new XSSFWorkbook();
//在Excel文件中创建一个sheet页
XSSFSheet sheet = excel.createSheet("info");
//在Sheet中创建行对象,rownum编号从0开始
XSSFRow row = sheet.createRow(1);
//创建单元格并且写入文件内容
row.createCell(1).setCellValue("姓名");
row.createCell(2).setCellValue("城市");

//创建一个新行
row = sheet.createRow(2);
row.createCell(1).setCellValue("张三");
row.createCell(2).setCellValue("北京");

row = sheet.createRow(3);
row.createCell(1).setCellValue("李四");
row.createCell(2).setCellValue("深圳");

//通过输出流将内存中的Excel文件写入到磁盘
FileOutputStream out = new FileOutputStream(new File("E:\\2_learn\\Java-waimai\\code\\info.xlsx"));
excel.write(out);

//关闭资源
out.close();
excel.close();
}

public static void main(String[] args) throws Exception {
write();
}
}

读取Excel文件中的数据。

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
//通过POI读取Excel文件中的内容
public static void read() throws Exception {
InputStream in = new FileInputStream(new File("E:\\2_learn\\Java-waimai\\code\\info.xlsx"));

//读取磁盘上已经存在的Excel文件
XSSFWorkbook excel = new XSSFWorkbook(in);
//读取Excel文件中的第一个sheet页
XSSFSheet sheet = excel.getSheetAt(0);

//获取Sheet中最后一行的行号
int lastRowNum = sheet.getLastRowNum();

for(int i = 1; i <= lastRowNum; i++) {
//获得某一行
XSSFRow row = sheet.getRow(i);
//获得单元格对象
String cellValue1 = row.getCell(1).getStringCellValue();
String cellValue2 = row.getCell(2).getStringCellValue();
System.out.println(cellValue1 + " " + cellValue2);
}

//关闭资源
in.close();
excel.close();
}

public static void main(String[] args) throws Exception {
//write();
read();
}

导出运营数据Excel报表

产品原型

导出的Excel报表格式:

业务规则:

  • 导出Excel形式的报表文件。
  • 导出最近30天的运营数据。
接口设计

【注意】当前接口没有返回数据,因为报表导出功能本质上是文件下载,服务端会通过输出流将Excel文件下载到客户端浏览器。

代码开发

实现步骤:

  • 设计Excel模板文件。
  • 查询近30天的运营数据。
  • 将查询到的运营数据写入模板文件。
  • 通过输出流将Excel文件下载到客户端浏览器。

1.根据接口定义,在ReportController中创建export方法。

1
2
3
4
5
6
7
//导出运营数据报表
@GetMapping("/export")
@ApiOperation("导出运营数据报表")
public void export(HttpServletResponse response){
//通过HttpServletResponse获得输出流对象,将文件下载到客户端浏览器
reportService.exportBusinessData(response);
}

2.在ReportService接口中声明导出运营数据报表的方法。

1
2
//导出运营数据报表
void exportBusinessData(HttpServletResponse response);

3.在ReportServiceImpl实现类中实现导出运营数据报表的方法。

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
@Autowired
private WorkspaceService workspaceService;

//导出运营数据报表
@Override
public void exportBusinessData(HttpServletResponse response) {
//1.查询数据库,获取营业数据:查询最近30天的运营数据
LocalDate dateBegin = LocalDate.now().minusDays(30);
LocalDate dateEnd = LocalDate.now().minusDays(1);

//查询数据概览
BusinessDataVO businessDataVO = workspaceService.getBusinessData(
LocalDateTime.of(dateBegin, LocalTime.MIN),
LocalDateTime.of(dateEnd, LocalTime.MAX));

//2.通过POI将数据写入到Excel文件中
InputStream in = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");

try{
//基于模板文件创建一个新的Excel文件
XSSFWorkbook excel = new XSSFWorkbook(in);

//获取表格文件的Sheet页
XSSFSheet sheet = excel.getSheet("Sheet1");

//填充数据
sheet.getRow(1).getCell(1).setCellValue("时间" + dateBegin + "至" + dateEnd);

//获得第4行
XSSFRow row = sheet.getRow(3);
row.getCell(2).setCellValue(businessDataVO.getTurnover());
row.getCell(4).setCellValue(businessDataVO.getOrderCompletionRate());
row.getCell(6).setCellValue(businessDataVO.getNewUsers());

//获得第5行
row = sheet.getRow(4);
row.getCell(2).setCellValue(businessDataVO.getValidOrderCount());
row.getCell(4).setCellValue(businessDataVO.getUnitPrice());

//填充明细数据
for(int i = 0; i < 30; i++){
LocalDate date = dateBegin.plusDays(i);
//查询某一天的营业数据
BusinessDataVO 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());
}

//3.通过输出流将Excel文件下载到客户端浏览器
ServletOutputStream out = response.getOutputStream();
excel.write(out);

//关闭资源
out.close();
excel.close();

} catch (Exception e) {
e.printStackTrace();
}
}

Java项目苍穹外卖:接口开发(5)
http://surourou8.github.io/2024/11/21/Java项目苍穹外卖:接口开发(5)/
作者
Su Rourou
发布于
2024年11月21日
许可协议