【实时数仓】DWS层的定位、DWS层之访客主题计算(PV、UV、跳出次数、计入页面数、连续访问时长)

news2025/6/26 10:37:22

文章目录

  • 一 DWS层与DWM层的设计
    • 1 设计思路
    • 2 需求梳理
    • 3 DWS层定位
  • 二 DWS层-访客主题计算
    • 1 需求分析与思路
    • 2 功能实现
      • (1)封装VisitorStatsApp,读取Kafka各个流数据
        • a 代码
        • b 测试
      • (2)合并数据流
        • a 封装主题宽表实体类VisitorStats
        • b 对读取的各个数据流进行结构的转换
          • 读取到的jsonStr格式如下
          • 代码如下
          • 输出结果
        • c 根据维度进行聚合
          • 思路分析
          • 代码实现
          • 输出结果
      • (3)写入OLAP数据库
        • a ClickHouse数据表准备
        • b 加入ClickHouse依赖包

一 DWS层与DWM层的设计

1 设计思路

在之前通过分流等手段,把数据分拆成了独立的kafka topic。那么接下来如何处理数据,就要思考一下到底要通过实时计算出哪些指标项。

因为实时计算与离线不同,实时计算的开发和运维成本都是非常高的,要结合实际情况考虑是否有必要像离线数仓一样,建一个大而全的中间层。

如果没有必要大而全,这时候就需要大体规划一下要实时计算出的指标需求了。把这些指标以主题宽表的形式输出就形成了DWS层。

2 需求梳理

当然除以下需求,实际需求还会有更多,这里主要以为可视化大屏为目的进行实时计算的处理。

统计主题需求指标输出方式计算来源来源层级
访客pv可视化大屏page_log直接可求dwd
uv可视化大屏需要用page_log过滤去重dwm
跳出次数可视化大屏需要通过page_log行为判断dwm
进入页面数可视化大屏需要识别开始访问标识dwd
连续访问时长可视化大屏page_log直接可求dwd
商品点击多维分析page_log直接可求dwd
曝光多维分析page_log直接可求dwd
收藏多维分析收藏表dwd
加入购物车多维分析购物车表dwd
下单可视化大屏订单宽表dwm
支付多维分析支付宽表dwm
退款多维分析退款表dwd
评论多维分析评论表dwd
地区pv多维分析page_log直接可求dwd
uv多维分析需要用page_log过滤去重dwm
下单可视化大屏订单宽表dwm
关键词搜索关键词可视化大屏页面访问日志 直接可求dwd
点击商品关键词可视化大屏商品主题下单再次聚合dws
下单商品关键词可视化大屏商品主题下单再次聚合dws

3 DWS层定位

轻度聚合,因为DWS层要应对很多实时查询,如果是完全的明细,查询的压力是非常大的。

将更多的实时数据以主题的方式组合起来便于管理,同时也能减少维度查询的次数。

二 DWS层-访客主题计算

统计主题需求指标输出方式计算来源来源层级
访客pv可视化大屏page_log直接可求dwd
uv可视化大屏需要用page_log过滤去重dwm
跳出次数可视化大屏需要通过page_log行为判断dwm
进入页面数可视化大屏需要识别开始访问标识dwd
连续访问时长可视化大屏page_log直接可求dwd

设计一张DWS层的表其实就两件事:维度和度量(事实数据)

  • 度量包括PV、UV、跳出次数、进入页面数(session_count)、连续访问时长
  • 维度包括在分析中比较重要的几个字段:渠道、地区、版本、新老用户进行聚合

1 需求分析与思路

  • 接收各个明细数据,变为数据流。
  • 把数据流合并在一起,成为一个相同格式对象的数据流。
  • 对合并的流进行聚合,聚合的时间窗口决定了数据的时效性。
  • 把聚合结果写在数据库中。

为了将三条流合并到一起,需要定义一个实体类VisitorStats【渠道、地区、版本、新老用户、PV、UV、跳出次数、进入页面数(session_count)、连续访问时长】

dwd_page_log
	new VisitorStats(渠道、地区、版本、新老用户、1L、0L、0L、1L、XXXXL)

dwm_unique_visitor
	new VisitorStats(渠道、地区、版本、新老用户、0L、1L、0L、0L、0L)

dwm_user_jump_detail
	new VisitorStats(渠道、地区、版本、新老用户、0L、0L、1L、0L、0L)

