SpringBoot + MyBatisPlus 实现多租户分库

news2024/11/15 23:39:00

一、引言

在如今的软件开发中,多租户(Multi-Tenancy)应用已经变得越来越常见。多租户是一种软件架构技术,它允许一个应用程序实例为多个租户提供服务。每个租户都有自己的数据和配置,但应用程序实例是共享的。而在我们的Spring Boot + MyBatis Plus环境中,我们可以利用动态数据源来实现多租户分库。

二、实现原理

SpringBoot + MyBatisPlus 动态数据源实现多租户分库的原理主要是通过切换不同的数据库连接来实现。对于每个租户,应用程序会使用一个独立的数据库连接,这样每个租户就拥有了自己的数据隔离空间。具体来说,当我们创建一个新的租户时,我们同时也为这个租户创建一个新的数据库连接。这些数据库连接被存储在一个数据源工厂中,我们可以根据租户的ID或者其他唯一标识符来获取对应的数据库连接。当一个租户需要访问其数据时,我们从数据源工厂中获取该租户对应的数据库连接,然后使用这个连接来执行数据库操作。

三、引入依赖

在pom.xml文件中引入下述相关的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.bc</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>6.0.1.Final</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.25</version>
		</dependency>

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.3.1</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.33</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

四、配置yml 

在application.yml文件中添加下述配置:

server:
  port: 10086

spring:
  application:
    name: demo
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
mybatis:
  mapper-locations: classpath:mapper/*.xml

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

五、数据准备 

在demo库中新建一张名为tenant_datasource的表,用于存储多租户的数据源配置信息: 

CREATE TABLE `tenant_datasource` (
  `tenant_id` varchar(50) NOT NULL,
  `url` varchar(255) DEFAULT NULL,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后执行下述SQL往tenant_datasource表中插入一些测试数据: 

insert into `tenant_datasource` (`tenant_id`, `url`, `username`, `password`) values('tenant1','jdbc:mysql://localhost:3306/tenant1_db','root','123456');
insert into `tenant_datasource` (`tenant_id`, `url`, `username`, `password`) values('tenant2','jdbc:mysql://localhost:3306/tenant2_db','root','123456');

在tenant1_db库中新建一张名为user的表,用于存储多租户1的用户信息:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(10) NOT NULL,
  `sex` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后执行下述SQL往tenant1_db库在的user表中插入一些测试数据: 

insert into `user` (`id`, `user_name`, `sex`) values('1','范闲','1');

在tenant2_db库中新建一张名为user的表,用于存储多租户2的用户信息: 

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(10) NOT NULL,
  `sex` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后执行下述SQL往tenant2_db库在的user表中插入一些测试数据: 

insert into `user` (`id`, `user_name`, `sex`) values('1','海棠朵朵','0');

六、编写实体类

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@TableName("user")
@Data
public class User {

    @TableId(type = IdType.AUTO)
    private Integer id;

    @TableField(value = "user_name")
    private String userName;

    @TableField(value = "sex")
    private Integer sex;
}
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.boot.jdbc.DataSourceBuilder;

import javax.sql.DataSource;

@Data
@TableName("tenant_datasource")
public class TenantDataSource {

    @TableId(type = IdType.INPUT)
    private String tenantId;

    @TableField(value = "url")
    private String url;

    @TableField(value = "username")
    private String username;

    @TableField(value = "password")
    private String password;

    public DataSource createDataSource() {
        return DataSourceBuilder.create()
                .url(this.url)
                .username(this.username)
                .password(this.password)
                .build();
    }
}

七、编写默认数据源配置类 

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DynamicDataSourceProperties {

    private String url;

    private String username;

    private String password;
}

八、构建Mapper接口和xml文件 

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends BaseMapper<User> {
    
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.TenantDataSource;
import org.springframework.stereotype.Repository;

@Repository
public interface TenantDataSourceMapper extends BaseMapper<TenantDataSource> {

}

在启动类配置扫描路径@MapperScan("com.example.demo.mapper"): 

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

@MapperScan("com.example.demo.mapper")
@SpringBootApplication
public class DemoApplication {

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

九、编写业务实现类 

import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User getUserById(Long id) {
        User user = userMapper.selectById(id);
        return user;
    }
}

 十、创建数据源管理器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 创建数据源管理器
 */
@Component
public class DataSourceManager {

    @Autowired
    private DynamicDataSourceProperties dynamicDataSourceProperties;

    private final Map<String, DataSource> dataSources = new HashMap<>();

    @PostConstruct
    public void init() {
        // 根据配置创建数据源并加入管理器
        DataSource defaultDataSource = DataSourceBuilder.create()
                .url(dynamicDataSourceProperties.getUrl())
                .username(dynamicDataSourceProperties.getUsername())
                .password(dynamicDataSourceProperties.getPassword())
                .build();
        dataSources.put("default", defaultDataSource);
    }

