Browse Source

!28 完成多数据源配置
Merge pull request !28 from timfruit/feature/multi-datasource

芋道源码 4 years ago
parent
commit
61799385c9
22 changed files with 235 additions and 569 deletions
  1. 6 5
      README.md
  2. 6 0
      pom.xml
  3. 0 27
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java
  4. 0 145
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScopeAspect.java
  5. 0 28
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java
  6. 0 64
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSourceAspect.java
  7. 0 19
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSourceType.java
  8. 0 24
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSource.java
  9. 0 41
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSourceContextHolder.java
  10. 0 116
      ruoyi-common/src/main/java/com/ruoyi/common/config/DruidConfig.java
  11. 0 37
      ruoyi-common/src/main/resources/application-druid.yml
  12. 27 1
      src/main/java/cn/iocoder/dashboard/framework/datasource/config/DataSourceConfiguration.java
  13. 22 0
      src/main/java/cn/iocoder/dashboard/framework/datasource/core/enums/DataSourceEnum.java
  14. 38 0
      src/main/java/cn/iocoder/dashboard/framework/datasource/core/filter/DruidAdRemoveFilter.java
  15. 1 5
      src/main/java/cn/iocoder/dashboard/modules/tool/dal/mysql/codegen/ToolSchemaTableMapper.java
  16. 2 2
      src/main/java/cn/iocoder/dashboard/modules/tool/service/codegen/impl/ToolCodegenServiceImpl.java
  17. 39 17
      src/main/resources/application-dev.yaml
  18. 39 17
      src/main/resources/application-local.yaml
  19. 3 1
      src/main/resources/application.yaml
  20. 3 2
      src/test-integration/java/cn/iocoder/dashboard/BaseDbAndRedisIntegrationTest.java
  21. 0 12
      src/test-integration/java/cn/iocoder/dashboard/modules/tool/dal/mysql/codegen/ToolInformationSchemaTableMapperTest.java
  22. 49 6
      src/test-integration/resources/application-integration-test.yaml

+ 6 - 5
README.md

@@ -35,12 +35,12 @@
 |  | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
 |  | 岗位管理 | 配置系统用户所属担任职务 |
 |  | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
-|  | 通知公告 | 系统通知公告信息发布维护 |
+| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、云片等主流短信平台 |
 | 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
 |  | 登录日志 | 系统登录日志记录查询包含登录异常 |
+|  | 通知公告 | 系统通知公告信息发布维护 |
 
 计划新增功能:
-* 短信
 * 邮件
 * 钉钉、飞书等通知
 
@@ -87,14 +87,15 @@
 
 ## 技术栈
 
