This commit is contained in:
2025-11-28 16:23:32 +08:00
commit a9e0e16c29
826 changed files with 89805 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
<?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>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-starter-tenant</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-cache</artifactId>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<scope>provided</scope>
</dependency>
<!--Dynamic-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<scope>provided</scope>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,134 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tenant.annotation.TableExclude;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import java.util.*;
/**
* 租户信息处理器
*
* @author Chill, L.cm
*/
@Slf4j
@RequiredArgsConstructor
public class BladeTenantHandler implements TenantLineHandler, SmartInitializingSingleton {
/**
* 匹配的多租户表
*/
private final List<String> tenantTableList = new ArrayList<>();
/**
* 需要排除进行自定义的多租户表
*/
private final List<String> excludeTableList = Arrays.asList("blade_user", "blade_dept", "blade_role", "blade_tenant", "act_de_model");
/**
* 多租户配置
*/
private final BladeTenantProperties tenantProperties;
/**
* 获取租户ID
*
* @return 租户ID
*/
@Override
public Expression getTenantId() {
return new StringValue(Func.toStr(AuthUtil.getTenantId(), BladeConstant.ADMIN_TENANT_ID));
}
/**
* 获取租户字段名称
*
* @return 租户字段名称
*/
@Override
public String getTenantIdColumn() {
return tenantProperties.getColumn();
}
/**
* 根据表名判断是否忽略拼接多租户条件
* 默认都要进行解析并拼接多租户条件
*
* @param tableName 表名
* @return 是否忽略, true:表示忽略false:需要解析并拼接多租户条件
*/
@Override
public boolean ignoreTable(String tableName) {
if (BladeTenantHolder.isIgnore()) {
return true;
}
return !(tenantTableList.contains(tableName) && StringUtil.isNotBlank(AuthUtil.getTenantId()));
}
@Override
public void afterSingletonsInstantiated() {
ApplicationContext context = SpringUtil.getContext();
if (tenantProperties.getAnnotationExclude() && context != null) {
Map<String, Object> tables = context.getBeansWithAnnotation(TableExclude.class);
List<String> excludeTables = tenantProperties.getExcludeTables();
for (Object o : tables.values()) {
TableExclude annotation = o.getClass().getAnnotation(TableExclude.class);
String value = annotation.value();
excludeTables.add(value);
}
}
List<TableInfo> tableInfos = TableInfoHelper.getTableInfos();
tableFor:
for (TableInfo tableInfo : tableInfos) {
String tableName = tableInfo.getTableName();
if (tenantProperties.getExcludeTables().contains(tableName) ||
excludeTableList.contains(tableName.toLowerCase()) ||
excludeTableList.contains(tableName.toUpperCase())) {
continue;
}
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
for (TableFieldInfo fieldInfo : fieldList) {
String column = fieldInfo.getColumn();
if (tenantProperties.getColumn().equals(column)) {
tenantTableList.add(tableName);
continue tableFor;
}
}
}
}
}

View File

@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant;
import org.springframework.core.NamedThreadLocal;
/**
* 租户线程处理
*
* @author Chill
*/
public class BladeTenantHolder {
private static final ThreadLocal<Boolean> TENANT_KEY_HOLDER = new NamedThreadLocal<Boolean>("blade-tenant") {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
};
public static void setIgnore(Boolean ignore) {
TENANT_KEY_HOLDER.set(ignore);
}
public static Boolean isIgnore() {
return TENANT_KEY_HOLDER.get();
}
public static void clear() {
TENANT_KEY_HOLDER.remove();
}
}

View File

@@ -0,0 +1,41 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant;
import org.springblade.core.tool.utils.RandomType;
import org.springblade.core.tool.utils.StringUtil;
/**
* blade租户id生成器
*
* @author Chill
*/
public class BladeTenantId implements TenantId {
@Override
public String generate() {
return StringUtil.random(6, RandomType.INT);
}
}

View File

