目录
一、Apache ECharts【前端】
1. 介绍
2. 入门案例
二、营业额统计
1. 需求分析和设计
1 产品原型
2 业务规则
3 接口设计
2. 代码开发
3. 功能测试
三、用户统计
1. 需求分析和设计
1 产品原型
2 业务规则
3 接口设计
2. 代码开发
3. 功能测试
四、订单统计
1. 需求分析和设计
1 产品原型
2 业务规则
3 接口设计
2. 代码开发
3. 功能测试
五、销量排名Top10
1. 需求分析和设计
1 产品原型
2 业务规则
3 接口设计
2. 代码开发
3. 功能测试
一、Apache ECharts【前端】
1. 介绍
Apache ECharts 是一款基于 Javascript 的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。 官网地址:Apache ECharts
常见效果展示:
1). 柱形图
2). 饼形图
3). 折线图
总结:不管是哪种形式的图形,最本质的东西实际上是数据,它其实是对数据的一种可视化展示。
2. 入门案例
Apache Echarts官方提供的快速入门:快速上手 - 使用手册 - Apache ECharts
效果展示:
实现步骤:
1). 引入echarts.js 文件(当天资料已提供)
2). 为 ECharts 准备一个设置宽高的 DOM
3). 初始化echarts实例
4). 指定图表的配置项和数据
5). 使用指定的配置项和数据显示图表
代码开发:
<!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>
测试:使用浏览器方式打开即可。
总结:使用Echarts,重点在于研究当前图表所需的数据格式。通常是需要后端提供符合格式要求的动态数据,然后响应给前端来展示图表。
二、营业额统计
1. 需求分析和设计
1 产品原型
营业额统计是基于折现图来展现,并且按照天来展示的。实际上,就是某一个时间范围之内的每一天的营业额。同时,不管光标放在哪个点上,那么它就会把具体的数值展示出来。并且还需要注意日期并不是固定写死的,是由上边时间选择器来决定。比如选择是近7天、或者是近30日,或者是本周,就会把相应这个时间段之内的每一天日期通过横坐标展示。
原型图 :
2 业务规则
-
营业额,指订单状态为已完成的订单金额合计
-
基于可视化报表的折线图展示营业额数据,X轴为日期,Y轴为营业额
-
根据时间选择区间,展示每天的营业额数据
3 接口设计
通过上述原型图,设计出对应的接口。
注意:具体返回数据一般由前端来决定,前端展示图表,具体折现图对应数据是什么格式,是有固定的要求的。 所以说,后端需要去适应前端,它需要什么格式的数据,我们就给它返回什么格式的数据。
2. 代码开发
//在sky-pojo模块,TurnoverReportVO.java已定义
package com.sky.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@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
//根据接口定义创建ReportController:
package com.sky.controller.admin;
import com.sky.result.Result;
import com.sky.service.ReportService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
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.RestController;
import java.time.LocalDate;
@RestController
@Api(tags = "数据统计相关接口")
@RequestMapping("/admin/report")
public class ReportController {
@Autowired
private ReportService reportService;
@GetMapping("/turnoverStatistics")
@ApiOperation("营业额统计接口")
public Result turnoverStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
return reportService.turnoverStatistics(begin, end);
}
}
-------------
//创建ReportService
package com.sky.service;
import com.sky.result.Result;
import java.time.LocalDate;
public interface ReportService {
/**
* 统计营业额
* @param begin
* @param end
* @return
*/
Result turnoverStatistics(LocalDate begin, LocalDate end);
}
-------------
//ReportServiceImpl
package com.sky.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sky.mapper.OrderMapper;
import com.sky.result.Result;
import com.sky.service.ReportService;
import com.sky.vo.TurnoverReportVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ReportServiceImpl implements ReportService {
@Autowired
private OrderMapper orderMapper;
@Override
public Result turnoverStatistics(LocalDate begin, LocalDate end) {
TurnoverReportVO vo = new TurnoverReportVO();
List<LocalDate> dateList = new ArrayList<>();
List<Double> amountList = new ArrayList<>();
while (!(begin.isAfter(end))) {
//1. 把日期添加到dateList里
dateList.add(begin);
//2. 查询这一天的营业额,添加到amountList里
LocalDateTime startOfDay = LocalDateTime.of(begin, LocalTime.MIN);
LocalDateTime endOfDay = LocalDateTime.of(begin, LocalTime.MAX);
Double amount = orderMapper.sumAmountByDate(startOfDay, endOfDay);
amount = amount == null ? 0D : amount;
amountList.add(amount);
//3. 到下一天
begin = begin.plusDays(1);
}
//dateList 日期,以逗号分隔,例如:2022-10-01,2022-10-02,2022-10-03
String dateListStr = dateList.stream()
.map(localDate -> localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.collect(Collectors.joining(","));
vo.setDateList(dateListStr);
//turnoverList 营业额,以逗号分隔,例如:406.0,1520.0,75.0
String turnoverListStr = amountList.stream()
.map(Object::toString)
.collect(Collectors.joining(","));
vo.setTurnoverList(turnoverListStr);
return Result.success(vo);
}
}
------------
//OrderMapper
@Select("select sum(amount) from orders where status = 5 and order_time between #{start} and #{end}")
Double sumAmountByDate(LocalDateTime start, LocalDateTime end);
3. 功能测试
可以通过如下方式进行测试:
-
接口文档测试
-
前后端联调测试
启动服务器,启动nginx,直接采用前后端联调测试。
进入数据统计模块
1). 查看近7日营业额统计
2). 查看近30日营业额统计
进入开发者模式,查看返回数据
也可通过断点方式启动,查看每步执行情况。
三、用户统计
1. 需求分析和设计
1 产品原型
所谓用户统计,实际上统计的是用户的数量。通过折线图来展示,上面这根蓝色线代表的是用户总量,下边这根绿色线代表的是新增用户数量,是具体到每一天。所以说用户统计主要统计两个数据,一个是总的用户数量,另外一个是新增用户数量。
原型图 :
2 业务规则
-
基于可视化报表的折线图展示用户数据,X轴为日期,Y轴为用户数
-
根据时间选择区间,展示每天新增用户量,和截止到当天的用户总量和
3 接口设计
根据上述原型图设计接口。
2. 代码开发
//在sky-pojo模块,UserReportVO.java已定义
package com.sky.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@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
@GetMapping("/userStatistics")
@ApiOperation("用户统计接口")
public Result userStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
return reportService.userStatistics(begin, end);
}
-------------
//ReportService
/**
* 统计用户
* @param begin
* @param end
* @return
*/
Result userStatistics(LocalDate begin, LocalDate end);
------------
//ReportServiceImpl
@Override
public Result userStatistics(LocalDate begin, LocalDate end) {
UserReportVO vo = new UserReportVO();
List<LocalDate> dateList = new ArrayList<>();
List<Integer> totalCountList = new ArrayList<>();
List<Integer> newCountList = new ArrayList<>();
while (!(begin.isAfter(end))) {
//把日期加到dateList集合里
dateList.add(begin);
//获取截止到当天的用户总量 select count(*) from user where create_time <= 当天日期
LocalDateTime endOfDay = LocalDateTime.of(begin, LocalTime.MAX);
Integer totalCount = userMapper.countByDate(null, endOfDay);
totalCountList.add(totalCount==null?0:totalCount);
//获取当天的新用户数量 select count(*) from user where create_time between 当天日期开始时间 and 当天日期结束时间
LocalDateTime startOfDay = LocalDateTime.of(begin, LocalTime.MIN);
Integer newCount = userMapper.countByDate(startOfDay, endOfDay);
newCountList.add(newCount==null?0:newCount);
//到下一天
begin = begin.plusDays(1);
}
//日期,以逗号分隔,例如:2022-10-01,2022-10-02,2022-10-03
String dateListStr = dateList.stream()
.map(date -> date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.collect(Collectors.joining(","));
vo.setDateList(dateListStr);
//用户总量,以逗号分隔,例如:200,210,220
String totalListStr = totalCountList.stream()
.map(Objects::toString)
.collect(Collectors.joining(","));
vo.setTotalUserList(totalListStr);
//新增用户,以逗号分隔,例如:20,21,10
String newListStr = newCountList.stream()
.map(Objects::toString)
.collect(Collectors.joining(","));
vo.setNewUserList(newListStr);
return Result.success(vo);
}
-------------
//UserMapper
/**
* 统计指定日期范围内的用户数量
*/
Integer countByDate(LocalDateTime startOfDay, LocalDateTime endOfDay);
-------------
//UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sky.mapper.UserMapper">
<select id="countByDate" resultType="int">
select count(*) from user
<where>
<if test="startOfDay!=null">
and create_time >= #{startOfDay}
</if>
<if test="endOfDay!=null">
and create_time <= #{endOfDay}
</if>
</where>
</select>
</mapper>
3. 功能测试
可以通过如下方式进行测试:
-
接口文档测试
-
前后端联调测试
进入数据统计模块
1). 查看近7日用户统计
进入开发者模式,查看返回数据
2). 查看近30日用户统计
进入开发者模式,查看返回数据
也可通过断点方式启动,查看每步执行情况。
四、订单统计
1. 需求分析和设计
1 产品原型
订单统计通过一个折现图来展现,折线图上有两根线,这根蓝色的线代表的是订单总数,而下边这根绿色的线代表的是有效订单数,指的就是状态是已完成的订单就属于有效订单,分别反映的是每一天的数据。上面还有3个数字,分别是订单总数、有效订单、订单完成率,它指的是整个时间区间之内总的数据。
原型图:
2 业务规则
-
有效订单,指状态为 “已完成” 的订单
-
基于可视化报表的折线图展示订单数据,X轴为日期,Y轴为订单数量
-
根据时间选择区间,展示每天的订单总数和有效订单数
-
展示所选时间区间内的有效订单数、总订单数、订单完成率,订单完成率 = 有效订单数 / 总订单数 * 100%
3 接口设计
根据上述原型图设计接口。
2. 代码开发
//在sky-pojo模块,OrderReportVO.java已定义
package com.sky.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@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
@GetMapping("/ordersStatistics")
@ApiOperation("订单统计接口")
public Result orderStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
return reportService.orderStatistics(begin, end);
}
--------------
//ReportService
/**
* 统计订单
* @param begin
* @param end
* @return
*/
Result orderStatistics(LocalDate begin, LocalDate end);
--------------
//ReportServiceImpl
@Override
public Result orderStatistics(LocalDate begin, LocalDate end) {
OrderReportVO vo = new OrderReportVO();
List<LocalDate> dateList = new ArrayList<>();
List<Integer> countList = new ArrayList<>();
List<Integer> completeCountList = new ArrayList<>();
while (!(begin.isAfter(end))) {
//把日期添加到dateList里
dateList.add(begin);
//统计当天的订单数量
LocalDateTime startOfDay = LocalDateTime.of(begin, LocalTime.MIN);
LocalDateTime endOfDay = LocalDateTime.of(begin, LocalTime.MAX);
Integer count = orderMapper.countStatistics(startOfDay, endOfDay, null);
countList.add(count==null?0:count);
//统计当天的有效订单数量
Integer completeCount = orderMapper.countStatistics(startOfDay, endOfDay, Orders.COMPLETED);
completeCountList.add(completeCount == null ? 0 : completeCount);
//到下一天
begin = begin.plusDays(1);
}
//日期,以逗号分隔,例如:2022-10-01,2022-10-02,2022-10-03
String dateListStr = dateList.stream()
.map(date -> date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.collect(Collectors.joining(","));
vo.setDateList(dateListStr);
//每日订单数,以逗号分隔,例如:260,210,215 select count(*) from orders where order_time >= ? and order_time <= ?
String countListStr = countList.stream()
.map(Objects::toString)
.collect(Collectors.joining(","));
vo.setOrderCountList(countListStr);
//每日有效订单数,以逗号分隔,例如:20,21,10 select count(*) from orders where order_time >= ? and order_time <= ? and status = 5
String completeListStr = completeCountList.stream()
.map(Objects::toString)
.collect(Collectors.joining(","));
vo.setValidOrderCountList(completeListStr);
//订单总数 select count(*) from orders
Integer totalCount = countList.stream().reduce(Integer::sum).orElse(0);
vo.setTotalOrderCount(totalCount);
//有效订单数 select count(*) from orders where status = 5
Integer completeCount = completeCountList.stream().reduce(Integer::sum).orElse(0);
vo.setValidOrderCount(completeCount);
//订单完成率 有效订单数/订单总数
if (totalCount == 0) {
vo.setOrderCompletionRate(0D);
}else{
vo.setOrderCompletionRate(1.0 * completeCount/totalCount);
}
return Result.success(vo);
}
-----------
//OrderMapper
/**
* 根据日期范围和状态,统计订单数量
*/
Integer countStatistics(LocalDateTime startOfDay, LocalDateTime endOfDay, Integer status);
------------
//OrderMapper.xml
<select id="countStatistics" resultType="int">
select count(*) from orders
<where>
<if test="startOfDay!=null">
and order_time >= #{startOfDay}
</if>
<if test="endOfDay!=null">
and order_time <= #{endOfDay}
</if>
<if test="status!=null">
and status = #{status}
</if>
</where>
</select>
3. 功能测试
可以通过如下方式进行测试:
-
接口文档测试
-
前后端联调
重启服务,直接采用前后端联调测试。
进入数据统计模块
1). 查看近7日订单统计
2). 查看近30日订单统计
进入开发者模式,查看返回数据
也可通过断点方式启动,查看每步执行情况
五、销量排名Top10
1. 需求分析和设计
1 产品原型
所谓销量排名,销量指的是商品销售的数量。项目当中的商品主要包含两类:一个是套餐,一个是菜品,所以销量排名其实指的就是菜品和套餐销售的数量排名。通过柱形图来展示销量排名,这些销量是按照降序来排列,并且只需要统计销量排名前十的商品。
原型图:
2 业务规则
-
根据时间选择区间,展示销量前10的商品(包括菜品和套餐)
-
基于可视化报表的柱状图降序展示商品销量
-
此处的销量为商品销售的份数
3 接口设计
2. 代码开发
//在sky-pojo模块,SalesTop10ReportVO.java已定义
package com.sky.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SalesTop10ReportVO implements Serializable {
//商品名称列表,以逗号分隔,例如:鱼香肉丝,宫保鸡丁,水煮鱼
private String nameList;
//销量列表,以逗号分隔,例如:260,215,200
private String numberList;
}
-------------
//ReportController
@GetMapping("/top10")
@ApiOperation("查询销量排名top10接口")
public Result top10Dishes(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
return reportService.top10Dishes(begin, end);
}
-------------
//ReportService
/**
* 查询销量排名top10接口
* @param begin
* @param end
* @return
*/
Result top10Dishes(LocalDate begin, LocalDate end);
------------
//ReportServiceImpl
@Override
public Result top10Dishes(LocalDate begin, LocalDate end) {
SalesTop10ReportVO vo = new SalesTop10ReportVO();
LocalDateTime start = LocalDateTime.of(begin, LocalTime.MIN);
LocalDateTime stop = LocalDateTime.of(end, LocalTime.MAX);
List<GoodsSalesDTO> salesTop10 = orderMapper.salesTop10(start, stop);
//商品名称列表,以逗号分隔,例如:鱼香肉丝,宫保鸡丁,水煮鱼
String names = salesTop10.stream()
.map(GoodsSalesDTO::getName)
.collect(Collectors.joining(","));
vo.setNameList(names);
//销量列表,以逗号分隔,例如:260,215,200
String numbers = salesTop10.stream()
.map(GoodsSalesDTO::getNumber)
.map(Objects::toString)
.collect(Collectors.joining(","));
vo.setNumberList(numbers);
return Result.success(vo);
}
-------------
//OrderMapper
/**
* 查询商品销量前10名
* @param begin
* @param end
*/
List<GoodsSalesDTO> salesTop10(LocalDateTime start, LocalDateTime stop);
------------
//OrderMapper.xml
<select id="salesTop10" resultType="com.sky.dto.GoodsSalesDTO">
select od.name as name, sum(od.number) as number from orders o, order_detail od
<where>
o.id = od.order_id and o.status = 5
<if test="start!=null">
and o.order_time >= #{start}
</if>
<if test="stop!=null">
and o.order_time <= #{stop}
</if>
</where>
group by od.name
order by number desc
limit 10
</select>
3. 功能测试
可以通过如下方式进行测试:
-
接口文档测试
-
前后端联调
重启服务,直接采用前后端联调测试。
查看近30日销量排名Top10统计
若查询的某一段时间没有销量数据,则显示不出效果。
进入开发者模式,查看返回数据
也可通过断点方式启动,查看每步执行情况。