first commit

This commit is contained in:
2023-04-26 19:48:44 +08:00
commit 6adac3aeb1
62 changed files with 2741 additions and 0 deletions

View File

@ -0,0 +1,36 @@
<?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">
<parent>
<artifactId>qiaoba-commons</artifactId>
<groupId>com.qiaoba</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>qiaoba-common-datasource</artifactId>
<dependencies>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-common-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,83 @@
package com.qiaoba.common.database.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.qiaoba.common.database.entity.DynamicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* 全局跨域配置
*
* @author ailanyin
* @version 1.0
* @since 2021/10/15 0015 下午 16:43
*/
@Configuration
public class DynamicDataSourceConfig {
/**
* 把DynamicDataSourceContext 纳入容器管理其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象并调用freshDataSource方法
*/
@Bean
@Primary
public static DynamicDataSourceContext dataSource() {
Map<Object, Object> targetDataSource = getDataSource();
//把DynamicDataSourceContext纳入容器管理
DynamicDataSourceContext dynamicDataSourceContext = new DynamicDataSourceContext();
dynamicDataSourceContext.freshDataSource(targetDataSource);
return dynamicDataSourceContext;
}
/**
* 构建初始化数据源 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;
}
/**
* 把数据源对象组装成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);
try {
dataSource.init();
} catch (SQLException e) {
e.printStackTrace();
}
return dataSource;
}
}

View File

@ -0,0 +1,62 @@
package com.qiaoba.common.database.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.Map;
/**
* 动态数据源上下文对象
*
* @author ailanyin
* @version 1.0
* @since 2021/10/15 0015 下午 16:43
*/
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.setTargetDataSources(targetDataSources);
//刷新(即把targetDataSources刷到resolvedDataSources中去resolvedDataSources才是我们真正存放数据源的map)
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
//获取当前指定的数据源
return getDataSource();
}
@Override
public void afterPropertiesSet() {
}
/**
* 获取上下文中的数据源
*/
public String getDataSource() {
return CONTEXT_HOLDER.get();
}
/**
* 设置上下文中的数据源
*/
public void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
/**
* 清除上下文中的数据源
*/
public void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}

View File

@ -0,0 +1,46 @@
package com.qiaoba.common.database.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 动态数据源实体
*
* @author ailanyin
* @version 1.0
* @date 2023-04-24 21:47:48
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DynamicDataSource {
/**
* 租户Code
*/
private String tenantCode;
/**
* 数据库-url
*/
private String url;
/**
* 数据库-username
*/
private String username;
/**
* 数据库-password
*/
private String password;
/**
* 数据库-驱动
*/
private String driver;
}

View File

@ -0,0 +1,33 @@
package com.qiaoba.common.database.factories;
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
import com.qiaoba.common.database.config.DynamicDataSourceContext;
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
*/
@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;
}
}

View File

@ -0,0 +1,36 @@
package com.qiaoba.common.database.filters;
import com.qiaoba.common.database.config.DynamicDataSourceContext;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 动态切换不同租户的数据源
*
* @author ailanyin
* @version 1.0
* @since 2023-04-25 22:48:43
*/
@Component
@Order(-10000)
public class HandleTenantFilter implements Filter {
@Resource
private DynamicDataSourceContext dynamicDataSourceContext;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String instanceId = request.getParameter("tenant");
//设置当前租户对应的数据库
dynamicDataSourceContext.setDataSource(instanceId);
System.out.println("当前数据源是:" + dynamicDataSourceContext.getDataSource());
filterChain.doFilter(servletRequest, servletResponse);
dynamicDataSourceContext.clearDataSource();
}
}

View File