-**后端**
+### 后端
 
 | 框架 | 说明 |  版本 | 学习指南 |
 | --- | --- | --- | --- |
 | [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.4.2 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
 | [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 |  |
 | [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.4 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
-| [MyBatis-Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
+| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
+| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.3.2 | [文档](hhttp://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | [Redis](https://redis.io/) | key-value 数据库 | 5.0 |  |
 | [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.1.46 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
 | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架  | 5.4.2 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
@@ -111,7 +112,7 @@
 | [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.7.0 | - |
 | [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 3.6.28 | - |
 
-**前端**
+### 前端
 
 | 框架 | 说明 |  版本 |
 | --- | --- | --- |

+ 6 - 0
pom.xml

@@ -30,6 +30,7 @@
         <mysql-connector-java.version>5.1.46</mysql-connector-java.version>
         <druid.version>1.2.4</druid.version>
         <mybatis-plus.version>3.4.2</mybatis-plus.version>
+        <dynamic-datasource.version>3.3.2</dynamic-datasource.version>
         <redisson.version>3.15.1</redisson.version>
         <!-- Config 配置中心相关 -->
         <apollo.version>1.7.0</apollo.version>
@@ -139,6 +140,11 @@
             <artifactId>mybatis-plus-boot-starter</artifactId>
             <version>${mybatis-plus.version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
+            <version>${dynamic-datasource.version}</version>
+        </dependency>
 
         <dependency>
             <groupId>org.redisson</groupId>

+ 0 - 27
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java

@@ -1,27 +0,0 @@
-package com.ruoyi.common.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * 数据权限过滤注解
- *
- * @author ruoyi
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface DataScope {
-    /**
-     * 部门表的别名
-     */
-    public String deptAlias() default "";
-
-    /**
-     * 用户表的别名
-     */
-    public String userAlias() default "";
-}

+ 0 - 145
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScopeAspect.java

@@ -1,145 +0,0 @@
-package com.ruoyi.framework.aspectj;
-
-import java.lang.reflect.Method;
-
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.Signature;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Before;
-import org.aspectj.lang.annotation.Pointcut;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.springframework.stereotype.Component;
-import com.ruoyi.common.annotation.DataScope;
-import com.ruoyi.common.core.domain.BaseEntity;
-import com.ruoyi.common.core.domain.entity.SysRole;
-import com.ruoyi.common.core.domain.entity.SysUser;
-import com.ruoyi.common.core.domain.model.LoginUser;
-import com.ruoyi.common.utils.ServletUtils;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.common.utils.spring.SpringUtils;
-import com.ruoyi.framework.web.service.TokenService;
-
-/**
- * 数据过滤处理
- *
- * @author ruoyi
- */
-@Aspect
-@Component
-public class DataScopeAspect {
-    /**
-     * 全部数据权限
-     */
-    public static final String DATA_SCOPE_ALL = "1";
-
-    /**
-     * 自定数据权限
-     */
-    public static final String DATA_SCOPE_CUSTOM = "2";
-
-    /**
-     * 部门数据权限
-     */
-    public static final String DATA_SCOPE_DEPT = "3";
-
-    /**
-     * 部门及以下数据权限
-     */
-    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
-
-    /**
-     * 仅本人数据权限
-     */
-    public static final String DATA_SCOPE_SELF = "5";
-
-    /**
-     * 数据权限过滤关键字
-     */
-    public static final String DATA_SCOPE = "dataScope";
-
-    // 配置织入点
-    @Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)")
-    public void dataScopePointCut() {
-    }
-
-    @Before("dataScopePointCut()")
-    public void doBefore(JoinPoint point) throws Throwable {
-        handleDataScope(point);
-    }
-
-    protected void handleDataScope(final JoinPoint joinPoint) {
-        // 获得注解
-        DataScope controllerDataScope = getAnnotationLog(joinPoint);
-        if (controllerDataScope == null) {
-            return;
-        }
-        // 获取当前的用户
-        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
-        if (StringUtils.isNotNull(loginUser)) {
-            SysUser currentUser = loginUser.getUser();
-            // 如果是超级管理员,则不过滤数据
-            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
-                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
-                        controllerDataScope.userAlias());
-            }
-        }
-    }
-
-    /**
-     * 数据范围过滤
-     *
-     * @param joinPoint 切点
-     * @param user      用户
-     * @param userAlias 别名
-     */
-    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) {
-        StringBuilder sqlString = new StringBuilder();
-
-        for (SysRole role : user.getRoles()) {
-            String dataScope = role.getDataScope();
-            if (DATA_SCOPE_ALL.equals(dataScope)) {
-                sqlString = new StringBuilder();
-                break;
-            } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
-                sqlString.append(StringUtils.format(
-                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
-                        role.getRoleId()));
-            } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
-                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
-            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
-                sqlString.append(StringUtils.format(
-                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
-                        deptAlias, user.getDeptId(), user.getDeptId()));
-            } else if (DATA_SCOPE_SELF.equals(dataScope)) {
-                if (StringUtils.isNotBlank(userAlias)) {
-                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
-                } else {
-                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
-                    sqlString.append(" OR 1=0 ");
-                }
-            }
-        }
-
-        if (StringUtils.isNotBlank(sqlString.toString())) {
-            Object params = joinPoint.getArgs()[0];
-            if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
-                BaseEntity baseEntity = (BaseEntity) params;
-                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
-            }
-        }
-    }
-
-    /**
-     * 是否存在注解,如果存在就获取
-     */
-    private DataScope getAnnotationLog(JoinPoint joinPoint) {
-        Signature signature = joinPoint.getSignature();
-        MethodSignature methodSignature = (MethodSignature) signature;
-        Method method = methodSignature.getMethod();
-
-        if (method != null) {
-            return method.getAnnotation(DataScope.class);
-        }
-        return null;
-    }
-}

+ 0 - 28
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java

@@ -1,28 +0,0 @@
-package com.ruoyi.common.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import com.ruoyi.common.enums.DataSourceType;
-
-/**
- * 自定义多数据源切换注解
- * <p>
- * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
- *
- * @author ruoyi
- */
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Inherited
-public @interface DataSource {
-    /**
-     * 切换数据源名称
-     */
-    public DataSourceType value() default DataSourceType.MASTER;
-}

+ 0 - 64
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSourceAspect.java

@@ -1,64 +0,0 @@
-package com.ruoyi.framework.aspectj;
-
-import java.util.Objects;
-
-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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.core.annotation.AnnotationUtils;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Component;
-import com.ruoyi.common.annotation.DataSource;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
-
-/**
- * 多数据源处理
- *
- * @author ruoyi
- */
-@Aspect
-@Order(1)
-@Component
-public class DataSourceAspect {
-    protected Logger logger = LoggerFactory.getLogger(getClass());
-
-    @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
-            + "|| @within(com.ruoyi.common.annotation.DataSource)")
-    public void dsPointCut() {
-
-    }
-
-    @Around("dsPointCut()")
-    public Object around(ProceedingJoinPoint point) throws Throwable {
-        DataSource dataSource = getDataSource(point);
-
-        if (StringUtils.isNotNull(dataSource)) {
-            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
-        }
-
-        try {
-            return point.proceed();
-        } finally {
-            // 销毁数据源 在执行方法之后
-            DynamicDataSourceContextHolder.clearDataSourceType();
-        }
-    }
-
-    /**
-     * 获取需要切换的数据源
-     */
-    public DataSource getDataSource(ProceedingJoinPoint point) {
-        MethodSignature signature = (MethodSignature) point.getSignature();
-        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
-        if (Objects.nonNull(dataSource)) {
-            return dataSource;
-        }
-
-        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
-    }
-}

+ 0 - 19
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSourceType.java

@@ -1,19 +0,0 @@
-package com.ruoyi.common.enums;
-
-/**
- * 数据源
- * 
- * @author ruoyi
- */
-public enum DataSourceType
-{
-    /**
-     * 主库
-     */
-    MASTER,
-
-    /**
-     * 从库
-     */
-    SLAVE
-}

+ 0 - 24
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSource.java

@@ -1,24 +0,0 @@
-package com.ruoyi.framework.datasource;
-
-import java.util.Map;
-import javax.sql.DataSource;
-
-import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
-
-/**
- * 动态数据源
- *
- * @author ruoyi
- */
-public class DynamicDataSource extends AbstractRoutingDataSource {
-    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
-        super.setDefaultTargetDataSource(defaultTargetDataSource);
-        super.setTargetDataSources(targetDataSources);
-        super.afterPropertiesSet();
-    }
-
-    @Override
-    protected Object determineCurrentLookupKey() {
-        return DynamicDataSourceContextHolder.getDataSourceType();
-    }
-}

