苍穹外卖day11笔记

今日首先介绍前端技术Apache ECharts,说明后端需要准备的数据,然后讲解具体统计功能的实现,包括营业额统计、用户统计、订单统计、销量排名。

一、ECharts

是什么

ECharts是一款基于 Javascript 的数据可视化图表库。我们用它来展示图表数据。

入门案例

步骤

1). 引入echarts.js 文件

2). 为 ECharts 准备一个设置宽高的 DOM

3). 初始化echarts实例

4). 指定图表的配置项和数据

5). 使用指定的配置项和数据显示图表

代码

js文件在黑马对应项目自取。

测试用的html代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ECharts</title>
    <!-- 引入刚刚下载的 ECharts 文件 -->
    <script src="echarts.js"></script>
  </head>
  <body>
    <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 指定图表的配置项和数据
      var option = {
        title: {
          text: '班级出勤人数'
        },
        tooltip: {},
        legend: {

          data: ['人数']
        },
        xAxis: {
          type: 'category',
          data: ['星期1', '星期2', '星期3', '星期4', '星期5']
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '人数',
            type: 'line',
            data: [160, 71, 66, 73, 68],
            smooth: true

          }
        ]
      };

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
    </script>
  </body>
</html>

结果页面如下:

然后我们打开ECharts官网Apache ECharts 选择一个图案来试着改一下。

首先进入官网,点击所有示例。

然后点击一个自己喜欢的样式:

复制左边的代码到原代码的option位置:

复制后代码如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ECharts</title>
    <!-- 引入刚刚下载的 ECharts 文件 -->
    <script src="echarts.js"></script>
  </head>
  <body>
    <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 指定图表的配置项和数据
      var option = {
  title: {
    text: 'Stacked Area Chart'
  },
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'cross',
      label: {
        backgroundColor: '#6a7985'
      }
    }
  },
  legend: {
    data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
  },
  toolbox: {
    feature: {
      saveAsImage: {}
    }
  },
  grid: {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  xAxis: [
    {
      type: 'category',
      boundaryGap: false,
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    }
  ],
  yAxis: [
    {
      type: 'value'
    }
  ],
  series: [
    {
      name: 'Email',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [120, 132, 101, 134, 90, 230, 210]
    },
    {
      name: 'Union Ads',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [220, 182, 191, 234, 290, 330, 310]
    },
    {
      name: 'Video Ads',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [150, 232, 201, 154, 190, 330, 410]
    },
    {
      name: 'Direct',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [320, 332, 301, 334, 390, 330, 320]
    },
    {
      name: 'Search Engine',
      type: 'line',
      stack: 'Total',
      label: {
        show: true,
        position: 'top'
      },
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [820, 932, 901, 934, 1290, 1330, 1320]
    }
  ]
};

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
    </script>
  </body>
</html>

页面展示结果如下:

总结

传输的两列数据,分别是下标和数据。在如下位置:

二、营业额统计

查看接口文档

分析

控制层

控制层只要接收数据传给业务层,返回VO(已经有设计好的TurnoverReportVO了)就可以。重点在业务层和持久层。

业务层

具体要处理得到两类,或者说两列数据,包括:

  1. 日期列表
  2. 营业额列表

所以步骤便是:

  1. 获取日期列表
  2. 获取日期对应的营业额的列表
  3. 封装返回 

持久层

那么持久层需要的操作就在第2步,即:

  • 根据日期查找当日营业额

之后几个案例都是大差不差的层次结构,除了数据的种类要求不同。

 具体代码

控制层

@RestController
@Slf4j
@Api(tags = "统计相关")
@RequestMapping("/admin/report")
public class ReportController {

    @Autowired
    private ReportService reportService;

    @GetMapping("/turnoverStatistics")
    public Result turnoverStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                                     @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
        TurnoverReportVO turnoverReportVO = reportService.turnoverStatistics(begin, end);
        return Result.success(turnoverReportVO);
    }
}

业务层