整体流程如下:

在这里插入图片描述

2 功能实现

(1)封装VisitorStatsApp,读取Kafka各个流数据

a 代码

package com.hzy.gmall.realtime.app.dws;
/**
 * 访客主题统计dws
 */
public class VisitorStatsApp {
    public static void main(String[] args) throws Exception {
        // TODO 1 基本环境准备
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(4);

        // TODO 2 从kafka中读取数据
        // 2.1 声明读取的主题和消费者组
        String pageViewSourceTopic = "dwd_page_log";
        String uniqueVisitSourceTopic = "dwm_unique_visitor";
        String userJumpDetailSourceTopic = "dwm_user_jump_detail";
        String groupId = "visitor_stats_app";

        // 2.2 获取kafka消费者
        FlinkKafkaConsumer<String> pvSource = MyKafkaUtil.getKafkaSource(pageViewSourceTopic, groupId);
        FlinkKafkaConsumer<String> uvSource = MyKafkaUtil.getKafkaSource(uniqueVisitSourceTopic, groupId);
        FlinkKafkaConsumer<String> ujdSource = MyKafkaUtil.getKafkaSource(userJumpDetailSourceTopic, groupId);

        // 2.3 读取数据,封装成流
        DataStreamSource<String> pvStrDS = env.addSource(pvSource);
        DataStreamSource<String> uvStrDS = env.addSource(uvSource);
        DataStreamSource<String> ujdStrDS = env.addSource(ujdSource);

        pvStrDS.print("1111");
        uvStrDS.print("2222");
        ujdStrDS.print("3333");

        env.execute();
    }
}

b 测试

启动zookeeper,kafka,logger.sh,BaseLogApp、UnionVistorApp、UserJumpDetailAPP、VisitorStatsApp,执行模拟用户行为日志生成脚本,查看是否包含以上三中类型信息。

(2)合并数据流

把数据流合并在一起,成为一个相同格式对象的数据流

合并数据流的核心算子是union。但是union算子,要求所有的数据流结构必须一致。所以union前要调整数据结构。

a 封装主题宽表实体类VisitorStats

package com.hzy.gmall.realtime.beans;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * Desc: 访客统计实体类  包括各个维度和度量
 */
@Data
@AllArgsConstructor
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 ts;
}

b 对读取的各个数据流进行结构的转换

