From 64685e74f9200e1a65e8f382c748851ffa249232 Mon Sep 17 00:00:00 2001
From: dataprince <dataprince@163.com>
Date: Mon, 25 Dec 2023 16:16:02 +0800
Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5ruoyi-vue-plus=E7=9A=842023-1?=
 =?UTF-8?q?1-17=E8=87=B32023-11-23=E7=9A=84=E6=9B=B4=E6=96=B0=20=20=20=20?=
 =?UTF-8?q?=EF=BC=881=EF=BC=89fix=20=E4=BF=AE=E5=A4=8D=20OssFactory=20?=
 =?UTF-8?q?=E5=B9=B6=E5=8F=91=E5=A4=9A=E5=88=9B=E5=BB=BA=E5=AE=9E=E4=BE=8B?=
 =?UTF-8?q?=E9=97=AE=E9=A2=98=20=20=20=20=EF=BC=882=EF=BC=89update=20?=
 =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?=
 =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0=20=20=20=20=EF=BC=883?=
 =?UTF-8?q?=EF=BC=89!454=20=E6=B7=BB=E5=8A=A0excel=E5=A4=9Asheet=E9=A1=B5?=
 =?UTF-8?q?=E5=AF=BC=E5=87=BA=20=20=20=20=EF=BC=884=EF=BC=89update=20?=
 =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E9=87=8D=E6=9E=84=20LoginHelper=20?=
 =?UTF-8?q?=E5=B0=86=E6=9C=AC=E5=9C=B0=E5=AD=98=E5=82=A8=E4=BB=A3=E7=A0=81?=
 =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=B0=81=E8=A3=85=20=20=20=20=EF=BC=885?=
 =?UTF-8?q?=EF=BC=89update=20=E4=BC=98=E5=8C=96=20=E5=88=A0=E9=99=A4?=
 =?UTF-8?q?=E6=97=A0=E7=94=A8=E4=BE=9D=E8=B5=96=20=20=20=20=EF=BC=886?=
 =?UTF-8?q?=EF=BC=89update=20=E4=BC=98=E5=8C=96=20=E5=88=A0=E9=99=A4?=
 =?UTF-8?q?=E6=97=A0=E7=94=A8=E6=B3=A8=E8=A7=A3=20=20=20=20=EF=BC=887?=
 =?UTF-8?q?=EF=BC=89update=20=E4=BC=98=E5=8C=96=20=E6=9B=B4=E6=96=B0?=
 =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=BC=82=E5=B8=B8=E6=8F=90=E7=A4=BA=20?=
 =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E5=BD=95=E8=B4=A6=E5=8F=B7=20=20=20?=
 =?UTF-8?q?=20=EF=BC=888=EF=BC=89fix=20=E4=BF=AE=E5=A4=8D=20token=20?=
 =?UTF-8?q?=E5=A4=B1=E6=95=88=E5=90=8E=20=E7=99=BB=E5=BD=95=E8=8E=B7?=
 =?UTF-8?q?=E5=8F=96=E7=94=A8=E6=88=B7null=E9=97=AE=E9=A2=98=20=20=20=20?=
 =?UTF-8?q?=EF=BC=889=EF=BC=89fix=20=E4=BF=AE=E5=A4=8D=20session=20?=
 =?UTF-8?q?=E5=A4=9A=E8=B4=A6=E5=8F=B7=E5=85=B1=E7=94=A8=E8=A6=86=E7=9B=96?=
 =?UTF-8?q?=E9=97=AE=E9=A2=98=20=E6=94=B9=E4=B8=BA=20tokenSession=20?=
 =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E5=AD=98=E5=82=A8=20=20=20=20=EF=BC=8810?=
 =?UTF-8?q?=EF=BC=89add=20=E6=96=B0=E5=A2=9E=20RedisUtils.setObjectIfExist?=
 =?UTF-8?q?s=20=E5=A6=82=E6=9E=9C=E5=AD=98=E5=9C=A8=E5=88=99=E8=AE=BE?=
 =?UTF-8?q?=E7=BD=AE=E6=96=B9=E6=B3=95=20=20=20=20=EF=BC=8811=EF=BC=89upda?=
 =?UTF-8?q?te=20transmittable-thread-local=202.14.2=20=3D>=202.14.4=20=20?=
 =?UTF-8?q?=20=20=EF=BC=8812=EF=BC=89update=20=E4=BC=98=E5=8C=96=20?=
 =?UTF-8?q?=E5=88=A0=E9=99=A4=20hikaricp=20=E5=AE=98=E6=96=B9=E4=B8=8D?=
 =?UTF-8?q?=E6=8E=A8=E8=8D=90=E4=BD=BF=E7=94=A8=E7=9A=84=E9=85=8D=E7=BD=AE?=
 =?UTF-8?q?=20jdbc4=20=E5=8D=8F=E8=AE=AE=E8=87=AA=E5=B8=A6=E6=A0=A1?=
 =?UTF-8?q?=E9=AA=8C=E6=96=B9=E6=B3=95=20=20=20=20=EF=BC=8813=EF=BC=89upda?=
 =?UTF-8?q?te=20=E4=BC=98=E5=8C=96=20=E5=BC=80=E5=90=AF=20redisson=20?=
 =?UTF-8?q?=E8=84=9A=E6=9C=AC=E7=BC=93=E5=AD=98=20=E5=87=8F=E5=B0=91?=
 =?UTF-8?q?=E7=BD=91=E7=BB=9C=E4=BC=A0=E8=BE=93=20=20=20=20=EF=BC=8814?=
 =?UTF-8?q?=EF=BC=89=E5=8D=87=E7=BA=A7=E4=BE=9D=E8=B5=96update=20easyexcel?=
 =?UTF-8?q?=203.3.2=20=3D>=203.3.3=20=20=20=20=20=20update=20springboot-ad?=
 =?UTF-8?q?min=203.1.7=20=3D>=203.1.8=20=20=20=20=20=20update=20aws-java-s?=
 =?UTF-8?q?dk-s3=201.12.540=20=3D>=201.12.600=20=20=20=20=EF=BC=8815?=
 =?UTF-8?q?=EF=BC=89=E7=BB=86=E5=8C=96oss=E9=85=8D=E7=BD=AE=E7=AE=A1?=
 =?UTF-8?q?=E7=90=86=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=EF=BC=8C=E9=9C=80?=
 =?UTF-8?q?=E8=A6=81=E6=89=A7=E8=A1=8C=E6=9B=B4=E6=96=B0=E6=95=B0=E6=8D=AE?=
 =?UTF-8?q?=E5=BA=93=E8=84=9A=E6=9C=ACupdate.sql=20=20=20=20=EF=BC=8816?=
 =?UTF-8?q?=EF=BC=89update=20=E4=BC=98=E5=8C=96=20=E5=B0=86=20OSS=E9=85=8D?=
 =?UTF-8?q?=E7=BD=AE=20=E6=94=B9=E4=B8=BA=E5=85=A8=E5=B1=80=E6=A8=A1?=
 =?UTF-8?q?=E5=BC=8F=20=20=20=20=EF=BC=8817=EF=BC=89fix=20=E4=BF=AE?=
 =?UTF-8?q?=E5=A4=8D=20excel=E5=90=88=E5=B9=B6=E6=B3=A8=E8=A7=A3=E4=BC=9A?=
 =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E7=AC=AC=E4=B8=80=E5=90=88=E5=B9=B6=E5=88=97?=
 =?UTF-8?q?=E7=9A=84=E7=BB=93=E6=9E=9C=E6=9D=A5=E5=86=B3=E5=AE=9A=E5=90=8E?=
 =?UTF-8?q?=E7=BB=AD=E7=9A=84=E5=88=97=E5=90=88=E5=B9=B6=20=20=20=20?=
 =?UTF-8?q?=EF=BC=8818=EF=BC=89fix=20=E4=BF=AE=E5=A4=8D=20MybatisSystemExc?=
 =?UTF-8?q?eption=20=E7=A9=BA=E6=8C=87=E9=92=88=E9=97=AE=E9=A2=98=20=20=20?=
 =?UTF-8?q?=20=EF=BC=8819=EF=BC=89update=20=E4=BC=98=E5=8C=96=20=E5=88=A0?=
 =?UTF-8?q?=E9=99=A4=E6=97=A0=E7=94=A8=E5=BC=82=E5=B8=B8=E7=B1=BB=20=20=20?=
 =?UTF-8?q?=20=EF=BC=8820=EF=BC=89update=20=E4=BC=98=E5=8C=96=20=E9=AA=8C?=
 =?UTF-8?q?=E8=AF=81=E7=A0=81=E6=8E=A5=E5=8F=A3=20=E5=A2=9E=E5=8A=A0?=
 =?UTF-8?q?=E9=99=90=E6=B5=81=E9=85=8D=E7=BD=AE=20=20=20=20=EF=BC=8821?=
 =?UTF-8?q?=EF=BC=89update=20=E4=BC=98=E5=8C=96=20=E4=B8=B0=E5=AF=8CRedisU?=
 =?UTF-8?q?tils=E5=AF=B9List=20Set=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=93=8D?=
 =?UTF-8?q?=E4=BD=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pom.xml                                       |  8 +-
 .../ruoyi/web/controller/AuthController.java  |  2 +-
 .../web/controller/CaptchaController.java     |  3 +
 .../ruoyi/web/service/SysLoginService.java    |  3 +
 .../src/main/resources/application-dev.yml    |  8 +-
 .../src/main/resources/application-prod.yml   |  4 +-
 ruoyi-common/ruoyi-common-core/pom.xml        |  6 --
 .../common/core/constant/CacheNames.java      |  2 +-
 .../core/exception/DemoModeException.java     | 15 ---
 .../core/exception/GlobalException.java       | 60 ------------
 .../common/core/exception/UtilException.java  | 29 ------
 .../user/UserPasswordNotMatchException.java   | 19 ----
 ...UserPasswordRetryLimitExceedException.java | 19 ----
 .../common/core/utils/poi/ExcelUtil.java      |  3 +-
 .../ruoyi/common/core/utils/sql/SqlUtil.java  |  9 +-
 .../ruoyi/common/core/utils/uuid/UUID.java    | 25 +++--
 .../common/excel/core/CellMergeStrategy.java  | 27 +++++-
 .../ruoyi/common/excel/utils/ExcelUtil.java   | 57 ++++++++++++
 .../ruoyi/common/log/event/OperLogEvent.java  |  2 +-
 .../orm/handler/MybatisExceptionHandler.java  |  2 +-
 ruoyi-common/ruoyi-common-oss/pom.xml         |  5 +
 .../common/oss/constant/OssConstant.java      |  4 +-
 .../ruoyi/common/oss/factory/OssFactory.java  |  2 +-
 .../common/redis/config/RedisConfig.java      |  2 +
 .../ruoyi/common/redis/utils/RedisUtils.java  | 49 ++++++++++
 .../common/security/config/SaTokenConfig.java |  2 +-
 .../security/core/dao/FlexSaTokenDao.java     | 36 +-------
 .../handler/GlobalExceptionHandler.java       |  9 --
 .../common/security/utils/LoginHelper.java    | 92 +++++++++++++------
 .../tenant/handle/PlusTenantLineHandler.java  |  4 +-
 .../common/tenant/helper/TenantHelper.java    |  2 +-
 .../system/SysOssConfigController.java        | 12 +--
 .../system/SysProfileController.java          |  5 +-
 script/sql/mysql/update.sql                   | 13 +++
 script/sql/postgresql/update.sql              | 12 +++
 35 files changed, 282 insertions(+), 270 deletions(-)
 delete mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/DemoModeException.java
 delete mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/GlobalException.java
 delete mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/UtilException.java
 delete mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordNotMatchException.java
 delete mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordRetryLimitExceedException.java

