Browse Source

Merge branch 'master' into dev

# Conflicts:
#	pom.xml
#	ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java
#	ruoyi-api/ruoyi-api-system/src/main/resources/META-INF/spring.factories
#	ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/domain/AjaxResult.java
#	ruoyi-common/ruoyi-common-datascope/src/main/resources/META-INF/spring.factories
#	ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/configure/RedisConfig.java
#	ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring.factories
#	ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/utils/DictUtils.java
#	ruoyi-gateway/src/main/resources/bootstrap.yml
#	ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/service/GenTableServiceImpl.java
#	ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/util/VelocityUtils.java
#	ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java
#	ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java
#	ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
#	ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
#	ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
#	ruoyi-ui/package.json
#	ruoyi-ui/src/store/getters.js
#	ruoyi-ui/src/views/index.vue
#	ruoyi-ui/src/views/login.vue
#	ruoyi-ui/src/views/register.vue
#	ruoyi-ui/src/views/system/dict/data.vue
#	ruoyi-ui/src/views/system/dict/index.vue
#	ruoyi-ui/src/views/system/logininfor/index.vue
#	ruoyi-ui/src/views/system/user/profile/index.vue
#	ruoyi-ui/vue.config.js
#	sql/ry_20220808.sql
lzm 2 years ago
parent
commit
bea9a4e861
100 changed files with 1049 additions and 682 deletions
  1. 3 3
      README.md
  2. 46 20
      pom.xml
  3. 1 1
      ruoyi-api/pom.xml
  4. 1 1
      ruoyi-api/ruoyi-api-system/pom.xml
  5. 0 8
      ruoyi-api/ruoyi-api-system/src/main/resources/META-INF/spring.factories
  6. 3 0
      ruoyi-api/ruoyi-api-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  7. 1 1
      ruoyi-auth/pom.xml
  8. 16 47
      ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
  9. 85 0
      ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java
  10. 49 0
      ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysRecordLogService.java
  11. 1 1
      ruoyi-common/pom.xml
  12. 3 3
      ruoyi-common/ruoyi-common-core/pom.xml
  13. 21 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/Excel.java
  14. 31 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
  15. 0 16
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java
  16. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/GlobalException.java
  17. 1 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/ServiceException.java
  18. 4 9
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/text/Convert.java
  19. 0 164
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ReUtil.java
  20. 4 4
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java
  21. 19 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/file/FileTypeUtils.java
  22. 2 2
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/HTMLFilter.java
  23. 288 64
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
  24. 35 14
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/domain/AjaxResult.java
  25. 0 4
      ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring.factories
  26. 1 0
      ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  27. 1 1
      ruoyi-common/ruoyi-common-datascope/pom.xml
  28. 9 1
      ruoyi-common/ruoyi-common-datascope/src/main/java/com/ruoyi/common/datascope/aspect/DataScopeAspect.java
  29. 0 5
      ruoyi-common/ruoyi-common-datascope/src/main/resources/META-INF/spring.factories
  30. 1 0
      ruoyi-common/ruoyi-common-datascope/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  31. 1 1
      ruoyi-common/ruoyi-common-datasource/pom.xml
  32. 1 1
      ruoyi-common/ruoyi-common-log/pom.xml
  33. 15 3
      ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java
  34. 24 0
      ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/filter/PropertyPreExcludeFilter.java
  35. 0 3
      ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring.factories
  36. 2 0
      ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  37. 1 1
      ruoyi-common/ruoyi-common-redis/pom.xml
  38. 6 28
      ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java
  39. 4 5
      ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/configure/RedisConfig.java
  40. 12 0
      ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
  41. 0 6
      ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring.factories
  42. 2 0
      ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  43. 1 1
      ruoyi-common/ruoyi-common-security/pom.xml
  44. 7 6
      ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/utils/DictUtils.java
  45. 0 6
      ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring.factories
  46. 5 0
      ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  47. 1 1
      ruoyi-common/ruoyi-common-swagger/pom.xml
  48. 0 4
      ruoyi-common/ruoyi-common-swagger/src/main/resources/META-INF/spring.factories
  49. 3 0
      ruoyi-common/ruoyi-common-swagger/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  50. 1 1
      ruoyi-gateway/pom.xml
  51. 2 2
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/KaptchaTextCreator.java
  52. 3 0
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/SwaggerProvider.java
  53. 7 25
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/CacheRequestFilter.java
  54. 3 2
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java
  55. 1 1
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java
  56. 6 5
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java
  57. 0 3
      ruoyi-gateway/src/main/resources/bootstrap.yml
  58. 1 1
      ruoyi-modules/pom.xml
  59. 1 1
      ruoyi-modules/ruoyi-file/pom.xml
  60. 2 2
      ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsSysFileServiceImpl.java
  61. 3 18
      ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/utils/FileUploadUtils.java
  62. 1 1
      ruoyi-modules/ruoyi-gen/pom.xml
  63. 4 4
      ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/service/GenTableServiceImpl.java
  64. 5 4
      ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/util/VelocityUtils.java
  65. 5 5
      ruoyi-modules/ruoyi-gen/src/main/resources/vm_bat/vue/v3/index.vue.vm
  66. 1 1
      ruoyi-modules/ruoyi-job/pom.xml
  67. 2 2
      ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/controller/SysJobController.java
  68. 1 1
      ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/service/ISysJobService.java
  69. 9 2
      ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/service/SysJobServiceImpl.java
  70. 6 1
      ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/util/ScheduleUtils.java
  71. 1 1
      ruoyi-modules/ruoyi-system/pom.xml
  72. 14 0
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysLogininforController.java
  73. 11 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysProfileController.java
  74. 1 0
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java
  75. 3 3
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java
  76. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
  77. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
  78. 9 23
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
  79. 1 1
      ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
  80. 1 1
      ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
  81. 3 3
      ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml
  82. 5 5
      ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
  83. 1 1
      ruoyi-ui/babel.config.js
  84. 7 0
      ruoyi-ui/src/api/system/logininfor.js
  85. 29 1
      ruoyi-ui/src/components/DictData/index.js
  86. 3 40
      ruoyi-ui/src/components/RightPanel/index.vue
  87. 19 2
      ruoyi-ui/src/components/RightToolbar/index.vue
  88. 63 60
      ruoyi-ui/src/layout/components/Settings/index.vue
  89. 1 0
      ruoyi-ui/src/store/getters.js
  90. 2 0
      ruoyi-ui/src/store/index.js
  91. 50 0
      ruoyi-ui/src/store/modules/dict.js
  92. 2 2
      ruoyi-ui/src/utils/ruoyi.js
  93. 5 5
      ruoyi-ui/src/views/login.vue
  94. 5 5
      ruoyi-ui/src/views/register.vue
  95. 4 1
      ruoyi-ui/src/views/system/dict/data.vue
  96. 1 0
      ruoyi-ui/src/views/system/dict/index.vue
  97. 27 1
      ruoyi-ui/src/views/system/logininfor/index.vue
  98. 1 1
      ruoyi-ui/src/views/system/user/profile/index.vue
  99. 1 1
      ruoyi-ui/src/views/system/user/profile/resetPwd.vue
  100. 0 0
      ruoyi-ui/src/views/tool/build/RightPanel.vue

File diff suppressed because it is too large
+ 3 - 3
README.md


+ 46 - 20
pom.xml

@@ -6,39 +6,40 @@
 
     <groupId>com.ruoyi</groupId>
     <artifactId>ruoyi</artifactId>
-    <version>3.5.0</version>
+    <version>3.6.0</version>
 
     <name>ruoyi</name>
     <url>http://www.ruoyi.vip</url>
     <description>若依微服务系统</description>
 
     <properties>
-        <ruoyi.version>3.5.0</ruoyi.version>
+        <ruoyi.version>3.6.0</ruoyi.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.8</java.version>
-        <spring-boot.version>2.6.7</spring-boot.version>
-        <spring-cloud.version>2021.0.1</spring-cloud.version>
+        <spring-boot.version>2.7.2</spring-boot.version>
+        <spring-cloud.version>2021.0.3</spring-cloud.version>
         <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
         <alibaba.nacos.version>2.0.4</alibaba.nacos.version>
-        <spring-boot-admin.version>2.6.7</spring-boot-admin.version>
+        <alibaba.seata.version>1.5.1</alibaba.seata.version>
+        <spring-boot-admin.version>2.7.3</spring-boot-admin.version>
         <spring-boot.mybatis>2.2.2</spring-boot.mybatis>
         <swagger.fox.version>3.0.0</swagger.fox.version>
         <swagger.core.version>1.6.2</swagger.core.version>
         <tobato.version>1.27.2</tobato.version>
         <kaptcha.version>2.3.2</kaptcha.version>
-        <pagehelper.boot.version>1.4.1</pagehelper.boot.version>
-        <druid.version>1.2.8</druid.version>
-        <dynamic-ds.version>3.5.0</dynamic-ds.version>
+        <pagehelper.boot.version>1.4.3</pagehelper.boot.version>
+        <druid.version>1.2.11</druid.version>
+        <dynamic-ds.version>3.5.1</dynamic-ds.version>
         <commons.io.version>2.11.0</commons.io.version>
         <commons.fileupload.version>1.4</commons.fileupload.version>
         <velocity.version>2.3</velocity.version>
-        <fastjson.version>1.2.80</fastjson.version>
+        <fastjson.version>2.0.11</fastjson.version>
         <jjwt.version>0.9.1</jjwt.version>
         <minio.version>8.2.2</minio.version>
         <poi.version>4.1.2</poi.version>
         <commons-collections.version>3.2.2</commons-collections.version>
-        <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
+        <transmittable-thread-local.version>2.13.2</transmittable-thread-local.version>
     </properties>
 
     <profiles>
@@ -113,6 +114,13 @@
                 <version>${alibaba.nacos.version}</version>
             </dependency>
 
+            <!-- Alibaba Seata 配置 -->
+            <dependency>
+                <groupId>io.seata</groupId>
+                <artifactId>seata-spring-boot-starter</artifactId>
+                <version>${alibaba.seata.version}</version>
+            </dependency>
+
             <!-- SpringBoot 依赖配置 -->
             <dependency>
                 <groupId>org.springframework.boot</groupId>
@@ -122,13 +130,6 @@
                 <scope>import</scope>
             </dependency>
 
-            <!--  SpringBoot 监控客户端 -->
-            <dependency>
-                <groupId>de.codecentric</groupId>
-                <artifactId>spring-boot-admin-starter-client</artifactId>
-                <version>${spring-boot-admin.version}</version>
-            </dependency>
-
             <!-- FastDFS 分布式文件系统 -->
             <dependency>
                 <groupId>com.github.tobato</groupId>
@@ -206,8 +207,8 @@
 
             <!-- JSON 解析器和生成器 -->
             <dependency>
-                <groupId>com.alibaba</groupId>
-                <artifactId>fastjson</artifactId>
+                <groupId>com.alibaba.fastjson2</groupId>
+                <artifactId>fastjson2</artifactId>
                 <version>${fastjson.version}</version>
             </dependency>
 
@@ -324,4 +325,29 @@
         </plugins>
     </build>
 
-</project>
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+</project>

+ 1 - 1
ruoyi-api/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
ruoyi-api/ruoyi-api-system/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-api</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 0 - 8
ruoyi-api/ruoyi-api-system/src/main/resources/META-INF/spring.factories

@@ -1,8 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  com.ruoyi.system.api.factory.RemoteUserFallbackFactory,\
-  com.ruoyi.system.api.factory.RemoteSysDjpdzbFallbackFactory,\
-  com.ruoyi.system.api.factory.RemoteLogFallbackFactory, \
-  com.ruoyi.system.api.factory.RemoteFileFallbackFactory,\
-  com.ruoyi.system.api.factory.RemoteJobFallbackFactory,\
-  com.ruoyi.system.api.factory.RemoteSysDeptJlFallbackFactory
-

+ 3 - 0
ruoyi-api/ruoyi-api-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,3 @@
+com.ruoyi.system.api.factory.RemoteUserFallbackFactory
+com.ruoyi.system.api.factory.RemoteLogFallbackFactory
+com.ruoyi.system.api.factory.RemoteFileFallbackFactory

+ 1 - 1
ruoyi-auth/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 16 - 47
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java

@@ -8,13 +8,9 @@ import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.enums.UserStatus;
 import com.ruoyi.common.core.exception.ServiceException;
-import com.ruoyi.common.core.utils.ServletUtils;
 import com.ruoyi.common.core.utils.StringUtils;
-import com.ruoyi.common.core.utils.ip.IpUtils;
 import com.ruoyi.common.security.utils.SecurityUtils;
-import com.ruoyi.system.api.RemoteLogService;
 import com.ruoyi.system.api.RemoteUserService;