+ 0 - 41
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSourceContextHolder.java

@@ -1,41 +0,0 @@
-package com.ruoyi.framework.datasource;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * 数据源切换处理
- *
- * @author ruoyi
- */
-public class DynamicDataSourceContextHolder {
-    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
-
-    /**
-     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
-     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
-     */
-    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
-
-    /**
-     * 设置数据源的变量
-     */
-    public static void setDataSourceType(String dsType) {
-        log.info("切换到{}数据源", dsType);
-        CONTEXT_HOLDER.set(dsType);
-    }
-
-    /**
-     * 获得数据源的变量
-     */
-    public static String getDataSourceType() {
-        return CONTEXT_HOLDER.get();
-    }
-
-    /**
-     * 清空数据源变量
-     */
-    public static void clearDataSourceType() {
-        CONTEXT_HOLDER.remove();
-    }
-}

+ 0 - 116
ruoyi-common/src/main/java/com/ruoyi/common/config/DruidConfig.java

@@ -1,116 +0,0 @@
-package com.ruoyi.framework.config;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.sql.DataSource;
-
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import com.alibaba.druid.pool.DruidDataSource;
-import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
-import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
-import com.alibaba.druid.util.Utils;
-import com.ruoyi.common.enums.DataSourceType;
-import com.ruoyi.common.utils.spring.SpringUtils;
-import com.ruoyi.framework.config.properties.DruidProperties;
-import com.ruoyi.framework.datasource.DynamicDataSource;
-
-/**
- * druid 配置多数据源
- *
- * @author ruoyi
- */
-@Configuration
-public class DruidConfig {
-    @Bean
-    @ConfigurationProperties("spring.datasource.druid.master")
-    public DataSource masterDataSource(DruidProperties druidProperties) {
-        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
-        return druidProperties.dataSource(dataSource);
-    }
-
-    @Bean
-    @ConfigurationProperties("spring.datasource.druid.slave")
-    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
-    public DataSource slaveDataSource(DruidProperties druidProperties) {
-        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
-        return druidProperties.dataSource(dataSource);
-    }
-
-    @Bean(name = "dynamicDataSource")
-    @Primary
-    public DynamicDataSource dataSource(DataSource masterDataSource) {
-        Map<Object, Object> targetDataSources = new HashMap<>();
-        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
-        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
-        return new DynamicDataSource(masterDataSource, targetDataSources);
-    }
-
-    /**
-     * 设置数据源
-     *
-     * @param targetDataSources 备选数据源集合
-     * @param sourceName        数据源名称
-     * @param beanName          bean名称
-     */
-    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
-        try {
-            DataSource dataSource = SpringUtils.getBean(beanName);
-            targetDataSources.put(sourceName, dataSource);
-        } catch (Exception e) {
-        }
-    }
-
-    /**
-     * 去除监控页面底部的广告
-     */
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    @Bean
-    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
-    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
-        // 获取web监控页面的参数
-        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
-        // 提取common.js的配置路径
-        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
-        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
-        final String filePath = "support/http/resources/js/common.js";
-        // 创建filter进行过滤
-        Filter filter = new Filter() {
-            @Override
-            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
-            }
-
-            @Override
-            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
-                    throws IOException, ServletException {
-                chain.doFilter(request, response);
-                // 重置缓冲区,响应头不会被重置
-                response.resetBuffer();
-                // 获取common.js
-                String text = Utils.readFromResource(filePath);
-                // 正则替换banner, 除去底部的广告信息
-                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
-                text = text.replaceAll("powered.*?shrek.wang</a>", "");
-                response.getWriter().write(text);
-            }
-
-            @Override
-            public void destroy() {
-            }
-        };
-        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
-        registrationBean.setFilter(filter);
-        registrationBean.addUrlPatterns(commonJsPattern);
-        return registrationBean;
-    }
-}