@@ -0,0 +1,433 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
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 net.sf.jsqlparser.statement.update.UpdateSet;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.CollectionUtil;
import java.util.*;
import java.util.stream.Collectors;
/**
* 租户拦截器
*
* @author Chill
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BladeTenantInterceptor extends TenantLineInnerInterceptor {
/**
* 租户处理器
*/
private TenantLineHandler tenantLineHandler;
/**
* 租户配置文件
*/
private BladeTenantProperties tenantProperties;
/**
* 超管需要启用租户过滤的表
*/
private List<String> adminTenantTables = Arrays.asList("blade_top_menu", "blade_dict_biz");
@Override
public void setTenantLineHandler(TenantLineHandler tenantLineHandler) {
super.setTenantLineHandler(tenantLineHandler);
this.tenantLineHandler = tenantLineHandler;
}
@Override
protected void processInsert(Insert insert, int index, String sql, Object obj) {
// 未启用租户增强,则使用原版逻辑
if (!tenantProperties.getEnhance()) {
super.processInsert(insert, index, sql, obj);
return;
}
if (tenantLineHandler.ignoreTable(insert.getTable().getName())) {
// 过滤退出执行
return;
}
List<Column> columns = insert.getColumns();
if (CollectionUtils.isEmpty(columns)) {
// 针对不给列名的insert 不处理
return;
}
String tenantIdColumn = tenantLineHandler.getTenantIdColumn();
if (tenantLineHandler.ignoreInsert(columns, tenantIdColumn)) {
// 针对已给出租户列的insert 不处理
return;
}
columns.add(new Column(tenantIdColumn));
// fixed gitee pulls/141 duplicate update
List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new StringValue(tenantIdColumn));
equalsTo.setRightExpression(tenantLineHandler.getTenantId());
duplicateUpdateColumns.add(equalsTo);
}
Select select = insert.getSelect();
if (select != null && (select.getSelectBody() instanceof PlainSelect)) { //fix github issue 4998 修复升级到4.5版本的问题
this.processInsertSelect(select.getSelectBody(), (String) obj);
} else if (insert.getItemsList() != null) {
// fixed github pull/295
ItemsList itemsList = insert.getItemsList();
Expression tenantId = tenantLineHandler.getTenantId();
if (itemsList instanceof MultiExpressionList) {
((MultiExpressionList) itemsList).getExpressionLists().forEach(el -> el.getExpressions().add(tenantId));
} else {
List<Expression> expressions = ((ExpressionList) itemsList).getExpressions();
if (CollectionUtils.isNotEmpty(expressions)) {//fix github issue 4998 jsqlparse 4.5 批量insert ItemsList不是MultiExpressionList 了,需要特殊处理
int len = expressions.size();
for (int i = 0; i < len; i++) {
Expression expression = expressions.get(i);
if (expression instanceof RowConstructor) {
((RowConstructor) expression).getExprList().getExpressions().add(tenantId);
} else if (expression instanceof Parenthesis) {
RowConstructor rowConstructor = new RowConstructor()
.withExprList(new ExpressionList(((Parenthesis) expression).getExpression(), tenantId));
expressions.set(i, rowConstructor);
} else {
if (len - 1 == i) { // (?,?) 只有最后一个expre的时候才拼接tenantId
expressions.add(tenantId);
}
}
}
} else {
expressions.add(tenantId);
}
}
} else {
throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
}
}
/**
* 处理 PlainSelect
*/
@Override
protected void processPlainSelect(final PlainSelect plainSelect, final String whereSegment) {
//#3087 github
List<SelectItem> selectItems = plainSelect.getSelectItems();
if (CollectionUtils.isNotEmpty(selectItems)) {
selectItems.forEach(selectItem -> processSelectItem(selectItem, whereSegment));
}
// 处理 where 中的子查询
Expression where = plainSelect.getWhere();
processWhereSubSelect(where, whereSegment);
// 处理 fromItem
FromItem fromItem = plainSelect.getFromItem();
List<Table> list = processFromItem(fromItem, whereSegment);
List<Table> mainTables = new ArrayList<>(list);
// 处理 join
List<Join> joins = plainSelect.getJoins();
if (CollectionUtils.isNotEmpty(joins)) {
mainTables = processJoins(mainTables, joins, whereSegment);
}
// 当有 mainTable 时,进行 where 条件追加
if (CollectionUtils.isNotEmpty(mainTables) && !doTenantFilters(mainTables)) {
plainSelect.setWhere(builderExpression(where, mainTables, whereSegment));
}
}
/**
* update 语句处理
*/
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
final Table table = update.getTable();
if (tenantLineHandler.ignoreTable(table.getName())) {
// 过滤退出执行
return;
}
if (doTenantFilter(table.getName())) {
// 过滤退出执行
return;
}
ArrayList<UpdateSet> sets = update.getUpdateSets();
if (!CollectionUtils.isEmpty(sets)) {
sets.forEach(us -> us.getExpressions().forEach(ex -> {
if (ex instanceof SubSelect) {
processSelectBody(((SubSelect) ex).getSelectBody(), (String) obj);
}
}));
}
update.setWhere(this.andExpression(table, update.getWhere(), (String) obj));
}
/**
* delete 语句处理
*/
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
final Table table = delete.getTable();
if (tenantLineHandler.ignoreTable(table.getName())) {
// 过滤退出执行
return;
}
if (doTenantFilter(table.getName())) {
// 过滤退出执行
return;
}
delete.setWhere(this.andExpression(table, delete.getWhere(), (String) obj));
}
/**
* delete update 语句 where 处理
*/
@Override
protected Expression andExpression(Table table, Expression where, final String whereSegment) {
//获得where条件表达式
final Expression expression = buildTableExpression(table, where, whereSegment);
if (expression == null) {
return where;
}
if (where != null) {
if (where instanceof OrExpression) {
return new AndExpression(new Parenthesis(where), expression);
} else {
return new AndExpression(where, expression);
}
}
return expression;
}
/**
* 构建租户条件表达式
*
* @param table 表对象
* @param where 当前where条件
* @param whereSegment 所属Mapper对象全路径在原租户拦截器功能中这个参数并不需要参与相关判断
* @return 租户条件表达式
*/
@Override
public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
//若是忽略的表则不进行数据隔离
if (tenantLineHandler.ignoreTable(table.getName())) {
return null;
}
//若是超管则不进行数据隔离
if (doTenantFilter(table.getName())) {
return null;
}
//获得条件表达式
return new EqualsTo(getAliasColumn(table), tenantLineHandler.getTenantId());
}
private List<Table> processFromItem(FromItem fromItem, final String whereSegment) {
// 处理括号括起来的表达式
while (fromItem instanceof ParenthesisFromItem) {
fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
}
List<Table> mainTables = new ArrayList<>();
// 无 join 时的处理逻辑
if (fromItem instanceof Table) {
Table fromTable = (Table) fromItem;
mainTables.add(fromTable);
} else if (fromItem instanceof SubJoin) {
// SubJoin 类型则还需要添加上 where 条件
List<Table> tables = processSubJoin((SubJoin) fromItem, whereSegment);
mainTables.addAll(tables);
} else {
// 处理下 fromItem
processOtherFromItem(fromItem, whereSegment);
}
return mainTables;
}
/**
* 处理 sub join
*
* @param subJoin subJoin
* @return Table subJoin 中的主表
*/
private List<Table> processSubJoin(SubJoin subJoin, final String whereSegment) {
List<Table> mainTables = new ArrayList<>();
if (subJoin.getJoinList() != null) {
List<Table> list = processFromItem(subJoin.getLeft(), whereSegment);
mainTables.addAll(list);
mainTables = processJoins(mainTables, subJoin.getJoinList(), whereSegment);
}
return mainTables;
}
/**
* 处理 joins
*
* @param mainTables 可以为 null
* @param joins join 集合
* @return List<Table> 右连接查询的 Table 列表
*/
private List<Table> processJoins(List<Table> mainTables, List<Join> joins, final String whereSegment) {
// join 表达式中最终的主表
Table mainTable = null;
// 当前 join 的左表
Table leftTable = null;
if (mainTables.size() == 1) {
mainTable = mainTables.get(0);
leftTable = mainTable;
}
//对于 on 表达式写在最后的 join需要记录下前面多个 on 的表名
Deque<List<Table>> onTableDeque = new LinkedList<>();
for (Join join : joins) {
// 处理 on 表达式
FromItem joinItem = join.getRightItem();
// 获取当前 join 的表subJoint 可以看作是一张表
List<Table> joinTables = null;
if (joinItem instanceof Table) {
joinTables = new ArrayList<>();
joinTables.add((Table) joinItem);
} else if (joinItem instanceof SubJoin) {
joinTables = processSubJoin((SubJoin) joinItem, whereSegment);
}
if (joinTables != null) {
// 如果是隐式内连接
if (join.isSimple()) {
mainTables.addAll(joinTables);
continue;
}
// 当前表是否忽略
Table joinTable = joinTables.get(0);
List<Table> onTables = null;
// 如果不要忽略,且是右连接,则记录下当前表
if (join.isRight()) {
mainTable = joinTable;
mainTables.clear();
if (leftTable != null) {
onTables = Collections.singletonList(leftTable);
}
} else if (join.isInner()) {
if (mainTable == null) {
onTables = Collections.singletonList(joinTable);
} else {
onTables = Arrays.asList(mainTable, joinTable);
}
mainTable = null;
mainTables.clear();
} else {
onTables = Collections.singletonList(joinTable);
}
if (mainTable != null && !mainTables.contains(mainTable)) {
mainTables.add(mainTable);
}
// 获取 join 尾缀的 on 表达式列表
Collection<Expression> originOnExpressions = join.getOnExpressions();
// 正常 join on 表达式只有一个,立刻处理
if (originOnExpressions.size() == 1 && onTables != null) {
List<Expression> onExpressions = new LinkedList<>();
onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables, whereSegment));
join.setOnExpressions(onExpressions);
leftTable = mainTable == null ? joinTable : mainTable;
continue;
}
// 表名压栈,忽略的表压入 null以便后续不处理
onTableDeque.push(onTables);
// 尾缀多个 on 表达式的时候统一处理
if (originOnExpressions.size() > 1) {
Collection<Expression> onExpressions = new LinkedList<>();
for (Expression originOnExpression : originOnExpressions) {
List<Table> currentTableList = onTableDeque.poll();
if (CollectionUtils.isEmpty(currentTableList)) {
onExpressions.add(originOnExpression);
} else {
onExpressions.add(builderExpression(originOnExpression, currentTableList, whereSegment));
}
}
join.setOnExpressions(onExpressions);
}
leftTable = joinTable;
} else {
processOtherFromItem(joinItem, whereSegment);
leftTable = null;
}
}
return mainTables;
}
/**
* 判断当前操作是否需要进行过滤
*
* @param tableName 表名
*/
public boolean doTenantFilter(String tableName) {
return AuthUtil.isAdministrator() && !adminTenantTables.contains(tableName);
}
/**
* 判断当前操作是否需要进行过滤
*
* @param tables 表名
*/
public boolean doTenantFilters(List<Table> tables) {
List<String> tableNames = tables.stream().map(Table::getName).collect(Collectors.toList());
return AuthUtil.isAdministrator() && !CollectionUtil.containsAny(adminTenantTables, tableNames);
}
}