-import com.ruoyi.system.api.domain.SysLogininfor;
 import com.ruoyi.system.api.domain.SysUser;
 import com.ruoyi.system.api.model.LoginUser;
 
@@ -27,10 +23,13 @@ import com.ruoyi.system.api.model.LoginUser;
 public class SysLoginService
 {
     @Autowired
-    private RemoteLogService remoteLogService;
+    private RemoteUserService remoteUserService;
 
     @Autowired
-    private RemoteUserService remoteUserService;
+    private SysPasswordService passwordService;
+
+    @Autowired
+    private SysRecordLogService recordLogService;
 
     /**
      * 登录
@@ -40,21 +39,21 @@ public class SysLoginService
         // 用户名或密码为空 错误
         if (StringUtils.isAnyBlank(username, password))
         {
-            recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
             throw new ServiceException("用户/密码必须填写");
         }
         // 密码如果不在指定范围内 错误
         if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                 || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
         {
-            recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
             throw new ServiceException("用户密码不在指定范围");
         }
         // 用户名不在指定范围内 错误
         if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                 || username.length() > UserConstants.USERNAME_MAX_LENGTH)
         {
-            recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
             throw new ServiceException("用户名不在指定范围");
         }
         // 查询用户信息
@@ -67,33 +66,29 @@ public class SysLoginService
 
         if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
         {
-            recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
             throw new ServiceException("登录用户:" + username + " 不存在");
         }
         LoginUser userInfo = userResult.getData();
         SysUser user = userResult.getData().getSysUser();
         if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
         {
-            recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
             throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
         }
         if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
         {
-            recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
             throw new ServiceException("对不起,您的账号:" + username + " 已停用");
         }
-        if (!SecurityUtils.matchesPassword(password, user.getPassword()))
-        {
-            recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
-            throw new ServiceException("用户不存在/密码错误");
-        }
-        recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
+        passwordService.validate(user, password);
+        recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
         return userInfo;
     }
 
     public void logout(String loginName)
     {
-        recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
+        recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
     }
 
     /**
@@ -128,32 +123,6 @@ public class SysLoginService
         {
             throw new ServiceException(registerResult.getMsg());
         }
-        recordLogininfor(username, Constants.REGISTER, "注册成功");
-    }
-
-    /**
-     * 记录登录信息
-     * 
-     * @param username 用户名
-     * @param status 状态
-     * @param message 消息内容
-     * @return
-     */
-    public void recordLogininfor(String username, String status, String message)
-    {
-        SysLogininfor logininfor = new SysLogininfor();
-        logininfor.setUserName(username);
-        logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
-        logininfor.setMsg(message);
-        // 日志状态
-        if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
-        {
-            logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS);
-        }
-        else if (Constants.LOGIN_FAIL.equals(status))
-        {
-            logininfor.setStatus(Constants.LOGIN_FAIL_STATUS);
-        }
-        remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER);
+        recordLogService.recordLogininfor(username, Constants.REGISTER, "注册成功");
     }
-}
+}

+ 85 - 0
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java

@@ -0,0 +1,85 @@
+package com.ruoyi.auth.service;
+
+import java.util.concurrent.TimeUnit;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.core.constant.CacheConstants;
+import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.redis.service.RedisService;
+import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.system.api.domain.SysUser;
+
+/**
+ * 登录密码方法
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysPasswordService
+{
+    @Autowired
+    private RedisService redisService;
+
+    private int maxRetryCount = CacheConstants.passwordMaxRetryCount;
+
+    private Long lockTime = CacheConstants.passwordLockTime;
+
+    @Autowired
+    private SysRecordLogService recordLogService;
+
+    /**
+     * 登录账户密码错误次数缓存键名
+     * 
+     * @param username 用户名
+     * @return 缓存键key
+     */
+    private String getCacheKey(String username)
+    {
+        return CacheConstants.PWD_ERR_CNT_KEY + username;
+    }
+
+    public void validate(SysUser user, String password)
+    {
+        String username = user.getUserName();
+
+        Integer retryCount = redisService.getCacheObject(getCacheKey(username));
+
+        if (retryCount == null)
+        {
+            retryCount = 0;
+        }
+
+        if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
+        {
+            String errMsg = String.format("密码输入错误%s次,帐户锁定%s分钟", maxRetryCount, lockTime);
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL,errMsg);
+            throw new ServiceException(errMsg);
+        }
+
+        if (!matches(user, password))
+        {
+            retryCount = retryCount + 1;
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, String.format("密码输入错误%s次", retryCount));
+            redisService.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
+            throw new ServiceException("用户不存在/密码错误");
+        }
+        else
+        {
+            clearLoginRecordCache(username);
+        }
+    }
+
+    public boolean matches(SysUser user, String rawPassword)
+    {
+        return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
+    }
+
+    public void clearLoginRecordCache(String loginName)
+    {
+        if (redisService.hasKey(getCacheKey(loginName)))
+        {
+            redisService.deleteObject(getCacheKey(loginName));
+        }
+    }
+}

+ 49 - 0
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysRecordLogService.java

@@ -0,0 +1,49 @@
+package com.ruoyi.auth.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.constant.SecurityConstants;
+import com.ruoyi.common.core.utils.ServletUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.core.utils.ip.IpUtils;
+import com.ruoyi.system.api.RemoteLogService;
+import com.ruoyi.system.api.domain.SysLogininfor;
+
+/**
+ * 记录日志方法
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysRecordLogService
+{
+    @Autowired
+    private RemoteLogService remoteLogService;
+
+    /**
+     * 记录登录信息
+     * 
+     * @param username 用户名
+     * @param status 状态
+     * @param message 消息内容
+     * @return
+     */
+    public void recordLogininfor(String username, String status, String message)
+    {
+        SysLogininfor logininfor = new SysLogininfor();
+        logininfor.setUserName(username);
+        logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        logininfor.setMsg(message);
+        // 日志状态
+        if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
+        {
+            logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS);
+        }
+        else if (Constants.LOGIN_FAIL.equals(status))
+        {
+            logininfor.setStatus(Constants.LOGIN_FAIL_STATUS);
+        }
+        remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER);
+    }
+}

+ 1 - 1
ruoyi-common/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 3 - 3
ruoyi-common/ruoyi-common-core/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -67,8 +67,8 @@
 
         <!-- Alibaba Fastjson -->
         <dependency>
-            <groupId>com.alibaba</groupId>
-            <artifactId>fastjson</artifactId>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
         </dependency>
 
         <!-- Jwt -->

+ 21 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/Excel.java

@@ -87,6 +87,11 @@ public @interface Excel
      */
     public String[] combo() default {};
 
+    /**
+     * 是否需要纵向合并单元格,应对需求:含有list集合单元格)
+     */
+    public boolean needMerge() default false;
+
     /**
      * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
      */
@@ -108,7 +113,22 @@ public @interface Excel
     public ColumnType cellType() default ColumnType.STRING;
 
     /**
-     * 导出字体颜色
+     * 导出列头背景色
+     */
+    public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
+
+    /**
+     * 导出列头字体颜色
+     */
+    public IndexedColors headerColor() default IndexedColors.WHITE;
+
+    /**
+     * 导出单元格背景色
+     */
+    public IndexedColors backgroundColor() default IndexedColors.WHITE;
+
+    /**
+     * 导出单元格字体颜色
      */
     public IndexedColors color() default IndexedColors.BLACK;
 

+ 31 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java

@@ -1,7 +1,7 @@
 package com.ruoyi.common.core.constant;
 
 /**
- * 缓存的key 常量
+ * 缓存常量信息
  * 
  * @author ruoyi
  */
@@ -17,8 +17,38 @@ public class CacheConstants
      */
     public final static long REFRESH_TIME = 120;
 
+    /**
+     * 密码最大错误次数
+     */
+    public final static int passwordMaxRetryCount = 5;
+
+    /**
+     * 密码锁定时间,默认10(分钟)
+     */
+    public final static long passwordLockTime = 10;
+
     /**
      * 权限缓存前缀
      */
     public final static String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    /**
+     * 验证码 redis key
+     */
+    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 参数管理 cache key
+     */
+    public static final String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
 }

+ 0 - 16
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java

@@ -102,27 +102,11 @@ public class Constants
      */
     public static final String IS_ASC = "isAsc";
 
-    /**
-     * 验证码 redis key
-     */
-    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
-
     /**
      * 验证码有效期(分钟)
      */
     public static final long CAPTCHA_EXPIRATION = 2;
 
-
-    /**
-     * 参数管理 cache key
-     */
-    public static final String SYS_CONFIG_KEY = "sys_config:";
-
-    /**
-     * 字典管理 cache key
-     */
-    public static final String SYS_DICT_KEY = "sys_dict:";
-
     /**
      * 字典管理 cache key
      */

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/GlobalException.java