diff --git a/pom.xml b/pom.xml
index f5958d7..a2dbeda 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,7 +30,7 @@
         <oshi.version>6.4.8</oshi.version>
         <commons.collections.version>3.2.2</commons.collections.version>
         <poi.version>5.2.3</poi.version>
-        <easyexcel.version>3.3.2</easyexcel.version>
+        <easyexcel.version>3.3.3</easyexcel.version>
         <velocity.version>2.3</velocity.version>
         <jwt.version>0.9.1</jwt.version>
         <servlet-api.version>6.0.0</servlet-api.version>
@@ -46,13 +46,13 @@
         <hutool.version>5.8.22</hutool.version>
         <redisson.version>3.25.1</redisson.version>
         <lock4j.version>2.2.4</lock4j.version>
-        <alibaba-ttl.version>2.14.3</alibaba-ttl.version>
-        <spring-boot-admin.version>3.1.7</spring-boot-admin.version>
+        <alibaba-ttl.version>2.14.4</alibaba-ttl.version>
+        <spring-boot-admin.version>3.1.8</spring-boot-admin.version>
         <powerjob.version>4.3.6</powerjob.version>
         <!-- 离线IP地址定位库 -->
         <ip2region.version>2.7.0</ip2region.version>
         <!-- OSS 配置 -->