    public void addDataSource(String tenantId, DataSource dataSource) {
        dataSources.put(tenantId, dataSource);
    }

    public DataSource getDataSource(String tenantId) {
        return dataSources.get(tenantId);
    }

    public Map<String, DataSource> getAllDataSources() {
        return dataSources;
    }

    /**
     * 判断是否包含数据源
     */
    public boolean containDataSourceKey(String key) {
        return dataSources.containsKey(key);
    }
}

十一、创建租户上下文 

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TenantContext {

    // 使用ThreadLocal来存储当前线程的数据源名称(租户标识),保证多线程情况下,各自的数据源互不影响
    private static ThreadLocal<String> tenantId = ThreadLocal.withInitial(() -> "default");

    public static void setTenantId(String id) {
        tenantId.set(id);
        log.info("已切换到数据源:{}", id);
    }

    public static String getTenantId() {
        return tenantId.get();
    }

    public static void clear() {
        tenantId.remove();
        log.info("已切换回默认数据源");
    }
}

十二、创建动态数据源

创建一个动态数据源类,继承AbstractRoutingDataSource,用于动态切换数据源:

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

@Slf4j
@Data
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据源
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContext.getTenantId();
    }
}

十三、创建数据源配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import java.util.HashMap;
import java.util.Map;

/**
 * 创建数据源配置类,用于配置动态数据源
 */

@Configuration
public class DynamicDataSourceConfig {

    @Autowired
    private DataSourceManager dataSourceManager;

    @Bean
    public DynamicDataSource dynamicDataSource() {
        // 1、将数据源default设置为默认数据源
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(dataSourceManager.getDataSource("default"));
        // 2、获取初始化时所有的数据源,并设置目标数据源,必须为targetDataSources设置初始值,否则会报错
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.putAll(dataSourceManager.getAllDataSources());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

十四、创建多租户数据源服务 

创建多租户数据源服务类,用于初始化多租户数据源:

import com.example.demo.entity.TenantDataSource;
import com.example.demo.mapper.TenantDataSourceMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class MultiTenantDataSourceService {

    @Autowired
    private DataSourceManager dataSourceManager;

    @Autowired
    private DynamicDataSource dynamicDataSource;

    @Autowired
    private TenantDataSourceMapper tenantDataSourceMapper;

    @PostConstruct
    public void initialize() {
        // 1、从默认的数据源中查询出所有的租户信息,然后覆盖DynamicDataSource中的targetDataSources属性
        Map<Object, Object> targetDataSources = new HashMap<>();
        List<TenantDataSource> tenantDataSources = tenantDataSourceMapper.selectList(null);
        for (TenantDataSource tenantDataSource : tenantDataSources) {
            dataSourceManager.addDataSource(tenantDataSource.getTenantId(), tenantDataSource.createDataSource());
        }
        targetDataSources.putAll(dataSourceManager.getAllDataSources());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        // 2、必须执行此操作,才会重新初始化AbstractRoutingDataSource中的resolvedDataSources,也只有这样,动态切换数据源才会起效
        dynamicDataSource.afterPropertiesSet();
    }
}

十五、构建拦截器,并将其注册到InterceptorRegistry中

import cn.hutool.core.util.StrUtil;
import com.example.demo.config.DataSourceManager;
import com.example.demo.config.TenantContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Configuration
public class AuthInterceptor implements HandlerInterceptor {

    @Autowired
    private DataSourceManager dataSourceManager;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = request.getHeader("tenantId");
        if (StrUtil.isNotBlank(tenantId) && dataSourceManager.containDataSourceKey(tenantId) && (!"default".equals(tenantId))) {
            TenantContext.setTenantId(tenantId);
            return true;
        }else{
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        TenantContext.clear();
    }
}
import com.example.demo.Interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor);
    }
}

十六、创建Controller 

import com.example.demo.entity.User;
import com.example.demo.serivice.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{userId}")
    public User getUser(@PathVariable Long userId) {
        return userService.getUserById(userId);
    }
}

十七、测试

启动应用程序,通过访问localhost:10086/user/{userId} 来测试多租户分库功能:

可以看到上述测试示例中,已经实现了不同的租户查询独立的数据库信息。

十八、适用场景

  • 多租户系统开发:适用于多租户系统,每个租户有独立的数据库,通过动态数据源切换实现多租户数据隔离。
  • 租户级数据隔离:当多个租户共享同一应用但需要数据隔离时,可以通过此模式实现。 
  • 灵活扩展:适用于系统需求可能动态扩展租户,每个租户有独立数据库的场景,不需修改系统架构。

