当前位置: 首页 > article >正文

苍穹外卖 数据可视化

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

        ReportController

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

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);
}

        实现类

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相对简单,这里不过多赘述。 

 

 


http://www.kler.cn/a/390286.html

相关文章:

  • golang分布式缓存项目 Day1 LRU 缓存淘汰策略
  • [HarmonyOS]简单说一下鸿蒙架构
  • [ComfyUI]Flux:繁荣生态魔盒已开启,6款LORA已来,更有MJ6写实动漫风景艺术迪士尼全套
  • NCC前端调用查询弹框
  • RAFT: Recurrent All-Pairs Field Transforms for Optical Flow用于光流估计的循环全对场变换
  • 修改Mysql 8 的密码
  • 标准化 Git 提交信息的约定
  • 17RAL_Visual-Inertial Monocular SLAM with Map Reuse
  • 基础算法练习--滑动窗口(已完结)
  • 分布式环境下宕机的处理方案有哪些?
  • 简单易用的 Node.js Git库
  • Oracle XE命令行创建数据库的一波三折(已解决)
  • 深度学习在推荐系统中的应用
  • Taro React-Native IOS 打包发布
  • 【R78/G15 开发板测评】串口打印 DHT11 温湿度传感器、DS18B20 温度传感器数据,LabVIEW 上位机绘制演化曲线
  • 本地连接IP地址的自主设置指南‌
  • 力扣 LeetCode 209. 长度最小的子数组
  • 传统型视频展台方案分享
  • IDEA打开项目后,所有文件都在报错(包括JDK自带的类也报错)
  • 磁集成技术给磁性材料带来哪些新要求?
  • 使用闲置安卓手机实现程图传
  • 【C++笔记】C++三大特性之继承
  • wordpress搬家迁移后怎么修改数据库用户名
  • redis 三种持久化对比
  • websocket服务器(协程风格)--swoole进阶篇
  • 【Spring Boot 入门四】Spring Boot安全机制 - 保护你的应用安全