【学习笔记】尚硅谷大数据项目之Flink实时数仓---数据可视化接口实现

这里写自定义目录标题

第 1 章 数据可视化接口

1.1 设计思路

之前数据分层处理,最后把轻度聚合的结果保存到 ClickHouse 中,主要的目的就是提供即时的数据查询、统计、分析服务。这些统计服务一般会用两种形式展现,一种是为专业的数据分析人员的 BI 工具,一种是面向非专业人员的更加直观的数据大屏。
以下主要是面向百度的 sugar 的数据大屏服务的接口开发。

1.2 需求梳理

1.2.1 最终显示效果图

在这里插入图片描述

1.2.2 分析可视化大屏

在这里插入图片描述
在可视化大屏中每个组件都需要一个单独的接口,图中一共涉及 8 个组件。
在这里插入图片描述

1.2.3 接口执行过程

在这里插入图片描述
之前我们实现了 DWS 层计算后写入到 ClickHouse 中,接下来就是要为可视化大屏服务,提供一个数据接口用来查询 ClickHouse 中的数据。这里主要有两项工作

➢ 配置可视化大屏服务。
➢ 编写数据查询接口以供可视化大屏进行访问。

第 2 章 Sugar 数据大屏

2.1 产品介绍

Sugar 是百度云推出的敏捷 BI 和数据可视化平台,目标是解决报表和大屏的数据 BI 分析和可视化问题,解放数据可视化系统的开发人力。

2.2 使用入口

https://cloud.baidu.com/product/sugar.html

2.3 创建数据大屏

第 3 章 总成交金额接口

3.1 Sugar 组件:数字翻牌器

3.1.1 添加组件

从大屏的编辑器上方选择【指标】→【数字翻牌器】

3.1.2 配置组件

点击组件,在右侧的菜单中选择【数据】,绑定方式改为【API 拉取】
下方的路径填写 $API_HOST/api/sugar/gmv
这个就是 sugar 会周期性访问的数据接口地址,可以自定义,其中$API_HOST 是个全局变量,需要在空间中配置(后面再说)。

3.1.3 查询组件需要的数据格式

在数据绑定的位置选择【静态 JSON】,可以看到数据需要的 JSON 格式
在这里插入图片描述

3.1.4 接口访问路径以及返回格式

➢ 访问路径

/api/sugar/gmv

➢ 返回格式

{
 "status": 0,
 "msg": "",
 "data": 1201081.1632389291
}

3.2 数据接口实现

3.2.1 创建数据接口模块

  1. 在 gmall2021-parent 项目下创建新的模块 gmall2021-publisher
  2. 在 pom.xml 文件中添加需要的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.atguigu</groupId>
    <artifactId>gmall2021-publisher-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gmall2021-publisher-test</name>
    <description>gmall2021-publisher-test</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>
        <dependency>
            <groupId>ru.yandex.clickhouse</groupId>
            <artifactId>clickhouse-jdbc</artifactId>
            <version>0.1.55</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.1</version>
                <configuration>
                    <mainClass>com.atguigu.gmall2021publishertest.Gmall2021PublisherTestApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3.2.2 代码分层结构以及实现

➢ 代码结构
在这里插入图片描述

  1. 修改 Springboot 核心配置文件 application.properties
server.port=8070
#配置 ClickHouse 驱动以及 URL
spring.datasource.driver-class-name=ru.yandex.clickhouse.ClickHouseDriver
spring.datasource.url=jdbc:clickhouse://hadoop102:8123/default
  1. 在 Application 中添加@MapperScan 的注解
package com.atguigu.gmall2021publishertest;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = "com.atguigu.gmall2021publishertest.mapper")
public class Gmall2021PublisherTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(Gmall2021PublisherTestApplication.class, args);
    }

}

  1. Mapper 层:创建 ProductStatsMapper 接口
package com.atguigu.gmall2021publishertest.mapper;


import org.apache.ibatis.annotations.Select;

import java.math.BigDecimal;

public interface ProductStatsMapper {

    // select sum(order_amount) from product_stats_2021 where toYYYYMMDD(stt)=20210901;
    @Select("select sum(order_amount) from product_stats_2021 where toYYYYMMDD(stt)=#{date}")
    BigDecimal selectGMV(int date);
}

  1. Service 层:创建 ProductStatsService 接口
package com.atguigu.gmall2021publishertest.service;

import com.atguigu.gmall2021publishertest.mapper.ProductStatsMapper;
import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal;

public interface SugarService {

    //获取某一天的总交易额
    public BigDecimal getGMV(int date);
}


  1. Service 层:创建 ProductStatsServiceImpl 实现类
package com.atguigu.gmall2021publishertest.service.impl;

import com.atguigu.gmall2021publishertest.mapper.ProductStatsMapper;
import com.atguigu.gmall2021publishertest.service.SugarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