十九、优点

  • 数据隔离性强:每个租户有独立的数据库,数据隔离,保护租户数据安全。
  • 性能优化:每个租户有独立的数据库,避免多租户共享同一数据库的性能瓶颈。
  • 方便扩展:可以轻松实现动态增加新租户,每个租户有独立的数据库。
  • 可维护性高:MyBatisPlus提供了便捷的操作数据库的功能,减少开发人员的工作量。
  • 易用性强:Spring Boot集成MyBatisPlus,简化了配置和集成流程,提高开发效率。

二十、总结 

Spring Boot与MyBatis Plus结合,通过动态数据源实现多租户分库,是一种高效、灵活、易维护的解决方案,适用于多租户系统的开发。可以有效地保护租户数据安全,提高系统性能,同时具有良好的可扩展性和可维护性。 

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

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

相关文章

【吊打面试官系列-MyBatis面试题】什么是 MyBatis 的接口绑定?有哪些实现方式?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是 MyBatis 的接口绑定&#xff1f;有哪些实现方式&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是 MyBatis 的接口绑定&#xff1f;有哪些实现方式&#xff1f; 接口绑定&#xff0c;就是在 MyBatis 中…

【windows OBS开启直播】Windows搭建RTMP视频流服务(Nginx服务器版)

如果您想在windows 电脑上设置RTMP服务器&#xff0c;并使用VLC播放器播放OBS的直播流&#xff0c;您可以使用一个本地的RTMP服务器软件&#xff0c;如nginx配合nginx-rtmp-module来搭建。下面 详细介绍下如何搭建此视频流服务。 1、安装和配置本地RTMP服务器 步骤1&#xff…

在Morelogin中使用IPXProxy海外代理IP的设置指南

Morelogin指纹浏览器是市场上较受欢迎的指纹浏览器&#xff0c;允许用户管理多个账号并进行自动化操作。它提供免费环境供用户进行体验&#xff0c;并且操作起来非常简单。大多数人都会将Morelogin指纹浏览器和海外代理IP进行使用&#xff0c;来应用于多种场景&#xff0c;如电…

拨开迷雾,寻找大模型应用落地的支点

自主可控大模型底座个性化刚需场景&#xff0c;这家大模型公司率先趟出一条个性化发展路径。 作者 | 辰纹 来源 | 洞见新研社 上海的温度很高&#xff0c;接近40度&#xff0c;比上海温度更高的是AI的热度。 7月4日&#xff0c;2024世界人工智能大会暨人工智能全球治理高…

uniapp内置组件uni.navigateTo跳转后页面空白问题解决

文章目录 导文空白问题 导文 在h5上跳转正常 但是在小程序里面跳转有问题 无任何报错 页面跳转地址显示正确&#xff0c;但页面内容为空 空白问题 控制台&#xff1a; 问题解决&#xff1a; 方法1&#xff1a; 可能是没有注册的问题&#xff0c;把没注册的页面 注册一下。 方…

如何理解数据模型?颗粒度、维度及指标?

问题1 什么是数据模型&#xff1f; 数据模型反映在数据库中就是一张表&#xff0c;该表把他分开来看有以下关注的点。 &#xff08;1&#xff09;主键&#xff1a;表明该表主要的分析对象&#xff0c;比如我们的分析对象是订单、是商品、是门店&#xff0c;那么主键就是订单id,…

烧烤炉发霉怎么处理 烧烤炉发霉的原因分析

仓库储存的烧烤炉表面布满了霉菌是什么原因&#xff1f;烧烤炉发霉不仅影响外观和卖点&#xff0c;若是出口给到客户手上还会导致面临客户的索赔的问题 &#xff0c;经ihaoer防霉人士介绍烧烤炉发霉处理方法如下&#xff1a; 烧烤炉发霉的原因分析 一、储存的环境潮湿&#xff…

动手学深度学习(Pytorch版)代码实践 -循环神经网络-55循环神经网络的从零开始实现和简洁实现

55循环神经网络的实现 1.从零开始实现 import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l import matplotlib.pyplot as plt import liliPytorch as lp# 读取H.G.Wells的时光机器数据集 batch_size, num_ste…

若依 ruoyi-vue SpringBoot highlight-textarea 输入框敏感词关键词高亮标红(二)

参考文章&#xff0c;非常感谢大佬的分享 实现可高亮的输入框 — HighlightTextarea GitHub:highlight-textarea 可看作者上一篇文章 若依 ruoyi-vue SpringBoot聊天敏感词过滤sensitive-word&#xff08;一&#xff09; 效果图 审核时&#xff0c;输入框高亮敏感词&#xff…

由于找不到emp.dll无法运行游戏的多个有效解决方法分享