@Service
public class ReportServiceImpl implements ReportService {

    @Autowired
    private OrdersMapper ordersMapper;

    @Override
    public TurnoverReportVO turnoverStatistics(LocalDate begin, LocalDate end) {
        // 1. 获取日期列表
        List<LocalDate> list = getDateList(begin, end);
        // 2. 查询每日营业额
        List<Double> result = new ArrayList<>();
        Double turnover;
        LocalDateTime dayBegin;
        LocalDateTime dayEnd;
        if (list != null && list.size() > 0) {
            dayBegin = LocalDateTime.of(list.get(0), LocalTime.MIN);  // 知识点2和3
            dayEnd = LocalDateTime.of(list.get(0), LocalTime.MAX);  // 知识点2和3
        } else {
            return new TurnoverReportVO();
        }
        for (LocalDate localDate : list) {
            Map<String, Object> map = new HashMap<>();
            map.put("status", Orders.COMPLETED);
            map.put("begin", dayBegin);
            map.put("end", dayEnd);
            turnover = ordersMapper.sumByMap(map);  // 知识点4
            result.add(turnover == null ? 0 : turnover);

            dayBegin = dayBegin.plusDays(1);
            dayEnd = dayEnd.plusDays(1);
        }
        // 3. 返回
        TurnoverReportVO turnoverReportVO = new TurnoverReportVO();
        turnoverReportVO.setDateList(StringUtils.join(list, ","));
        turnoverReportVO.setTurnoverList(StringUtils.join(result, ","));
        return turnoverReportVO;
    }

    private List<LocalDate> getDateList(LocalDate begin, LocalDate end) {
        List<LocalDate> list = new ArrayList<>();
        while (begin.compareTo(end) <= 0) {  // 知识点1
            list.add(begin);
            begin = begin.plusDays(1);
        }
        return list;
    }
}

4个知识点

这里体现了4个知识点:

  1. 日期之间用compareTo比较
  2. LocalDate和LocalTime组合成LocalDateTime,用LocalDateTime的of方法
  3. LocalTime.MIN与LocalTime.MAX
  4. 用Map封装数据交给mapper查找。

持久层

直接上xml文件了:

elect id="sumByMap" resultType="java.lang.Double">
    select sum(amount)
    from orders
    <where>
        <if test="status!=null and status!=''">
            status = #{status}
        </if>
        <if test="begin!=null and end!=null">
            and order_time between #{begin} and #{end}
        </if>
    </where>
</select>

三、用户统计

接口文档

 分析所需数据

由于三层的架构都大差不差,所以直接介绍所需数据的不同。

  1. 日期列表
  2. 新增用户数列表
  3. 总用户数列表

具体代码

控制层

@GetMapping("/userStatistics")
public Result userStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                                 @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
    UserReportVO userReportVO = reportService.userStatistics(begin, end);
    return Result.success(userReportVO);
}

业务层

@Override
public UserReportVO userStatistics(LocalDate begin, LocalDate end) {
    // 1. 获取日期列表
    List<LocalDate> dateList = getDateList(begin, end);
    // 2. 获取用户数量列表
    List<Integer> newUserList = new ArrayList<>();
    List<Integer> totalUserList = new ArrayList<>();

    LocalDateTime dayBegin;
    LocalDateTime dayEnd;

    if (dateList != null && dateList.size() > 0) {
        dayBegin = LocalDateTime.of(dateList.get(0), LocalTime.MIN);
        dayEnd = LocalDateTime.of(dateList.get(0), LocalTime.MAX);
    } else {
        return new UserReportVO();
    }
    Integer totalUser;
    Integer newUser;
    for (LocalDate localDate : dateList) {
        Map<String, Object> map = new HashMap<>();
        map.put("end", dayEnd);
        totalUser = userMapper.countByMap(map);
        totalUserList.add(totalUser == null ? 0 : totalUser);
        map.put("begin", dayBegin);
        newUser = userMapper.countByMap(map);
        newUserList.add(newUser == null ? 0 : newUser);

        dayBegin = dayBegin.plusDays(1);
        dayEnd = dayEnd.plusDays(1);
    }
    // 3. 返回
    UserReportVO userReportVO = new UserReportVO();
    userReportVO.setDateList(StringUtils.join(dateList, ","));
    userReportVO.setNewUserList(StringUtils.join(newUserList, ","));
    userReportVO.setTotalUserList(StringUtils.join(totalUserList, ","));
    return userReportVO;
}