@@ -7,7 +7,6 @@ package com.ruoyi.common.core.exception;
  */
 public class GlobalException extends RuntimeException
 {
-
     private static final long serialVersionUID = 1L;
 
     /**
@@ -45,6 +44,7 @@ public class GlobalException extends RuntimeException
         return this;
     }
 
+    @Override
     public String getMessage()
     {
         return message;

+ 1 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/ServiceException.java

@@ -49,6 +49,7 @@ public final class ServiceException extends RuntimeException
         return detailMessage;
     }
 
+    @Override
     public String getMessage()
     {
         return message;

+ 4 - 9
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/text/Convert.java

@@ -561,17 +561,12 @@ public class Convert
         switch (valueStr)
         {
             case "true":
-                return true;
-            case "false":
-                return false;
             case "yes":
-                return true;
             case "ok":
-                return true;
-            case "no":
-                return false;
             case "1":
                 return true;
+            case "false":
+            case "no":
             case "0":
                 return false;
             default:
@@ -907,7 +902,7 @@ public class Convert
      */
     public static String toSBC(String input, Set<Character> notConvertSet)
     {
-        char c[] = input.toCharArray();
+        char[] c = input.toCharArray();
         for (int i = 0; i < c.length; i++)
         {
             if (null != notConvertSet && notConvertSet.contains(c[i]))
@@ -949,7 +944,7 @@ public class Convert
      */
     public static String toDBC(String text, Set<Character> notConvertSet)
     {
-        char c[] = text.toCharArray();
+        char[] c = text.toCharArray();
         for (int i = 0; i < c.length; i++)
         {
             if (null != notConvertSet && notConvertSet.contains(c[i]))

+ 0 - 164
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ReUtil.java

@@ -1,164 +0,0 @@
-package com.ruoyi.common.core.utils;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import com.ruoyi.common.core.text.Convert;
-import com.ruoyi.common.core.utils.StringUtils;
-
-public class ReUtil
-{
-    public final static Pattern GROUP_VAR = Pattern.compile("\\$(\\d+)");
-
-    /**
-     * 正则中需要被转义的关键字
-     */
-    public final static Set<Character> RE_KEYS = new HashSet<>(
-            Arrays.asList('$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'));;
-
-    /**
-     * 正则替换指定值<br>
-     * 通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串
-     *
-     * <p>
-     * 例如:原字符串是:中文1234,我想把1234换成(1234),则可以:
-     *
-     * <pre>
-     * ReUtil.replaceAll("中文1234", "(\\d+)", "($1)"))
-     *
-     * 结果:中文(1234)
-     * </pre>
-     *
-     * @param content 文本
-     * @param regex 正则
-     * @param replacementTemplate 替换的文本模板,可以使用$1类似的变量提取正则匹配出的内容
-     * @return 处理后的文本
-     */
-    public static String replaceAll(CharSequence content, String regex, String replacementTemplate)
-    {
-        final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
-        return replaceAll(content, pattern, replacementTemplate);
-    }
-
-    /**
-     * 正则替换指定值<br>
-     * 通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串
-     *
-     * @param content 文本
-     * @param pattern {@link Pattern}
-     * @param replacementTemplate 替换的文本模板,可以使用$1类似的变量提取正则匹配出的内容
-     * @return 处理后的文本
-     * @since 3.0.4
-     */
-    public static String replaceAll(CharSequence content, Pattern pattern, String replacementTemplate)
-    {
-        if (StringUtils.isEmpty(content))
-        {
-            return StringUtils.EMPTY;
-        }
-
-        final Matcher matcher = pattern.matcher(content);
-        boolean result = matcher.find();
-        if (result)
-        {
-            final Set<String> varNums = findAll(GROUP_VAR, replacementTemplate, 1, new HashSet<>());
-            final StringBuffer sb = new StringBuffer();
-            do
-            {
-                String replacement = replacementTemplate;
-                for (String var : varNums)
-                {
-                    int group = Integer.parseInt(var);
-                    replacement = replacement.replace("$" + var, matcher.group(group));
-                }
-                matcher.appendReplacement(sb, escape(replacement));
-                result = matcher.find();
-            }
-            while (result);
-            matcher.appendTail(sb);
-            return sb.toString();
-        }
-        return Convert.toStr(content);
-    }
- 
-    /**
-     * 取得内容中匹配的所有结果
-     *
-     * @param <T> 集合类型
-     * @param pattern 编译后的正则模式
-     * @param content 被查找的内容
-     * @param group 正则的分组
-     * @param collection 返回的集合类型
-     * @return 结果集
-     */
-    public static <T extends Collection<String>> T findAll(Pattern pattern, CharSequence content, int group,
-            T collection)
-    {
-        if (null == pattern || null == content)
-        {
-            return null;
-        }
-
-        if (null == collection)
-        {
-            throw new NullPointerException("Null collection param provided!");
-        }
-
-        final Matcher matcher = pattern.matcher(content);
-        while (matcher.find())
-        {
-            collection.add(matcher.group(group));
-        }
-        return collection;
-    }
-
-    /**
-     * 转义字符,将正则的关键字转义
-     *
-     * @param c 字符
-     * @return 转义后的文本
-     */
-    public static String escape(char c)
-    {
-        final StringBuilder builder = new StringBuilder();
-        if (RE_KEYS.contains(c))
-        {
-            builder.append('\\');
-        }
-        builder.append(c);
-        return builder.toString();
-    }
-
-    /**
-     * 转义字符串,将正则的关键字转义
-     *
-     * @param content 文本
-     * @return 转义后的文本
-     */
-    public static String escape(CharSequence content)
-    {
-        if (StringUtils.isBlank(content))
-        {
-            return StringUtils.EMPTY;
-        }
-
-        final StringBuilder builder = new StringBuilder();
-        int len = content.length();
-        char current;
-        for (int i = 0; i < len; i++)
-        {
-            current = content.charAt(i);
-            if (RE_KEYS.contains(current))
-            {
-                builder.append('\\');
-            }
-            builder.append(current);
-        }
-        return builder.toString();
-    }
-
-}

+ 4 - 4
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java

@@ -5,7 +5,6 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
 import java.util.Enumeration;
-import java.util.LinkedHashMap;
 import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -15,10 +14,11 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.util.LinkedCaseInsensitiveMap;
 import org.springframework.web.context.request.RequestAttributes;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
 import com.ruoyi.common.core.constant.Constants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.text.Convert;
@@ -142,7 +142,7 @@ public class ServletUtils
 
     public static Map<String, String> getHeaders(HttpServletRequest request)
     {
-        Map<String, String> map = new LinkedHashMap<>();
+        Map<String, String> map = new LinkedCaseInsensitiveMap<>();
         Enumeration<String> enumeration = request.getHeaderNames();
         if (enumeration != null)
         {
@@ -296,7 +296,7 @@ public class ServletUtils
         response.setStatusCode(status);
         response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
         R<?> result = R.fail(code, value.toString());
-        DataBuffer dataBuffer = response.bufferFactory().wrap(JSONObject.toJSONString(result).getBytes());
+        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
         return response.writeWith(Mono.just(dataBuffer));
     }
 }

+ 19 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/file/FileTypeUtils.java

@@ -1,7 +1,10 @@
 package com.ruoyi.common.core.utils.file;
 
 import java.io.File;
+import java.util.Objects;
+import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 文件类型工具类
@@ -45,6 +48,22 @@ public class FileTypeUtils
         return fileName.substring(separatorIndex + 1).toLowerCase();
     }
 
+    /**
+     * 获取文件名的后缀
+     * 
+     * @param file 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtension(MultipartFile file)
+    {
+        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+        if (StringUtils.isEmpty(extension))
+        {
+            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
+        }
+        return extension;
+    }
+
     /**
      * 获取文件类型
      * 

+ 2 - 2
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/HTMLFilter.java

@@ -332,7 +332,7 @@ public final class HTMLFilter
             final String name = m.group(1).toLowerCase();
             if (allowed(name))
             {
-                if (false == inArray(name, vSelfClosingTags))
+                if (!inArray(name, vSelfClosingTags))
                 {
                     if (vTagCounts.containsKey(name))
                     {
@@ -387,7 +387,7 @@ public final class HTMLFilter
                         {
                             paramValue = processParamProtocol(paramValue);
                         }
-                        params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\"");
+                        params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\"");
                     }
                 }
 

+ 288 - 64
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java

@@ -4,12 +4,14 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.math.BigDecimal;
 import java.text.DecimalFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
@@ -18,7 +20,9 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.RegExUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.poi.ss.usermodel.BorderStyle;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellStyle;
@@ -125,6 +129,26 @@ public class ExcelUtil<T>
      */
     private short maxHeight;
 
+    /**
+     * 合并后最后行数
+     */
+    private int subMergedLastRowNum = 0;
+
+    /**
+     * 合并后开始行数
+     */
+    private int subMergedFirstRowNum = 1;
+
+    /**
+     * 对象的子列表方法
+     */
+    private Method subMethod;
+
+    /**
+     * 对象的子列表属性
+     */
+    private List<Field> subFields;
+
     /**
      * 统计列表
      */
@@ -140,11 +164,27 @@ public class ExcelUtil<T>
      */
     public Class<T> clazz;
 
+    /**
+     * 需要排除列属性
+     */
+    public String[] excludeFields;
+
     public ExcelUtil(Class<T> clazz)
     {
         this.clazz = clazz;
     }
 
+    /**
+     * 隐藏Excel中列属性
+     *
+     * @param fields 列属性名 示例[单个"name"/多个"id","name"]
+     * @throws Exception
+     */
+    public void hideColumn(String... fields)
+    {
+        this.excludeFields = fields;
+    }
+
     public void init(List<T> list, String sheetName, String title, Type type)
     {
         if (list == null)
@@ -158,6 +198,7 @@ public class ExcelUtil<T>
         createExcelField();
         createWorkbook();
         createTitle();
+        createSubHead();
     }
 
     /**
@@ -167,19 +208,54 @@ public class ExcelUtil<T>
     {
         if (StringUtils.isNotEmpty(title))
         {
+            subMergedFirstRowNum++;
+            subMergedLastRowNum++;
+            int titleLastCol = this.fields.size() - 1;
+            if (isSubList())
+            {
+                titleLastCol = titleLastCol + subFields.size() - 1;
+            }
             Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
             titleRow.setHeightInPoints(30);
             Cell titleCell = titleRow.createCell(0);
             titleCell.setCellStyle(styles.get("title"));
             titleCell.setCellValue(title);
-            sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(),
-                    this.fields.size() - 1));
+            sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol));
+        }
+    }
+
+    /**
+     * 创建对象的子列表名称
+     */
+    public void createSubHead()
+    {
+        if (isSubList())
+        {
+            subMergedFirstRowNum++;
+            subMergedLastRowNum++;
+            Row subRow = sheet.createRow(rownum);
+            int excelNum = 0;
+            for (Object[] objects : fields)
+            {
+                Excel attr = (Excel) objects[1];
+                Cell headCell1 = subRow.createCell(excelNum);
+                headCell1.setCellValue(attr.name());
+                headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
+                excelNum++;
+            }
+            int headFirstRow = excelNum - 1;
+            int headLastRow = headFirstRow + subFields.size() - 1;
+            if (headLastRow > headFirstRow)
+            {
+                sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));
+            }
+            rownum++;
         }
     }
 
     /**
      * 对excel表单默认第一个索引名转换成list
-     *
+     * 
      * @param is 输入流
      * @return 转换后集合
      */
@@ -190,7 +266,7 @@ public class ExcelUtil<T>
 
     /**
      * 对excel表单默认第一个索引名转换成list
-     *
+     * 
      * @param is 输入流
      * @param titleNum 标题占用行数
      * @return 转换后集合
@@ -202,7 +278,7 @@ public class ExcelUtil<T>
 
     /**
      * 对excel表单指定表格索引名转换成list
-     *
+     * 
      * @param sheetName 表格索引名
      * @param titleNum 标题占用行数
      * @param is 输入流
@@ -406,7 +482,6 @@ public class ExcelUtil<T>
      * @param list 导出数据集合
      * @param sheetName 工作表的名称
      * @return 结果
-     * @throws IOException
      */
     public void exportExcel(HttpServletResponse response, List<T> list, String sheetName)
     {
@@ -421,7 +496,6 @@ public class ExcelUtil<T>
      * @param sheetName 工作表的名称
      * @param title 标题
      * @return 结果
-     * @throws IOException
      */
     public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title)
     {
@@ -431,12 +505,6 @@ public class ExcelUtil<T>
         exportExcel(response);
     }
 
-    /**
-     * 对list数据源将其里面的数据导入到excel表单
-     *
-     * @param sheetName 工作表的名称
-     * @return 结果
-     */
     /**
      * 对list数据源将其里面的数据导入到excel表单
      *
@@ -465,7 +533,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     *
+     * 
      * @return 结果
      */
     public void exportExcel(HttpServletResponse response)
@@ -502,8 +570,20 @@ public class ExcelUtil<T>
             // 写入各个字段的列头名称
             for (Object[] os : fields)
             {
+                Field field = (Field) os[0];
                 Excel excel = (Excel) os[1];
-                this.createCell(excel, row, column++);
+                if (Collection.class.isAssignableFrom(field.getType()))
+                {
+                    for (Field subField : subFields)
+                    {
+                        Excel subExcel = subField.getAnnotation(Excel.class);
+                        this.createHeadCell(subExcel, row, column++);
+                    }
+                }
+                else
+                {
+                    this.createHeadCell(excel, row, column++);
+                }
             }
             if (Type.EXPORT.equals(type))
             {
@@ -515,32 +595,71 @@ public class ExcelUtil<T>
 
     /**
      * 填充excel数据
-     *
+     * 
      * @param index 序号
      * @param row 单元格行
      */
+    @SuppressWarnings("unchecked")
     public void fillExcelData(int index, Row row)
     {
         int startNo = index * sheetSize;
         int endNo = Math.min(startNo + sheetSize, list.size());
+        int rowNo = (1 + rownum) - startNo;
         for (int i = startNo; i < endNo; i++)
         {
-            row = sheet.createRow(i + 1 + rownum - startNo);
+            rowNo = i > 1 ? rowNo + 1 : rowNo + i;
+            row = sheet.createRow(rowNo);
             // 得到导出对象.
             T vo = (T) list.get(i);
+            Collection<?> subList = null;
+            if (isSubListValue(vo))
+            {
+                subList = getListCellValue(vo);
+                subMergedLastRowNum = subMergedLastRowNum + subList.size();
+            }
+
             int column = 0;
             for (Object[] os : fields)
             {
                 Field field = (Field) os[0];
                 Excel excel = (Excel) os[1];
-                this.addCell(excel, row, vo, field, column++);
+                if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList))
+                {
+                    boolean subFirst = false;
+                    for (Object obj : subList)
+                    {
+                        if (subFirst)
+                        {
+                            rowNo++;
+                            row = sheet.createRow(rowNo);
+                        }
+                        List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
+                        int subIndex = 0;
+                        for (Field subField : subFields)
+                        {
+                            if (subField.isAnnotationPresent(Excel.class))
+                            {
+                                subField.setAccessible(true);
+                                Excel attr = subField.getAnnotation(Excel.class);
+                                this.addCell(attr, row, (T) obj, subField, column + subIndex);
+                            }
+                            subIndex++;
+                        }
+                        subFirst = true;
+                    }
+                    this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();
+                }
+                else
+                {
+                    this.addCell(excel, row, vo, field, column++);
+                }
             }
         }
     }
 
     /**
      * 创建表格样式
-     *
+     * 
      * @param wb 工作薄对象
      * @return 样式列表
      */
@@ -575,20 +694,6 @@ public class ExcelUtil<T>
         style.setFont(dataFont);
         styles.put("data", style);
 
-        style = wb.createCellStyle();
-        style.cloneStyleFrom(styles.get("data"));
-        style.setAlignment(HorizontalAlignment.CENTER);
-        style.setVerticalAlignment(VerticalAlignment.CENTER);
-        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
-        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
-        Font headerFont = wb.createFont();
-        headerFont.setFontName("Arial");
-        headerFont.setFontHeightInPoints((short) 10);
-        headerFont.setBold(true);
-        headerFont.setColor(IndexedColors.WHITE.getIndex());
-        style.setFont(headerFont);
-        styles.put("header", style);
-
         style = wb.createCellStyle();
         style.setAlignment(HorizontalAlignment.CENTER);
         style.setVerticalAlignment(VerticalAlignment.CENTER);
@@ -598,24 +703,60 @@ public class ExcelUtil<T>
         style.setFont(totalFont);
         styles.put("total", style);
 
-        styles.putAll(annotationStyles(wb));
+        styles.putAll(annotationHeaderStyles(wb, styles));
+
+        styles.putAll(annotationDataStyles(wb));
 
         return styles;
     }
 
     /**
-     * 根据Excel注解创建表格样式
+     * 根据Excel注解创建表格样式
      * 
      * @param wb 工作薄对象
      * @return 自定义样式列表
      */
