多租户
2024/8/6大约 4 分钟
多租户
1、Mybatis-plus
提示
- Mybatis-plus只支持自身的多租户方式
springboot
@Component
public class MpTenantHandler implements TenantLineHandler {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Override
public Expression getTenantId() {
// 返回租户ID的表达式,LongValue 是 JSQLParser 中表示 bigint 类型的 class
return new LongValue(2);
}
@Override
public String getTenantIdColumn() {
return threadLocal.get();
}
/**
* 指定租户字段
* @param tableName 表名
* @return
*/
@Override
public boolean ignoreTable(String tableName) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
fieldList.forEach(field -> {
// 如果业务和工作流引擎中的租户字段不一致,可以通过这种方式动态切换
if (field.getColumn().equals("tenant_id") || field.getColumn().equals("tenant_code")) {
threadLocal.set(field.getColumn());
}
});
// 获取表字段
return false;
}
/**
* 如果业务系统不开启租户,使用下面方法,指定流程表才开启
* @param tableName 表名
* @return
*/
@Override
public boolean ignoreTable(String tableName) {
// 流程表
List<String> flowTableName = Arrays.asList("flow_definition", "flow_his_task", "flow_instance", "flow_node"
,"flow_skip", "flow_task", "flow_user");
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
AtomicBoolean flag = new AtomicBoolean(true);
if (flowTableName.contains(tableInfo.getTableName())) {
flag.set(false);
}
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
fieldList.forEach(field -> {
// 如果业务和工作流引擎中的租户字段不一致,可以通过这种方式动态切换
if (field.getColumn().equals("tenant_id")) {
threadLocal.set(field.getColumn());
}
});
// 获取表字段
return flag.get();
}
}
@Configuration
public class MybatisPlusConfig {
@Resource
private MpTenantHandler mpTenantHandler;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(mpTenantHandler);
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}
}solon
public class MpTenantHandler implements TenantLineHandler {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Override
public Expression getTenantId() {
// 返回租户ID的表达式,LongValue 是 JSQLParser 中表示 bigint 类型的 class
return new LongValue(2);
}
@Override
public String getTenantIdColumn() {
return threadLocal.get();
}
@Override
public boolean ignoreTable(String tableName) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
fieldList.forEach(field -> {
// 如果业务和工作流引擎中的租户字段不一致,可以通过这种方式动态切换
if (field.getColumn().equals("tenant_id") || field.getColumn().equals("tenant_code")) {
threadLocal.set(field.getColumn());
}
});
// 获取表字段
return false;
}
}
@Configuration
public class WarmFlowConfig {
@Bean(value = "db1", typed = true)
public DataSource db1(@Inject("${demo.db1}") HikariDataSource ds) {
return ds;
}
/**
* MybatisPlus全局配置
*
* @param globalConfig 数据源
*/
@Bean
public void db1_cfg(@Db("db1") MybatisConfiguration cfg,
@Db("db1") GlobalConfig globalConfig) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件 必须放到第一位
interceptor.addInnerInterceptor(tenantLineInnerInterceptor());
cfg.addInterceptor(interceptor);
}
/**
* 多租户插件
*/
public TenantLineInnerInterceptor tenantLineInnerInterceptor() {
return new TenantLineInnerInterceptor(new MpTenantHandler());
}
}2、Easy-Query
提示
- 默认逻辑未开启,如诺需要开启,需高版本比如3.1.79或者以上
- 如果使用Easy-Query的orm框架,只支持Easy-Query自身的多租户方式
2.1、和原系统不共用多租户拦截器
public class TenantInterceptor implements EntityInterceptor, PredicateFilterInterceptor {
@Override
public String name() {
return TenantInterceptor.class.getName();
}
@Override
public boolean apply(@NonNull Class<?> entityClass) {
// 流程表开启多租户
List<String> excludes = List.of("flow_definition", "flow_node", "flow_skip", "flow_instance"
, "flow_task", "flow_his_task", "flow_user");
// 获取entityClass上的@Table注解的值
Table annotation = entityClass.getAnnotation(Table.class);
if (annotation == null) {
return false;
}
String tableName = annotation.value();
return excludes.contains(tableName);
}
@Override
public void configure(Class<?> entityClass, LambdaEntityExpressionBuilder lambdaEntityExpressionBuilder, WherePredicate<Object> wherePredicate) {
// 流程表的租户字段名
String tenantName = "tenantId";
EntitySegmentComparer tenantIdComparer = new EntitySegmentComparer(entityClass, tenantName);
PredicateSegment where = getPredicateSegment(lambdaEntityExpressionBuilder);
if (where == null) {
return;
}
where.forEach(s -> {
if (s.getTable() != null) {
tenantIdComparer.visit(s);
return tenantIdComparer.isInSegment();
}
return false;
});
// 如果已经设置了租户字段,则不需要添加
if (tenantIdComparer.isInSegment()) {
return;
}
// 获取租户值
String tenantId = TenantHelper.getDynamic();
if (StrUtil.isBlank(tenantId)) {
tenantId = LoginHelper.getTenantId();
}
// 设置租户
if (StrUtil.isNotEmpty(tenantId)) {
wherePredicate.eq(tenantName, tenantId);
}
}
@Override
public void configureInsert(Class<?> entityClass, EntityInsertExpressionBuilder entityInsertExpressionBuilder, Object entity) {
setTenantId(entity);
}
@Override
public void configureUpdate(Class<?> entityClass, EntityUpdateExpressionBuilder entityUpdateExpressionBuilder, Object entity) {
setTenantId(entity);
}
private void setTenantId(Object entity) {
if (ObjectUtil.isNotNull(entity)) {
if (entity instanceof RootEntity rootEntity) {
if (StrUtil.isEmpty(rootEntity.getTenantId())) {
rootEntity.setTenantId(LoginHelper.getTenantId());
}
}
}
}
private PredicateSegment getPredicateSegment(LambdaEntityExpressionBuilder lambdaEntityExpressionBuilder) {
PredicateSegment where = null;
if (lambdaEntityExpressionBuilder instanceof QueryExpressionBuilder queryExpressionBuilder) {
where = queryExpressionBuilder.getWhere();
} else if (lambdaEntityExpressionBuilder instanceof UpdateExpressionBuilder updateExpressionBuilder) {
where = updateExpressionBuilder.getWhere();
} else if (lambdaEntityExpressionBuilder instanceof DeleteExpressionBuilder deleteExpressionBuilder) {
where = deleteExpressionBuilder.getWhere();
}
return where;
}
}2.2、和原系统共用多租户拦截器
@Slf4j
public class TenantInterceptor implements EntityInterceptor, PredicateFilterInterceptor {
@Override
public String name() {
return TenantInterceptor.class.getName();
}
@Override
public boolean apply(@NonNull Class<?> entityClass) {
TenantProperties tenantProperties = Solon.context().getBean(TenantProperties.class);
if (!tenantProperties.getEnable()) {
return false;
}
// 不需开启多租户的表
List<String> excludes = tenantProperties.getExcludes();
// 获取entityClass上的@Table注解的值
Table annotation = entityClass.getAnnotation(Table.class);
if (annotation == null) {
return false;
}
String tableName = annotation.value();
return !excludes.contains(tableName);
}
@Override
public void configure(Class<?> entityClass, LambdaEntityExpressionBuilder lambdaEntityExpressionBuilder, WherePredicate<Object> wherePredicate) {
// 本系统的租户字段名
String tenantName = TenantConstants.TENANT_OBJECT_NAME;
// 如果流程表和本系统的租户字段名不一样,则分别配置
if (RootEntity.class.isAssignableFrom(entityClass)) {
tenantName = TenantConstants.TENANT_OBJECT_NAME;
}
EntitySegmentComparer tenantIdComparer = new EntitySegmentComparer(entityClass, tenantName);
PredicateSegment where = getPredicateSegment(lambdaEntityExpressionBuilder);
if (where == null) {
return;
}
where.forEach(s -> {
if (s.getTable() != null) {
tenantIdComparer.visit(s);
return tenantIdComparer.isInSegment();
}
return false;
});
// 如果已经设置了租户字段,则不需要添加
if (tenantIdComparer.isInSegment()) {
return;
}
// 获取租户值
String tenantId = TenantHelper.getDynamic();
if (StrUtil.isBlank(tenantId)) {
tenantId = LoginHelper.getTenantId();
}
// 设置租户
if (StrUtil.isNotEmpty(tenantId)) {
wherePredicate.eq(tenantName, tenantId);
}
}
@Override
public void configureInsert(Class<?> entityClass, EntityInsertExpressionBuilder entityInsertExpressionBuilder, Object entity) {
setTenantId(entity);
}
@Override
public void configureUpdate(Class<?> entityClass, EntityUpdateExpressionBuilder entityUpdateExpressionBuilder, Object entity) {
setTenantId(entity);
}
private void setTenantId(Object entity) {
if (ObjectUtil.isNotNull(entity)) {
// 流程表和本系统对象要分别设置
if (entity instanceof TenantEntity tenantEntity) {
if (StrUtil.isEmpty(tenantEntity.getTenantId())) {
tenantEntity.setTenantId(LoginHelper.getTenantId());
}
} else if (entity instanceof RootEntity rootEntity) {
if (StrUtil.isEmpty(rootEntity.getTenantId())) {
rootEntity.setTenantId(LoginHelper.getTenantId());
}
}
}
}
private PredicateSegment getPredicateSegment(LambdaEntityExpressionBuilder lambdaEntityExpressionBuilder) {
PredicateSegment where = null;
if (lambdaEntityExpressionBuilder instanceof QueryExpressionBuilder queryExpressionBuilder) {
where = queryExpressionBuilder.getWhere();
} else if (lambdaEntityExpressionBuilder instanceof UpdateExpressionBuilder updateExpressionBuilder) {
where = updateExpressionBuilder.getWhere();
} else if (lambdaEntityExpressionBuilder instanceof DeleteExpressionBuilder deleteExpressionBuilder) {
where = deleteExpressionBuilder.getWhere();
}
return where;
}
}:::
3、通用多租户
yaml
# warm-flow工作流配置
warm-flow:
# 全局租户处理器(可通过配置文件注入,也可用@Bean/@Component方式
tenant_handler_path: org.dromara.warm.flow.core.test.handle.CustomTenantHandler@bean
@Configuration
public class WarmFlowConfig {
/**
* 全局租户处理器(可通过配置文件注入,也可用@Bean/@Component方式
*/
@Bean
public TenantHandler tenantHandler() {
return new CustomTenantHandler();
}
}@Component
/**
* 全局租户处理器(可通过配置文件注入,也可用@Bean/@Component方式
*
* @author warm
*/
@Component
public class CustomTenantHandler implements TenantHandler {
@Override
public String getTenantId() {
// 这里返回系统中的当前办理人的租户ID,一般会有工具类获取
return "000000";
}
}

