苍穹外卖 数据可视化

        将营业额、用户数据、订单数据、商品销量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是前端需要使用的东西,后端只需要按照和前端的约定,为其提供数据即可@Autowiredprivate 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 {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate 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 = endbegin = begin.plusDays(1);// 加入集合dateList.add(begin);}return dateList;}/*** 根据时间区间查询营业额数据** @param begin* @param end* @return*/@Overridepublic 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.0turnover = 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*/@Overridepublic 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变为0totalUser = 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*/@Overridepublic 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变为0totalOrders = 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;}// 计算完单率// 如果没有订单,完单率就是0Double 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*/@Overridepublic SalesTop10ReportVO getSalesTop10Statistics(LocalDate begin, LocalDate end) {// 因为不需要统计每一天的销量,只需要统计在这段时间之内的销量,所以说不需要遍历每一天的销量,可以直接使用begin和endLocalDateTime 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.mzph.cn/pingmian/60244.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

漏洞与攻击技术详解

漏洞与攻击技术是网络安全领域中的重要议题&#xff0c;它们之间存在着密切的关系。以下是对漏洞与攻击技术的详细分析&#xff1a; 一、漏洞的定义与分类漏洞是指在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷&#xff0c;这些缺陷使得攻击者能够在未授权的情况下访…

kafka面试题解答(四)

5、消费者组和分区数之间的关系是怎样的&#xff1f; 消费者组数小于等于分区数&#xff0c;消费者组内每个消费者负责消费不同分区的数据&#xff0c;一个分区只能由一个组内消费者消费。 6、kafka如何知道哪个消费者消费哪个分区&#xff1f; 生产者把数据发送给各个分区&…

鸿蒙华为商城APP案例

模拟器运行效果如下&#xff1a; 鸿蒙版APP-华为商城-演示视频

C++【STL容器系列(二)】vector的模拟实现

文章目录 1. vector的结构2. vector的默认成员函数2.1构造函数2.1.1 默认构造2.1.2 迭代器构造2.1.3 用n个val初始化构造 2.2 拷贝构造2.3 析构函数2.4 operator 3. vector iterator函数3.1 begin 和 cbegin函数3.2 end() 和 cend()函数 4. vector的小函数4.1 size函数4.2 capa…

git修改当前分支名称并推送到远程仓库

git修改当前分支名称并推送到远程仓库 在Git中修改当前分支的名称可以通过两种主要方式进行&#xff1a;直接在本地修改分支名称&#xff0c;或者如果你已经推送了分支到远程仓库&#xff0c;你也需要更新远程分支的名称。以下是详细步骤&#xff1a; 修改本地分支名称 查看当…

第 8 章 - Go语言 数组与切片

在Go语言中&#xff0c;数组和切片是两种非常基础且重要的数据结构。它们都用来存储一系列相同类型的元素&#xff0c;但是它们之间存在一些关键的区别。下面我们将详细探讨数组和切片的定义、使用以及切片的动态特性。 数组的定义和使用 定义 在Go语言中&#xff0c;数组是…

Linux开发讲课49--- Linux 启动过程分析

理解运转良好的系统对于处理不可避免的故障是最好的准备。 启动过程非常简单。内核在单核上以单线程和同步状态启动&#xff0c;似乎可以理解。但内核本身是如何启动的呢&#xff1f;initrd&#xff08;initial ramdisk&#xff09; 和引导程序(bootloader)具有哪些功能&#…

vscode中执行git合并操作需要输入合并commit信息,打开的nano小型文本编辑器说明-

1.前提: VScode中的git组件执行任何合并动作的时候需要提交远程合并的commit信息,然后编辑器自动打开的是nano文本编辑器 2.nano编辑器说明: 1.保存文件:按 Ctrl + O,然后按 Enter 来保存文件。 2.退出编辑器:按 Ctrl + X,这会退出 nano。 3.剪切文本:移动光标到要剪…

Java 并发相关集合

文章目录 一、CopyOnWriteArrayList 源码1.1. 概述1.2. 思想1.3. 源码① 数据结构② 初始化③ 添加元素④ 获取元素⑤ 删除元素 二、ArrayBlockingQueue 源码2.1. 概述2.2. 思想2.3. 源码① 数据结构② 初始化③ 阻塞式获取和新增元素④ 非阻塞式获取和新增元素⑤ 指定超时时间…

Mysql个人八股总结

1.一条 SQL 查询语句是如何执行的 第一步&#xff1a;连接器 连接数据库&#xff1a;当用户发起SQL查询时&#xff0c;连接器负责与数据库建立连接&#xff0c;验证用户身份并准备执行查询。 第二步&#xff1a;查询缓存 检查查询缓存&#xff1a;在执行查询之前&#xff0…

MySQL数据导入与清洗

在现代数据分析的工作流程中,数据导入与清洗是最基础且重要的环节。无论是通过CSV、Excel,还是SQL文件进行数据导入,数据清洗的操作对于数据的质量至关重要。高质量的数据源是后续分析的根本,数据清洗可以帮助分析者获得干净、整洁且可靠的数据集,减少数据噪音,提升分析的…

Linux如何更优质调节系统性能

一、硬件优化 增加物理内存&#xff1a;最直接的提升系统性能的方法。内存不足时&#xff0c;系统会频繁进行交换&#xff08;swapping&#xff09;活动&#xff0c;这会显著降低系统的响应速度&#xff0c;因为磁盘IO速度远低于内存访问速度。通过增加内存&#xff0c;可以减…

GET和POST的区别

GET 和 POST 是 HTTP 协议中最常用的两种请求方法&#xff0c;它们在用途、安全性、数据处理等方面存在显著差异。下面是 GET 和 POST 的详细对比&#xff1a; GET 请求 1. 用途&#xff1a; 主要用于从服务器获取数据。通常用于检索信息&#xff0c;如搜索、查询数据库等。…

AutoDL使用简记

AutoDL使用简记 一、前言二、AutoDL显卡配置、价格简介2.1显卡配置及价格2.2计费方式的种类2.3开通会员及优惠 三、AutoDL使用教程3.1选择深度学习架构3.2文件传输3.3运行程序 一、前言 在进行深度学习模型训练时&#xff0c;通常会面临本地显卡显存或者运行速度的不足&#x…

基于STM32智能电流表

采用STM32F103C8T6微控制器为核心&#xff0c;设计了一款精密的电流表。该电流表通过精确采集采样电阻上的分压信号&#xff0c;并进行信号放大处理&#xff0c;随后利用ADC&#xff08;模数转换器&#xff09;高效地捕获放大后的电压信号&#xff0c;通过一系列算法运算&#…

【harbor】离线安装2.9.0-arm64架构服务制作和升级部署

harbor官网地址&#xff1a;Harbor 参考文档可以看这里&#xff1a;部署 harbor 2.10.1 arm64 - 简书。 前提环境准备&#xff1a; 安装docker 和 docker-compose 先拉arm64架构的harbor相关镜像 docker pull --platformlinux/arm64 ghcr.io/octohelm/harbor/harbor-regist…

Java API类与接口:类的转换方法与正则表达式

文章目录 Java包装类的概述对应包装类包装类的转换方法&#xff08;parse)Integer.parseInt(String s)Long.parseLong(String s)Byte.parseByte(String s)Short.parseShort(String s)Float.parseFloat(String s)Double.parseDouble(String s) 正则表达式常用方法 字符规则. 匹配…

Linux: network: ip link M-DOWN的具体含义是什么?

文章目录 参考简介实例代码解释openstack上的显示如果是在一个interface上建立了vlan参考 https://unix.stackexchange.com/questions/348327/using-ip-what-does-m-down-mean www.policyrouting.org/iproute2.doc.html#ss9.1 简介 是指上一级的接口的状态。 实例 4: ersp…

支持 Win10 的网络环境模拟(丢包,延迟,带宽)

升级 Windows 10 以后&#xff0c;原来各种网络模拟软件都挂掉了&#xff0c;目前能用的就是只有 clumsy&#xff1a; 唯一问题是不支持模拟带宽&#xff0c;那么平时要模拟一些糟糕的网络情况的话&#xff0c;是不太方便的&#xff0c;而开虚拟机用 Linux tc 或者设置个远程 l…

网页web无插件播放器EasyPlayer.js点播播放器遇到视频地址播放不了的现象及措施

在数字媒体时代&#xff0c;视频点播已成为用户获取信息和娱乐的重要方式。EasyPlayer.js作为一款流行的点播播放器&#xff0c;以其强大的功能和易用性受到广泛欢迎。然而&#xff0c;在使用过程中&#xff0c;用户可能会遇到视频地址无法播放的问题&#xff0c;这不仅影响用户…