Flink实时数仓-04-ADS层搭建以及基于Sugar和SpringBoot进行数据可视化展示

数据可视化

设计思路

DWS 层把轻度聚合的结果保存到 ClickHouse 中,主要的目的就是提供即时的数据查询、统计、分析服务。这些统计服务一般会以两种形式呈现,一种是面向专业数据分析人员准备的 BI 工具,一种是面向非专业人员的更加直观的数据大屏。

本项目将面向 sugar 数据大屏开发数据接口服务。

需求梳理

最终显示效果

M

M

M

接口执行过程

M

DWS 层计算结果存储在 ClickHouse,本项目将开发数据接口查询ClickHouse中的数据,提供给 Sugar 进行大屏展示。这里主要有两项工作:

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

sugar简介

产品介绍

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

使用入口

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

M

创建数据大屏

1)点击【立即使用】后 登录百度账号

2)然后首先创建组织

3)创建中选择产品【大屏尝鲜版】,首次使用有一个月的试用期

M

4)新建好组织后选择【进入组织】

5)然后进入默认的【第一个空间】

M

6)在空间中选择【待创建大屏】后的【新建】

M

7)选择大屏的模板

M

8)可以选空模板,也可以根据现有的模板进行修改

我们这里选择空白模板,并指定大屏的名称

M

9)进入大屏的编辑窗口

M

SpringBoot环境

依赖:

添加依赖
<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.3</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>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </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>

目录结构:

M

在 application.properties 内添加如下内容:

server.port=8070
#配置ClickHouse驱动以及URL
spring.datasource.driver-class-name=ru.yandex.clickhouse.ClickHouseDriver
spring.datasource.url=jdbc:clickhouse://hadoop102:8123/gmall_rebuild

SpringBoot 内嵌了 Tomact,默认端口为 8080。集群 Zookeeper 版本为 3.5.7,该版本提供的 AdminServer 服务端口号也是 8080,为了避免端口冲突,此处将 SpringBoot 内嵌的 Tomcat 容器端口号修改为 8070。

接口对接的数据库为 ClickHouse,需要指定驱动及 url。

内网穿透

内网穿透简介

内网即局域网。假设局域网中有一台电脑部署了web服务,现在希望所有人都能访问它。很显然,这台电脑只有一个局域网ip,没有公网ip。同一局域网内的设备可以通过局域网ip访问此电脑,而局域网之外的设备无法访问。

内网穿透可以将 ip + 端口唯一标识的本机服务映射为公网域名,局域网之外的设备可以通过该域名访问本机服务。

实现步骤

本项目将使用钉钉提供的内网穿透工具。

Github 地址为 https://github.com/open-dingtalk/dingtalk-pierced-client

1)原理

M

2)步骤

(1)确保本机已安装 git。

(2)在任意目录右键空白处,单击 Git Bash Here。

(3)在弹出的窗口中执行以下命令

git clone https://github.com/open-dingtalk/dingtalk-pierced-client.git

(4)查看目录,多了 dingtalk-pierced-client 文件。

M

(5)进入 windows_64 目录

M

(6)在地址栏输入 cmd,回车

(7)在弹出的 cmd 窗口中执行以下命令:

ding --config ding.cfg --subdomain dinghhh 8070                                           
  • –config 指定内网穿透的配置文件,固定为钉钉提供的./ding.cfg,无需修改。
  • –subdomain 指定需要使用的域名前缀,该前缀将会匹配到“vaiwan.cn”前面,此处 subdomain 是 dinghhh,启动工具后会将 dinghhh.vaiwan.cn 映射到本地。
  • 8070 为需要代理的本地服务 http-server 端口。

执行完毕后,局域网之外的设备可以通过 http://dinghhh.vaiwan.cn 访问本地 8070 端口的 web 服务。

(8)启动客户端后http://dinghhh.vaiwan.cn/xxx 的请求会映射到 http://localhost:8070/xxx。

ADS层

流量主题

各渠道流量统计

1)需求统计

统计周期 统计粒度 指标 说明
当日 渠道 独立访客数 统计访问人数
当日 渠道 会话总数 统计会话总数
当日 渠道 会话平均浏览页面数 统计每个会话平均浏览页面数
当日 渠道 会话平均停留时长 统计每个会话平均停留时长
当日 渠道 跳出率 只有一个页面的会话的比例

2)需求分析

柱状图可以直观展示不同渠道的度量值,本节将为上述五个指标各生成一张柱状图。

3)数据结构

图表所需数据结构的获取详见Sugar配置。

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "categories": [
      "Appstore",
      "xiaomi",
      ...
    ],
    "series": [
      {
    
    
        "name": "独立访客数",
        "data": [
          "99",
          "74",
          ...
        ]
      }
    ]
  }
}

(1)categories

数组中存储的元素为柱状图横轴所有取值。

(2)data

数组中存储的元素为与横轴对应的纵轴取值。

4)Mapper层

(1)实体类

TrafficUvCt:

@Data
@AllArgsConstructor
public class TrafficUvCt {
    
    
    // 渠道
    String ch;
    // 独立访客数
    Integer uvCt;
}

TrafficSvCt:

@Data
@AllArgsConstructor
public class TrafficSvCt {
    
    
    // 渠道
    String ch;
    // 会话数
    Integer svCt;
}

TrafficPvPerSession:

@Data
@AllArgsConstructor
public class TrafficPvPerSession {
    
    
    // 渠道
    String ch;
    // 各会话页面浏览数
    Double pvPerSession;
}

TrafficDurPerSession:

@Data
@AllArgsConstructor
public class TrafficDurPerSession {
    
    
    // 渠道
    String ch;
    // 各会话页面访问时长
    Double durPerSession;
}

TrafficUjRate:

@Data
@AllArgsConstructor
public class TrafficUjRate {
    
    
    // 渠道
    String ch;
    // 跳出率
    Double ujRate;
}

(2)Mapper 接口

public interface TrafficChannelStatsMapper {
    
    
    // 1. 获取各渠道独立访客数
    @Select("select ch,\n" +
            "       sum(uv_ct)           uv_ct\n" +
            "from dws_traffic_vc_ch_ar_is_new_page_view_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt), ch\n" +
            "order by uv_ct desc;")
    List<TrafficUvCt> selectUvCt(@Param("date")Integer date);

    // 2. 获取各渠道会话数
    @Select("select ch,\n" +
            "       sum(sv_ct)           sv_ct\n" +
            "from dws_traffic_vc_ch_ar_is_new_page_view_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt), ch\n" +
            "order by sv_ct desc;")
    List<TrafficSvCt> selectSvCt(@Param("date")Integer date);

    // 3. 获取各渠道会话平均页面浏览数
    @Select("select ch,\n" +
            "       sum(pv_ct) / sum(sv_ct)   pv_per_session\n" +
            "from dws_traffic_vc_ch_ar_is_new_page_view_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt), ch\n" +
            "order by pv_per_session desc;")
    List<TrafficPvPerSession> selectPvPerSession(@Param("date")Integer date);

    // 4. 获取各渠道会话平均页面访问时长
    @Select("select ch,\n" +
            "       sum(dur_sum) / sum(sv_ct) dur_per_session\n" +
            "from dws_traffic_vc_ch_ar_is_new_page_view_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt), ch\n" +
            "order by dur_per_session desc;")
    List<TrafficDurPerSession> selectDurPerSession(@Param("date")Integer date);

    // 5. 获取各渠道跳出率
    @Select("select ch,\n" +
            "       sum(uj_ct) / sum(sv_ct)   uj_rate\n" +
            "from dws_traffic_vc_ch_ar_is_new_page_view_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt), ch\n" +
            "order by uj_rate desc;")
    List<TrafficUjRate> selectUjRate(@Param("date")Integer date);
}

(3)注解

  • @Select 注解

    位于方法定义语句上方,修饰方法。

    添加该注解后, Mybatis 会自动实现 JDBC 的环境准备,实现对应的 Mapper 层接口,并在 Tomcat 容器启动时将该实现类加载到容器中,包含 @Service 注解的功能。可以在服务层的 Impl 类中通过 @Autowired 自动装载该实现类。该实现类是单例的。

    注意:要将该类加载到容器中,需要在启动类上方添加 @MapperScan 注解,指明 mapper 包路径,如下:

    @SpringBootApplication
    @MapperScan(basePackages = "com.atguigu.gmall.publisher.mapper")
    public class GmallPublisher2022Application {
          
          
    
        public static void main(String[] args) {
          
          
            SpringApplication.run(GmallPublisher2022Application.class, args);
        }
    
    }
    
  • @Param 注解

    位于方法参数数据类型之前,修饰参数。

    该注解可以将方法参数接收的值赋给注解参数中的变量,该变量可以在方法上方 @Select 注解的 SQL 语句中使用,调用方式为 #{变量名}。

    如 @Param(“paramDate”)Integer date,将参数 date 接收到的数据赋值给 paramDate,可以在 SQL 中通过 #{paramDate} 获取 paramDate 参数的值。

5)Service层

(1)service 接口

public interface TrafficChannelStatsService {
    
    
    // 1. 获取各渠道独立访客数
    List<TrafficUvCt> getUvCt(Integer date);

    // 2. 获取各渠道会话数
    List<TrafficSvCt> getSvCt(Integer date);

    // 3. 获取各渠道会话平均页面浏览数
    List<TrafficPvPerSession> getPvPerSession(Integer date);

    // 4. 获取各渠道会话平均页面访问时长
    List<TrafficDurPerSession> getDurPerSession(Integer date);

    // 5. 获取各渠道跳出率
    List<TrafficUjRate> getUjRate(Integer date);
}

(2)service 实现类

@Service
public class TrafficChannelStatsServiceImpl implements TrafficChannelStatsService {
    
    

    // 自动装载 Mapper 接口实现类
    @Autowired
    TrafficChannelStatsMapper trafficChannelStatsMapper;

    // 1. 获取各渠道独立访客数
    @Override
    public List<TrafficUvCt> getUvCt(Integer date) {
    
    
        return trafficChannelStatsMapper.selectUvCt(date);
    }

    // 2. 获取各渠道会话数
    @Override
    public List<TrafficSvCt> getSvCt(Integer date) {
    
    
        return trafficChannelStatsMapper.selectSvCt(date);
    }

    // 3. 获取各渠道会话平均页面浏览数
    @Override
    public List<TrafficPvPerSession> getPvPerSession(Integer date) {
    
    
        return trafficChannelStatsMapper.selectPvPerSession(date);
    }

    // 4. 获取各渠道会话平均页面访问时长
    @Override
    public List<TrafficDurPerSession> getDurPerSession(Integer date) {
    
    
        return trafficChannelStatsMapper.selectDurPerSession(date);
    }

    // 5. 获取各渠道跳出率
    @Override
    public List<TrafficUjRate> getUjRate(Integer date) {
    
    
        return trafficChannelStatsMapper.selectUjRate(date);
    }
}

