前言:
今天写day11的内容,主要讲了四个统计接口的制作。看起来内容较多,其实代码逻辑都是相似的,这里我们过一遍。
今日所学:
- Apache ECharts
- 营业额统计
- 用户统计
- 订单统计
- 销量排行统计
1. Apache ECharts
1.1 介绍
Apache ECharts 是一个功能强大、易于使用的数据可视化工具,适用于多种场景下的数据展示需求。通过简单的配置和灵活的定制,用户可以快速生成美观且交互性强的图表,提升数据分析和展示的效果。
总结:
Apache ECharts 是一个前端数据可视化库,适用于Web端的数据展示和分析
我们后端需要做的,就是提供符合格式要求的动态数据,然后响应给前端来展示图表。
1.2 使用流程:
1.下载echarts.js
这边黑马的资料中给我们准备好了

2.在前端的代码中引用
打开echartsDemo.html文件代码,可以看到已成功引入了echarts.js

运行这个html文件

2.营业额统计
需求分析
业务规则:
1.营业额指的是订单状态为完成的订单金额合计
2.X轴表示日期,Y轴表示营业额
3. 根据时间选择区间,展示每天的营业额数据

请求参数是开始时间和结束时间,这里注意传给前端的dataList和turnoverList都要是String类型的数据
代码展示
Controller层:

这里注意begin,end是LocalData变量,传入的时候要指定时间格式
service层:
这里具体的代码逻辑是:
1.先是定义两个arrayList容器用于后续储存每日的日期还有相应的营业额
2.while循环储存从开始(begin)到结束(end)的每一天日期数据
3.将每一天的日期数据(LocalData变量)转换成(LocalDataTime变量),分别取极小值(00:00:00)和极大值(23:59:5999)
4.调用Ordermapper,执行如下SQL语句:
select sum(amount) from Orders where order_time > beginTime(一天的极小值) and order_time < endTime(一天的极大值) and status = 5(已完成)
注意下这里传给mapper的数据是由map储存的
/**
* 指定时间内的营业额
* @param begin
* @param end
* @return
*/
@Override
public TurnoverReportVO turnoverStatistics(LocalDate begin, LocalDate end) {
// 用于储存begin,end范围内每天的日期
List<LocalDate> dateList = new ArrayList<>();
List<Double> turnoverList = new ArrayList<>();
dateList.add(begin);
while(!begin.equals(end)) {
// 指定日期的后一天
begin = begin.plusDays(1);
dateList.add(begin);
}
for (LocalDate date : dateList) {
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
Map map = new HashMap();
map.put("beginTime", beginTime);
map.put("endTime", endTime);
map.put("status", Orders.COMPLETED);
Double amount = orderMapper.getSumBymap(map);
amount = amount == null ? 0.0 : amount;
turnoverList.add(amount);
}
return TurnoverReportVO
.builder()
.turnoverList(StringUtils.join(turnoverList, ","))
.dateList(StringUtils.join(dateList, ","))
.build();
}
mapper层:
<select id="getSumBymap" resultType="java.lang.Double">
select sum(amount) from orders
<where>
<if test="begin != null">
and order_time > #{beginTime}
</if>
<if test="end != null">
and order_time < #{endTime}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>
运行展示:

问题
这里我遇到的问题是几个:
1.路径写错了(写成另一个接口的了),导致前端图表一直展示不出来,改了挺久的

2.StringUtils包导错了

这里要注意导入的是我标注的那个包,而不是我最上面注释掉的那个
3.这里要注意要写的是Double,而不是double.Double是包装类,是一种类,存在null值,而double是一种基本数据类型,是不存在null值的

3.用户统计
需求分析
业务规则:
- 基于可视化报表的折线图展示用户数据,X轴为日期,Y轴为用户数
- 根据时间选择区间,展示每天的用户总量和新增用户量数据