3个注意的点

第一点,获取日期列表可以抽取出来,供营业额统计、用户统计共同调用。

小tips,抽取函数的快捷键是 ctrl + alt + m 哦。

第二点,持久层的两次查找,可以巧妙的用一个函数来完成的。用动态sql的if判断,分为有begin时间的判断和没有begin时间的判断进行处理。具体看下面持久层代码。

第三点,两个统计都要判断持久层查到的结果是不是null,是的话要归为0哦。

持久层

<select id="countByMap" resultType="java.lang.Integer">
    select count(*) from user
    <where>
        <if test="end!=null">
            and create_time &lt;= #{end}
        </if>
        <if test="begin!=null">
            and create_time &gt;= #{begin}
        </if>
    </where>
</select>

四、订单统计

接口文档

所需数据

  1. 日期列表
  2. 所有订单每日总数列表
  3. 所有订单总数
  4. 有效订单每日总数列表
  5. 有效订单总数
  6. 订单完成率

具体代码

控制层

@GetMapping("/ordersStatistics")
public Result ordersStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                               @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
    OrderReportVO orderReportVO = reportService.ordersStatistics(begin, end);
    return Result.success(orderReportVO);
}

业务层

@Override
public OrderReportVO ordersStatistics(LocalDate begin, LocalDate end) {
    OrderReportVO orderReportVO = new OrderReportVO();
    // 1. 日期列表
    List<LocalDate> dateList = getDateList(begin, end);
    if (dateList == null) {
        return orderReportVO;
    }
    // 2. 订单数列表
    List<Integer> totalOrderList = new ArrayList<>();
    // 3. 有效订单数列表
    List<Integer> validOrderList = new ArrayList<>();
    // 4. 订单总数
    Integer totalOrderCount = 0;
    // 5. 有效订单总数
    Integer validOrderCount = 0;
    for (LocalDate localDate : dateList) {
        Map map = new HashMap();
        map.put("begin", LocalDateTime.of(localDate, LocalTime.MIN));
        map.put("end", LocalDateTime.of(localDate, LocalTime.MAX));
        Integer total = ordersMapper.countByMap(map);
        total = total == null ? 0 : total;
        map.put("status", Orders.COMPLETED);
        Integer valid = ordersMapper.countByMap(map);
        valid = valid == null ? 0 : valid;
        totalOrderList.add(total);
        validOrderList.add(valid);
        totalOrderCount += total;
        validOrderCount += valid;
    }
    // 6. 订单完成率
    Double completionR = 0.0;
    if (totalOrderCount != null) {
        completionR = validOrderCount * 1.0 / totalOrderCount;
    }

    orderReportVO.setDateList(StringUtils.join(dateList, ","));
    orderReportVO.setOrderCountList(StringUtils.join(totalOrderList, ","));
    orderReportVO.setValidOrderCountList(StringUtils.join(validOrderList, ","));
    orderReportVO.setTotalOrderCount(totalOrderCount);
    orderReportVO.setValidOrderCount(validOrderCount);
    orderReportVO.setOrderCompletionRate(completionR);

    return orderReportVO;
}

1个注意的巩固知识点

还是用动态sql来巧妙的满足一个函数查询两种不同的数据,即status的if判断是否查询。

持久层

<select id="countByMap" resultType="java.lang.Integer">
    select count(id) from orders
    <where>
        <if test="status != null">
            and status = #{status}
        </if>
        <if test="begin != null">
            and order_time &gt;= #{begin}
        </if>
        <if test="end != null">
            and order_time &lt;= #{end}
        </if>
    </where>