(3)注解

  • @Service 注解

    位于实现类定义语句上方,修饰类。

    把该实现类注册成一个组件,这个组件常驻内存,而且默认为单态(即单例,整个服务器进程,该类对象只有一个)。启动时会扫描所有被 @Service 注解修饰的实现类,将它们加载到内存。

  • @Autowired 注解

    位于成员变量(属性)定义语句上方,修饰类的属性。

    ① 控制器组件会扫描接口名上方添加了 @Autowired(自动装配)注解的接口,然后去内存的常驻组件中寻找适配组件(实现类),令此处的引用指向适配组件。

    ② 如果一个接口有多个实现类注册成为了组件,可以通过 @ Qualifier(“组件名称”) 注解指定不同的适配组件。此处的组件名称在 @Service 注解的参数中指定,如 @Service(“组件名称”),只有一个实现类时可以不起名(不传参),直接使用 @Service 注解。

6)controller层

(1)代码

@RestController
@RequestMapping("/gmall/realtime/traffic")
public class TrafficController {
    
    

    // 自动装载渠道流量统计服务实现类
    @Autowired
    private TrafficChannelStatsService trafficChannelStatsService;

    // 1. 独立访客请求拦截方法
    @RequestMapping("/uvCt")
    public String getUvCt(
            @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TrafficUvCt> trafficUvCtList = trafficChannelStatsService.getUvCt(date);
        if (trafficUvCtList == null) {
    
    
            return "";
        }
        StringBuilder categories = new StringBuilder("[");
        StringBuilder uvCtValues = new StringBuilder("[");

        for (int i = 0; i < trafficUvCtList.size(); i++) {
    
    
            TrafficUvCt trafficUvCt = trafficUvCtList.get(i);
            String ch = trafficUvCt.getCh();
            Integer uvCt = trafficUvCt.getUvCt();

            categories.append("\"").append(ch).append("\"");
            uvCtValues.append("\"").append(uvCt).append("\"");

            if (i < trafficUvCtList.size() - 1) {
    
    
                categories.append(",");
                uvCtValues.append(",");
            } else {
    
    
                categories.append("]");
                uvCtValues.append("]");
            }
        }

        return "{\n" +
                "  \"status\": 0,\n" +
                "  \"msg\": \"\",\n" +
                "  \"data\": {\n" +
                "    \"categories\":" + categories + ",\n" +
                "    \"series\": [\n" +
                "      {\n" +
                "        \"name\": \"独立访客数\",\n" +
                "        \"data\": " + uvCtValues + "\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";
    }

    // 2. 会话数请求拦截方法
    @RequestMapping("/svCt")
    public String getPvCt(
            @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TrafficSvCt> trafficSvCtList = trafficChannelStatsService.getSvCt(date);
        if (trafficSvCtList == null) {
    
    
            return "";
        }
        StringBuilder categories = new StringBuilder("[");
        StringBuilder svCtValues = new StringBuilder("[");

        for (int i = 0; i < trafficSvCtList.size(); i++) {
    
    
            TrafficSvCt trafficSvCt = trafficSvCtList.get(i);
            String ch = trafficSvCt.getCh();
            Integer svCt = trafficSvCt.getSvCt();

            categories.append("\"").append(ch).append("\"");
            svCtValues.append("\"").append(svCt).append("\"");

            if (i < trafficSvCtList.size() - 1) {
    
    
                categories.append(",");
                svCtValues.append(",");
            } else {
    
    
                categories.append("]");
                svCtValues.append("]");
            }
        }

        return "{\n" +
                "  \"status\": 0,\n" +
                "  \"msg\": \"\",\n" +
                "  \"data\": {\n" +
                "    \"categories\":" + categories + ",\n" +
                "    \"series\": [\n" +
                "      {\n" +
                "        \"name\": \"会话数\",\n" +
                "        \"data\": " + svCtValues + "\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";
    }

    // 3. 各会话浏览页面数请求拦截方法
    @RequestMapping("/pvPerSession")
    public String getPvPerSession(
            @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TrafficPvPerSession> trafficPvPerSessionList = trafficChannelStatsService.getPvPerSession(date);
        if (trafficPvPerSessionList == null) {
    
    
            return "";
        }
        StringBuilder categories = new StringBuilder("[");
        StringBuilder pvPerSessionValues = new StringBuilder("[");

        for (int i = 0; i < trafficPvPerSessionList.size(); i++) {
    
    
            TrafficPvPerSession trafficPvPerSession = trafficPvPerSessionList.get(i);
            String ch = trafficPvPerSession.getCh();
            Double pvPerSession = trafficPvPerSession.getPvPerSession();

            categories.append("\"").append(ch).append("\"");
            pvPerSessionValues.append("\"").append(pvPerSession).append("\"");

            if (i < trafficPvPerSessionList.size() - 1) {
    
    
                categories.append(",");
                pvPerSessionValues.append(",");
            } else {
    
    
                categories.append("]");
                pvPerSessionValues.append("]");
            }
        }

        return "{\n" +
                "  \"status\": 0,\n" +
                "  \"msg\": \"\",\n" +
                "  \"data\": {\n" +
                "    \"categories\":" + categories + ",\n" +
                "    \"series\": [\n" +
                "      {\n" +
                "        \"name\": \"会话平均页面浏览数\",\n" +
                "        \"data\": " + pvPerSessionValues + "\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";
    }

    // 4. 各会话累计访问时长请求拦截方法
    @RequestMapping("/durPerSession")
    public String getDurPerSession(
            @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TrafficDurPerSession> trafficDurPerSessionList = trafficChannelStatsService.getDurPerSession(date);
        if (trafficDurPerSessionList == null) {
    
    
            return "";
        }
        StringBuilder categories = new StringBuilder("[");
        StringBuilder durPerSessionValues = new StringBuilder("[");

        for (int i = 0; i < trafficDurPerSessionList.size(); i++) {
    
    
            TrafficDurPerSession trafficDurPerSession = trafficDurPerSessionList.get(i);
            String ch = trafficDurPerSession.getCh();
            Double durPerSession = trafficDurPerSession.getDurPerSession();

            categories.append("\"").append(ch).append("\"");
            durPerSessionValues.append("\"").append(durPerSession).append("\"");

            if (i < trafficDurPerSessionList.size() - 1) {
    
    
                categories.append(",");
                durPerSessionValues.append(",");
            } else {
    
    
                categories.append("]");
                durPerSessionValues.append("]");
            }
        }

        return "{\n" +
                "  \"status\": 0,\n" +
                "  \"msg\": \"\",\n" +
                "  \"data\": {\n" +
                "    \"categories\":" + categories + ",\n" +
                "    \"series\": [\n" +
                "      {\n" +
                "        \"name\": \"会话平均页面访问时长\",\n" +
                "        \"data\": " + durPerSessionValues + "\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";
    }

    // 5. 跳出率请求拦截方法
    @RequestMapping("/ujRate")
    public String getUjRate(
            @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TrafficUjRate> trafficUjRateList = trafficChannelStatsService.getUjRate(date);
        if (trafficUjRateList == null) {
    
    
            return "";
        }
        StringBuilder categories = new StringBuilder("[");
        StringBuilder ujRateValues = new StringBuilder("[");

        for (int i = 0; i < trafficUjRateList.size(); i++) {
    
    
            TrafficUjRate trafficUjRate = trafficUjRateList.get(i);
            String ch = trafficUjRate.getCh();
            Double ujRate = trafficUjRate.getUjRate();

            categories.append("\"").append(ch).append("\"");
            ujRateValues.append("\"").append(ujRate).append("\"");

            if (i < trafficUjRateList.size() - 1) {
    
    
                categories.append(",");
                ujRateValues.append(",");
            } else {
    
    
                categories.append("]");
                ujRateValues.append("]");
            }
        }

        return "{\n" +
                "  \"status\": 0,\n" +
                "  \"msg\": \"\",\n" +
                "  \"data\": {\n" +
                "    \"categories\":" + categories + ",\n" +
                "    \"series\": [\n" +
                "      {\n" +
                "        \"name\": \"跳出率\",\n" +
                "        \"data\": " + ujRateValues + "\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";
}
}

(2)注解

① @RestController

位于控制类定义语句上方,修饰控制类。

标注方法为控制类,可以将外部请求引入该类的方法中。当方法返回值类型为 String 时,不会发生页面跳转,相当于 @Controller + @ResponseBody。

② @RequestMapping

可以在控制类定义语句上方添加,也可以在方法定义语句上方添加。

示例:@RequestMapping(value = “/traffic”, method = RequestMethod.GET)。

该注解用于建立请求URL和请求处理方法之间的对应关系。value 用于指定请求 URL。请求处理方法最终拦截的 URL为类注解指定的路径与方法注解指定的路径拼接所得的 URL。如当前 SpringBoot 服务占用了本机的 8080 端口,类上注解为 @RequestMapping(“/gmall “),方法上方注解为 @RequestMapping(”/uvCt”),则最终拦截的 url 为 localhost:8080/gmall/uvCt。控制类上方可以没有该注解。

如果拦截的是 GET 请求,method 参数赋值语句可以省略。此外,SpringBoot 的注解只有一个参数,且参数名称为 value时,可以省略参数名称和等号。

所以,@RequestMapping(value = “/traffic”, method = RequestMethod.GET) 可简写为 @RequestMapping(“/traffic”)。

该注解相当于 @GetMapping(“/traffic”)

③ @RequestParma(“name”)

位于方法参数数据类型之前。接收请求路径中键值对(名值对)的参数(通常用于查询条件、辅助参数) 并传递给所修饰的参数。

如 URL 为 localhost:8080/test?paramDt=20220221,注解为 @RequestParam(“paramDt”) Integer dt,则 20220221 会被解析为 Integer 类型数据赋值给 dt 参数。

7)Sugar配置

(1)页面宏定义变量

单击大屏空白处,在右侧“控制面板”中点击“页面宏定义变量”,定义 GMALL_HOST 和 GMALL_DATE。

  • GMALL_HOST:本地 8070 端口服务映射的公网域名。
  • GMALL_DATE:当日日期。本项目的“当日”为模拟数据的业务日期。

M

(2)在“图表”中选择“柱状图”

M

(3)在弹出的控制面板中选择 API 拉取

  • API 拉取:通过给定的数据接口获取数据。我们选择这种方式。
  • 静态 JSON:通过给定的静态 JSON 字符串获取数据。

API 拉取返回的数据格式与静态 JSON 相同,因此可以通过静态 JSON 的示例数据查看数据格式。

M

M

(4)输入数据接口的 URL

${GMALL_HOST}/gmall/realtime/traffic/uvCt?date=${GMALL_DATE}

(5)刷新图表,查看效果

M

另外四张柱状图的配置同理,不再赘述。

所有图表的 URL 如下:

# 独立访客
${GMALL_HOST}/gmall/realtime/traffic/uvCt?date=${GMALL_DATE}

# 会话数
${GMALL_HOST}/gmall/realtime/traffic/svCt?date=${GMALL_DATE}

# 会话平均页面浏览数
${GMALL_HOST}/gmall/realtime/traffic/pvPerSession?date=${GMALL_DATE}

# 会话平均访问时长
${GMALL_HOST}/gmall/realtime/traffic/durPerSession?date=${GMALL_DATE}

# 跳出率
${GMALL_HOST}/gmall/realtime/traffic/ujRate?date=${GMALL_DATE}

