first commit

This commit is contained in:
2023-05-04 19:53:10 +08:00
parent 6adac3aeb1
commit 3ecf1ecc7e
16 changed files with 411 additions and 96 deletions

View File

@ -1,5 +1,6 @@
package com.qiaoba.api.system.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.qiaoba.common.base.entity.BaseEntity;
import lombok.Data;
@ -21,17 +22,12 @@ public class SysTenant extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
@TableId
private Long tenantId;
private String code;
private String tenantCode;
private String name;
private String domainName;
private Date expiryDate;
private String contact;
private String tenantName;
private String province;
@ -41,5 +37,14 @@ public class SysTenant extends BaseEntity {
private String address;
private String contactUser;
private String contactPhone;
private String dataType;
private Date expireTime;
private String status;
}

View File

@ -1,11 +1,32 @@
spring:
qiaoba:
datasource:
master:
driver: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://121.5.136.69:3306/qiaoba-boot-1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
username: root
password: LpYN7LUoL?l0OSpR2
pool:
init: 5 #连接池初始化大小
min: 10 #最小空闲连接数
max: 20 #最大连接数
slaves:
- driver: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://121.5.136.69:3306/qiaoba-boot?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
username: root
password: LpYN7LUoL?l0OSpR2
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
pool:
init: 5
min: 10
max: 20
weight: 2 #权重 默认 1
is-use: true #是否使用 默认 true
- driver: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/qiaoba-boot?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
username: root
password: root
pool:
init: 5
min: 10
max: 20
is-use: false # 不使用该库

View File

@ -1,6 +1,8 @@
server:
port: 80
spring:
main:
allow-bean-definition-overriding: true #新创建的bean覆盖旧的bean
application:
name: qiaoba-boot
profiles:
@ -13,8 +15,7 @@ spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
main:
allow-bean-definition-overriding: true
# springdoc-openapi项目配置

View File

@ -1,14 +1,23 @@
package com.qiaoba.common.database.config;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.qiaoba.common.database.entity.DynamicDataSource;
import org.springframework.context.annotation.Bean;
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
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.utils.JdbcUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 全局跨域配置
@ -17,67 +26,62 @@ import java.util.Map;
* @version 1.0
* @since 2021/10/15 0015 下午 16:43
*/
@Slf4j
@Configuration
public class DynamicDataSourceConfig {
@Resource
private DynamicDataSourceContext dataSource;
@Resource
private DefaultDataSourceProperties defaultDataSourceProperties;
public static Map<Object, Object> DATA_SOURCE_MAP = new ConcurrentHashMap<>();
/**
* 把DynamicDataSourceContext 纳入容器管理其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象并调用freshDataSource方法
*/
@Bean
@Primary
public static DynamicDataSourceContext dataSource() {
Map<Object, Object> targetDataSource = getDataSource();
//把DynamicDataSourceContext纳入容器管理
DynamicDataSourceContext dynamicDataSourceContext = new DynamicDataSourceContext();
dynamicDataSourceContext.freshDataSource(targetDataSource);
return dynamicDataSourceContext;
@PostConstruct
public void init() {
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()));
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()));
}
}
}
dataSource.freshDataSource(DATA_SOURCE_MAP);
}
/**
* 构建初始化数据源 TODO 生成中去其他地方获取初始化数据源(例如:表里面获取)
*
* @return
*/
public static Map<Object, Object> getDataSource() {
DynamicDataSource ds2 = new DynamicDataSource();
ds2.setTenantCode("1");
ds2.setDriver("com.mysql.cj.jdbc.Driver");
ds2.setUrl("jdbc:mysql://121.5.136.69:3306/qiaoba-boot?useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");
ds2.setUsername("root");
ds2.setPassword("LpYN7LUoL?l0OSpR2");
DynamicDataSource ds1 = new DynamicDataSource();
ds1.setTenantCode("2");
ds1.setDriver("com.mysql.cj.jdbc.Driver");
ds1.setUrl("jdbc:mysql://120.79.217.22:3306/qiaoba-boot?useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");
ds1.setUsername("root");
ds1.setPassword("FeyZ7xZr6JtuKibm");
Map<Object, Object> map = new HashMap<>();
map.put(ds1.getTenantCode(), buildDataSource(ds1));
map.put(ds2.getTenantCode(), buildDataSource(ds2));
return map;
private static Object buildDataSource(String driver, String url, String username, String password, PoolInfo poolInfo) {
boolean flag = JdbcUtil.checkConnect(driver, url, username, password);
if (!flag) {
log.error("数据源: " + url + " , 连接失败,请检查!");
return null;
}
/**
* 把数据源对象组装成HikariDataSource
*
* @param DynamicDataSource
* @return
*/
private static Object buildDataSource(DynamicDataSource DynamicDataSource) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(DynamicDataSource.getUrl());
dataSource.setUsername(DynamicDataSource.getUsername());
dataSource.setPassword(DynamicDataSource.getPassword());
dataSource.setDriverClassName(DynamicDataSource.getDriver());
dataSource.setInitialSize(5);
dataSource.setMinIdle(10);
dataSource.setMaxActive(20);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driver);
dataSource.setInitialSize(poolInfo.getInit());
dataSource.setMinIdle(poolInfo.getMin());
dataSource.setMaxActive(poolInfo.getMax());
try {
dataSource.init();
} catch (SQLException e) {
e.printStackTrace();
}
return dataSource;
} catch (SQLException e) {
dataSource.close();
return null;
}
}
private void addDataSourceToMap(String name, Object dataSource) {
if (Objects.nonNull(dataSource)) {
DATA_SOURCE_MAP.put(name, dataSource);
}
}
}

