实时步数统计系统 kafka + spark +redis

news2025/4/25 16:40:43

基于微服务架构设计并实现了一个实时步数统计系统,采用生产者-消费者模式,利用Kafka实现消息队列,Spark Streaming处理实时数据流,Redis提供高性能数据存储,实现了一个高并发、低延迟的数据处理系统,支持多用户运动数据的实时采集、传输、处理和统计分析。

1.介绍

1.数据采集与生产者(StepDataProducer)
  • 作用:负责生成用户步数数据并发送到Kafka主题。
  • 原理:生产者会随机生成用户步数数据(包括用户ID、位置、步数和时间戳),然后将这些数据序列化为JSON格式,通过Kafka Producer API发送到指定的Kafka主题。
2. 消息队列(Kafka)
  • 作用:作为消息中间件,负责解耦数据生产者和消费者。
  • 原理:Kafka是一个分布式流处理平台,支持高吞吐量的消息发布和订阅。生产者将数据发送到Kafka主题,消费者从主题中读取数据。Kafka保证消息的顺序性和持久性,支持水平扩展。
3. 实时数据处理(Spark Streaming)
  • 作用:实时处理从Kafka中消费的数据流。
  • 原理:Spark Streaming是一个实时数据处理框架,能够处理实时数据流。它从Kafka中消费数据,进行数据解析和处理(如步数统计),并将处理结果输出到下游系统。Spark Streaming支持微批处理,能够在低延迟的情况下处理大规模数据。
4. 数据存储(Redis)
  • 作用:存储处理后的用户步数数据,支持快速读写。
  • 原理:Redis是一个高性能的内存数据库,支持多种数据结构。处理后的用户步数数据会被存储在Redis中,Redis的高性能读写能力确保了系统的实时性和响应速度。
5. 消费者(StepCounterApp)
  • 作用:从Kafka中消费数据,进行处理并更新到Redis。
  • 原理:消费者从Kafka主题中读取数据,使用Spark Streaming进行实时处理,然后将处理结果(如用户的累计步数)存储到Redis中。消费者负责整个数据处理链路的执行。

2.文件结构

microservices/
├── pom.xml
├── dependency-reduced-pom.xml
├── 教程.txt
├── query
├── target/
└── src/
    └── main/
        ├── resources/
        │   └── application.properties  (包含Kafka、Redis和Spark配置)
        └── java/
            └── com/
                └── example/
                    └── stepcounter/
                        ├── StepCounterApp.java
                        ├── StepDataProducer.java
                        ├── service/
                        ├── config/
                        └── model/

3.具体代码

Appconfig.java
package com.example.stepcounter.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class AppConfig {
    private static final Properties properties = new Properties();

    static {
        try (InputStream input = AppConfig.class.getClassLoader().getResourceAsStream("application.properties")) {
            if (input == null) {
                throw new RuntimeException("Unable to find application.properties");
            }
            properties.load(input);
        } catch (IOException e) {
            throw new RuntimeException("Error loading application.properties", e);
        }
    }

    public static String getProperty(String key) {
        String value = properties.getProperty(key);
        return value != null ? value.trim() : null;
    }

    public static Properties getKafkaProperties() {
        Properties kafkaProps = new Properties();
        kafkaProps.put("bootstrap.servers", getProperty("kafka.bootstrap.servers"));
        kafkaProps.put("group.id", getProperty("kafka.group.id"));
        kafkaProps.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        kafkaProps.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        return kafkaProps;
    }
} 
Userstep.java
package com.example.stepcounter.model;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserStep {
    private String userId;
    private String location;
    private Integer steps;
    private Long timestamp;
} 
RedisService.java
package com.example.stepcounter.service;