-        <aws-java-sdk-s3.version>1.12.540</aws-java-sdk-s3.version>
+        <aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version>
 
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/AuthController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/AuthController.java
index 6103548..30b6e19 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/AuthController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/AuthController.java
@@ -62,7 +62,7 @@ public class AuthController {
      * @return 结果
      */
     @PostMapping("/login")
-    public R<LoginVo> login(@Validated @RequestBody LoginBody loginBody) {
+    public R<LoginVo> login(@RequestBody LoginBody loginBody) {
 
         AjaxResult ajax = AjaxResult.success();
         // 授权类型和客户端id
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java
index 9cbfe3e..91fb8b1 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java
@@ -6,8 +6,10 @@ import java.time.Duration;
 import cn.hutool.captcha.AbstractCaptcha;
 import cn.hutool.captcha.generator.CodeGenerator;
 import cn.hutool.core.util.IdUtil;
+import com.ruoyi.common.core.annotation.RateLimiter;
 import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.core.core.domain.AjaxResult;
+import com.ruoyi.common.core.enums.LimitType;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.core.utils.reflect.ReflectUtils;
 import com.ruoyi.common.core.utils.SpringUtils;
@@ -43,6 +45,7 @@ public class CaptchaController
     /**
      * 生成验证码
      */
+    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
     @GetMapping("/captchaImage")
     public AjaxResult getCode() {
         CaptchaVo captchaVo = new CaptchaVo();
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java
index e704039..de81f4b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java
@@ -161,6 +161,9 @@ public class SysLoginService {
     public void logout() {
         try {
             LoginUser loginUser = LoginHelper.getLoginUser();
+            if (ObjectUtil.isNull(loginUser)) {
+                return;
+            }
             if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
                 // 超级管理员 登出清除动态租户
                 TenantHelper.clearDynamic();
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 98296a0..a71140e 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -15,8 +15,6 @@ spring:
       idleTimeout: 600000
       # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
       maxLifetime: 1800000
-      # 连接测试query(配置检测连接是否有效)
-      connectionTestQuery: SELECT 1
       # 多久检查一次连接的活性
       keepaliveTime: 30000
 mybatis-flex:
@@ -93,7 +91,7 @@ redisson:
 --- # 监控中心客户端配置
 spring.boot.admin.client:
   # 增加客户端开关
-  enabled: false
+  enabled: true
   url: http://localhost:9090/admin
   instance:
     service-host-type: IP
@@ -104,7 +102,7 @@ spring.boot.admin.client:
 powerjob:
   worker:
     # 如何开启调度中心请查看文档教程
-    enabled: false
+    enabled: true
     # 需要先在 powerjob 登录页执行应用注册后才能使用
     app-name: ruoyi-worker
     # 28080 端口 随着主应用端口飘逸 避免集群冲突
@@ -112,6 +110,6 @@ powerjob:
     protocol: http
     server-address: 127.0.0.1:7700
     store-strategy: disk
-    enable-test-mode: false
+    allow-lazy-connect-server: false
     max-appended-wf-context-length: 4096
     max-result-length: 4096
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index f7f353e..b3afd6d 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -15,8 +15,6 @@ spring:
       idleTimeout: 600000
       # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
       maxLifetime: 1800000
-      # 连接测试query(配置检测连接是否有效)
-      connectionTestQuery: SELECT 1
       # 多久检查一次连接的活性
       keepaliveTime: 30000
 mybatis-flex:
@@ -112,6 +110,6 @@ powerjob:
     protocol: http
     server-address: 127.0.0.1:7700
     store-strategy: disk
-    enable-test-mode: false
+    allow-lazy-connect-server: false
     max-appended-wf-context-length: 4096
     max-result-length: 4096
diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml
index 02aa480..1fb19ba 100644
--- a/ruoyi-common/ruoyi-common-core/pom.xml
+++ b/ruoyi-common/ruoyi-common-core/pom.xml
@@ -125,12 +125,6 @@
             <artifactId>hutool-extra</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>cn.hutool</groupId>
-            <artifactId>hutool-json</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
         <!-- mapstruct-plus -->
         <dependency>
             <groupId>io.github.linpeilie</groupId>
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheNames.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheNames.java
index 81e92f5..f1c4f85 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheNames.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheNames.java
@@ -53,7 +53,7 @@ public interface CacheNames {
     /**
      * OSS配置
      */
-    String SYS_OSS_CONFIG = "sys_oss_config";
+    String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
 
     /**
      * 在线用户
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/DemoModeException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/DemoModeException.java
deleted file mode 100644
index 6197e98..0000000
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/DemoModeException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.ruoyi.common.core.exception;
-
-/**
- * 演示模式异常
- * 
- * @author ruoyi
- */
-public class DemoModeException extends RuntimeException
-{
-    private static final long serialVersionUID = 1L;
-
-    public DemoModeException()
-    {
-    }
-}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/GlobalException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/GlobalException.java
deleted file mode 100644
index cd59447..0000000
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/GlobalException.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.ruoyi.common.core.exception;
-
-import java.io.Serial;
-
-/**
- * 全局异常
- * 
- * @author ruoyi
- */
-public class GlobalException extends RuntimeException
-{
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 错误提示
-     */
-    private String message;
-
-    /**
-     * 错误明细,内部调试错误
-     *
-     */
-    private String detailMessage;
-
-    /**
-     * 空构造方法,避免反序列化问题
-     */
-    public GlobalException()
-    {
-    }
-
-    public GlobalException(String message)
-    {
-        this.message = message;
-    }
-
-    public String getDetailMessage()
-    {
-        return detailMessage;
-    }
-
-    public GlobalException setDetailMessage(String detailMessage)
-    {
-        this.detailMessage = detailMessage;
-        return this;
-    }
-
-    @Override
-    public String getMessage()
-    {
-        return message;
-    }
-
-    public GlobalException setMessage(String message)
-    {
-        this.message = message;
-        return this;
-    }
-}
\ No newline at end of file
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/UtilException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/UtilException.java
deleted file mode 100644
index 2674028..0000000
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/UtilException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.ruoyi.common.core.exception;
-
-import java.io.Serial;
-
-/**
- * 工具类异常
- * 
- * @author ruoyi
- */
-public class UtilException extends RuntimeException
-{
-    @Serial
-    private static final long serialVersionUID = 8247610319171014183L;
-
-    public UtilException(Throwable e)
-    {
-        super(e.getMessage(), e);
-    }
-
-    public UtilException(String message)
-    {
-        super(message);
-    }
-
-    public UtilException(String message, Throwable throwable)
-    {
-        super(message, throwable);
-    }
-}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordNotMatchException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordNotMatchException.java
deleted file mode 100644
index fa594b9..0000000
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordNotMatchException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.ruoyi.common.core.exception.user;
-
-import java.io.Serial;
-
-/**
- * 用户密码不正确或不符合规范异常类
- *
- * @author ruoyi
- */
-public class UserPasswordNotMatchException extends UserException
-{
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    public UserPasswordNotMatchException()
-    {
-        super("user.password.not.match", (Object)null);
-    }
-}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordRetryLimitExceedException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordRetryLimitExceedException.java
deleted file mode 100644
index 4f0c98d..0000000
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserPasswordRetryLimitExceedException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.ruoyi.common.core.exception.user;
-
-import java.io.Serial;
-
-/**
- * 用户错误最大次数异常类
- * 
- * @author ruoyi
- */
-public class UserPasswordRetryLimitExceedException extends UserException
-{
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime)
-    {
-        super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime });
-    }
-}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
index 2055164..4dd03bf 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
@@ -76,7 +76,6 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import com.ruoyi.common.core.config.RuoYiConfig;
-import com.ruoyi.common.core.exception.UtilException;
 import com.ruoyi.common.core.utils.DateUtils;
 import com.ruoyi.common.core.utils.file.FileTypeUtils;
 import com.ruoyi.common.core.utils.file.FileUtils;
@@ -540,7 +539,7 @@ public class ExcelUtil<T> {
             return AjaxResult.success(filename);
         } catch (Exception e) {
             log.error("导出Excel异常{}", e.getMessage());
-            throw new UtilException("导出Excel失败,请联系网站管理员!");
+            throw new IllegalArgumentException("导出Excel失败,请联系网站管理员!");
         } finally {
             IOUtils.closeQuietly(wb);
             IOUtils.closeQuietly(out);
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/sql/SqlUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/sql/SqlUtil.java
index 78a64ad..992839e 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/sql/SqlUtil.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/sql/SqlUtil.java
@@ -1,11 +1,10 @@
 package com.ruoyi.common.core.utils.sql;
 
 import com.ruoyi.common.core.utils.StringUtils;
-import com.ruoyi.common.core.exception.UtilException;
 
 /**
  * sql操作工具类
- * 
+ *
  * @author ruoyi
  */
 public class SqlUtil
@@ -32,11 +31,11 @@ public class SqlUtil
     {
         if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
         {
-            throw new UtilException("参数不符合规范,不能进行查询");
+            throw new IllegalArgumentException("参数不符合规范,不能进行查询");
         }
         if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH)
         {
-            throw new UtilException("参数已超过最大限制,不能进行查询");
+            throw new IllegalArgumentException("参数已超过最大限制,不能进行查询");
         }
         return value;
     }
@@ -63,7 +62,7 @@ public class SqlUtil
         {
             if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1)
             {
-                throw new UtilException("参数存在SQL注入风险");
+                throw new IllegalArgumentException("参数存在SQL注入风险");
             }
         }
     }
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/uuid/UUID.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/uuid/UUID.java
index 263f465..9ed5a76 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/uuid/UUID.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/uuid/UUID.java
@@ -5,7 +5,6 @@ import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.Random;
 import java.util.concurrent.ThreadLocalRandom;
-import com.ruoyi.common.core.exception.UtilException;
 
 /**
  * 提供通用唯一识别码(universally unique identifier)(UUID)实现
@@ -33,7 +32,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
 
     /**
      * 私有构造
-     * 
+     *
      * @param data 数据
      */
     private UUID(byte[] data)
@@ -67,7 +66,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
 
     /**
      * 获取类型 4(伪随机生成的)UUID 的静态工厂。
-     * 
+     *
      * @return 随机生成的 {@code UUID}
      */
     public static UUID fastUUID()
@@ -77,7 +76,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
 
     /**
      * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
-     * 
+     *
      * @return 随机生成的 {@code UUID}
      */
     public static UUID randomUUID()
@@ -87,7 +86,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
 
     /**
      * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
-     * 
+     *
      * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能
      * @return 随机生成的 {@code UUID}
      */
@@ -289,7 +288,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
      *
      * <p>
      * UUID 的字符串表示形式由此 BNF 描述:
-     * 
+     *
      * <pre>
      * {@code
      * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
@@ -302,7 +301,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
      * hexDigit               = [0-9a-fA-F]
      * }
      * </pre>
-     * 
+     *
      * </blockquote>
      *
      * @return 此{@code UUID} 的字符串表现形式
@@ -319,7 +318,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
      *
      * <p>
      * UUID 的字符串表示形式由此 BNF 描述:
-     * 
+     *
      * <pre>
      * {@code
      * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
@@ -332,7 +331,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
      * hexDigit               = [0-9a-fA-F]
      * }
      * </pre>
-     * 
+     *
      * </blockquote>
      *
      * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串
@@ -432,7 +431,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
     // Private method start
     /**
      * 返回指定数字对应的hex值
-     * 
+     *
      * @param val 值
      * @param digits 位
      * @return 值
@@ -456,7 +455,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
 
     /**
      * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
-     * 
+     *
      * @return {@link SecureRandom}
      */
     public static SecureRandom getSecureRandom()
@@ -467,14 +466,14 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
         }
         catch (NoSuchAlgorithmException e)
         {
-            throw new UtilException(e);
+            throw new IllegalArgumentException(e);
         }
     }
 
     /**
      * 获取随机数生成器对象<br>
      * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
-     * 
+     *
      * @return {@link ThreadLocalRandom}
      */
     public static ThreadLocalRandom getRandom()
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/core/CellMergeStrategy.java b/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/core/CellMergeStrategy.java
index 2e7e4d9..1301daf 100644
--- a/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/core/CellMergeStrategy.java
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/core/CellMergeStrategy.java
@@ -98,9 +98,30 @@ public class CellMergeStrategy extends AbstractMergeStrategy {
                             cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
                         }
                         map.put(field, new RepeatCell(val, i));
-                    } else if (i == list.size() - 1) {
-                        if (i > repeatCell.getCurrent()) {
-                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                    } else if (j == 0) {
+                        if (i == list.size() - 1) {
+                            if (i > repeatCell.getCurrent()) {
+                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                            }
+                        }
+                    } else {
+                        // 判断前面的是否合并了
+                        RepeatCell firstCell = map.get(mergeFields.get(0));
+                        if (repeatCell.getCurrent() != firstCell.getCurrent()) {
+                            if (i == list.size() - 1) {
+                                if (i > repeatCell.getCurrent()) {
+                                    cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                                }
+                            } else if (repeatCell.getCurrent() < firstCell.getCurrent()) {
+                                if (i - repeatCell.getCurrent() > 1) {
+                                    cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
+                                }
+                                map.put(field, new RepeatCell(val, i));
+                            }
+                        } else if (i == list.size() - 1) {
+                            if (i > repeatCell.getCurrent()) {
+                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                            }
                         }
                     }
                 }
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/utils/ExcelUtil.java
index e3ceb60..62a6986 100644
--- a/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/utils/ExcelUtil.java
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/utils/ExcelUtil.java
@@ -269,6 +269,27 @@ public class ExcelUtil {
         }
     }
 
