Browse Source

完成主要在线 session 的功能

YunaiV 4 years ago
parent
commit
753c7678ee
34 changed files with 1349 additions and 1260 deletions
  1. 3 5
      ruoyi-common/src/main/java/com/ruoyi/common/SwaggerController.java
  2. 145 145
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScopeAspect.java
  3. 64 64
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSourceAspect.java
  4. 19 19
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSourceType.java
  5. 24 24
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSource.java
  6. 41 41
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSourceContextHolder.java
  7. 116 116
      ruoyi-common/src/main/java/com/ruoyi/common/config/DruidConfig.java
  8. 58 58
      ruoyi-common/src/main/java/com/ruoyi/common/config/FilterConfig.java
  9. 44 44
      ruoyi-common/src/main/java/com/ruoyi/common/config/ResourcesConfig.java
  10. 30 30
      ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java
  11. 58 58
      ruoyi-common/src/main/java/com/ruoyi/common/config/ThreadPoolConfig.java
  12. 75 75
      ruoyi-common/src/main/java/com/ruoyi/common/config/properties/DruidProperties.java
  13. 21 21
      ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatSubmit.java
  14. 49 49
      ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatSubmitInterceptor.java
  15. 114 114
      ruoyi-common/src/main/java/com/ruoyi/common/filter/SameUrlDataInterceptor.java
  16. 56 56
      ruoyi-common/src/main/resources/application-druid.yml
  17. 44 44
      ruoyi-common/src/main/resources/application.yml
  18. 36 36
      ruoyi-common/src/main/resources/i18n/messages.properties
  19. 92 92
      ruoyi-common/src/main/resources/logback.xml
  20. 18 18
      ruoyi-ui/src/api/system/session.js
  21. 121 128
      ruoyi-ui/src/views/system/session/index.vue
  22. 34 4
      src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysUserSessionController.java
  23. 3 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageItemRespVO.java
  24. 0 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageReqVO.java
  25. 16 0
      src/main/java/cn/iocoder/dashboard/modules/system/convert/auth/SysUserSessionConvert.java
  26. 14 2
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserSessionMapper.java
  27. 4 0
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/user/SysUserMapper.java
  28. 2 1
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/auth/SysUserSessionDO.java
  29. 2 2
      src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/auth/SysLoginUserRedisDAO.java
  30. 1 0
      src/main/java/cn/iocoder/dashboard/modules/system/job/package-info.java
  31. 25 5
      src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java
  32. 8 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserService.java
  33. 5 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java
  34. 7 7
      src/main/java/cn/iocoder/dashboard/util/collection/CollectionUtils.java

+ 3 - 5
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SwaggerController.java → ruoyi-common/src/main/java/com/ruoyi/common/SwaggerController.java

@@ -8,17 +8,15 @@ import com.ruoyi.common.core.controller.BaseController;
 
 /**
  * swagger 接口
- * 
+ *
  * @author ruoyi
  */
 @Controller
 @RequestMapping("/tool/swagger")
-public class SwaggerController extends BaseController
-{
+public class SwaggerController extends BaseController {
     @PreAuthorize("@ss.hasPermi('tool:swagger:view')")
     @GetMapping()
-    public String index()
-    {
+    public String index() {
         return redirect("/swagger-ui.html");
     }
 }

+ 145 - 145
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java → ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScopeAspect.java

@@ -1,145 +1,145 @@
-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;
-    }
-}
+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;
+    }
+}

+ 64 - 64
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java → ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSourceAspect.java

@@ -1,64 +1,64 @@
-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);
-    }
-}
+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);
+    }
+}

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

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

+ 24 - 24
ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java → ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSource.java

@@ -1,24 +1,24 @@
-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();
-    }
-}
+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();
+    }
+}

+ 41 - 41
ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java → ruoyi-common/src/main/java/com/ruoyi/common/annotation/DynamicDataSourceContextHolder.java

@@ -1,41 +1,41 @@
-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();
-    }
-}
+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();
+    }
+}

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

@@ -1,116 +1,116 @@
-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;
-    }
-}
+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;
+    }
+}

+ 58 - 58
ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java → ruoyi-common/src/main/java/com/ruoyi/common/config/FilterConfig.java