这里的传入参数和返回数据跟营业额统计格式上基本是一致的:传入开始日期和结束日期,
返回数据也都是要String类型的
代码展示:
Controller层:
@ApiOperation("用户统计")
@GetMapping("/userStatistics")
public Result<UserReportVO> userStatistics(
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
UserReportVO userReportVO = reportService.userStatistics(begin, end);
return Result.success(userReportVO);
}
这里同样注意传入的LocalData类型的数据要用@DataTimeFormat规定时间格式
Service层:
具体逻辑:
1.定义三个ArrayList格式的数据,分别用于储存每一天的时间,每一天的用户总量,每一天的用户增量
2.while循环得到开始日期到结束日期之间每天的日期
3..将每一天的日期数据(LocalData变量)转换成(LocalDataTime变量),分别取极小值(00:00:00)和极大值(23:59:5999)
4.调用mapper,执行相应的语句,得到每一天的用户总量和用户新增量
这里注意用户总量只要查询endTime就行了,用户新增量再查询beginTime和endTime之间创建的用户,具体SQL执行逻辑:
select count(id) from user where create_time < endTime and create_time > beginTime(求用户新增量的时间再传入相应参数)
mapper参数还是由map传入
/**
* 用户统计
* @param begin
* @param end
* @return
*/
@Override
public UserReportVO userStatistics(LocalDate begin, LocalDate end) {
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
while(!begin.equals(end)) {
begin = begin.plusDays(1);
dateList.add(begin);
}
List<Integer> totalUserList = new ArrayList<>();
List<Integer> newUserList = 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("endTime", endTime);
Integer userNum = userMapper.countByMap(map);
map.put("beginTime", beginTime);
Integer newUserNum = userMapper.countByMap(map);
userNum = userNum == null ? 0 : userNum;
totalUserList.add(userNum);
newUserList.add(newUserNum);
}
return UserReportVO.builder()
.dateList(StringUtils.join(dateList,","))
.totalUserList(StringUtils.join(totalUserList, ","))
.newUserList(StringUtils.join(newUserList, ","))
.build();
}
mapper层
<select id="countByMap" resultType="java.lang.Integer">
select count(id) from user
<where>
<if test="beginTime != null">
and create_time > #{beginTime}
</if>
<if test="beginTime != null">
and create_time < #{endTime}
</if>
</where>
</select>
运行展示:

4.订单统计
需求分析
业务规则:
- 有效订单指状态为 “已完成” 的订单
- 基于可视化报表的折线图展示订单数据,X轴为日期,Y轴为订单数量
- 根据时间选择区间,展示每天的订单总数和有效订单数
- 展示所选时间区间内的有效订单数、总订单数、订单完成率,订单完成率 = 有效订单数 / 总订单数 * 100%

跟上两个接口传入参数返回数据格式上是一致的,请求参数是开始时间和结束时间,传给前端的orderCountList和validOrderCountList都要是String类型的数据
代码展示
Controller层
/**
* 订单统计接口
* @param begin
* @param end
* @return
*/
@ApiOperation("订单统计接口")
@GetMapping("/ordersStatistics")
public Result<OrderReportVO> orderStatistics(
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end
){
log.info("订单统计接口{},{}", begin, end);
OrderReportVO orderReportVO = reportService.orderStatistics(begin, end);
return Result.success(orderReportVO);
}
这里注意begin,end是LocalData变量,传入的时候要指定时间格式
service层
具体执行逻辑:
1.先是定义三个arrayList容器,分别用于储存每日的日期,每日的订单数,以及有效订单数
2.再定义两个total(Integer),用来统计订单总数和有效订单总数
3.while循环储存从开始(begin)到结束(end)的每一天日期数据
4.将每一天的日期数据(LocalData变量)转换成(LocalDataTime变量),分别取极小值(00:00:00)和极大值(23:59:5999)
4.调用Ordermapper,执行如下SQL语句:
select count(id) from Orders where order_time > beginTime(一天的极小值) and order_time < endTime(一天的极大值) and status = 5(已完成->当统计有效订单再传入相应参数)
每统计一天的,加入相应的容器和总数中
注意下这里传给mapper的数据是由map储存的
/**
* 订单统计
* @param begin
* @param end
* @return
*/
@Override
public OrderReportVO orderStatistics(LocalDate begin, LocalDate 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<>();
Integer totalOrderCount = 0;
List<Integer> vaildOrderCountList = new ArrayList<>();
Integer VaildOrderCount = 0;
for(LocalDate date : dateList) {
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
Map map = new HashMap();
map.put("beginTime", beginTime);
map.put("endTime", endTime);
Integer orderCount = orderMapper.CountByMap(map);
orderCountList.add(orderCount);
totalOrderCount += orderCount;
map.put("status",Orders.COMPLETED);
Integer vaildCount = orderMapper.CountByMap(map);
vaildOrderCountList.add(vaildCount);
VaildOrderCount += vaildCount;
}
return OrderReportVO.builder()
.orderCountList(StringUtils.join(orderCountList, ","))
.totalOrderCount(totalOrderCount)
.validOrderCount(VaildOrderCount)
.validOrderCountList(StringUtils.join(vaildOrderCountList, ","))
.dateList(StringUtils.join(dateList,","))
.build();
}
mapper层
<select id="CountByMap" resultType="java.lang.Integer">
select count(id) from orders
<where>
<if test="beginTime != null">
and order_time > #{beginTime}
</if>
<if test="endTime != null">
and order_time < #{endTime}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>
运行展示:

