mybatis-plus多租户兼容多字段租户标识

默认租户插件处理器的缺陷

在springboot工程中引入mybatis-plus的租户插件TenantLineInnerInterceptor,能简化我们的数据隔离操作,例如各类含租户用户登录权限的rest接口中,不需要再根据登录用户-set租户条件-触发查询,租户插件能帮我们省略手动插入条件的繁琐过程。

mybatis-plus默认仅支持单个字段的租户条件,实际使用场景中,我们的“租户”在系统中,可能是一个多类型的数据概念,例如菜单大类模块一可能一种用户能访问,菜单大类模块二是另一种用户能访问,两种模块用户权限都在同一管理菜单、权限、用户中进行配置,即用户区分类型,不同类型租户id属性来源不同。由于不同大类模块字段可能不一致,即“TenantId”在不同表中,是不同的字段名称,这时候使用原始的租户插件接口,就满足不了需求了。

public interface TenantLineHandler {/*** 获取租户 ID 值表达式,只支持单个 ID 值** @return 租户 ID 值表达式*/Expression getTenantId();/*** 获取租户字段名* 默认字段名叫: tenant_id** @return 租户字段名*/default String getTenantIdColumn() {return "tenant_id";}/*** 根据表名判断是否忽略拼接多租户条件* 默认都要进行解析并拼接多租户条件** @param tableName 表名* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件*/default boolean ignoreTable(String tableName) {return false;}/*** 忽略插入租户字段逻辑** @param columns        插入字段* @param tenantIdColumn 租户 ID 字段* @return*/default boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {return columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(tenantIdColumn));}
}

上述接口中getTenantId和getTenantIdColumn是唯一的,无法区分不同表不同租户字段。

针对多字段条件租户插件的实现

改造步骤如下:

1、定义一个新的租户数据行处理器

如这里命名FixTenantLineHandler,

仅更新getTenantId和getTenantIdColumn方法,方便根据表名来判断取什么租户字段。

package com.infypower.vpp.security.permission;import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;import java.util.List;/*** 租户处理器( TenantId 行级 )** @author endcy* @see com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler*/
public interface FixTenantLineHandler {/*** 获取租户 ID 值表达式,只支持单个 ID 值* <p>** @return 租户 ID 值表达式*/Expression getTenantId(String tableName);/*** 获取租户字段名* <p>* 默认字段名叫: tenant_id** @return 租户字段名*/default String getTenantIdColumn(String tableName) {return "tenant_id";}/*** 根据表名判断是否忽略拼接多租户条件* <p>* 默认都要进行解析并拼接多租户条件** @param tableName 表名* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件*/default boolean ignoreTable(String tableName) {return false;}/*** 忽略插入租户字段逻辑** @param columns        插入字段* @param tenantIdColumn 租户 ID 字段* @return*/default boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {return columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(tenantIdColumn));}
}

2、实现自定义TenantLineHandler

下列TenantSecurityUtils是用户登录时注入的requestScope或者ThreadLocal存储的用户信息,包含不同租户类型信息及ID、管理员租户数据授权配置信息等,可自定义实现。