+ 0 - 37
ruoyi-common/src/main/resources/application-druid.yml

@@ -1,37 +0,0 @@
-# 数据源配置
-spring:
-    datasource:
-        type: com.alibaba.druid.pool.DruidDataSource
-        driverClassName: com.mysql.cj.jdbc.Driver
-        druid:
-            # 主库数据源
-            master:
-                url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                username: root
-                password: password
-            # 从库数据源
-            slave:
-                # 从数据源开关/默认关闭
-                enabled: false
-                url:
-                username:
-                password:
-            # 初始连接数
-            initialSize: 5
-            # 最小连接池数量
-            minIdle: 10
-            # 最大连接池数量
-            maxActive: 20
-            # 配置获取连接等待超时的时间
-            maxWait: 60000
-            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-            timeBetweenEvictionRunsMillis: 60000
-            # 配置一个连接在池中最小生存的时间,单位是毫秒
-            minEvictableIdleTimeMillis: 300000
-            # 配置一个连接在池中最大生存的时间,单位是毫秒
-            maxEvictableIdleTimeMillis: 900000
-            # 配置检测连接是否有效
-            validationQuery: SELECT 1 FROM DUAL
-            testWhileIdle: true
-            testOnBorrow: false
-            testOnReturn: false

+ 27 - 1
src/main/java/cn/iocoder/dashboard/framework/datasource/config/DataSourceConfiguration.java