import com.example.stepcounter.config.AppConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisService {
    private static final JedisPool jedisPool;

    static {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(10);
        poolConfig.setMaxIdle(5);
        poolConfig.setMinIdle(1);

        String host = AppConfig.getProperty("redis.host");
        int port = Integer.parseInt(AppConfig.getProperty("redis.port"));
        String password = AppConfig.getProperty("redis.password");
        int database = Integer.parseInt(AppConfig.getProperty("redis.database"));

        if (password != null && !password.trim().isEmpty()) {
            jedisPool = new JedisPool(poolConfig, host, port, 2000, password, database);
        } else {
            jedisPool = new JedisPool(poolConfig, host, port, 2000);
        }
    }

    public static void incrementUserSteps(String userId, int steps) {
        try (Jedis jedis = jedisPool.getResource()) {
            String key = "user:" + userId + ":steps";
            jedis.incrBy(key, steps);
        }
    }

    public static Long getUserTotalSteps(String userId) {
        try (Jedis jedis = jedisPool.getResource()) {
            String key = "user:" + userId + ":steps";
            String steps = jedis.get(key);
            return steps != null ? Long.parseLong(steps) : 0L;
        }
    }
} 
StepCounterApp.java
package com.example.stepcounter;

import com.example.stepcounter.config.AppConfig;
import com.example.stepcounter.model.UserStep;
import com.example.stepcounter.service.RedisService;
import com.google.gson.Gson;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.spark.SparkConf;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka010.ConsumerStrategies;
import org.apache.spark.streaming.kafka010.KafkaUtils;
import org.apache.spark.streaming.kafka010.LocationStrategies;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class StepCounterApp {
    private static final Gson gson = new Gson();

    public static void main(String[] args) throws InterruptedException {
        // 创建Spark配置
        SparkConf sparkConf = new SparkConf()
                .setAppName(AppConfig.getProperty("spark.app.name"))
                .setMaster(AppConfig.getProperty("spark.master"));

        // 创建StreamingContext
        JavaStreamingContext streamingContext = new JavaStreamingContext(
                sparkConf,
                Durations.seconds(Integer.parseInt(AppConfig.getProperty("spark.streaming.batch.duration")))
        );

        // 配置Kafka消费者
        Properties kafkaProps = AppConfig.getKafkaProperties();
        String topic = AppConfig.getProperty("kafka.topic");

        // 将Properties转换为Map
        Map<String, Object> kafkaParams = new HashMap<>();
        for (String key : kafkaProps.stringPropertyNames()) {
            kafkaParams.put(key, kafkaProps.get(key));
        }

        // 创建Kafka输入流
        JavaInputDStream<ConsumerRecord<String, String>> stream = KafkaUtils.createDirectStream(
                streamingContext,
                LocationStrategies.PreferConsistent(),
                ConsumerStrategies.Subscribe(Collections.singletonList(topic), kafkaParams)
        );

        // 处理数据流
        stream.foreachRDD(rdd -> {
            rdd.foreach(record -> {
                String json = record.value();
                UserStep userStep = gson.fromJson(json, UserStep.class);
                
                // 更新Redis中的用户步数
                RedisService.incrementUserSteps(userStep.getUserId(), userStep.getSteps());
                
                // 获取并打印用户总步数
                Long totalSteps = RedisService.getUserTotalSteps(userStep.getUserId());
                System.out.printf("User %s at %s walked %d steps, total steps: %d%n",
                        userStep.getUserId(),
                        userStep.getLocation(),
                        userStep.getSteps(),
                        totalSteps);
            });
        });

        // 启动Streaming处理
        streamingContext.start();
        streamingContext.awaitTermination();
    }
} 
StepDataProducer.java
package com.example.stepcounter;

import com.example.stepcounter.config.AppConfig;
import com.example.stepcounter.model.UserStep;
import com.google.gson.Gson;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.Random;

public class StepDataProducer {
    private static final Gson gson = new Gson();
    private static final Random random = new Random();
    private static final String[] LOCATIONS = {"Home", "Park", "Office", "Gym", "Mall"};
    private static final String[] USER_IDS = {"user1", "user2", "user3", "user4", "user5"};