读取到的jsonStr格式如下
{
    "common": {
        "ar": "530000",
        "uid": "9",
        "os": "Android 11.0",
        "ch": "vivo",
        "is_new": "1",
        "md": "Xiaomi Mix2 ",
        "mid": "mid_6",
        "vc": "v2.1.134",
        "ba": "Xiaomi"
    },
    "page": {
        "page_id": "home",
        "item":"9",
        "during_time":15839,
        "item_type":"sku_id",
        "last_page_id":"home",
        "source_type":"query"
    },
    "displays": [
        {
            "display_type": "activity",
            "item": "1",
            "item_type": "activity_id",
            "pos_id": 5,
            "order": 1
        }
    ],
    "ts": 1670913783000
}
代码如下
// TODO 3 对流中的数据进行类型转换 jsonStr -> VisitorStats
// 3.1 dwd_page_loge流中数据的转化
SingleOutputStreamOperator<VisitorStats> pvStatsDS = pvStrDS.map(
        new MapFunction<String, VisitorStats>() {
            @Override
            public VisitorStats map(String jsonStr) throws Exception {
                JSONObject jsonObj = JSON.parseObject(jsonStr);
                JSONObject commonJsonObj = jsonObj.getJSONObject("common");
                JSONObject pageJsonObj = jsonObj.getJSONObject("page");
                VisitorStats visitorStats = new VisitorStats(
                        "",
                        "",
                        commonJsonObj.getString("vc"),
                        commonJsonObj.getString("ch"),
                        commonJsonObj.getString("ar"),
                        commonJsonObj.getString("is_new"),
                        0L,
                        1L,
                        0L,
                        0L,
                        pageJsonObj.getLong("during_time"),
                        jsonObj.getLong("ts")
                );
                // 判断是否为新的会话,是则sessionViewCount + 1
                String lastPageId = pageJsonObj.getString("last_page_id");
                if (lastPageId == null || lastPageId.length() == 0) {
                    visitorStats.setSv_ct(1L);
                }
                return visitorStats;
            }
        }
);
// 3.2 dwm_unique_visitor流中数据的转化
SingleOutputStreamOperator<VisitorStats> uvStatsDS = uvStrDS.map(
        new MapFunction<String, VisitorStats>() {
            @Override
            public VisitorStats map(String jsonStr) throws Exception {
                JSONObject jsonObj = JSON.parseObject(jsonStr);
                JSONObject commonJsonObj = jsonObj.getJSONObject("common");
                VisitorStats visitorStats = new VisitorStats(
                        "",
                        "",
                        commonJsonObj.getString("vc"),
                        commonJsonObj.getString("ch"),
                        commonJsonObj.getString("ar"),
                        commonJsonObj.getString("is_new"),
                        1L,
                        0L,
                        0L,
                        0L,
                        0L,
                        jsonObj.getLong("ts")
                );
                return visitorStats;
            }
        }
);
// 3.3 dwm_user_jump_detail流中数据的转化
SingleOutputStreamOperator<VisitorStats> ujdStatsDS = ujdStrDS.map(
        new MapFunction<String, VisitorStats>() {
            @Override
            public VisitorStats map(String jsonStr) throws Exception {
                JSONObject jsonObj = JSON.parseObject(jsonStr);
                JSONObject commonJsonObj = jsonObj.getJSONObject("common");
                VisitorStats visitorStats = new VisitorStats(
                        "",
                        "",
                        commonJsonObj.getString("vc"),
                        commonJsonObj.getString("ch"),
                        commonJsonObj.getString("ar"),
                        commonJsonObj.getString("is_new"),
                        0L,
                        0L,
                        0L,
                        1L,
                        0L,
                        jsonObj.getLong("ts")
                );
                return visitorStats;
            }
        }
);
// TODO 4 将三条流转换后的数据进行合并
DataStream<VisitorStats> unionDS = pvStatsDS.union(uvStatsDS, ujdStatsDS);

unionDS.print(">>>");
输出结果
VisitorStats(stt=, edt=, vc=v2.1.134, ch=xiaomi, ar=110000, is_new=0, uv_ct=0, pv_ct=1, sv_ct=0, uj_ct=0, dur_sum=8283, ts=1670918057000)

c 根据维度进行聚合

思路分析

因为涉及开窗聚合,所以要设定事件时间及水位线。

是否需要将多个明细的同样的维度统计在一起:

  • 因为单位时间内mid的操作数据非常有限不能明显的压缩数据量(如果是数据量够大,或者单位时间够长可以)。
  • 所以用常用统计的四个维度进行聚合:渠道、新老用户、app版本、省市区域。
  • 度量值包括 :启动、日活(当日首次启动)、访问页面数、新增用户数、跳出数、平均页面停留时长、总访问时长。
  • 聚合窗口: 10秒。
代码实现
// TODO 5 指定Watermark以及提取事件时间字段
SingleOutputStreamOperator<VisitorStats> visitorStatsWithWatermarkDS = unionDS.assignTimestampsAndWatermarks(
        WatermarkStrategy.<VisitorStats>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                .withTimestampAssigner(
                        new SerializableTimestampAssigner<VisitorStats>() {
                            @Override
                            public long extractTimestamp(VisitorStats visitorStats, long recordTimestamp) {
                                return visitorStats.getTs();
                            }
                        }
                )
);

// TODO 6 按照维度对流中的数据进行分组
//维度有:版本,渠道,地区,新老访客 定义分组的key为Tuple4类型
KeyedStream<VisitorStats, Tuple4<String, String, String, String>> keyedDS = visitorStatsWithWatermarkDS.keyBy(
        new KeySelector<VisitorStats, Tuple4<String, String, String, String>>() {
            @Override
            public Tuple4<String, String, String, String> getKey(VisitorStats visitorStats) throws Exception {
                return Tuple4.of(
                        visitorStats.getVc(),
                        visitorStats.getCh(),
                        visitorStats.getAr(),
                        visitorStats.getIs_new()
                );
            }
        }
);

// TODO 7 对分组之后的数据,进行开窗处理
// 每个分组是独立的窗口,分组之间互不影响
WindowedStream<VisitorStats, Tuple4<String, String, String, String>, TimeWindow> windowDS = keyedDS.window(TumblingEventTimeWindows.of(Time.seconds(10)));