@@ -1,12 +1,38 @@
 package cn.iocoder.dashboard.framework.datasource.config;
 
+import cn.iocoder.dashboard.framework.datasource.core.filter.DruidAdRemoveFilter;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 /**
- * 数据库匹配类
+ * 数据库配置类
+ *
+ * @author 芋道源码
  */
 @Configuration
 @EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理
 public class DataSourceConfiguration {
+
+    /**
+     * 创建 DruidAdRemoveFilter 过滤器,过滤 common.js 的广告
+     */
+    @Bean
+    @ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true")
+    public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
+        // 获取 druid web 监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取 common.js 的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+        // 创建 DruidAdRemoveFilter Bean
+        FilterRegistrationBean<DruidAdRemoveFilter> registrationBean = new FilterRegistrationBean<>();
+        registrationBean.setFilter(new DruidAdRemoveFilter());
+        registrationBean.addUrlPatterns(commonJsPattern);
+        return registrationBean;
+    }
+
 }

+ 22 - 0
src/main/java/cn/iocoder/dashboard/framework/datasource/core/enums/DataSourceEnum.java

@@ -0,0 +1,22 @@
+package cn.iocoder.dashboard.framework.datasource.core.enums;
+
+/**
+ * 对应于多数据源中不同数据源配置
+ *
+ * 通过在方法上,使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解,设置使用的数据源。
+ * 注意,默认是 {@link #MASTER} 数据源
+ *
+ * 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html
+ */
+public interface DataSourceEnum {
+
+    /**
+     * 主库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Master} 注解
+     */
+    String MASTER = "master";
+    /**
+     * 从库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Slave} 注解
+     */
+    String SLAVE = "slave";
+
+}

+ 38 - 0
src/main/java/cn/iocoder/dashboard/framework/datasource/core/filter/DruidAdRemoveFilter.java

@@ -0,0 +1,38 @@
+package cn.iocoder.dashboard.framework.datasource.core.filter;
+
+import com.alibaba.druid.util.Utils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Druid 底部广告过滤器
+ *
+ * @author 芋道源码
+ */
+public class DruidAdRemoveFilter extends OncePerRequestFilter {
+
+    /**
+     * common.js 的路径
+     */
+    private static final String COMMON_JS_ILE_PATH = "support/http/resources/js/common.js";
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        chain.doFilter(request, response);
+        // 重置缓冲区,响应头不会被重置
+        response.resetBuffer();
+        // 获取 common.js
+        String text = Utils.readFromResource(COMMON_JS_ILE_PATH);
+        // 正则替换 banner, 除去底部的广告信息
+        text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+        text = text.replaceAll("powered.*?shrek.wang</a>", "");
+        response.getWriter().write(text);
+    }
+
+}

+ 1 - 5
src/main/java/cn/iocoder/dashboard/modules/tool/dal/mysql/codegen/ToolSchemaTableMapper.java