View File

@@ -0,0 +1,80 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* 多租户配置
*
* @author Chill
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "blade.tenant")
public class BladeTenantProperties {
/**
* 是否增强多租户
*/
private Boolean enhance = Boolean.FALSE;
/**
* 是否开启授权码校验
*/
private Boolean license = Boolean.FALSE;
/**
* 是否开启动态数据源功能
*/
private Boolean dynamicDatasource = Boolean.FALSE;
/**
* 是否开启动态数据源全局扫描
*/
private Boolean dynamicGlobal = Boolean.FALSE;
/**
* 多租户字段名称
*/
private String column = "tenant_id";
/**
* 是否开启注解排除
*/
private Boolean annotationExclude = Boolean.FALSE;
/**
* 需要排除进行自定义的多租户表
*/
private List<String> excludeTables = new ArrayList<>();
}

View File

@@ -0,0 +1,42 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant;
/**
* 租户id生成器
*
* @author Chill
*/
public interface TenantId {
/**
* 生成自定义租户id
*
* @return tenantId
*/
String generate();
}

View File

@@ -0,0 +1,39 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.annotation;
import java.lang.annotation.*;
/**
* 排除租户数据源自动切换.
*
* @author Chill
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NonDS {
}

View File

@@ -0,0 +1,43 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* 指定租户表排除.
*
* @author Chill
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Component
public @interface TableExclude {
String value() default "";
}

View File

@@ -0,0 +1,42 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.annotation;
import com.baomidou.dynamic.datasource.annotation.DS;
import java.lang.annotation.*;
/**
* 指定租户动态数据源切换.
*
* @author Chill
*/
@DS("#token.tenantId")
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantDS {
}