@ -0,0 +1,306 @@
package com.qiaoba.common.database.mapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.qiaoba.common.web.utils.BeanCopyUtil;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 自定义 Mapper 接口, 实现 自定义扩展
*
* @param <M> mapper 泛型
* @param <T> table 泛型
* @param <V> vo 泛型
* @author ailanyin
* @since 2023-04-23 17:20:32
*/
@SuppressWarnings("unchecked")
public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
/**
* currentVoClass
*
* @return Vo
*/
default Class<V> currentVoClass() {
return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
}
/**
* currentModelClass
*
* @return model
*/
default Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
}
/**
* currentMapperClass
*
* @return mapper
*/
default Class<M> currentMapperClass() {
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
}
/**
* 查询所有
*
* @return all lst
*/
default List<T> selectList() {
return this.selectList(new QueryWrapper<>());
}
/**
* 批量新增
*
* @param entityList list
* @return 结果
*/
default boolean insertBatch(Collection<T> entityList) {
return Db.saveBatch(entityList);
}
/**
* 批量更新
*
* @param entityList list
* @return 结果
*/
default boolean updateBatchById(Collection<T> entityList) {
return Db.updateBatchById(entityList);
}
/**
* 批量插入或更新
*
* @param entityList list
* @return 结果
*/
default boolean insertOrUpdateBatch(Collection<T> entityList) {
return Db.saveOrUpdateBatch(entityList);
}
/**
* 批量插入(包含限制条数)
*
* @param entityList list
* @param batchSize 最大调数
* @return 结果
*/
default boolean insertBatch(Collection<T> entityList, int batchSize) {
return Db.saveBatch(entityList, batchSize);
}
/**
* 批量更新(包含限制条数)
*
* @param entityList list
* @param batchSize 最大调数
* @return 结果
*/
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
return Db.updateBatchById(entityList, batchSize);
}
/**
* 批量插入或更新(包含限制条数)
*
* @param entityList list
* @param batchSize 最大调数
* @return 结果
*/
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
return Db.saveOrUpdateBatch(entityList, batchSize);
}
/**
* 插入或更新
* @param entity entity
* @return 结果
*/
default boolean insertOrUpdate(T entity) {
return Db.saveOrUpdate(entity);
}
/**
* 查询Vo by id
* @param id 主键
* @return Vo
*/
default V selectVoById(Serializable id) {
return selectVoById(id, this.currentVoClass());
}
/**
* 根据 ID 查询
*
* @param id 主键
* @param voClass Vo
* @param <C> 泛型
* @return Vo
*/
default <C> C selectVoById(Serializable id, Class<C> voClass) {
T obj = this.selectById(id);
if (ObjectUtil.isNull(obj)) {
return null;
}
return BeanCopyUtil.copy(obj, voClass);
}
/**
* 通过Ids 查询 list
*
* @param idList ids
* @return list
*/
default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
return selectVoBatchIds(idList, this.currentVoClass());
}
/**
* 通过Ids 查询 list
*
* @param idList ids
* @param voClass Vo
* @param <C> 泛型
* @return list
*/
default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
List<T> list = this.selectBatchIds(idList);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtil.copyList(list, voClass);
}
/**
* selectVoByMap
*
* @param map map
* @return Vo
*/
default List<V> selectVoByMap(Map<String, Object> map) {
return selectVoByMap(map, this.currentVoClass());
}
/**
* selectVoByMap
*
* @param map map
* @param voClass Vo
* @param <C> 泛型
* @return Vo
*/
default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
List<T> list = this.selectByMap(map);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtil.copyList(list, voClass);
}
/**
* 根据条件查询一条记录
*
* @param wrapper 条件
* @return Vo
*/
default V selectVoOne(Wrapper<T> wrapper) {
return selectVoOne(wrapper, this.currentVoClass());
}
/**
* 根据条件查询一条记录
*
* @param wrapper 条件
* @param voClass Vo
* @param <C> 泛型
* @return Vo
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
T obj = this.selectOne(wrapper);
if (ObjectUtil.isNull(obj)) {
return null;
}
return BeanCopyUtil.copy(obj, voClass);
}
/**
* 根据条件查询所有
*
* @param wrapper 条件
* @return list
*/
default List<V> selectVoList(Wrapper<T> wrapper) {
return selectVoList(wrapper, this.currentVoClass());
}
/**
* 根据条件查询所有
*
* @param wrapper 条件
* @param voClass Vo
* @param <C> 泛型
* @return list vo
*/
default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
List<T> list = this.selectList(wrapper);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtil.copyList(list, voClass);
}
/**
* 分页查询Vo
*
* @param page 分页对象
* @param wrapper 条件
* @param <P> 泛型
* @return IPage
*/
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
return selectVoPage(page, wrapper, this.currentVoClass());
}
/**
* 分页查询Vo
*
* @param page 分页对象
* @param wrapper 条件
* @param voClass Vo
* @param <C> 泛型
* @param <P> 泛型
* @return IPage
*/
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
IPage<T> pageData = this.selectPage(page, wrapper);
IPage<C> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
if (CollUtil.isEmpty(pageData.getRecords())) {
return (P) voPage;
}
voPage.setRecords(BeanCopyUtil.copyList(pageData.getRecords(), voClass));
return (P) voPage;
}
}

View File

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