@Service
public class SugarServiceImpl implements SugarService {

    @Autowired
    private ProductStatsMapper productStatsMapper;

    @Override
    public BigDecimal getGMV(int date){
        return productStatsMapper.selectGMV(date);
    }
}

  1. Controller 层:创建 SugarController 类
    该类主要接收用户请求,并做出相应。根据 sugar 不同的组件,返回不同的格式
package com.atguigu.gmall2021publishertest.controller;

import com.atguigu.gmall2021publishertest.service.SugarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;

@RestController
@RequestMapping("/api/sugar")
public class SugerController {

    @Autowired
    private SugarService sugarService;


    @RequestMapping("/gmv")
    public String getGmv(@RequestParam(value = "date", defaultValue = "0" ) int date){
        if(date == 0){
            date = getToday();
        }

        return "{ " +
                "  \"status\": 0, " +
                "  \"msg\": \"\", " +
                "  \"data\": " + sugarService.getGMV(date)+ " " +
                "}";
    }

    private int getToday() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String dateTime = sdf.format(System.currentTimeMillis());
        return Integer.parseInt(dateTime);
    }
}

3.2.3 测试本地接口

  1. 启动 SpringBoot 应用程序
  2. 用浏览器访问测试接口
    http://localhost:8070/api/sugar/gmv
  3. 输出结果
    在这里插入图片描述

3.3 内网穿透

3.3.1 作用

通常个人电脑无论是连接 WIFI 上网还是用网线上网,都是属于局域网里边的,外网无法直接访问到你的电脑,内网穿透可以让你的局域网中的电脑实现被外网访问功能。

3.3.2 工具

目前国内网穿透工具很多,常见的比如花生壳、Ngrok、网云穿等。
官网:
花生壳:https://hsk.oray.com
Ngrok: http://www.ngrok.cc
网云穿:http://www.neiwangchuantou.net/

3.4 配置 Sugar 大屏

3.4.1 配置服务器全局 Host

回到 Sugar 的空间管理中,在【空间设置】中增加$API_HOST

3.4.2 大屏刷新数据

然后回到大屏配置中,刷新图表数据,能看到数字已经显示

第 4 章 商品交易额不同维度的统计

4.1 三个关于商品交易额方面的统计

➢ 品牌,水平柱状图
➢ 品类,饼形图
➢ 商品 spu,轮播图
这三个的共同特征是可以根据商品统计信息计算出来。

4.2 Sugar 组件:横向柱图、轮播饼图、轮播表格

4.2.1 添加组件

  1. 横向柱图,用于显示品牌排行
  2. 轮播饼图,用于显示品类图
  3. 轮播表格,用于显示热门商品排行

4.2.2 品牌排行的柱形图组件配置

  1. 修改获取数据的方式,指定访问路径
    访问路径: ${API_HOST}/api/sugar/trademark?limit=5
  2. 修改排序规则
    因为排序规则是从下到上,所以排序定位从小到大
  3. 查看返回值数据格式
{
  "status": 0,
  "msg": "",
  "data": {
    "categories": [
      "苹果",
      "三星",
      "华为",
      "oppo",
      "vivo",
      "小米62"
    ],
    "series": [
      {
        "name": "手机品牌",
        "data": [
          9922,
          5774,
          5323,
          8043,
          7511,
          6487
        ]
      }
    ]
  }
}

4.2.3 品类分布的饼形图组件配置

  1. 修改获取数据的方式,指定访问路径
    访问路径:${API_HOST}/api/sugar/category3
  2. 查看返回值数据格式
{
  "status": 0,
  "msg": "",
  "data": [
    {
      "name": "PC",
      "value": 97,
      "url": "http://www.baidu.com"
    },
    {
      "name": "iOS",
      "value": 50,
      "url": "http://www.baidu.com"
    },
    {
      "name": "Android",
      "value": 59,
      "url": "http://www.baidu.com"
    },
    {
      "name": "windows phone",
      "value": 29
    },
    {
      "name": "Black berry",
      "value": 3
    },
    {
      "name": "Nokia S60",
      "value": 2
    },
    {
      "name": "Nokia S90",
      "value": 1
    }
  ]
}

4.2.4 商品排行的轮播表格组件配置

  1. 修改获取数据的方式,指定访问路径
    访问路径:${API_HOST}/api/sugar/spu?limit=10
  2. 查看返回值数据格式
{
  "status": 0,
  "msg": "",
  "data": {
    "columns": [
      {
        "name": "商品名称",
        "id": "spu_name"
      },
      {
        "name": "成交金额",
        "id": "amount"
      }
    ],
    "rows": [
      {
        "spu_name": "商品 1",
        "amount": "金额 1"
      },
      {
        "spu_name": "商品 2",
        "amount": "金额 2"
      },
      {
        "spu_name": "商品 3",
        "amount": "金额 3"
      }
    ]
  }
}