@@ -1,58 +1,58 @@
-package com.ruoyi.framework.config;
-
-import java.util.HashMap;
-import java.util.Map;
-import javax.servlet.DispatcherType;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import com.ruoyi.common.filter.RepeatableFilter;
-import com.ruoyi.common.filter.XssFilter;
-import com.ruoyi.common.utils.StringUtils;
-
-/**
- * Filter配置
- *
- * @author ruoyi
- */
-@Configuration
-public class FilterConfig {
-    @Value("${xss.enabled}")
-    private String enabled;
-
-    @Value("${xss.excludes}")
-    private String excludes;
-
-    @Value("${xss.urlPatterns}")
-    private String urlPatterns;
-
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    @Bean
-    public FilterRegistrationBean xssFilterRegistration() {
-        FilterRegistrationBean registration = new FilterRegistrationBean();
-        registration.setDispatcherTypes(DispatcherType.REQUEST);
-        registration.setFilter(new XssFilter());
-        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
-        registration.setName("xssFilter");
-        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
-        Map<String, String> initParameters = new HashMap<String, String>();
-        initParameters.put("excludes", excludes);
-        initParameters.put("enabled", enabled);
-        registration.setInitParameters(initParameters);
-        return registration;
-    }
-
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    @Bean
-    public FilterRegistrationBean someFilterRegistration() {
-        FilterRegistrationBean registration = new FilterRegistrationBean();
-        registration.setFilter(new RepeatableFilter());
-        registration.addUrlPatterns("/*");
-        registration.setName("repeatableFilter");
-        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
-        return registration;
-    }
-
-}
+package com.ruoyi.framework.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.DispatcherType;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.ruoyi.common.filter.RepeatableFilter;
+import com.ruoyi.common.filter.XssFilter;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * Filter配置
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class FilterConfig {
+    @Value("${xss.enabled}")
+    private String enabled;
+
+    @Value("${xss.excludes}")
+    private String excludes;
+
+    @Value("${xss.urlPatterns}")
+    private String urlPatterns;
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    @Bean
+    public FilterRegistrationBean xssFilterRegistration() {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setDispatcherTypes(DispatcherType.REQUEST);
+        registration.setFilter(new XssFilter());
+        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
+        registration.setName("xssFilter");
+        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+        Map<String, String> initParameters = new HashMap<String, String>();
+        initParameters.put("excludes", excludes);
+        initParameters.put("enabled", enabled);
+        registration.setInitParameters(initParameters);
+        return registration;
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    @Bean
+    public FilterRegistrationBean someFilterRegistration() {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setFilter(new RepeatableFilter());
+        registration.addUrlPatterns("/*");
+        registration.setName("repeatableFilter");
+        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
+        return registration;
+    }
+
+}

+ 44 - 44
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java → ruoyi-common/src/main/java/com/ruoyi/common/config/ResourcesConfig.java

@@ -1,44 +1,44 @@
-package com.ruoyi.framework.config;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-import org.springframework.web.filter.CorsFilter;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import com.ruoyi.common.config.RuoYiConfig;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
-
-/**
- * 通用配置
- *
- * @author ruoyi
- */
-@Configuration
-public class ResourcesConfig implements WebMvcConfigurer {
-    @Autowired
-    private RepeatSubmitInterceptor repeatSubmitInterceptor;
-
-    @Override
-    public void addResourceHandlers(ResourceHandlerRegistry registry) {
-        /** 本地文件上传路径 */
-        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
-
-        /** swagger配置 */
-        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
-        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
-    }
-
-    /**
-     * 自定义拦截规则
-     */
-    @Override
-    public void addInterceptors(InterceptorRegistry registry) {
-        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
-    }
-
-}
+package com.ruoyi.framework.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
+
+/**
+ * 通用配置
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class ResourcesConfig implements WebMvcConfigurer {
+    @Autowired
+    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        /** 本地文件上传路径 */
+        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
+
+        /** swagger配置 */
+        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
+        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+    }
+
+    /**
+     * 自定义拦截规则
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    }
+
+}

+ 30 - 30
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java → ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java

@@ -1,30 +1,30 @@
-package com.ruoyi.framework.config;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.springframework.stereotype.Component;
-import com.ruoyi.common.utils.ServletUtils;
-
-/**
- * 服务相关配置
- *
- * @author ruoyi
- */
-@Component
-public class ServerConfig {
-    /**
-     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
-     *
-     * @return 服务地址
-     */
-    public String getUrl() {
-        HttpServletRequest request = ServletUtils.getRequest();
-        return getDomain(request);
-    }
-
-    public static String getDomain(HttpServletRequest request) {
-        StringBuffer url = request.getRequestURL();
-        String contextPath = request.getServletContext().getContextPath();
-        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
-    }
-}
+package com.ruoyi.framework.config;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.ServletUtils;
+
+/**
+ * 服务相关配置
+ *
+ * @author ruoyi
+ */
+@Component
+public class ServerConfig {
+    /**
+     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
+     *
+     * @return 服务地址
+     */
+    public String getUrl() {
+        HttpServletRequest request = ServletUtils.getRequest();
+        return getDomain(request);
+    }
+
+    public static String getDomain(HttpServletRequest request) {
+        StringBuffer url = request.getRequestURL();
+        String contextPath = request.getServletContext().getContextPath();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
+    }
+}

+ 58 - 58
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java → ruoyi-common/src/main/java/com/ruoyi/common/config/ThreadPoolConfig.java

@@ -1,58 +1,58 @@
-package com.ruoyi.framework.config;
-
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadPoolExecutor;
-
-import org.apache.commons.lang3.concurrent.BasicThreadFactory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-import com.ruoyi.common.utils.Threads;
-
-/**
- * 线程池配置
- *
- * @author ruoyi
- **/
-@Configuration
-public class ThreadPoolConfig {
-    // 核心线程池大小
-    private int corePoolSize = 50;
-
-    // 最大可创建的线程数
-    private int maxPoolSize = 200;
-
-    // 队列最大长度
-    private int queueCapacity = 1000;
-
-    // 线程池维护线程所允许的空闲时间
-    private int keepAliveSeconds = 300;
-
-    @Bean(name = "threadPoolTaskExecutor")
-    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
-        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-        executor.setMaxPoolSize(maxPoolSize);
-        executor.setCorePoolSize(corePoolSize);
-        executor.setQueueCapacity(queueCapacity);
-        executor.setKeepAliveSeconds(keepAliveSeconds);
-        // 线程池对拒绝任务(无线程可用)的处理策略
-        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
-        return executor;
-    }
-
-    /**
-     * 执行周期性或定时任务
-     */
-    @Bean(name = "scheduledExecutorService")
-    protected ScheduledExecutorService scheduledExecutorService() {
-        return new ScheduledThreadPoolExecutor(corePoolSize,
-                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) {
-            @Override
-            protected void afterExecute(Runnable r, Throwable t) {
-                super.afterExecute(r, t);
-                Threads.printException(r, t);
-            }
-        };
-    }
-}
+package com.ruoyi.framework.config;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import com.ruoyi.common.utils.Threads;
+
+/**
+ * 线程池配置
+ *
+ * @author ruoyi
+ **/
+@Configuration
+public class ThreadPoolConfig {
+    // 核心线程池大小
+    private int corePoolSize = 50;
+
+    // 最大可创建的线程数
+    private int maxPoolSize = 200;
+
+    // 队列最大长度
+    private int queueCapacity = 1000;
+
+    // 线程池维护线程所允许的空闲时间
+    private int keepAliveSeconds = 300;
+
+    @Bean(name = "threadPoolTaskExecutor")
+    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setCorePoolSize(corePoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveSeconds);
+        // 线程池对拒绝任务(无线程可用)的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService() {
+        return new ScheduledThreadPoolExecutor(corePoolSize,
+                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t) {
+                super.afterExecute(r, t);
+                Threads.printException(r, t);
+            }
+        };
+    }
+}