-    private Map<String, CellStyle> annotationStyles(Workbook wb)
+    private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles)
+    {
+        Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>();
+        for (Object[] os : fields)
+        {
+            Excel excel = (Excel) os[1];
+            String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor());
+            if (!headerStyles.containsKey(key))
+            {
+                CellStyle style = wb.createCellStyle();
+                style = wb.createCellStyle();
+                style.cloneStyleFrom(styles.get("data"));
+                style.setAlignment(HorizontalAlignment.CENTER);
+                style.setVerticalAlignment(VerticalAlignment.CENTER);
+                style.setFillForegroundColor(excel.headerBackgroundColor().index);
+                style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+                Font headerFont = wb.createFont();
+                headerFont.setFontName("Arial");
+                headerFont.setFontHeightInPoints((short) 10);
+                headerFont.setBold(true);
+                headerFont.setColor(excel.headerColor().index);
+                style.setFont(headerFont);
+                headerStyles.put(key, style);
+            }
+        }
+        return headerStyles;
+    }
+
+    /**
+     * 根据Excel注解创建表格列样式
+     * 
+     * @param wb 工作薄对象
+     * @return 自定义样式列表
+     */
+    private Map<String, CellStyle> annotationDataStyles(Workbook wb)
     {
         Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
         for (Object[] os : fields)
         {
             Excel excel = (Excel) os[1];
-            String key = "data_" + excel.align() + "_" + excel.color();
+            String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor());
             if (!styles.containsKey(key))
             {
                 CellStyle style = wb.createCellStyle();
@@ -630,6 +771,8 @@ public class ExcelUtil<T>
                 style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
                 style.setBorderBottom(BorderStyle.THIN);
                 style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+                style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+                style.setFillForegroundColor(excel.backgroundColor().getIndex());
                 Font dataFont = wb.createFont();
                 dataFont.setFontName("Arial");
                 dataFont.setFontHeightInPoints((short) 10);
@@ -644,20 +787,29 @@ public class ExcelUtil<T>
     /**
      * 创建单元格
      */
-    public Cell createCell(Excel attr, Row row, int column)
+    public Cell createHeadCell(Excel attr, Row row, int column)
     {
         // 创建列
         Cell cell = row.createCell(column);
         // 写入列信息
         cell.setCellValue(attr.name());
         setDataValidation(attr, row, column);
-        cell.setCellStyle(styles.get("header"));
+        cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
+        if (isSubList())
+        {
+            // 填充默认样式,防止合并单元格样式失效
+            sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor())));
+            if (attr.needMerge())
+            {
+                sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));
+            }
+        }
         return cell;
     }
 
     /**
      * 设置单元格信息
-     *
+     * 
      * @param value 单元格值
      * @param attr 注解相关
      * @param cell 单元格信息
@@ -759,7 +911,12 @@ public class ExcelUtil<T>
             {
                 // 创建cell
                 cell = row.createCell(column);
-                cell.setCellStyle(styles.get("data_" + attr.align() + "_" + attr.color()));
+                if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge())
+                {
+                    CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column);
+                    sheet.addMergedRegion(cellAddress);
+                }
+                cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor())));
 
                 // 用于读取对象中的属性
                 Object value = getTargetValue(vo, field, attr);
@@ -778,7 +935,7 @@ public class ExcelUtil<T>
                 }
                 else if (value instanceof BigDecimal && -1 != attr.scale())
                 {
-                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
+                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
                 }
                 else if (!attr.handler().equals(ExcelHandlerAdapter.class))
                 {
@@ -855,7 +1012,7 @@ public class ExcelUtil<T>
         for (String item : convertSource)
         {
             String[] itemArray = item.split("=");
-            if (StringUtils.containsAny(separator, propertyValue))
+            if (StringUtils.containsAny(propertyValue, separator))
             {
                 for (String value : propertyValue.split(separator))
                 {
@@ -879,7 +1036,7 @@ public class ExcelUtil<T>
 
     /**
      * 反向解析值 男=0,女=1,未知=2
-     *
+     * 
      * @param propertyValue 参数值
      * @param converterExp 翻译注解
      * @param separator 分隔符
@@ -892,7 +1049,7 @@ public class ExcelUtil<T>
         for (String item : convertSource)
         {
             String[] itemArray = item.split("=");
-            if (StringUtils.containsAny(separator, propertyValue))
+            if (StringUtils.containsAny(propertyValue, separator))
             {
                 for (String value : propertyValue.split(separator))
                 {
@@ -1054,29 +1211,39 @@ public class ExcelUtil<T>
         tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
         for (Field field : tempFields)
         {
-            // 单注解
-            if (field.isAnnotationPresent(Excel.class))
-            {
-                Excel attr = field.getAnnotation(Excel.class);
-                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
-                {
-                    field.setAccessible(true);
-                    fields.add(new Object[] { field, attr });
-                }
-            }
-
-            // 多注解
-            if (field.isAnnotationPresent(Excels.class))
+            if (!ArrayUtils.contains(this.excludeFields, field.getName()))
             {
-                Excels attrs = field.getAnnotation(Excels.class);
-                Excel[] excels = attrs.value();
-                for (Excel attr : excels)
+                // 单注解
+                if (field.isAnnotationPresent(Excel.class))
                 {
+                    Excel attr = field.getAnnotation(Excel.class);
                     if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
                     {
                         field.setAccessible(true);
                         fields.add(new Object[] { field, attr });
                     }
+                    if (Collection.class.isAssignableFrom(field.getType()))
+                    {
+                        subMethod = getSubMethod(field.getName(), clazz);
+                        ParameterizedType pt = (ParameterizedType) field.getGenericType();
+                        Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
+                        this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
+                    }
+                }
+
+                // 多注解
+                if (field.isAnnotationPresent(Excels.class))
+                {
+                    Excels attrs = field.getAnnotation(Excels.class);
+                    Excel[] excels = attrs.value();
+                    for (Excel attr : excels)
+                    {
+                        if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                        {
+                            field.setAccessible(true);
+                            fields.add(new Object[] { field, attr });
+                        }
+                    }
                 }
             }
         }
@@ -1110,7 +1277,7 @@ public class ExcelUtil<T>
 
     /**
      * 创建工作表
-     *
+     * 
      * @param sheetNo sheet数量
      * @param index 序号
      */
@@ -1127,7 +1294,7 @@ public class ExcelUtil<T>
 
     /**
      * 获取单元格值
-     *
+     * 
      * @param row 获取的行
      * @param column 获取单元格列号
      * @return 单元格值
@@ -1187,7 +1354,7 @@ public class ExcelUtil<T>
 
     /**
      * 判断是否是空行
-     *
+     * 
      * @param row 判断的行
      * @return
      */
@@ -1240,4 +1407,61 @@ public class ExcelUtil<T>
         }
         return str;
     }
+
+    /**
+     * 是否有对象的子列表
+     */
+    public boolean isSubList()
+    {
+        return StringUtils.isNotNull(subFields) && subFields.size() > 0;
+    }
+
+    /**
+     * 是否有对象的子列表,集合不为空
+     */
+    public boolean isSubListValue(T vo)
+    {
+        return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0;
+    }
+
+    /**
+     * 获取集合的值
+     */
+    public Collection<?> getListCellValue(Object obj)
+    {
+        Object value;
+        try
+        {
+            value = subMethod.invoke(obj, new Object[] {});
+        }
+        catch (Exception e)
+        {
+            return new ArrayList<Object>();
+        }
+        return (Collection<?>) value;
+    }
+
+    /**
+     * 获取对象的子列表方法
+     * 
+     * @param name 名称
+     * @param pojoClass 类对象
+     * @return 子列表方法
+     */
+    public Method getSubMethod(String name, Class<?> pojoClass)
+    {
+        StringBuffer getMethodName = new StringBuffer("get");
+        getMethodName.append(name.substring(0, 1).toUpperCase());
+        getMethodName.append(name.substring(1));
+        Method method = null;
+        try
+        {
+            method = pojoClass.getMethod(getMethodName.toString(), new Class[] {});
+        }
+        catch (Exception e)
+        {
+            log.error("获取对象异常{}", e.getMessage());
+        }
+        return method;
+    }
 }

+ 35 - 14
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/domain/AjaxResult.java

@@ -4,6 +4,7 @@ import java.util.HashMap;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.TypeReference;
+import java.util.Objects;
 import com.ruoyi.common.core.constant.HttpStatus;
 import com.ruoyi.common.core.utils.StringUtils;
 
@@ -60,20 +61,6 @@ public class AjaxResult extends HashMap<String, Object>
             super.put(DATA_TAG, data);
         }
     }
-    
-    /**
-     * 方便链式调用
-     *
-     * @param key
-     * @param value
-     * @return
-     */
-    @Override
-    public AjaxResult put(String key, Object value)
-    {
-        super.put(key, value);
-        return this;
-    }
 
     /**
      * 扩展获取data为实际对象
@@ -174,4 +161,38 @@ public class AjaxResult extends HashMap<String, Object>
     {
         return new AjaxResult(code, msg, null);
     }
+
+    /**
+     * 是否为成功消息
+     *
+     * @return 结果
+     */
+    public boolean isSuccess()
+    {
+        return !isError();
+    }
+
+    /**
+     * 是否为错误消息
+     *
+     * @return 结果
+     */
+    public boolean isError()
+    {
+        return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
+    }
+
+    /**
+     * 方便链式调用
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    @Override
+    public AjaxResult put(String key, Object value)
+    {
+        super.put(key, value);
+        return this;
+    }
 }

+ 0 - 4
ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring.factories

@@ -1,4 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  com.ruoyi.common.core.utils.SpringUtils
-
-  

+ 1 - 0
ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.ruoyi.common.core.utils.SpringUtils

+ 1 - 1
ruoyi-common/ruoyi-common-datascope/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 9 - 1
ruoyi-common/ruoyi-common-datascope/src/main/java/com/ruoyi/common/datascope/aspect/DataScopeAspect.java

@@ -1,5 +1,7 @@
 package com.ruoyi.common.datascope.aspect;
 
+import java.util.ArrayList;
+import java.util.List;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
@@ -85,10 +87,15 @@ public class DataScopeAspect
     public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
     {
         StringBuilder sqlString = new StringBuilder();
+        List<String> conditions = new ArrayList<String>();
 
         for (SysRole role : user.getRoles())
         {
             String dataScope = role.getDataScope();
+            if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
+            {
+                continue;
+            }
             if (DATA_SCOPE_ALL.equals(dataScope))
             {
                 sqlString = new StringBuilder();
@@ -119,9 +126,10 @@ public class DataScopeAspect
                 else
                 {
                     // 数据权限为仅本人且没有userAlias别名不查询任何数据
-                    sqlString.append(" OR 1=0 ");
+                    sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
                 }
             }
+            conditions.add(dataScope);
         }
 
         if (StringUtils.isNotBlank(sqlString.toString()))

+ 0 - 5
ruoyi-common/ruoyi-common-datascope/src/main/resources/META-INF/spring.factories

@@ -1,5 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  com.ruoyi.common.datascope.aspect.DataScopeAspect,\
-  com.ruoyi.common.datascope.config.MybatisPlusConfiguration
-
-  

+ 1 - 0
ruoyi-common/ruoyi-common-datascope/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.ruoyi.common.datascope.aspect.DataScopeAspect

+ 1 - 1
ruoyi-common/ruoyi-common-datasource/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 1 - 1
ruoyi-common/ruoyi-common-log/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 15 - 3
ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java

@@ -15,12 +15,13 @@ import org.springframework.http.HttpMethod;
 import org.springframework.stereotype.Component;
 import org.springframework.validation.BindingResult;
 import org.springframework.web.multipart.MultipartFile;
-import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson2.JSON;
 import com.ruoyi.common.core.utils.ServletUtils;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.core.utils.ip.IpUtils;
 import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessStatus;
+import com.ruoyi.common.log.filter.PropertyPreExcludeFilter;
 import com.ruoyi.common.log.service.AsyncLogService;
 import com.ruoyi.common.security.utils.SecurityUtils;
 import com.ruoyi.system.api.domain.SysOperLog;
@@ -35,7 +36,10 @@ import com.ruoyi.system.api.domain.SysOperLog;
 public class LogAspect
 {
     private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
-    
+
+    /** 排除敏感属性字段 */
+    public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
+
     @Autowired
     private AsyncLogService asyncLogService;
 