流量分时统计

1)需求说明

统计周期 指标 说明
1 小时 独立访客数 统计当日各小时独立访客数
1 小时 页面浏览数 统计当日各小时页面浏览数
1 小时 新访客数 统计当日各小时新访客数

2)需求分析

本节统计分时指标,选用折线图进行展示。三个指标将体现在同一张折线图中。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "categories": [
      "00",
      "01",
      ...
    ],
    "series": [
      {
    
    
        "name": "独立访客数",
        "data": [
          0,
          300,
          ...
        ]
      },
      {
    
    
        "name": "页面浏览数",
        "data": [
          0,
          988,
          ...
        ]
      },
      {
    
    
        "name": "新访客数",
        "data": [
          0,
          155,
          ...
        ]
      }
    ]
  }
}

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class TrafficVisitorStatsPerHour {
    
    
    // 小时
    Integer hr;
    // 独立访客数
    Long uvCt;
    // 页面浏览数
    Long pvCt;
    // 新访客数
    Long newUvCt;
}

(2)Mapper 接口

public interface TrafficVisitorStatsMapper {
    
    
    
// 分时流量数据查询
    @Select("select\n" +
            "toHour(stt) hr,\n" +
            "sum(uv_ct)  uv_ct,\n" +
            "sum(pv_ct)  pv_ct,\n" +
            "sum(if(is_new = '1', dws_traffic_vc_ch_ar_is_new_page_view_window.uv_ct, 0)) new_uv_ct\n" +
            "from dws_traffic_vc_ch_ar_is_new_page_view_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by hr")
    List<TrafficVisitorStatsPerHour> selectVisitorStatsPerHr(Integer date);
}

5)Service层

(1)Service 接口

public interface TrafficVisitorStatsService {
    
    
    // 获取分时流量数据
    List<TrafficVisitorStatsPerHour> getVisitorPerHrStats(Integer date);
}

(2)实现类

@Service
public class TrafficVisitorStatsServiceImpl implements TrafficVisitorStatsService {
    
    

    @Autowired
    private TrafficVisitorStatsMapper trafficVisitorStatsMapper;

    // 获取分时流量统计数据
    @Override
    public List<TrafficVisitorStatsPerHour> getVisitorPerHrStats(Integer date) {
    
    
        return trafficVisitorStatsMapper.selectVisitorStatsPerHr(date);
    }
}

6)Controller层

在 TrafficController 类中补充成员变量和请求拦截方法:

// 自动装载访客状态统计服务实现类
@Autowired
private TrafficVisitorStatsService trafficVisitorStatsService;

// 访客状态分时统计请求拦截方法
@RequestMapping("/visitorPerHr")
public String getVisitorPerHr(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<TrafficVisitorStatsPerHour> visitorPerHrStatsList = trafficVisitorStatsService.getVisitorPerHrStats(date);
    if (visitorPerHrStatsList == null || visitorPerHrStatsList.size() == 0) {
    
    
        return "";
    }

    TrafficVisitorStatsPerHour[] perHrArr = new TrafficVisitorStatsPerHour[24];
    for (TrafficVisitorStatsPerHour trafficVisitorStatsPerHour : visitorPerHrStatsList) {
    
    
        Integer hr = trafficVisitorStatsPerHour.getHr();
        perHrArr[hr] = trafficVisitorStatsPerHour;
    }

    String[] hrs = new String[24];
    Long[] uvArr = new Long[24];
    Long[] pvArr = new Long[24];
    Long[] newUvArr = new Long[24];

    for (int hr = 0; hr < 24; hr++) {
    
    
        hrs[hr] = String.format("%02d", hr);
        TrafficVisitorStatsPerHour trafficVisitorStatsPerHour = perHrArr[hr];
        if (trafficVisitorStatsPerHour != null) {
    
    
            uvArr[hr] = trafficVisitorStatsPerHour.getUvCt();
            pvArr[hr] = trafficVisitorStatsPerHour.getPvCt();
            newUvArr[hr] = trafficVisitorStatsPerHour.getNewUvCt();
        } else{
    
    
            uvArr[hr] = 0L;
            pvArr[hr] = 0L;
            newUvArr[hr] = 0L;
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": {\n" +
        "    \"categories\": [\n\"" +
        StringUtils.join(hrs, "\",\"") + "\"\n" +
        "    ],\n" +
        "    \"series\": [\n" +
        "      {\n" +
        "        \"name\": \"独立访客数\",\n" +
        "        \"data\": [\n" +
        StringUtils.join(uvArr, ",") + "\n" +
        "        ]\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"页面浏览数\",\n" +
        "        \"data\": [\n" +
        StringUtils.join(pvArr, ",") + "\n" +
        "        ]\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"新访客数\",\n" +
        "        \"data\": [\n" +
        StringUtils.join(newUvArr, ",") + "\n" +
        "        ]\n" +
        "      }\n" +
        "    ]\n" +
        "  }\n" +
        "}";
}

7)Sugar配置

① 选择折线图。

M

② 数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/traffic/visitorPerHr?date=${GMALL_DATE}

③效果如下

M

新老访客流量统计

1)需求介绍

统计周期 统计粒度 指标 说明
当日 访客类型 访客数 分别统计新老访客数
当日 访客类型 页面浏览数 分别统计新老访客页面浏览数
当日 访客类型 跳出率 分别统计新老访客跳出率
当日 访客类型 平均在线时长 分别统计新老访客平均在线时长
当日 访客类型 平均访问页面数 分别统计新老访客平均访问页面数

2)需求分析

本节将通过表格对数据进行展示。所有指标将在同一张表格中体现。

3)数据结构

{
    
    
    "status": 0,
    "data": {
    
    
        "total": 5,
        "columns": [
            {
    
    
                "name": "类别",
                "id": "type"
            },
            {
    
    
                "name": "新访客",
                "id": "new"
            },
            {
    
    
                "name": "老访客",
                "id": "old"
            }
        ],
        "rows": [
            {
    
    
                "type": "访客数(人)",
                "new": 155,
                "old": 145
            },
            {
    
    
                "type": "总访问页面数(次)",
                "new": 507,
                "old": 481
            },
            {
    
    
                "type": "跳出率(%)",
                "new": 0,
                "old": 0
            },
            {
    
    
                "type": "平均在线时长(秒)",
                "new": 33.659716129032255,
                "old": 35.37985517241379
            },
            {
    
    
                "type": "平均访问页面数(人次)",
                "new": 3.270967741935484,
                "old": 3.317241379310345
            }
        ]
    }
}

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class TrafficVisitorTypeStats {
    
    
    // 新老访客状态标记
    String isNew;
    // 独立访客数
    Long uvCt;
    // 页面浏览数
    Long pvCt;
    // 会话数
    Long svCt;
    // 跳出会话数
    Long ujCt;
    // 累计访问时长
    Long durSum;
    // 跳出率
    public Double getUjRate(){
    
    
        if(svCt == 0) {
    
    
            return 0.0;
        }
        return (double)ujCt/(double)svCt;
    }
    // 会话平均在线时长(秒)
    public Double getAvgDurSum() {
    
    
        if(svCt == 0) {
    
    
            return 0.0;
        }
        return (double)durSum/(double) svCt / 1000;
    }
    // 会话平均访问页面数
    public Double getAvgPvCt(){
    
    
        if(svCt == 0) {
    
    
            return 0.0;
        }
        return (double)pvCt / (double) svCt;
    }

}

(2)Mapper 接口

在 TrafficVisitorStatsMapper 接口中补充方法。

@Select("select\n" +
        "is_new,\n" +
        "sum(uv_ct) uv_ct,\n" +
        "sum(pv_ct) pv_ct,\n" +
        "sum(sv_ct) sv_ct,\n" +
        "sum(uj_ct) uj_ct,\n" +
        "sum(dur_sum) dur_sum\n" +
        "from dws_traffic_vc_ch_ar_is_new_page_view_window\n" +
        "where toYYYYMMDD(stt) =#{date}\n" +
        "group by is_new")
List<TrafficVisitorTypeStats> selectVisitorTypeStats(@Param("date")Integer date);

5)Service层

(1)Service 接口

在 TrafficVisitorStatsService 接口中补充方法。

List<TrafficVisitorTypeStats> getVisitorTypeStats(Integer date);

(2)Service 实现类

在 TrafficVisitorStatsServiceImpl 实现类中补充 getVisitorTypeStats 方法的实现,

@Override
public List<TrafficVisitorTypeStats> getVisitorTypeStats(Integer date) {
    
    
    return trafficVisitorStatsMapper.selectVisitorTypeStats(date);
}

6)Controller层

在 TrafficController 类中补充请求拦截方法。

