文章目录
前言
六级怎么办,刷了15套卷子阅读还是错那么多,加油!业务逻辑不难,其实是stream语法的复习和echarts的前端知识运行不太熟悉了。
总的service:
service:
java
public interface ReportService {
// 统计营业额数据
TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end);
// 统计用户数据
UserReportVO getUserStatistics(LocalDate begin, LocalDate end);
// 统计订单数据
OrderReportVO getOrderStatistics(LocalDate begin, LocalDate end);
// 统计top10 降序
SalesTop10ReportVO getTop10(LocalDate begin, LocalDate end);
}
一、营业额统计
对于图表的样式,我们转战到前端将再深入,echarts能够快速使用一些预制好的图,比如条状图,折线图等。
1.controller
由于需要传入有指定格式的日期,由前端传给我们,我们获取到开始与结束时间后就可以开始统计。
@DateTimeFormat 注解是 Spring Framework 提供的注解,用于处理日期时间格式的转换。
controller:
java
@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:{}", begin, end);
return Result.success(reportService.getTurnoverStatistics(begin,end));
}
2.serviceImpl
首先,我们需要返回VO对象,VO对象里面需要包含dateList和turnoverList,所以我们可以分别来处理。
dateList:可以通过与end的对比慢慢接近end,
- 为什么不加end?
- 由于这里指向的是begin后面一个数,所以我们在end - 1这个地方就已经把end加进去了,所以end是加进去了的,如果实在不理解,我只能说,多去刷刷算法题!
turnoverList:由于我们在数据库中的日期格式是LocalDateTime所以我们需要进行类型转换,
- 为什么是MIN和MAX
- 就是可以把时间定格在这一天之内,无限接近前一天,无限接近后一天,那么中间就是这一天。
- 为什么要用map来装数据
- 这样可以增加扩展性,便于在写mapper的时候可以直接引用key传value,甚至是便于封装。
最后稍微检验一下是否为0值,利用链式调用构造对象进行返回。注意:
StringUtils是阿帕奇的包,可以联想python中的join语句,其实这里是一样的。将列表转化为字符串。
serviceImpl:
java
@Override
public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) {
// 1.先得到dateList
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
// 实际上这里已经将end装入list了 因为添加的begin已经朝后加了1日了
while (!begin.equals(end)) {
begin = begin.plusDays(1);
dateList.add(begin);
}
// 2.计算每天的营业额
List<Double> turnoverList = new ArrayList<>();
for (LocalDate date : dateList) {
// 2.1查询date日期对应的营业额数据 我们需要精确到时分秒 所以需要这样进行转换
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
// 2.2进行数据库查询操作
// 为什么用map 我觉得哈 这里如果不止三个参数你又如何应对呢 用map就可以增加扩展性
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.0 : turnover;
turnoverList.add(turnover);
}
// 需要注意的是我们需要导入的是apache的包
return TurnoverReportVO
.builder()
.dateList(StringUtils.join(dateList, ",")) // 将dateList通过逗号,连接
.turnoverList(StringUtils.join(turnoverList,",")) // 将turnoverList通过逗号,连接
.build();
}
3.mapper
统计营业额,营业额就是总收入,首先保证时间范围,同时订单已完成(status),因为是xml文件,所以尽量使用转义字符,不然有bug,利用sql的聚合函数进行统计,sum(字段),进行求和。
mapper:
xml
<select id="sumByMap" resultType="java.lang.Double">
select sum(amount) from orders
<where>
<if test="begin != null">
and order_time > #{begin}
</if>
<if test="end != null">
and order_time < #{end}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>
二、用户统计
1.controller
同理,接收时间范围进行统计。
controller:
java
@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:{}", begin, end);
return Result.success(reportService.getUserStatistics(begin,end));
}
2.serviceImpl
同样,我们看接口文档需要返回三个列表,dataList,newUserList,totalUserList。
dateList同前面的分析。
dateList里面存放了时间范围内的日期,所以遍历每一天进行每一天的统计,并添加即可,因为每一天的用户数量和目前的用户总量实际上是同一条sql语句,只是条件不一样,我们很自然的想到动态sql,所以先传条件少的,再传条件多的,避免重复。
java
/**
* 统计用户数据
* @param begin
* @param end
* @return
*/
@Override
public UserReportVO getUserStatistics(LocalDate begin, LocalDate end) {
// 1.先得到dateList
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
// 实际上这里已经将end装入list了 因为添加的begin已经朝后加了1日了
while (!begin.equals(end)) {
begin = begin.plusDays(1);
dateList.add(begin);
}
// 2.存放每天新增用户的数量
List<Integer> newUserList = new ArrayList<>();
// 3.存放每天总用户数量
List<Integer> totalUserList = new ArrayList<>();
for (LocalDate date : dateList) {
// 2.1查询date日期对应的营业额数据 我们需要精确到时分秒 所以需要这样进行转换
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
// 2.2进行数据库统计操作
Map map = new HashMap();
map.put("end", endTime);
totalUserList.add(userMapper.countByMap(map));
map.put("begin", beginTime);
newUserList.add(userMapper.countByMap(map));
}
return UserReportVO
.builder()
.dateList(StringUtils.join(dateList, ",")) // 将dateList通过逗号,连接
.newUserList(StringUtils.join(newUserList,",")) // 将turnoverList通过逗号,连接
.totalUserList(StringUtils.join(totalUserList,",")) // 将turnoverList通过逗号,连接
.build();
}
3.mapper
这里换成了count()聚合函数而已,统计个数即可,不需要sum求和。
mapper:
xml
<!-- 通过map统计订单-->
<select id="countByMap" resultType="java.lang.Integer">
select count(id) from user
<where>
<if test="begin != null">
and create_time > #{begin}
</if>
<if test="end != null">
and create_time < #{end}
</if>
</where>
</select>
三、订单统计
1.controller
controller:
java
@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:{}", begin, end);
return Result.success(reportService.getOrderStatistics(begin,end));
}
2.serviceImpl
serviceImpl:
实际上这里大同小异,需要返回dateList、orderCountList、validOrderCountList、totalOrderCount、validOrderCount,orderCompletionRate
dateList同前面分析,orderCountList、validOrderCountList需要统计每一天的总订单数、有效订单数,所以我们自然的遍历每一天,转换时间格式后进行传入,由于大家都是map传值嘛,我们封装成了一个private方法,最后统计这一时间范围内的总订单的数量,有效订单的数量,利用stream流的语法进行快速统计,实际上这里就是求列表和而已。
java
@Override
public OrderReportVO getOrderStatistics(LocalDate begin, LocalDate end) {
// 1.先得到dateList
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
// 实际上这里已经将end装入list了 因为添加的begin已经朝后加了1日了
while (!begin.equals(end)) {
begin = begin.plusDays(1);
dateList.add(begin);
}
// 2.存放每天的总订单数
List<Integer> orderCountList = new ArrayList<>();
// 3.存放每天有效订单数
List<Integer> validOrderCountList = new ArrayList<>();
for (LocalDate date : dateList) {
// 2.1查询date日期对应的营业额数据 我们需要精确到时分秒 所以需要这样进行转换
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
// 2.2进行数据库统计操作
orderCountList.add(getOrderCount(beginTime,endTime,null));
validOrderCountList.add(getOrderCount(beginTime,endTime,Orders.COMPLETED)); // 已完成的是有效订单
}
// 4.计算时间区间内的总订单数量 实际上就是求列表的和 这里的stream流优化了效率
Integer totalOrderCount = orderCountList.stream().reduce(Integer::sum).get();
// 5.计算时间区间内的总的有效订单数量
Integer validOrderCount = validOrderCountList.stream().reduce(Integer::sum).get();
Double orderCompletionRate = 0.0;
if(totalOrderCount != 0){
// 6.计算完成率
orderCompletionRate = orderCompletionRate / totalOrderCount; // 有效单 / 总单数
}
return OrderReportVO.builder()
.dateList(StringUtils.join(dateList, ","))
.orderCompletionRate(orderCompletionRate)
.orderCountList(StringUtils.join(orderCountList, ","))
.validOrderCount(validOrderCount)
.validOrderCountList(StringUtils.join(validOrderCountList, ","))
.totalOrderCount(totalOrderCount)
.build();
}
java
/**
* 封装一个通过map返回订单统计结果
* @param begin
* @param end
* @param status
* @return
*/
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);
}
3.mapper
mapper:
xml
<!-- 通过map统计订单-->
<select id="countByMap" resultType="java.lang.Integer">
select count(id) from orders
<where>
<if test="begin != null">
and order_time > #{begin}
</if>
<if test="end != null">
and order_time < #{end}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>
四、top10排名统计
1.controller
controller:
java
@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:{}", begin, end);
return Result.success(reportService.getTop10(begin,end));
}
2.serviceImpl
统计总的排名,不需要每一天进行遍历了,我们需要从mapper中返回两个字段,返回的两个都是String所以我们需要进行转化为List后再用逗号进行拼接,这里运用stream流的形式进行快速类型转换。
serviceImpl:
java
/**
* 统计top10
* @param begin
* @param end
* @return
*/
@Override
public SalesTop10ReportVO getTop10(LocalDate begin, LocalDate end) {
// 1.开始寻找
// 1.1查询date日期对应的名称 我们需要精确到时分秒 所以需要这样进行转换
LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);
// 1.2进行数据库统计操作
// 从**销售数据DTO**中提取**商品名称name**和**销售数量number**,并转换成用逗号分隔的字符串
List<GoodsSalesDTO> salesTop10 = orderMapper.getSalesTop(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();
}
3.mapper
我觉得这是本项目中最难的sql,没有之一。需要返回name和总数量,它们在不同的表中,所以使用多表查询,通过外键进行连接,实际上将两张表拼接成一张表进行操作,再根据条件进行筛选,通过name进行分组,因为这是个多表查询嘛,生成子表进行分组,再降序排序,limit只显示前十个。
mapper:
xml
<!-- 最难的sql出现了 多表查询-->
<select id="getSalesTop" 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 > #{begin}
</if>
<if test="end != null">
and o.order_time < #{end}
</if>
group by od.name
order by number desc
limit 0,10
</select>
总结
实际上这里业务逻辑并不难,熟练掌握看接口文档和封装,stream语法的熟练使用即可。
关于stream流的复习,我将后面再回头讨论,这里的具体运用。