4.3 数据接口实现

这三个图基本上都是根据用不同维度进行分组,金额进行聚合的方式查询商品统计表。
直接先实现三个 sql 查询

4.3.1 创建商品交易额统计实体类 ProductStats

package com.atguigu.gmall2021publishertest.bean;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
 * Desc: 商品交易额统计实体类
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductStats {
    String stt;
    String edt;
    Long sku_id;
    String sku_name;
    BigDecimal sku_price;
    Long spu_id;
    String spu_name;
    Long tm_id ;
    String tm_name;
    Long category3_id ;
    String category3_name ;
    @Builder.Default
    Long display_ct=0L;
    @Builder.Default
    Long click_ct=0L;
    @Builder.Default
    Long cart_ct=0L;
    @Builder.Default
    Long order_sku_num=0L;
    @Builder.Default
    BigDecimal order_amount=BigDecimal.ZERO;
    @Builder.Default
    Long order_ct=0L;
    @Builder.Default
    BigDecimal payment_amount=BigDecimal.ZERO;
    @Builder.Default
    Long refund_ct=0L;
    @Builder.Default
    BigDecimal refund_amount=BigDecimal.ZERO;
    @Builder.Default
    Long comment_ct=0L;
    @Builder.Default
    Long good_comment_ct=0L ;
    Long ts;
}

4.3.2 Mapper 层:在 ProductStatsMapper 中添加方法

    //统计某天不同 SPU 商品交易额排名
    @Select("select spu_id,spu_name,sum(order_amount) order_amount," +
            "sum(product_stats.order_ct) order_ct from product_stats_2021 " +
            "where toYYYYMMDD(stt)=#{date} group by spu_id,spu_name " +
            "having order_amount>0 order by order_amount desc limit #{limit} ")
    public List<ProductStats> getProductStatsGroupBySpu(@Param("date") int date, @Param("limit") int limit);

    //统计某天不同类别商品交易额排名
    @Select("select category3_id,category3_name,sum(order_amount) order_amount " +
            "from product_stats_2021 " +
            "where toYYYYMMDD(stt)=#{date} group by category3_id,category3_name " +
            "having order_amount>0 order by order_amount desc limit #{limit}")
    public List<ProductStats> getProductStatsGroupByCategory3(@Param("date")int date ,
                                                              @Param("limit") int limit);

    //统计某天不同品牌商品交易额排名
    @Select("select tm_id,tm_name,sum(order_amount) order_amount " +
            "from product_stats_2021 " +
            "where toYYYYMMDD(stt)=#{date} group by tm_id,tm_name " +
            "having order_amount>0 order by order_amount desc limit #{limit} ")
    public List<ProductStats> getProductStatsByTrademark(@Param("date")int date,
                                                         @Param("limit") int limit);

4.3.3 Service 层:在 ProductStatsService 中增加方法

    //统计某天不同 SPU 商品交易额排名
    public List<ProductStats> getProductStatsGroupBySpu(int date, int limit);
    //统计某天不同类别商品交易额排名
    public List<ProductStats> getProductStatsGroupByCategory3(int date,int limit);
    //统计某天不同品牌商品交易额排名
    public List<ProductStats> getProductStatsByTrademark(int date,int limit);

4.3.4 Service 层:在 ProductStatsServiceImpl 增加方法实现

 @Override
    public List<ProductStats> getProductStatsGroupBySpu(int date, int limit) {
        return productStatsMapper.getProductStatsGroupBySpu(date, limit);
    }
    @Override
    public List<ProductStats> getProductStatsGroupByCategory3(int date, int limit) {
        return productStatsMapper.getProductStatsGroupByCategory3(date, limit);
    }
    @Override
    public List<ProductStats> getProductStatsByTrademark(int date, int limit) {
        return productStatsMapper.getProductStatsByTrademark(date, limit);
    }

4.3.5 Controller 层:在 SugarCongroller 添加方法