+    /**
+     * 多sheet模板导出 模板格式为 {key.属性}
+     *
+     * @param filename     文件名
+     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名
+     *                     例如: excel/temp.xlsx
+     *                     重点: 模板文件必须放置到启动类对应的 resource 目录下
+     * @param data         模板需要的数据
+     * @param response     响应体
+     */
+    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
+        try {
+            resetResponse(filename, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportTemplateMultiSheet(data, templatePath, os);
+        } catch (IOException e) {
+            throw new RuntimeException("导出Excel异常");
+        }
+    }
+
+
     /**
      * 多表多数据模板导出 模板格式为 {key.属性}
      *
@@ -303,6 +324,42 @@ public class ExcelUtil {
         excelWriter.finish();
     }
 
+    /**
+     * 多sheet模板导出 模板格式为 {key.属性}
+     *
+     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名
+     *                     例如: excel/temp.xlsx
+     *                     重点: 模板文件必须放置到启动类对应的 resource 目录下
+     * @param data         模板需要的数据
+     * @param os           输出流
+     */
+    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
+        ClassPathResource templateResource = new ClassPathResource(templatePath);
+        ExcelWriter excelWriter = EasyExcel.write(os)
+            .withTemplate(templateResource.getStream())
+            .autoCloseStream(false)
+            // 大数值自动转换 防止失真
+            .registerConverter(new ExcelBigNumberConvert())
+            .build();
+        if (CollUtil.isEmpty(data)) {
+            throw new IllegalArgumentException("数据为空");
+        }
+        for (int i = 0; i < data.size(); i++) {
+            WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
+            for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
+                // 设置列表后续还有数据
+                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
+                if (map.getValue() instanceof Collection) {
+                    // 多表导出必须使用 FillWrapper
+                    excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
+                } else {
+                    excelWriter.fill(map.getValue(), writeSheet);
+                }
+            }
+        }
+        excelWriter.finish();
+    }
+
     /**
      * 重置响应体
      */
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java
index 683cb41..147fc60 100644
--- a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java
@@ -26,7 +26,7 @@ public class OperLogEvent implements Serializable {
     /**
      * 租户ID
      */
-    private String tenantId;
+    private Long tenantId;
 
     /**
      * 操作模块
diff --git a/ruoyi-common/ruoyi-common-orm/src/main/java/com/ruoyi/common/orm/handler/MybatisExceptionHandler.java b/ruoyi-common/ruoyi-common-orm/src/main/java/com/ruoyi/common/orm/handler/MybatisExceptionHandler.java
index 2567cab..a0a1d10 100644
--- a/ruoyi-common/ruoyi-common-orm/src/main/java/com/ruoyi/common/orm/handler/MybatisExceptionHandler.java
+++ b/ruoyi-common/ruoyi-common-orm/src/main/java/com/ruoyi/common/orm/handler/MybatisExceptionHandler.java
@@ -35,7 +35,7 @@ public class MybatisExceptionHandler {
     public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
         String requestURI = request.getRequestURI();
         String message = e.getMessage();
-        if (message.contains("CannotFindDataSourceException")) {
+        if ("CannotFindDataSourceException".contains(message)) {
             log.error("请求地址'{}', 未找到数据源", requestURI);
             return R.fail("未找到数据源,请联系管理员确认");
         }
diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml
index 3725533..1d5bd98 100644
--- a/ruoyi-common/ruoyi-common-oss/pom.xml
+++ b/ruoyi-common/ruoyi-common-oss/pom.xml
@@ -16,6 +16,11 @@
     </description>
 
     <dependencies>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-json</artifactId>
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/constant/OssConstant.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/constant/OssConstant.java
index 50a3292..6c81937 100644
--- a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/constant/OssConstant.java
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/constant/OssConstant.java
@@ -1,5 +1,7 @@
 package com.ruoyi.common.oss.constant;
 
+import com.ruoyi.common.core.constant.GlobalConstants;
+
 import java.util.Arrays;
 import java.util.List;
 
@@ -13,7 +15,7 @@ public interface OssConstant {
     /**
      * 默认配置KEY
      */
-    String DEFAULT_CONFIG_KEY = "sys_oss:default_config";
+    String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config";
 
     /**
      * 预览列表资源开关Key
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java
index b723230..4f94e1f 100644
--- a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java
@@ -39,7 +39,7 @@ public class OssFactory {
     /**
      * 根据类型获取实例
      */
-    public static OssClient instance(String configKey) {
+    public static synchronized OssClient instance(String configKey) {
         String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
         if (json == null) {
             throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/config/RedisConfig.java b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/config/RedisConfig.java
index ea1ef22..63c3a8e 100644
--- a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/config/RedisConfig.java
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/config/RedisConfig.java
@@ -49,6 +49,8 @@ public class RedisConfig {
             CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
             config.setThreads(redissonProperties.getThreads())
                 .setNettyThreads(redissonProperties.getNettyThreads())
+                // 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
+                .setUseScriptCache(true)
                 .setCodec(codec);
             RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
             if (ObjectUtil.isNotNull(singleServerConfig)) {
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/utils/RedisUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/utils/RedisUtils.java
index 61e9620..427e338 100644
--- a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/utils/RedisUtils.java
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/utils/RedisUtils.java
@@ -142,6 +142,18 @@ public class RedisUtils {
         return bucket.setIfAbsent(value, duration);
     }
 
+    /**
+     * 如果存在则设置 并返回 true 如果存在则返回 false
+     *
+     * @param key   缓存的键值
+     * @param value 缓存的值
+     * @return set成功或失败
+     */
+    public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
+        RBucket<T> bucket = CLIENT.getBucket(key);
+        return bucket.setIfExists(value, duration);
+    }
+
     /**
      * 注册对象监听器
      * <p>
@@ -243,6 +255,18 @@ public class RedisUtils {
         return rList.addAll(dataList);
     }
 
+    /**
+     * 追加缓存List数据
+     *
+     * @param key  缓存的键值
+     * @param data 待缓存的数据
+     * @return 缓存的对象
+     */
+    public static <T> boolean addCacheList(final String key, final T data) {
+        RList<T> rList = CLIENT.getList(key);
+        return rList.add(data);
+    }
+
     /**
      * 注册List监听器
      * <p>
@@ -267,6 +291,19 @@ public class RedisUtils {
         return rList.readAll();
     }
 
+    /**
+     * 获得缓存的list对象(范围)
+     *
+     * @param key  缓存的键值
+     * @param form 起始下标
+     * @param to   截止下标
+     * @return 缓存键值对应的数据
+     */
+    public static <T> List<T> getCacheListRange(final String key, int form, int to) {
+        RList<T> rList = CLIENT.getList(key);
+        return rList.range(form, to);
+    }
+
     /**
      * 缓存Set
      *
@@ -279,6 +316,18 @@ public class RedisUtils {
         return rSet.addAll(dataSet);
     }
 
+    /**
+     * 追加缓存Set数据
+     *
+     * @param key  缓存的键值
+     * @param data 待缓存的数据
+     * @return 缓存的对象
+     */
+    public static <T> boolean addCacheSet(final String key, final T data) {
+        RSet<T> rSet = CLIENT.getSet(key);
+        return rSet.add(data);
+    }
+
     /**
      * 注册Set监听器
      * <p>
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/SaTokenConfig.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/SaTokenConfig.java
index 5915baa..aab2380 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/SaTokenConfig.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/SaTokenConfig.java
@@ -19,7 +19,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  */
 @AutoConfiguration
 @PropertySource(value = "classpath:common-satoken.yml", factory = YmlPropertySourceFactory.class)
-public class SaTokenConfig implements WebMvcConfigurer {
+public class SaTokenConfig {
 
     @Bean
     public StpLogic getStpLogicJwt() {
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/core/dao/FlexSaTokenDao.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/core/dao/FlexSaTokenDao.java
index 3736e96..627e35c 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/core/dao/FlexSaTokenDao.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/core/dao/FlexSaTokenDao.java
@@ -45,12 +45,9 @@ public class FlexSaTokenDao implements SaTokenDao {
      */
     @Override
     public void update(String key, String value) {
-        long expire = getTimeout(key);
-        // -2 = 无此键
-        if (expire == NOT_VALUE_EXPIRE) {
-            return;
+        if (RedisUtils.hasKey(key)) {
+            RedisUtils.setCacheObject(key, value, true);
         }
-        this.set(key, value, expire);
     }
 
     /**
@@ -75,17 +72,6 @@ public class FlexSaTokenDao implements SaTokenDao {
      */
     @Override
     public void updateTimeout(String key, long timeout) {
-        // 判断是否想要设置为永久
-        if (timeout == NEVER_EXPIRE) {
-            long expire = getTimeout(key);
-            if (expire == NEVER_EXPIRE) {
-                // 如果其已经被设置为永久,则不作任何处理
-            } else {
-                // 如果尚未被设置为永久,那么再次set一次
-                this.set(key, this.get(key), timeout);
-            }
-            return;
-        }
         RedisUtils.expire(key, Duration.ofSeconds(timeout));
     }
 
@@ -119,12 +105,9 @@ public class FlexSaTokenDao implements SaTokenDao {
      */
     @Override
     public void updateObject(String key, Object object) {
-        long expire = getObjectTimeout(key);
-        // -2 = 无此键
-        if (expire == NOT_VALUE_EXPIRE) {
-            return;
+        if (RedisUtils.hasKey(key)) {
+            RedisUtils.setCacheObject(key, object, true);
         }
-        this.setObject(key, object, expire);
     }
 
     /**
@@ -149,17 +132,6 @@ public class FlexSaTokenDao implements SaTokenDao {
      */
     @Override
     public void updateObjectTimeout(String key, long timeout) {
-        // 判断是否想要设置为永久
-        if (timeout == NEVER_EXPIRE) {
-            long expire = getObjectTimeout(key);
-            if (expire == NEVER_EXPIRE) {
-                // 如果其已经被设置为永久,则不作任何处理
-            } else {
-                // 如果尚未被设置为永久,那么再次set一次
-                this.setObject(key, this.getObject(key), timeout);
-            }
-            return;
-        }
         RedisUtils.expire(key, Duration.ofSeconds(timeout));
     }
 
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
index 8d4687e..8883600 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
@@ -11,7 +11,6 @@ import jakarta.validation.ConstraintViolation;
 import jakarta.validation.ConstraintViolationException;
 import lombok.extern.slf4j.Slf4j;
 import com.ruoyi.common.core.core.domain.R;
-import com.ruoyi.common.core.exception.DemoModeException;
 import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.utils.StreamUtils;
 import org.springframework.context.support.DefaultMessageSourceResolvable;
@@ -161,12 +160,4 @@ public class GlobalExceptionHandler {
         String message = e.getBindingResult().getFieldError().getDefaultMessage();
         return R.fail(message);
     }
-
-    /**
-     * 演示模式异常
-     */
-    @ExceptionHandler(DemoModeException.class)
-    public R<Void> handleDemoModeException(DemoModeException e) {
-        return R.fail("演示模式,不允许操作");
-    }
 }
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/utils/LoginHelper.java
index cbe802b..51b750a 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/utils/LoginHelper.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/utils/LoginHelper.java
@@ -15,6 +15,7 @@ import com.ruoyi.common.core.core.domain.model.LoginUser;
 import com.ruoyi.common.core.enums.UserType;
 
 import java.util.Set;
+import java.util.function.Supplier;
 
 /**
  * 登录鉴权助手
@@ -36,6 +37,7 @@ public class LoginHelper {
     public static final String USER_KEY = "userId";
     public static final String DEPT_KEY = "deptId";
     public static final String CLIENT_KEY = "clientid";
+    public static final String TENANT_ADMIN_KEY = "isTenantAdmin";
 
     /**
      * 登录系统 基于 设备类型
@@ -56,32 +58,45 @@ public class LoginHelper {
             model.setExtra(TENANT_KEY, loginUser.getTenantId())
                 .setExtra(USER_KEY, loginUser.getUserId())
                 .setExtra(DEPT_KEY, loginUser.getDeptId()));
-        StpUtil.getSession().set(LOGIN_USER_KEY, loginUser);
+        //StpUtil.getSession().set(LOGIN_USER_KEY, loginUser);
+        StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
     }
 
     /**
      * 获取用户(多级缓存)
      */
     public static LoginUser getLoginUser() {
-        LoginUser loginUser = (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY);
-        if (loginUser != null) {
-            return loginUser;
-        }
-        SaSession session = StpUtil.getSession();
-        if (ObjectUtil.isNull(session)) {
-            return null;
-        }
-        loginUser = (LoginUser) session.get(LOGIN_USER_KEY);
-        SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
-        return loginUser;
+//        LoginUser loginUser = (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY);
+//        if (loginUser != null) {
+//            return loginUser;
+//        }
+//        SaSession session = StpUtil.getSession();
+//        if (ObjectUtil.isNull(session)) {
+//            return null;
+//        }
+//        loginUser = (LoginUser) session.get(LOGIN_USER_KEY);
+//        SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
+//        return loginUser;
+        return (LoginUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> {
+            SaSession session = StpUtil.getTokenSession();
+            if (ObjectUtil.isNull(session)) {
+                return null;
+            }
+            return session.get(LOGIN_USER_KEY);
+        });
     }
 
     /**
      * 获取用户基于token
      */
     public static LoginUser getLoginUser(String token) {
-        Object loginId = StpUtil.getLoginIdByToken(token);
-        SaSession session = StpUtil.getSessionByLoginId(loginId);
+//        Object loginId = StpUtil.getLoginIdByToken(token);
+//        SaSession session = StpUtil.getSessionByLoginId(loginId);
+//        if (ObjectUtil.isNull(session)) {
+//            return null;
+//        }
+//        return (LoginUser) session.get(LOGIN_USER_KEY);
+        SaSession session = StpUtil.getTokenSessionByToken(token);
         if (ObjectUtil.isNull(session)) {
             return null;
         }
@@ -98,8 +113,8 @@ public class LoginHelper {
     /**
      * 获取租户ID
      */
-    public static String getTenantId() {
-        return Convert.toStr(getExtra(TENANT_KEY));
+    public static Long getTenantId() {
+        return Convert.toLong(getExtra(TENANT_KEY));
     }
 
     /**
@@ -110,17 +125,18 @@ public class LoginHelper {
     }
 
     private static Object getExtra(String key) {
-        Object obj;
-        try {
-            obj = SaHolder.getStorage().get(key);
-            if (ObjectUtil.isNull(obj)) {
-                obj = StpUtil.getExtra(key);
-                SaHolder.getStorage().set(key, obj);
-            }
-        } catch (Exception e) {
-            return null;
-        }
-        return obj;
+//        Object obj;
+//        try {
+//            obj = SaHolder.getStorage().get(key);
+//            if (ObjectUtil.isNull(obj)) {
+//                obj = StpUtil.getExtra(key);
+//                SaHolder.getStorage().set(key, obj);
+//            }
+//        } catch (Exception e) {
+//            return null;
+//        }
+//        return obj;
+        return getStorageIfAbsentSet(key, () -> StpUtil.getExtra(key));
     }
 
     /**
@@ -163,7 +179,27 @@ public class LoginHelper {
     }
 
     public static boolean isTenantAdmin() {
-        return isTenantAdmin(getLoginUser().getRolePermission());
+        Object value = getStorageIfAbsentSet(TENANT_ADMIN_KEY, () -> {
+            return isTenantAdmin(getLoginUser().getRolePermission());
+        });
+        return Convert.toBool(value);
+    }
+
+    public static boolean isLogin() {
+        return getLoginUser() != null;
+    }
+
+    public static Object getStorageIfAbsentSet(String key, Supplier<Object> handle) {
+        try {
+            Object obj = SaHolder.getStorage().get(key);
+            if (ObjectUtil.isNull(obj)) {
+                obj = handle.get();
+                SaHolder.getStorage().set(key, obj);
+            }
+            return obj;
+        } catch (Exception e) {
+            return null;
+        }
     }
 
 }
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java
index c67a22a..8b12552 100644
--- a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java
@@ -24,7 +24,7 @@ public class PlusTenantLineHandler {
     private final TenantProperties tenantProperties;
 
     public Expression getTenantId() {
-        String tenantId = LoginHelper.getTenantId();
+        String tenantId = LoginHelper.getTenantId().toString();
         if (StringUtils.isBlank(tenantId)) {
             return new NullValue();
         }
@@ -38,7 +38,7 @@ public class PlusTenantLineHandler {
     }
 
     public boolean ignoreTable(String tableName) {
-        String tenantId = LoginHelper.getTenantId();
+        String tenantId = LoginHelper.getTenantId().toString();
         // 判断是否有租户
         if (StringUtils.isNotBlank(tenantId)) {
             // 不需要过滤租户的表
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java
index 5b04f3d..8c0ff5c 100644
--- a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java
@@ -130,7 +130,7 @@ public class TenantHelper {
     public static String getTenantId() {
         String tenantId = TenantHelper.getDynamic();
         if (StringUtils.isBlank(tenantId)) {
-            tenantId = LoginHelper.getTenantId();
+            tenantId = LoginHelper.getTenantId().toString();
         }
         return tenantId;
     }
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java
index ca118d7..dc59492 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java
@@ -43,7 +43,7 @@ public class SysOssConfigController extends BaseController {
     /**
      * 查询对象存储配置列表
      */
-    @SaCheckPermission("system:oss:list")
+    @SaCheckPermission("system:ossConfig:list")
     @GetMapping("/list")
     public TableDataInfo<SysOssConfigVo> list(SysOssConfigBo bo) {
         return ossConfigService.queryPageList(bo);
@@ -54,7 +54,7 @@ public class SysOssConfigController extends BaseController {
      *
      * @param ossConfigId OSS配置ID
      */
-    @SaCheckPermission("system:oss:query")
+    @SaCheckPermission("system:ossConfig:list")
     @GetMapping("/{ossConfigId}")
     public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
                                      @PathVariable Long ossConfigId) {
@@ -64,7 +64,7 @@ public class SysOssConfigController extends BaseController {
     /**
      * 新增对象存储配置
      */
-    @SaCheckPermission("system:oss:add")
+    @SaCheckPermission("system:ossConfig:add")
     @Log(title = "对象存储配置", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping()
@@ -79,7 +79,7 @@ public class SysOssConfigController extends BaseController {
     /**
      * 修改对象存储配置
      */
-    @SaCheckPermission("system:oss:edit")
+    @SaCheckPermission("system:ossConfig:edit")
     @Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
     @PutMapping()
@@ -96,7 +96,7 @@ public class SysOssConfigController extends BaseController {
      *
      * @param ossConfigIds OSS配置ID串
      */
-    @SaCheckPermission("system:oss:remove")
+    @SaCheckPermission("system:ossConfig:remove")
     @Log(title = "对象存储配置", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ossConfigIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
@@ -111,7 +111,7 @@ public class SysOssConfigController extends BaseController {
     /**
      * 状态修改
      */
-    @SaCheckPermission("system:oss:edit")
+    @SaCheckPermission("system:ossConfig:edit")
     @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
     @PutMapping("/changeStatus")
     public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java
index 4521ce4..edd181c 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java
@@ -72,16 +72,17 @@ public class SysProfileController extends BaseController
     public R<Void> updateProfile(@RequestBody SysUserProfileBo profile)
     {
         SysUserBo user = BeanUtil.toBean(profile, SysUserBo.class);
+        String username = LoginHelper.getUsername();
         LoginUser loginUser = LoginHelper.getLoginUser();
         SysUserVo sysUser = userService.selectUserById(loginUser.getUserId());
         user.setUserName(sysUser.getUserName());
         if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
         {
-            return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
+            return R.fail("修改用户'" + username + "'失败,手机号码已存在");
         }
         if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
         {
-            return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+            return R.fail("修改用户'" + username + "'失败,邮箱账号已存在");
         }
         user.setUserId(sysUser.getUserId());
 //        user.setPassword(null);
diff --git a/script/sql/mysql/update.sql b/script/sql/mysql/update.sql
index df9ec58..dcc2c7c 100644
--- a/script/sql/mysql/update.sql
+++ b/script/sql/mysql/update.sql
@@ -33,3 +33,16 @@ UPDATE `pj_job_info` SET `processor_info`='com.ruoyi.job.processors.StandalonePr
 UPDATE `pj_job_info` SET `processor_info`='com.ruoyi.job.processors.BroadcastProcessorDemo' WHERE  `id`=2;
 UPDATE `pj_job_info` SET `processor_info`='com.ruoyi.job.processors.MapProcessorDemo' WHERE  `id`=3;
 UPDATE `pj_job_info` SET `processor_info`='com.ruoyi.job.processors.MapReduceProcessorDemo' WHERE  `id`=4;
+
+--升级“文件管理配置”相关脚本
+ALTER TABLE `sys_menu`
+    CHANGE COLUMN `update_by` `update_by` BIGINT(19) NULL DEFAULT '0' COMMENT '更新者' AFTER `create_time`;
+delete from sys_menu where menu_id in (1604, 1605);
+insert into sys_menu values('1600', '文件查询', '118', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:query',        '#', 1, sysdate(), null, null, '');
+insert into sys_menu values('1601', '文件上传', '118', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:upload',       '#', 1, sysdate(), null, null, '');
+insert into sys_menu values('1602', '文件下载', '118', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:download',     '#', 1, sysdate(), null, null, '');
+insert into sys_menu values('1603', '文件删除', '118', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:remove',       '#', 1, sysdate(), null, null, '');
+insert into sys_menu values('1620', '配置列表', '118', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:list',   '#', 1, sysdate(), null, null, '');
+insert into sys_menu values('1621', '配置添加', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:add',    '#', 1, sysdate(), null, null, '');
+insert into sys_menu values('1622', '配置编辑', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:edit',   '#', 1, sysdate(), null, null, '');
+insert into sys_menu values('1623', '配置删除', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:remove', '#', 1, sysdate(), null, null, '');
diff --git a/script/sql/postgresql/update.sql b/script/sql/postgresql/update.sql
index fcb0671..c7e5af9 100644
--- a/script/sql/postgresql/update.sql
+++ b/script/sql/postgresql/update.sql
@@ -41,3 +41,15 @@ ALTER COLUMN "node_params" TYPE TEXT,
 	ALTER COLUMN "node_params" DROP NOT NULL,
 	ALTER COLUMN "node_params" DROP DEFAULT;
 COMMENT ON COLUMN "pj_workflow_node_info"."node_params" IS '';
+
+--升级“文件管理配置”相关脚本
+delete from sys_menu where menu_id in (1604, 1605);
+insert into sys_menu values('1600', '文件查询', '118', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:query',        '#', 1, now(), null, null, '');
+insert into sys_menu values('1601', '文件上传', '118', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:upload',       '#', 1, now(), null, null, '');
+insert into sys_menu values('1602', '文件下载', '118', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:download',     '#', 1, now(), null, null, '');
+insert into sys_menu values('1603', '文件删除', '118', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:remove',       '#', 1, now(), null, null, '');
+insert into sys_menu values('1620', '配置列表', '118', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:list',   '#', 1, now(), null, null, '');
+insert into sys_menu values('1621', '配置添加', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:add',    '#', 1, now(), null, null, '');
+insert into sys_menu values('1622', '配置编辑', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:edit',   '#', 1, now(), null, null, '');
+insert into sys_menu values('1623', '配置删除', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:remove', '#', 1, now(), null, null, '');
+