add
This commit is contained in:
@ -31,6 +31,10 @@ qiaoba:
|
||||
max: 20
|
||||
is-use: false # 不使用该库
|
||||
|
||||
# 连接测试query(配置检测连接是否有效)
|
||||
connectionTestQuery: SELECT 1
|
||||
# 多久检查一次连接的活性
|
||||
keepaliveTime: 30000
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
|
@ -1,19 +0,0 @@
|
||||
package com.qiaoba.common.base;
|
||||
|
||||
/**
|
||||
* 数据源接口
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/8 17:23
|
||||
*/
|
||||
public interface DatasourceService {
|
||||
|
||||
/**
|
||||
* 检查租户是否允许访问
|
||||
*
|
||||
* @param tenantId tenantId
|
||||
* @return true = 允许
|
||||
*/
|
||||
boolean checkTenantInfo(String tenantId);
|
||||
}
|
@ -29,6 +29,11 @@ public class BaseConstant {
|
||||
*/
|
||||
public static final String COLON_JOIN_STR = ":";
|
||||
|
||||
/**
|
||||
* 中划线拼接符号: '-'
|
||||
*/
|
||||
public static final String HYPHEN_JOIN_STR = "-";
|
||||
|
||||
/**
|
||||
* 树的key的命名
|
||||
*/
|
||||
|
@ -14,6 +14,11 @@ public class BaseContext {
|
||||
*/
|
||||
private static final ThreadLocal<String> DATABASE_TYPE_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* SCHEMA
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> SCHEMA_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@ -66,6 +71,24 @@ public class BaseContext {
|
||||
DATASOURCE_HOLDER.set(dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是 Schema模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public static Boolean getSchema() {
|
||||
return SCHEMA_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否是 Schema模式
|
||||
*
|
||||
* @param isSchema 是/否
|
||||
*/
|
||||
public static void setSchema(boolean isSchema) {
|
||||
SCHEMA_HOLDER.set(isSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有的ThreadLocal
|
||||
*/
|
||||
@ -76,7 +99,8 @@ public class BaseContext {
|
||||
TENANT_ID_HOLDER.remove();
|
||||
// 清除上下文中数据库类型
|
||||
DATABASE_TYPE_HOLDER.remove();
|
||||
|
||||
// 清除上下文中 SCHEMA
|
||||
SCHEMA_HOLDER.remove();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,12 +1,18 @@
|
||||
package com.qiaoba.common.database.config;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.qiaoba.common.base.constants.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.base.enums.BaseEnum;
|
||||
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import com.qiaoba.common.database.properties.DefaultDataSourceProperties;
|
||||
import com.qiaoba.common.database.properties.MasterInfo;
|
||||
import com.qiaoba.common.database.properties.PoolInfo;
|
||||
import com.qiaoba.common.database.properties.SlaveInfo;
|
||||
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
||||
import com.qiaoba.common.database.utils.JdbcUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -15,10 +21,7 @@ import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@ -32,40 +35,88 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Configuration
|
||||
public class DynamicDataSourceConfig {
|
||||
|
||||
/**
|
||||
* Bean 必须叫 dataSource
|
||||
*/
|
||||
@Resource
|
||||
private DynamicDataSourceContext dataSource;
|
||||
@Resource
|
||||
private DefaultDataSourceProperties defaultDataSourceProperties;
|
||||
@Resource
|
||||
private DynamicDatasourceService dynamicDatasourceService;
|
||||
|
||||
public static Map<Object, Object> DATA_SOURCE_MAP = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* 使用中的数据源
|
||||
*/
|
||||
public static Map<Object, Object> MASTER_SOURCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 备用数据源
|
||||
*/
|
||||
public static Map<String, List<SlaveInfo>> SLAVE_SOURCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 把DynamicDataSourceContext 纳入容器管理,其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象,并调用freshDataSource方法
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 加载系统默认数据源
|
||||
initDefault();
|
||||
// 加载租户数据源
|
||||
initTenant();
|
||||
}
|
||||
|
||||
private void initDefault() {
|
||||
MasterInfo master = defaultDataSourceProperties.getMaster();
|
||||
List<SlaveInfo> slaves = defaultDataSourceProperties.getSlaves();
|
||||
addDataSourceToMap(DynamicDatasourceConstant.DEFAULT_MASTER_DATASOURCE_KEY, buildDataSource(master.getDriver(), master.getUrl(), master.getUsername(), master.getPassword(), master.getPool()));
|
||||
// 将主数据源使用
|
||||
addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID, buildDataSource(master.getDriver(), master.getUrl(), master.getUsername(), master.getPassword(), master.getPool()));
|
||||
dataSource.freshDataSource(MASTER_SOURCE_MAP);
|
||||
// 将从数据源备用
|
||||
if (CollUtil.isNotEmpty(slaves)) {
|
||||
for (int i = 0; i < slaves.size(); i++) {
|
||||
SlaveInfo slave = slaves.get(i);
|
||||
if (slave.getIsUse()) {
|
||||
addDataSourceToMap(DynamicDatasourceConstant.DEFAULT_SLAVE_DATASOURCE_PREFIX + i, buildDataSource(slave.getDriver(), slave.getUrl(), slave.getUsername(), slave.getPassword(), slave.getPool()));
|
||||
if (!slaves.get(i).getIsUse()) {
|
||||
slaves.remove(i);
|
||||
}
|
||||
}
|
||||
if (CollUtil.isNotEmpty(slaves)) {
|
||||
SLAVE_SOURCE_MAP.put(DynamicDatasourceConstant.SLAVE_PREFIX + TenantConstant.DEFAULT_TENANT_ID, slaves);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initTenant() {
|
||||
setDefaultSetting();
|
||||
List<DynamicDataSource> dynamicDataSourceList = dynamicDatasourceService.loadAllTenantDatasource();
|
||||
for (DynamicDataSource dynamicDataSource : dynamicDataSourceList) {
|
||||
// 将主数据源加载
|
||||
if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsMaster())) {
|
||||
addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId(), buildDataSource(dynamicDataSource.getDriver(), dynamicDataSource.getUrl(), dynamicDataSource.getUsername(), dynamicDataSource.getPassword(), new PoolInfo(dynamicDataSource.getInitCount(), dynamicDataSource.getMinCount(), dynamicDataSource.getMaxCount())));
|
||||
dataSource.freshDataSource(MASTER_SOURCE_MAP);
|
||||
}
|
||||
// 将从数据源备用
|
||||
else {
|
||||
if (Objects.isNull(SLAVE_SOURCE_MAP.get(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId()))) {
|
||||
List<SlaveInfo> slaves = new ArrayList<>();
|
||||
slaves.add(datasourceToSlave(dynamicDataSource));
|
||||
SLAVE_SOURCE_MAP.put(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId(), slaves);
|
||||
} else {
|
||||
List<SlaveInfo> slaves = SLAVE_SOURCE_MAP.get(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId());
|
||||
slaves.add(datasourceToSlave(dynamicDataSource));
|
||||
}
|
||||
}
|
||||
}
|
||||
dataSource.freshDataSource(DATA_SOURCE_MAP);
|
||||
BaseContext.clearAllHolder();
|
||||
}
|
||||
|
||||
public void addTenantDataSource(String tenantId, String driver, String url, String username, String password, PoolInfo poolInfo) {
|
||||
addDataSourceToMap(DynamicDatasourceConstant.MASTER_PREFIX + tenantId, buildDataSource(driver, url, username, password, poolInfo));
|
||||
dataSource.freshDataSource(DATA_SOURCE_MAP);
|
||||
addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + tenantId, buildDataSource(driver, url, username, password, poolInfo));
|
||||
dataSource.freshDataSource(MASTER_SOURCE_MAP);
|
||||
}
|
||||
|
||||
public void deleteTenantDataSource(String tenantId) {
|
||||
DATA_SOURCE_MAP.remove(DynamicDatasourceConstant.MASTER_PREFIX + tenantId);
|
||||
dataSource.freshDataSource(DATA_SOURCE_MAP);
|
||||
MASTER_SOURCE_MAP.remove(DynamicDatasourceConstant.MASTER_PREFIX + tenantId);
|
||||
dataSource.freshDataSource(MASTER_SOURCE_MAP);
|
||||
}
|
||||
|
||||
private static Object buildDataSource(String driver, String url, String username, String password, PoolInfo poolInfo) {
|
||||
@ -96,16 +147,27 @@ public class DynamicDataSourceConfig {
|
||||
*/
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
Set<Map.Entry<Object, Object>> entries = DATA_SOURCE_MAP.entrySet();
|
||||
Set<Map.Entry<Object, Object>> entries = MASTER_SOURCE_MAP.entrySet();
|
||||
for (Map.Entry<Object, Object> entry : entries) {
|
||||
DruidDataSource dataSource = (DruidDataSource) entry.getValue();
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void addDataSourceToMap(String name, Object dataSource) {
|
||||
private void addMasterMap(String name, Object dataSource) {
|
||||
if (Objects.nonNull(dataSource)) {
|
||||
DATA_SOURCE_MAP.put(name, dataSource);
|
||||
MASTER_SOURCE_MAP.put(name, dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
private SlaveInfo datasourceToSlave(DynamicDataSource dynamicDataSource) {
|
||||
SlaveInfo slaveInfo = BeanUtil.copyProperties(dynamicDataSource, SlaveInfo.class);
|
||||
slaveInfo.setPool(new PoolInfo(dynamicDataSource.getInitCount(), dynamicDataSource.getMinCount(), dynamicDataSource.getMaxCount()));
|
||||
return slaveInfo;
|
||||
}
|
||||
|
||||
public void setDefaultSetting() {
|
||||
BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID);
|
||||
BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.qiaoba.common.database.config;
|
||||
|
||||
import com.qiaoba.common.base.constants.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
@ -20,8 +21,8 @@ public class DynamicDataSourceContext extends AbstractRoutingDataSource {
|
||||
* 设置默认数据源、全部数据源,及刷新
|
||||
*/
|
||||
public void freshDataSource(Map<Object, Object> targetDataSources) {
|
||||
//默认数据源
|
||||
super.setDefaultTargetDataSource(targetDataSources.get(DynamicDatasourceConstant.DEFAULT_MASTER_DATASOURCE_KEY));
|
||||
//默认数据源 (determineCurrentLookupKey 如果找不到就使用默认数据源)
|
||||
super.setDefaultTargetDataSource(targetDataSources.get(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID));
|
||||
//设置全部数据源
|
||||
super.setTargetDataSources(targetDataSources);
|
||||
//刷新(即把targetDataSources刷到resolvedDataSources中去,resolvedDataSources才是我们真正存放数据源的map)
|
||||
|
@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.database.interceptors.SchemaInterceptor;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.StringValue;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -31,6 +32,7 @@ public class MybatisPlusConfig {
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new SchemaInterceptor());
|
||||
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
|
@ -9,16 +9,6 @@ package com.qiaoba.common.database.constants;
|
||||
*/
|
||||
public class DynamicDatasourceConstant {
|
||||
|
||||
/**
|
||||
* 系统默认的主数据源名称
|
||||
*/
|
||||
public static final String DEFAULT_MASTER_DATASOURCE_KEY = "master_default";
|
||||
|
||||
/**
|
||||
* 系统默认的从数据源名称
|
||||
*/
|
||||
public static final String DEFAULT_SLAVE_DATASOURCE_PREFIX = "slave_default_";
|
||||
|
||||
/**
|
||||
* 主数据源拼接前缀 master_tenantId
|
||||
*/
|
||||
|
@ -23,6 +23,11 @@ public class DynamicDataSource {
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 主库
|
||||
*/
|
||||
private String isMaster;
|
||||
|
||||
/**
|
||||
* 数据库-url
|
||||
*/
|
||||
@ -43,4 +48,19 @@ public class DynamicDataSource {
|
||||
*/
|
||||
private String driver;
|
||||
|
||||
/**
|
||||
* 连接池-初始化大小
|
||||
*/
|
||||
private Integer initCount;
|
||||
|
||||
/**
|
||||
* 连接池-最小空闲数
|
||||
*/
|
||||
private Integer minCount;
|
||||
|
||||
/**
|
||||
* 连接池-最大连接数
|
||||
*/
|
||||
private Integer maxCount;
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package com.qiaoba.common.database.factories;
|
||||
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceContext;
|
||||
import com.qiaoba.common.database.filters.DynamicDataSourceFilter;
|
||||
import com.qiaoba.common.database.properties.DefaultDataSourceProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -27,11 +26,6 @@ public class DynamicDataSourceFactory {
|
||||
return new DynamicDataSourceConfig();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DynamicDataSourceFilter handleTenantFilter() {
|
||||
return new DynamicDataSourceFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultDataSourceProperties defaultDataSourceProperties() {
|
||||
return new DefaultDataSourceProperties();
|
||||
|
@ -1,80 +0,0 @@
|
||||
package com.qiaoba.common.database.filters;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.qiaoba.common.base.DatasourceService;
|
||||
import com.qiaoba.common.base.constants.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
|
||||
import com.qiaoba.common.web.utils.ResponseUtil;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 动态切换不同租户的数据源
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-25 22:48:43
|
||||
*/
|
||||
@Component
|
||||
@Order(-10000)
|
||||
public class DynamicDataSourceFilter extends OncePerRequestFilter {
|
||||
|
||||
@Resource
|
||||
private DatasourceService datasourceService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String tenantId = request.getHeader(TenantConstant.HEADER_KEY_TENANT);
|
||||
// 主库或没有tenantId
|
||||
if (StrUtil.isBlank(tenantId)
|
||||
|| TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)
|
||||
|| request.getRequestURI().startsWith("/tenant")) {
|
||||
setDefaultSetting();
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkTenantInfo(tenantId)) {
|
||||
// 检查租户的信息,是否存在,是否过期,是否禁用
|
||||
ResponseUtil.response(response, "租户可能到期或被禁用");
|
||||
return;
|
||||
}
|
||||
before(tenantId);
|
||||
filterChain.doFilter(request, response);
|
||||
after();
|
||||
}
|
||||
|
||||
private void before(String tenantId) {
|
||||
//设置当前租户对应的数据源
|
||||
BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + tenantId);
|
||||
//设置当前租户对应的租户ID
|
||||
BaseContext.setTenantId(tenantId);
|
||||
//设置当前租户对应的数据库类型
|
||||
BaseContext.setDatabaseType("MySQL");
|
||||
}
|
||||
|
||||
private void after() {
|
||||
BaseContext.clearAllHolder();
|
||||
}
|
||||
|
||||
private boolean checkTenantInfo(String tenantId) {
|
||||
return datasourceService.checkTenantInfo(tenantId);
|
||||
}
|
||||
|
||||
private void setDefaultSetting() {
|
||||
BaseContext.setDataSource(DynamicDatasourceConstant.DEFAULT_MASTER_DATASOURCE_KEY);
|
||||
//设置当前租户对应的租户ID
|
||||
BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
|
||||
//设置当前租户对应的数据库类型
|
||||
BaseContext.setDatabaseType("MySQL");
|
||||
}
|
||||
}
|
@ -1,24 +1,39 @@
|
||||
package com.qiaoba.common.database.interceptors;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
/**
|
||||
* SchemaInterceptor
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-25 22:48:43
|
||||
*/
|
||||
public class SchemaInterceptor implements InnerInterceptor {
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String baseDatabase;
|
||||
|
||||
@Override
|
||||
public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) {
|
||||
String sql = "use `qiaoba-boot`;";
|
||||
try {
|
||||
conn.createStatement().execute(sql);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (Objects.nonNull(BaseContext.getSchema()) && BaseContext.getSchema()) {
|
||||
// use qiaoba-1;
|
||||
String sql = StrUtil.format("use `{}-{}`;", baseDatabase, BaseContext.getTenantId());
|
||||
try {
|
||||
conn.createStatement().execute(sql);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
InnerInterceptor.super.beforePrepare(sh, conn, transactionTimeout);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
package com.qiaoba.common.database.service;
|
||||
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 动态数据源接口
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/9 13:15
|
||||
*/
|
||||
public interface DynamicDatasourceService {
|
||||
|
||||
/**
|
||||
* 加载所有租户的数据源
|
||||
*
|
||||
* @return 数据源集合
|
||||
*/
|
||||
List<DynamicDataSource> loadAllTenantDatasource();
|
||||
}
|
@ -35,7 +35,7 @@ public class SysTenant extends BaseEntity {
|
||||
/**
|
||||
* 数据库模式
|
||||
*/
|
||||
public static final String DATABASE_MODE = "2";
|
||||
public static final String SCHEMA_MODE = "2";
|
||||
|
||||
/**
|
||||
* 数据源模式
|
||||
|
@ -0,0 +1,36 @@
|
||||
package com.qiaoba.module.tenant.entity.param;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 租户数据源查询参数
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/9 13:18
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class SysTenantDatasourceParam implements Serializable {
|
||||
|
||||
private String tenantId;
|
||||
|
||||
private String isMaster;
|
||||
|
||||
private String isUse;
|
||||
|
||||
|
||||
public SysTenantDatasourceParam(String tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public SysTenantDatasourceParam(String tenantId, String isMaster) {
|
||||
this.tenantId = tenantId;
|
||||
this.isMaster = isMaster;
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package com.qiaoba.module.tenant.filters;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.qiaoba.common.base.constants.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.base.enums.BaseEnum;
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
|
||||
import com.qiaoba.common.web.utils.ResponseUtil;
|
||||
import com.qiaoba.module.tenant.entity.SysTenant;
|
||||
import com.qiaoba.module.tenant.service.SysTenantService;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 动态切换不同租户的数据源
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-25 22:48:43
|
||||
*/
|
||||
@Component
|
||||
@Order(-10000)
|
||||
public class DynamicDataSourceFilter extends OncePerRequestFilter {
|
||||
|
||||
@Resource
|
||||
private SysTenantService sysTenantService;
|
||||
@Resource
|
||||
private DynamicDataSourceConfig dynamicDataSourceConfig;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String tenantId = request.getHeader(TenantConstant.HEADER_KEY_TENANT);
|
||||
// 主库或没有tenantId
|
||||
if (StrUtil.isBlank(tenantId)
|
||||
|| TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)
|
||||
|| request.getRequestURI().startsWith("/tenant")) {
|
||||
dynamicDataSourceConfig.setDefaultSetting();
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
SysTenant sysTenant = sysTenantService.selectById(tenantId);
|
||||
if (checkTenantIsNotAllow(response, sysTenant)) {
|
||||
return;
|
||||
}
|
||||
before(sysTenant);
|
||||
filterChain.doFilter(request, response);
|
||||
after();
|
||||
}
|
||||
|
||||
private void before(SysTenant sysTenant) {
|
||||
BaseContext.setTenantId(sysTenant.getTenantId());
|
||||
BaseContext.setSchema(sysTenant.getMode().equals(SysTenant.SCHEMA_MODE));
|
||||
// 数据源模式-设置第三方数据源
|
||||
if (sysTenant.getMode().equals(SysTenant.DATASOURCE_MODE)) {
|
||||
//设置当前租户对应的数据源
|
||||
BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + sysTenant.getTenantId());
|
||||
}
|
||||
// 字段模式/Schema模式-数据源选择默认数据源
|
||||
else {
|
||||
BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void after() {
|
||||
BaseContext.clearAllHolder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查租户是否不允许访问
|
||||
*
|
||||
* @param sysTenant sysTenant
|
||||
* @return 是->不允许
|
||||
*/
|
||||
private boolean checkTenantIsNotAllow(HttpServletResponse response, SysTenant sysTenant) throws IOException {
|
||||
if (Objects.isNull(sysTenant)) {
|
||||
// 未找到租户信息
|
||||
ResponseUtil.errorAuth(response, 401, "未找到租户信息");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sysTenant.getStatus().equals(BaseEnum.ABNORMAL.getCode())) {
|
||||
// 封禁状态
|
||||
ResponseUtil.errorAuth(response, 401, "租户已被封禁");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DateUtil.compare(sysTenant.getExpireTime(), new Date()) < 0) {
|
||||
// 已过期
|
||||
ResponseUtil.errorAuth(response, 401, "租户已过期");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -48,7 +48,9 @@ public class MysqlDataHandler implements DataHandler {
|
||||
// 手动提交
|
||||
conn.setAutoCommit(false);
|
||||
// 创建表
|
||||
initTables(conn);
|
||||
if (needCreateTables) {
|
||||
initTables(conn);
|
||||
}
|
||||
// 处理 sys_config
|
||||
handleSysConfig(conn, tenantId);
|
||||
// 处理 sys_post
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.qiaoba.module.tenant.service;
|
||||
|
||||
import com.qiaoba.module.tenant.entity.SysTenantDatasource;
|
||||
import com.qiaoba.module.tenant.entity.param.SysTenantDatasourceParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -52,4 +53,12 @@ public interface SysTenantDatasourceService {
|
||||
* @return list
|
||||
*/
|
||||
List<SysTenantDatasource> selectList(String tenantId);
|
||||
|
||||
/**
|
||||
* 查询租户数据源
|
||||
*
|
||||
* @param param 条件
|
||||
* @return list
|
||||
*/
|
||||
List<SysTenantDatasource> selectList(SysTenantDatasourceParam param);
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
package com.qiaoba.module.tenant.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.qiaoba.common.base.enums.BaseEnum;
|
||||
import com.qiaoba.common.base.enums.DataBaseEnum;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
||||
import com.qiaoba.module.tenant.entity.SysTenantDatasource;
|
||||
import com.qiaoba.module.tenant.entity.param.SysTenantDatasourceParam;
|
||||
import com.qiaoba.module.tenant.service.SysTenantDatasourceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 动态数据源接口
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/9 13:16
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DynamicDatasourceServiceImpl implements DynamicDatasourceService {
|
||||
|
||||
private final SysTenantDatasourceService sysTenantDatasourceService;
|
||||
|
||||
@Override
|
||||
public List<DynamicDataSource> loadAllTenantDatasource() {
|
||||
SysTenantDatasourceParam param = new SysTenantDatasourceParam();
|
||||
param.setIsUse(BaseEnum.YES.getCode());
|
||||
List<SysTenantDatasource> datasourceList = sysTenantDatasourceService.selectList(param);
|
||||
return transform(datasourceList);
|
||||
}
|
||||
|
||||
private List<DynamicDataSource> transform(List<SysTenantDatasource> datasourceList) {
|
||||
List<DynamicDataSource> dynamicDataSourceList = new ArrayList<>();
|
||||
for (SysTenantDatasource datasource : datasourceList) {
|
||||
DynamicDataSource dynamicDataSource = BeanUtil.copyProperties(datasource, DynamicDataSource.class);
|
||||
dynamicDataSource.setDriver(DataBaseEnum.getDriver(datasource.getType()));
|
||||
dynamicDataSource.setUrl(DataBaseEnum.getUrl(datasource.getType(), datasource.getIp(), datasource.getPort(), datasource.getName()));
|
||||
dynamicDataSourceList.add(dynamicDataSource);
|
||||
}
|
||||
return dynamicDataSourceList;
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package com.qiaoba.module.tenant.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.qiaoba.common.base.enums.BaseEnum;
|
||||
import com.qiaoba.module.tenant.entity.SysTenantDatasource;
|
||||
import com.qiaoba.module.tenant.entity.param.SysTenantDatasourceParam;
|
||||
import com.qiaoba.module.tenant.mapper.SysTenantDatasourceMapper;
|
||||
import com.qiaoba.module.tenant.service.SysTenantDatasourceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -25,11 +27,7 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic
|
||||
|
||||
@Override
|
||||
public SysTenantDatasource selectMaster(String tenantId) {
|
||||
QueryWrapper<SysTenantDatasource> wrapper = new QueryWrapper<>();
|
||||
wrapper.lambda()
|
||||
.eq(SysTenantDatasource::getTenantId, tenantId)
|
||||
.eq(SysTenantDatasource::getIsMaster, BaseEnum.YES.getCode());
|
||||
return sysTenantDatasourceMapper.selectOne(wrapper);
|
||||
return sysTenantDatasourceMapper.selectOne(paramToWrapper(new SysTenantDatasourceParam(tenantId, BaseEnum.YES.getCode())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,9 +47,21 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic
|
||||
|
||||
@Override
|
||||
public List<SysTenantDatasource> selectList(String tenantId) {
|
||||
return sysTenantDatasourceMapper.selectList(paramToWrapper(new SysTenantDatasourceParam(tenantId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysTenantDatasource> selectList(SysTenantDatasourceParam param) {
|
||||
return sysTenantDatasourceMapper.selectList(paramToWrapper(param));
|
||||
}
|
||||
|
||||
private QueryWrapper<SysTenantDatasource> paramToWrapper(SysTenantDatasourceParam param) {
|
||||
QueryWrapper<SysTenantDatasource> wrapper = new QueryWrapper<>();
|
||||
wrapper.lambda()
|
||||
.eq(SysTenantDatasource::getTenantId, tenantId);
|
||||
return sysTenantDatasourceMapper.selectList(wrapper);
|
||||
.eq(StrUtil.isNotBlank(param.getTenantId()), SysTenantDatasource::getTenantId, param.getTenantId())
|
||||
.eq(StrUtil.isNotBlank(param.getIsUse()), SysTenantDatasource::getIsUse, param.getIsUse())
|
||||
.eq(StrUtil.isNotBlank(param.getIsMaster()), SysTenantDatasource::getIsMaster, param.getIsMaster());
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package com.qiaoba.module.tenant.service.impl;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.qiaoba.auth.utils.SecurityUtil;
|
||||
import com.qiaoba.common.base.DatasourceService;
|
||||
import com.qiaoba.common.base.constants.BaseConstant;
|
||||
import com.qiaoba.common.base.enums.BaseEnum;
|
||||
import com.qiaoba.common.base.enums.DataBaseEnum;
|
||||
import com.qiaoba.common.base.exceptions.ServiceException;
|
||||
@ -38,7 +38,7 @@ import java.util.Objects;
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysTenantServiceImpl implements SysTenantService, DatasourceService {
|
||||
public class SysTenantServiceImpl implements SysTenantService {
|
||||
|
||||
@Value("${qiaoba.datasource.master.driver}")
|
||||
private String driver;
|
||||
@ -143,8 +143,8 @@ public class SysTenantServiceImpl implements SysTenantService, DatasourceService
|
||||
if (SysTenant.COLUMN_MODE.equals(sysTenant.getMode())) {
|
||||
return column();
|
||||
}
|
||||
if (SysTenant.DATABASE_MODE.equals(sysTenant.getMode())) {
|
||||
return database(sysTenant.getTenantId());
|
||||
if (SysTenant.SCHEMA_MODE.equals(sysTenant.getMode())) {
|
||||
return schema(sysTenant.getTenantId());
|
||||
}
|
||||
if (SysTenant.DATASOURCE_MODE.equals(sysTenant.getMode())) {
|
||||
return datasource(sysTenant.getTenantId());
|
||||
@ -163,8 +163,8 @@ public class SysTenantServiceImpl implements SysTenantService, DatasourceService
|
||||
/**
|
||||
* 处理数据库模式
|
||||
*/
|
||||
private Connection database(String tenantId) {
|
||||
String realUrl = this.url.replaceFirst(baseDatabase, baseDatabase + "-" + tenantId);
|
||||
private Connection schema(String tenantId) {
|
||||
String realUrl = this.url.replaceFirst(baseDatabase, baseDatabase + BaseConstant.HYPHEN_JOIN_STR + tenantId);
|
||||
return JdbcUtil.getConnection(driver, realUrl, username, password);
|
||||
}
|
||||
|
||||
@ -179,13 +179,4 @@ public class SysTenantServiceImpl implements SysTenantService, DatasourceService
|
||||
return JdbcUtil.getConnection(DataBaseEnum.getDriver(master.getType()), DataBaseEnum.getUrl(master.getType(), master.getIp(), master.getPort(), master.getName()), master.getUsername(), master.getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTenantInfo(String tenantId) {
|
||||
SysTenant sysTenant = selectById(tenantId);
|
||||
if (Objects.isNull(sysTenant)) {
|
||||
return false;
|
||||
}
|
||||
// todo 检查租户有没有过期/禁用
|
||||
return sysTenant.getStatus().equals(BaseEnum.NORMAL.getCode());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user