潘志宝
2024-08-21 d450172ef89c7478a6120675ea6ba7655d47baaa
TenantDS
已修改3个文件
已添加2个文件
167 ■■■■■ 文件已修改
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/db/dynamic/TenantDS.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/db/dynamic/TenantDsProcessor.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/service/TenantFrameworkService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/service/TenantFrameworkServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/constant/Constant.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/db/dynamic/TenantDS.java
对比新文件
@@ -0,0 +1,25 @@
package com.iailab.framework.tenant.core.db.dynamic;
import com.baomidou.dynamic.datasource.annotation.DS;
import java.lang.annotation.*;
/**
 * 使用租户所在的数据源
 *
 * 使用方式:当我们希望一个表使用租户所在的数据源,可以在该表的 Mapper 上添加该注解
 *
 * @author 芋道源码
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS(TenantDS.KEY)
public @interface TenantDS {
    /**
     * 租户对应的数据源的占位符
     */
    String KEY = "#context.tenantId";
}
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/db/dynamic/TenantDsProcessor.java
对比新文件
@@ -0,0 +1,85 @@
package com.iailab.framework.tenant.core.db.dynamic;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.iailab.framework.tenant.core.context.TenantContextHolder;
import com.iailab.framework.tenant.core.service.TenantFrameworkService;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.annotation.Lazy;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Objects;
/**
 * 基于 {@link TenantDS} 的数据源处理器
 *
 * 1. 如果有 @TenantDS 注解,返回该租户的数据源
 * 2. 如果该租户的数据源未创建,则进行创建
 *
 * @author 芋道源码
 */
@RequiredArgsConstructor
public class TenantDsProcessor extends DsProcessor {
    /**
     * 用于获取租户数据源配置的 Service
     */
    @Resource
    @Lazy
    private TenantFrameworkService tenantFrameworkService;
    /**
     * 动态数据源
     */
    @Resource
    @Lazy  // 为什么添加 @Lazy 注解?因为它和 DynamicRoutingDataSource 相互依赖,导致无法初始化
    private DynamicRoutingDataSource dynamicRoutingDataSource;
    /**
     * 用于创建租户数据源的 Creator
     */
    @Resource
    @Lazy
    private DefaultDataSourceCreator dataSourceCreator;
    @Override
    public boolean matches(String key) {
        return Objects.equals(key, TenantDS.KEY);
    }
    @Override
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        // 获得数据源配置
        Long tenantId = TenantContextHolder.getRequiredTenantId();
        DataSourceProperty dataSourceProperty = tenantFrameworkService.getDataSourceProperty(tenantId);
        // 创建 or 创建数据源,并返回数据源名字
        return createDatasourceIfAbsent(dataSourceProperty);
    }
    private String createDatasourceIfAbsent(DataSourceProperty dataSourceProperty) {
        // 1. 重点:如果数据源不存在,则进行创建
        if (isDataSourceNotExist(dataSourceProperty)) {
            // 问题一:为什么要加锁?因为,如果多个线程同时执行到这里,会导致多次创建数据源
            // 问题二:为什么要使用 poolName 加锁?保证多个不同的 poolName 可以并发创建数据源
            // 问题三:为什么要使用 intern 方法?因为,intern 方法,会返回一个字符串的常量池中的引用
            // intern 的说明,可见 https://www.cnblogs.com/xrq730/p/6662232.html 文章
            synchronized (dataSourceProperty.getPoolName().intern()) {
                if (isDataSourceNotExist(dataSourceProperty)) {
                    DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
                    dynamicRoutingDataSource.addDataSource(dataSourceProperty.getPoolName(), dataSource);
                }
            }
        }
        // 2. 返回数据源的名字
        return dataSourceProperty.getPoolName();
    }
    private boolean isDataSourceNotExist(DataSourceProperty dataSourceProperty) {
        return !dynamicRoutingDataSource.getDataSources().containsKey(dataSourceProperty.getPoolName());
    }
}
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/service/TenantFrameworkService.java
@@ -1,5 +1,7 @@
package com.iailab.framework.tenant.core.service;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import java.util.List;
/**
@@ -23,4 +25,12 @@
     */
    void validTenant(Long id);
    /**
     * 获得租户对应的数据源配置
     *
     * @param id 租户编号
     * @return 数据源配置
     */
    DataSourceProperty getDataSourceProperty(Long id);
}
iailab-framework/iailab-common-biz-tenant/src/main/java/com/iailab/framework/tenant/core/service/TenantFrameworkServiceImpl.java
@@ -1,10 +1,13 @@
package com.iailab.framework.tenant.core.service;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.framework.common.util.cache.CacheUtils;
import com.iailab.module.system.api.tenant.TenantApi;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.iailab.module.system.api.tenant.dto.TenantDataSourceConfigRespDTO;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@@ -51,6 +54,35 @@
            });
    /**
     * 针对 {@link #getDataSourceProperty(Long)} 的缓存
     */
    private final LoadingCache<Long, DataSourceProperty> dataSourcePropertyCache = CacheUtils.buildAsyncReloadingCache(
            Duration.ofMinutes(1L), // 过期时间 1 分钟
            new CacheLoader<Long, DataSourceProperty>() {
                @Override
                public DataSourceProperty load(Long id) {
                    // 获得租户对应的数据源配置
                    TenantDataSourceConfigRespDTO dataSourceConfig = tenantApi.getTenantDataSourceConfig(id);
                    if (dataSourceConfig == null) {
                        return null;
                    }
                    // 转换成 dynamic-datasource 配置
                    /*return new DataSourceProperty()
                            .setPoolName(dataSourceConfig.getName()).setUrl(dataSourceConfig.getUrl())
                            .setUsername(dataSourceConfig.getUsername()).setPassword(dataSourceConfig.getPassword());*/
                    DataSourceProperty ds = new DataSourceProperty();
                    ds.setPoolName(dataSourceConfig.getName());
                    ds.setUrl(dataSourceConfig.getUrl());
                    ds.setUsername(dataSourceConfig.getUsername());
                    ds.setPassword(dataSourceConfig.getPassword());
                    return ds;
                }
            });
    @Override
    @SneakyThrows
    public List<Long> getTenantIds() {
@@ -63,4 +95,9 @@
        validTenantCache.get(id).checkError();
    }
    @Override
    @SneakyThrows
    public DataSourceProperty getDataSourceProperty(Long id) {
        return dataSourcePropertyCache.get(id);
    }
}
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/constant/Constant.java
@@ -71,6 +71,16 @@
    String TOKEN_HEADER = "token";
    /**
     * tenantCode
     */
    String TENANT_CODE = "tenantCode";
    /**
     * tenantId
     */
    String TENANT_ID = "tenantId";
    /**
     * 云存储配置KEY
     */
    String CLOUD_STORAGE_CONFIG_KEY = "CLOUD_STORAGE_CONFIG_KEY";