    public static void main(String[] args) throws InterruptedException {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, AppConfig.getProperty("kafka.bootstrap.servers"));
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        KafkaProducer<String, String> producer = new KafkaProducer<>(props);
        String topic = AppConfig.getProperty("kafka.topic");

        try {
            while (true) {
                UserStep userStep = generateRandomUserStep();
                String json = gson.toJson(userStep);
                
                producer.send(new ProducerRecord<>(topic, userStep.getUserId(), json));
                System.out.println("Sent: " + json);
                
                Thread.sleep(1000); // 每秒发送一条数据
            }
        } finally {
            producer.close();
        }
    }

    private static UserStep generateRandomUserStep() {
        String userId = USER_IDS[random.nextInt(USER_IDS.length)];
        String location = LOCATIONS[random.nextInt(LOCATIONS.length)];
        int steps = random.nextInt(100) + 1; // 1-100步
        long timestamp = System.currentTimeMillis();

        return new UserStep(userId, location, steps, timestamp);
    }
} 
application.properties
# Kafka Configuration
kafka.bootstrap.servers=localhost:9092
kafka.topic=user-steps
kafka.group.id=step-counter-group

# Redis Configuration
redis.host=localhost
redis.port=6379
redis.password=
redis.database=0

# Spark Configuration
spark.app.name=StepCounter
spark.master=local[2]
spark.streaming.batch.duration=5 
dependency-reduced-pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>step-counter</artifactId>
  <version>1.0-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.target}</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.4</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer>
                  <mainClass>com.example.stepcounter.StepCounterApp</mainClass>
                </transformer>
                <transformer />
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.26</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <spark.version>3.3.0</spark.version>
    <maven.compiler.target>1.8</maven.compiler.target>
    <kafka.version>3.5.0</kafka.version>
  </properties>
</project>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>step-counter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spark.version>3.3.0</spark.version>
        <kafka.version>3.5.0</kafka.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Spark Core -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>${spark.version}</version>
        </dependency>

        <!-- Spark Streaming -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.12</artifactId>
            <version>${spark.version}</version>
        </dependency>

        <!-- Spark Streaming Kafka -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
            <version>${spark.version}</version>
        </dependency>

        <!-- Kafka Clients -->
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>${kafka.version}</version>
        </dependency>

        <!-- Redis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.3.1</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
            <scope>provided</scope>
        </dependency>

        <!-- Gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>

        <!-- SLF4J -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.36</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.stepcounter.StepCounterApp</mainClass>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project> 

4.运行

在window开了6个cmd

REM 启动zookeeper!!



C:\kafka\kafka_2.12-3.6.1\bin\windows>zookeeper-server-start.bat ..\..\config\zookeeper.properties




REM 启动Redis!!!



C:\Users\86182>redis-server.exe



REM 启动kafka!!!



C:\kafka\kafka_2.12-3.6.1\bin\windows>kafka-server-start.bat ..\..\config\server.properties



REM 创建kafka主题!!!



C:\kafka\kafka_2.12-3.6.1\bin\windows>kafka-topics.bat --create --topic user-steps --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1
Created topic user-steps.



REM 消费者!!!



C:\microservices>java -cp target/step-counter-1.0-SNAPSHOT.jar com.example.stepcounter.StepCounterApp



REM 生产者!!!



C:\microservices>java -cp target/step-counter-1.0-SNAPSHOT.jar com.example.stepcounter.StepDataProducer




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

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

相关文章

CentOS 7 安装教程

准备&#xff1a; 软件&#xff1a;VMware Workstation 镜像文件&#xff1a;CentOS-7-x86_64-bin-DVD1.iso &#xff08;附&#xff1a;教程较为详细&#xff0c;注释较多&#xff0c;故将操作的选项进行了加粗字体显示。&#xff09; 1、文件–新建虚拟机–自定义 2、硬盘…

Python+AI提示词出租车出行轨迹预测:梯度提升GBR、KNN、LR回归、随机森林融合及贝叶斯概率异常检测研究