// TODO 8 聚合计算
// 对窗口中的数据进行两两聚合计算
SingleOutputStreamOperator<VisitorStats> reduceDS = windowDS.reduce(
        new ReduceFunction<VisitorStats>() {
            @Override
            public VisitorStats reduce(VisitorStats stats1, VisitorStats stats2) throws Exception {
                // 度量值进行两两相加
                stats1.setPv_ct(stats1.getPv_ct() + stats2.getPv_ct());
                stats1.setUv_ct(stats1.getUv_ct() + stats2.getUv_ct());
                stats1.setSv_ct(stats1.getSv_ct() + stats2.getSv_ct());
                stats1.setDur_sum(stats1.getDur_sum() + stats2.getDur_sum());
                stats1.setUj_ct(stats1.getUj_ct() + stats2.getUj_ct());
                return stats1;
            }
        },
        new ProcessWindowFunction<VisitorStats, VisitorStats, Tuple4<String, String, String, String>, TimeWindow>() {
            @Override
            public void process(Tuple4<String, String, String, String> Tuple4, Context context, Iterable<VisitorStats> elements, Collector<VisitorStats> out) throws Exception {
                // 补全时间字段的值
                for (VisitorStats visitorStats : elements) {
                    visitorStats.setStt(DateTimeUtil.toYMDHMS(new Date(context.window().getStart())));
                    visitorStats.setEdt(DateTimeUtil.toYMDHMS(new Date(context.window().getEnd())));
                    // 操作时间为当前系统时间
                    visitorStats.setTs(System.currentTimeMillis());
                    // 将处理之后的数据向下游发送
                    out.collect(visitorStats);
                }
            }
        }
);

reduceDS.print(">>>");
输出结果
>>>:1> VisitorStats(stt=2022-12-13 16:48:40, edt=2022-12-13 16:48:50, vc=v2.1.134, ch=Appstore, ar=500000, is_new=0, uv_ct=0, pv_ct=8, sv_ct=1, uj_ct=0, dur_sum=77934, ts=1670921334767)
>>>:2> VisitorStats(stt=2022-12-13 16:48:40, edt=2022-12-13 16:48:50, vc=v2.1.134, ch=xiaomi, ar=110000, is_new=0, uv_ct=1, pv_ct=7, sv_ct=1, uj_ct=0, dur_sum=78835, ts=1670921334767)

(3)写入OLAP数据库

ClickHouse官网。

# 启动ClickHouse
sudo systemctl start clickhouse-server
# 启动客户端
clickhouse-client -m

为何要写入ClickHouse数据库,ClickHouse数据库作为专门解决大量数据统计分析的数据库,在保证了海量数据存储的能力,同时又兼顾了响应速度。而且还支持标准SQL,即灵活又易上手。

ClickHouse数据库的详细安装及入门,请参考。

a ClickHouse数据表准备

create table  
visitor_stats_2022 (
        stt DateTime,
        edt DateTime,
        vc  String,
        ch  String ,
        ar  String ,
        is_new String ,
        uv_ct UInt64,
        pv_ct UInt64,
        sv_ct UInt64,
        uj_ct UInt64,
        dur_sum  UInt64,
        ts UInt64
        ) engine = ReplacingMergeTree(ts)
        partition by  toYYYYMMDD(stt)
        order by  (stt,edt,is_new,vc,ch,ar);

之所以选用ReplacingMergeTree引擎主要是靠它来保证数据表的幂等性。

  • paritition by 把日期变为数字类型(如:20201126),用于分区。所以尽量保证查询条件尽量包含stt字段。
  • order by 后面字段数据在同一分区下,出现重复会被去重,重复数据保留ts最大的数据。
  • 建表语句中字段顺序和名字要与VisitorStats的属性顺序和名字要保持一致。

b 加入ClickHouse依赖包

<dependency>
    <groupId>ru.yandex.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.3.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </exclusion>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-jdbc_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

其中flink-connector-jdbc 是官方通用的jdbcSink包。只要引入对应的jdbc驱动,flink可以用它应对各种支持jdbc的数据库,比如phoenix也可以用它。但是这个jdbc-sink只支持数据流对应一张数据表。如果是一流对多表,就必须通过自定义的方式实现了,比如之前的维度数据。