@RequestMapping("/visitorPerType")
public String getVisitorPerType(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<TrafficVisitorTypeStats> visitorTypeStatsList = trafficVisitorStatsService.getVisitorTypeStats(date);
    if (visitorTypeStatsList == null || visitorTypeStatsList.size() == 0) {
    
    
        return "";
    }
    // 方法一:通过循环的方式拼接字符串,较为繁琐,不推荐
    //        StringBuilder columns = new StringBuilder("[\n" +
    //                "      {\n" +
    //                "        \"name\": \"指标\",\n" +
    //                "        \"id\": \"indicators\"\n" +
    //                "      },");
    //
    //        StringBuilder uvRow = new StringBuilder("{\n" +
    //                "        \"indicators\": \"独立访客数\",\n");
    //
    //        StringBuilder pvRow = new StringBuilder("{\n" +
    //                "        \"indicators\": \"页面浏览数\",\n");
    //
    //        StringBuilder ujRow = new StringBuilder("{\n" +
    //                "        \"indicators\": \"跳出率\",\n");
    //
    //        StringBuilder avgDurRow = new StringBuilder("{\n" +
    //                "        \"indicators\": \"会话平均访问时长\",\n");
    //
    //        StringBuilder avgPvRow = new StringBuilder("{\n" +
    //                "        \"indicators\": \"会话平均页面浏览数\",\n");
    //
    //        for (int i = 0; i < visitorTypeStatsList.size(); i++) {
    
    
    //            TrafficVisitorTypeStats trafficVisitorTypeStats = visitorTypeStatsList.get(i);
    //            String isNew = trafficVisitorTypeStats.getIsNew();
    //            Integer uvCt = trafficVisitorTypeStats.getUvCt();
    //            Integer pvCt = trafficVisitorTypeStats.getPvCt();
    //            Double ujRate = trafficVisitorTypeStats.getUjRate();
    //            Double avgDurSum = trafficVisitorTypeStats.getAvgDurSum();
    //            Double avgPvCt = trafficVisitorTypeStats.getAvgPvCt();
    //            if (isNew.equals("1")) {
    
    
    //                columns.append("{\n" +
    //                        "        \"name\": \"新访客\",\n" +
    //                        "        \"id\": \"newVisitor\"\n" +
    //                        "      }");
    //                uvRow.append("\"newVisitor\": " + uvCt);
    //                pvRow.append("\"newVisitor\": " + pvCt);
    //                ujRow.append("\"newVisitor\": " + ujRate);
    //                avgDurRow.append("\"newVisitor\": " + avgDurSum);
    //                avgPvRow.append("\"newVisitor\": " + avgPvCt);
    //            } else {
    
    
    //                columns.append("{\n" +
    //                        "        \"name\": \"老访客\",\n" +
    //                        "        \"id\": \"oldVisitor\"\n" +
    //                        "      }");
    //
    //                uvRow.append("\"oldVisitor\": " + uvCt + "\n");
    //                pvRow.append("\"oldVisitor\": " + pvCt + "\n");
    //                ujRow.append("\"oldVisitor\": " + ujRate + "\n");
    //                avgDurRow.append("\"oldVisitor\": " + avgDurSum + "\n");
    //                avgPvRow.append("\"oldVisitor\": " + avgPvCt + "\n");
    //            }
    //            if (i == 0) {
    
    
    //                columns.append(",\n");
    //                uvRow.append(",\n");
    //                pvRow.append(",\n");
    //                ujRow.append(",\n");
    //                avgDurRow.append(",\n");
    //                avgPvRow.append(",\n");
    //            } else {
    
    
    //                columns.append("\n]");
    //                uvRow.append("\n}");
    //                pvRow.append("\n}");
    //                ujRow.append("\n}");
    //                avgDurRow.append("\n}");
    //                avgPvRow.append("\n}");
    //            }
    //        }
    //        return "{\n" +
    //                "  \"status\": 0,\n" +
    //                "  \"msg\": \"\",\n" +
    //                "  \"data\": {\n" +
    //                "    \"columns\": "+ columns +",\n" +
    //                "    \"rows\": [\n" +
    //                "      "+ uvRow +",\n" +
    //                "      "+ pvRow +",\n" +
    //                "      "+ ujRow +",\n" +
    //                "      "+ avgDurRow +",\n" +
    //                "      "+ avgPvRow +"\n" +
    //                "    ]\n" +
    //                "  }\n" +
    //                "}";

    // 方法二,直接拼接字符串,简单明了
    TrafficVisitorTypeStats newVisitorStats = null;
    TrafficVisitorTypeStats oldVisitorStats = null;
    for (TrafficVisitorTypeStats visitorStats : visitorTypeStatsList) {
    
    
        //            System.out.println(visitorStats);
        if ("1".equals(visitorStats.getIsNew())) {
    
    
            // 新访客
            newVisitorStats = visitorStats;
        } else {
    
    
            // 老访客
            oldVisitorStats = visitorStats;
        }
    }
    //拼接json字符串
    String json = "{\"status\":0,\"data\":{\"total\":5," s
        "\"columns\":[" +
        "{\"name\":\"类别\",\"id\":\"type\"}," +
        "{\"name\":\"新访客\",\"id\":\"new\"}," +
        "{\"name\":\"老访客\",\"id\":\"old\"}]," +
        "\"rows\":[" +
        "{\"type\":\"访客数(人)\",\"new\":" + newVisitorStats.getUvCt() + ",\"old\":" + oldVisitorStats.getUvCt() + "}," +
        "{\"type\":\"总访问页面数(次)\",\"new\":" + newVisitorStats.getPvCt() + ",\"old\":" + oldVisitorStats.getPvCt() + "}," +
        "{\"type\":\"跳出率(%)\",\"new\":" + newVisitorStats.getUjRate() + ",\"old\":" + oldVisitorStats.getUjRate() + "}," +
        "{\"type\":\"平均在线时长(秒)\",\"new\":" + newVisitorStats.getAvgDurSum() + ",\"old\":" + oldVisitorStats.getAvgDurSum() + "}," +
        "{\"type\":\"平均访问页面数(人次)\",\"new\":" + newVisitorStats.getAvgPvCt() + ",\"old\":" + oldVisitorStats.getAvgPvCt() + "}]}}";

    return json;
}

7)Sugar配置

① 选择表格。

M

② 数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/traffic/visitorPerType?date=${GMALL_DATE}

③ 效果如下。

M

关键词统计

1)需求介绍

统计周期 统计粒度 指标 说明
当日 关键词 关键词评分 根据不同来源和频次计算得分

2)需求分析

关键词的展示使用 3D 词云实现。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": [
    {
    
    
      "name": "小米",
      "value": 2370
    },
    {
    
    
      "name": "ps5",
      "value": 1290
    },
    ...
  ]
}

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class TrafficKeywords {
    
    
    // 关键词
    String keyword;
    // 关键词评分
    Integer keywordScore;
}

(2)Mapper 接口

public interface TrafficKeywordsMapper {
    
    
    @Select("select keyword,\n" +
            "       sum(keyword_count * multiIf(\n" +
            "               source = 'SEARCH', 10,\n" +
            "               source = 'ORDER', 5,\n" +
            "               source = 'CART', 2,\n" +
            "               source = 'CLICK', 1, 0\n" +
            "           ))          keyword_score\n" +
            "from dws_traffic_source_keyword_page_view_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt), keyword\n" +
            "order by keyword_score desc;")
    List<TrafficKeywords> selectKeywords(@Param(value = "date") Integer date);
}

5)Service层

(1)Service 接口

public interface TrafficKeywordsService {
    
    
    List<TrafficKeywords> getKeywords(Integer date);
}

(2)Service 实现类

@Service
public class TrafficKeywordsServiceImpl implements TrafficKeywordsService {
    
    

    @Autowired
    TrafficKeywordsMapper trafficKeywordsMapper;

    @Override
    public List<TrafficKeywords> getKeywords(Integer date) {
    
    
        return trafficKeywordsMapper.selectKeywords(date);
    }
}

6)Controller层

在 TrafficController 中补充关键词服务接口类型成员变量和请求拦截方法。

@Autowired
private TrafficKeywordsService trafficKeywordsService;

@RequestMapping("/keywords")
public String getKeywords(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<TrafficKeywords> keywordsList = trafficKeywordsService.getKeywords(date);
    if (keywordsList == null) {
    
    
        return "";
    }

    StringBuilder data = new StringBuilder("[");

    for (int i = 0; i < keywordsList.size(); i++) {
    
    
        TrafficKeywords trafficKeywords = keywordsList.get(i);
        String keyword = trafficKeywords.getKeyword();
        Integer keywordScore = trafficKeywords.getKeywordScore();
        data.append("" +
                    "{\n" +
                    "      \"name\": \"" + keyword + "\",\n" +
                    "      \"value\": " + keywordScore + "\n" +
                    "    }");
        if (i < keywordsList.size() - 1) {
    
    
            data.append(",");
        } else {
    
    
            data.append("]");
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": " + data + "\n" +
        "}";
}

7)Sugar配置

① 选择 3D 词云

M

② 数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/traffic/keywords?date=${GMALL_DATE}

③ 效果如下

M

用户主题

用户变动统计

1)需求介绍

统计周期 指标 说明
当日 回流用户数 之前的活跃用户,一段时间未活跃(流失),今日又活跃了,就称为回流用户。此处要求统计回流用户总数。

本节需求与用户新增活跃统计需求合并展示。

用户新增活跃统计

1)需求介绍

统计周期 指标 指标说明
当日 新增用户数
当日 活跃用户数

2)需求分析

本节和 用户变动统计 指标展示将使用表格展示。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "columns": [
      {
    
    
        "name": "变动类型",
        "id": "type"
      },
      {
    
    
        "name": "用户数",
        "id": "user_ct"
      }
    ],
    "rows": [
      {
    
    
        "type": "activeUserCt",
        "user_ct": "915"
      },
      {
    
    
        "type": "newUserCt",
        "user_ct": "20"
      },
      {
    
    
        "type": "backCt",
        "user_ct": "0"
      }
    ]
  }
}

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class UserChangeCtPerType {
    
    
    // 变动类型
    String type;
    // 用户数
    Integer userCt;
}

(2)Mapper 接口

public interface UserStatsMapper {
    
    

    @Select("select 'backCt'          type,\n" +
            "       sum(back_ct)    back_ct\n" +
            "from dws_user_user_login_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "union all\n" +
            "select 'activeUserCt'     type,\n" +
            "       sum(uu_ct)      uu_ct\n" +
            "from dws_user_user_login_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "union all\n" +
            "select 'newUserCt' type,\n" +
            "       sum(register_ct) register_ct\n" +
            "from dws_user_user_register_window\n" +
            "where toYYYYMMDD(stt) = #{date};")
    List<UserChangeCtPerType> selectUserChangeCtPerType(@Param("date")Integer date);
}

5)Service层

(1)Service 接口

public interface UserStatsService {
    
    
    List<UserChangeCtPerType> getUserChangeCt(Integer date);
}

(2)Service 实现类

@Service
public class UserStatsServiceImpl implements UserStatsService {
    
    

    @Autowired
    UserStatsMapper userStatsMapper;

    @Override
    public List<UserChangeCtPerType> getUserChangeCt(Integer date) {
    
    
        return userStatsMapper.selectUserChangeCtPerType(date);
    }
}

6)Controller层

@RestController
@RequestMapping("/gmall/realtime/user")
public class UserStatsController {
    
    

    @Autowired
    private UserStatsService userStatsService;

    @RequestMapping("/userChangeCt")
    public String getUserChange(
        @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<UserChangeCtPerType> userChangeCtList = userStatsService.getUserChangeCt(date);
        if (userChangeCtList == null) {
    
    
            return "";
        }
        StringBuilder rows = new StringBuilder("[");
        for (int i = 0; i < userChangeCtList.size(); i++) {
    
    
            UserChangeCtPerType userChangeCt = userChangeCtList.get(i);
            String type = userChangeCt.getType();
            Integer userCt = userChangeCt.getUserCt();
            rows.append("{\n" +
                        "\t\"type\": \"" + type + "\",\n" +
                        "\t\"user_ct\": \"" + userCt + "\"\n" +
                        "}");
            if (i < userChangeCtList.size() - 1) {
    
    
                rows.append(",");
            } else {
    
    
                rows.append("]");
            }
        }

        return "{\n" +
            "  \"status\": 0,\n" +
            "  \"msg\": \"\",\n" +
            "  \"data\": {\n" +
            "    \"columns\": [\n" +
            "      {\n" +
            "        \"name\": \"变动类型\",\n" +
            "        \"id\": \"type\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"用户数\",\n" +
            "        \"id\": \"user_ct\"\n" +
            "      }\n" +
            "    ],\n" +
            "    \"rows\": " + rows + "\n" +
            "  }\n" +
            "}";
    }
}

7)Sugar配置

① 选择表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/user/userChangeCt?date=${GMALL_DATE}

② 效果如下

M

用户行为漏斗分析

1)需求介绍

漏斗分析是一个数据分析模型,它能够科学反映一个业务过程从起点到终点各阶段用户转化情况。由于其能将各阶段环节都展示出来,故哪个阶段存在问题,就能一目了然。

MM

该需求要求统计一个完整的购物流程各个阶段的人数,具体说明如下:

统计周期 指标 说明
当日 首页浏览人数
当日 商品详情页浏览人数
当日 加购人数
当日 下单人数
当日 支付人数 支付成功人数

2)需求分析