hasProperty方法会取mybatis-plus缓存的表信息,根据表名判断表映射属性是否包含不同租户id字段,这样做的好处是无需额外编码处理不同表对应不同属性名称配置判断,直接使用mybatis-plus原有的TableInfoHelper相关功能。

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.gitee.coadmin.enums.UserIdentityTypeEnum;
import com.gitee.coadmin.utils.TenantSecurityUtils;
import com.infypower.vpp.common.CesConstant;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;/*** ...** @author endcy* @date 2025/9/9*/
@Slf4j
@Component
public class FixTenantLineInnerInterceptor implements FixTenantLineHandler {private static final String TENANT_ID1_COLUMN = "tenant_id1";private static final String TENANT_ID1_NAME = "tenantId1";private static final String TENANT_ID2_COLUMN = "tenant_id2";private static final String TENANT_ID2_NAME = "tenantId2";private static final String TENANT_ID3_COLUMN = "tenant_id3";private static final String TENANT_ID3_NAME = "tenantId3";//存在租户id 字段但忽略租户过滤的数据表 逐行加private final List<String> IGNORE_TABLES = CollUtil.newArrayList("");@Overridepublic Expression getTenantId(String tableName) {UserIdentityTypeEnum identityType = TenantSecurityUtils.getCurrentUserIdentityType();//如果是平台权限用户 可能有需要过滤租户类型1数据if (identityType == UserIdentityTypeEnum.PLATFORM) {Set<Long> tenantIds = TenantSecurityUtils.getCurrentPlatformUserTenantId1List();if (CollUtil.isEmpty(tenantIds)) {//默认返回平台管理权限return new LongValue(0L);}List<Expression> valueList = tenantIds.stream().map(LongValue::new).collect(Collectors.toList());return new InExpression(new Column(getTenantIdColumn(tableName)),new ExpressionList(valueList));}return new LongValue(TenantSecurityUtils.getCurrentUserMajorIdentityId());}@Overridepublic String getTenantIdColumn(String tableName) {String tenantColumn = TENANT_ID1_COLUMN;//平台理员绑定了特定租户权限if (hasProperty(tableName, TENANT_ID1_NAME) && hasProperty(tableName, TENANT_ID2_NAME)) {tenantColumn = TENANT_ID1_COLUMN;if (TenantSecurityUtils.getCurrentUserIdentityType() == UserIdentityTypeEnum.TENANT1) {tenantColumn = TENANT_ID2_COLUMN;}} else if (hasProperty(tableName, TENANT_ID2_NAME)) {tenantColumn = TENANT_ID2_COLUMN;} else if (hasProperty(tableName, TENANT_ID3_NAME)) {tenantColumn = TENANT_ID3_COLUMN;}//默认返回主租户字段log.debug(">>>> table:{} tenantColumn:{}", tableName, tenantColumn);return tenantColumn;}@Overridepublic boolean ignoreTable(String tableName) {boolean ignore;if (IGNORE_TABLES.contains(tableName)) {return true;}//全数据管理员略过if (TenantSecurityUtils.getCurrentUserMajorIdentityId() == 0L&& CollUtil.isEmpty(TenantSecurityUtils.getCurrentPlatformUserTenantId1List())) {return true;}if (!hasProperty(tableName, TENANT_ID1_NAME)&& !hasProperty(tableName, TENANT_ID2_NAME)&& !hasProperty(tableName, TENANT_ID3_NAME)) {ignore = true;log.debug(">>>> ignore tenant data for table:{}", tableName);} else {ignore = false;}if (ignore) {IGNORE_TABLES.add(tableName);return true;}
//        ignore = checkIgnoreTable(tableName);return false;}private boolean checkIgnoreTable(String tableName) {if (IGNORE_TABLES.contains(tableName.toLowerCase())) {return true;}//其他校验逻辑待拓展return false;}public static boolean hasProperty(String tableName, String propertyName) {TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);if (tableInfo == null) {return false;}// 检查字段列表是否包含该属性return tableInfo.getFieldList().stream().anyMatch(field -> field.getProperty().equals(propertyName));}}

3、重写租户sql注入处理器

参考原TenantLineInnerInterceptor实现,使用tenantLineHandler,并替换需要传入表名的调用

package com.infypower.vpp.security.permission;import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.toolkit.PropertyMapper;
import lombok.*;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;/*** @author endcy* @since 2029/9/9* @see com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@SuppressWarnings({"rawtypes"})
public class FixTenantLineInnerInterceptor2 extends JsqlParserSupport implements InnerInterceptor {private FixTenantLineHandler tenantLineHandler;@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {……}@Overridepublic void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {……}@Overrideprotected void processSelect(Select select, int index, String sql, Object obj) {……}protected void processSelectBody(SelectBody selectBody) {……}@Overrideprotected void processInsert(Insert insert, int index, String sql, Object obj) {String tableName = insert.getTable().getName();if (tenantLineHandler.ignoreTable(tableName)) {// 过滤退出执行return;}List<Column> columns = insert.getColumns();if (CollectionUtils.isEmpty(columns)) {// 针对不给列名的insert 不处理return;}String tenantIdColumn = tenantLineHandler.getTenantIdColumn(tableName);if (tenantLineHandler.ignoreInsert(columns, tenantIdColumn)) {// 针对已给出租户列的insert 不处理return;}columns.add(new Column(tenantIdColumn));// fixed gitee pulls/141 duplicate updateList<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {EqualsTo equalsTo = new EqualsTo();equalsTo.setLeftExpression(new StringValue(tenantIdColumn));equalsTo.setRightExpression(tenantLineHandler.getTenantId(tableName));duplicateUpdateColumns.add(equalsTo);}Select select = insert.getSelect();if (select != null) {this.processInsertSelect(tableName, select.getSelectBody());} else if (insert.getItemsList() != null) {// fixed github pull/295ItemsList itemsList = insert.getItemsList();if (itemsList instanceof MultiExpressionList) {((MultiExpressionList) itemsList).getExpressionLists().forEach(el -> el.getExpressions().add(tenantLineHandler.getTenantId(tableName)));} else {((ExpressionList) itemsList).getExpressions().add(tenantLineHandler.getTenantId(tableName));}} else {throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");}}/*** update 语句处理*/@Overrideprotected void processUpdate(Update update, int index, String sql, Object obj) {final Table table = update.getTable();if (tenantLineHandler.ignoreTable(table.getName())) {// 过滤退出执行return;}update.setWhere(this.andExpression(table, update.getWhere()));}/*** delete 语句处理*/@Overrideprotected void processDelete(Delete delete, int index, String sql, Object obj) {……}/*** delete update 语句 where 处理*/protected BinaryExpression andExpression(Table table, Expression where) {//获得where条件表达式EqualsTo equalsTo = new EqualsTo();equalsTo.setLeftExpression(this.getAliasColumn(table));equalsTo.setRightExpression(tenantLineHandler.getTenantId(table.getName()));if (null != where) {if (where instanceof OrExpression) {return new AndExpression(equalsTo, new Parenthesis(where));} else {return new AndExpression(equalsTo, where);}}return equalsTo;}/*** 处理 insert into select* <p>* 进入这里表示需要 insert 的表启用了多租户,则 select 的表都启动了** @param selectBody SelectBody*/protected void processInsertSelect(String tableName, SelectBody selectBody) {PlainSelect plainSelect = (PlainSelect) selectBody;FromItem fromItem = plainSelect.getFromItem();if (fromItem instanceof Table) {// fixed gitee pulls/141 duplicate updateprocessPlainSelect(plainSelect);appendSelectItem(tableName, plainSelect.getSelectItems());} else if (fromItem instanceof SubSelect) {SubSelect subSelect = (SubSelect) fromItem;appendSelectItem(tableName, plainSelect.getSelectItems());processInsertSelect(tableName, subSelect.getSelectBody());}}/*** 追加 SelectItem** @param selectItems SelectItem*/protected void appendSelectItem(String tableName, List<SelectItem> selectItems) {if (CollectionUtils.isEmpty(selectItems))return;if (selectItems.size() == 1) {SelectItem item = selectItems.get(0);if (item instanceof AllColumns || item instanceof AllTableColumns)return;}selectItems.add(new SelectExpressionItem(new Column(tenantLineHandler.getTenantIdColumn(tableName))));}/*** 处理 PlainSelect*/protected void processPlainSelect(PlainSelect plainSelect) {……}/*** 处理where条件内的子查询** @param where where 条件*/protected void processWhereSubSelect(Expression where) {……}protected void processSelectItem(SelectItem selectItem) {……}/*** 处理函数* <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>* <p> fixed gitee pulls/141</p>** @param function*/protected void processFunction(Function function) {ExpressionList parameters = function.getParameters();if (parameters != null) {parameters.getExpressions().forEach(expression -> {if (expression instanceof SubSelect) {processSelectBody(((SubSelect) expression).getSelectBody());} else if (expression instanceof Function) {processFunction((Function) expression);}});}}/*** 处理子查询等*/protected void processFromItem(FromItem fromItem) {……}/*** 处理 joins** @param joins join 集合*/private void processJoins(List<Join> joins) {……}/*** 处理联接语句*/protected void processJoin(Join join) {……}/*** 处理条件*/protected Expression builderExpression(Expression currentExpression, Table table) {EqualsTo equalsTo = new EqualsTo();equalsTo.setLeftExpression(this.getAliasColumn(table));equalsTo.setRightExpression(tenantLineHandler.getTenantId(table.getName()));if (currentExpression == null) {return equalsTo;}if (currentExpression instanceof OrExpression) {return new AndExpression(new Parenthesis(currentExpression), equalsTo);} else {return new AndExpression(currentExpression, equalsTo);}}/*** 租户字段别名设置* <p>tenantId 或 tableAlias.tenantId</p>** @param table 表对象* @return 字段*/protected Column getAliasColumn(Table table) {StringBuilder column = new StringBuilder();if (table.getAlias() != null) {column.append(table.getAlias().getName()).append(StringPool.DOT);}column.append(tenantLineHandler.getTenantIdColumn(table.getName()));return new Column(column.toString());}@Overridepublic void setProperties(Properties properties) {PropertyMapper.newInstance(properties).whenNotBlank("tenantLineHandler",ClassUtils::newInstance, this::setTenantLineHandler);}
}

4、注入自定义租户插件

在mybatis-plus配置类中注入对应租户插件及其他数据权限插件等。

package com.infypower.vpp.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.infypower.vpp.security.permission.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 配置文件* @author endcy* @since 2025/9/9*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class MybatisPlusConfig {private final FixTenantLineInnerInterceptor tenantLineInnerInterceptor;/*** admin模块MP插件* 多租户插件、数据权限插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 租户代理interceptor.addInnerInterceptor(new FixTenantLineInnerInterceptor(tenantLineInnerInterceptor));// 其他数据权限,根据用户配置进行数据权限控制DataPermissionInterceptor xxxPermissionInterceptor = new ResourceUserPermissionInterceptor();resourceUserPermissionInterceptor.setDataPermissionHandler(new XxxPermissionHandler());interceptor.addInnerInterceptor(xxxPermissionInterceptor);return interceptor;}}

其他配置保持不变。

上述getTenantId和getTenantIdColumn即核心实现,根据不同的用户类型,赋不同的租户字段标识和租户id信息。相比于网上其他多字段租户方案,本方案不依赖额外配置,不需要复杂解析,应该是最简洁且拓展最为便捷的方式。

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

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

相关文章

HBase高级特性(布隆过滤器和协处理器)、列族设计、rowkey设计以及热点问题处理

在阐述HBase高级特性和热点问题处理前&#xff0c;首先回顾一下HBase的特点&#xff1a;分布式、列存储、支持实时读写、存储的数据类型都是字节数组byte[]&#xff0c;主要用来处理结构化和半结构化数据&#xff0c;底层数据存储基于hdfs。 同时&#xff0c;HBase和传统数据库…

redis sentinel 与 clauster 的区别

Redis Sentinel(哨兵)和Redis Cluster(集群)是Redis提供的两种不同的高可用和扩展性解决方案,它们的设计目标和适用场景有显著区别: 1. 核心功能与目标 Redis Sentinel 主要解决主从架构的高可用问题,实现自动故障转移 监控主从节点状态,当主节点故障时自动将从节点提…

MySQL数据库中快速导入大数据sql

1.PwerShell命令页面导入全表数据库 -P3310 指定数据库端口号Get-Content "本地sql文件目录" | .\mysql -u root -p -P 33102.PwerShell命令页面导入单表到数据库 -P3310 指定数据库端口号Get-Content "本地sql文件目录" | .\mysql -u root -p -P 3310 数…

消息类型proto的编写和生成

消息类型proto的编写和生成 代码如下&#xff1a; syntax"proto3"; package xypmq;enum ExchangeType {UNKNOWNTYPE0;DIRECT1;FANOUT2;TOPIC3; };enum DeliveryMode {UNKNOWNMODE0;UNDURABLE1;DURABLE2; };message BasicProperties {string id1;DeliveryMode deliver…

Vuetify:构建优雅Vue应用的Material Design组件库

Vuetify是一个基于Material Design设计规范的Vue.js UI组件库&#xff0c;它提供了80多个精心设计的组件&#xff0c;帮助开发者快速构建美观且功能丰富的企业级应用。核心特性1. 完整的Material Design实现// 所有组件遵循Material Design规范 <v-btn color"primary&q…

SpringBoot 注解深剖:@RequestParam 与 @RequestBody 的终极对决,90% 的开发者都踩过这些坑!

在 SpringBoot 开发中&#xff0c;处理 HTTP 请求参数是我们每天都要面对的工作。而RequestParam和RequestBody这两个注解&#xff0c;就像是我们手中的两把利剑&#xff0c;既能高效解决问题&#xff0c;用不好也可能 "误伤" 自己。作为一名资深 Java 开发者&#x…

【Docker】P2 Docker环境构建准备:MacOS 与 Linux

目录操作系统与 Docker 的兼容性分析Docker 技术本质MacOS 环境下的 Docker 构建1. 安装前准备2. Docker Desktop安装3. 镜像加速配置高级操作&#xff1a;文件共享配置Linux 环境下的 Docker 构建卸载历史版本配置软件源Docker 核心组件安装系统服务配置镜像加速器配置应用配置…

OpenCV 发票识别全流程:透视变换与轮廓检测详解

目录 前言 一、核心技术原理&#xff1a;透视变换与轮廓检测 1. 透视变换&#xff1a;让倾斜发票 “正过来” &#xff08;1&#xff09;什么是透视变换&#xff1f; &#xff08;2&#xff09;透视变换的 5 个关键步骤 2. 轮廓检测&#xff1a;精准定位发票区域 &#x…

并发:使用volatile和不可变性实现线程安全

《Java并发编程实战》中的VolatileCachedFactorizer展示了如何使用volatile和不可变性来实现线程安全。解决了简单缓存实现中可能出现的线程安全问题&#xff0c;同时避免了全量同步带来的性能开销。 场景背景 假设有一个服务&#xff08;如因数分解服务&#xff09;&#xff0…

Linux x86 stability和coredump

1 POSIX pthread_create原理 1&#xff09;fork()、pthread_create()、vfork()对应的系统调用分别是sys_fork()、sys_clone()、sys_vfork()&#xff0c;它们在内核中都是通过do_fork()实现的。 2&#xff09;系统中所有的进程都组织在init_task.tasks链表下面&#xff0c;每个进…

【PyTorch】多对象分割

对象分割任务的目标是找到图像中目标对象的边界。实际应用例如自动驾驶汽车和医学成像分析。这里将使用PyTorch开发一个深度学习模型来完成多对象分割任务。多对象分割的主要目标是自动勾勒出图像中多个目标对象的边界。 对象的边界通常由与图像大小相同的分割掩码定义&#xf…

RabbitMQ---面试题

总结我们所学内容&#xff0c;这里推荐博客进行复习 RabbitMQ---面试题_rabbitmq常问面试题-CSDN博客

MasterGo自动布局(Auto Layout)

自动布局是用来表示 子元素与子元素之间互相影响的一种排版方式,是一种响应式布局技术。一般是将所有元素设计完成后再使用自动布局进行设置。 自动布局就是响应式布局,就是在不同尺寸的手机上宽度不同都应该怎么展示。 一般页面的一级元素使用约束进行相对定位,二级元素及里…

还在重启应用改 Topic?Spring Boot 动态 Kafka 消费的“终极形态”

场景描述&#xff1a; 你的一个微服务正在稳定地消费 Kafka 的 order_topic。现在&#xff0c;上游系统为了做业务隔离&#xff0c;新增加了一个 order_topic_vip&#xff0c;并开始向其中投递 VIP 用户的订单。你需要在不重启、不发布新版本的情况下&#xff0c;让你现有的消费…

使用vllm部署neo4j的text2cypher-gemma-2-9b-it-finetuned-2024v1模型

使用vllm部署neo4j的text2cypher-gemma-2-9b-it-finetuned-2024v1模型 系统环境准备 由于使用的基于 nvcr.io/nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 的 workbench,需要进行以下准备(其他系统环境可忽略) ldconfig -p | grep libcudnn 找到 libcudnn 的so库,然…

Coze源码分析-资源库-创建知识库-前端源码-核心组件

概述 本文深入分析Coze Studio中用户创建知识库功能的前端实现。该功能允许用户在资源库中创建、编辑和管理知识库资源&#xff0c;为开发者提供了强大的知识管理和数据处理能力。通过对源码的详细解析&#xff0c;我们将了解从资源库入口到知识库配置弹窗的完整架构设计、组件…

基于时空数据的网约车订单需求预测与调度优化

一、引言随着共享出行行业的蓬勃发展&#xff0c;网约车已成为城市交通的重要组成部分。如何精准预测订单需求并优化车辆调度&#xff0c;是提升平台运营效率、改善用户体验的关键。本文提出一种基于时空数据的网约车订单需求预测与调度优化方案&#xff0c;通过网格化城市空间…

数据结构 Java对象的比较

在Java中&#xff0c;凡是涉及到比较的&#xff0c;可以分为两类情况&#xff1a;一类是基本数据类型的比较&#xff0c;另一类是引用数据类型的比较。对于基本数据类型的比较&#xff0c;我们通过关系运算符&#xff08;、>、<、!、>、<&#xff09;进行它们之间的…

企智汇建筑施工项目管理系统:全周期数字化管控,赋能工程企业降本增效!​建筑工程项目管理软件!建筑工程项目管理系统!建筑项目管理软件企智汇软件

在建筑施工行业&#xff0c;项目进度滞后、成本超支、质量安全隐患频发、多方协同不畅等问题&#xff0c;一直是制约企业发展的痛点。传统依赖人工记录、Excel 统计的管理模式&#xff0c;不仅效率低下&#xff0c;更易因信息断层导致决策失误。企智汇建筑施工项目管理系统凭借…

k8s-临时容器学习

临时容器学习1. 什么是临时容器2. 实验1. 什么是临时容器 在官网&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/ephemeral-containers/ 中有介绍 临时容器是用于调试Pod中崩溃的容器或者不具备调试工具&#xff0c;比如在一个运行着业务的容器中&am…