注意:Controller 方法的定义必须依照,定好的接口访问路径和返回值格式

  1. 商品列表接口方法
    @RequestMapping("/spu")
    public String getProductStatsGroupBySpu(
            @RequestParam(value = "date", defaultValue = "0") Integer date,
            @RequestParam(value = "limit", defaultValue = "10") int limit) {
        if (date == 0) date = now();
        List<ProductStats> statsList = productStatsService.getProductStatsGroupBySpu(date, limit);
        //设置表头
        StringBuilder jsonBuilder =
                new StringBuilder(" " +
                        "{\"status\":0,\"data\":{\"columns\":[" +
                        "{\"name\":\"商品名称\",\"id\":\"spu_name\"}," +
                        "{\"name\":\"交易额\",\"id\":\"order_amount\"}," +
                        "{\"name\":\"订单数\",\"id\":\"order_ct\"}]," +
                        "\"rows\":[");
        //循环拼接表体
        for (int i = 0; i < statsList.size(); i++) {
            ProductStats productStats = statsList.get(i);
            if (i >= 1) {
                jsonBuilder.append(",");
            }
            jsonBuilder.append("{\"spu_name\":\"" + productStats.getSpu_name() + "\"," +
                    "\"order_amount\":" + productStats.getOrder_amount() + "," +
                    "\"order_ct\":" + productStats.getOrder_ct() + "}");
        }
        jsonBuilder.append("]}}");
        return jsonBuilder.toString();
    }
  1. 品类接口方法
  @RequestMapping("/category3")
    public String getProductStatsGroupByCategory3(
            @RequestParam(value = "date", defaultValue = "0") Integer date,
            @RequestParam(value = "limit", defaultValue = "4") int limit) {
        if (date == 0) {
            date = now();
        }
        List<ProductStats> statsList
                = productStatsService.getProductStatsGroupByCategory3(date, limit);
        StringBuilder dataJson = new StringBuilder("{ \"status\": 0, \"data\": [");
        int i = 0;
        for (ProductStats productStats : statsList) {
            if (i++ > 0) {
                dataJson.append(",");
            }
            ;
            dataJson.append("{\"name\":\"")
                    .append(productStats.getCategory3_name()).append("\",");
            dataJson.append("\"value\":")
                    .append(productStats.getOrder_amount()).append("}");
        }
        dataJson.append("]}");
        return dataJson.toString();
    }
  1. 品牌接口方法
 @RequestMapping("/trademark")
    public String getProductStatsByTrademark(
            @RequestParam(value = "date", defaultValue = "0") Integer date,
            @RequestParam(value = "limit", defaultValue = "20") int limit) {
        if (date == 0) {
            date = now();
        }
        List<ProductStats> productStatsByTrademarkList
                = productStatsService.getProductStatsByTrademark(date, limit);
        List<String> tradeMarkList = new ArrayList<>();
        List<BigDecimal> amountList = new ArrayList<>();
        for (ProductStats productStats : productStatsByTrademarkList) {
            tradeMarkList.add(productStats.getTm_name());
            amountList.add(productStats.getOrder_amount());
        }
        String json = "{\"status\":0,\"data\":{" + "\"categories\":" +
                "[\"" + StringUtils.join(tradeMarkList, "\",\"") + "\"],\"series\":[" +
                "{\"data\":[" + StringUtils.join(amountList, ",") + "]}]}}";
        return json;
    }

4.3.6 本地接口测试

  1. 可以生成当前日期数据,具体步骤如下

➢ 启动 ZK、Kafka、ClickHouse、Redis、HDFS、Hbase、Maxwell
➢ 运行 BaseDBApp
➢ 运行 OrderWideApp
➢ 运行 ProductsStatsApp
➢ 运行 rt_dblog 目录下的 jar 包
➢ 查看 ClickHouse 中 products_stats_2021 表数据

  1. 启动 SpringBoot 项目,根据访问地址分别用浏览器测试一下接口
    在这里插入图片描述

4.4 刷新大屏图表数据

在这里插入图片描述

第 5 章 分省市的热力图统计

5.1 Sugar 组件:中国省份色彩

5.1.1 添加组件

在上方地图栏位中选择【中国省份色彩】

在这里插入图片描述

5.1.2 配置组件

  1. 修改获取数据的方式,指定访问路径
    访问路径:${API_HOST}/api/sugar/province

5.1.3 接口访问路径以及返回格式

➢ 访问路径
${API_HOST}/api/sugar/province
➢ 返回值格式

{
 "status": 0,
 "data": {
 "mapData": [
 {
 "name": "北京",
 "value": 9131
 },
 {
 "name": "天津",
 "value": 5740
 }
 ]
 }
}

5.2 数据接口实现

5.2.1 创建地区交易额统计实体类 ProvinceStats

package com.atguigu.gmall2021publishertest.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
 * Desc: 地区交易额统计实体类
 */
@AllArgsConstructor
@Data
@NoArgsConstructor
public class ProvinceStats {
    private String stt;
    private String edt;
    private String province_id;
    private String province_name;
    private BigDecimal order_amount;
    private String ts;
}

5.2.2 Mapper 层:创建 ProvinceStatsMapper 接口

package com.atguigu.gmall2021publishertest.mapper;
import com.atguigu.gmall2021publishertest.bean.ProvinceStats;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * Desc: 地区维度统计 Mapper
 */
public interface ProvinceStatsMapper {
    //按地区查询交易额
    @Select("select province_name,sum(order_amount) order_amount " +
            "from province_stats_2021 where toYYYYMMDD(stt)=#{date} " +
            "group by province_id ,province_name ")
    public List<ProvinceStats> selectProvinceStats(int date);
}

