苍穹外卖 数据可视化

将营业额、用户数据、订单数据、商品销量top10数据全部使用Apache Echarts可视化,展现在前端,后端只需要按照需要的格式,为前端提供数据即可。

ReportController

java 复制代码
package com.sky.controller.admin;

import com.sky.result.Result;
import com.sky.service.ReportService;
import com.sky.vo.OrderReportVO;
import com.sky.vo.SalesTop10ReportVO;
import com.sky.vo.TurnoverReportVO;
import com.sky.vo.UserReportVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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
@RequestMapping("/admin/report")
@Slf4j
@Api(tags = "统计报表相关接口")
public class ReportController {
    // Apache Echarts
    // Apache Echarts是一个基于JavaScript的数据可视化图标库,提供直观、生动、可交互的数据可视化图表
    // 无论是什么形式的图形,其本质上是数据,ApacheEcharts就是对数据的可视化展示

    // 但是Apache Echarts是前端需要使用的东西,后端只需要按照和前端的约定,为其提供数据即可

    @Autowired
    private ReportService reportService;

    /**
     * 营业额数据统计
     *
     * @param begin
     * @param end
     * @return
     */
    // 查询一段时间内的营业额,前端给后端传递开始时间和结束时间,后端需要查询这段时间内的营业额,并封装到VO中、
    // 约定好VO中封装两个字符串,时间和对应的营业额,用","分隔
    @GetMapping("/turnoverStatistics")
    @ApiOperation("营业额数据统计")
    // 使用@DateTimeFormat限定前端传递的时间的格式
    public Result<TurnoverReportVO> turnoverStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd")
                                                       LocalDate begin,
                                                       @DateTimeFormat(pattern = "yyyy-MM-dd")
                                                       LocalDate end) {
        return Result.success(reportService.getTurnoverStatistics(begin, end));
    }

    /**
     * 用户数据统计
     *
     * @param begin
     * @param end
     * @return
     */
    // 用户数据统计分为两个部分:用户总量和新增用户;用户总量很好理解------------
    // 而新增用户可以理解为:假如是今天是11.11日,
    // 那么在11.11日最小时间(00:00:00)------------11.11日最大时间(23:59:59)创建的用户都是这一天的新用户
    @GetMapping("/userStatistics")
    @ApiOperation("用户数据统计")
    public Result<UserReportVO> userStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd")
                                               LocalDate begin,
                                               @DateTimeFormat(pattern = "yyyy-MM-dd")
                                               LocalDate end) {
        return Result.success(reportService.getUserStatistics(begin, end));
    }

    /**
     * 订单数据统计
     *
     * @param begin
     * @param end
     * @return
     */
    // 订单数据统计需要查询当天的所有订单和完成了的有效订单,并根据这两个数据计算出订单总数、有效订单总数、订单完成率
    @GetMapping("/ordersStatistics")
    @ApiOperation("订单数据统计")
    public Result<OrderReportVO> ordersStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd")
                                                  LocalDate begin,
                                                  @DateTimeFormat(pattern = "yyyy-MM-dd")
                                                  LocalDate end) {
        return Result.success(reportService.getOrdersStatistics(begin, end));
    }

    /**
     * top10畅销商品统计
     *
     * @param begin
     * @param end
     * @return
     */
    @GetMapping("top10")
    @ApiOperation("top10畅销商品统计")
    public Result<SalesTop10ReportVO> top10Statistics(@DateTimeFormat(pattern = "yyyy-MM-dd")
                                                      LocalDate begin,
                                                      @DateTimeFormat(pattern = "yyyy-MM-dd")
                                                      LocalDate end) {
        return Result.success(reportService.getSalesTop10Statistics(begin, end));
    }
}

ReportService

java 复制代码
package com.sky.service;

import com.sky.vo.OrderReportVO;
import com.sky.vo.SalesTop10ReportVO;
import com.sky.vo.TurnoverReportVO;
import com.sky.vo.UserReportVO;
import org.springframework.stereotype.Service;

import java.time.LocalDate;

@Service
public interface ReportService {

    /**
     * 根据时间区间统计营业额数据
     *
     * @param begin
     * @param end
     * @return
     */
    TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end);

    /**
     * 根据时间区间统计用户数量
     *
     * @param begin
     * @param end
     * @return
     */
    UserReportVO getUserStatistics(LocalDate begin, LocalDate end);

    /**
     * 根据时间区间统计订单数据
     *
     * @param begin
     * @param end
     * @return
     */
    OrderReportVO getOrdersStatistics(LocalDate begin, LocalDate end);

    /**
     * 根据时间区间统计畅销top10商品
     *
     * @param begin
     * @param end
     * @return
     */
    SalesTop10ReportVO getSalesTop10Statistics(LocalDate begin, LocalDate end);
}