+ 75 - 75
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java → ruoyi-common/src/main/java/com/ruoyi/common/config/properties/DruidProperties.java

@@ -1,75 +1,75 @@
-package com.ruoyi.framework.config.properties;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Configuration;
-import com.alibaba.druid.pool.DruidDataSource;
-
-/**
- * druid 配置属性
- *
- * @author ruoyi
- */
-@Configuration
-public class DruidProperties {
-    @Value("${spring.datasource.druid.initialSize}")
-    private int initialSize;
-
-    @Value("${spring.datasource.druid.minIdle}")
-    private int minIdle;
-
-    @Value("${spring.datasource.druid.maxActive}")
-    private int maxActive;
-
-    @Value("${spring.datasource.druid.maxWait}")
-    private int maxWait;
-
-    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
-    private int timeBetweenEvictionRunsMillis;
-
-    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
-    private int minEvictableIdleTimeMillis;
-
-    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
-    private int maxEvictableIdleTimeMillis;
-
-    @Value("${spring.datasource.druid.validationQuery}")
-    private String validationQuery;
-
-    @Value("${spring.datasource.druid.testWhileIdle}")
-    private boolean testWhileIdle;
-
-    @Value("${spring.datasource.druid.testOnBorrow}")
-    private boolean testOnBorrow;
-
-    @Value("${spring.datasource.druid.testOnReturn}")
-    private boolean testOnReturn;
-
-    public DruidDataSource dataSource(DruidDataSource datasource) {
-        /** 配置初始化大小、最小、最大 */
-        datasource.setInitialSize(initialSize);
-        datasource.setMaxActive(maxActive);
-        datasource.setMinIdle(minIdle);
-
-        /** 配置获取连接等待超时的时间 */
-        datasource.setMaxWait(maxWait);
-
-        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
-        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
-
-        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
-        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
-        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
-
-        /**
-         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
-         */
-        datasource.setValidationQuery(validationQuery);
-        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
-        datasource.setTestWhileIdle(testWhileIdle);
-        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
-        datasource.setTestOnBorrow(testOnBorrow);
-        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
-        datasource.setTestOnReturn(testOnReturn);
-        return datasource;
-    }
-}
+package com.ruoyi.framework.config.properties;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import com.alibaba.druid.pool.DruidDataSource;
+
+/**
+ * druid 配置属性
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class DruidProperties {
+    @Value("${spring.datasource.druid.initialSize}")
+    private int initialSize;
+
+    @Value("${spring.datasource.druid.minIdle}")
+    private int minIdle;
+
+    @Value("${spring.datasource.druid.maxActive}")
+    private int maxActive;
+
+    @Value("${spring.datasource.druid.maxWait}")
+    private int maxWait;
+
+    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
+    private int timeBetweenEvictionRunsMillis;
+
+    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
+    private int minEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
+    private int maxEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.druid.validationQuery}")
+    private String validationQuery;
+
+    @Value("${spring.datasource.druid.testWhileIdle}")
+    private boolean testWhileIdle;
+
+    @Value("${spring.datasource.druid.testOnBorrow}")
+    private boolean testOnBorrow;
+
+    @Value("${spring.datasource.druid.testOnReturn}")
+    private boolean testOnReturn;
+
+    public DruidDataSource dataSource(DruidDataSource datasource) {
+        /** 配置初始化大小、最小、最大 */
+        datasource.setInitialSize(initialSize);
+        datasource.setMaxActive(maxActive);
+        datasource.setMinIdle(minIdle);
+
+        /** 配置获取连接等待超时的时间 */
+        datasource.setMaxWait(maxWait);
+
+        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
+        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+
+        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
+        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
+        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
+
+        /**
+         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
+         */
+        datasource.setValidationQuery(validationQuery);
+        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
+        datasource.setTestWhileIdle(testWhileIdle);
+        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnBorrow(testOnBorrow);
+        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnReturn(testOnReturn);
+        return datasource;
+    }
+}

+ 21 - 21
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java → ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatSubmit.java

