init
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?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>
|
||||
|
||||
<properties>
|
||||
<module.name>org.springblade.blade.starter.tenant</module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--Blade-->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-context</artifactId>
|
||||
</dependency>
|
||||
<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>
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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.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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 租户信息处理器
|
||||
*
|
||||
* @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(TenantUtil.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;
|
||||
}
|
||||
if (TenantUtil.isIgnore()) {
|
||||
return true;
|
||||
}
|
||||
return !(tenantTableList.contains(tableName) && StringUtil.isNotBlank(TenantUtil.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2024, baomidou (jobob@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springblade.core.tenant;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
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.schema.Table;
|
||||
import net.sf.jsqlparser.statement.select.FromItem;
|
||||
import net.sf.jsqlparser.statement.select.Join;
|
||||
import net.sf.jsqlparser.statement.select.ParenthesedFromItem;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 租户基础拦截器,拓展mybatis-plus私有方法便于继承
|
||||
*
|
||||
* @author Chill、hubin
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BladeTenantInnerInterceptor extends TenantLineInnerInterceptor {
|
||||
|
||||
@Override
|
||||
public void setTenantLineHandler(TenantLineHandler tenantLineHandler) {
|
||||
super.setTenantLineHandler(tenantLineHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主表列表
|
||||
*/
|
||||
protected List<Table> processMainTables(final PlainSelect plainSelect, final String whereSegment) {
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
List<Table> list = processFromItem(fromItem, whereSegment);
|
||||
return new ArrayList<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将父类方法修改为 protected,便于子类继承
|
||||
*/
|
||||
protected List<Table> processFromItem(FromItem fromItem, final String whereSegment) {
|
||||
// 处理括号括起来的表达式
|
||||
List<Table> mainTables = new ArrayList<>();
|
||||
// 无 join 时的处理逻辑
|
||||
if (fromItem instanceof Table) {
|
||||
Table fromTable = (Table) fromItem;
|
||||
mainTables.add(fromTable);
|
||||
} else if (fromItem instanceof ParenthesedFromItem) {
|
||||
// SubJoin 类型则还需要添加上 where 条件
|
||||
List<Table> tables = processSubJoin((ParenthesedFromItem) fromItem, whereSegment);
|
||||
mainTables.addAll(tables);
|
||||
} else {
|
||||
// 处理下 fromItem
|
||||
processOtherFromItem(fromItem, whereSegment);
|
||||
}
|
||||
return mainTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将父类方法修改为 protected,便于子类继承
|
||||
*/
|
||||
protected List<Table> processSubJoin(ParenthesedFromItem subJoin, final String whereSegment) {
|
||||
while (subJoin.getJoins() == null && subJoin.getFromItem() instanceof ParenthesedFromItem) {
|
||||
subJoin = (ParenthesedFromItem) subJoin.getFromItem();
|
||||
}
|
||||
List<Table> tableList = processFromItem(subJoin.getFromItem(), whereSegment);
|
||||
List<Table> mainTables = new ArrayList<>(tableList);
|
||||
if (subJoin.getJoins() != null) {
|
||||
processJoins(mainTables, subJoin.getJoins(), whereSegment);
|
||||
}
|
||||
return mainTables;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将父类方法修改为 protected,便于子类继承
|
||||
*/
|
||||
protected 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 ParenthesedFromItem) {
|
||||
joinTables = processSubJoin((ParenthesedFromItem) joinItem, whereSegment);
|
||||
}
|
||||
|
||||
if (joinTables != null && !joinTables.isEmpty()) {
|
||||
|
||||
// 如果是隐式内连接
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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.extension.plugins.handler.TenantLineHandler;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
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.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.update.Update;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.CollectionUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 租户拦截器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BladeTenantInterceptor extends BladeTenantInnerInterceptor {
|
||||
|
||||
/**
|
||||
* 租户配置文件
|
||||
*/
|
||||
private BladeTenantProperties tenantProperties;
|
||||
/**
|
||||
* 超管需要启用租户过滤的表
|
||||
*/
|
||||
private List<String> adminTenantTables = Arrays.asList("blade_top_menu", "blade_dict_biz");
|
||||
|
||||
@Override
|
||||
public void setTenantLineHandler(TenantLineHandler tenantLineHandler) {
|
||||
super.setTenantLineHandler(tenantLineHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* select 条件处理
|
||||
*/
|
||||
@Override
|
||||
protected void processPlainSelect(final PlainSelect plainSelect, final String whereSegment) {
|
||||
// 判断 mainTable 进行条件追加
|
||||
List<Table> mainTables = super.processMainTables(plainSelect, whereSegment);
|
||||
if (CollectionUtils.isNotEmpty(mainTables) && !doTenantFilters(mainTables)) {
|
||||
super.processPlainSelect(plainSelect, whereSegment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* insert 条件处理
|
||||
*/
|
||||
@Override
|
||||
protected void processInsert(Insert insert, int index, String sql, Object obj) {
|
||||
// 官方已支持租户ID自定义设置,无需再定义租户增强功能
|
||||
super.processInsert(insert, index, sql, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* update 条件处理
|
||||
*/
|
||||
@Override
|
||||
protected void processUpdate(Update update, int index, String sql, Object obj) {
|
||||
final Table table = update.getTable();
|
||||
if (doTenantFilter(table.getName())) {
|
||||
// 过滤退出执行
|
||||
return;
|
||||
}
|
||||
super.processUpdate(update, index, sql, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete 条件处理
|
||||
*/
|
||||
@Override
|
||||
protected void processDelete(Delete delete, int index, String sql, Object obj) {
|
||||
final Table table = delete.getTable();
|
||||
if (doTenantFilter(table.getName())) {
|
||||
// 过滤退出执行
|
||||
return;
|
||||
}
|
||||
super.processDelete(delete, index, sql, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前操作是否需要进行过滤
|
||||
*
|
||||
* @param tableName 表名
|
||||
*/
|
||||
public boolean doTenantFilter(String tableName) {
|
||||
return AuthUtil.isAdministrator() && tenantProperties.getEnhance() && !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() && tenantProperties.getEnhance() && !CollectionUtil.containsAny(adminTenantTables, tableNames);
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -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.TRUE;
|
||||
|
||||
/**
|
||||
* 是否开启授权码校验
|
||||
*/
|
||||
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<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 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.extension.service.IService;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tenant.exception.TenantException;
|
||||
import org.springblade.core.tool.utils.CollectionUtil;
|
||||
import org.springblade.core.tool.utils.ReflectUtil;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 租户归属守卫
|
||||
* <p>
|
||||
* 统一拦截「目标实体是否属于当前会话租户」的越权写操作。平台超管(administrator)默认放行。
|
||||
* 通过反射访问实体的 getId / getTenantId / setTenantId 方法,调用方无需传入 lambda。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class TenantGuard {
|
||||
|
||||
private static final String METHOD_GET_ID = "getId";
|
||||
private static final String METHOD_GET_TENANT_ID = "getTenantId";
|
||||
private static final String METHOD_SET_TENANT_ID = "setTenantId";
|
||||
|
||||
private static final ConcurrentHashMap<Class<?>, Method> ID_GETTER_CACHE = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<Class<?>, Method> TENANT_GETTER_CACHE = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<Class<?>, Method> TENANT_SETTER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private TenantGuard() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 受租户隔离保护的实体类型
|
||||
* <p>
|
||||
* 集中管理实体中文标签,避免调用处散落硬编码字符串。新增受保护实体时,
|
||||
* 在此枚举追加常量即可,调用方使用 static import 让代码更紧凑。
|
||||
*/
|
||||
public enum EntityType {
|
||||
|
||||
ROLE("角色"),
|
||||
ROLE_PARENT("上级角色"),
|
||||
DEPT("部门"),
|
||||
DEPT_PARENT("上级部门"),
|
||||
POST("岗位"),
|
||||
USER("用户"),
|
||||
;
|
||||
|
||||
private final String label;
|
||||
|
||||
EntityType(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String label() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个 id 归属当前会话租户
|
||||
* <p>
|
||||
* 适用于 update/detail by id 等场景。校验通过后返回查询出的实体,
|
||||
* 调用方可基于返回值进行后续业务(如把 existEntity.tenantId 回写到入参以防篡改)。
|
||||
*
|
||||
* @param service MyBatis-Plus IService 实例
|
||||
* @param id 目标主键
|
||||
* @param entityType 实体类型枚举
|
||||
* @param <T> 实体类型
|
||||
* @return 查询出的实体(仅当超管查询不存在的 id 时可能为 null)
|
||||
* @throws TenantException 当非超管且目标实体不存在或归属其他租户时抛出
|
||||
*/
|
||||
public static <T> T verify(IService<T> service, Long id, EntityType entityType) {
|
||||
T entity = service.getById(id);
|
||||
if (AuthUtil.isAdministrator()) {
|
||||
return entity;
|
||||
}
|
||||
if (entity == null || !AuthUtil.getTenantId().equals(tenantIdOf(entity))) {
|
||||
throw new TenantException("无权操作非本租户的" + entityType.label());
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交时(新增 / 修改)的租户绑定守卫
|
||||
* <p>
|
||||
* 新增(id 为空):超管放行,非超管强制写入当前会话 tenantId,避免前端注入。<br/>
|
||||
* 修改(id 不空):调用 {@link #verify} 校验归属,并把已存在实体的 tenantId 回写到入参,防止 update 篡改。
|
||||
*
|
||||
* @param service MyBatis-Plus IService 实例
|
||||
* @param entity 入参实体
|
||||
* @param entityType 实体类型
|
||||
* @param <T> 实体类型
|
||||
* @throws TenantException 当修改路径下目标实体不存在或归属其他租户时抛出
|
||||
*/
|
||||
public static <T> void bindTenant(IService<T> service, T entity, EntityType entityType) {
|
||||
Long id = idOf(entity);
|
||||
if (id == null) {
|
||||
if (!AuthUtil.isAdministrator()) {
|
||||
bindTenantId(entity, AuthUtil.getTenantId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
T existEntity = verify(service, id, entityType);
|
||||
if (existEntity == null) {
|
||||
return;
|
||||
}
|
||||
// 非超管路径下 verify 已确保 existEntity.tenantId 等于会话 tenantId,直接复用避免重复反射
|
||||
String tenantId = AuthUtil.isAdministrator() ? tenantIdOf(existEntity) : AuthUtil.getTenantId();
|
||||
bindTenantId(entity, tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验批量 ids 全部归属当前会话租户,并返回查询出的实体列表
|
||||
* <p>
|
||||
* 适用于 batch remove/grant/reset/unlock 等场景。任一 id 不存在或归属其他租户,整体抛异常。
|
||||
* 超管放行。返回值供调用方复用,避免二次查询。
|
||||
*
|
||||
* @param service MyBatis-Plus IService 实例
|
||||
* @param ids 目标主键集合
|
||||
* @param entityType 实体类型枚举
|
||||
* @param <T> 实体类型
|
||||
* @return 查询出的实体列表(ids 为空时返回空列表)
|
||||
* @throws TenantException 当非超管且任一目标实体不存在或归属其他租户时抛出
|
||||
*/
|
||||
public static <T> List<T> verifyBatch(IService<T> service, List<Long> ids, EntityType entityType) {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 去重入参主键,避免重复 id 导致查询行数不等于入参数量而误判为越权
|
||||
List<Long> distinctIds = ids.stream().distinct().toList();
|
||||
List<T> list = service.listByIds(distinctIds);
|
||||
if (AuthUtil.isAdministrator()) {
|
||||
return list;
|
||||
}
|
||||
String currentTenantId = AuthUtil.getTenantId();
|
||||
if (list.size() != distinctIds.size() || list.stream().anyMatch(entity -> !currentTenantId.equals(tenantIdOf(entity)))) {
|
||||
throw new TenantException("无权操作非本租户的" + entityType.label());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射读取实体主键
|
||||
*/
|
||||
private static Long idOf(Object entity) {
|
||||
Method method = ID_GETTER_CACHE.computeIfAbsent(entity.getClass(),
|
||||
clazz -> requireMethod(clazz, METHOD_GET_ID));
|
||||
Object value = ReflectUtil.invokeMethod(method, entity);
|
||||
if (value != null && !(value instanceof Long)) {
|
||||
throw new IllegalArgumentException("实体 " + entity.getClass().getSimpleName() + " 的主键类型必须为 Long");
|
||||
}
|
||||
return (Long) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射读取实体 tenantId
|
||||
*/
|
||||
private static String tenantIdOf(Object entity) {
|
||||
Method method = TENANT_GETTER_CACHE.computeIfAbsent(entity.getClass(),
|
||||
clazz -> requireMethod(clazz, METHOD_GET_TENANT_ID));
|
||||
return (String) ReflectUtil.invokeMethod(method, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射写入实体 tenantId
|
||||
*/
|
||||
private static void bindTenantId(Object entity, String tenantId) {
|
||||
Method method = TENANT_SETTER_CACHE.computeIfAbsent(entity.getClass(),
|
||||
clazz -> requireMethod(clazz, METHOD_SET_TENANT_ID, String.class));
|
||||
ReflectUtil.invokeMethod(method, entity, tenantId);
|
||||
}
|
||||
|
||||
private static Method requireMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
|
||||
Method method = ReflectUtil.findMethod(clazz, name, paramTypes);
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException("实体 " + clazz.getSimpleName() + " 缺少 " + name + " 方法,不支持租户守卫");
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Tenant 工具
|
||||
*
|
||||
* @author L.cm,BladeX
|
||||
*/
|
||||
@UtilityClass
|
||||
public class TenantUtil {
|
||||
|
||||
/**
|
||||
* 租户ID线程
|
||||
*/
|
||||
private static final ThreadLocal<String> TENANT_ID_HOLDER = new NamedThreadLocal<>("blade-tenant-id") {
|
||||
@Override
|
||||
protected String initialValue() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 租户状态线程
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> TENANT_IGNORE_HOLDER = new NamedThreadLocal<>("blade-tenant-ignore") {
|
||||
@Override
|
||||
protected Boolean initialValue() {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取租户id
|
||||
*
|
||||
* @return 租户id
|
||||
*/
|
||||
public static String getTenantId() {
|
||||
String tenantId = TENANT_ID_HOLDER.get();
|
||||
if (tenantId != null) {
|
||||
return tenantId;
|
||||
}
|
||||
return AuthUtil.getTenantId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用租户 id 执行函数
|
||||
*
|
||||
* @param tenantId tenantId
|
||||
* @param supplier supplier
|
||||
* @param <R> 泛型
|
||||
* @return R 函数返回
|
||||
*/
|
||||
public static <R> R use(String tenantId, Supplier<R> supplier) {
|
||||
Assert.hasText(tenantId, "参数 tenantId 为空");
|
||||
try {
|
||||
TENANT_ID_HOLDER.set(tenantId);
|
||||
return supplier.get();
|
||||
} finally {
|
||||
TENANT_ID_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用租户 id 执行函数
|
||||
*
|
||||
* @param tenantId tenantId
|
||||
* @param runnable Runnable
|
||||
*/
|
||||
public static void use(String tenantId, Runnable runnable) {
|
||||
Assert.hasText(tenantId, "参数 tenantId 为空");
|
||||
try {
|
||||
TENANT_ID_HOLDER.set(tenantId);
|
||||
runnable.run();
|
||||
} finally {
|
||||
TENANT_ID_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否忽略租户
|
||||
*/
|
||||
public static Boolean isIgnore() {
|
||||
return TENANT_IGNORE_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略租户 执行函数
|
||||
*
|
||||
* @param supplier supplier
|
||||
* @param <R> 泛型
|
||||
* @return R 函数返回
|
||||
*/
|
||||
public static <R> R ignore(Supplier<R> supplier) {
|
||||
try {
|
||||
TENANT_IGNORE_HOLDER.set(Boolean.TRUE);
|
||||
return supplier.get();
|
||||
} finally {
|
||||
TENANT_IGNORE_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略租户 执行函数
|
||||
*
|
||||
* @param runnable Runnable
|
||||
*/
|
||||
public static void ignore(Runnable runnable) {
|
||||
try {
|
||||
TENANT_IGNORE_HOLDER.set(Boolean.TRUE);
|
||||
runnable.run();
|
||||
} finally {
|
||||
TENANT_IGNORE_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
+43
@@ -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 "";
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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 TenantAsync {
|
||||
/**
|
||||
* 是否开启租户动态数据源
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
boolean datasource() default false;
|
||||
}
|
||||
+39
@@ -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 TenantDS {
|
||||
}
|
||||
+39
@@ -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 {
|
||||
}
|
||||
+42
@@ -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 {
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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 com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springblade.core.context.BladeContext;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tenant.TenantUtil;
|
||||
import org.springblade.core.tenant.annotation.TenantAsync;
|
||||
import org.springblade.core.tenant.exception.TenantException;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 租户异步切面
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class BladeTenantAsyncAspect {
|
||||
|
||||
private final BladeContext bladeContext;
|
||||
|
||||
@Pointcut("@within(org.springblade.core.tenant.annotation.TenantAsync) || " +
|
||||
"@annotation(org.springblade.core.tenant.annotation.TenantAsync)")
|
||||
public void tenantAsyncPointcut() {
|
||||
}
|
||||
|
||||
@Around("tenantAsyncPointcut()")
|
||||
public Object handleTenantAsync(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
// 获取注解和租户信息
|
||||
TenantAsync tenantAsync = resolveTenantAsync(joinPoint);
|
||||
|
||||
// 优先从认证信息获取租户ID,其次从上下文获取
|
||||
String tenantId = StringUtil.isNotBlank(AuthUtil.getTenantId())
|
||||
? AuthUtil.getTenantId()
|
||||
: bladeContext.getTenantId();
|
||||
|
||||
// 无需处理租户切换的情况
|
||||
if (tenantAsync == null || Func.isBlank(tenantId)) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
// 根据配置执行租户操作
|
||||
return executeTenantOperation(joinPoint, tenantId, tenantAsync.datasource());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析TenantAsync注解
|
||||
*/
|
||||
private TenantAsync resolveTenantAsync(ProceedingJoinPoint joinPoint) {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
|
||||
// 优先从方法获取注解
|
||||
TenantAsync tenantAsync = AnnotationUtils.findAnnotation(method, TenantAsync.class);
|
||||
if (tenantAsync == null) {
|
||||
// 从类级别获取注解
|
||||
tenantAsync = AnnotationUtils.findAnnotation(joinPoint.getTarget().getClass(), TenantAsync.class);
|
||||
}
|
||||
return tenantAsync;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行租户操作
|
||||
*/
|
||||
private Object executeTenantOperation(ProceedingJoinPoint joinPoint, String tenantId, boolean useDatasource) {
|
||||
if (!useDatasource) {
|
||||
// 仅切换租户上下文
|
||||
return executeWithTenantContext(joinPoint, tenantId);
|
||||
}
|
||||
|
||||
// 需要切换数据源的情况
|
||||
try {
|
||||
DynamicDataSourceContextHolder.push(tenantId);
|
||||
return executeWithTenantContext(joinPoint, tenantId);
|
||||
} finally {
|
||||
DynamicDataSourceContextHolder.poll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在租户上下文中执行业务逻辑
|
||||
*/
|
||||
private Object executeWithTenantContext(ProceedingJoinPoint joinPoint, String tenantId) {
|
||||
return TenantUtil.use(tenantId, () -> {
|
||||
try {
|
||||
return joinPoint.proceed();
|
||||
} catch (Throwable e) {
|
||||
throw new TenantException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.aspectj.lang.annotation.Pointcut;
|
||||
import org.springblade.core.tenant.BladeTenantHolder;
|
||||
|
||||
/**
|
||||
* 自定义租户切面
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
public class BladeTenantIgnoreAspect {
|
||||
|
||||
@Pointcut("@within(org.springblade.core.tenant.annotation.TenantIgnore) || " +
|
||||
"@annotation(org.springblade.core.tenant.annotation.TenantIgnore)")
|
||||
public void tenantIgnorePointcut() {
|
||||
}
|
||||
|
||||
@Around("tenantIgnorePointcut()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
try {
|
||||
//开启忽略
|
||||
BladeTenantHolder.setIgnore(Boolean.TRUE);
|
||||
//执行方法
|
||||
return point.proceed();
|
||||
} finally {
|
||||
//关闭忽略
|
||||
BladeTenantHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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.context.BladeContext;
|
||||
import org.springblade.core.mp.config.MybatisPlusConfiguration;
|
||||
import org.springblade.core.tenant.*;
|
||||
import org.springblade.core.tenant.aspect.BladeTenantAsyncAspect;
|
||||
import org.springblade.core.tenant.aspect.BladeTenantIgnoreAspect;
|
||||
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 BladeTenantIgnoreAspect bladeTenantIgnoreAspect() {
|
||||
return new BladeTenantIgnoreAspect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义租户异步切面
|
||||
*/
|
||||
@Bean
|
||||
public BladeTenantAsyncAspect bladeTenantAsyncAspect(BladeContext bladeContext) {
|
||||
return new BladeTenantAsyncAspect(bladeContext);
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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:";
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.error;
|
||||
|
||||
import jakarta.servlet.Servlet;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tenant.exception.TenantDataSourceException;
|
||||
import org.springblade.core.tenant.exception.TenantException;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* 租户异常处理器
|
||||
* <p>
|
||||
* 仅当 tenant starter 上线时随 {@code TenantConfiguration} 一起激活,把租户域内的两类异常翻译到 HTTP:
|
||||
* <ul>
|
||||
* <li>{@link TenantException} — 越权访问 / 租户隔离违规,返回 HTTP 403 Forbidden</li>
|
||||
* <li>{@link TenantDataSourceException} — 租户数据源切换失败 / 配置缺失,返回 HTTP 500 Internal Server Error</li>
|
||||
* </ul>
|
||||
* 优先级高于全局兜底翻译器,避免被 {@code Throwable} handler 吞掉。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@RestControllerAdvice
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class BladeTenantExceptionTranslator {
|
||||
|
||||
@ExceptionHandler(TenantException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public R handleError(TenantException e) {
|
||||
log.error("租户异常", e);
|
||||
return R.fail(e.getResultCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(TenantDataSourceException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public R handleError(TenantDataSourceException e) {
|
||||
log.error("租户数据源异常", e);
|
||||
return R.fail(e.getResultCode(), e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -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.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springblade.core.tool.api.IResultCode;
|
||||
import org.springblade.core.tool.api.ResultCode;
|
||||
|
||||
/**
|
||||
* 租户数据源异常
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class TenantDataSourceException extends RuntimeException {
|
||||
|
||||
@Getter
|
||||
private final IResultCode resultCode;
|
||||
|
||||
public TenantDataSourceException(String message) {
|
||||
super(message);
|
||||
this.resultCode = ResultCode.FAILURE;
|
||||
}
|
||||
|
||||
public TenantDataSourceException(Throwable throwable) {
|
||||
super(throwable);
|
||||
this.resultCode = ResultCode.FAILURE;
|
||||
}
|
||||
|
||||
public TenantDataSourceException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
this.resultCode = ResultCode.FAILURE;
|
||||
}
|
||||
|
||||
public TenantDataSourceException(IResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
public TenantDataSourceException(IResultCode resultCode, Throwable cause) {
|
||||
super(cause);
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提高性能
|
||||
*
|
||||
* @return Throwable
|
||||
*/
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Throwable doFillInStackTrace() {
|
||||
return super.fillInStackTrace();
|
||||
}
|
||||
}
|
||||
+80
@@ -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.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springblade.core.tool.api.IResultCode;
|
||||
import org.springblade.core.tool.api.ResultCode;
|
||||
|
||||
/**
|
||||
* 租户异常
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class TenantException extends RuntimeException {
|
||||
|
||||
@Getter
|
||||
private final IResultCode resultCode;
|
||||
|
||||
public TenantException(String message) {
|
||||
super(message);
|
||||
this.resultCode = ResultCode.FAILURE;
|
||||
}
|
||||
|
||||
public TenantException(Throwable throwable) {
|
||||
super(throwable);
|
||||
this.resultCode = ResultCode.FAILURE;
|
||||
}
|
||||
|
||||
public TenantException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
this.resultCode = ResultCode.FAILURE;
|
||||
}
|
||||
|
||||
public TenantException(IResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
public TenantException(IResultCode resultCode, Throwable cause) {
|
||||
super(cause);
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提高性能
|
||||
*
|
||||
* @return Throwable
|
||||
*/
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Throwable doFillInStackTrace() {
|
||||
return super.fillInStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user