View File

@ -1,5 +1,6 @@
package com.qiaoba.common.database.config;
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.Map;
@ -16,12 +17,13 @@ public class DynamicDataSourceContext extends AbstractRoutingDataSource {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置默认数据源、全部数据源,及刷新
*/
public void freshDataSource(Map<Object, Object> targetDataSources) {
//默认数据源
super.setDefaultTargetDataSource(targetDataSources.get("master"));
super.setDefaultTargetDataSource(targetDataSources.get(DynamicDatasourceConstant.DEFAULT_MASTER_DATASOURCE_KEY));
//设置全部数据源
super.setTargetDataSources(targetDataSources);
//刷新(即把targetDataSources刷到resolvedDataSources中去resolvedDataSources才是我们真正存放数据源的map)

View File

@ -0,0 +1,32 @@
package com.qiaoba.common.database.constants;
/**
* DynamicDatasourceConstant
*
* @author ailanyin
* @version 1.0
* @since 2023-04-27 19:35:28
*/
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
*/
public static final String MASTER_PREFIX = "master_";
/**
* 从数据源拼接前缀 slave_tenantId_序号
*/
public static final String SLAVE_PREFIX = "slave_";
}

View File

@ -2,32 +2,38 @@ 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;
import org.springframework.context.annotation.Primary;
import java.util.Map;
/**
* 动态数据源工厂,主要解决缓存依赖
* 动态数据源工厂
*
* @version 1.0
* @author ailanyin
* @since 2023-04-25 21:47:34
* @version 1.0
* @since 2021/10/15 0015 下午 16:43
*/
@Configuration
public class DynamicDataSourceFactory {
/**
* 把DynamicDataSourceContext 纳入容器管理其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象并调用freshDataSource方法
*/
@Bean
@Primary
public static DynamicDataSourceContext dataSource() {
Map<Object, Object> targetDataSource = DynamicDataSourceConfig.getDataSource();
//把DynamicDataSourceContext纳入容器管理
DynamicDataSourceContext dynamicDataSourceContext = new DynamicDataSourceContext();
dynamicDataSourceContext.freshDataSource(targetDataSource);
return dynamicDataSourceContext;
public DynamicDataSourceContext dataSource() {
return new DynamicDataSourceContext();
}
@Bean
public DynamicDataSourceConfig dynamicDataSourceConfig() {
return new DynamicDataSourceConfig();
}
@Bean
public DynamicDataSourceFilter handleTenantFilter() {
return new DynamicDataSourceFilter();
}
@Bean
public DefaultDataSourceProperties defaultDataSourceProperties() {
return new DefaultDataSourceProperties();
}
}

View File

@ -1,12 +1,14 @@
package com.qiaoba.common.database.filters;
import com.qiaoba.common.database.config.DynamicDataSourceContext;
import com.qiaoba.common.web.utils.ResponseUtil;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
@ -18,7 +20,7 @@ import java.io.IOException;
*/
@Component
@Order(-10000)
public class HandleTenantFilter implements Filter {
public class DynamicDataSourceFilter implements Filter {
@Resource
private DynamicDataSourceContext dynamicDataSourceContext;
@ -26,11 +28,21 @@ public class HandleTenantFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String instanceId = request.getParameter("tenant");
String tenantCode = request.getParameter("tenant");
if (!checkTenantInfo(tenantCode, servletResponse)) {
return;
}
//设置当前租户对应的数据库
dynamicDataSourceContext.setDataSource(instanceId);
dynamicDataSourceContext.setDataSource(tenantCode);
System.out.println("当前数据源是:" + dynamicDataSourceContext.getDataSource());
filterChain.doFilter(servletRequest, servletResponse);
dynamicDataSourceContext.clearDataSource();
}
private boolean checkTenantInfo(String tenantCode, ServletResponse servletResponse) throws IOException {
// 检查租户的信息,是否存在,是否过期,是否禁用
ResponseUtil.response((HttpServletResponse) servletResponse, "租户: " + tenantCode + "已到期");
return false;
}
}

View File

@ -0,0 +1,20 @@
package com.qiaoba.common.database.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "qiaoba.datasource")
@Data
@EnableConfigurationProperties
public class DefaultDataSourceProperties {
private MasterInfo master;
private List<SlaveInfo> slaves;
}

View File

@ -0,0 +1,12 @@
package com.qiaoba.common.database.properties;
import lombok.Data;
@Data
public class MasterInfo {
private String driver;
private String url;
private String username;
private String password;
private PoolInfo pool;
}

View File

@ -0,0 +1,10 @@
package com.qiaoba.common.database.properties;
import lombok.Data;
@Data
public class PoolInfo {
private Integer init;
private Integer min;
private Integer max;
}

View File

@ -0,0 +1,14 @@
package com.qiaoba.common.database.properties;
import lombok.Data;
@Data
public class SlaveInfo {
private Boolean isUse = true;
private String driver;
private String url;
private String username;
private String password;
private Integer weight = 1;
private PoolInfo pool;
}

View File

@ -0,0 +1,48 @@
package com.qiaoba.common.database.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* JdbcUtil
*
* @author ailanyin
* @version 1.0
* @since 2021/10/15 0015 下午 16:43
*/
public class JdbcUtil {
/**
* 检查数据源是否可以连接
*
* @param driver 数据库驱动
* @param url url
* @param username 用户名
* @param password 密码
* @return true = 是
*/
public static boolean checkConnect(String driver, String url, String username, String password) {
Connection conn = null;
try {
try {
Class.forName(driver);
//建立连接
conn = DriverManager.getConnection(url, username, password);
return true;
} catch (SQLException e) {
return false;
}
} catch (Exception e) {
return false;
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,108 @@
{
"groups": [
{
"name": "qiaoba.datasource",
"type": "com.qiaoba.common.database.properties.DefaultDataSourceProperties",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
}
],
"properties": [
{
"name": "qiaoba.datasource.master",
"type": "com.qiaoba.common.database.properties.MasterInfo",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.driver",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.url",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.username",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.password",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.pool",
"type": "com.qiaoba.common.database.properties.PoolInfo",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.pool.init",
"type": "java.lang.Integer",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.pool.min",
"type": "java.lang.Integer",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.master.pool.max",
"type": "java.lang.Integer",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves",
"type": "java.util.List<com.qiaoba.common.database.properties.SlaveInfo>",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.url",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.username",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.password",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.weight",
"type": "java.lang.String",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.is-use",
"type": "java.lang.Boolean",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties",
"defaultValue": true
},
{
"name": "qiaoba.datasource.slaves.pool",
"type": "com.qiaoba.common.database.properties.PoolInfo",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.pool.init",
"type": "java.lang.Integer",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.pool.min",
"type": "java.lang.Integer",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
},
{
"name": "qiaoba.datasource.slaves.pool.max",
"type": "java.lang.Integer",
"sourceType": "com.qiaoba.common.database.properties.DefaultDataSourceProperties"
}
],
"hints": []
}

View File

@ -1,5 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.qiaoba.common.database.config.DynamicDataSourceConfig,\
com.qiaoba.common.database.filters.HandleTenantFilter
com.qiaoba.common.database.factories.DynamicDataSourceFactory

View File

@ -0,0 +1,21 @@
package com.qiaoba.common.web.utils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* ResponseUtil
*
* @author ailanyin
* @version 1.0
* @since 2023-04-25 22:48:43
*/
public class ResponseUtil {
public static void response(HttpServletResponse response, String msg) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().write(msg);
}
}