View File

@@ -0,0 +1,39 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.annotation;
import java.lang.annotation.*;
/**
* 排除租户逻辑.
*
* @author Chill
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantIgnore {
}

View File

@@ -0,0 +1,42 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.annotation;
import com.baomidou.dynamic.datasource.annotation.DS;
import java.lang.annotation.*;
/**
* 指定租户ID动态数据源切换.
*
* @author Chill
*/
@DS("#tenantId")
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantParamDS {
}

View File

@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springblade.core.tenant.BladeTenantHolder;
import org.springblade.core.tenant.annotation.TenantIgnore;
/**
* 自定义租户切面
*
* @author Chill
*/
@Slf4j
@Aspect
public class BladeTenantAspect {
@Around("@annotation(tenantIgnore)")
public Object around(ProceedingJoinPoint point, TenantIgnore tenantIgnore) throws Throwable {
try {
//开启忽略
BladeTenantHolder.setIgnore(Boolean.TRUE);
//执行方法
return point.proceed();
} finally {
//关闭忽略
BladeTenantHolder.clear();
}
}
}

View File

@@ -0,0 +1,97 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.config;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.config.MybatisPlusConfiguration;
import org.springblade.core.tenant.*;
import org.springblade.core.tenant.aspect.BladeTenantAspect;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* 多租户配置类
*
* @author Chill
*/
@AllArgsConstructor
@AutoConfiguration(before = MybatisPlusConfiguration.class)
@EnableConfigurationProperties(BladeTenantProperties.class)
public class TenantConfiguration {
/**
* 自定义多租户处理器
*
* @param tenantProperties 多租户配置类
* @return TenantHandler
*/
@Bean
@Primary
public TenantLineHandler bladeTenantHandler(BladeTenantProperties tenantProperties) {
return new BladeTenantHandler(tenantProperties);
}
/**
* 自定义租户拦截器
*
* @param tenantHandler 多租户处理器
* @param tenantProperties 多租户配置类
* @return BladeTenantInterceptor
*/
@Bean
@Primary
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantHandler, BladeTenantProperties tenantProperties) {
BladeTenantInterceptor tenantInterceptor = new BladeTenantInterceptor();
tenantInterceptor.setTenantLineHandler(tenantHandler);
tenantInterceptor.setTenantProperties(tenantProperties);
return tenantInterceptor;
}
/**
* 自定义租户id生成器
*
* @return TenantId
*/
@Bean
@ConditionalOnMissingBean(TenantId.class)
public TenantId tenantId() {
return new BladeTenantId();
}
/**
* 自定义租户切面
*/
@Bean
public BladeTenantAspect bladeTenantAspect() {
return new BladeTenantAspect();
}
}