@@ -162,7 +166,7 @@ public class LogAspect
                 {
                     try
                     {
-                        Object jsonObj = JSON.toJSON(o);
+                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
                         params += jsonObj.toString() + " ";
                     }
                     catch (Exception e)
@@ -174,6 +178,14 @@ public class LogAspect
         return params.trim();
     }
 
+    /**
+     * 忽略敏感属性
+     */
+    public PropertyPreExcludeFilter excludePropertyPreFilter()
+    {
+        return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
+    }
+
     /**
      * 判断是否需要过滤的对象。
      * 

+ 24 - 0
ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/filter/PropertyPreExcludeFilter.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.log.filter;
+
+import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
+
+/**
+ * 排除JSON敏感属性
+ * 
+ * @author ruoyi
+ */
+public class PropertyPreExcludeFilter extends SimplePropertyPreFilter
+{
+    public PropertyPreExcludeFilter()
+    {
+    }
+
+    public PropertyPreExcludeFilter addExcludes(String... filters)
+    {
+        for (int i = 0; i < filters.length; i++)
+        {
+            this.getExcludes().add(filters[i]);
+        }
+        return this;
+    }
+}

+ 0 - 3
ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring.factories

@@ -1,3 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  com.ruoyi.common.log.service.AsyncLogService,\
-  com.ruoyi.common.log.aspect.LogAspect

+ 2 - 0
ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,2 @@
+com.ruoyi.common.log.service.AsyncLogService
+com.ruoyi.common.log.aspect.LogAspect

+ 1 - 1
ruoyi-common/ruoyi-common-redis/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 6 - 28
ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java

@@ -1,15 +1,11 @@
 package com.ruoyi.common.redis.configure;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import java.nio.charset.Charset;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import com.alibaba.fastjson.parser.ParserConfig;
-import org.springframework.util.Assert;
-import java.nio.charset.Charset;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 
 /**
  * Redis使用FastJson序列化
@@ -18,17 +14,10 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
-
     public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
     private Class<T> clazz;
 
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
 
     public FastJson2JsonRedisSerializer(Class<T> clazz)
     {
@@ -43,7 +32,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
     }
 
     @Override
@@ -55,17 +44,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
         }
         String str = new String(bytes, DEFAULT_CHARSET);
 
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
     }
 }

+ 4 - 5
ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/configure/RedisConfig.java

@@ -5,12 +5,15 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
 
 /**
  * redis配置
@@ -19,6 +22,7 @@ import org.springframework.data.redis.core.RedisTemplate;
  */
 @Configuration
 @EnableCaching
+@AutoConfigureBefore(RedisAutoConfiguration.class)
 public class RedisConfig extends CachingConfigurerSupport
 {
     @Bean
@@ -30,11 +34,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(redisKeySerializer);//template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);

+ 12 - 0
ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java

@@ -240,6 +240,18 @@ public class RedisService
         return redisTemplate.opsForHash().multiGet(key, hKeys);
     }
 
+    /**
+     * 删除Hash中的某条数据
+     *
+     * @param key Redis键
+     * @param hKey  Hash键
+     * @return 是否成功
+     */
+    public boolean deleteCacheMapValue(final String key, final String hKey)
+    {
+        return Boolean.TRUE.equals(redisTemplate.opsForHash().delete(key, hKey));
+    }
+
     /**
      * 获得缓存的基本对象列表
      * 

+ 0 - 6
ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring.factories

@@ -1,6 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  com.ruoyi.common.redis.configure.RedisConfig,\
-  com.ruoyi.common.redis.configure.RedisKeySerializer,\
-  com.ruoyi.common.redis.service.RedisService
-
-  

+ 2 - 0
ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,2 @@
+com.ruoyi.common.redis.configure.RedisConfig
+com.ruoyi.common.redis.service.RedisService

+ 1 - 1
ruoyi-common/ruoyi-common-security/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 7 - 6
ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/utils/DictUtils.java

@@ -1,6 +1,7 @@
 package com.ruoyi.common.security.utils;
 
-import com.ruoyi.common.core.constant.Constants;
+import com.alibaba.fastjson2.JSONArray;
+import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.utils.SpringUtils;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.redis.configure.RedisKeySerializer;
@@ -38,10 +39,10 @@ public class DictUtils
      */
     public static List<SysDictData> getDictCache(String key)
     {
-        Object cacheObj = SpringUtils.getBean(RedisService.class).getCacheObject(getCacheKey(key));
-        if (StringUtils.isNotNull(cacheObj))
+        JSONArray arrayCache = SpringUtils.getBean(RedisService.class).getCacheObject(getCacheKey(key));
+        if (StringUtils.isNotNull(arrayCache))
         {
-            return StringUtils.cast(cacheObj);
+            return arrayCache.toList(SysDictData.class);
         }
         return null;
     }
@@ -61,7 +62,7 @@ public class DictUtils
      */
     public static void clearDictCache()
     {
-        Collection<String> keys = SpringUtils.getBean(RedisService.class).keys(Constants.SYS_DICT_KEY + "*");
+        Collection<String> keys = SpringUtils.getBean(RedisService.class).keys(CacheConstants.SYS_DICT_KEY + "*");
         Collection<String> keys2 = keys.stream().map(item -> item.replace(RedisKeySerializer.key + ":", "")).collect(Collectors.toSet());
         SpringUtils.getBean(RedisService.class).deleteObject(keys2);
     }
@@ -74,7 +75,7 @@ public class DictUtils
      */
     public static String getCacheKey(String configKey)
     {
-        return Constants.SYS_DICT_KEY + configKey;
+        return CacheConstants.SYS_DICT_KEY + configKey;
     }
 
 

+ 0 - 6
ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring.factories

@@ -1,6 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  com.ruoyi.common.security.config.WebMvcConfig,\
-  com.ruoyi.common.security.service.TokenService,\
-  com.ruoyi.common.security.aspect.PreAuthorizeAspect,\
-  com.ruoyi.common.security.aspect.InnerAuthAspect,\
-  com.ruoyi.common.security.handler.GlobalExceptionHandler

+ 5 - 0
ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,5 @@
+com.ruoyi.common.security.config.WebMvcConfig
+com.ruoyi.common.security.service.TokenService
+com.ruoyi.common.security.aspect.PreAuthorizeAspect
+com.ruoyi.common.security.aspect.InnerAuthAspect
+com.ruoyi.common.security.handler.GlobalExceptionHandler

+ 1 - 1
ruoyi-common/ruoyi-common-swagger/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     

+ 0 - 4
ruoyi-common/ruoyi-common-swagger/src/main/resources/META-INF/spring.factories

@@ -1,4 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  com.ruoyi.common.swagger.config.SwaggerAutoConfiguration,\
-  com.ruoyi.common.swagger.config.SwaggerWebConfiguration,\
-  com.ruoyi.common.swagger.config.SwaggerBeanPostProcessor

+ 3 - 0
ruoyi-common/ruoyi-common-swagger/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,3 @@
+com.ruoyi.common.swagger.config.SwaggerAutoConfiguration
+com.ruoyi.common.swagger.config.SwaggerWebConfiguration
+com.ruoyi.common.swagger.config.SwaggerBeanPostProcessor

+ 1 - 1
ruoyi-gateway/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 2
ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/KaptchaTextCreator.java

@@ -20,7 +20,7 @@ public class KaptchaTextCreator extends DefaultTextCreator
         int x = random.nextInt(10);
         int y = random.nextInt(10);
         StringBuilder suChinese = new StringBuilder();