5.2.3 Service 层:创建 ProvinceStatsService 接口

package com.atguigu.gmall2021publishertest.service;

import com.atguigu.gmall2021publishertest.bean.ProvinceStats;

import java.util.List;

/**
 * Desc: 地区维度统计接口
 */
public interface ProvinceStatsService {
    public List<ProvinceStats> getProvinceStats(int date);
}

5.2.4 Service 层:创建 ProvinceStatsServiceImpl 实现类

package com.atguigu.gmall2021publishertest.service.impl;

import com.atguigu.gmall2021publishertest.bean.ProvinceStats;
import com.atguigu.gmall2021publishertest.mapper.ProvinceStatsMapper;
import com.atguigu.gmall2021publishertest.service.ProvinceStatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * Desc: 按地区维度统计 Service 实现
 */
@Service
public class ProvinceStatsServiceImpl implements ProvinceStatsService {
    @Autowired
    ProvinceStatsMapper provinceStatsMapper;
    @Override
    public List<ProvinceStats> getProvinceStats(int date) {
        return provinceStatsMapper.selectProvinceStats(date);
    }
}

5.2.5 Controller 层:在 SugarController 中增加方法

  @Autowired
    ProvinceStatsService provinceStatsService;

    @RequestMapping("/province")
    public String getProvinceStats(@RequestParam(value = "date", defaultValue = "0") Integer date) {
        if (date == 0) {
            date = now();
        }
        StringBuilder jsonBuilder = new StringBuilder("{\"status\":0,\"data\":{\"mapData\":[");
        List<ProvinceStats> provinceStatsList = provinceStatsService.getProvinceStats(date);
        if (provinceStatsList.size() == 0) {
            // jsonBuilder.append( "{\"name\":\"北京\",\"value\":0.00}");
        }
        for (int i = 0; i < provinceStatsList.size(); i++) {
            if (i >= 1) {
                jsonBuilder.append(",");
            }
            ProvinceStats provinceStats = provinceStatsList.get(i);
            jsonBuilder.append("{\"name\":\"" + provinceStats.getProvince_name() +
                    "\",\"value\":" + provinceStats.getOrder_amount() + " }");
        }
        jsonBuilder.append("]}}");
        return jsonBuilder.toString();
    }

第 6 章 流量统计数据

在这里插入图片描述

6.1 Sugar 组件:表格

6.1.1 添加组件

  1. 表格,用于显示新老访客对比
    在上方【表格】栏位中选择【表格】
  2. 折线图,用于显示分时流量
    在上方【图表】栏位中选择【折线图】

6.1.2 新老访客对比的表格组件配置

  1. 修改获取数据的方式,指定访问路径
    访问路径: $API_HOST/api/sugar/visitor

  2. 查看返回值数据格式

{
	"status": 0,
	"data": {
		"combineNum": 1,
		"columns": [{
				"name": "类别",
				"id": "type"
			},
			{
				"name": "新用户",
				"id": "new"
			},
			{
				"name": "老用户",
				"id": "old"
			}
		],
		"rows": [{
				"type": "用户数",
				"new": 123,
				"old": 13
			},
			{
				"type": "总访问页面",
				"new": 123,
				"old": 145
			},
			{
				"type": "跳出率",
				"new": 123,
				"old": 145
			},
			{
				"type": "平均在线时长",
				"new": 123,
				"old": 145

			},
			{
				"type": "平均访问页面数",
				"new": 23,
				"old": 145
			}
		]
	}
}

6.1.3 分时流量显示的折线组件配置

  1. 修改获取数据的方式,指定访问路径
    访问路径: ${API_HOST}/api/sugar/hr
  2. 查看返回值数据格式
{
	"status": 0,
	"data": {
		"categories": [
			"01",
			"02",
			"03",
			"04",
			"05"
		],
		"series": [{
				"name": "uv",
				"data": [
					888065,
					892945,
					678379,
					733572,
					525091
				]
			},

			{
				"name": "pv",
				"data": [
					563998,
					571831,
					622419,
					675294,
					708512
				]
			},
			{
				"name": "新用户",
				"data": [
					563998,
					571831,
					622419,
					675294,
					708512
				]
			}
		]
	}
}

6.2 数据接口实现

6.2.1 创建访问流量统计实体类 VisitorStats