本节指标展示使用轮播表格实现。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "columns": [
      {
    
    
        "name": "页面",
        "id": "page_id"
      },
      {
    
    
        "name": "独立访客数",
        "id": "uv_ct"
      }
    ],
    "rows": [
      {
    
    
        "page_id": "home",
        "uv_ct": "2820"
      },
      {
    
    
        "page_id": "good_detail",
        "uv_ct": "2638"
      },
      {
    
    
        "page_id": "payment",
        "uv_ct": "242"
      },
      {
    
    
        "page_id": "cart",
        "uv_ct": "1178"
      },
      {
    
    
        "page_id": "trade",
        "uv_ct": "1681"
      }
    ]
  }
}

4)Mapper 层

(1)实体类

@Data
@AllArgsConstructor
public class UserPageCt {
    
    
    // 页面 id
    String pageId;
    //独立访客数
    Integer uvCt;
}

(2)Mapper 接口

在 UserStatsMapper 接口中补充查询方法。

@Select("select 'home'  page_id,\n" +
        "       sum(home_uv_ct)        uvCt\n" +
        "       from dws_traffic_page_view_window\n" +
        "       where toYYYYMMDD(stt) = #{date}\n" +
        "       union all\n" +
        "select 'good_detail' page_id,\n" +
        "       sum(good_detail_uv_ct) uvCt\n" +
        "       from dws_traffic_page_view_window\n" +
        "       where toYYYYMMDD(stt) = #{date}\n" +
        "       union all\n" +
        "select 'cart' page_id,\n" +
        "       sum(cart_add_uu_ct) uvCt\n" +
        "       from dws_trade_cart_add_uu_window\n" +
        "       where toYYYYMMDD(stt) = #{date}\n" +
        "       union all\n" +
        "select 'trade' page_id,\n" +
        "       sum(order_unique_user_count) uvCt\n" +
        "       from dws_trade_order_window\n" +
        "       where toYYYYMMDD(stt) = #{date}\n" +
        "       union all\n" +
        "select 'payment' page_id,\n" +
        "       sum(payment_suc_unique_user_count) uvCt\n" +
        "       from dws_trade_payment_suc_window\n" +
        "       where toYYYYMMDD(stt) = #{date};")
List<UserPageCt> selectUvByPage(@Param("date") Integer date);

5)Service层

(1)Service 接口

在 UserStatsService 接口中补充方法。

List<UserPageCt> getUvByPage(Integer date);

(2)Service 实现类

在实现类 UserStatsServiceImpl 中补充方法:

@Override
public List<UserPageCt> getUvByPage(Integer date) {
    
    
    return userStatsMapper.selectUvByPage(date);
}

6)Controller层

在 UserStatsController 控制类中补充方法:

@RequestMapping("/uvPerPage")
public String getUvPerPage(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<UserPageCt> uvByPageList = userStatsService.getUvByPage(date);
    if (uvByPageList == null) {
    
    
        return "";
    }
    StringBuilder rows = new StringBuilder("[");
    for (int i = 0; i < uvByPageList.size(); i++) {
    
    
        UserPageCt userPageCt = uvByPageList.get(i);
        String pageId = userPageCt.getPageId();
        Integer uvCt = userPageCt.getUvCt();
        rows.append("{\n" +
                    "        \"page_id\": \"" + pageId + "\",\n" +
                    "        \"uv_ct\": \"" + uvCt + "\"\n" +
                    "      }");
        if (i < uvByPageList.size() - 1) {
    
    
            rows.append(",");
        } else {
    
    
            rows.append("]");
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": {\n" +
        "    \"columns\": [\n" +
        "      {\n" +
        "        \"name\": \"页面\",\n" +
        "        \"id\": \"page_id\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"独立访客数\",\n" +
        "        \"id\": \"uv_ct\"\n" +
        "      }\n" +
        "    ],\n" +
        "    \"rows\": " + rows + "\n" +
        "  }\n" +
        "}";
}

7)Sugar配置

① 选择轮播表格

M

② 数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/user/uvPerPage?date=${GMALL_DATE}

③ 效果如下

M

新增交易用户统计

1)需求说明

统计周期 指标 说明
当日 新增下单人数
当日 新增支付人数

2)需求分析

本节指标展示使用轮播表格实现。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "columns": [
      {
    
    
        "name": "交易类型",
        "id": "type"
      },
      {
    
    
        "name": "新增用户数",
        "id": "user_ct"
      }
    ],
    "rows": [
      {
    
    
        "type": "order",
        "user_ct": "1681"
      },
      {
    
    
        "type": "payment",
        "user_ct": "242"
      }
    ]
  }
}

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class UserTradeCt {
    
    
    // 交易类型
    String type;
    // 用户数
    Integer userCt;
}

(2)Mapper 接口

在 UserStatsMapper 类中补充方法。

@Select("select 'order' trade_type,\n" +
        "       sum(order_new_user_count) order_new_user_count\n" +
        "       from dws_trade_order_window\n" +
        "       where toYYYYMMDD(stt) = #{date}\n" +
        "       union all\n" +
        "select 'payment' trade_type,\n" +
        "       sum(payment_new_user_count) pay_suc_new_user_count\n" +
        "       from dws_trade_payment_suc_window\n" +
        "       where toYYYYMMDD(stt) = #{date};")
List<UserTradeCt> selectTradeUserCt(@Param("date")Integer date);

5)Service层

(1)Service 接口

在 UserStatsService 接口中补充方法。

List<UserTradeCt> getTradeUserCt(Integer date);

(2)Service 实现类

在 UserStatsServiceImpl 实现类中补充方法。

@Override
public List<UserTradeCt> getTradeUserCt(Integer date) {
    
    
    return userStatsMapper.selectTradeUserCt(date);
}

6)Controller层

在 UserStatsController 控制类中补充方法。

@RequestMapping("/userTradeCt")
public String getUserTradeCt(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<UserTradeCt> tradeUserCtList = userStatsService.getTradeUserCt(date);
    if (tradeUserCtList == null) {
    
    
        return "";
    }
    StringBuilder rows = new StringBuilder("[");
    for (int i = 0; i < tradeUserCtList.size(); i++) {
    
    
        UserTradeCt userTradeCt = tradeUserCtList.get(i);
        String type = userTradeCt.getType();
        Integer userCt = userTradeCt.getUserCt();
        rows.append("{\n" +
                    "\t\"type\": \"" + type + "\",\n" +
                    "\t\"user_ct\": \"" + userCt + "\"\n" +
                    "}");
        if (i < tradeUserCtList.size() - 1) {
    
    
            rows.append(",");
        } else {
    
    
            rows.append("]");
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": {\n" +
        "    \"columns\": [\n" +
        "      {\n" +
        "        \"name\": \"交易类型\",\n" +
        "        \"id\": \"type\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"新增用户数\",\n" +
        "        \"id\": \"user_ct\"\n" +
        "      }\n" +
        "    ],\n" +
        "    \"rows\": " + rows + "\n" +
        "  }\n" +
        "}";
}

7)Sugar配置

① 选择轮播表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/user/userTradeCt?date=${GMALL_DATE}

② 效果如下

M

商品主题

各品牌商品交易统计

1)需求说明

统计周期 统计粒度 指标 说明
当日 品牌 订单数
当日 品牌 订单人数
当日 品牌 订单金额
当日 品牌 退单数
当日 品牌 退单人数

2)需求分析

本节指标的展示使用轮播表格和环形饼图实现。其中轮播表格展示所有指标,环形饼图仅展示订单金额。

3)数据结构

(1)轮播表格数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "columns": [
      {
    
    
        "name": "品牌名称",
        "id": "trademark"
      },
      {
    
    
        "name": "订单数",
        "id": "order_count"
      },
      {
    
    
        "name": "订单人数",
        "id": "uu_count"
      },
      {
    
    
        "name": "订单金额",
        "id": "order_amount"
      },
      {
    
    
        "name": "退单数",
        "id": "refund_count"
      },
      {
    
    
        "name": "退单人数",
        "id": "refund_uu_count"
      }
    ],
    "rows": [
      {
    
    
        "trademark": "索芙特",
        "order_count": "734",
        "uu_count": "628",
        "order_amount": "1075887.91",
        "refund_count": "37",
        "refund_uu_count": "36"
      },
      {
    
    
        "trademark": "苹果",
        "order_count": "995",
        "uu_count": "826",
        "order_amount": "1.2911363511E8",
        "refund_count": "52",
        "refund_uu_count": "49"
      },
      ...
    ]
  }
}

(2)环形饼图数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": [
    {
    
    
      "name": "索芙特",
      "value": 1075887.91
    },
    {
    
    
      "name": "苹果",
      "value": 129113635.11
    },
    ...
  ]
}

4)Mapper层

(1)实体类

① 轮播表格实体类

@Data
@AllArgsConstructor
public class TrademarkCommodityStats {
    
    
    // 品牌名称
    String trademarkName;
    // 订单数
    Integer orderCt;
    // 订单人数
    Integer uuCt;
    // 订单金额
    Double orderAmount;
    // 退单数
    Integer refundCt;
    // 退单人数
    Integer refundUuCt;
}

② 环形饼图实体类

@Data
@AllArgsConstructor
public class TrademarkOrderAmountPieGraph {
    
    
    // 品牌名称
    String trademarkName;
    // 销售额
    Double orderAmount;
}

(2)Mapper 接口

public interface CommodityStatsMapper {
    
    
    @Select("select trademark_name,\n" +
            "       order_count,\n" +
            "       uu_count,\n" +
            "       order_amount,\n" +
            "       refund_count,\n" +
            "       refund_uu_count\n" +
            "from (select trademark_id,\n" +
            "             trademark_name,\n" +
            "             sum(order_count)        order_count,\n" +
            "             count(distinct user_id) uu_count,\n" +
            "             sum(order_amount)       order_amount\n" +
            "      from dws_trade_trademark_category_user_spu_order_window\n" +
            "      where toYYYYMMDD(stt) = #{date}\n" +
            "      group by trademark_id, trademark_name) oct\n" +
            "         full outer join\n" +
            "     (select trademark_id,\n" +
            "             trademark_name,\n" +
            "             sum(refund_count)       refund_count,\n" +
            "             count(distinct user_id) refund_uu_count\n" +
            "      from dws_trade_trademark_category_user_refund_window\n" +
            "      where toYYYYMMDD(stt) = #{date}\n" +
            "      group by trademark_id, trademark_name) rct\n" +
            "     on oct.trademark_id = rct.trademark_id;\n")
    List<TrademarkCommodityStats> selectTrademarkStats(@Param("date") Integer date);

    @Select("select trademark_name,\n" +
            "       sum(order_amount) order_amount\n" +
            "from dws_trade_trademark_category_user_spu_order_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by trademark_id, trademark_name;")
    List<TrademarkOrderAmountPieGraph> selectTmOrderAmtPieGra(@Param("date")Integer date);
}

5)Service层

(1)Service 接口

public interface CommodityStatsService {
    
    
    List<TrademarkCommodityStats> getTrademarkCommodityStatsService(Integer date);

    List<TrademarkOrderAmountPieGraph> getTmOrderAmtPieGra(Integer date);
}

(2)Service 实现类

@Service
public class CommodityStatsServiceImpl implements CommodityStatsService {
    
    