@@ -1,21 +1,21 @@
-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;
-
-/**
- * 自定义注解防止表单重复提交
- *
- * @author ruoyi
- */
-@Inherited
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface RepeatSubmit {
-
-}
+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;
+
+/**
+ * 自定义注解防止表单重复提交
+ *
+ * @author ruoyi
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit {
+
+}

+ 49 - 49
ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java → ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatSubmitInterceptor.java

@@ -1,49 +1,49 @@
-package com.ruoyi.framework.interceptor;
-
-import java.lang.reflect.Method;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.springframework.stereotype.Component;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
-import com.alibaba.fastjson.JSONObject;
-import com.ruoyi.common.annotation.RepeatSubmit;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.utils.ServletUtils;
-
-/**
- * 防止重复提交拦截器
- *
- * @author ruoyi
- */
-@Component
-public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
-    @Override
-    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-        if (handler instanceof HandlerMethod) {
-            HandlerMethod handlerMethod = (HandlerMethod) handler;
-            Method method = handlerMethod.getMethod();
-            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
-            if (annotation != null) {
-                if (this.isRepeatSubmit(request)) {
-                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
-                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
-                    return false;
-                }
-            }
-            return true;
-        } else {
-            return super.preHandle(request, response, handler);
-        }
-    }
-
-    /**
-     * 验证是否重复提交由子类实现具体的防重复提交的规则
-     *
-     * @param request
-     * @return
-     * @throws Exception
-     */
-    public abstract boolean isRepeatSubmit(HttpServletRequest request);
-}
+package com.ruoyi.framework.interceptor;
+
+import java.lang.reflect.Method;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.ServletUtils;
+
+/**
+ * 防止重复提交拦截器
+ *
+ * @author ruoyi
+ */
+@Component
+public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        if (handler instanceof HandlerMethod) {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            Method method = handlerMethod.getMethod();
+            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
+            if (annotation != null) {
+                if (this.isRepeatSubmit(request)) {
+                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
+                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return super.preHandle(request, response, handler);
+        }
+    }
+
+    /**
+     * 验证是否重复提交由子类实现具体的防重复提交的规则
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    public abstract boolean isRepeatSubmit(HttpServletRequest request);
+}

+ 114 - 114
ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java → ruoyi-common/src/main/java/com/ruoyi/common/filter/SameUrlDataInterceptor.java

@@ -1,114 +1,114 @@
-package com.ruoyi.framework.interceptor.impl;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import javax.servlet.http.HttpServletRequest;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-import com.alibaba.fastjson.JSONObject;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.common.core.redis.RedisCache;
-import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.common.utils.http.HttpHelper;
-import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
-
-/**
- * 判断请求url和数据是否和上一次相同,
- * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
- *
- * @author ruoyi
- */
-@Component
-public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
-    public final String REPEAT_PARAMS = "repeatParams";
-
-    public final String REPEAT_TIME = "repeatTime";
-
-    // 令牌自定义标识
-    @Value("${token.header}")
-    private String header;
-
-    @Autowired
-    private RedisCache redisCache;
-
-    /**
-     * 间隔时间,单位:秒 默认10秒
-     * <p>
-     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
-     */
-    private int intervalTime = 10;
-
-    public void setIntervalTime(int intervalTime) {
-        this.intervalTime = intervalTime;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public boolean isRepeatSubmit(HttpServletRequest request) {
-        String nowParams = "";
-        if (request instanceof RepeatedlyRequestWrapper) {
-            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
-            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
-        }
-
-        // body参数为空,获取Parameter的数据
-        if (StringUtils.isEmpty(nowParams)) {
-            nowParams = JSONObject.toJSONString(request.getParameterMap());
-        }
-        Map<String, Object> nowDataMap = new HashMap<String, Object>();
-        nowDataMap.put(REPEAT_PARAMS, nowParams);
-        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
-
-        // 请求地址(作为存放cache的key值)
-        String url = request.getRequestURI();
-
-        // 唯一值(没有消息头则使用请求地址)
-        String submitKey = request.getHeader(header);
-        if (StringUtils.isEmpty(submitKey)) {
-            submitKey = url;
-        }
-
-        // 唯一标识(指定key + 消息头)
-        String cache_repeat_key = Constants.REPEAT_SUBMIT_KEY + submitKey;
-
-        Object sessionObj = redisCache.getCacheObject(cache_repeat_key);
-        if (sessionObj != null) {
-            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
-            if (sessionMap.containsKey(url)) {
-                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
-                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) {
-                    return true;
-                }
-            }
-        }
-        Map<String, Object> cacheMap = new HashMap<String, Object>();
-        cacheMap.put(url, nowDataMap);
-        redisCache.setCacheObject(cache_repeat_key, cacheMap, intervalTime, TimeUnit.SECONDS);
-        return false;
-    }
-
-    /**
-     * 判断参数是否相同
-     */
-    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
-        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
-        String preParams = (String) preMap.get(REPEAT_PARAMS);
-        return nowParams.equals(preParams);
-    }
-
-    /**
-     * 判断两次间隔时间
-     */
-    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap) {
-        long time1 = (Long) nowMap.get(REPEAT_TIME);
-        long time2 = (Long) preMap.get(REPEAT_TIME);
-        if ((time1 - time2) < (this.intervalTime * 1000)) {
-            return true;
-        }
-        return false;
-    }
-}
+package com.ruoyi.framework.interceptor.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.http.HttpHelper;
+import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
+
+/**
+ * 判断请求url和数据是否和上一次相同,
+ * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
+ *
+ * @author ruoyi
+ */
+@Component
+public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
+    public final String REPEAT_PARAMS = "repeatParams";
+
+    public final String REPEAT_TIME = "repeatTime";
+
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 间隔时间,单位:秒 默认10秒
+     * <p>
+     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
+     */
+    private int intervalTime = 10;
+
+    public void setIntervalTime(int intervalTime) {
+        this.intervalTime = intervalTime;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request) {
+        String nowParams = "";
+        if (request instanceof RepeatedlyRequestWrapper) {
+            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
+            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
+        }
+
+        // body参数为空,获取Parameter的数据
+        if (StringUtils.isEmpty(nowParams)) {
+            nowParams = JSONObject.toJSONString(request.getParameterMap());
+        }
+        Map<String, Object> nowDataMap = new HashMap<String, Object>();
+        nowDataMap.put(REPEAT_PARAMS, nowParams);
+        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = request.getHeader(header);
+        if (StringUtils.isEmpty(submitKey)) {
+            submitKey = url;
+        }
+
+        // 唯一标识(指定key + 消息头)
+        String cache_repeat_key = Constants.REPEAT_SUBMIT_KEY + submitKey;
+
+        Object sessionObj = redisCache.getCacheObject(cache_repeat_key);
+        if (sessionObj != null) {
+            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
+            if (sessionMap.containsKey(url)) {
+                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
+                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) {
+                    return true;
+                }
+            }
+        }
+        Map<String, Object> cacheMap = new HashMap<String, Object>();
+        cacheMap.put(url, nowDataMap);
+        redisCache.setCacheObject(cache_repeat_key, cacheMap, intervalTime, TimeUnit.SECONDS);
+        return false;
+    }
+
+    /**
+     * 判断参数是否相同
+     */
+    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
+        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
+        String preParams = (String) preMap.get(REPEAT_PARAMS);
+        return nowParams.equals(preParams);
+    }
+
+    /**
+     * 判断两次间隔时间
+     */
+    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap) {
+        long time1 = (Long) nowMap.get(REPEAT_TIME);
+        long time2 = (Long) preMap.get(REPEAT_TIME);
+        if ((time1 - time2) < (this.intervalTime * 1000)) {
+            return true;
+        }
+        return false;
+    }
+}