虽然这种jdbc-sink只能一流对一表,但是由于内部使用了预编译器,所以可以实现批量提交以优化写入速度。

目前已完成功能如下图:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/110206.html

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

相关文章

CMakeList

目录 .1 简介 .2 常用命令 2.1 指定 cmake 的最小版本 2.2 设置项目名称 2.3 设置编译类型 2.4 指定编译包含的源文件 2.4.1 明确指定包含哪些源文件 2.4.2 搜索所有的 cpp 文件 2.4.3自定义搜索规则 2.5 查找指定的库文件 2.6. 设置包含的目录 2.7. 设置链接库搜索…

中国晶振市场规模将增长至2026年的263.21亿元,国产市场未来可期

晶振作为频率控制和频率选择基础元件&#xff0c;广泛应用于资讯设备、移动终端、通信及网络设备、汽车电子、智能电表、电子银行口令卡等领域&#xff0c;随着新兴电子产业、物联网的快速发展&#xff0c;及以 5G、蓝牙 5.0、Wi-Fi 6 等无线通信新技术的广泛应用&#xff0c;预…

基于Python的Flask WEB框架实现后台权限管理系统(含数据库),内容包含:用户管理、角色管理、资源管理和机构管理

#基于Flask实现后台权限管理系统 重磅&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 全新的风格界面&#xff0c;完全的前后端分离。基于ElementUI&#xff0c;前端代码基于RuoYi…

plotly parallel_coordinates平行坐标可视化

使用plotly画平行坐标图&#xff0c;代码如下&#xff1a; 其中数据使用excel的csv格式&#xff08;当然可以使用其它格式&#xff09;&#xff0c;csv的标头是参数名。 import plotly.express as px import numpy as np import pandas as pd# df px.data.iris() df pd.read…

【爬虫】JS逆向解决反爬问题系列3—sign破解

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…

DOM(一):获取页面元素、操作元素

DOM&#xff08;一&#xff09;获取页面元素事件操作元素获取页面元素 1.根据ID获取 使用getElementById()方法获取带有ID的元素对象,格式如下&#xff1a; var 变量名 document.getElementById(‘id名’); 例如&#xff1a; <div id time>2022-12-18</div> &…

PyTorch——应用一个已训练好的图片分类网络——AlexNet

1.识别一个图像主体的预训练网络 ImageNet数据集是由一个Stanford大学维护的包含1400多万幅图像的非常大的数据集&#xff0c;所有的图像都用来自WordNet数据集的名词层次结构标记&#xff0c;而WordNet数据集又是一个大型的英语词汇数据库。 1.1获取一个预先训练好的网络用于…

【矩阵论】7.范数理论——基本概念——矩阵范数生成向量范数谱范不等式

7.1.3 矩阵范数产生向量范数 CnnC^{n\times n}Cnn 上任一矩阵范数 ∥∙∥\Vert \bullet\Vert∥∙∥ 都产生一个向量范数 φ(X)∥X∥V\varphi(X)\Vert X\Vert_Vφ(X)∥X∥V​ 矩阵范数与向量范数的相容性&#xff1a;φ(Ax)≤∥A∥φ(x)\varphi(Ax)\le \Vert A\Vert\varphi(x)φ…

蓝牙耳机什么牌子好?音质好、配置高的蓝牙耳机分享

​经常听到很多网友在讨论哪些蓝牙耳机好用&#xff0c;什么蓝牙耳机的配置高......选购蓝牙耳机无非就是音质、蓝牙技术、续航、佩戴体验等各方面条件&#xff0c;还有哪位朋友不知道该如何选购蓝牙耳机的&#xff1f;根据我对蓝牙耳机的了解&#xff0c;从网上整理了好几款蓝…

【关于eps8266自动重启 Soft WDT reset】

【关于eps8266自动重启 Soft WDT reset】1. 前言2. 分析问题2.1 长时间没有喂狗2.2 delayMicroseconds 函数触发3. 解决问题3.1 解决长时间没有喂狗3.2 解决delayMicroseconds 函数触发5. 小结1. 前言 最近使用esp8266进行远程遥控时, 但是在驱动舵机servo库的过程中出现了esp…

第二证券|两大板块掀涨停潮,有个股猛拉20cm!这只港股复牌一度暴跌