View File

@@ -0,0 +1,110 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.constant;
/**
* 租户常量.
*
* @author Chill
*/
public interface TenantBaseConstant {
/**
* 租户数据源缓存名
*/
String TENANT_DATASOURCE_CACHE = "blade:datasource";
/**
* 租户数据源缓存键
*/
String TENANT_DATASOURCE_KEY = "tenant:id:";
/**
* 租户数据源缓存键
*/
String TENANT_DATASOURCE_EXIST_KEY = "tenant:exist:";
/**
* 租户动态数据源键
*/
String TENANT_DYNAMIC_DATASOURCE_PROP = "blade.tenant.dynamic-datasource";
/**
* 租户全局动态数据源切面键
*/
String TENANT_DYNAMIC_GLOBAL_PROP = "blade.tenant.dynamic-global";
/**
* 租户是否存在数据源
*/
String TENANT_DATASOURCE_EXIST_STATEMENT = "select datasource_id from blade_tenant WHERE is_deleted = 0 AND tenant_id = ?";
/**
* 租户数据源基础SQL
*/
String TENANT_DATASOURCE_BASE_STATEMENT = "SELECT category, tenant_id as tenantId, driver_class as driverClass, url, username, password, sharding_config as shardingConfig from blade_tenant tenant LEFT JOIN blade_datasource datasource ON tenant.datasource_id = datasource.id ";
/**
* 租户单数据源SQL
*/
String TENANT_DATASOURCE_SINGLE_STATEMENT = TENANT_DATASOURCE_BASE_STATEMENT + "WHERE tenant.is_deleted = 0 AND tenant.tenant_id = ?";
/**
* 租户集动态数据源SQL
*/
String TENANT_DATASOURCE_GROUP_STATEMENT = TENANT_DATASOURCE_BASE_STATEMENT + "WHERE tenant.is_deleted = 0";
/**
* 租户未找到返回信息
*/
String TENANT_DATASOURCE_NOT_FOUND = "未找到租户信息,数据源加载失败!";
/**
* oracle驱动类
*/
String ORACLE_DRIVER_CLASS = "oracle.jdbc.OracleDriver";
/**
* oracle校验
*/
String ORACLE_VALIDATE_STATEMENT = "select 1 from dual";
/**
* 通用校验
*/
String COMMON_VALIDATE_STATEMENT = "select 1";
/**
* jdbc数据源分类
*/
int JDBC_CATEGORY = 1;
/**
* sharding数据源分类
*/
int SHARDING_CATEGORY = 2;
}

View File

@@ -0,0 +1,52 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.exception;
/**
* 租户数据源异常
*
* @author Chill
*/
public class TenantDataSourceException extends RuntimeException {
public TenantDataSourceException(String message) {
super(message);
}
/**
* 提高性能
*
* @return Throwable
*/
@Override
public Throwable fillInStackTrace() {
return this;
}
public Throwable doFillInStackTrace() {
return super.fillInStackTrace();
}
}

View File

@@ -0,0 +1,49 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.tenant.mp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.mp.base.BaseEntity;
/**
* 租户基础实体类
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TenantEntity extends BaseEntity {
/**
* 租户ID
*/
@Schema(description = "租户ID")
private String tenantId;
}