+ 56 - 56
ruoyi-admin/src/main/resources/application-druid.yml → ruoyi-common/src/main/resources/application-druid.yml

@@ -1,57 +1,57 @@
-# 数据源配置
-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
-            webStatFilter: 
-                enabled: true
-            statViewServlet:
-                enabled: true
-                # 设置白名单,不填则允许所有访问
-                allow:
-                url-pattern: /druid/*
-                # 控制台管理用户名和密码
-                login-username: 
-                login-password: 
-            filter:
-                stat:
-                    enabled: true
-                    # 慢SQL记录
-                    log-slow-sql: true
-                    slow-sql-millis: 1000
-                    merge-sql: true
-                wall:
-                    config:
+# 数据源配置
+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
+            webStatFilter: 
+                enabled: true
+            statViewServlet:
+                enabled: true
+                # 设置白名单,不填则允许所有访问
+                allow:
+                url-pattern: /druid/*
+                # 控制台管理用户名和密码
+                login-username: 
+                login-password: 
+            filter:
+                stat:
+                    enabled: true
+                    # 慢SQL记录
+                    log-slow-sql: true
+                    slow-sql-millis: 1000
+                    merge-sql: true
+                wall:
+                    config:
                         multi-statement-allow: true

+ 44 - 44
ruoyi-admin/src/main/resources/application.yml → ruoyi-common/src/main/resources/application.yml

@@ -1,44 +1,44 @@
-# 项目相关配置
-ruoyi:
-  # 名称
-  name: RuoYi
-  # 版本
-  version: 3.3.0
-  # 版权年份
-  copyrightYear: 2020
-  # 实例演示开关
-  demoEnabled: true
-  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
-  profile: D:/ruoyi/uploadPath
-  # 获取ip地址开关
-  addressEnabled: false
-
-# 开发环境配置
-server:
-  # 服务器的HTTP端口,默认为8080
-  port: 8080
-  servlet:
-    # 应用的访问路径
-    context-path: /
-  tomcat:
-    # tomcat的URI编码
-    uri-encoding: UTF-8
-    # tomcat最大线程数,默认为200
-    max-threads: 800
-    # Tomcat启动初始化的线程数,默认值25
-    min-spare-threads: 30
-
-# 日志配置
-logging:
-  level:
-    com.ruoyi: debug
-    org.springframework: warn
-
-# 防止XSS攻击
-xss:
-  # 过滤开关
-  enabled: true
-  # 排除链接(多个用逗号分隔)
-  excludes: /system/notice/*
-  # 匹配链接
-  urlPatterns: /system/*,/monitor/*,/tool/*
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: RuoYi
+  # 版本
+  version: 3.3.0
+  # 版权年份
+  copyrightYear: 2020
+  # 实例演示开关
+  demoEnabled: true
+  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
+  profile: D:/ruoyi/uploadPath
+  # 获取ip地址开关
+  addressEnabled: false
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 8080
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # tomcat最大线程数,默认为200
+    max-threads: 800
+    # Tomcat启动初始化的线程数,默认值25
+    min-spare-threads: 30
+
+# 日志配置
+logging:
+  level:
+    com.ruoyi: debug
+    org.springframework: warn
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /system/notice/*
+  # 匹配链接
+  urlPatterns: /system/*,/monitor/*,/tool/*

+ 36 - 36
ruoyi-admin/src/main/resources/i18n/messages.properties → ruoyi-common/src/main/resources/i18n/messages.properties

@@ -1,36 +1,36 @@
-#错误消息
-not.null=* 必须填写
-user.jcaptcha.error=验证码错误
-user.jcaptcha.expire=验证码已失效
-user.not.exists=用户不存在/密码错误
-user.password.not.match=用户不存在/密码错误
-user.password.retry.limit.count=密码输入错误{0}次
-user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
-user.password.delete=对不起,您的账号已被删除
-user.blocked=用户已封禁,请联系管理员
-role.blocked=角色已封禁,请联系管理员
-user.logout.success=退出成功
-
-length.not.valid=长度必须在{min}到{max}个字符之间
-
-user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
-user.password.not.valid=* 5-50个字符
- 
-user.email.not.valid=邮箱格式错误
-user.mobile.phone.number.not.valid=手机号格式错误
-user.login.success=登录成功
-user.notfound=请重新登录
-user.forcelogout=管理员强制退出,请重新登录
-user.unknown.error=未知错误,请重新登录
-
-##文件上传消息
-upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
-upload.filename.exceed.length=上传的文件名最长{0}个字符
-
-##权限
-no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
-no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
-no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
-no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
-no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
-no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 92 - 92
ruoyi-admin/src/main/resources/logback.xml → ruoyi-common/src/main/resources/logback.xml

@@ -1,93 +1,93 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<configuration>
-    <!-- 日志存放路径 -->
-	<property name="log.path" value="/home/ruoyi/logs" />
-    <!-- 日志输出格式 -->
-	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
-
-	<!-- 控制台输出 -->
-	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
-		<encoder>
-			<pattern>${log.pattern}</pattern>
-		</encoder>
-	</appender>
-	
-	<!-- 系统日志输出 -->
-	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
-	    <file>${log.path}/sys-info.log</file>
-        <!-- 循环政策:基于时间创建日志文件 -->
-		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 日志文件名格式 -->
-			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
-			<!-- 日志最大的历史 60天 -->
-			<maxHistory>60</maxHistory>
-		</rollingPolicy>
-		<encoder>
-			<pattern>${log.pattern}</pattern>
-		</encoder>
-		<filter class="ch.qos.logback.classic.filter.LevelFilter">
-            <!-- 过滤的级别 -->
-            <level>INFO</level>
-            <!-- 匹配时的操作:接收(记录) -->
-            <onMatch>ACCEPT</onMatch>
-            <!-- 不匹配时的操作:拒绝(不记录) -->
-            <onMismatch>DENY</onMismatch>
-        </filter>
-	</appender>
-	
-	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
-	    <file>${log.path}/sys-error.log</file>
-        <!-- 循环政策:基于时间创建日志文件 -->
-        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 日志文件名格式 -->
-            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
-			<!-- 日志最大的历史 60天 -->
-			<maxHistory>60</maxHistory>
-        </rollingPolicy>
-        <encoder>
-            <pattern>${log.pattern}</pattern>
-        </encoder>
-        <filter class="ch.qos.logback.classic.filter.LevelFilter">
-            <!-- 过滤的级别 -->
-            <level>ERROR</level>
-			<!-- 匹配时的操作:接收(记录) -->
-            <onMatch>ACCEPT</onMatch>
-			<!-- 不匹配时的操作:拒绝(不记录) -->
-            <onMismatch>DENY</onMismatch>
-        </filter>
-    </appender>
-	
-	<!-- 用户访问日志输出  -->
-    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
-		<file>${log.path}/sys-user.log</file>
-        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 按天回滚 daily -->
-            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
-            <!-- 日志最大的历史 60天 -->
-            <maxHistory>60</maxHistory>
-        </rollingPolicy>
-        <encoder>
-            <pattern>${log.pattern}</pattern>
-        </encoder>
-    </appender>
-	
-	<!-- 系统模块日志级别控制  -->
-	<logger name="com.ruoyi" level="info" />
-	<!-- Spring日志级别控制  -->
-	<logger name="org.springframework" level="warn" />
-
-	<root level="info">
-		<appender-ref ref="console" />
-	</root>
-	
-	<!--系统操作日志-->
-    <root level="info">
-        <appender-ref ref="file_info" />
-        <appender-ref ref="file_error" />
-    </root>
-	
-	<!--系统用户操作日志-->
-    <logger name="sys-user" level="info">
-        <appender-ref ref="sys-user"/>
-    </logger>
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/ruoyi/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+	
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+	
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+	
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+	
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.ruoyi" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+	
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+	
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
 </configuration> 

+ 18 - 18
ruoyi-ui/src/api/monitor/online.js → ruoyi-ui/src/api/system/session.js

@@ -1,18 +1,18 @@
-import request from '@/utils/request'
-
-// 查询在线用户列表
-export function list(query) {
-  return request({
-    url: '/monitor/online/list',
-    method: 'get',
-    params: query
-  })
-}
-
-// 强退用户
-export function forceLogout(tokenId) {
-  return request({
-    url: '/monitor/online/' + tokenId,
-    method: 'delete'
-  })
-}
+import request from '@/utils/request'
+
+// 查询在线用户列表
+export function list(query) {
+  return request({
+    url: '/system/user-session/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 强退用户
+export function forceLogout(tokenId) {
+  return request({
+    url: '/system/user-session/delete?id=' + tokenId,
+    method: 'delete'
+  })
+}

+ 121 - 128
ruoyi-ui/src/views/monitor/online/index.vue → ruoyi-ui/src/views/system/session/index.vue

@@ -1,128 +1,121 @@
-<template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
-      <el-form-item label="登录地址" prop="ipaddr">
-        <el-input
-          v-model="queryParams.ipaddr"
-          placeholder="请输入登录地址"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="用户名称" prop="userName">
-        <el-input
-          v-model="queryParams.userName"
-          placeholder="请输入用户名称"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-      </el-form-item>
-
-    </el-form>
-    <el-table
-      v-loading="loading"
-      :data="list.slice((pageNum-1)*pageSize,pageNum*pageSize)"
-      style="width: 100%;"
-    >
-      <el-table-column label="序号" type="index" align="center">
-        <template slot-scope="scope">
-          <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
-      <el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
-      <el-table-column label="部门名称" align="center" prop="deptName" />
-      <el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
-      <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
-      <el-table-column label="浏览器" align="center" prop="browser" />
-      <el-table-column label="操作系统" align="center" prop="os" />
-      <el-table-column label="登录时间" align="center" prop="loginTime" width="180">
-        <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.loginTime) }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-delete"
-            @click="handleForceLogout(scope.row)"
-            v-hasPermi="['monitor:online:forceLogout']"
-          >强退</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" />
-  </div>
-</template>
-
-<script>
-import { list, forceLogout } from "@/api/monitor/online";
-
-export default {
-  name: "Online",
-  data() {
-    return {
-      // 遮罩层
-      loading: true,
-      // 总条数
-      total: 0,
-      // 表格数据
-      list: [],
-      pageNum: 1,
-      pageSize: 10,
-      // 查询参数
-      queryParams: {
-        ipaddr: undefined,
-        userName: undefined
-      }
-    };
-  },
-  created() {
-    this.getList();
-  },
-  methods: {
-    /** 查询登录日志列表 */
-    getList() {
-      this.loading = true;
-      list(this.queryParams).then(response => {
-        this.list = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.pageNum = 1;
-      this.getList();
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm("queryForm");
-      this.handleQuery();
-    },
-    /** 强退按钮操作 */
-    handleForceLogout(row) {
-      this.$confirm('是否确认强退名称为"' + row.userName + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return forceLogout(row.tokenId);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("强退成功");
-        })
-    }
-  }
-};
-</script>
-
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
+      <el-form-item label="登录地址" prop="userIp">
+        <el-input
+          v-model="queryParams.userIp"
+          placeholder="请输入登录地址"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户名称" prop="username">
+        <el-input
+          v-model="queryParams.username"
+          placeholder="请输入用户名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+
+    </el-form>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      style="width: 100%;"
+    >
+      <el-table-column label="会话编号" align="center" prop="id" width="300" />
+      <el-table-column label="登录名称" align="center" prop="username" width="100" />
+      <el-table-column label="部门名称" align="center" prop="deptName" width="100" />
+      <el-table-column label="登陆地址" align="center" prop="userIp" width="100" />
+      <el-table-column label="userAgent" align="center" prop="userAgent" :show-overflow-tooltip="true" />
+      <el-table-column label="登录时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleForceLogout(scope.row)"
+            v-hasPermi="['system:user-session:delete']"
+          >强退</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="pageNo" :limit.sync="pageSize" />
+  </div>
+</template>
+
+<script>
+import { list, forceLogout } from "@/api/system/session";
+
+export default {
+  name: "Online",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 总条数
+      total: 0,
+      // 表格数据
+      list: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        userIp: undefined,
+        username: undefined
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询登录日志列表 */
+    getList() {
+      this.loading = true;
+      list(this.queryParams).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 强退按钮操作 */
+    handleForceLogout(row) {
+      this.$confirm('是否确认强退名称为"' + row.username + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return forceLogout(row.id);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("强退成功");
+        })
+    }
+  }
+};
+</script>
+

