苍穹外卖Day11代码解析以及深入思考
文章目录
- 前言
- 一、营业额统计
- 1.controller
- 2.serviceImpl
- 3.mapper
- 二、用户统计
- 1.controller
- 2.serviceImpl
- 3.mapper
- 三、订单统计
- 1.controller
- 2.serviceImpl
- 3.mapper
- 四、top10排名统计
- 1.controller
- 2.serviceImpl
- 3.mapper
- 总结
前言
六级怎么办,刷了15套卷子阅读还是错那么多,加油!业务逻辑不难,其实是stream语法的复习和echarts的前端知识运行不太熟悉了。
总的service:
service:
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:
@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:
@Overridepublic TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) {// 1.先得到dateListList<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);// 简单的三目运算符判断一下是否为nullturnover = 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:
<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:
@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,所以先传条件少的,再传条件多的,避免重复。
/*** 统计用户数据* @param begin* @param end* @return*/@Overridepublic UserReportVO getUserStatistics(LocalDate begin, LocalDate end) {// 1.先得到dateListList<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:
<!-- 通过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:
@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流的语法进行快速统计,实际上这里就是求列表和而已。
@Overridepublic OrderReportVO getOrderStatistics(LocalDate begin, LocalDate end) {// 1.先得到dateListList<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();}
/*** 封装一个通过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:
<!-- 通过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:
@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:
/*** 统计top10* @param begin* @param end* @return*/@Overridepublic 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:
<!-- 最难的sql出现了 多表查询--><select id="getSalesTop" resultType="com.sky.dto.GoodsSalesDTO">select od.name, sum(od.number) number from order_detail od,orders owhere 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.nameorder by number desclimit 0,10</select>
总结
实际上这里业务逻辑并不难,熟练掌握看接口文档和封装,stream语法的熟练使用即可。
关于stream流的复习,我将后面再回头讨论,这里的具体运用。