    @Autowired
    private CommodityStatsMapper commodityStatsMapper;

    @Override
    public List<TrademarkCommodityStats> getTrademarkCommodityStatsService(Integer date) {
    
    
        return commodityStatsMapper.selectTrademarkStats(date);
    }

    @Override
    public List<TrademarkOrderAmountPieGraph> getTmOrderAmtPieGra(Integer date) {
    
    
        return commodityStatsMapper.selectTmOrderAmtPieGra(date);
    }
}

6)Controller层

@RestController
@RequestMapping("/gmall/realtime/commodity")
public class CommodityStatsController {
    
    

    @Autowired
    private CommodityStatsService commodityStatsService;

    @RequestMapping("/trademark")
    public String getTrademarkCommodityStats(
        @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TrademarkCommodityStats> trademarkCommodityStatsList = commodityStatsService.getTrademarkCommodityStatsService(date);
        if (trademarkCommodityStatsList == null) {
    
    
            return "";
        }
        StringBuilder rows = new StringBuilder("[");
        for (int i = 0; i < trademarkCommodityStatsList.size(); i++) {
    
    
            TrademarkCommodityStats trademarkCommodityStats = trademarkCommodityStatsList.get(i);
            String trademarkName = trademarkCommodityStats.getTrademarkName();
            Integer orderCt = trademarkCommodityStats.getOrderCt();
            Integer uuCt = trademarkCommodityStats.getUuCt();
            Double orderAmount = trademarkCommodityStats.getOrderAmount();
            Integer refundCt = trademarkCommodityStats.getRefundCt();
            Integer refundUuCt = trademarkCommodityStats.getRefundUuCt();

            rows.append("{\n" +
                        "\t\"trademark\": \"" + trademarkName + "\",\n" +
                        "\t\"order_count\": \"" + orderCt + "\",\n" +
                        "\t\"uu_count\": \"" + uuCt + "\",\n" +
                        "\t\"order_amount\": \"" + orderAmount + "\",\n" +
                        "\t\"refund_count\": \"" + refundCt + "\",\n" +
                        "\t\"refund_uu_count\": \"" + refundUuCt + "\"\n" +
                        "}");
            if (i < trademarkCommodityStatsList.size() - 1) {
    
    
                rows.append(",");
            } else {
    
    
                rows.append("]");
            }
        }
        return "{\n" +
            "  \"status\": 0,\n" +
            "  \"msg\": \"\",\n" +
            "  \"data\": {\n" +
            "    \"columns\": [\n" +
            "      {\n" +
            "        \"name\": \"品牌名称\",\n" +
            "        \"id\": \"trademark\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"订单数\",\n" +
            "        \"id\": \"order_count\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"订单人数\",\n" +
            "        \"id\": \"uu_count\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"订单金额\",\n" +
            "        \"id\": \"order_amount\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"退单数\",\n" +
            "        \"id\": \"refund_count\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"退单人数\",\n" +
            "        \"id\": \"refund_uu_count\"\n" +
            "      }\n" +
            "    ],\n" +
            "    \"rows\": " + rows + "\n" +
            "  }\n" +
            "}";
    }

    @RequestMapping("/tmPieGraph")
    public String getTmOrderAmtPieGra(
        @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if(date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TrademarkOrderAmountPieGraph> tmOrderAmtPieGraList = commodityStatsService.getTmOrderAmtPieGra(date);
        if(tmOrderAmtPieGraList == null || tmOrderAmtPieGraList.size() == 0) {
    
    
            return "";
        }
        StringBuilder dataSet = new StringBuilder("[");
        for (int i = 0; i < tmOrderAmtPieGraList.size(); i++) {
    
    
            TrademarkOrderAmountPieGraph trademarkOrderAmountPieGraph = tmOrderAmtPieGraList.get(i);
            String trademarkName = trademarkOrderAmountPieGraph.getTrademarkName();
            Double orderAmount = trademarkOrderAmountPieGraph.getOrderAmount();
            dataSet.append("{\n" +
                           "      \"name\": \""+ trademarkName +"\",\n" +
                           "      \"value\": "+ orderAmount +"\n" +
                           "    }");
            if(i < tmOrderAmtPieGraList.size() - 1) {
    
    
                dataSet.append(",");
            } else {
    
    
                dataSet.append("]");
            }
        }

        return "{\n" +
            "  \"status\": 0,\n" +
            "  \"msg\": \"\",\n" +
            "  \"data\": "+ dataSet +"\n" +
            "}";
    }
}

7)Sugar配置

① 选择轮播表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/commodity/trademark?date=${GMALL_DATE}

② 效果如下

M

③ 选择环形饼图。

M

④ 数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/commodity/tmPieGraph?date=${GMALL_DATE}

⑤ 效果如下。

M

各品类商品交易统计

1)需求说明

统计周期 统计粒度 指标 说明
当日 品类 订单数
当日 品类 订单人数
当日 品类 订单金额
当日 品类 退单数
当日 品类 退单人数

2)需求分析

本节指标的展示使用轮播表格实现。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "columns": [
      {
    
    
        "name": "一级品类名称",
        "id": "category1_name"
      },
      {
    
    
        "name": "二级品类名称",
        "id": "category2_name"
      },
      {
    
    
        "name": "三级品类名称",
        "id": "category3_name"
      },
      {
    
    
        "name": "订单数",
        "id": "order_count"
      },
      {
    
    
        "name": "订单人数",
        "id": "uu_count"
      },
      {
    
    
        "name": "订单金额",
        "id": "order_amount"
      },
      {
    
    
        "name": "退单数",
        "id": "refund_count"
      },
      {
    
    
        "name": "退单人数",
        "id": "refund_uu_count"
      }
    ],
    "rows": [
      {
    
    
        "category1_name": "家用电器",
        "category2_name": "大 家 电",
        "category3_name": "平板电视",
        "order_count": "1834",
        "uu_count": "1000",
        "order_amount": "1.10715225E8",
        "refund_count": "71",
        "refund_uu_count": "70.0"
      },
      ...
    ]
  }
}

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class CategoryCommodityStats {
    
    
    // 一级品类名称
    String category1_name;
    // 二级品类名称
    String category2_name;
    // 三级品类名称
    String category3_name;
    // 订单数
    Integer orderCt;
    // 下单用户数
    Integer uuCt;
    // 订单金额
    Double orderAmount;
    // 退单数
    Integer refundCt;
    // 退单金额
    Double refundUcCt;
}

(2)Mapper 接口

在 CommodityStatsMapper 接口中补充方法。

@Select("select category1_name,\n" +
        "       category2_name,\n" +
        "       category3_name,\n" +
        "       order_count,\n" +
        "       uu_count,\n" +
        "       order_amount,\n" +
        "       refund_count,\n" +
        "       refund_uu_count\n" +
        "from (select category1_id,\n" +
        "             category1_name,\n" +
        "             category2_id,\n" +
        "             category2_name,\n" +
        "             category3_id,\n" +
        "             category3_name,\n" +
        "             sum(order_count)        order_count,\n" +
        "             count(distinct user_id) uu_count,\n" +
        "             sum(order_amount)       order_amount\n" +
        "      from dws_trade_trademark_category_user_spu_order_window\n" +
        "      where toYYYYMMDD(stt) = #{date}\n" +
        "      group by category1_id,\n" +
        "               category1_name,\n" +
        "               category2_id,\n" +
        "               category2_name,\n" +
        "               category3_id,\n" +
        "               category3_name) oct\n" +
        "         full outer join\n" +
        "     (select category1_id,\n" +
        "             category1_name,\n" +
        "             category2_id,\n" +
        "             category2_name,\n" +
        "             category3_id,\n" +
        "             category3_name,\n" +
        "             sum(refund_count)       refund_count,\n" +
        "             count(distinct user_id) refund_uu_count\n" +
        "      from dws_trade_trademark_category_user_refund_window\n" +
        "      where toYYYYMMDD(stt) = #{date}\n" +
        "      group by category1_id,\n" +
        "               category1_name,\n" +
        "               category2_id,\n" +
        "               category2_name,\n" +
        "               category3_id,\n" +
        "               category3_name) rct\n" +
        "     on oct.category1_id = rct.category1_id\n" +
        "         and oct.category2_id = rct.category2_id\n" +
        "         and oct.category3_id = rct.category3_id;")
List<CategoryCommodityStats> selectCategoryStats(@Param("date") Integer date);

5)Service层

(1)Service 接口

在 CommodityStatsService 接口中补充方法。

List<CategoryCommodityStats> getCategoryStatsService(Integer date);

(2)Service 实现类

在 CommodityStatsServiceImpl 实现类中补充方法。

@Override
public List<CategoryCommodityStats> getCategoryStatsService(Integer date) {
    
    
    return commodityStatsMapper.selectCategoryStats(date);
}

6)Controller层

在 CommodityStatsController 类中补充方法。

@RequestMapping("/category")
public String getCategoryStats(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<CategoryCommodityStats> categoryStatsServiceList = commodityStatsService.getCategoryStatsService(date);
    if (categoryStatsServiceList == null) {
    
    
        return "";
    }
    StringBuilder rows = new StringBuilder("[");
    for (int i = 0; i < categoryStatsServiceList.size(); i++) {
    
    
        CategoryCommodityStats categoryCommodityStats = categoryStatsServiceList.get(i);
        String category1Name = categoryCommodityStats.getCategory1_name();
        String category2Name = categoryCommodityStats.getCategory2_name();
        String category3Name = categoryCommodityStats.getCategory3_name();
        Integer orderCt = categoryCommodityStats.getOrderCt();
        Integer uuCt = categoryCommodityStats.getUuCt();
        Double orderAmount = categoryCommodityStats.getOrderAmount();
        Integer refundCt = categoryCommodityStats.getRefundCt();
        Double refundUcCt = categoryCommodityStats.getRefundUcCt();

        rows.append("{\n" +
                    "\t\"category1_name\": \"" + category1Name + "\",\n" +
                    "\t\"category2_name\": \"" + category2Name + "\",\n" +
                    "\t\"category3_name\": \"" + category3Name + "\",\n" +
                    "\t\"order_count\": \"" + orderCt + "\",\n" +
                    "\t\"uu_count\": \"" + uuCt + "\",\n" +
                    "\t\"order_amount\": \"" + orderAmount + "\",\n" +
                    "\t\"refund_count\": \"" + refundCt + "\",\n" +
                    "\t\"refund_uu_count\": \"" + refundUcCt + "\"\n" +
                    "}");
        if (i < categoryStatsServiceList.size() - 1) {
    
    
            rows.append(",");
        } else {
    
    
            rows.append("]");
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": {\n" +
        "    \"columns\": [\n" +
        "      {\n" +
        "        \"name\": \"一级品类名称\",\n" +
        "        \"id\": \"category1_name\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"二级品类名称\",\n" +
        "        \"id\": \"category2_name\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"三级品类名称\",\n" +
        "        \"id\": \"category3_name\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"订单数\",\n" +
        "        \"id\": \"order_count\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"订单人数\",\n" +
        "        \"id\": \"uu_count\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"订单金额\",\n" +
        "        \"id\": \"order_amount\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"退单数\",\n" +
        "        \"id\": \"refund_count\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"退单人数\",\n" +
        "        \"id\": \"refund_uu_count\"\n" +
        "      }\n" +
        "    ],\n" +
        "    \"rows\": " + rows + "\n" +
        "  }\n" +
        "}";
}

7)Sugar配置

① 选择轮播表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/commodity/category?date=${GMALL_DATE}

② 效果如下

M

各 SPU 商品交易统计

1)需求说明