@@ -18,11 +18,7 @@ public interface ToolSchemaTableMapper extends BaseMapperX<ToolSchemaTableDO> {
                 .likeIfPresent("table_comment", tableComment));
     }
 
-    default List<ToolSchemaTableDO> selectListByTableSchema(String tableSchema) {
-        return selectList(new QueryWrapper<ToolSchemaTableDO>().eq("table_schema", tableSchema));
-    }
-
-    default ToolSchemaTableDO selectByTableName1(String tableSchema, String tableName) {
+    default ToolSchemaTableDO selectByTableSchemaAndTableName(String tableSchema, String tableName) {
         return selectOne(new QueryWrapper<ToolSchemaTableDO>().eq("table_schema",tableSchema)
                         .eq("table_name", tableName));
     }

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/tool/service/codegen/impl/ToolCodegenServiceImpl.java

@@ -101,10 +101,10 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
 
     @Override
     public Long createCodegen(String tableName) {
-        //获取当前schema
+        // 获取当前schema
         String tableSchema = codegenProperties.getDbSchemas().iterator().next();
         // 从数据库中,获得数据库表结构
-        ToolSchemaTableDO schemaTable = schemaTableMapper.selectByTableName1(tableSchema, tableName);
+        ToolSchemaTableDO schemaTable = schemaTableMapper.selectByTableSchemaAndTableName(tableSchema, tableName);
         List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableSchema, tableName);
         // 导入
         return this.createCodegen0(ToolCodegenImportTypeEnum.DB, schemaTable, schemaColumns);

+ 39 - 17
src/main/resources/application-dev.yaml

@@ -4,34 +4,56 @@ server:
 --- #################### 数据库相关配置 ####################
 
 spring:
-  # 数据源配置项 TODO 多数据源;TODO 监控配置
+  # 数据源配置项
+  autoconfigure:
+    exclude:
+      - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
   datasource:
-    name: ruoyi-vue-pro
-    url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
-    driver-class-name: com.mysql.jdbc.Driver
-    username: root
-    password: 3WLiVUBEwTbvAfsh
-    druid:
+    druid: # Druid 【监控】相关的全局配置
       web-stat-filter:
         enabled: true
       stat-view-servlet:
         enabled: true
-        # 设置白名单,不填则允许所有访问
-        allow:
+        allow: # 设置白名单,不填则允许所有访问
         url-pattern: /druid/*
-        # 控制台管理用户名和密码
-        login-username:
+        login-username: # 控制台管理用户名和密码
         login-password:
       filter:
         stat:
           enabled: true
-          # 慢 SQL 记录
-          log-slow-sql: true
+          log-slow-sql: true # 慢 SQL 记录
           slow-sql-millis: 100
           merge-sql: true
         wall:
           config:
             multi-statement-allow: true
+    dynamic: # 多数据源配置
+      druid: # Druid 【连接池】相关的全局配置
+        initial-size: 5 # 初始连接数
+        min-idle: 10 # 最小连接池数量
+        max-active: 20 # 最大连接池数量
+        max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
+        time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
+        min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
+        max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
+        validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
+        test-while-idle: true
+        test-on-borrow: false
+        test-on-return: false
+      primary: master
+      datasource:
+        master:
+          name: ruoyi-vue-pro
+          url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 3WLiVUBEwTbvAfsh
+        slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
+          name: ruoyi-vue-pro
+          url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 3WLiVUBEwTbvAfsh
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:
@@ -77,9 +99,9 @@ apollo:
     eagerLoad:
       enabled: true # 设置 Apollo 在日志初始化前生效,可以实现日志的动态级别配置
   jdbc: # 自定义的 JDBC 配置项,用于数据库的地址
-    url: ${spring.datasource.url}
-    username: ${spring.datasource.username}
-    password: ${spring.datasource.password}
+    url: ${spring.datasource.dynamic.datasource.master.url}
+    username: ${spring.datasource.dynamic.datasource.master.username}
+    password: ${spring.datasource.dynamic.datasource.master.password}
 
 --- #################### 服务保障相关配置 ####################
 
@@ -155,7 +177,7 @@ yudao:
     base-path: http://127.0.0.1:${server.port}${yudao.web.api-prefix}/system/file/get/
   codegen:
     base-package: ${yudao.info.base-package}
-    db-schemas: ${spring.datasource.name}
+    db-schemas: ${spring.datasource.dynamic.datasource.master.name}
   xss:
     enable: false
     exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系

+ 39 - 17
src/main/resources/application-local.yaml

@@ -4,34 +4,56 @@ server:
 --- #################### 数据库相关配置 ####################
 
 spring:
-  # 数据源配置项 TODO 多数据源;TODO 监控配置
+  # 数据源配置项
+  autoconfigure:
+    exclude:
+      - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
   datasource:
-    name: ruoyi-vue-pro
-    url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
-    driver-class-name: com.mysql.jdbc.Driver
-    username: root
-    password: 123456
-    druid:
+    druid: # Druid 【监控】相关的全局配置
       web-stat-filter:
         enabled: true
       stat-view-servlet:
         enabled: true
-        # 设置白名单,不填则允许所有访问
-        allow:
+        allow: # 设置白名单,不填则允许所有访问
         url-pattern: /druid/*
-        # 控制台管理用户名和密码
-        login-username:
+        login-username: # 控制台管理用户名和密码
         login-password:
       filter:
         stat:
           enabled: true
-          # 慢 SQL 记录
-          log-slow-sql: true
+          log-slow-sql: true # 慢 SQL 记录
           slow-sql-millis: 100
           merge-sql: true
         wall:
           config:
             multi-statement-allow: true
+    dynamic: # 多数据源配置
+      druid: # Druid 【连接池】相关的全局配置
+        initial-size: 5 # 初始连接数
+        min-idle: 10 # 最小连接池数量
+        max-active: 20 # 最大连接池数量
+        max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
+        time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
+        min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
+        max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
+        validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
+        test-while-idle: true
+        test-on-borrow: false
+        test-on-return: false
+      primary: master
+      datasource:
+        master:
+          name: ruoyi-vue-pro
+          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 123456
+        slave: # 模拟从库,可根据自己需要修改
+          name: ruoyi-vue-pro
+          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 123456
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:
@@ -77,9 +99,9 @@ apollo:
     eagerLoad:
       enabled: true # 设置 Apollo 在日志初始化前生效,可以实现日志的动态级别配置
   jdbc: # 自定义的 JDBC 配置项,用于数据库的地址
-    url: ${spring.datasource.url}
-    username: ${spring.datasource.username}
-    password: ${spring.datasource.password}
+    url: ${spring.datasource.dynamic.datasource.master.url}
+    username: ${spring.datasource.dynamic.datasource.master.username}
+    password: ${spring.datasource.dynamic.datasource.master.password}
 
 --- #################### 服务保障相关配置 ####################
 
@@ -155,7 +177,7 @@ yudao:
     base-path: http://127.0.0.1:${server.port}${yudao.web.api-prefix}/system/file/get/
   codegen:
     base-package: ${yudao.info.base-package}
-    db-schemas: ${spring.datasource.name}
+    db-schemas: ${spring.datasource.dynamic.datasource.master.name}
   xss:
     enable: false
     exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系

+ 3 - 1
src/main/resources/application.yaml

@@ -12,7 +12,6 @@ spring:
       max-file-size: 16MB # 单个文件大小
       max-request-size: 32MB # 设置总上传的文件大小
 
-  # Jackson 配置项
   jackson:
     serialization:
       write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
@@ -32,3 +31,6 @@ mybatis-plus:
       logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
   mapper-locations: classpath*:mapper/*.xml
   type-aliases-package: ${yudao.info.base-package}.modules.*.dal.dataobject
+
+--- #################### 芋道相关配置 ####################
+

+ 3 - 2
src/test-integration/java/cn/iocoder/dashboard/BaseDbAndRedisIntegrationTest.java

@@ -3,7 +3,7 @@ package cn.iocoder.dashboard;
 import cn.iocoder.dashboard.framework.datasource.config.DataSourceConfiguration;
 import cn.iocoder.dashboard.framework.mybatis.config.MybatisConfiguration;
 import cn.iocoder.dashboard.framework.redis.config.RedisConfig;
-import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
 import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@@ -19,13 +19,14 @@ public class BaseDbAndRedisIntegrationTest {
 
     @Import({
             // DB 配置类
+            DynamicDataSourceAutoConfiguration.class, // Dynamic Datasource 配置类
             DataSourceConfiguration.class, // 自己的 DB 配置类
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
-            DruidDataSourceAutoConfigure.class, // Druid 自动配置类
             // MyBatis 配置类
             MybatisConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
+
             // Redis 配置类
             RedisAutoConfiguration.class, // Spring Redis 自动配置类
             RedisConfig.class, // 自己的 Redis 配置类

+ 0 - 12
src/test-integration/java/cn/iocoder/dashboard/modules/tool/dal/mysql/codegen/ToolInformationSchemaTableMapperTest.java

@@ -1,24 +1,12 @@
 package cn.iocoder.dashboard.modules.tool.dal.mysql.codegen;
 
 import cn.iocoder.dashboard.BaseDbUnitTest;
-import cn.iocoder.dashboard.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
-import org.junit.jupiter.api.Test;
 
 import javax.annotation.Resource;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class ToolInformationSchemaTableMapperTest extends BaseDbUnitTest {
 
     @Resource
     private ToolSchemaTableMapper toolInformationSchemaTableMapper;
 
-    @Test
-    public void tstSelectListByTableSchema() {
-        List<ToolSchemaTableDO> tables = toolInformationSchemaTableMapper
-                .selectListByTableSchema("ruoyi-vue-pro");
-        assertTrue(tables.size() > 0);
-    }
-
 }

+ 49 - 6
src/test-integration/resources/application-integration-test.yaml

@@ -7,12 +7,55 @@ spring:
 
 spring:
   # 数据源配置项
+  autoconfigure:
+    exclude:
+      - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
   datasource:
-    name: ruoyi-vue-pro
-    url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
-    driver-class-name: com.mysql.jdbc.Driver
-    username: root
-    password: 123456
+    druid: # Druid 【监控】相关的全局配置
+      web-stat-filter:
+        enabled: true
+      stat-view-servlet:
+        enabled: true
+        allow: # 设置白名单,不填则允许所有访问
+        url-pattern: /druid/*
+        login-username: # 控制台管理用户名和密码
+        login-password:
+      filter:
+        stat:
+          enabled: true
+          log-slow-sql: true # 慢 SQL 记录
+          slow-sql-millis: 100
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+    dynamic: # 多数据源配置
+      druid: # Druid 【连接池】相关的全局配置
+        initial-size: 5 # 初始连接数
+        min-idle: 10 # 最小连接池数量
+        max-active: 20 # 最大连接池数量
+        max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
+        time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
+        min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
+        max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
+        validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
+        test-while-idle: true
+        test-on-borrow: false
+        test-on-return: false
+      primary: master
+      datasource:
+        master:
+          name: ruoyi-vue-pro
+          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 123456
+        slave: # 模拟从库,可根据自己需要修改
+          name: ruoyi-vue-pro
+          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 123456
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:
@@ -70,7 +113,7 @@ yudao:
     base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/
   codegen:
     base-package: ${yudao.info.base-package}.modules
-    db-schemas: ${spring.datasource.name}
+    db-schemas: ${spring.datasource.dynamic.datasource.master.name}
   xss:
     enable: false
     exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系