原文链接&#xff1a;tecdat.cn/?p41693 在当今数字化浪潮席卷全球的时代&#xff0c;城市交通领域的海量数据如同蕴藏着无限价值的宝藏等待挖掘。作为数据科学家&#xff0c;我们肩负着从复杂数据中提取关键信息、构建有效模型以助力决策的使命&#xff08;点击文末“阅读原文…

直接偏好优化(Direct Preference Optimization,DPO):论文与源码解析

简介 虽然大规模无监督语言模型&#xff08;LMs&#xff09;学习了广泛的世界知识和一些推理技能&#xff0c;但由于它们是基于完全无监督训练&#xff0c;仍很难控制其行为。 微调无监督LM使其对齐偏好&#xff0c;尽管大规模无监督的语言模型&#xff08;LMs&#xff09;能…

UE5.2+VarjoXR3,Lumen、GI、Nanite无效的两种解决方案

一、问题描述 最近在做一个基于VarjoXR3的VR项目开发&#xff0c;UE版本使用的是5.2&#xff0c;效果采用Lumen。首先在PC版本中调整了一个效果&#xff0c;但是当切换到VR运行后&#xff0c;就发现Lumen效果就丢失了。但是测试的其他的头显就没有问题&#xff0c;比如Quest。…

PH热榜 | 2025-04-24

1. Peek 标语&#xff1a;AI个人财务教练&#xff0c;帮你做出明智的财务决策。 介绍&#xff1a;Peek的人工智能助手能够主动进行财务检查&#xff0c;分析你的消费模式&#xff0c;并以一种细腻而积极的方式帮助你改善习惯。完全没有评判&#xff0c;也没有负罪感。就像为你…

相机雷达外参标定算法调研

0. 简介 相机与激光雷达的外参标定是自动驾驶、机器人等领域的基础工作。精准的标定不仅有助于提高数据融合的效果&#xff0c;还能提升算法的整体性能。随着技术的发展&#xff0c;许多研究者和公司致力于开发高效的标定工具和算法&#xff0c;本文将对无目标标定和有目标标定…

网络原理 - 7(TCP - 4)

目录 6. 拥塞控制 7. 延时应答 8. 捎带应答 9. 面向字节流 10. 异常情况 总结&#xff1a; 6. 拥塞控制 虽然 TCP 有了滑动窗口这个大杀器&#xff0c;就能够高效可靠的发送大量的数据&#xff0c;但是如果在刚开始阶段就发送大量的数据&#xff0c;仍然可能引起大量的…

idea连接远程服务器kafka

一、idea插件安装 首先idea插件市场搜索“kafka”进行插件安装 二、kafka链接配置 1、检查服务器kafka配置 配置链接前需要保证远程服务器的kafka配置里边有配置好服务器IP&#xff0c;以及开放好kafka端口9092&#xff08;如果有修改 过端口的开放对应端口就好&#xff09; …

Linux操作系统--基础I/O(上)

目录 1.回顾C文件接口 stdin、stdout、stderr 2.系统文件I/O 3.接口介绍 4.open函数返回值 5.文件描述符fd 5.1 0&1&2 1.回顾C文件接口 hello.c写文件 #include<stdio.h> #include<string.h>int main() {FILE *fp fopen("myfile","…

weibo_har鸿蒙微博分享,单例二次封装,鸿蒙微博,微博登录

weibo_har鸿蒙微博分享&#xff0c;单例二次封装&#xff0c;鸿蒙微博 HarmonyOS 5.0.3 Beta2 SDK&#xff0c;原样包含OpenHarmony SDK Ohos_sdk_public 5.0.3.131 (API Version 15 Beta2) &#x1f3c6;简介 zyl/weibo_har是微博封装使用&#xff0c;支持原生core使用 &a…

【MySQL数据库入门到精通-06 DCL操作】