统计周期 统计粒度 指标 说明
当日 SPU 订单数
当日 SPU 订单人数
当日 SPU 订单金额

2)需求分析

关键词的展示使用轮播表格实现。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "columns": [
      {
    
    
        "name": "SPU 名称",
        "id": "spu_name"
      },
      {
    
    
        "name": "下单次数",
        "id": "order_count"
      },
      {
    
    
        "name": "下单人数",
        "id": "uu_count"
      },
      {
    
    
        "name": "订单金额",
        "id": "order_amount"
      }
    ],
    "rows": [
      {
    
    
        "spu_name": "香奈儿(Chanel)女士香水5号香水 粉邂逅柔情淡香水EDT ",
        "order_count": "565",
        "uu_count": "485",
        "order_amount": "2060032.0"
      },
      ...
    ]
  }
}

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class SpuCommodityStats {
    
    
    // SPU 名称
    String spuName;
    // 下单数
    Integer orderCt;
    // 下单用户数
    Integer uuCt;
    // 下单金额
    Double orderAmount;
}

(2)Mapper 接口

在 CommodityStatsMapper 接口中补充方法。

@Select("select spu_name,\n" +
        "       sum(order_count)        order_count,\n" +
        "       count(distinct user_id) uu_count,\n" +
        "       sum(order_amount)       order_amount\n" +
        "from dws_trade_trademark_category_user_spu_order_window\n" +
        "where toYYYYMMDD(stt) = #{date}\n" +
        "group by spu_id, spu_name;")
List<SpuCommodityStats> selectSpuStats(@Param("date") Integer date);

5)Service层

(1)Service 接口

在 CommodityStatsService 中补充方法。

List<SpuCommodityStats> getSpuCommodityStats(Integer date);

(2)Service 实现类

在 CommodityStatsServiceImpl 实现类中补充方法。

@Override
public List<SpuCommodityStats> getSpuCommodityStats(Integer date) {
    
    
    return commodityStatsMapper.selectSpuStats(date);
}

6)Controller层

在 CommodityStatsController 中补充方法。

@RequestMapping("/spu")
public String getSpuStats(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<SpuCommodityStats> spuCommodityStatsList = commodityStatsService.getSpuCommodityStats(date);
    if (spuCommodityStatsList == null) {
    
    
        return "";
    }
    StringBuilder rows = new StringBuilder("[");
    for (int i = 0; i < spuCommodityStatsList.size(); i++) {
    
    
        SpuCommodityStats spuCommodityStats = spuCommodityStatsList.get(i);
        String spuName = spuCommodityStats.getSpuName();
        Integer orderCt = spuCommodityStats.getOrderCt();
        Integer uuCt = spuCommodityStats.getUuCt();
        Double orderAmount = spuCommodityStats.getOrderAmount();
        rows.append("{\n" +
                    "\t\"spu_name\": \"" + spuName + "\",\n" +
                    "\t\"order_count\": \"" + orderCt + "\",\n" +
                    "\t\"uu_count\": \"" + uuCt + "\",\n" +
                    "\t\"order_amount\": \"" + orderAmount + "\"\n" +
                    "}");
        if (i < spuCommodityStatsList.size() - 1) {
    
    
            rows.append(",");
        } else {
    
    
            rows.append("]");
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": {\n" +
        "    \"columns\": [\n" +
        "      {\n" +
        "        \"name\": \"SPU 名称\",\n" +
        "        \"id\": \"spu_name\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"下单次数\",\n" +
        "        \"id\": \"order_count\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"下单人数\",\n" +
        "        \"id\": \"uu_count\"\n" +
        "      },\n" +
        "      {\n" +
        "        \"name\": \"订单金额\",\n" +
        "        \"id\": \"order_amount\"\n" +
        "      }\n" +
        "    ],\n" +
        "    \"rows\": " + rows + "\n" +
        "  }\n" +
        "}";
}

7)Sugar配置

① 选择轮播表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/commodity/spu?date=${GMALL_DATE}

② 效果如下

M

交易主题

交易综合统计

1)需求说明

统计周期 指标 说明
当日 订单总额 订单最终金额
当日 订单数
当日 订单人数
当日 退单数
当日 退单人数

2)需求分析

订单总额用数字翻牌器展示,其余指标用轮播表格展示。

3)数据结构

(1)数字翻牌器

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

(2)轮播表格

同上,不再展示。

4)Mapper层

(1)实体类

数字翻牌器无须实体类。

@Data
@AllArgsConstructor
public class TradeStats {
    
    
    // 指标类型
    String type;
    // 度量值
    Integer orderCt;
}

(2)Mapper 接口

public interface TradeStatsMapper {
    
    
    // 交易总金额
    @Select("select sum(order_amount) order_total_amount\n" +
            "from dws_trade_province_order_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt);")
    Double selectTotalAmount(@Param("date")Integer date);

    // 下单情况统计
    @Select("select '下单数' type,\n" +
            "       sum(order_count)        value\n" +
            "from dws_trade_trademark_category_user_spu_order_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "union all\n" +
            "select '下单人数' type,\n" +
            "       count(distinct user_id) value\n" +
            "from dws_trade_trademark_category_user_spu_order_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "union all\n" +
            "select '退单数' type,\n" +
            "       sum(refund_count)       value\n" +
            "from dws_trade_trademark_category_user_refund_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "union all\n" +
            "select '退单人数' type,\n" +
            "       count(distinct user_id) value\n" +
            "from dws_trade_trademark_category_user_refund_window\n" +
            "where toYYYYMMDD(stt) = #{date};")
    List<TradeStats> selectTradeStats(@Param("date")Integer date);
}

5)Service层

(1)Service 接口

public interface TradeStatsService {
    
    
    Double getTotalAmount(Integer date);

    List<TradeStats> getTradeStats(Integer date);
}

(2)Service 实现类

@Service
public class TradeStatsServiceImpl implements TradeStatsService {
    
    

    @Autowired
    TradeStatsMapper tradeStatsMapper;

    @Override
    public Double getTotalAmount(Integer date) {
    
    
        return tradeStatsMapper.selectTotalAmount(date);
    }

    @Override
    public List<TradeStats> getTradeStats(Integer date) {
    
    
        return tradeStatsMapper.selectTradeStats(date);
    }
}

6)Controller层

@RestController
@RequestMapping("/gmall/realtime/trade")
public class TradeController {
    
    

    @Autowired
    private TradeStatsService tradeStatsService;

    @RequestMapping("/total")
    public String getTotalAmount(
        @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        Double totalAmount = tradeStatsService.getTotalAmount(date);
        if (totalAmount == null) {
    
    
            return "";
        }
        return "{\n" +
            "  \"status\": 0,\n" +
            "  \"msg\": \"\",\n" +
            "  \"data\": " + totalAmount + "\n" +
            "}";
    }

    @RequestMapping("/stats")
    public String getStats(
        @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<TradeStats> tradeStatsList = tradeStatsService.getTradeStats(date);
        if (tradeStatsList == null) {
    
    
            return "";
        }

        StringBuilder rows = new StringBuilder("[");
        for (int i = 0; i < tradeStatsList.size(); i++) {
    
    
            TradeStats tradeStats = tradeStatsList.get(i);
            String type = tradeStats.getType();
            Integer orderCt = tradeStats.getOrderCt();
            rows.append("{\n" +
                        "\t\"type\": \"" + type + "\",\n" +
                        "\t\"value\": " + orderCt + "\n" +
                        "}\n");
            if (i < tradeStatsList.size() - 1) {
    
    
                rows.append(",");
            } else {
    
    
                rows.append("]");
            }
        }

        return "{\n" +
            "  \"status\": 0,\n" +
            "  \"msg\": \"\",\n" +
            "  \"data\": {\n" +
            "    \"columns\": [\n" +
            "      {\n" +
            "        \"name\": \"指标类型\",\n" +
            "        \"id\": \"type\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"度量值\",\n" +
            "        \"id\": \"value\"\n" +
            "      }\n" +
            "    ],\n" +
            "    \"rows\": " + rows + "\n" +
            "  }\n" +
            "}\n";
    }
}

7)Sugar配置

① 选择数字翻牌器

M

② 数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/trade/total?date=${GMALL_DATE}

③ 效果如下

M

④ 选择轮播表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/trade/stats?date=${GMALL_DATE}

⑤ 效果如下

M

各省份交易统计

1)需求说明

统计周期 统计粒度 指标 说明
当日 省份 订单数
当日 省份 订单金额

2)需求分析

本节将为两个指标各生成一张中国省份色彩地图。

3)数据结构

{
    
    
  "status": 0,
  "msg": "",
  "data": {
    
    
    "mapData": [
      {
    
    
        "name": "内蒙古",
        "value": 12
      },
      {
    
    
        "name": "上海",
        "value": 10
      },
      ...
    ],
    "valueName": "订单数"
  }
}

4)Mapper层

(1)实体类

① 订单数实体类

@Data
@AllArgsConstructor
public class TradeProvinceOrderCt {
    
    
    // 省份名称
    String provinceName;
    // 订单数
    Integer orderCt;
}

② 订单金额实体类

@Data
@AllArgsConstructor
public class TradeProvinceOrderAmount {
    
    
    // 省份名称
    String provinceName;
    // 下单金额
    Double orderAmount;
}

(2)Mapper 接口

在 TradeStatsMapper 接口中补充方法。

@Select("select province_name,\n" +
        "       sum(order_count)  order_count\n" +
        "from dws_trade_province_order_window\n" +
        "where toYYYYMMDD(stt) = #{date}\n" +
        "group by province_id, province_name;")
List<TradeProvinceOrderCt> selectTradeProvinceOrderCt(@Param("date")Integer date);

@Select("select province_name,\n" +
        "       sum(order_amount) order_amount\n" +
        "from dws_trade_province_order_window\n" +
        "where toYYYYMMDD(stt) = #{date}\n" +
        "group by province_id, province_name;")
List<TradeProvinceOrderAmount> selectTradeProvinceOrderAmount(@Param("date")Integer date);

5)Service层

(1)Service 接口

在 TradeStatsService 接口中补充方法。

List<TradeProvinceOrderCt> getTradeProvinceOrderCt(Integer date);

List<TradeProvinceOrderAmount> getTradeProvinceOrderAmount(Integer date);

(2)Service 实现类

在 TradeStatsServiceImpl 实现类中补充方法。

@Override
public List<TradeProvinceOrderCt> getTradeProvinceOrderCt(Integer date) {
    
    
    return tradeStatsMapper.selectTradeProvinceOrderCt(date);
}

@Override
public List<TradeProvinceOrderAmount> getTradeProvinceOrderAmount(Integer date) {
    
    
    return tradeStatsMapper.selectTradeProvinceOrderAmount(date);
}

