之前数据分层处理,最后把轻度聚合的结果保存到 ClickHouse 中,主要的目的就是提供即时的数据查询、统计、分析服务。这些统计服务一般会用两种形式展现,一种是为专业的数据分析人员的 BI 工具,一种是面向非专业人员的更加直观的数据大屏。
以下主要是面向百度的 sugar 的数据大屏服务的接口开发。
在可视化大屏中每个组件都需要一个单独的接口,图中一共涉及 8 个组件。
之前我们实现了 DWS 层计算后写入到 ClickHouse 中,接下来就是要为可视化大屏服务,提供一个数据接口用来查询 ClickHouse 中的数据。这里主要有两项工作
➢ 配置可视化大屏服务。
➢ 编写数据查询接口以供可视化大屏进行访问。
Sugar 是百度云推出的敏捷 BI 和数据可视化平台,目标是解决报表和大屏的数据 BI 分析和可视化问题,解放数据可视化系统的开发人力。
https://cloud.baidu.com/product/sugar.html
从大屏的编辑器上方选择【指标】→【数字翻牌器】
点击组件,在右侧的菜单中选择【数据】,绑定方式改为【API 拉取】
下方的路径填写 $API_HOST/api/sugar/gmv
这个就是 sugar 会周期性访问的数据接口地址,可以自定义,其中$API_HOST 是个全局变量,需要在空间中配置(后面再说)。
在数据绑定的位置选择【静态 JSON】,可以看到数据需要的 JSON 格式
➢ 访问路径
/api/sugar/gmv
➢ 返回格式
{
"status": 0,
"msg": "",
"data": 1201081.1632389291
}
<?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>
server.port=8070
#配置 ClickHouse 驱动以及 URL
spring.datasource.driver-class-name=ru.yandex.clickhouse.ClickHouseDriver
spring.datasource.url=jdbc:clickhouse://hadoop102:8123/default
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);
}
}
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);
}
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);
}
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);
}
}
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);
}
}
通常个人电脑无论是连接 WIFI 上网还是用网线上网,都是属于局域网里边的,外网无法直接访问到你的电脑,内网穿透可以让你的局域网中的电脑实现被外网访问功能。
目前国内网穿透工具很多,常见的比如花生壳、Ngrok、网云穿等。
官网:
花生壳:https://hsk.oray.com
Ngrok: http://www.ngrok.cc
网云穿:http://www.neiwangchuantou.net/
回到 Sugar 的空间管理中,在【空间设置】中增加$API_HOST
然后回到大屏配置中,刷新图表数据,能看到数字已经显示
➢ 品牌,水平柱状图
➢ 品类,饼形图
➢ 商品 spu,轮播图
这三个的共同特征是可以根据商品统计信息计算出来。
{
"status": 0,
"msg": "",
"data": {
"categories": [
"苹果",
"三星",
"华为",
"oppo",
"vivo",
"小米62"
],
"series": [
{
"name": "手机品牌",
"data": [
9922,
5774,
5323,
8043,
7511,
6487
]
}
]
}
}
{
"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
}
]
}
{
"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"
}
]
}
}
这三个图基本上都是根据用不同维度进行分组,金额进行聚合的方式查询商品统计表。
直接先实现三个 sql 查询
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;
}
//统计某天不同 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);
//统计某天不同 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);
@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);
}
注意:Controller 方法的定义必须依照,定好的接口访问路径和返回值格式。
@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();
}
@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();
}
@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;
}
➢ 启动 ZK、Kafka、ClickHouse、Redis、HDFS、Hbase、Maxwell
➢ 运行 BaseDBApp
➢ 运行 OrderWideApp
➢ 运行 ProductsStatsApp
➢ 运行 rt_dblog 目录下的 jar 包
➢ 查看 ClickHouse 中 products_stats_2021 表数据
在上方地图栏位中选择【中国省份色彩】
➢ 访问路径
${API_HOST}/api/sugar/province
➢ 返回值格式
{
"status": 0,
"data": {
"mapData": [
{
"name": "北京",
"value": 9131
},
{
"name": "天津",
"value": 5740
}
]
}
}
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;
}
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);
}
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);
}
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);
}
}
@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();
}
修改获取数据的方式,指定访问路径
访问路径: $API_HOST/api/sugar/visitor
查看返回值数据格式
{
"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
}
]
}
}
{
"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
]
}
]
}
}
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;
}
}
}
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);
}
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);
}
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);
}
}
@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;
}
@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.3 刷新大屏组件数据
在上方【文字】栏位中选择【字符云】
访问路径:${API_HOST}/api/sugar/keyword
➢ 访问路径
${API_HOST}/api/sugar/keyword
➢ 返回值格式
{
"status": 0,
"data": [{
"name": "data",
"value": 60679,
},
{
"name": "dataZoom",
"value": 24347,
}
]
}
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;
}
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};
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);
}
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);
}
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);
}
}
@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();
}
数据接口部分开发的重点:
➢ 学会通过 springboot 搭建一个 web 服务。
➢ 学会在 Web 服务使用注解方式,通过 SQL 语句查询 ClickHouse。
➢ 学会通过 Sugar 实现数据大屏可视化配置,了解其中的地图、柱形图、饼图、折线图、
表格、轮播表、字符云等组件的使用预配置。
➢ 学会使用内网穿透工具,方便调试本地接口与互联网服务对接