在玩游戏时候是否遇到过找不到emp.dll,无法继续执行代码问题无法打开游戏&#xff1f;那么这个emp.dll是什么呢&#xff1f;为什么会丢失&#xff0c;emp.dll丢失要怎么办&#xff1f;今天就给大家详细介绍一下emp.dll文件与emp.dll丢失的多个解决方法&#xff01; 一、emp.dll…

详解[USACO07OPEN] Cheapest Palindrome G(洛谷PP2890)(区间DP经典题)

题目 思路 考虑区间DP。 设dp[i][j]为从i到j这段区间被修正为回文串的最小花费 c[cc][1]为添加字符cc的花费 c[cc][2]为删去字符cc的花费 s为题目给出的字符串。 用[i 1,j]区间转移&#xff1a;这种转移相当于在[i1,j]区间的左边加入一个字符&#xff0c;让[i,j]变为回文的方…

一款专业的 Windows 恶意程序分析与清理工具

大家好&#xff0c;今天给大家分享一款专业的 Windows 恶意程序分析与清理工具OpenArk&#xff0c;它能够帮助用户发现系统中隐藏的恶意软件。 OpenArk是一款Windows平台上的开源Ark工具. Ark是Anti-Rootkit&#xff08;对抗恶意程序&#xff09;的简写, OpenArk目标成为逆向工…

农牧行业CRM洞察:打造营、销、服一体化数字营销平台

01、行业应用背景 保持企业活力&#xff0c;支撑业务单元协调发展&#xff0c;稳定核心产品竞争力&#xff0c;将成为农牧行业企业数字化、数智化建设的指导方向。 积极发挥数据在生产、流通、消费各个环节的决策支撑&#xff0c;为农牧企业特别是多业态集团型企业&#xff0…

(附源码)c#+winform实现远程开机(广域网可用)

实现逻辑 利用UDP协议发送特定格式的魔术包&#xff0c;以远程唤醒具有特定MAC地址的目标计算机。目标计算机的BIOS和网络配置需要支持Wake-on-LAN&#xff08;WOL&#xff09;功能&#xff0c;并且需要在目标计算机上配置正确的网络唤醒设置。 源码在最后 准备工作 进入Bio…

从混乱到有序:三品产品生命周期管理PLM系统改善工艺管理

在当今竞争激烈的市场环境中&#xff0c;企业必须不断寻求提高效率和降低成本的方法。工艺管理作为产品开发和制造过程中的关键环节&#xff0c;对产品的成本和质量有着决定性的影响。随着信息化和并行化的发展&#xff0c;工艺管理的复杂性日益增加&#xff0c;传统的管理方式…

MATLAB数据统计描述和分析

描述性统计就是搜集、整理、加工和分析统计数据&#xff0c; 使之系统化、条理化&#xff0c;以显示出数据资料的趋势、特征和数量关系。它是统计推断的基础&#xff0c;实用性较强&#xff0c;在数学建模的数据描述部分经常使用。 目录 1.频数表和直方图 2 .统计量 3.统计…

基于智能座舱视觉DMS/OMS/RMS的简介

基于智能座舱视觉DMS/OMS/RMS的简介 引言 随着智能驾驶技术的迅猛发展&#xff0c;智能座舱逐渐成为汽车科技领域的热点话题。在智能座舱系统中&#xff0c;驾驶员监控系统&#xff08;DMS&#xff09;、乘员监控系统&#xff08;OMS&#xff09;以及舱室监控系统&#xff08;…

可视化作品集(10):智慧楼宇大屏,美学效果杠杠的。

追求颜值在智慧楼宇大屏设计中是非常重要的&#xff0c;可以帮助提升用户体验、品牌形象和信息传递效果&#xff0c;为大屏的实际应用和效果带来更多的积极影响。 1. 吸引眼球&#xff1a; 精美的设计和视觉效果可以吸引用户的注意力&#xff0c;让用户更愿意去关注和了解大屏…

scratch绘制四个三角形 2024年6月中国电子学会 图形化编程 scratch编程等级考试二级真题和答案解析

scratch绘制四个三角形 一、题目要求 2024年6月电子学会图形化编程Scratch等级考试二级真题 1、准备工作 1.保留默认角色小猫; 2.添加背景Stars。 2、功能实现 1 .隐藏角色小猫&#xff0c;设置画笔裙始位置为(0,0)&#xff0c;画笔颜色为黄色&#xff0c;画笔的粗细为5…

土壤品质检测仪:守护大地之母的科技卫士

土壤&#xff0c;作为地球生命之源&#xff0c;承载着万物的生长与繁衍。然而&#xff0c;随着现代农业的快速发展&#xff0c;土壤品质问题日益凸显&#xff0c;对农作物的生长和人们的健康构成了潜在威胁。 随着环保意识的增强和农业可持续发展的需求&#xff0c;土壤品质检测…