6)Controller层

在 TradeController 控制类中补充方法。

@RequestMapping("/provinceOrderCt")
public String getProvinceOrderCt(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<TradeProvinceOrderCt> tradeProvinceOrderCtList = tradeStatsService.getTradeProvinceOrderCt(date);
    if (tradeProvinceOrderCtList == null) {
    
    
        return "";
    }
    StringBuilder mapData = new StringBuilder("[");
    for (int i = 0; i < tradeProvinceOrderCtList.size(); i++) {
    
    
        TradeProvinceOrderCt tradeProvinceOrderCt = tradeProvinceOrderCtList.get(i);
        String provinceName = tradeProvinceOrderCt.getProvinceName();
        Integer orderCt = tradeProvinceOrderCt.getOrderCt();
        mapData.append("{\n" +
                       "        \"name\": \"" + provinceName + "\",\n" +
                       "        \"value\": " + orderCt + "\n" +
                       "      }");
        if (i < tradeProvinceOrderCtList.size() - 1) {
    
    
            mapData.append(",");
        } else {
    
    
            mapData.append("]");
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": {\n" +
        "    \"mapData\": " + mapData + ",\n" +
        "    \"valueName\": \"订单数\"\n" +
        "  }\n" +
        "}";
}

@RequestMapping("/provinceOrderAmount")
public String getProvinceOrderAmount(
    @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
    if (date == 1) {
    
    
        date = DateUtil.now();
    }
    List<TradeProvinceOrderAmount> tradeProvinceOrderAmountList = tradeStatsService.getTradeProvinceOrderAmount(date);
    if (tradeProvinceOrderAmountList == null) {
    
    
        return "";
    }
    StringBuilder mapData = new StringBuilder("[");
    for (int i = 0; i < tradeProvinceOrderAmountList.size(); i++) {
    
    
        TradeProvinceOrderAmount tradeProvinceOrderAmount = tradeProvinceOrderAmountList.get(i);
        String provinceName = tradeProvinceOrderAmount.getProvinceName();
        Double orderAmount = tradeProvinceOrderAmount.getOrderAmount();
        mapData.append("{\n" +
                       "        \"name\": \"" + provinceName + "\",\n" +
                       "        \"value\": " + orderAmount + "\n" +
                       "      }");
        if (i < tradeProvinceOrderAmountList.size() - 1) {
    
    
            mapData.append(",");
        } else {
    
    
            mapData.append("]");
        }
    }

    return "{\n" +
        "  \"status\": 0,\n" +
        "  \"msg\": \"\",\n" +
        "  \"data\": {\n" +
        "    \"mapData\": " + mapData + ",\n" +
        "    \"valueName\": \"订单金额\"\n" +
        "  }\n" +
        "}";
}

7)Sugar配置

① 选择中国省份色彩地图

M

② 数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

# 订单数 URL
${GMALL_HOST}/gmall/realtime/trade/provinceOrderCt?date=${GMALL_DATE}
# 订单金额 URL
${GMALL_HOST}/gmall/realtime/trade/provinceOrderAmount?date=${GMALL_DATE}

③ 效果如下

M

M

优惠券主题

当日优惠券补贴率

1)需求说明

统计周期 统计粒度 指标 说明
当日 优惠券 补贴率 用券的订单明细优惠券减免金额总和/原始金额总和

2)需求分析

本节指标使用轮播表格展示。

3)数据结构

前文已有展示,略。

4)Mapper层

(1)实体类

@Data
@AllArgsConstructor
public class CouponReduceStats {
    
    
    // 优惠券减免金额
    Double couponReduceAmount;
    // 原始金额
    Double originTotalAmount;
    // 优惠券补贴率
    Double couponSubsidyRate;
}

(2)Mapper 接口

public interface CouponStatsMapper {
    
    
    @Select("select sum(order_coupon_reduce_amount)                     coupon_reduce_amount,\n" +
            "       sum(order_origin_total_amount)                      origin_total_amount,\n" +
            "       round(round(toFloat64(coupon_reduce_amount), 5) /\n" +
            "             round(toFloat64(origin_total_amount), 5), 20) coupon_subsidy_rate\n" +
            "from dws_trade_order_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt);")
    List<CouponReduceStats> selectCouponStats(@Param("date")Integer date);
}

5)Service层

(1)Service 接口

public interface CouponStatsService {
    
    
    List<CouponReduceStats> getCouponStats(Integer date);
}

(2)Service 实现类

@Service
public class CouponStatsServiceImpl implements CouponStatsService {
    
    

    @Autowired
    private CouponStatsMapper couponStatsMapper;

    @Override
    public List<CouponReduceStats> getCouponStats(Integer date) {
    
    
        return couponStatsMapper.selectCouponStats(date);
    }
}

6)Controller层

@RestController
@RequestMapping("gmall/realtime/coupon")
public class CouponStatsController {
    
    

    @Autowired
    private CouponStatsService couponStatsService;

    @RequestMapping("/stats")
    public String getCouponStats(
        @RequestParam(value = "date", defaultValue = "1") Integer date){
    
    
        if(date == 1) {
    
    
            date = DateUtil.now();
        }
        List<CouponReduceStats> couponStatsList = couponStatsService.getCouponStats(date);
        if(couponStatsList == null) {
    
    
            return "";
        }
        StringBuilder rows = new StringBuilder("[");
        for (int i = 0; i < couponStatsList.size(); i++) {
    
    
            CouponReduceStats couponReduceStats = couponStatsList.get(i);
            Double couponReduceAmount = couponReduceStats.getCouponReduceAmount();
            Double originTotalAmount = couponReduceStats.getOriginTotalAmount();
            Double couponSubsidyRate = couponReduceStats.getCouponSubsidyRate();
            rows.append("{\n" +
                        "        \"couponReduceAmount\": "+ couponReduceAmount +",\n" +
                        "        \"originTotalAmount\": "+ originTotalAmount +",\n" +
                        "        \"couponSubsidyRate\": "+ couponSubsidyRate +"\n" +
                        "      }");
            if(i < couponStatsList.size() - 1) {
    
    
                rows.append(",");
            } else {
    
    
                rows.append("]");
            }
        }

        return "{\n" +
            "  \"status\": 0,\n" +
            "  \"msg\": \"\",\n" +
            "  \"data\": {\n" +
            "    \"columns\": [\n" +
            "      {\n" +
            "        \"name\": \"优惠券减免金额\",\n" +
            "        \"id\": \"couponReduceAmount\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"原始金额总和\",\n" +
            "        \"id\": \"originTotalAmount\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"name\": \"优惠券补贴率\",\n" +
            "        \"id\": \"couponSubsidyRate\"\n" +
            "      }\n" +
            "    ],\n" +
            "    \"rows\": "+ rows +"\n" +
            "  }\n" +
            "}";
    }
}

7)Sugar配置

① 选择轮播表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/coupon/stats?date=${GMALL_DATE}

② 效果如下

M

活动主题

当日活动补贴率

1)需求说明

统计周期 统计粒度 指标 说明
当日 活动 补贴率 参与促销活动的订单明细活动减免金额总和/原始金额总和

2)需求分析

本节指标使用轮播表格展示。

3)数据结构

略。

4)Mapper 层

(1)实体类

@Data
@AllArgsConstructor
public class ActivityReduceStats {
    
    
    // 活动减免金额
    Double activityReduceAmount;
    // 原始金额
    Double originTotalAmount;
    // 活动补贴率
    Double activitySubsidyRate;
}

(2)Mapper 接口

public interface ActivityStatsMapper {
    
    
    @Select("select sum(order_activity_reduce_amount)                   activity_reduce_amount,\n" +
            "       sum(order_origin_total_amount)                      origin_total_amount,\n" +
            "       round(round(toFloat64(activity_reduce_amount), 5) /\n" +
            "             round(toFloat64(origin_total_amount), 5), 20) subsidyRate\n" +
            "from dws_trade_order_window\n" +
            "where toYYYYMMDD(stt) = #{date}\n" +
            "group by toYYYYMMDD(stt);")
    List<ActivityReduceStats> selectActivityStats(@Param(value = "date")Integer date);
}

5)Service层

(1)Service 接口

public interface ActivityReduceService {
    
    
    List<ActivityReduceStats> getActivityStats(Integer date);
}

(2)Service 实现类

@Service
public class ActivityReduceServiceImpl implements ActivityReduceService {
    
    

    @Autowired
    private ActivityStatsMapper activityStatsMapper;

    @Override
    public List<ActivityReduceStats> getActivityStats(Integer date) {
    
    
        return activityStatsMapper.selectActivityStats(date);
    }
}

6)Controller层

@RestController
@RequestMapping("/gmall/realtime/activity")
public class ActivityStatsController {
    
    

    @Autowired
    private ActivityReduceService activityReduceService;

    @RequestMapping("/stats")
    public String getActivityStats(
            @RequestParam(value = "date", defaultValue = "1") Integer date) {
    
    
        if (date == 1) {
    
    
            date = DateUtil.now();
        }
        List<ActivityReduceStats> activityStatsList = activityReduceService.getActivityStats(date);
        if (activityStatsList == null) {
    
    
            return "";
        }
        StringBuilder rows = new StringBuilder("[");
        for (int i = 0; i < activityStatsList.size(); i++) {
    
    
            ActivityReduceStats activityReduceStats = activityStatsList.get(i);
            Double activityReduceAmount = activityReduceStats.getActivityReduceAmount();
            Double originTotalAmount = activityReduceStats.getOriginTotalAmount();
            Double activitySubsidyRate = activityReduceStats.getActivitySubsidyRate();
            rows.append("{\n" +
                    "    \t\"activityReduceAmount\": " + activityReduceAmount + ",\n" +
                    "    \t\"originTotalAmount\": " + originTotalAmount + ",\n" +
                    "    \t\"activitySubsidyRate\": " + activitySubsidyRate + "\n" +
                    "    }");
            if (i < activityStatsList.size() - 1) {
    
    
                rows.append(",");
            } else {
    
    
                rows.append("]");
            }
        }

        return "{\n" +
                "  \"status\": 0,\n" +
                "  \"msg\": \"\",\n" +
                "  \"data\": {\n" +
                "    \"columns\": [\n" +
                "      {\n" +
                "        \"name\": \"活动减免金额\",\n" +
                "        \"id\": \"activityReduceAmount\"\n" +
                "      },\n" +
                "      {\n" +
                "        \"name\": \"原始金额总和\",\n" +
                "        \"id\": \"originTotalAmount\"\n" +
                "      },\n" +
                "      {\n" +
                "        \"name\": \"活动补贴率\",\n" +
                "        \"id\": \"activitySubsidyRate\"\n" +
                "      }\n" +
                "    ],\n" +
                "    \"rows\": " + rows + "\n" +
                "  }\n" +
                "}";
    }
}

7)Sugar配置

① 选择轮播表格,数据绑定方式为 “API 拉取”,输入数据接口的 URL,如下。

${GMALL_HOST}/gmall/realtime/activity/stats?date=${GMALL_DATE}

② 效果如下

M

猜你喜欢

转载自blog.csdn.net/qq_44766883/article/details/131001784