+ 34 - 4
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysUserSessionController.java

@@ -4,8 +4,14 @@ import cn.iocoder.dashboard.common.pojo.CommonResult;
 import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageItemRespVO;
 import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
+import cn.iocoder.dashboard.modules.system.convert.auth.SysUserSessionConvert;
 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
 import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
+import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
+import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
+import cn.iocoder.dashboard.util.collection.MapUtils;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
@@ -14,26 +20,50 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
+import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertList;
 
 @Api("用户 Session API")
 @RestController
-@RequestMapping("/user-session")
+@RequestMapping("/system/user-session")
 public class SysUserSessionController {
 
     @Resource
     private SysUserSessionService userSessionService;
+    @Resource
+    private SysUserService userService;
+    @Resource
+    private SysDeptService deptService;
 
     @ApiOperation("获得 Session 分页列表")
     @PreAuthorize("@ss.hasPermission('system:user-session:page')")
     @GetMapping("/page")
     public CommonResult<PageResult<SysUserSessionPageItemRespVO>> getUserSessionPage(@Validated SysUserSessionPageReqVO reqVO) {
         // 获得 Session 分页
-        PageResult<SysUserSessionDO> sessionPage = userSessionService.getUserSessionPage(reqVO);
+        PageResult<SysUserSessionDO> pageResult = userSessionService.getUserSessionPage(reqVO);
 
-        //
-        return null;
+        // 获得拼接需要的数据
+        Map<Long, SysUserDO> userMap = userService.getUserMap(
+                convertList(pageResult.getList(), SysUserSessionDO::getUserId));
+        Map<Long, SysDeptDO> deptMap = deptService.getDeptMap(
+                convertList(userMap.values(), SysUserDO::getDeptId));
+        // 拼接结果返回
+        List<SysUserSessionPageItemRespVO> sessionList = new ArrayList<>(pageResult.getList().size());
+        pageResult.getList().forEach(session -> {
+            SysUserSessionPageItemRespVO respVO = SysUserSessionConvert.INSTANCE.convert(session);
+            sessionList.add(respVO);
+            // 设置用户账号
+            MapUtils.findAndThen(userMap, session.getUserId(), user -> {
+                respVO.setUsername(user.getUsername());
+                // 设置用户部门
+                MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> respVO.setDeptName(dept.getName()));
+            });
+        });
+        return success(new PageResult<>(sessionList, pageResult.getTotal()));
     }
 
     @ApiOperation("删除 Session")