</select>

没啥好说的,算一个巩固练习。

五、销量排名top10

接口文档

所需数据

  1. 商品名列表
  2. 销量列表

具体代码

控制层

@GetMapping("/top10")
public Result top10(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                    @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
    SalesTop10ReportVO salesTop10ReportVO = reportService.top10(begin, end);
    return Result.success(salesTop10ReportVO);
}

业务层

@Override
public SalesTop10ReportVO top10(LocalDate begin, LocalDate end) {
    List<GoodsSalesDTO> goodsSalesDTOS = ordersMapper.countSaleTop10(LocalDateTime.of(begin, LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
    if (goodsSalesDTOS == null) {
        return new SalesTop10ReportVO();
    }

    List<String> nameList = new ArrayList<>();
    List<Integer> numberList = new ArrayList<>();
    for (GoodsSalesDTO goodsSalesDTO : goodsSalesDTOS) {
        nameList.add(goodsSalesDTO.getName());
        numberList.add(goodsSalesDTO.getNumber());
    }  // 思考:这里可不可以简写?


    SalesTop10ReportVO salesTop10ReportVO = new SalesTop10ReportVO();
    salesTop10ReportVO.setNameList(StringUtils.join(nameList, ","));
    salesTop10ReportVO.setNumberList(StringUtils.join(numberList, ","));
    return salesTop10ReportVO;
}

2个注意的点

第一个,下面持久层的多表查询。

第二个,查询到DTO后,对象数据到两列数据的转换。

  • 法一,普通方法,老老实实用两个List添加。
  • 法二,流方法。值得练习,公司中可能会见到、用到很多,资深程序员必备。

练习:用流的写法完成查询数据到两个列表数据的转换

尝试用流的写法完成。

答案如下:

@Override
public SalesTop10ReportVO top10(LocalDate begin, LocalDate end) {
    List<GoodsSalesDTO> goodsSalesDTOS = ordersMapper.countSaleTop10(LocalDateTime.of(begin, LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
    if (goodsSalesDTOS == null) {
        return new SalesTop10ReportVO();
    }

//        List<String> nameList = new ArrayList<>();
//        List<Integer> numberList = new ArrayList<>();
//        for (GoodsSalesDTO goodsSalesDTO : goodsSalesDTOS) {
//            nameList.add(goodsSalesDTO.getName());
//            numberList.add(goodsSalesDTO.getNumber());
//        }
    // ==========注意这里的写法==========
    List<String> nameList = goodsSalesDTOS.stream().map(GoodsSalesDTO::getName).collect(Collectors.toList());
    List<Integer> numberList = goodsSalesDTOS.stream().map(GoodsSalesDTO::getNumber).collect(Collectors.toList());
    // ==========注意上面的写法==========

    SalesTop10ReportVO salesTop10ReportVO = new SalesTop10ReportVO();
    salesTop10ReportVO.setNameList(StringUtils.join(nameList, ","));
    salesTop10ReportVO.setNumberList(StringUtils.join(numberList, ","));
    return salesTop10ReportVO;
}

持久层

<select id="countSaleTop10" resultType="com.sky.dto.GoodsSalesDTO">
    select t2.name, sum(t2.number) as number
    from orders as t1
             inner join order_detail as t2
                        on t1.id = t2.order_id
    where t1.status = 5
      and t1.order_time >= #{begin}
      and t1.order_time &lt;= #{end}
    group by t2.name
    order by number desc limit 0, 10;
</select>

复习

1.ECharts最少需要准备几列数据?

2.LocalDateTime的比较,以及比较接口讲解的复习

3.日期时间的拼接、时间在一天的最大、最小值

4.Map封装数据进行查找的代码手法

5.统计中,持久层查询为null的归0化处理;

6.查找增量与总量时的简写mapper查询。

猜你喜欢

转载自blog.csdn.net/m0_46948660/article/details/132203879