5. 销量排行统计
需求分析:
业务规则:
- 根据时间选择区间,展示销量前10的商品(包括菜品和套餐)
- 基于可视化报表的柱状图降序展示商品销量
- 此处的销量为商品销售的份数

请求参数是开始时间和结束时间,这里注意传给前端的nameList和numberList都要是String类型的数据
代码展示:
controller层
/**
* 销量排行展示
* @param begin
* @param end
* @return
*/
@ApiOperation("销量排名展示")
@GetMapping("/top10")
public Result<SalesTop10ReportVO> salesTop10Statistics(
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end
){
log.info("销量排名展示{},{}", begin,end);
SalesTop10ReportVO salesTop10ReportVO = reportService.top10(begin, end);
return Result.success(salesTop10ReportVO);
}
这里注意begin,end是LocalData变量,传入的时候要指定时间格式
service层:
具体实现逻辑:
1.这里我们项目有一个专门封装了菜品名称和菜品销量的dto(GoodsSalesDTO),所以我们不用专门再创建容器去记录数据
2.因为销量统计是统计我们这一整段时间(从begin到end)的菜品销量,所以不用在遍历每一天的订单数据
3.将LocalDate变量数据转换成LocalDataTime,求得这一段时间的极小值和极大值
4.调用ordermapper,因为涉及到订单(orders)的下单时间和订单详情(order_detail)的菜品数据,所以这里我采用join进行连接查询,具体SQL语句为:
select od.name as name , coount(od.name) as number from orders o join order_detail od
on od.order_id = o.id
where status = 5(查看已下单的数据) and order_time > beginTime and order_time < endTime
group by name order by name desc limit 0, 10(查询销量前十的菜品)
5.得到相应的数据后使用stream流进行遍历,最后用StringUtils.join连接起来
/**
* 销量统计
* @param begin
* @param end
* @return
*/
@Override
public SalesTop10ReportVO top10(LocalDate begin, LocalDate end) {
LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);
List<GoodsSalesDTO> goodsSalesDTOList = orderMapper.getByNameNum(beginTime, endTime);
String nameList = StringUtils.join(goodsSalesDTOList.stream().map(GoodsSalesDTO::getName).collect(Collectors.toList()), ",");
String numList = StringUtils.join(goodsSalesDTOList.stream().map(GoodsSalesDTO::getNumber).collect(Collectors.toList()), ",");
return SalesTop10ReportVO.builder()
.nameList(nameList)
.numberList(numList)
.build();
}
mapper层
<select id="getByNameNum" resultType="com.sky.dto.GoodsSalesDTO">
select od.name as name, sum(od.name) as number from orders o join order_detail od
on o.id = od.order_id
and o.status = 5
<where>
<if test="beginTime != null">
and order_time > #{beginTime}
</if>
<if test="endTime != null">
and order_time < #{endTime}
</if>
</where>
group by name
order by number desc
limit 0, 10
</select>
运行展示:

最后:
今天的分享就到这里。如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!(๑`・ᴗ・´๑)