+ 3 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageItemRespVO.java

@@ -8,6 +8,8 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 
+import java.util.Date;
+
 @ApiModel(value = "用户在线 Session Response VO", description = "相比用户基本信息来说,会多部门、用户账号等信息")
 @Data
 @NoArgsConstructor
@@ -25,7 +27,7 @@ public class SysUserSessionPageItemRespVO extends PageParam {
     private String userAgent;
 
     @ApiModelProperty(value = "登陆时间", required = true)
-    private String createTime;
+    private Date createTime;
 
     @ApiModelProperty(value = "用户账号", required = true, example = "yudao")
     private String username;

+ 0 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageReqVO.java

@@ -14,7 +14,6 @@ import javax.validation.constraints.NotEmpty;
 public class SysUserSessionPageReqVO extends PageParam {
 
     @ApiModelProperty(value = "用户 IP", example = "127.0.0.1", notes = "模糊匹配")
-    @NotEmpty(message = "用户 IP 不能为空")
     private String userIp;
 
     @ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配")

+ 16 - 0
src/main/java/cn/iocoder/dashboard/modules/system/convert/auth/SysUserSessionConvert.java

@@ -0,0 +1,16 @@
+package cn.iocoder.dashboard.modules.system.convert.auth;
+
+import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageItemRespVO;
+import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageItemRespVO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SysUserSessionConvert {
+
+    SysUserSessionConvert INSTANCE = Mappers.getMapper(SysUserSessionConvert.class);
+
+    SysUserSessionPageItemRespVO convert(SysUserSessionDO session);
+
+}

+ 14 - 2
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserSessionMapper.java

@@ -1,9 +1,21 @@
 package cn.iocoder.dashboard.modules.system.dal.mysql.dao.auth;
 
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
+
 @Mapper
-public interface SysUserSessionMapper extends BaseMapper<SysUserSessionDO> {
+public interface SysUserSessionMapper extends BaseMapperX<SysUserSessionDO> {
+
+    default PageResult<SysUserSessionDO> selectPage(SysUserSessionPageReqVO reqVO, Collection<Long> userIds) {
+        return selectPage(reqVO, new QueryWrapperX<SysUserSessionDO>()
+                .inIfPresent("user_id", userIds)
+                .likeIfPresent("user_ip", reqVO.getUserIp()));
+    }
+
 }

+ 4 - 0
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/user/SysUserMapper.java

@@ -48,5 +48,9 @@ public interface SysUserMapper extends BaseMapperX<SysUserDO> {
         return selectList(new QueryWrapperX<SysUserDO>().like("nickname", nickname));
     }
 
+    default List<SysUserDO> selectListByUsername(String username) {
+        return selectList(new QueryWrapperX<SysUserDO>().like("username", username));
+    }
+
 }
 

+ 2 - 1
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/auth/SysUserSessionDO.java

@@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth;
 import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.dashboard.framework.security.core.LoginUser;
 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Builder;
@@ -27,7 +28,7 @@ public class SysUserSessionDO extends BaseDO {
     /**
      * 会话编号, 即 sessionId
      */
-    @TableId
+    @TableId(type = IdType.INPUT)
     private String id;
     /**
      * 用户编号

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/auth/SysLoginUserRedisDAO.java

@@ -30,8 +30,8 @@ public class SysLoginUserRedisDAO {
         stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), LOGIN_USER.getTimeout());
     }
 
-    public void delete(String accessToken) {
-        String redisKey = formatKey(accessToken);
+    public void delete(String sessionId) {
+        String redisKey = formatKey(sessionId);
         stringRedisTemplate.delete(redisKey);
     }
 

+ 1 - 0
src/main/java/cn/iocoder/dashboard/modules/system/job/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.dashboard.modules.system.job;

+ 25 - 5
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java

@@ -1,19 +1,26 @@
 package cn.iocoder.dashboard.modules.system.service.auth.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
 import cn.iocoder.dashboard.framework.security.core.LoginUser;
 import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.auth.SysUserSessionMapper;
 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
 import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO;
 import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
+import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.util.Collection;
 import java.util.Date;
 
+import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertSet;
+
 /**
  * 在线用户 Session Service 实现类
  *
@@ -27,10 +34,12 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
 
     @Resource
     private SysLoginUserRedisDAO loginUserRedisDAO;
-
     @Resource
     private SysUserSessionMapper userSessionMapper;
 
+    @Resource
+    private SysUserService userService;
+
     @Override
     public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
         // 生成 Session 编号
@@ -39,8 +48,8 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
         loginUser.setUpdateTime(new Date());
         loginUserRedisDAO.set(sessionId, loginUser);
         // 写入 DB 中
-        SysUserSessionDO userSession = SysUserSessionDO.builder().userId(loginUser.getId())
-                .userIp(userIp).userAgent(userAgent).build();
+        SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId)
+                .userId(loginUser.getId()).userIp(userIp).userAgent(userAgent).build();
         userSessionMapper.insert(userSession);
         // 返回 Session 编号
         return sessionId;
@@ -59,7 +68,10 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
 
     @Override
     public void deleteUserSession(String sessionId) {
-
+        // 删除 Redis 缓存
+        loginUserRedisDAO.delete(sessionId);
+        // 删除 DB 记录
+        userSessionMapper.deleteById(sessionId);
     }
 
     @Override
@@ -74,7 +86,15 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
 
     @Override
     public PageResult<SysUserSessionDO> getUserSessionPage(SysUserSessionPageReqVO reqVO) {
-        return null;
+        // 处理基于用户昵称的查询
+        Collection<Long> userIds = null;
+        if (StrUtil.isNotEmpty(reqVO.getUsername())) {
+            userIds = convertSet(userService.listUsersByUsername(reqVO.getUsername()), SysUserDO::getId);
+            if (CollUtil.isEmpty(userIds)) {
+                return PageResult.empty();
+            }
+        }
+        return userSessionMapper.selectPage(reqVO, userIds);
     }
 
     /**

+ 8 - 0
src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserService.java

@@ -79,6 +79,14 @@ public interface SysUserService {
      */
     List<SysUserDO> listUsersByNickname(String nickname);
 
+    /**
+     * 获得用户列表,基于用户账号模糊匹配
+     *
+     * @param username 用户账号
+     * @return 用户列表
+     */
+    List<SysUserDO> listUsersByUsername(String username);
+
     /**
      * 创建用户
      *

+ 5 - 0
src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java

@@ -92,6 +92,11 @@ public class SysUserServiceImpl implements SysUserService {
         return userMapper.selectListByNickname(nickname);
     }
 
+    @Override
+    public List<SysUserDO> listUsersByUsername(String username) {
+        return userMapper.selectListByUsername(username);
+    }
+
     /**
      * 获得部门条件:查询指定部门的子部门编号们,包括自身
      *

+ 7 - 7
src/main/java/cn/iocoder/dashboard/util/collection/CollectionUtils.java

@@ -26,35 +26,35 @@ public class CollectionUtils {
         return from.stream().filter(predicate).collect(Collectors.toList());
     }
 
-    public static <T, U> List<U> convertList(List<T> from, Function<T, U> func) {
+    public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
             return new ArrayList<>();
         }
         return from.stream().map(func).collect(Collectors.toList());
     }
 
-    public static <T, U> Set<U> convertSet(List<T> from, Function<T, U> func) {
+    public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
             return new HashSet<>();
         }
         return from.stream().map(func).collect(Collectors.toSet());
     }
 
-    public static <T, K> Map<K, T> convertMap(List<T> from, Function<T, K> keyFunc) {
+    public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
         if (CollUtil.isEmpty(from)) {
             return new HashMap<>();
         }
         return from.stream().collect(Collectors.toMap(keyFunc, item -> item));
     }
 
-    public static <T, K, V> Map<K, V> convertMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
+    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
         if (CollUtil.isEmpty(from)) {
             return new HashMap<>();
         }
         return from.stream().collect(Collectors.toMap(keyFunc, valueFunc));
     }
 
-    public static <T, K> Map<K, List<T>> convertMultiMap(List<T> from, Function<T, K> keyFunc) {
+    public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
         if (CollUtil.isEmpty(from)) {
             return new HashMap<>();
         }
@@ -62,7 +62,7 @@ public class CollectionUtils {
                 Collectors.mapping(t -> t, Collectors.toList())));
     }
 
-    public static <T, K, V> Map<K, List<V>> convertMultiMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
+    public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
         if (CollUtil.isEmpty(from)) {
             return new HashMap<>();
         }
@@ -71,7 +71,7 @@ public class CollectionUtils {
     }
 
     // 暂时没想好名字,先以 2 结尾噶
-    public static <T, K, V> Map<K, Set<V>> convertMultiMap2(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
+    public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
         if (CollUtil.isEmpty(from)) {
             return new HashMap<>();
         }