实现类

java 复制代码
package com.sky.service.impl;

import com.sky.dto.GoodsSalesDTO;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.UserMapper;
import com.sky.service.ReportService;
import com.sky.vo.OrderReportVO;
import com.sky.vo.SalesTop10ReportVO;
import com.sky.vo.TurnoverReportVO;
import com.sky.vo.UserReportVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
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.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ReportServiceImpl implements ReportService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private UserMapper userMapper;

    /**
     * 抽取方法:根据begin------------end时间区间创建日期集合
     *
     * @param begin
     * @param end
     * @return
     */
    private List<LocalDate> createTimeList (LocalDate begin, LocalDate end) {
        // 创建一个集合,用于存储从begin-end时间内的所有日期,用于查找对应的营业额
        List<LocalDate> dateList = new ArrayList<>();
        // 先加入起始日期
        dateList.add(begin);
        // 若还没有加入到最后一个日期,就一直加入
        while (!begin.equals(end)) {
            // 每次都将begin日期后延1天,直到begin = end
            begin = begin.plusDays(1);
            // 加入集合
            dateList.add(begin);
        }
        return dateList;
    }

    /**
     * 根据时间区间查询营业额数据
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) {
        // 通过抽取的方法创建时间区间的日期集合
        List<LocalDate> dateList = createTimeList(begin, end);
        // 创建营业额集合,用于存储每天的营业额
        List<Double> turnoverList = new ArrayList<>();
        // 遍历日期集合,按照每一天进行逻辑处理
        for (LocalDate date : dateList) {
            // 因为表中的订单的时间是LocalDateTime类型的,所以说要将日期集合中的LocalDate封装为LocalDateTime
            // 当天的营业额是大于当天的最小时间(00:00:00),小于当天的最大时间的(23:59:59),所以说可以将日期集合中的元素对应的
            // 那天的beginTime设置为当天最小时间;endTime设置为当天的最大时间
            LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
            LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
            // 用Map来封装查询的条件
            Map<Object, Object> map = new HashMap<>();
            // 要统计当天的营业额,只统计已经完成了的订单
            map.put("status", Orders.COMPLETED);
            // 封装查询的时间(时间为当天)
            map.put("begin", beginTime);
            map.put("end", endTime);
            Double turnover = orderMapper.sumAmount(map);
            // 判断当天是否有营业额,若没有营业额则turnover为空,但是这不符合前端展示的逻辑,需要对其检查,若没有营业额,那么营业额是0.0
            turnover = turnover == null ? 0.0 : turnover;
            // 将当前date对应的营业额加入turnover集合
            turnoverList.add(turnover);
        }
        // 处理数据返回
        // 使用StringUtils进行数据封装,封装为前端需要的格式返回。StringUtils是Apache的
        return TurnoverReportVO.builder()
                .dateList(StringUtils.join(dateList, ","))
                .turnoverList(StringUtils.join(turnoverList, ","))
                .build();
    }

    /**
     * 根据时间区间查询用户数据
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public UserReportVO getUserStatistics(LocalDate begin, LocalDate end) {
        // 通过抽取的方法创建时间区间的日期集合
        List<LocalDate> dateList = createTimeList(begin, end);
        // 新增用户集合
        List<Integer> newUserList = new ArrayList<>();
        // 总用户集合
        List<Integer> totalUserList = new ArrayList<>();
        for (LocalDate date : dateList) {
            LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
            LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
            // 总用户,只要在目标时间之前创建的用户都算是当前时间的总用户
            // 建议先查询总用户数,因为查询条件更加简单
            Integer totalUser = getUserCount(null, endTime);
            // 新增用户可以理解为:假如是今天是11.11日,
            // 那么在11.11日最小时间(00:00:00)------------11.11日最大时间(23:59:59)创建的用户都是这一天的新用户
            Integer newUser = getUserCount(beginTime, endTime);
            // 进行前端逻辑处理,将null变为0
            totalUser = totalUser == null ? 0 : totalUser;
            newUser = newUser == null ? 0 : newUser;
            // 加入对应集合
            totalUserList.add(totalUser);
            newUserList.add(newUser);
        }
        // 封装数据返回
        return UserReportVO.builder()
                .dateList(StringUtils.join(dateList, ","))
                .newUserList(StringUtils.join(newUserList, ","))
                .totalUserList(StringUtils.join(totalUserList, ","))
                .build();
    }

    /**
     * 根据时间区间统计用户数量
     *
     * @param beginTime
     * @param endTime
     * @return
     */
    private Integer getUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
        // 将开始时间和结束时间封装为map再在数据库中进行查询
        Map<Object, Object> map = new HashMap<>();
        map.put("begin", beginTime);
        map.put("end", endTime);
        return userMapper.countUsersByTime(map);
    }

    /**
     * 根据时间区间查询订单数据
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public OrderReportVO getOrdersStatistics(LocalDate begin, LocalDate end) {
        // 通过抽取的方法创建时间区间的日期集合
        List<LocalDate> dateList = createTimeList(begin, end);

        // 总订单集合
        List<Integer> totalOrdersList = new ArrayList<>();
        // 有效订单集合 valid   adj.有效的
        List<Integer> validOrdersList = new ArrayList<>();

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

            // 查询总订单
            Integer totalOrders = getOrdersCount(beginTime, endTime, null);
            // 查询有效订单
            Integer validOrders = getOrdersCount(beginTime, endTime, Orders.COMPLETED);
            // 进行前端逻辑处理,将null变为0
            totalOrders = totalOrders == null ? 0 : totalOrders;
            // TODO 细心!细心!细心!不要再犯这种傻逼错误
            validOrders = validOrders == null ? 0 : validOrders;
            // 将其加入对应的集合
            totalOrdersList.add(totalOrders);
            validOrdersList.add(validOrders);
        }

        // 计算总订单数量
        Integer totalOrdersCount = 0;
        for (Integer order : totalOrdersList) {
            totalOrdersCount += order;
        }
        // 计算总有效订单数量
        Integer validOrdersCount = 0;
        for (Integer order : validOrdersList) {
            validOrdersCount += order;
        }
        // 计算完单率
        // 如果没有订单,完单率就是0
        Double orderCompletionRate = 0.0;
        if (totalOrdersCount != 0) {
            // 只有存在订单,才计算完单率
         orderCompletionRate = validOrdersCount.doubleValue() / totalOrdersCount;
        }

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

    /**
     * 根据时间区间和订单状态查询订单数据
     *
     * @param beginTime
     * @param endTime
     * @param status
     * @return
     */
    private Integer getOrdersCount(LocalDateTime beginTime, LocalDateTime endTime, Integer status) {
        // 将时间和状态封装为map进行查询
        // 在SQL中先根据status查询,若status不对,那么就可以不用比对后面的属性,提高效率
        Map<Object, Object> map = new HashMap<>();
        map.put("status", status);
        map.put("begin", beginTime);
        map.put("end", endTime);
        return orderMapper.statisticsOrders(map);
    }

    /**
     * 根据时间区间统计畅销top10商品
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public SalesTop10ReportVO getSalesTop10Statistics(LocalDate begin, LocalDate end) {
        // 因为不需要统计每一天的销量,只需要统计在这段时间之内的销量,所以说不需要遍历每一天的销量,可以直接使用begin和end
        LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN);
        LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);

        // 查询销量前10的商品,并封装在GoodsSalesDTO中(商品名和销量)
        List<GoodsSalesDTO> goodsSalesDTOList = orderMapper.getSalesTop10(beginTime, endTime);

        // 处理goodsSalesDTOList中数据
        String nameList = StringUtils.join(goodsSalesDTOList.stream().map(GoodsSalesDTO::getName)
                .collect(Collectors.toList()), ",");

        String numberList = StringUtils.join(goodsSalesDTOList.stream().map(GoodsSalesDTO::getNumber)
                .collect(Collectors.toList()), ",");

        // 封装成对应的VO返回
        return SalesTop10ReportVO.builder()
                .nameList(nameList)
                .numberList(numberList)
                .build();
    }


}

Mapper相对简单,这里不过多赘述。

相关推荐
编码小袁27 分钟前
PHP:通往动态Web开发世界的桥梁
开发语言·前端·php
小蒜学长34 分钟前
校园周边美食探索及分享平台
java·spring boot·后端·spring·apache·美食
2401_857610031 小时前
SpringBoot技术下的共享汽车运营平台
spring boot·后端·汽车
liuxin334455661 小时前
高效编程训练:Spring Boot系统设计与实践
数据库·spring boot·php
Wx-bishekaifayuan2 小时前
PHP动物收容所管理系统-计算机设计毕业源码94164
java·css·spring boot·spring·spring cloud·servlet·php
初晴~2 小时前
【动态规划】打家劫舍类问题
java·数据结构·c++·python·算法·leetcode·动态规划
自信人间三百年3 小时前
数据结构与算法-前缀和数组
java·数据结构·算法·leetcode
翔云API3 小时前
PHP开发示例-vin码识别接口-引领汽车行业数字化新风向
开发语言·php
ac-er88883 小时前
如何对PHP的API接口权限认证
开发语言·php
菜鸟、小高3 小时前
从0开始学PHP面向对象内容之(常用魔术方法续一)
开发语言·php