当前位置: 首页 > news >正文

SpringBoot + MyBatisPlus 实现多租户分库

一、引言

在如今的软件开发中,多租户(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: 10086spring:application:name: demodatasource:url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=UTCusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSource
mybatis:mapper-locations: classpath:mapper/*.xmlmybatis-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 {@Autowiredprivate 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 {@Autowiredprivate DynamicDataSourceProperties dynamicDataSourceProperties;private final Map<String, DataSource> dataSources = new HashMap<>();@PostConstructpublic 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*/@Overrideprotected 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 {@Autowiredprivate DataSourceManager dataSourceManager;@Beanpublic 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;}@Beanpublic 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 {@Autowiredprivate DataSourceManager dataSourceManager;@Autowiredprivate DynamicDataSource dynamicDataSource;@Autowiredprivate TenantDataSourceMapper tenantDataSourceMapper;@PostConstructpublic 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 {@Autowiredprivate DataSourceManager dataSourceManager;@Overridepublic 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;}}@Overridepublic 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 {@Autowiredprivate AuthInterceptor authInterceptor;@Overridepublic 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 {@Autowiredprivate 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结合,通过动态数据源实现多租户分库,是一种高效、灵活、易维护的解决方案,适用于多租户系统的开发。可以有效地保护租户数据安全,提高系统性能,同时具有良好的可扩展性和可维护性。 

相关文章:

SpringBoot + MyBatisPlus 实现多租户分库

一、引言 在如今的软件开发中&#xff0c;多租户(Multi-Tenancy)应用已经变得越来越常见。多租户是一种软件架构技术&#xff0c;它允许一个应用程序实例为多个租户提供服务。每个租户都有自己的数据和配置&#xff0c;但应用程序实例是共享的。而在我们的Spring Boot MyBati…...

【数据挖掘】银行信用卡风险大数据分析与挖掘

银行信用卡风险大数据分析与挖掘 1、实验目的 中国某个商业银行高层发现自家信用卡存在严重的欺诈和拖欠现象,已经影响到自身经营和发展。银行高层希望大数据分析部门采用数据挖掘技术,对影响用户信用等级的主要因素进行分析,结合信用卡用户的人口特征属性对欺诈行为和拖欠…...

使用 Qt 和 ECharts 进行数据可视化

文章目录 示例图表预览折线图散点图柱状图使用 Qt 和 ECharts 进行数据可视化一、准备工作1. 安装 Qt2. 准备 ECharts二、在 Qt 中使用 ECharts1. 创建 Qt 项目2. 配置项目文件3. 在 UI 中添加 WebEngineView4. 加载 ECharts三、创建折线图、散点图和柱状图1. 折线图2. 散点图3…...

【机器学习】在【Pycharm】中的实践教程:使用【逻辑回归模型】进行【乳腺癌检测】

目录 案例背景 具体问题 1. 环境准备 小李的理解 知识点 2. 数据准备 2.1 导入必要的库和数据集 小李的理解 知识点 2.2 数据集基本信息 小李的理解 知识点 注意事项 3. 数据预处理 3.1 划分训练集和测试集 小李的理解 知识点 注意事项 3.2 数据标准化 小李…...

【搭建Nacos服务】centos7 docker从0搭建Nacos服务

前言 本次搭建基于阿里云服务器系统为&#xff08;CentOS7 Linux&#xff09;、Nacos&#xff08;2.0.3&#xff09;、Docker version 26.1.4 本次搭建基于一个新的云服务器 安装java yum install -y java-1.8.0-openjdk.x86_64安装驱动以及gcc等前置需要的命令 yum install …...

将 build.gradle 配置从 Groovy 迁移到 Kotlin

目录 时间轴 常用术语 脚本文件命名 转换语法 为方法调用添加圆括号 为分配调用添加 转换字符串 重命名文件扩展名 将 def 替换为 val 或 var 为布尔值属性添加 is 前缀 转换列表和映射 配置 build 类型 从 buildscript 迁移到插件块 查找插件 ID 执行重构 转…...

5G(NR) NTN 卫星组网架构

5G(NR) NTN 卫星组网架构 参考 3GPP TR 38.821 5G NTN 技术适用于高轨、低轨等多种星座部署场景&#xff0c;是实现星地网络融合发展的可行技术路线。5G NTN 网络分为用户段、空间段和地面段三部分。其中用户段由各种用户终端组成&#xff0c;包括手持、便携站、嵌入式终端、车…...

WEB安全-文件上传漏洞

1 需求 2 接口 3 MIME类型 在Web开发中&#xff0c;MIME&#xff08;Multipurpose Internet Mail Extensions&#xff09;类型用于标识和表示文档的格式。这些类型在HTTP请求和响应头中扮演着重要的角色&#xff0c;告诉浏览器如何解释和处理接收到的资源12。 以下是一些Web开发…...

Python函数 之 函数基础

print() 在控制台输出 input() 获取控制台输⼊的内容 type() 获取变量的数据类型 len() 获取容器的⻓度 (元素的个数) range() ⽣成⼀个序列[0, n) 以上都是我们学过的函数&#xff0c;函数可以实现⼀个特定的功能。我们将学习⾃⼰如何定义函数, 实现特定的功能。 1.函数是什么…...

昇思25天学习打卡营第11天|SSD目标检测

SSD网络 目标检测问题可以分为以下两个问题&#xff1a;1&#xff09;分类:所有类别的概率&#xff1b;2&#xff09;定位: 4个值(中心位置x,y,宽w,高h) Single Shot MultiBox Detector&#xff0c;SSD&#xff1a;单阶段的目标检测算法&#xff0c;通过卷积神经网络进行特征…...

MySQL篇五:基本查询

文章目录 前言1. Create1.1 单行数据 全列插入1.2 多行数据 指定列插入1.3 插入否则更新1.4 替换 2. Retrieve2.1 SELECT 列2.1.1 全列查询2.1.2 指定列查询2.1.3 查询字段为表达式2.1.4 为查询结果指定别名2.1.5 结果去重 2.2 WHERE 条件2.2.1 练习 2.3 结果排序2.3.1 练习 …...

FreeBSD@ThinkPad x250因电池耗尽关机后无法启动的问题存档

好几次碰到电池耗尽FreeBSD关机&#xff0c;再启动&#xff0c;网络通了之后到了该出Xwindows窗体的时候&#xff0c;屏幕灭掉&#xff0c;网络不通&#xff0c;只有风扇在响&#xff0c;启动失败。关键是长按开关键后再次开机&#xff0c;还是启动失败。 偶尔有时候重启到单人…...

pdfplumber vs PyMuPDF:PDF文本、图像和表格识别的比较

pdfplumber vs PyMuPDF:PDF文本、图像和表格识别的比较 1. 文本提取pdfplumberPyMuPDF 2. 图像提取pdfplumberPyMuPDF 3. 表格提取pdfplumberPyMuPDF 总结 在处理PDF文件时,提取文本、图像和表格是常见的需求。本文将比较两个流行的Python PDF处理库:pdfplumber和PyMuPDF(fitz)…...

深入Django系列

Django简介与环境搭建 引言 在这个系列的第一天&#xff0c;我们将从Django的基本概念开始&#xff0c;逐步引导你搭建一个Django开发环境&#xff0c;并运行你的第一个Django项目。 Django简介 Django是一个开源的Web框架&#xff0c;它鼓励快速开发和干净、实用的设计。D…...

【Python】找Excel重复行

【背景】 找重复行虽然可以通过Excel实现,但是当数据量巨大时光是找结果就很费时间,所以考虑用Python实现。 【代码】 import pandas as pd# 读取Excel文件 file_path = your excel file path df = pd.read_excel(file_path)# 查找重复行 # 这里假设要检查所有列的重复项 …...

重读AI金典算法模型-GPT系列

2023年对于AI来说&#xff0c;可以算是一个里程碑式的年份&#xff0c;随着OpenAI的chatGPT的大火&#xff0c;遍地的生成式AI应用应运而生。在这些上层应用大放异彩的时候&#xff0c;我们需要了解一些底层的算法模型&#xff0c;并从中窥探出为什么时代选择了OpenAI的chatGPT…...

仙人掌中的SNMP检测不到服务器

登录有问题的服务器1.检测snmp localhost:~ # ps -ef|grep snmp root 55180 1 0 08:37 ? 00:00:08 /usr/sbin/snmpd -r -A -LF n /var/log/net-snmpd.log -p /var/run/snmpd.pid root 58436 53989 0 09:44 pts/0 00:00:00 grep --colorauto snmp2.检测…...

git只列出本地分支

git只列出本地分支 git branch --list git强制删除本地分支 git branch -D_error: the branch dlx-test is not fully merged. -CSDN博客文章浏览阅读648次。git branch -d 可以通过: git branch 查看所有本地分支及其名字&#xff0c;然后删除特定分支。git删除远程remote分支…...

算力狂飙|WAIC 2024上的服务器

7月7日&#xff0c;2024世界人工智能大会暨人工智能全球治理高级别会议&#xff08;WAIC 2024&#xff09;在上海落下帷幕。这场备受瞩目的AI盛宴与热辣夏日碰撞&#xff0c;吸引了全球科技、产业及学术界的广泛关注&#xff0c;线下参观人数突破30万人次&#xff0c;线上流量突…...

uniapp app端跳转第三方app(高德地图/百度地图为例)

1.先写一个picker选择器 <picker change"bindPickerChange" :value"index" :range"array"><view class"uni-input">{{array[index] || 打开第三方app }}</view></picker> 2.在data中定义好高德地图/百度地图…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...