A股商场周五上午窄幅震动&#xff0c;上证指数微涨0.09点&#xff0c;核算机等板块领涨。 新股体现持续分解&#xff0c;4只今日上市的新股中&#xff0c;两只科创板新股上涨&#xff0c;两只北交所新股则破发。 港股全体小幅调整&#xff0c;全体动摇不算大&#xff0c;但仍…

【推荐】智慧农业解决方案资料合集30篇

智慧农业 是农业中的智慧经济&#xff0c;智慧经济形态在农业中的具体表现。智慧农业是智慧经济重要的组成部分&#xff1b;对于发展中国家而言&#xff0c;智慧农业是智慧经济主要的组成部分&#xff0c;是发展中国家消除贫困、实现后发优势、经济发展后来居上、实现赶超战略的…

16含风光水的虚拟电厂与配电公司协调调度模型(场景削减MATLAB程序)

参考文献 含风光水的虚拟电厂与配电公司协调调度模型——董文略&#xff08;复现场景削减部分&#xff09; 主要内容 代码主要做的是风电、光伏以及电价场景不确定性模拟&#xff0c;首先由一组确定性的方案&#xff0c;生成50种光伏场景&#xff0c;为了避免大规模光伏场景…

小结 | 支持向量机 (SVM)

一.基本原理 SVM是一种二分类模型 基本思想&#xff1a;在特征空间中寻找间隔最大的分离超平面使数据得到高效的二分类&#xff0c;具体来讲&#xff0c;有三种情况&#xff08;不加核函数的话就是线性模型&#xff0c;加了之后才会升级为一个非线性模型&#xff09; 当训练…

MybatisPlus详解 | DQLDML快速开发... | 系统性学习 | 无知的我费曼笔记

无知的我正在复盘MybatisPlus&#xff0c;顺便上传笔记。。。 下图是我总结的 MP 知识的初级思维导图&#xff0c;后续会不断补充 文章目录1 MyBatisPlus入门案例与简介1.1 入门案例回顾SpringBoot整合Mybatis的开发过程:步骤1:创建数据库及表步骤2:创建SpringBoot工程步骤…

二苯并环辛炔-聚乙二醇-生物素DBCO-PEG-Biotin简介 DBCO-PEG-Biotin衍生物可以在没有金属催化剂(铜离子)作用下发生点击化学反应

中文名&#xff1a;二苯并环辛炔-聚乙二醇-生物素&#xff0c;生物素-聚乙二醇环辛炔 英文名&#xff1a;DBCO-PEG-Biotin&#xff0c;Biotin-PEG-DBCO 溶剂&#xff1a;溶于大部分有机溶剂&#xff0c;如&#xff1a;DCM、DMF、DMSO、THF等等&#xff0c;在水中有很好的溶解性…

变分自编码器VAE的数学原理

变分自编码器(VAE)是一种应用广泛的无监督学习方法&#xff0c;它的应用包括图像生成、表示学习和降维等。虽然在网络架构上经常与Auto-Encoder联系在一起&#xff0c;但VAE的理论基础和数学公式是截然不同的。本文将讨论是什么让VAE如此不同&#xff0c;并解释VAE如何连接“变…

【JVS低代码】一分钟学会如何快速创建应用

应用中心功能介绍 在JVS角色中有“应用管理员”的角色&#xff0c;如果赋予该角色&#xff0c;则用户为应用管理员&#xff0c;应用管理员有进入应用配置中心的权限。 创建空白应用&#xff1a; 从模板创建应用&#xff1a; 应用中心入口 具备应用管理员权限的用户&#xff…

嵌入式软件开发为什么需要DevOps?

DevOps提出至今已逾十年&#xff0c;作为热门概念&#xff0c;DevOps近年来频频出现于各大技术社区和媒体文章中&#xff0c;备受行业大咖追捧。作为新一代持续集成/持续开发&#xff08;CI/CD&#xff09;的方法论&#xff0c;DevOps正被快速引入嵌入式软件开发领域中。 为什…

【C++】类和对象(C++门槛)

唯有奋力奔跑&#xff0c;才能方得始终。 文章目录一、struct > class(类 方法/成员函数 属性/成员变量)1.自定义类型 struct 和 class 的区别2.类放在内存中的什么存储区&#xff1f;3.类中函数的两种定义方式3.1 声明和定义分离&#xff08;增强代码可读性&#xff0c;…