package com.atguigu.gmall2021publishertest.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
 * Desc: 访客流量统计实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class VisitorStats {
    private String stt;
    private String edt;
    private String vc;
    private String ch;
    private String ar;
    private String is_new;
    private Long uv_ct = 0L;
    private Long pv_ct = 0L;
    private Long sv_ct = 0L;
    private Long uj_ct = 0L;
    private Long dur_sum = 0L;
    private Long new_uv = 0L;
    private Long ts;
    private int hr;
    //计算跳出率 = 跳出次数*100/访问次数
    public BigDecimal getUjRate() {
        if (uv_ct != 0L) {
            return BigDecimal.valueOf(uj_ct)
                    .multiply(BigDecimal.valueOf(100))
                    .divide(BigDecimal.valueOf(sv_ct), 2, RoundingMode.HALF_UP);
        } else {
            return BigDecimal.ZERO;
        }
    }
    //计算每次访问停留时间(秒) = 当日总停留时间(毫秒)/当日访问次数/1000
    public BigDecimal getDurPerSv() {
        if (uv_ct != 0L) {
            return BigDecimal.valueOf(dur_sum)
                    .divide(BigDecimal.valueOf(sv_ct), 0, RoundingMode.HALF_UP)
                    .divide(BigDecimal.valueOf(1000), 1, RoundingMode.HALF_UP);
        } else {
            return BigDecimal.ZERO;
        }
    }
    //计算每次访问停留页面数 = 当日总访问页面数/当日访问次数
    public BigDecimal getPvPerSv() {
        if (uv_ct != 0L) {
            return BigDecimal.valueOf(pv_ct)
                    .divide(BigDecimal.valueOf(sv_ct), 2, RoundingMode.HALF_UP);
        } else {
            return BigDecimal.ZERO;
        }
    }
}

6.2.2 Mapper 层:创建 VisitorStatsMapper

package com.atguigu.gmall2021publishertest.mapper;

import com.atguigu.gmall2021publishertest.bean.VisitorStats;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * Desc: 访客流量统计 Mapper
 */
public interface VisitorStatsMapper {
    //新老访客流量统计
    @Select("select is_new,sum(uv_ct) uv_ct,sum(pv_ct) pv_ct," +
            "sum(sv_ct) sv_ct, sum(uj_ct) uj_ct,sum(dur_sum) dur_sum " +
            "from visitor_stats_2021 where toYYYYMMDD(stt)=#{date} group by is_new")
    public List<VisitorStats> selectVisitorStatsByNewFlag(int date);
    //分时流量统计
    @Select("select sum(if(is_new='1', visitor_stats_2021.uv_ct,0)) new_uv,toHour(stt) hr," +
            "sum(visitor_stats_2021.uv_ct) uv_ct, sum(pv_ct) pv_ct, sum(uj_ct) uj_ct " +
            "from visitor_stats_2021 where toYYYYMMDD(stt)=#{date} group by toHour(stt)")
    public List<VisitorStats> selectVisitorStatsByHour(int date);
    @Select("select count(pv_ct) pv_ct from visitor_stats_2021 " +
            "where toYYYYMMDD(stt)=#{date} ")
    public Long selectPv(int date);
    @Select("select count(uv_ct) uv_ct from visitor_stats_2021 " +
            "where toYYYYMMDD(stt)=#{date} ")
    public Long selectUv(int date);
}

6.2.3 Service 层:创建 VisitorStatsService 接口

package com.atguigu.gmall2021publishertest.service;

import com.atguigu.gmall2021publishertest.bean.VisitorStats;

import java.util.List;

public interface VisitorStatsService {

    public List<VisitorStats> getVisitorStatsByNewFlag(int date);
    public List<VisitorStats> getVisitorStatsByHour(int date);
    public Long getPv(int date);
    public Long getUv(int date);
}

6.2.4 Service 层:创建 VisitorStatsServiceImpl 实现类

package com.atguigu.gmall2021publishertest.service.impl;
import com.atguigu.gmall2021publishertest.bean.VisitorStats;
import com.atguigu.gmall2021publishertest.mapper.VisitorStatsMapper;
import com.atguigu.gmall2021publishertest.service.VisitorStatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * Desc: 访问流量统计 Service 实现类
 */
@Service
public class VisitorStatsServiceImpl implements VisitorStatsService {
    @Autowired
    VisitorStatsMapper visitorStatsMapper;
    @Override
    public List<VisitorStats> getVisitorStatsByNewFlag(int date) {
        return visitorStatsMapper.selectVisitorStatsByNewFlag(date);
    }
    @Override
    public List<VisitorStats> getVisitorStatsByHour(int date) {
        return visitorStatsMapper.selectVisitorStatsByHour(date);

    }
    @Override
    public Long getPv(int date) {
        return visitorStatsMapper.selectPv(date);
    }
    @Override
    public Long getUv(int date) {
        return visitorStatsMapper.selectUv(date);
    }
}