一、DCL DCL英文全称是Data Control Language(数据控制语言)&#xff0c;用来管理数据库用户、控制数据库的访 问权限。 二、管理用户 1.查询与创建用户 代码如下&#xff08;示例&#xff09;&#xff1a; -- DCL 管理用户 -- 1.查询用户 use mysql; select *from user;-…

无感字符编码原址转换术——系统内存(Mermaid文本图表版/DeepSeek)

安全便捷无依赖&#xff0c;不学就会无感觉。 笔记模板由python脚本于2025-04-24 20:00:05创建&#xff0c;本篇笔记适合正在研究字符串编码制式的coder翻阅。 学习的细节是欢悦的历程 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不仅仅是知识的简单复述。 P…

第七部分:向量数据库和索引策略

什么是矢量数据库&#xff1f; 简单来说&#xff0c;向量数据库是一种专门化的数据库&#xff0c;旨在优化存储和检索以高维向量形式表示的文本。 为什么这些数据库对RAG至关重要&#xff1f;因为向量表示能够在大规模文档库中进行高效的基于相似性的搜索&#xff0c;根据用户…

查看MAC 地址以及简单了解

MAC地址 简介 MAC 地址&#xff08;Media Access Control Address&#xff09;&#xff0c;直译为媒体访问控制地址&#xff0c;又称局域网地址&#xff08;LAN Address&#xff09;、MAC 地址、以太网地址&#xff08;Ethernet Address&#xff09;、硬件地址&#xff08;Ha…

《100天精通Python——基础篇 2025 第2天:Python解释器安装与基础语法入门》

目录 一、Windows安装Python1.1 下载并安装 Python1.2 测试安装是否成功 二、Linux系统安装Python(新手可以跳过)2.1 基于RockyLinux系统安装Python(编译安装)2.2 基于Ubuntu系统安装Python(编译安装)2.3 macOS 安装python解释器 三、如何运行Python程序&#xff1f;3.1 Python…

MyBatis 和 MyBatis-Plus 在 Spring Boot 中的配置、功能对比及 SQL 日志输出的详细说明,重点对比日志输出的配置差异

以下是 MyBatis 和 MyBatis-Plus 在 Spring Boot 中的配置、功能对比及 SQL 日志输出的详细说明&#xff0c;重点对比日志输出的配置差异&#xff1a; 1. MyBatis 和 MyBatis-Plus 核心对比 特性MyBatisMyBatis-Plus定位基础持久层框架MyBatis 的增强版&#xff0c;提供代码生…

动手试一试 Spring Boot默认缓存管理

1.准备数据 使用之前创建的springbootdata的数据库&#xff0c;该数据库有两个表t_article和t_comment&#xff0c;这两个表预先插入几条测试数据。 2.编写数据库表对应的实体类 Entity(name "t_comment") public class Comment {IdGeneratedValue(strategy Gener…

Opencv图像处理:旋转、打包、多图像匹配

文章目录 一、图像的旋转1、使用numpy方法实现旋转1&#xff09;顺时针旋转90度2&#xff09;逆时针旋转90度 2、使用opencv的方法实现图像旋转1&#xff09;顺时针旋转90度2&#xff09;逆时针旋转90度3&#xff09;旋转180度 3、效果 二、多图像匹配1、模板2、匹配对象3、代码…

BOM与DOM(解疑document window关系)

BOM&#xff08;浏览器对象模型&#xff09; 定义与作用 BOM&#xff08;Browser Object Model&#xff09;提供与浏览器窗口交互的接口&#xff0c;用于控制导航、窗口尺寸、历史记录等浏览器行为 window&#xff1a;浏览器窗口的顶层对象&#xff0c;包含全局属性和方法&am…

数据仓库建设全解析!

目录 一、数据仓库建设的重要性 1. 整合企业数据资源 2. 支持企业决策制定 3. 提升企业竞争力 二、数据仓库建设的前期准备 1. 明确业务需求 2. 评估数据源 3. 制定项目计划 三、数据仓库建设的具体流程 1.需求分析​ 2.架构设计​ 3.数据建模​ 4.ETL 开发​ 5.…