-        int randomoperands = (int) Math.round(Math.random() * 2);
+        int randomoperands = random.nextInt(3);
         if (randomoperands == 0)
         {
             result = x * y;
@@ -30,7 +30,7 @@ public class KaptchaTextCreator extends DefaultTextCreator
         }
         else if (randomoperands == 1)
         {
-            if (!(x == 0) && y % x == 0)
+            if ((x != 0) && y % x == 0)
             {
                 result = y / x;
                 suChinese.append(CNUMBERS[y]);

+ 3 - 0
ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/SwaggerProvider.java

@@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cloud.gateway.config.GatewayProperties;
 import org.springframework.cloud.gateway.route.RouteLocator;
 import org.springframework.cloud.gateway.support.NameUtils;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 import org.springframework.web.reactive.config.ResourceHandlerRegistry;
 import org.springframework.web.reactive.config.WebFluxConfigurer;
@@ -24,9 +25,11 @@ public class SwaggerProvider implements SwaggerResourcesProvider, WebFluxConfigu
      * Swagger2默认的url后缀
      */
     public static final String SWAGGER2URL = "/v2/api-docs";
+
     /**
      * 网关路由
      */
+    @Lazy
     @Autowired
     private RouteLocator routeLocator;
 

+ 7 - 25
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/CacheRequestFilter.java

@@ -6,14 +6,10 @@ import org.springframework.cloud.gateway.filter.GatewayFilter;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
 import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
 import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.core.io.buffer.DataBufferFactory;
-import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
 import org.springframework.http.HttpMethod;
-import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
 import org.springframework.stereotype.Component;
 import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 /**
@@ -54,30 +50,16 @@ public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheReques
         {
             // GET DELETE 不过滤
             HttpMethod method = exchange.getRequest().getMethod();
-            if (method == null || method.matches("GET") || method.matches("DELETE"))
+            if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE)
             {
                 return chain.filter(exchange);
             }
-            return DataBufferUtils.join(exchange.getRequest().getBody()).map(dataBuffer -> {
-                byte[] bytes = new byte[dataBuffer.readableByteCount()];
-                dataBuffer.read(bytes);
-                DataBufferUtils.release(dataBuffer);
-                return bytes;
-            }).defaultIfEmpty(new byte[0]).flatMap(bytes -> {
-                DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
-                ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest())
+            return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
+                if (serverHttpRequest == exchange.getRequest())
                 {
-                    @Override
-                    public Flux<DataBuffer> getBody()
-                    {
-                        if (bytes.length > 0)
-                        {
-                            return Flux.just(dataBufferFactory.wrap(bytes));
-                        }
-                        return Flux.empty();
-                    }
-                };
-                return chain.filter(exchange.mutate().request(decorator).build());
+                    return chain.filter(exchange);
+                }
+                return chain.filter(exchange.mutate().request(serverHttpRequest).build());
             });
         }
     }

+ 3 - 2
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java

@@ -10,7 +10,8 @@ import org.springframework.core.io.buffer.DataBuffer;
 import org.springframework.core.io.buffer.DataBufferUtils;
 import org.springframework.http.server.reactive.ServerHttpRequest;
 import org.springframework.stereotype.Component;
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.ruoyi.common.core.utils.ServletUtils;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.gateway.config.properties.CaptchaProperties;
@@ -52,7 +53,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
             try
             {
                 String rspStr = resolveBodyFromRequest(request);
-                JSONObject obj = JSONObject.parseObject(rspStr);
+                JSONObject obj = JSON.parseObject(rspStr);
                 validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
             }
             catch (Exception e)

+ 1 - 1
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java

@@ -44,7 +44,7 @@ public class XssFilter implements GlobalFilter, Ordered
         ServerHttpRequest request = exchange.getRequest();
         // GET DELETE 不过滤
         HttpMethod method = request.getMethod();
-        if (method == null || method.matches("GET") || method.matches("DELETE"))
+        if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE)
         {
             return chain.filter(exchange);
         }

+ 6 - 5
ruoyi-gateway/src/main/java/com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java

@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.FastByteArrayOutputStream;
 import com.google.code.kaptcha.Producer;
+import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.constant.Constants;
 import com.ruoyi.common.core.exception.CaptchaException;
 import com.ruoyi.common.core.utils.StringUtils;
@@ -46,16 +47,16 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
     public AjaxResult createCaptcha() throws IOException, CaptchaException
     {
         AjaxResult ajax = AjaxResult.success();
-        boolean captchaOnOff = captchaProperties.getEnabled();
-        ajax.put("captchaOnOff", captchaOnOff);
-        if (!captchaOnOff)
+        boolean captchaEnabled = captchaProperties.getEnabled();
+        ajax.put("captchaEnabled", captchaEnabled);
+        if (!captchaEnabled)
         {
             return ajax;
         }
 
         // 保存验证码信息
         String uuid = IdUtils.simpleUUID();
-        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
 
         String capStr = null, code = null;
         BufferedImage image = null;
@@ -106,7 +107,7 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
         {
             throw new CaptchaException("验证码已失效");
         }
-        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
         String captcha = redisService.getCacheObject(verifyKey);
         redisService.deleteObject(verifyKey);
 

+ 0 - 3
ruoyi-gateway/src/main/resources/bootstrap.yml

@@ -10,9 +10,6 @@ spring:
   profiles:
     # 环境配置
     active: @profiles.active@
-  main:
-    allow-circular-references: true
-    allow-bean-definition-overriding: true
   cloud:
     nacos:
       # nacos 服务地址

+ 1 - 1
ruoyi-modules/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
ruoyi-modules/ruoyi-file/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-modules</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 2
ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsSysFileServiceImpl.java

@@ -1,12 +1,12 @@
 package com.ruoyi.file.service;
 
-import org.apache.commons.io.FilenameUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 import com.github.tobato.fastdfs.domain.fdfs.StorePath;
 import com.github.tobato.fastdfs.service.FastFileStorageClient;
+import com.ruoyi.common.core.utils.file.FileTypeUtils;
 
 /**
  * FastDFS 文件存储
@@ -36,7 +36,7 @@ public class FastDfsSysFileServiceImpl implements ISysFileService
     public String uploadFile(MultipartFile file) throws Exception
     {
         StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(),
-                FilenameUtils.getExtension(file.getOriginalFilename()), null);
+                FileTypeUtils.getExtension(file), null);
         return domain + "/" + storePath.getFullPath();
     }
 }

+ 3 - 18
ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/utils/FileUploadUtils.java

@@ -5,6 +5,7 @@ import com.ruoyi.common.core.exception.file.FileSizeLimitExceededException;
 import com.ruoyi.common.core.exception.file.InvalidExtensionException;
 import com.ruoyi.common.core.utils.DateUtils;
 import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.core.utils.file.FileTypeUtils;
 import com.ruoyi.common.core.utils.file.MimeTypeUtils;
 import com.ruoyi.common.core.utils.uuid.Seq;
 import org.apache.commons.io.FilenameUtils;
@@ -89,7 +90,7 @@ public class FileUploadUtils
     public static final String extractFilename(MultipartFile file)
     {
         return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
-                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
+                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), FileTypeUtils.getExtension(file));
     }
 
     private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
@@ -129,7 +130,7 @@ public class FileUploadUtils
         }
 
         String fileName = file.getOriginalFilename();
-        String extension = getExtension(file);
+        String extension = FileTypeUtils.getExtension(file);
         if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
         {
             if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
@@ -177,20 +178,4 @@ public class FileUploadUtils
         }
         return false;
     }
-
-    /**
-     * 获取文件名的后缀
-     * 
-     * @param file 表单文件
-     * @return 后缀名
-     */
-    public static final String getExtension(MultipartFile file)
-    {
-        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
-        if (StringUtils.isEmpty(extension))
-        {
-            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
-        }
-        return extension;
-    }
 }

+ 1 - 1
ruoyi-modules/ruoyi-gen/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-modules</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 4 - 4
ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/service/GenTableServiceImpl.java

@@ -1,7 +1,7 @@
 package com.ruoyi.gen.service;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.ruoyi.common.core.constant.Constants;
 import com.ruoyi.common.core.constant.GenConstants;
 import com.ruoyi.common.core.exception.ServiceException;
@@ -403,7 +403,7 @@ public class GenTableServiceImpl implements IGenTableService
         if (GenConstants.TPL_TREE.equals(genTable.getTplCategory()))
         {
             String options = JSON.toJSONString(genTable.getParams());
-            JSONObject paramsObj = JSONObject.parseObject(options);
+            JSONObject paramsObj = JSON.parseObject(options);
             if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE)))
             {
                 throw new ServiceException("树编码字段不能为空");
@@ -487,7 +487,7 @@ public class GenTableServiceImpl implements IGenTableService
      */
     public void setTableFromOptions(GenTable genTable)
     {
-        JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions());
+        JSONObject paramsObj = JSON.parseObject(genTable.getOptions());
         if (StringUtils.isNotNull(paramsObj))
         {
             String treeCode = paramsObj.getString(GenConstants.TREE_CODE);

+ 5 - 4
ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/util/VelocityUtils.java

@@ -1,6 +1,7 @@
 package com.ruoyi.gen.util;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.ruoyi.common.core.constant.GenConstants;
 import com.ruoyi.common.core.utils.DateUtils;
 import com.ruoyi.common.core.utils.StringUtils;
@@ -76,7 +77,7 @@ public class VelocityUtils
     public static void setMenuVelocityContext(VelocityContext context, GenTable genTable)
     {
         String options = genTable.getOptions();
-        JSONObject paramsObj = JSONObject.parseObject(options);
+        JSONObject paramsObj = JSON.parseObject(options);
         String parentMenuId = getParentMenuId(paramsObj);
         context.put("parentMenuId", parentMenuId);
     }
@@ -84,7 +85,7 @@ public class VelocityUtils
     public static void setTreeVelocityContext(VelocityContext context, GenTable genTable)
     {
         String options = genTable.getOptions();
-        JSONObject paramsObj = JSONObject.parseObject(options);
+        JSONObject paramsObj = JSON.parseObject(options);
         String treeCode = getTreecode(paramsObj);
         String treeParentCode = getTreeParentCode(paramsObj);
         String treeName = getTreeName(paramsObj);
@@ -387,7 +388,7 @@ public class VelocityUtils
     public static int getExpandColumn(GenTable genTable)
     {
         String options = genTable.getOptions();
-        JSONObject paramsObj = JSONObject.parseObject(options);
+        JSONObject paramsObj = JSON.parseObject(options);
         String treeName = paramsObj.getString(GenConstants.TREE_NAME);
         int num = 0;
         for (GenTableColumn column : genTable.getColumns())

+ 5 - 5
ruoyi-modules/ruoyi-gen/src/main/resources/vm_bat/vue/v3/index.vue.vm

@@ -472,8 +472,8 @@ function handleAdd() {
 /** 修改按钮操作 */
 function handleUpdate(row) {
   reset();
-  const ${pkColumn.javaField} = row.${pkColumn.javaField} || ids.value
-  get${BusinessName}(${pkColumn.javaField}).then(response => {
+  const _${pkColumn.javaField} = row.${pkColumn.javaField} || ids.value
+  get${BusinessName}(_${pkColumn.javaField}).then(response => {
     form.value = response.data;
 #foreach ($column in $columns)
 #if($column.htmlType == "checkbox")
@@ -519,9 +519,9 @@ function submitForm() {
 
 /** 删除按钮操作 */
 function handleDelete(row) {
-  const ${pkColumn.javaField}s = row.${pkColumn.javaField} || ids.value;
-  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(function() {
-    return del${BusinessName}(${pkColumn.javaField}s);
+  const _${pkColumn.javaField}s = row.${pkColumn.javaField} || ids.value;
+  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?').then(function() {
+    return del${BusinessName}(_${pkColumn.javaField}s);
   }).then(() => {
     getList();
     proxy.#[[$modal]]#.msgSuccess("删除成功");

+ 1 - 1
ruoyi-modules/ruoyi-job/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-modules</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 2
ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/controller/SysJobController.java

@@ -159,8 +159,8 @@ public class SysJobController extends BaseController
     @PutMapping("/run")
     public AjaxResult run(@RequestBody SysJob job) throws SchedulerException
     {
-        jobService.run(job);
-        return AjaxResult.success();
+        boolean result = jobService.run(job);
+        return result ? success() : error("任务不存在或已过期!");
     }
 
     /**

+ 1 - 1
ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/service/ISysJobService.java

@@ -75,7 +75,7 @@ public interface ISysJobService
      * @param job 调度信息
      * @return 结果
      */
-    public void run(SysJob job) throws SchedulerException;
+    public boolean run(SysJob job) throws SchedulerException;
 
     /**
      * 新增任务

+ 9 - 2
ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/service/SysJobServiceImpl.java

@@ -179,15 +179,22 @@ public class SysJobServiceImpl implements ISysJobService
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void run(SysJob job) throws SchedulerException
+    public boolean run(SysJob job) throws SchedulerException
     {
+        boolean result = false;
         Long jobId = job.getJobId();
         String jobGroup = job.getJobGroup();
         SysJob properties = selectJobById(job.getJobId());
         // 参数
         JobDataMap dataMap = new JobDataMap();
         dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
-        scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap);
+        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
+        if (scheduler.checkExists(jobKey))
+        {
+            result = true;
+            scheduler.triggerJob(jobKey, dataMap);
+        }
+        return result;
     }
 
     /**

+ 6 - 1
ruoyi-modules/ruoyi-job/src/main/java/com/ruoyi/job/util/ScheduleUtils.java

@@ -83,7 +83,12 @@ public class ScheduleUtils
             scheduler.deleteJob(getJobKey(jobId, jobGroup));
         }
 
-        scheduler.scheduleJob(jobDetail, trigger);
+        // 判断任务是否过期
+        if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
+        {
+            // 执行调度任务
+            scheduler.scheduleJob(jobDetail, trigger);
+        }
 
         // 暂停任务
         if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))

+ 1 - 1
ruoyi-modules/ruoyi-system/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ruoyi</groupId>
         <artifactId>ruoyi-modules</artifactId>
-        <version>3.5.0</version>
+        <version>3.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 14 - 0
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysLogininforController.java

@@ -10,12 +10,14 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.web.controller.BaseController;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.common.core.web.page.TableDataInfo;
 import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.redis.service.RedisService;
 import com.ruoyi.common.security.annotation.InnerAuth;
 import com.ruoyi.common.security.annotation.RequiresPermissions;
 import com.ruoyi.system.api.domain.SysLogininfor;
@@ -33,6 +35,9 @@ public class SysLogininforController extends BaseController
     @Autowired
     private ISysLogininforService logininforService;
 
+    @Autowired
+    private RedisService redisService;
+
     @RequiresPermissions("system:logininfor:list")
     @GetMapping("/list")
     public TableDataInfo list(SysLogininfor logininfor)
@@ -69,6 +74,15 @@ public class SysLogininforController extends BaseController
         return AjaxResult.success();
     }
 
+    @RequiresPermissions("system:logininfor:unlock")
+    @Log(title = "账户解锁", businessType = BusinessType.OTHER)
+    @GetMapping("/unlock/{userName}")
+    public AjaxResult unlock(@PathVariable("userName") String userName)
+    {
+        redisService.deleteObject(CacheConstants.PWD_ERR_CNT_KEY + userName);
+        return success();
+    }
+
     @InnerAuth
     @PostMapping
     public AjaxResult add(@RequestBody SysLogininfor logininfor)

+ 11 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysProfileController.java

@@ -1,6 +1,6 @@
 package com.ruoyi.system.controller;
 
-import java.io.IOException;
+import java.util.Arrays;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -13,6 +13,8 @@ import org.springframework.web.multipart.MultipartFile;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.core.utils.file.FileTypeUtils;
+import com.ruoyi.common.core.utils.file.MimeTypeUtils;
 import com.ruoyi.common.core.web.controller.BaseController;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.common.log.annotation.Log;
@@ -79,6 +81,8 @@ public class SysProfileController extends BaseController
         }
         user.setUserId(sysUser.getUserId());
         user.setPassword(null);
+        user.setAvatar(null);
+        user.setDeptId(null);
         if (userService.updateUserProfile(user) > 0)
         {
             // 更新缓存用户信息
@@ -126,11 +130,16 @@ public class SysProfileController extends BaseController
      */
     @Log(title = "用户头像", businessType = BusinessType.UPDATE)
     @PostMapping("/avatar")
-    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws IOException
+    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file)
     {
         if (!file.isEmpty())
         {
             LoginUser loginUser = SecurityUtils.getLoginUser();
+            String extension = FileTypeUtils.getExtension(file);
+            if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION))
+            {
+                return AjaxResult.error("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
+            }
             R<SysFile> fileResult = remoteFileService.upload(file);
             if (StringUtils.isNull(fileResult) || StringUtils.isNull(fileResult.getData()))
             {

+ 1 - 0
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java

@@ -2,6 +2,7 @@ package com.ruoyi.system.mapper;
 
 import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
 import com.ruoyi.common.datascope.utils.BaseMapperPlus;
+import java.util.List;
 import com.ruoyi.system.api.domain.SysDictType;
 import org.apache.ibatis.annotations.Param;
 

+ 3 - 3
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java

@@ -1,6 +1,6 @@
 package com.ruoyi.system.service.impl;
 
-import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.text.Convert;
@@ -165,7 +165,7 @@ public class SysConfigServiceImpl implements ISysConfigService
     @Override
     public void clearConfigCache()
     {
-        Collection<String> keys = redisService.keys(Constants.SYS_CONFIG_KEY + "*");
+        Collection<String> keys = redisService.keys(CacheConstants.SYS_CONFIG_KEY + "*");
         Collection<String> keys2 = keys.stream().map(item -> item.replace(RedisKeySerializer.key + ":", "")).collect(Collectors.toSet());
         redisService.deleteObject(keys2);
     }
@@ -206,6 +206,6 @@ public class SysConfigServiceImpl implements ISysConfigService
      */
     private String getCacheKey(String configKey)
     {
-        return Constants.SYS_CONFIG_KEY + configKey;
+        return CacheConstants.SYS_CONFIG_KEY + configKey;
     }
 }

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java

@@ -198,7 +198,7 @@ public class SysDeptServiceImpl implements ISysDeptService
     public boolean hasChildByDeptId(Long deptId)
     {
         int result = deptMapper.hasChildByDeptId(deptId);
-        return result > 0 ? true : false;
+        return result > 0;
     }
 
     /**

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java

@@ -277,7 +277,7 @@ public class SysMenuServiceImpl implements ISysMenuService
     public boolean hasChildByMenuId(Long menuId)
     {
         int result = menuMapper.hasChildByMenuId(menuId);
-        return result > 0 ? true : false;
+        return result > 0;
     }
 
     /**
@@ -290,7 +290,7 @@ public class SysMenuServiceImpl implements ISysMenuService
     public boolean checkMenuExistRole(Long menuId)
     {
         int result = roleMenuMapper.checkMenuExistRole(menuId);
-        return result > 0 ? true : false;
+        return result > 0;
     }
 
     /**

+ 9 - 23
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

@@ -373,20 +373,7 @@ public class SysUserServiceImpl implements ISysUserService {
      * @param user 用户对象
      */
     public void insertUserRole(SysUser user) {
-        Long[] roles = user.getRoleIds();
-        if (StringUtils.isNotNull(roles)) {
-            // 新增用户与角色管理
-            List<SysUserRole> list = new ArrayList<SysUserRole>();
-            for (Long roleId : roles) {
-                SysUserRole ur = new SysUserRole();
-                ur.setUserId(user.getUserId());
-                ur.setRoleId(roleId);
-                list.add(ur);
-            }
-            if (list.size() > 0) {
-                userRoleMapper.batchUserRole(list);
-            }
-        }
+        this.insertUserRole(user.getUserId(), user.getRoleIds());
     }
 
     /**
@@ -396,7 +383,8 @@ public class SysUserServiceImpl implements ISysUserService {
      */
     public void insertUserPost(SysUser user) {
         Long[] posts = user.getPostIds();
-        if (StringUtils.isNotNull(posts)) {
+        if (StringUtils.isNotEmpty(posts))
+        {
             // 新增用户与岗位管理
             List<SysUserPost> list = new ArrayList<SysUserPost>();
             for (Long postId : posts) {
@@ -405,9 +393,7 @@ public class SysUserServiceImpl implements ISysUserService {
                 up.setPostId(postId);
                 list.add(up);
             }
-            if (list.size() > 0) {
-                userPostMapper.batchUserPost(list);
-            }
+            userPostMapper.batchUserPost(list);
         }
     }
 
@@ -417,8 +403,10 @@ public class SysUserServiceImpl implements ISysUserService {
      * @param userId  用户ID
      * @param roleIds 角色组
      */
-    public void insertUserRole(Long userId, Long[] roleIds) {
-        if (StringUtils.isNotNull(roleIds)) {
+    public void insertUserRole(Long userId, Long[] roleIds)
+    {
+        if (StringUtils.isNotEmpty(roleIds))
+        {
             // 新增用户与角色管理
             List<SysUserRole> list = new ArrayList<SysUserRole>();
             for (Long roleId : roleIds) {
@@ -427,9 +415,7 @@ public class SysUserServiceImpl implements ISysUserService {
                 ur.setRoleId(roleId);
                 list.add(ur);
             }
-            if (list.size() > 0) {
-                userRoleMapper.batchUserRole(list);
-            }
+            userRoleMapper.batchUserRole(list);
         }
     }
 

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml

@@ -113,7 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 	<select id="checkDeptNameUnique" resultMap="SysDeptResult">
 	    <include refid="selectDeptVo"/>
-		where dept_name=#{deptName} and parent_id = #{parentId} limit 1
+		where dept_name=#{deptName} and parent_id = #{parentId} and del_flag = '0' limit 1
 	</select>
 
     <insert id="insertDept" parameterType="SysDept">

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml

@@ -38,7 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 			<if test="title != null and title != ''">
 				AND title like concat('%', #{title}, '%')
 			</if>
-			<if test="businessType != null and businessType != ''">
+			<if test="businessType != null">
 				AND business_type = #{businessType}
 			</if>
 			<if test="businessTypes != null and businessTypes.length > 0">

+ 3 - 3
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml

@@ -99,12 +99,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	
 	<select id="checkRoleNameUnique" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
-		 where r.role_name=#{roleName} limit 1
+		 where r.role_name=#{roleName} and r.del_flag = '0' limit 1
 	</select>
 	
 	<select id="checkRoleKeyUnique" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
-		 where r.role_key=#{roleKey} limit 1
+		 where r.role_key=#{roleKey} and r.del_flag = '0' limit 1
 	</select>
 	
  	<insert id="insertRole" parameterType="SysRole" useGeneratedKeys="true" keyProperty="roleId">
@@ -174,7 +174,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	</update>
 	
 	<delete id="deleteRoleById" parameterType="Long">
-		update sys_role set del_flag = '2' where role_id = #{roleId}
+ 		update sys_role set del_flag = '2' where role_id = #{roleId}
  	</delete>
  	
  	<delete id="deleteRoleByIds" parameterType="Long">

+ 5 - 5
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

@@ -162,7 +162,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 	<select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
 	    <include refid="selectUserVo"/>
-		where u.user_name = #{userName}
+		where u.user_name = #{userName} and u.del_flag = '0'
 	</select>
 
 	<select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
@@ -171,15 +171,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	</select>
 
 	<select id="checkUserNameUnique" parameterType="String" resultType="int">
-		select count(1) from sys_user where user_name = #{userName} limit 1
+		select count(1) from sys_user where user_name = #{userName} and del_flag = '0' limit 1
 	</select>
 
 	<select id="checkPhoneUnique" parameterType="String" resultMap="SysUserResult">
-		select user_id, phonenumber from sys_user where phonenumber = #{phonenumber} limit 1
+		select user_id, phonenumber from sys_user where phonenumber = #{phonenumber} and del_flag = '0' limit 1
 	</select>
 
 	<select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult">
-		select user_id, email from sys_user where email = #{email} limit 1
+		select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1
 	</select>
     <select id="selectJgCount" parameterType="SysUser" resultType="HashMap">
 		select count(1) as count,dept_id from sys_user where user_type = #{userType} and jg_id = #{jgId}
@@ -315,7 +315,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	</update>
 
 	<delete id="deleteUserById" parameterType="Long">
-		update sys_user set del_flag = '2' where user_id = #{userId}
+ 		update sys_user set del_flag = '2' where user_id = #{userId}
  	</delete>
 
  	<delete id="deleteUserByIds" parameterType="Long">

+ 1 - 1
ruoyi-ui/babel.config.js

@@ -10,4 +10,4 @@ module.exports = {
       'plugins': ['dynamic-import-node']
     }
   }
-}
+}

+ 7 - 0
ruoyi-ui/src/api/system/logininfor.js

@@ -17,6 +17,13 @@ export function delLogininfor(infoId) {
   })
 }
 
+// 解锁用户登录状态
+export function unlockLogininfor(userName) {
+  return request({
+    url: '/system/logininfor/unlock/' + userName,
+    method: 'get'
+  })
+}
 // 清空登录日志
 export function cleanLogininfor() {
   return request({

+ 29 - 1
ruoyi-ui/src/components/DictData/index.js

@@ -1,7 +1,23 @@
 import Vue from 'vue'
+import store from '@/store'
 import DataDict from '@/utils/dict'
 import { getDicts as getDicts } from '@/api/system/dict/data'
 
+function searchDictByKey(dict, key) {
+  if (key == null && key == "") {
+    return null
+  }
+  try {
+    for (let i = 0; i < dict.length; i++) {
+      if (dict[i].key == key) {
+        return dict[i].value
+      }
+    }
+  } catch (e) {
+    return null
+  }
+}
+
 function install() {
   Vue.use(DataDict, {
     metas: {
@@ -9,7 +25,19 @@ function install() {
         labelField: 'dictLabel',
         valueField: 'dictValue',
         request(dictMeta) {
-          return getDicts(dictMeta.type).then(res => res.data)
+          const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)
+          if (storeDict) {
+            return new Promise(resolve => { resolve(storeDict) })
+          } else {
+            return new Promise((resolve, reject) => {
+              getDicts(dictMeta.type).then(res => {
+                store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })
+                resolve(res.data)
+              }).catch(error => {
+                reject(error)
+              })
+            })
+          }
         },
       },
     },

+ 3 - 40
ruoyi-ui/src/components/RightPanel/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
+  <div ref="rightPanel" class="rightPanel-container">
     <div class="rightPanel-background" />
     <div class="rightPanel">
       <div class="rightPanel-items">
@@ -10,18 +10,12 @@
 </template>
 
 <script>
-import { addClass, removeClass } from '@/utils'
-
 export default {
   name: 'RightPanel',
   props: {
     clickNotClose: {
       default: false,
       type: Boolean
-    },
-    buttonTop: {
-      default: 250,
-      type: Number
     }
   },
   computed: {
@@ -35,21 +29,13 @@ export default {
           value: val
         })
       }
-    },
-    theme() {
-      return this.$store.state.settings.theme
-    },
+    }
   },
   watch: {
     show(value) {
       if (value && !this.clickNotClose) {
         this.addEventClick()
       }
-      if (value) {
-        addClass(document.body, 'showRightPanel')
-      } else {
-        removeClass(document.body, 'showRightPanel')
-      }
     }
   },
   mounted() {
@@ -65,7 +51,7 @@ export default {
       window.addEventListener('click', this.closeSidebar)
     },
     closeSidebar(evt) {
-      const parent = evt.target.closest('.rightPanel')
+      const parent = evt.target.closest('.el-drawer__body')
       if (!parent) {
         this.show = false
         window.removeEventListener('click', this.closeSidebar)
@@ -80,14 +66,6 @@ export default {
 }
 </script>
 
-<style>
-.showRightPanel {
-  overflow: hidden;
-  position: relative;
-  width: calc(100% - 15px);
-}
-</style>
-
 <style lang="scss" scoped>
 .rightPanel-background {
   position: fixed;
@@ -113,21 +91,6 @@ export default {
   z-index: 40000;
 }
 
-.show {
-  transition: all .3s cubic-bezier(.7, .3, .1, 1);
-
-  .rightPanel-background {
-    z-index: 20000;
-    opacity: 1;
-    width: 100%;
-    height: 100%;
-  }
-
-  .rightPanel {
-    transform: translate(0);
-  }
-}
-
 .handle-button {
   width: 48px;
   height: 48px;

+ 19 - 2
ruoyi-ui/src/components/RightToolbar/index.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="top-right-btn">
+  <div class="top-right-btn" :style="style">
     <el-row>
-      <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
+      <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
         <el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" />
       </el-tooltip>
       <el-tooltip class="item" effect="dark" content="刷新" placement="top">
@@ -42,6 +42,23 @@ export default {
     columns: {
       type: Array,
     },
+    search: {
+      type: Boolean,
+      default: true,
+    },
+    gutter: {
+      type: Number,
+      default: 10,
+    },
+  },
+  computed: {
+    style() {
+      const ret = {};
+      if (this.gutter) {
+        ret.marginRight = `${this.gutter / 2}px`;
+      }
+      return ret;
+    }
   },
   created() {
     // 显隐列初始默认隐藏列

+ 63 - 60
ruoyi-ui/src/layout/components/Settings/index.vue

@@ -1,78 +1,76 @@
 <template>
-  <div class="drawer-container">
-    <div>
-      <div class="setting-drawer-content">
-        <div class="setting-drawer-title">
-          <h3 class="drawer-title">主题风格设置</h3>
-        </div>
-        <div class="setting-drawer-block-checbox">
-          <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
-            <img src="@/assets/images/dark.svg" alt="dark">
-            <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
-              <i aria-label="图标: check" class="anticon anticon-check">
-                <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true"
-                     focusable="false" class="">
-                  <path
-                    d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
-                </svg>
-              </i>
-            </div>
+  <el-drawer size="280px" :visible="visible" :with-header="false" :append-to-body="true" :show-close="false">
+    <div class="drawer-container">
+      <div>
+        <div class="setting-drawer-content">
+          <div class="setting-drawer-title">
+            <h3 class="drawer-title">主题风格设置</h3>
           </div>
-          <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
-            <img src="@/assets/images/light.svg" alt="light">
-            <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
-              <i aria-label="图标: check" class="anticon anticon-check">
-                <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true"
-                     focusable="false" class="">
-                  <path
-                    d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
-                </svg>
-              </i>
+          <div class="setting-drawer-block-checbox">
+            <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
+              <img src="@/assets/images/dark.svg" alt="dark">
+              <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
+                <i aria-label="图标: check" class="anticon anticon-check">
+                  <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class="">
+                    <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
+                  </svg>
+                </i>
+              </div>
+            </div>
+            <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
+              <img src="@/assets/images/light.svg" alt="light">
+              <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
+                <i aria-label="图标: check" class="anticon anticon-check">
+                  <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class="">
+                    <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
+                  </svg>
+                </i>
+              </div>
             </div>
           </div>
-        </div>
 
-        <div class="drawer-item">
-          <span>主题颜色</span>
-          <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
+          <div class="drawer-item">
+            <span>主题颜色</span>
+            <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
+          </div>
         </div>
-      </div>
 
-      <el-divider/>
+        <el-divider/>
 
-      <h3 class="drawer-title">系统布局配置</h3>
+        <h3 class="drawer-title">系统布局配置</h3>
       
-      <div class="drawer-item">
-        <span>开启 TopNav</span>
-        <el-switch v-model="topNav" class="drawer-switch" />
-      </div>
+        <div class="drawer-item">
+          <span>开启 TopNav</span>
+          <el-switch v-model="topNav" class="drawer-switch" />
+        </div>
 
-      <div class="drawer-item">
-        <span>开启 Tags-Views</span>
-        <el-switch v-model="tagsView" class="drawer-switch" />
-      </div>
+        <div class="drawer-item">
+          <span>开启 Tags-Views</span>
+          <el-switch v-model="tagsView" class="drawer-switch" />
+        </div>
 
-      <div class="drawer-item">
-        <span>固定 Header</span>
-        <el-switch v-model="fixedHeader" class="drawer-switch" />
-      </div>
+        <div class="drawer-item">
+          <span>固定 Header</span>
+          <el-switch v-model="fixedHeader" class="drawer-switch" />
+        </div>
 
-      <div class="drawer-item">
-        <span>显示 Logo</span>
-        <el-switch v-model="sidebarLogo" class="drawer-switch" />
-      </div>
+        <div class="drawer-item">
+          <span>显示 Logo</span>
+          <el-switch v-model="sidebarLogo" class="drawer-switch" />
+        </div>
 
-      <div class="drawer-item">
-        <span>动态标题</span>
-        <el-switch v-model="dynamicTitle" class="drawer-switch" />
-      </div>
+        <div class="drawer-item">
+          <span>动态标题</span>
+          <el-switch v-model="dynamicTitle" class="drawer-switch" />
+        </div>
 
-      <el-divider/>
+        <el-divider/>
 
-      <el-button size="small" type="primary" plain icon="el-icon-document-add" @click="saveSetting">保存配置</el-button>
-      <el-button size="small" plain icon="el-icon-refresh" @click="resetSetting">重置配置</el-button>
+        <el-button size="small" type="primary" plain icon="el-icon-document-add" @click="saveSetting">保存配置</el-button>
+        <el-button size="small" plain icon="el-icon-refresh" @click="resetSetting">重置配置</el-button>
+      </div>
     </div>
-  </div>
+  </el-drawer>
 </template>
 
 <script>
@@ -87,6 +85,11 @@ export default {
     };
   },
   computed: {
+    visible: {
+      get() {
+        return this.$store.state.settings.showSettings
+      }
+    },
     fixedHeader: {
       get() {
         return this.$store.state.settings.fixedHeader
@@ -232,7 +235,7 @@ export default {
   }
 
   .drawer-container {
-    padding: 24px;
+    padding: 20px;
     font-size: 14px;
     line-height: 1.5;
     word-wrap: break-word;

+ 1 - 0
ruoyi-ui/src/store/getters.js

@@ -2,6 +2,7 @@ const getters = {
   sidebar: state => state.app.sidebar,
   size: state => state.app.size,
   device: state => state.app.device,
+  dict: state => state.dict.dict,
   visitedViews: state => state.tagsView.visitedViews,
   cachedViews: state => state.tagsView.cachedViews,
   token: state => state.user.token,

+ 2 - 0
ruoyi-ui/src/store/index.js

@@ -1,6 +1,7 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
 import app from './modules/app'
+import dict from './modules/dict'
 import user from './modules/user'
 import tagsView from './modules/tagsView'
 import permission from './modules/permission'
@@ -12,6 +13,7 @@ Vue.use(Vuex)
 const store = new Vuex.Store({
   modules: {
     app,
+    dict,
     user,
     tagsView,
     permission,

+ 50 - 0
ruoyi-ui/src/store/modules/dict.js

@@ -0,0 +1,50 @@
+const state = {
+  dict: new Array()
+}
+const mutations = {
+  SET_DICT: (state, { key, value }) => {
+    if (key !== null && key !== "") {
+      state.dict.push({
+        key: key,
+        value: value
+      })
+    }
+  },
+  REMOVE_DICT: (state, key) => {
+    try {
+      for (let i = 0; i < state.dict.length; i++) {
+        if (state.dict[i].key == key) {
+          state.dict.splice(i, i)
+          return true
+        }
+      }
+    } catch (e) {
+    }
+  },
+  CLEAN_DICT: (state) => {
+    state.dict = new Array()
+  }
+}
+
+const actions = {
+  // 设置字典
+  setDict({ commit }, data) {
+    commit('SET_DICT', data)
+  },
+  // 删除字典
+  removeDict({ commit }, key) {
+    commit('REMOVE_DICT', key)
+  },
+  // 清空字典
+  cleanDict({ commit }) {
+    commit('CLEAN_DICT')
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 2 - 2
ruoyi-ui/src/utils/ruoyi.js

@@ -207,10 +207,10 @@ export function tansParams(params) {
   for (const propName of Object.keys(params)) {
     const value = params[propName];
     var part = encodeURIComponent(propName) + "=";
-    if (value !== null && typeof (value) !== "undefined") {
+    if (value !== null && value !== "" && typeof (value) !== "undefined") {
       if (typeof value === 'object') {
         for (const key of Object.keys(value)) {
-          if (value[key] !== null && typeof (value[key]) !== 'undefined') {
+          if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
             let params = propName + '[' + key + ']';
             var subPart = encodeURIComponent(params) + "=";
             result += subPart + encodeURIComponent(value[key]) + "&";

+ 5 - 5
ruoyi-ui/src/views/login.vue

@@ -28,7 +28,7 @@
           <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
         </el-input>
       </el-form-item>
-      <el-form-item prop="code" v-if="captchaOnOff">
+      <el-form-item prop="code" v-if="captchaEnabled">
         <el-input
           v-model="loginForm.code"
           auto-complete="off"
@@ -97,7 +97,7 @@ export default {
       },
       loading: false,
       // 验证码开关
-      captchaOnOff: true,
+      captchaEnabled: true,
       // 注册开关
       register: false,
       redirect: undefined,
@@ -126,8 +126,8 @@ export default {
   methods: {
     getCode() {
       getCodeImg().then(res => {
-        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
-        if (this.captchaOnOff) {
+        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
+        if (this.captchaEnabled) {
           this.codeUrl = "data:image/gif;base64," + res.img;
           this.loginForm.uuid = res.uuid;
         }
@@ -160,7 +160,7 @@ export default {
             this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
           }).catch(() => {
             this.loading = false;
-            if (this.captchaOnOff) {
+            if (this.captchaEnabled) {
               this.getCode();
             }
           });

+ 5 - 5
ruoyi-ui/src/views/register.vue

@@ -29,7 +29,7 @@
           <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
         </el-input>
       </el-form-item>
-      <el-form-item prop="code" v-if="captchaOnOff">
+      <el-form-item prop="code" v-if="captchaEnabled">
         <el-input
           v-model="registerForm.code"
           auto-complete="off"
@@ -104,7 +104,7 @@ export default {
         code: [{ required: true, trigger: "change", message: "请输入验证码" }]
       },
       loading: false,
-      captchaOnOff: true
+      captchaEnabled: true
     };
   },
   created() {
@@ -113,8 +113,8 @@ export default {
   methods: {
     getCode() {
       getCodeImg().then(res => {
-        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
-        if (this.captchaOnOff) {
+        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
+        if (this.captchaEnabled) {
           this.codeUrl = "data:image/gif;base64," + res.img;
           this.registerForm.uuid = res.uuid;
         }
@@ -134,7 +134,7 @@ export default {
             }).catch(() => {});
           }).catch(() => {
             this.loading = false;
-            if (this.captchaOnOff) {
+            if (this.captchaEnabled) {
               this.getCode();
             }
           })

+ 4 - 1
ruoyi-ui/src/views/system/dict/data.vue

@@ -144,7 +144,7 @@
             <el-option
               v-for="item in listClassOptions"
               :key="item.value"
-              :label="item.label"
+              :label="item.label + '(' + item.value + ')'"
               :value="item.value"
             ></el-option>
           </el-select>
@@ -346,12 +346,14 @@ export default {
         if (valid) {
           if (this.form.dictCode != undefined) {
             updateData(this.form).then(response => {
+              this.$store.dispatch('dict/removeDict', this.queryParams.dictType);
               this.$modal.msgSuccess("修改成功");
               this.open = false;
               this.getList();
             });
           } else {
             addData(this.form).then(response => {
+              this.$store.dispatch('dict/removeDict', this.queryParams.dictType);
               this.$modal.msgSuccess("新增成功");
               this.open = false;
               this.getList();
@@ -368,6 +370,7 @@ export default {
       }).then(() => {
         this.getList();
         this.$modal.msgSuccess("删除成功");
+        this.$store.dispatch('dict/removeDict', this.queryParams.dictType);
       }).catch(() => {});
     },
     /** 导出按钮操作 */

+ 1 - 0
ruoyi-ui/src/views/system/dict/index.vue

@@ -317,6 +317,7 @@ export default {
     handleRefreshCache() {
       refreshCache().then(() => {
         this.$modal.msgSuccess("刷新成功");
+        this.$store.dispatch('dict/cleanDict');
       });
     }
   }

+ 27 - 1
ruoyi-ui/src/views/system/logininfor/index.vue

@@ -62,6 +62,17 @@
           v-hasPermi="['system:logininfor:remove']"
         >清空</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-unlock"
+          size="mini"
+          :disabled="single"
+          @click="handleUnlock"
+          v-hasPermi="['system:logininfor:unlock']"
+        >解锁</el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -104,7 +115,7 @@
 </template>
 
 <script>
-import { list, delLogininfor, cleanLogininfor } from "@/api/system/logininfor";
+import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from "@/api/system/logininfor";
 
 export default {
   name: "Logininfor",
@@ -115,8 +126,12 @@ export default {
       loading: true,
       // 选中数组
       ids: [],
+      // 非单个禁用
+      single: true,
       // 非多个禁用
       multiple: true,
+      // 选择用户名
+      selectName: "",
       // 显示搜索条件
       showSearch: true,
       // 总条数
@@ -166,7 +181,9 @@ export default {
     /** 多选框选中数据 */
     handleSelectionChange(selection) {
       this.ids = selection.map(item => item.infoId)
+      this.single = selection.length!=1
       this.multiple = !selection.length
+      this.selectName = selection.map(item => item.userName);
     },
     /** 排序触发事件 */
     handleSortChange(column, prop, order) {
@@ -193,6 +210,15 @@ export default {
         this.$modal.msgSuccess("清空成功");
       }).catch(() => {});
     },
+    /** 解锁按钮操作 */
+    handleUnlock() {
+      const username = this.selectName;
+      this.$modal.confirm('是否确认解锁用户"' + username + '"数据项?').then(function() {
+        return unlockLogininfor(username);
+      }).then(() => {
+        this.$modal.msgSuccess("用户" + username + "解锁成功");
+      }).catch(() => {});
+    },
     /** 导出按钮操作 */
     handleExport() {
       this.download('system/logininfor/export', {

+ 1 - 1
ruoyi-ui/src/views/system/user/profile/index.vue

@@ -49,7 +49,7 @@
               <userInfo :user="user" />
             </el-tab-pane>
             <el-tab-pane label="修改密码" name="resetPwd">
-              <resetPwd :user="user" />
+              <resetPwd />
             </el-tab-pane>
           </el-tabs>
         </el-card>

+ 1 - 1
ruoyi-ui/src/views/system/user/profile/resetPwd.vue

@@ -7,7 +7,7 @@
       <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/>
     </el-form-item>
     <el-form-item label="确认密码" prop="confirmPassword">
-      <el-input v-model="user.confirmPassword" placeholder="请确认密码" type="password" show-password/>
+      <el-input v-model="user.confirmPassword" placeholder="请确认密码" type="password" show-password/>
     </el-form-item>
     <el-form-item>
       <el-button type="primary" size="mini" @click="submit">保存</el-button>

+ 0 - 0
ruoyi-ui/src/views/tool/build/RightPanel.vue


Some files were not shown because too many files changed in this diff