6.2.5 Controller 层:在 SugarController 中增加方法

  1. 新老访客流量对比
    @Autowired
    VisitorStatsService visitorStatsService;
    @RequestMapping("/visitor")
    public String getVisitorStatsByNewFlag(@RequestParam(value = "date", defaultValue = "0")
                                                   Integer date) {
        if (date == 0) date = now();
        List<VisitorStats> visitorStatsByNewFlag =
                visitorStatsService.getVisitorStatsByNewFlag(date);
        VisitorStats newVisitorStats = new VisitorStats();
        VisitorStats oldVisitorStats = new VisitorStats();
        //循环把数据赋给新访客统计对象和老访客统计对象
        for (VisitorStats visitorStats : visitorStatsByNewFlag) {
            if (visitorStats.getIs_new().equals("1")) {
                newVisitorStats = visitorStats;
            } else {
                oldVisitorStats = visitorStats;
            }
        }
        //把数据拼接入字符串
        String json = "{\"status\":0,\"data\":{\"combineNum\":1,\"columns\":" +
                "[{\"name\":\"类别\",\"id\":\"type\"}," +
                "{\"name\":\"新用户\",\"id\":\"new\"}," +
                "{\"name\":\"老用户\",\"id\":\"old\"}]," +
                "\"rows\":" +
                "[{\"type\":\"用户数(人)\"," +
                "\"new\": " + newVisitorStats.getUv_ct() + "," +
                "\"old\":" + oldVisitorStats.getUv_ct() + "}," +
                "{\"type\":\"总访问页面(次)\"," +
                "\"new\":" + newVisitorStats.getPv_ct() + "," +
                "\"old\":" + oldVisitorStats.getPv_ct() + "}," +
                "{\"type\":\"跳出率(%)\"," +
                "\"new\":" + newVisitorStats.getUjRate() + "," +
                "\"old\":" + oldVisitorStats.getUjRate() + "}," +
                "{\"type\":\"平均在线时长(秒)\"," +
                "\"new\":" + newVisitorStats.getDurPerSv() + "," +
                "\"old\":" + oldVisitorStats.getDurPerSv() + "}," +
                "{\"type\":\"平均访问页面数(人次)\"," +
                "\"new\":" + newVisitorStats.getPvPerSv() + "," +
                "\"old\":" + oldVisitorStats.getPvPerSv()
                + "}]}}";
        return json;
    }
  1. 分时流量统计
 @RequestMapping("/hr")
    public String getMidStatsGroupbyHourNewFlag(@RequestParam(value = "date",defaultValue =
            "0") Integer date ) {
        if(date==0) date=now();
        List<VisitorStats> visitorStatsHrList
                = visitorStatsService.getVisitorStatsByHour(date);
        //构建 24 位数组
        VisitorStats[] visitorStatsArr=new VisitorStats[24];
        //把对应小时的位置赋值
        for (VisitorStats visitorStats : visitorStatsHrList) {
            visitorStatsArr[visitorStats.getHr()] =visitorStats ;
        }
        List<String> hrList=new ArrayList<>();
        List<Long> uvList=new ArrayList<>();
        List<Long> pvList=new ArrayList<>();
        List<Long> newMidList=new ArrayList<>();
        //循环出固定的 0-23 个小时 从结果 map 中查询对应的值
        for (int hr = 0; hr <=23 ; hr++) {
            VisitorStats visitorStats = visitorStatsArr[hr];
            if (visitorStats!=null){
                uvList.add(visitorStats.getUv_ct()) ;
                pvList.add( visitorStats.getPv_ct());
                newMidList.add( visitorStats.getNew_uv());
            }else{ //该小时没有流量补零
                uvList.add(0L) ;
                pvList.add( 0L);
                newMidList.add( 0L);
            }
            //小时数不足两位补零
            hrList.add(String.format("%02d", hr));
        }
        //拼接字符串
        String json = "{\"status\":0,\"data\":{" + "\"categories\":" +
                "[\""+StringUtils.join(hrList,"\",\"")+ "\"],\"series\":[" +
                "{\"name\":\"uv\",\"data\":["+ StringUtils.join(uvList,",") +"]}," +
                "{\"name\":\"pv\",\"data\":["+ StringUtils.join(pvList,",") +"]}," +
                "{\"name\":\"新用户\",\"data\":["+ StringUtils.join(newMidList,",") +"]}]}}";
        return json;

6.2.6 本地接口测试

  1. 可以生成当前日期数据,具体步骤如下
    ➢ 启动 ZK、Kafka、Logger.sh、ClickHouse
    ➢ 运行 BaseLogApp
    ➢ 运行 UniqueVisitApp
    ➢ 运行 UserJumpDetailApp
    ➢ 运行 VisitorStatsApp
    ➢ 运行 rt_applog 目录下的 jar 包
    ➢ 查看 ClickHouse 中 visitor_stats_2021 表数据
  2. 启动 SpringBoot 项目,根据访问地址分别用浏览器测试一下接口

6.3 刷新大屏组件数据

第 7 章 热词字符云

7.1 Sugar 组件:字符云

7.1.1 添加组件

在上方【文字】栏位中选择【字符云】

7.1.2 配置组件

访问路径:${API_HOST}/api/sugar/keyword

7.1.3 接口访问路径以及返回格式

➢ 访问路径
${API_HOST}/api/sugar/keyword
➢ 返回值格式

{
	"status": 0,
	"data": [{
			"name": "data",
			"value": 60679,
		},
		{
			"name": "dataZoom",
			"value": 24347,
		}
	]
}

7.2 数据接口实现

7.2.1 创建关键词统计实体类

package com.atguigu.gmall2021publishertest.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * Desc: 关键词统计实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class KeywordStats {
    private String stt;
    private String edt;
    private String keyword;
    private Long ct;
    private String ts;
}

7.2.2 Mapper 层:创建 KeywordStatsMapper

  1. SQL 语句
    根据关键词的出现类型分配不同的热度分数
    ➢ 搜索关键词=10 分
    ➢ 下单商品=5 分
    ➢ 加入购物车=2 分
    ➢ 点击商品=1 分
    ➢ 其他=0 分
    其中 ClickHouse 函数 multiIf 类似于 case when
select keyword, 
sum(keyword_stats_2021.ct * 
multiIf(
source='SEARCH',10,
source='ORDER',5,
source='CART',2,
source='CLICK',1,0
)) ct 
from 
keyword_stats 
where 
toYYYYMMDD(stt)=#{date}
group by 
keyword 
order by 
sum(keyword_stats.ct) 
limit #{limit};
  1. 接口类
package com.atguigu.gmall2021publishertest.mapper;

import com.atguigu.gmall2021publishertest.bean.KeywordStats;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * Desc: 关键词统计 Mapper
 */
public interface KeywordStatsMapper {
    @Select("select keyword," +
            "sum(keyword_stats_2021.ct * " +
            "multiIf(source='SEARCH',10,source='ORDER',3,source='CART',2,source='CLICK',1,0)) ct"
            +
            " from keyword_stats_2021 where toYYYYMMDD(stt)=#{date} group by keyword " +
            "order by sum(keyword_stats_2021.ct) desc limit #{limit} ")
    public List<KeywordStats> selectKeywordStats(@Param("date") int date, @Param("limit") int
            limit);
}

7.2.3 Service 层:创建 KeywordStatsService 接口

package com.atguigu.gmall2021publishertest.service;
import com.atguigu.gmall2021publishertest.bean.KeywordStats;

import java.util.List;
/**
 * Desc: 关键词统计接口
 */
public interface KeywordStatsService {
    public List<KeywordStats> getKeywordStats(int date, int limit);
}

7.2.4 Service 层:创建 KeywordStatsServiceImpl

package com.atguigu.gmall2021publishertest.service.impl;
import com.atguigu.gmall2021publishertest.bean.KeywordStats;
import com.atguigu.gmall2021publishertest.mapper.KeywordStatsMapper;
import com.atguigu.gmall2021publishertest.service.KeywordStatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * Desc:关键词统计接口实现类
 */
@Service
public class KeywordStatsServiceImpl implements KeywordStatsService {
    @Autowired
    KeywordStatsMapper keywordStatsMapper;
    @Override
    public List<KeywordStats> getKeywordStats(int date, int limit) {
        return keywordStatsMapper.selectKeywordStats(date,limit);
    }
}

7.2.5 Controller 层:在 SugarController 中增加方法

    @Autowired
    private KeywordStatsService keywordStatsService;

    @RequestMapping("/keyword")
    public String getKeywordStats(@RequestParam(value = "date",defaultValue = "0") Integer date,
                                  @RequestParam(value = "limit",defaultValue = "20") int
                                          limit){
        if(date==0){
            date=now();
        }
        //查询数据
        List<KeywordStats> keywordStatsList
                = keywordStatsService.getKeywordStats(date, limit);
        StringBuilder jsonSb=new StringBuilder( "{\"status\":0,\"msg\":\"\",\"data\":[" );
        //循环拼接字符串
        for (int i = 0; i < keywordStatsList.size(); i++) {
            KeywordStats keywordStats = keywordStatsList.get(i);
            if(i>=1){
                jsonSb.append(",");
            }
            jsonSb.append( "{\"name\":\"" + keywordStats.getKeyword() + "\"," +
                    "\"value\":"+keywordStats.getCt()+"}");
        }
        jsonSb.append( "]}");
        return jsonSb.toString();
    }

7.2.6 本地接口测试

第 9 章 总结

数据接口部分开发的重点:
➢ 学会通过 springboot 搭建一个 web 服务。
➢ 学会在 Web 服务使用注解方式,通过 SQL 语句查询 ClickHouse。
➢ 学会通过 Sugar 实现数据大屏可视化配置,了解其中的地图、柱形图、饼图、折线图、
表格、轮播表、字符云等组件的使用预配置。
➢ 学会使用内网穿透工具,方便调试本地接口与互联网服务对接

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值