Browse Source

Merge remote-tracking branch 'origin/dev' into 4.X

# Conflicts:
#	ruoyi-common/pom.xml
疯狂的狮子li 2 years ago
parent
commit
025f3a9bcf
100 changed files with 2021 additions and 201 deletions
  1. 1 1
      .run/ruoyi-monitor-admin.run.xml
  2. 1 1
      .run/ruoyi-server.run.xml
  3. 1 1
      .run/ruoyi-xxl-job-admin.run.xml
  4. 1 1
      README.md
  5. 18 10
      pom.xml
  6. 1 1
      ruoyi-admin/pom.xml
  7. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java
  8. 4 4
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
  9. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java
  10. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
  11. 4 4
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java
  12. 2 4
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  13. 26 11
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
  14. 6 10
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
  15. 5 3
      ruoyi-admin/src/main/resources/application-dev.yml
  16. 5 3
      ruoyi-admin/src/main/resources/application-prod.yml
  17. 20 3
      ruoyi-admin/src/main/resources/application.yml
  18. 7 2
      ruoyi-common/pom.xml
  19. 2 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java
  20. 44 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java
  21. 3 1
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java
  22. 30 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java
  23. 6 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
  24. 8 3
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
  25. 39 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java
  26. 21 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java
  27. 10 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheNames.java
  28. 30 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/TransConstant.java
  29. 0 6
      ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
  30. 75 0
      ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java
  31. 2 2
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/PageQuery.java
  32. 3 3
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
  33. 4 4
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
  34. 2 2
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
  35. 4 4
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
  36. 2 9
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
  37. 7 3
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
  38. 1 2
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
  39. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/DeptService.java
  40. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/OssService.java
  41. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/UserService.java
  42. 41 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/EncryptContext.java
  43. 35 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/IEncryptor.java
  44. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/AbstractEncryptor.java
  45. 69 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/AesEncryptor.java
  46. 48 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/Base64Encryptor.java
  47. 65 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/RsaEncryptor.java
  48. 65 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/Sm2Encryptor.java
  49. 67 0
      ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/Sm4Encryptor.java
  50. 48 0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/AlgorithmType.java
  51. 26 0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/EncodeType.java
  52. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java
  53. 17 0
      ruoyi-common/src/main/java/com/ruoyi/common/helper/DataPermissionHelper.java
  54. 31 33
      ruoyi-common/src/main/java/com/ruoyi/common/helper/LoginHelper.java
  55. 2 0
      ruoyi-common/src/main/java/com/ruoyi/common/jackson/DictDataJsonSerializer.java
  56. 17 0
      ruoyi-common/src/main/java/com/ruoyi/common/translation/TranslationInterface.java
  57. 29 0
      ruoyi-common/src/main/java/com/ruoyi/common/translation/handler/TranslationBeanSerializerModifier.java
  58. 65 0
      ruoyi-common/src/main/java/com/ruoyi/common/translation/handler/TranslationHandler.java
  59. 25 0
      ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/DeptNameTranslationImpl.java
  60. 29 0
      ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/DictTypeTranslationImpl.java
  61. 25 0
      ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/OssUrlTranslationImpl.java
  62. 28 0
      ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/UserNameTranslationImpl.java
  63. 21 1
      ruoyi-common/src/main/java/com/ruoyi/common/utils/BeanCopyUtils.java
  64. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java
  65. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/utils/StreamUtils.java
  66. 62 10
      ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
  67. 2 2
      ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
  68. 1 1
      ruoyi-demo/pom.xml
  69. 55 0
      ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestEncryptController.java
  70. 29 0
      ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestDemoEncrypt.java
  71. 13 0
      ruoyi-demo/src/main/java/com/ruoyi/demo/mapper/TestDemoEncryptMapper.java
  72. 1 1
      ruoyi-extend/pom.xml
  73. 1 1
      ruoyi-extend/ruoyi-monitor-admin/pom.xml
  74. 1 1
      ruoyi-extend/ruoyi-xxl-job-admin/pom.xml
  75. 1 1
      ruoyi-framework/pom.xml
  76. 9 6
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
  77. 67 12
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
  78. 39 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/EncryptorConfig.java
  79. 1 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java
  80. 24 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SaTokenConfig.java
  81. 1 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SwaggerConfig.java
  82. 50 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/TranslationConfig.java
  83. 50 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/EncryptorProperties.java
  84. 0 5
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SwaggerProperties.java
  85. 113 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/encrypt/MybatisDecryptInterceptor.java
  86. 118 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/encrypt/MybatisEncryptInterceptor.java
  87. 41 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/handler/AllUrlHandler.java
  88. 96 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/manager/EncryptorManager.java
  89. 0 2
      ruoyi-framework/src/main/java/com/ruoyi/framework/satoken/dao/PlusSaTokenDao.java
  90. 0 2
      ruoyi-framework/src/main/java/com/ruoyi/framework/satoken/service/SaPermissionImpl.java
  91. 1 1
      ruoyi-generator/pom.xml
  92. 1 1
      ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java
  93. 3 3
      ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
  94. 1 1
      ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
  95. 1 0
      ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
  96. 2 1
      ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
  97. 2 1
      ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
  98. 2 1
      ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm
  99. 3 3
      ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm
  100. 1 1
      ruoyi-job/pom.xml

+ 1 - 1
.run/ruoyi-monitor-admin.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.5.0" />
+        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.6.0" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
       </settings>

+ 1 - 1
.run/ruoyi-server.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-server:4.5.0" />
+        <option name="imageTag" value="ruoyi/ruoyi-server:4.6.0" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
       </settings>

+ 1 - 1
.run/ruoyi-xxl-job-admin.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-xxl-job-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.5.0" />
+        <option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.6.0" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" />
       </settings>

+ 1 - 1
README.md

@@ -4,7 +4,7 @@
 [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE)
 [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
 <br>
-[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.5.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.6.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
 [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.7-blue.svg)]()
 [![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
 [![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()

+ 18 - 10
pom.xml

@@ -6,45 +6,46 @@
 
     <groupId>com.ruoyi</groupId>
     <artifactId>ruoyi-vue-plus</artifactId>
-    <version>4.5.0</version>
+    <version>4.6.0</version>
 
     <name>RuoYi-Vue-Plus</name>
     <url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
     <description>RuoYi-Vue-Plus后台管理系统</description>
 
     <properties>
-        <ruoyi-vue-plus.version>4.5.0</ruoyi-vue-plus.version>
-        <spring-boot.version>2.7.7</spring-boot.version>
+        <ruoyi-vue-plus.version>4.6.0</ruoyi-vue-plus.version>
+        <spring-boot.version>2.7.9</spring-boot.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.8</java.version>
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
         <spring-boot.mybatis>2.2.2</spring-boot.mybatis>
-        <springdoc.version>1.6.14</springdoc.version>
+        <springdoc.version>1.6.15</springdoc.version>
         <poi.version>5.2.3</poi.version>
-        <easyexcel.version>3.1.5</easyexcel.version>
+        <easyexcel.version>3.2.1</easyexcel.version>
         <velocity.version>2.3</velocity.version>
         <satoken.version>1.34.0</satoken.version>
         <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
         <p6spy.version>3.9.1</p6spy.version>
-        <hutool.version>5.8.11</hutool.version>
+        <hutool.version>5.8.15</hutool.version>
         <okhttp.version>4.10.0</okhttp.version>
         <spring-boot-admin.version>2.7.10</spring-boot-admin.version>
-        <redisson.version>3.19.1</redisson.version>
+        <redisson.version>3.20.0</redisson.version>
         <lock4j.version>2.2.3</lock4j.version>
         <dynamic-ds.version>3.5.2</dynamic-ds.version>
         <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
         <xxl-job.version>2.3.1</xxl-job.version>
-        <lombok.version>1.18.24</lombok.version>
+        <lombok.version>1.18.26</lombok.version>
+        <bouncycastle.version>1.72</bouncycastle.version>
 
         <!-- 临时修复 snakeyaml 漏洞 -->
         <snakeyaml.version>1.33</snakeyaml.version>
 
         <!-- OSS 配置 -->
-        <aws-java-sdk-s3.version>1.12.373</aws-java-sdk-s3.version>
+        <aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
         <!-- SMS 配置 -->
         <aliyun.sms.version>2.0.23</aliyun.sms.version>
-        <tencent.sms.version>3.1.660</tencent.sms.version>
+        <tencent.sms.version>3.1.687</tencent.sms.version>
     </properties>
 
     <profiles>
@@ -264,6 +265,13 @@
                 <version>${snakeyaml.version}</version>
             </dependency>
 
+            <!-- 加密包引入 -->
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15to18</artifactId>
+                <version>${bouncycastle.version}</version>
+            </dependency>
+
             <!-- 定时任务 -->
             <dependency>
                 <groupId>com.ruoyi</groupId>

+ 1 - 1
ruoyi-admin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>

+ 2 - 2
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java

@@ -79,7 +79,7 @@ public class SysConfigController extends BaseController {
     @Log(title = "参数管理", businessType = BusinessType.INSERT)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysConfig config) {
-        if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) {
+        if (!configService.checkConfigKeyUnique(config)) {
             return R.fail("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
         }
         configService.insertConfig(config);
@@ -93,7 +93,7 @@ public class SysConfigController extends BaseController {
     @Log(title = "参数管理", businessType = BusinessType.UPDATE)
     @PutMapping
     public R<Void> edit(@Validated @RequestBody SysConfig config) {
-        if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) {
+        if (!configService.checkConfigKeyUnique(config)) {
             return R.fail("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
         }
         configService.updateConfig(config);

+ 4 - 4
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java

@@ -1,7 +1,7 @@
 package com.ruoyi.web.controller.system;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.convert.Convert;
 import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.controller.BaseController;
@@ -49,7 +49,7 @@ public class SysDeptController extends BaseController {
     public R<List<SysDept>> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) {
         List<SysDept> depts = deptService.selectDeptList(new SysDept());
         depts.removeIf(d -> d.getDeptId().equals(deptId)
-            || ArrayUtil.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""));
+            || StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId)));
         return R.ok(depts);
     }
 
@@ -72,7 +72,7 @@ public class SysDeptController extends BaseController {
     @Log(title = "部门管理", businessType = BusinessType.INSERT)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysDept dept) {
-        if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) {
+        if (!deptService.checkDeptNameUnique(dept)) {
             return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
         }
         return toAjax(deptService.insertDept(dept));
@@ -87,7 +87,7 @@ public class SysDeptController extends BaseController {
     public R<Void> edit(@Validated @RequestBody SysDept dept) {
         Long deptId = dept.getDeptId();
         deptService.checkDeptDataScope(deptId);
-        if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) {
+        if (!deptService.checkDeptNameUnique(dept)) {
             return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
         } else if (dept.getParentId().equals(deptId)) {
             return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");

+ 2 - 2
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java

@@ -69,7 +69,7 @@ public class SysDictTypeController extends BaseController {
     @Log(title = "字典类型", businessType = BusinessType.INSERT)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysDictType dict) {
-        if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) {
+        if (!dictTypeService.checkDictTypeUnique(dict)) {
             return R.fail("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
         }
         dictTypeService.insertDictType(dict);
@@ -83,7 +83,7 @@ public class SysDictTypeController extends BaseController {
     @Log(title = "字典类型", businessType = BusinessType.UPDATE)
     @PutMapping
     public R<Void> edit(@Validated @RequestBody SysDictType dict) {
-        if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) {
+        if (!dictTypeService.checkDictTypeUnique(dict)) {
             return R.fail("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
         }
         dictTypeService.updateDictType(dict);

+ 2 - 2
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java

@@ -82,7 +82,7 @@ public class SysMenuController extends BaseController {
     @Log(title = "菜单管理", businessType = BusinessType.INSERT)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysMenu menu) {
-        if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
+        if (!menuService.checkMenuNameUnique(menu)) {
             return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
         } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
             return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
@@ -97,7 +97,7 @@ public class SysMenuController extends BaseController {
     @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
     @PutMapping
     public R<Void> edit(@Validated @RequestBody SysMenu menu) {
-        if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
+        if (!menuService.checkMenuNameUnique(menu)) {
             return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
         } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
             return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");

+ 4 - 4
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java

@@ -69,9 +69,9 @@ public class SysPostController extends BaseController {
     @Log(title = "岗位管理", businessType = BusinessType.INSERT)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysPost post) {
-        if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) {
+        if (!postService.checkPostNameUnique(post)) {
             return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
-        } else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) {
+        } else if (!postService.checkPostCodeUnique(post)) {
             return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
         }
         return toAjax(postService.insertPost(post));
@@ -84,9 +84,9 @@ public class SysPostController extends BaseController {
     @Log(title = "岗位管理", businessType = BusinessType.UPDATE)
     @PutMapping
     public R<Void> edit(@Validated @RequestBody SysPost post) {
-        if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) {
+        if (!postService.checkPostNameUnique(post)) {
             return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
-        } else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) {
+        } else if (!postService.checkPostCodeUnique(post)) {
             return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
         }
         return toAjax(postService.updatePost(post));

+ 2 - 4
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java

@@ -58,12 +58,10 @@ public class SysProfileController extends BaseController {
     @Log(title = "个人信息", businessType = BusinessType.UPDATE)
     @PutMapping
     public R<Void> updateProfile(@RequestBody SysUser user) {
-        if (StringUtils.isNotEmpty(user.getPhonenumber())
-            && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) {
+        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
             return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
         }
-        if (StringUtils.isNotEmpty(user.getEmail())
-            && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
+        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
             return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
         }
         user.setUserId(getUserId());

+ 26 - 11
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java

@@ -1,8 +1,11 @@
 package com.ruoyi.web.controller.system;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.hutool.core.util.ObjectUtil;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.collection.CollUtil;
 import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.CacheConstants;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.PageQuery;
@@ -84,9 +87,9 @@ public class SysRoleController extends BaseController {
     @Log(title = "角色管理", businessType = BusinessType.INSERT)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysRole role) {
-        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
+        if (!roleService.checkRoleNameUnique(role)) {
             return R.fail("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
-        } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
+        } else if (!roleService.checkRoleKeyUnique(role)) {
             return R.fail("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
         }
         return toAjax(roleService.insertRole(role));
@@ -102,20 +105,32 @@ public class SysRoleController extends BaseController {
     public R<Void> edit(@Validated @RequestBody SysRole role) {
         roleService.checkRoleAllowed(role);
         roleService.checkRoleDataScope(role.getRoleId());
-        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
+        if (!roleService.checkRoleNameUnique(role)) {
             return R.fail("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
-        } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
+        } else if (!roleService.checkRoleKeyUnique(role)) {
             return R.fail("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
         }
 
         if (roleService.updateRole(role) > 0) {
-            // 更新缓存用户权限
-            LoginUser loginUser = getLoginUser();
-            SysUser sysUser = userService.selectUserById(loginUser.getUserId());
-            if (ObjectUtil.isNotNull(sysUser) && !sysUser.isAdmin()) {
-                loginUser.setMenuPermission(permissionService.getMenuPermission(sysUser));
-                LoginHelper.setLoginUser(loginUser);
+            List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
+            if (CollUtil.isEmpty(keys)) {
+                return R.ok();
             }
+            // 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作
+            keys.parallelStream().forEach(key -> {
+                String token = key.replace(CacheConstants.LOGIN_TOKEN_KEY, "");
+                // 如果已经过期则跳过
+                if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
+                    return;
+                }
+                LoginUser loginUser = LoginHelper.getLoginUser(token);
+                if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(role.getRoleId()))) {
+                    try {
+                        StpUtil.logoutByTokenValue(token);
+                    } catch (NotLoginException ignored) {
+                    }
+                }
+            });
             return R.ok();
         }
         return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");

+ 6 - 10
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java

@@ -136,13 +136,11 @@ public class SysUserController extends BaseController {
     @Log(title = "用户管理", businessType = BusinessType.INSERT)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysUser user) {
-        if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user))) {
+        if (!userService.checkUserNameUnique(user)) {
             return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
-        } else if (StringUtils.isNotEmpty(user.getPhonenumber())
-            && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) {
+        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
             return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
-        } else if (StringUtils.isNotEmpty(user.getEmail())
-            && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
+        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
             return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
         }
         user.setPassword(BCrypt.hashpw(user.getPassword()));
@@ -158,13 +156,11 @@ public class SysUserController extends BaseController {
     public R<Void> edit(@Validated @RequestBody SysUser user) {
         userService.checkUserAllowed(user);
         userService.checkUserDataScope(user.getUserId());
-        if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user))) {
+        if (!userService.checkUserNameUnique(user)) {
             return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
-        } else if (StringUtils.isNotEmpty(user.getPhonenumber())
-            && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) {
+        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
             return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
-        } else if (StringUtils.isNotEmpty(user.getEmail())
-            && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
+        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
             return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
         }
         return toAjax(userService.updateUser(user));

+ 5 - 3
ruoyi-admin/src/main/resources/application-dev.yml

@@ -86,15 +86,17 @@ spring:
         # 最小空闲线程数量
         minIdle: 10
         # 配置获取连接等待超时的时间
-        connectionTimeout: 10000
+        connectionTimeout: 30000
         # 校验超时时间
         validationTimeout: 5000
         # 空闲连接存活最大时间,默认10分钟
-        idleTimeout: 60000
+        idleTimeout: 600000
         # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
-        maxLifetime: 900000
+        maxLifetime: 1800000
         # 连接测试query(配置检测连接是否有效)
         connectionTestQuery: SELECT 1
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
 
 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring:

+ 5 - 3
ruoyi-admin/src/main/resources/application-prod.yml

@@ -89,15 +89,17 @@ spring:
         # 最小空闲线程数量
         minIdle: 10
         # 配置获取连接等待超时的时间
-        connectionTimeout: 10000
+        connectionTimeout: 30000
         # 校验超时时间
         validationTimeout: 5000
         # 空闲连接存活最大时间,默认10分钟
-        idleTimeout: 60000
+        idleTimeout: 600000
         # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
-        maxLifetime: 900000
+        maxLifetime: 1800000
         # 连接测试query(配置检测连接是否有效)
         connectionTestQuery: SELECT 1
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
 
 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring:

+ 20 - 3
ruoyi-admin/src/main/resources/application.yml

@@ -128,8 +128,10 @@ security:
     - /**/*.html
     - /**/*.css
     - /**/*.js
-    # swagger 文档配置
+    # 公共路径
     - /favicon.ico
+    - /error
+    # swagger 文档配置
     - /*/api-docs
     - /*/api-docs/**
     # actuator 监控配置
@@ -180,10 +182,22 @@ mybatis-plus:
       # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
       where-strategy: NOT_NULL
 
+# 数据加密
+mybatis-encryptor:
+  # 是否开启加密
+  enable: false
+  # 默认加密算法
+  algorithm: BASE64
+  # 编码方式 BASE64/HEX。默认BASE64
+  encode: BASE64
+  # 安全秘钥 对称算法的秘钥 如:AES,SM4
+  password:
+  # 公私钥 非对称算法的公私钥 如:SM2,RSA
+  publicKey:
+  privateKey:
+
 # Swagger配置
 swagger:
-  # 是否开启swagger
-  enabled: true
   info:
     # 标题
     title: '标题:${ruoyi.name}后台管理系统_接口文档'
@@ -205,6 +219,9 @@ swagger:
         name: ${sa-token.token-name}
 
 springdoc:
+  api-docs:
+    # 是否开启接口文档
+    enabled: true
   swagger-ui:
     # 持久化认证数据
     persistAuthorization: true

+ 7 - 2
ruoyi-common/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -146,7 +146,6 @@
         <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson-spring-data-27</artifactId>
-            <version>${redisson.version}</version>
         </dependency>
 
         <dependency>
@@ -154,6 +153,12 @@
             <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
         </dependency>
 
+        <!-- 加密包引入 -->
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15to18</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 2 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java

@@ -13,7 +13,9 @@ import java.lang.annotation.Target;
  * 字典数据映射注解
  *
  * @author itino
+ * @deprecated 建议使用通用翻译注解
  */
+@Deprecated
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.FIELD, ElementType.METHOD})
 @JacksonAnnotationsInside

+ 44 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java

@@ -0,0 +1,44 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+import java.lang.annotation.*;
+
+/**
+ * 字段加密注解
+ *
+ * @author 老马
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptField {
+
+    /**
+     * 加密算法
+     */
+    AlgorithmType algorithm() default AlgorithmType.DEFAULT;
+
+    /**
+     * 秘钥。AES、SM4需要
+     */
+    String password() default "";
+
+    /**
+     * 公钥。RSA、SM2需要
+     */
+    String publicKey() default "";
+
+    /**
+     * 公钥。RSA、SM2需要
+     */
+    String privateKey() default "";
+
+    /**
+     * 编码方式。对加密算法为BASE64的不起作用
+     */
+    EncodeType encode() default EncodeType.DEFAULT;
+
+}

+ 3 - 1
ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java

@@ -1,5 +1,7 @@
 package com.ruoyi.common.annotation;
 
+import com.ruoyi.common.utils.StringUtils;
+
 import java.lang.annotation.*;
 
 /**
@@ -25,6 +27,6 @@ public @interface ExcelDictFormat {
     /**
      * 分隔符,读取字符串组内容
      */
-    String separator() default ",";
+    String separator() default StringUtils.SEPARATOR;
 
 }

+ 30 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 枚举格式化
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+    /**
+     * 字典枚举类型
+     */
+    Class<? extends Enum<?>> enumClass();
+
+    /**
+     * 字典枚举类中对应的code属性名称,默认为code
+     */
+    String codeField() default "code";
+
+    /**
+     * 字典枚举类中对应的text属性名称,默认为text
+     */
+    String textField() default "text";
+
+}

+ 6 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java

@@ -38,4 +38,10 @@ public @interface Log {
      * 是否保存响应的参数
      */
     boolean isSaveResponseData() default true;
+
+    /**
+     * 排除指定的请求参数
+     */
+    String[] excludeParamNames() default {};
+
 }

+ 8 - 3
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java

@@ -1,6 +1,5 @@
 package com.ruoyi.common.annotation;
 
-import com.ruoyi.common.constant.CacheConstants;
 import com.ruoyi.common.enums.LimitType;
 
 import java.lang.annotation.*;
@@ -15,9 +14,10 @@ import java.lang.annotation.*;
 @Documented
 public @interface RateLimiter {
     /**
-     * 限流key
+     * 限流key,支持使用Spring el表达式来动态获取方法上的参数值
+     * 格式类似于  #code.id #{#code}
      */
-    String key() default CacheConstants.RATE_LIMIT_KEY;
+    String key() default "";
 
     /**
      * 限流时间,单位秒
@@ -33,4 +33,9 @@ public @interface RateLimiter {
      * 限流类型
      */
     LimitType limitType() default LimitType.DEFAULT;
+
+    /**
+     * 提示消息 支持国际化 格式为 {code}
+     */
+    String message() default "{rate.limiter.message}";
 }

+ 39 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.translation.handler.TranslationHandler;
+
+import java.lang.annotation.*;
+
+/**
+ * 通用翻译注解
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Documented
+@JacksonAnnotationsInside
+@JsonSerialize(using = TranslationHandler.class)
+public @interface Translation {
+
+    /**
+     * 类型 (需与实现类上的 {@link com.ruoyi.common.annotation.TranslationType} 注解type对应)
+     * <p>
+     * 默认取当前字段的值 如果设置了 @{@link Translation#mapper()} 则取映射字段的值
+     */
+    String type();
+
+    /**
+     * 映射字段 (如果不为空则取此字段的值)
+     */
+    String mapper() default "";
+
+    /**
+     * 其他条件 例如: 字典type(sys_user_sex)
+     */
+    String other() default "";
+
+}

+ 21 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 翻译类型注解 (标注到{@link com.ruoyi.common.translation.TranslationInterface} 的实现类)
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+public @interface TranslationType {
+
+    /**
+     * 类型
+     */
+    String type();
+
+}

+ 10 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheNames.java

@@ -31,6 +31,16 @@ public interface CacheNames {
     String SYS_DICT = "sys_dict";
 
     /**
+     * 用户账户
+     */
+    String SYS_USER_NAME = "sys_user_name#30d";
+
+    /**
+     * 部门
+     */
+    String SYS_DEPT = "sys_dept#30d";
+
+    /**
      * OSS内容
      */
     String SYS_OSS = "sys_oss#30d";

+ 30 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/TransConstant.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 翻译常量
+ *
+ * @author Lion Li
+ */
+public interface TransConstant {
+
+    /**
+     * 用户id转账号
+     */
+    String USER_ID_TO_NAME = "user_id_to_name";
+
+    /**
+     * 部门id转名称
+     */
+    String DEPT_ID_TO_NAME = "dept_id_to_name";
+
+    /**
+     * 字典type转label
+     */
+    String DICT_TYPE_TO_LABEL = "dict_type_to_label";
+
+    /**
+     * ossId转url
+     */
+    String OSS_ID_TO_URL = "oss_id_to_url";
+
+}

+ 0 - 6
ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java

@@ -113,12 +113,6 @@ public interface UserConstants {
     String INNER_LINK = "InnerLink";
 
     /**
-     * 校验返回结果码
-     */
-    String UNIQUE = "0";
-    String NOT_UNIQUE = "1";
-
-    /**
      * 用户名长度限制
      */
     int USERNAME_MIN_LENGTH = 2;

+ 75 - 0
ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java

@@ -0,0 +1,75 @@
+package com.ruoyi.common.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import com.ruoyi.common.annotation.ExcelEnumFormat;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 枚举格式化转换处理
+ *
+ * @author Liang
+ */
+@Slf4j
+public class ExcelEnumConvert implements Converter<Object> {
+
+    @Override
+    public Class<Object> supportJavaTypeKey() {
+        return Object.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return null;
+    }
+
+    @Override
+    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        Object codeValue = cellData.getData();
+        // 如果是空值
+        if (ObjectUtil.isNull(codeValue)) {
+            return null;
+        }
+        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
+        String textValue = enumValueMap.get(codeValue);
+        return Convert.convert(contentProperty.getField().getType(), textValue);
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNull(object)) {
+            return new WriteCellData<>("");
+        }
+        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
+        String value = Convert.toStr(enumValueMap.get(object), "");
+        return new WriteCellData<>(value);
+    }
+
+    private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
+        ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
+        Map<Object, String> enumValueMap = new HashMap<>();
+        Enum<?>[] enumConstants = anno.enumClass().getEnumConstants();
+        for (Enum<?> enumConstant : enumConstants) {
+            Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
+            String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
+            enumValueMap.put(codeValue, textValue);
+        }
+        return enumValueMap;
+    }
+
+    private ExcelEnumFormat getAnnotation(Field field) {
+        return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
+    }
+}

+ 2 - 2
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/PageQuery.java

@@ -87,8 +87,8 @@ public class PageQuery implements Serializable {
         // 兼容前端排序类型
         isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
 
-        String[] orderByArr = orderBy.split(",");
-        String[] isAscArr = isAsc.split(",");
+        String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
+        String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
         if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
             throw new ServiceException("排序参数有误");
         }

+ 3 - 3
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java

@@ -34,7 +34,7 @@ public class SysDept extends TreeEntity<SysDept> {
      * 部门名称
      */
     @NotBlank(message = "部门名称不能为空")
-    @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符")
+    @Size(min = 0, max = 30, message = "部门名称长度不能超过{max}个字符")
     private String deptName;
 
     /**
@@ -51,14 +51,14 @@ public class SysDept extends TreeEntity<SysDept> {
     /**
      * 联系电话
      */
-    @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符")
+    @Size(min = 0, max = 11, message = "联系电话长度不能超过{max}个字符")
     private String phone;
 
     /**
      * 邮箱
      */
     @Email(message = "邮箱格式不正确")
-    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
     private String email;
 
     /**

+ 4 - 4
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java

@@ -44,7 +44,7 @@ public class SysDictData extends BaseEntity {
      */
     @ExcelProperty(value = "字典标签")
     @NotBlank(message = "字典标签不能为空")
-    @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "字典标签长度不能超过{max}个字符")
     private String dictLabel;
 
     /**
@@ -52,7 +52,7 @@ public class SysDictData extends BaseEntity {
      */
     @ExcelProperty(value = "字典键值")
     @NotBlank(message = "字典键值不能为空")
-    @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "字典键值长度不能超过{max}个字符")
     private String dictValue;
 
     /**
@@ -60,13 +60,13 @@ public class SysDictData extends BaseEntity {
      */
     @ExcelProperty(value = "字典类型")
     @NotBlank(message = "字典类型不能为空")
-    @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "字典类型长度不能超过{max}个字符")
     private String dictType;
 
     /**
      * 样式属性(其他样式扩展)
      */
-    @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "样式属性长度不能超过{max}个字符")
     private String cssClass;
 
     /**

+ 2 - 2
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java

@@ -38,7 +38,7 @@ public class SysDictType extends BaseEntity {
      */
     @ExcelProperty(value = "字典名称")
     @NotBlank(message = "字典名称不能为空")
-    @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "字典类型名称长度不能超过{max}个字符")
     private String dictName;
 
     /**
@@ -46,7 +46,7 @@ public class SysDictType extends BaseEntity {
      */
     @ExcelProperty(value = "字典类型")
     @NotBlank(message = "字典类型不能为空")
-    @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "字典类型类型长度不能超过{max}个字符")
     @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
     private String dictType;
 

+ 4 - 4
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java

@@ -32,7 +32,7 @@ public class SysMenu extends TreeEntity<SysMenu> {
      * 菜单名称
      */
     @NotBlank(message = "菜单名称不能为空")
-    @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符")
+    @Size(min = 0, max = 50, message = "菜单名称长度不能超过{max}个字符")
     private String menuName;
 
     /**
@@ -44,13 +44,13 @@ public class SysMenu extends TreeEntity<SysMenu> {
     /**
      * 路由地址
      */
-    @Size(min = 0, max = 200, message = "路由地址不能超过200个字符")
+    @Size(min = 0, max = 200, message = "路由地址不能超过{max}个字符")
     private String path;
 
     /**
      * 组件路径
      */
-    @Size(min = 0, max = 200, message = "组件路径不能超过255个字符")
+    @Size(min = 0, max = 200, message = "组件路径不能超过{max}个字符")
     private String component;
 
     /**
@@ -88,7 +88,7 @@ public class SysMenu extends TreeEntity<SysMenu> {
      * 权限字符串
      */
     @JsonInclude(JsonInclude.Include.NON_NULL)
-    @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "权限标识长度不能超过{max}个字符")
     private String perms;
 
     /**

+ 2 - 9
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java

@@ -17,7 +17,6 @@ import lombok.NoArgsConstructor;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
-import java.util.Set;
 
 /**
  * 角色表 sys_role
@@ -44,7 +43,7 @@ public class SysRole extends BaseEntity {
      */
     @ExcelProperty(value = "角色名称")
     @NotBlank(message = "角色名称不能为空")
-    @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符")
+    @Size(min = 0, max = 30, message = "角色名称长度不能超过{max}个字符")
     private String roleName;
 
     /**
@@ -52,7 +51,7 @@ public class SysRole extends BaseEntity {
      */
     @ExcelProperty(value = "角色权限")
     @NotBlank(message = "权限字符不能为空")
-    @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符")
+    @Size(min = 0, max = 100, message = "权限字符长度不能超过{max}个字符")
     private String roleKey;
 
     /**
@@ -115,12 +114,6 @@ public class SysRole extends BaseEntity {
     @TableField(exist = false)
     private Long[] deptIds;
 
-    /**
-     * 角色菜单权限
-     */
-    @TableField(exist = false)
-    private Set<String> permissions;
-
     public SysRole(Long roleId) {
         this.roleId = roleId;
     }

+ 7 - 3
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java

@@ -1,6 +1,8 @@
 package com.ruoyi.common.core.domain.entity;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.ruoyi.common.annotation.Sensitive;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.domain.BaseEntity;
@@ -44,14 +46,14 @@ public class SysUser extends BaseEntity {
      */
     @Xss(message = "用户账号不能包含脚本字符")
     @NotBlank(message = "用户账号不能为空")
-    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符")
     private String userName;
 
     /**
      * 用户昵称
      */
     @Xss(message = "用户昵称不能包含脚本字符")
-    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
     private String nickName;
 
     /**
@@ -64,7 +66,7 @@ public class SysUser extends BaseEntity {
      */
     @Sensitive(strategy = SensitiveStrategy.EMAIL)
     @Email(message = "邮箱格式不正确")
-    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
     private String email;
 
     /**
@@ -91,6 +93,8 @@ public class SysUser extends BaseEntity {
         updateStrategy = FieldStrategy.NOT_EMPTY,
         whereStrategy = FieldStrategy.NOT_EMPTY
     )
+    @JsonIgnore
+    @JsonProperty
     private String password;
 
     /**

+ 1 - 2
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java

@@ -1,7 +1,6 @@
 package com.ruoyi.common.core.domain.model;
 
 import com.ruoyi.common.core.domain.dto.RoleDTO;
-import com.ruoyi.common.helper.LoginHelper;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
@@ -111,7 +110,7 @@ public class LoginUser implements Serializable {
         if (userId == null) {
             throw new IllegalArgumentException("用户ID不能为空");
         }
-        return userType + LoginHelper.JOIN_CODE + userId;
+        return userType + ":" + userId;
     }
 
 }

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/DeptService.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.service;
+
+/**
+ * 通用 部门服务
+ *
+ * @author Lion Li
+ */
+public interface DeptService {
+
+    /**
+     * 通过部门ID查询部门名称
+     *
+     * @param deptIds 部门ID串逗号分隔
+     * @return 部门名称串逗号分隔
+     */
+    String selectDeptNameByIds(String deptIds);
+
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/OssService.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.service;
+
+/**
+ * 通用 OSS服务
+ *
+ * @author Lion Li
+ */
+public interface OssService {
+
+    /**
+     * 通过ossId查询对应的url
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return url串逗号分隔
+     */
+    String selectUrlByIds(String ossIds);
+
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/UserService.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.service;
+
+/**
+ * 通用 用户服务
+ *
+ * @author Lion Li
+ */
+public interface UserService {
+
+    /**
+     * 通过用户ID查询用户账户
+     *
+     * @param userId 用户ID
+     * @return 用户账户
+     */
+    String selectUserNameById(Long userId);
+
+}

+ 41 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/EncryptContext.java

@@ -0,0 +1,41 @@
+package com.ruoyi.common.encrypt;
+
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+import lombok.Data;
+
+/**
+ * 加密上下文 用于encryptor传递必要的参数。
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Data
+public class EncryptContext {
+
+    /**
+     * 默认算法
+     */
+    private AlgorithmType algorithm;
+
+    /**
+     * 安全秘钥
+     */
+    private String password;
+
+    /**
+     * 公钥
+     */
+    private String publicKey;
+
+    /**
+     * 私钥
+     */
+    private String privateKey;
+
+    /**
+     * 编码方式,base64/hex
+     */
+    private EncodeType encode;
+
+}

+ 35 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/IEncryptor.java

@@ -0,0 +1,35 @@
+package com.ruoyi.common.encrypt;
+
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+/**
+ * 加解者
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public interface IEncryptor {
+
+    /**
+     * 获得当前算法
+     */
+    AlgorithmType algorithm();
+
+    /**
+     * 加密
+     *
+     * @param value      待加密字符串
+     * @param encodeType 加密后的编码格式
+     * @return 加密后的字符串
+     */
+    String encrypt(String value, EncodeType encodeType);
+
+    /**
+     * 解密
+     *
+     * @param value      待加密字符串
+     * @return 解密后的字符串
+     */
+    String decrypt(String value);
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/AbstractEncryptor.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.encrypt.encryptor;
+
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.encrypt.IEncryptor;
+
+/**
+ * 所有加密执行者的基类
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public abstract class AbstractEncryptor implements IEncryptor {
+
+    public AbstractEncryptor(EncryptContext context) {
+        // 用户配置校验与配置注入
+    }
+
+}

+ 69 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/AesEncryptor.java

@@ -0,0 +1,69 @@
+package com.ruoyi.common.encrypt.encryptor;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.symmetric.AES;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * AES算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class AesEncryptor extends AbstractEncryptor {
+
+    private final AES aes;
+
+    public AesEncryptor(EncryptContext context) {
+        super(context);
+        String password = context.getPassword();
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("AES没有获得秘钥信息");
+        }
+        // aes算法的秘钥要求是16位、24位、32位
+        int[] array = {16, 24, 32};
+        if (!ArrayUtil.contains(array, password.length())) {
+            throw new IllegalArgumentException("AES秘钥长度应该为16位、24位、32位,实际为" + password.length() + "位");
+        }
+        aes = SecureUtil.aes(context.getPassword().getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * 获得当前算法
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.AES;
+    }
+
+    /**
+     * 加密
+     *
+     * @param value      待加密字符串
+     * @param encodeType 加密后的编码格式
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return aes.encryptHex(value);
+        } else {
+            return aes.encryptBase64(value);
+        }
+    }
+
+    /**
+     * 解密
+     *
+     * @param value      待加密字符串
+     */
+    @Override
+    public String decrypt(String value) {
+        return this.aes.decryptStr(value);
+    }
+}

+ 48 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/Base64Encryptor.java

@@ -0,0 +1,48 @@
+package com.ruoyi.common.encrypt.encryptor;
+
+import cn.hutool.core.codec.Base64;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+/**
+ * Base64算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class Base64Encryptor extends AbstractEncryptor {
+
+    public Base64Encryptor(EncryptContext context) {
+        super(context);
+    }
+
+    /**
+     * 获得当前算法
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.BASE64;
+    }
+
+    /**
+     * 加密
+     *
+     * @param value      待加密字符串
+     * @param encodeType 加密后的编码格式
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        return Base64.encode(value);
+    }
+
+    /**
+     * 解密
+     *
+     * @param value      待加密字符串
+     */
+    @Override
+    public String decrypt(String value) {
+        return Base64.decodeStr(value);
+    }
+}

+ 65 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/RsaEncryptor.java

@@ -0,0 +1,65 @@
+package com.ruoyi.common.encrypt.encryptor;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+import com.ruoyi.common.utils.StringUtils;
+
+
+/**
+ * RSA算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class RsaEncryptor extends AbstractEncryptor {
+
+    private final RSA rsa;
+
+    public RsaEncryptor(EncryptContext context) {
+        super(context);
+        String privateKey = context.getPrivateKey();
+        String publicKey = context.getPublicKey();
+        if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+            throw new IllegalArgumentException("RSA公私钥均需要提供,公钥加密,私钥解密。");
+        }
+        this.rsa = SecureUtil.rsa(Base64.decode(privateKey), Base64.decode(publicKey));
+    }
+
+    /**
+     * 获得当前算法
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.RSA;
+    }
+
+    /**
+     * 加密
+     *
+     * @param value      待加密字符串
+     * @param encodeType 加密后的编码格式
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return rsa.encryptHex(value, KeyType.PublicKey);
+        } else {
+            return rsa.encryptBase64(value, KeyType.PublicKey);
+        }
+    }
+
+    /**
+     * 解密
+     *
+     * @param value      待加密字符串
+     */
+    @Override
+    public String decrypt(String value) {
+        return this.rsa.decryptStr(value, KeyType.PrivateKey);
+    }
+}

+ 65 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/Sm2Encryptor.java

@@ -0,0 +1,65 @@
+package com.ruoyi.common.encrypt.encryptor;
+
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.crypto.SmUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.SM2;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * sm2算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class Sm2Encryptor extends AbstractEncryptor {
+
+    private final SM2 sm2;
+
+    public Sm2Encryptor(EncryptContext context) {
+        super(context);
+        String privateKey = context.getPrivateKey();
+        String publicKey = context.getPublicKey();
+        if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+            throw new IllegalArgumentException("SM2公私钥均需要提供,公钥加密,私钥解密。");
+        }
+        this.sm2 = SmUtil.sm2(Base64.decode(privateKey), Base64.decode(publicKey));
+    }
+
+    /**
+     * 获得当前算法
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.SM2;
+    }
+
+    /**
+     * 加密
+     *
+     * @param value      待加密字符串
+     * @param encodeType 加密后的编码格式
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return sm2.encryptHex(value, KeyType.PublicKey);
+        } else {
+            return sm2.encryptBase64(value, KeyType.PublicKey);
+        }
+    }
+
+    /**
+     * 解密
+     *
+     * @param value      待加密字符串
+     */
+    @Override
+    public String decrypt(String value) {
+        return this.sm2.decryptStr(value, KeyType.PrivateKey);
+    }
+}

+ 67 - 0
ruoyi-common/src/main/java/com/ruoyi/common/encrypt/encryptor/Sm4Encryptor.java

@@ -0,0 +1,67 @@
+package com.ruoyi.common.encrypt.encryptor;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SmUtil;
+import cn.hutool.crypto.symmetric.SM4;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * sm4算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class Sm4Encryptor extends AbstractEncryptor {
+
+    private final SM4 sm4;
+
+    public Sm4Encryptor(EncryptContext context) {
+        super(context);
+        String password = context.getPassword();
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("SM4没有获得秘钥信息");
+        }
+        // sm4算法的秘钥要求是16位长度
+        if (16 != password.length()) {
+            throw new IllegalArgumentException("SM4秘钥长度应该为16位,实际为" + password.length() + "位");
+        }
+        this.sm4 = SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * 获得当前算法
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.SM4;
+    }
+
+    /**
+     * 加密
+     *
+     * @param value      待加密字符串
+     * @param encodeType 加密后的编码格式
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return sm4.encryptHex(value);
+        } else {
+            return sm4.encryptBase64(value);
+        }
+    }
+
+    /**
+     * 解密
+     *
+     * @param value      待加密字符串
+     */
+    @Override
+    public String decrypt(String value) {
+        return this.sm4.decryptStr(value);
+    }
+}

+ 48 - 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/AlgorithmType.java

@@ -0,0 +1,48 @@
+package com.ruoyi.common.enums;
+
+import com.ruoyi.common.encrypt.encryptor.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 算法名称
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Getter
+@AllArgsConstructor
+public enum AlgorithmType {
+
+    /**
+     * 默认走yml配置
+     */
+    DEFAULT(null),
+
+    /**
+     * base64
+     */
+    BASE64(Base64Encryptor.class),
+
+    /**
+     * aes
+     */
+    AES(AesEncryptor.class),
+
+    /**
+     * rsa
+     */
+    RSA(RsaEncryptor.class),
+
+    /**
+     * sm2
+     */
+    SM2(Sm2Encryptor.class),
+
+    /**
+     * sm4
+     */
+    SM4(Sm4Encryptor.class);
+
+    private final Class<? extends AbstractEncryptor> clazz;
+}

+ 26 - 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/EncodeType.java

@@ -0,0 +1,26 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 编码类型
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public enum EncodeType {
+
+    /**
+     * 默认使用yml配置
+     */
+    DEFAULT,
+
+    /**
+     * base64编码
+     */
+    BASE64,
+
+    /**
+     * 16进制编码
+     */
+    HEX;
+
+}

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

@@ -25,7 +25,7 @@ public class XssFilter implements Filter {
     public void init(FilterConfig filterConfig) throws ServletException {
         String tempExcludes = filterConfig.getInitParameter("excludes");
         if (StringUtils.isNotEmpty(tempExcludes)) {
-            String[] url = tempExcludes.split(",");
+            String[] url = tempExcludes.split(StringUtils.SEPARATOR);
             for (int i = 0; url != null && i < url.length; i++) {
                 excludes.add(url[i]);
             }

+ 17 - 0
ruoyi-common/src/main/java/com/ruoyi/common/helper/DataPermissionHelper.java

@@ -3,6 +3,8 @@ package com.ruoyi.common.helper;
 import cn.dev33.satoken.context.SaHolder;
 import cn.dev33.satoken.context.model.SaStorage;
 import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
@@ -44,4 +46,19 @@ public class DataPermissionHelper {
         }
         throw new NullPointerException("data permission context type exception");
     }
+
+    /**
+     * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭)
+     */
+    public static void enableIgnore() {
+        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
+    }
+
+    /**
+     * 关闭忽略数据权限
+     */
+    public static void disableIgnore() {
+        InterceptorIgnoreHelper.clearIgnoreStrategy();
+    }
+
 }

+ 31 - 33
ruoyi-common/src/main/java/com/ruoyi/common/helper/LoginHelper.java

@@ -1,25 +1,25 @@
 package com.ruoyi.common.helper;
 
 import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
-import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.enums.DeviceType;
 import com.ruoyi.common.enums.UserType;
-import com.ruoyi.common.exception.UtilException;
-import com.ruoyi.common.utils.StringUtils;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
 /**
  * 登录鉴权助手
- *
+ * <p>
  * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app
  * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios
  * 可以组成 用户类型与设备类型多对多的 权限灵活控制
- *
+ * <p>
  * 多用户体系 针对 多种用户类型 但权限控制不一致
  * 可以组成 多用户类型表与多设备类型 分别控制权限
  *
@@ -28,8 +28,8 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class LoginHelper {
 
-    public static final String JOIN_CODE = ":";
     public static final String LOGIN_USER_KEY = "loginUser";
+    public static final String USER_KEY = "userId";
 
     /**
      * 登录系统
@@ -37,9 +37,7 @@ public class LoginHelper {
      * @param loginUser 登录用户信息
      */
     public static void login(LoginUser loginUser) {
-        SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
-        StpUtil.login(loginUser.getLoginId());
-        setLoginUser(loginUser);
+        loginByDevice(loginUser, null);
     }
 
     /**
@@ -49,15 +47,14 @@ public class LoginHelper {
      * @param loginUser 登录用户信息
      */
     public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
-        SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
-        StpUtil.login(loginUser.getLoginId(), deviceType.getDevice());
-        setLoginUser(loginUser);
-    }
-
-    /**
-     * 设置用户数据(多级缓存)
-     */
-    public static void setLoginUser(LoginUser loginUser) {
+        SaStorage storage = SaHolder.getStorage();
+        storage.set(LOGIN_USER_KEY, loginUser);
+        storage.set(USER_KEY, loginUser.getUserId());
+        SaLoginModel model = new SaLoginModel();
+        if (ObjectUtil.isNotNull(deviceType)) {
+            model.setDevice(deviceType.getDevice());
+        }
+        StpUtil.login(loginUser.getLoginId(), model.setExtra(USER_KEY, loginUser.getUserId()));
         StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
     }
 
@@ -75,26 +72,27 @@ public class LoginHelper {
     }
 
     /**
+     * 获取用户基于token
+     */
+    public static LoginUser getLoginUser(String token) {
+        return (LoginUser) StpUtil.getTokenSessionByToken(token).get(LOGIN_USER_KEY);
+    }
+
+    /**
      * 获取用户id
      */
     public static Long getUserId() {
-        LoginUser loginUser = getLoginUser();
-        if (ObjectUtil.isNull(loginUser)) {
-            String loginId = StpUtil.getLoginIdAsString();
-            String userId = null;
-            for (UserType value : UserType.values()) {
-                if (StringUtils.contains(loginId, value.getUserType())) {
-                    String[] strs = StringUtils.split(loginId, JOIN_CODE);
-                    // 用户id在总是在最后
-                    userId = strs[strs.length - 1];
-                }
-            }
-            if (StringUtils.isBlank(userId)) {
-                throw new UtilException("登录用户: LoginId异常 => " + loginId);
+        Long userId;
+        try {
+            userId = Convert.toLong(SaHolder.getStorage().get(USER_KEY));
+            if (ObjectUtil.isNull(userId)) {
+                userId = Convert.toLong(StpUtil.getExtra(USER_KEY));
+                SaHolder.getStorage().set(USER_KEY, userId);
             }
-            return Long.parseLong(userId);
+        } catch (Exception e) {
+            return null;
         }
-        return loginUser.getUserId();
+        return userId;
     }
 
     /**

+ 2 - 0
ruoyi-common/src/main/java/com/ruoyi/common/jackson/DictDataJsonSerializer.java

@@ -22,7 +22,9 @@ import java.util.Objects;
  * 字典数据json序列化工具
  *
  * @author itino
+ * @deprecated 建议使用通用翻译注解
  */
+@Deprecated
 @Slf4j
 public class DictDataJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
 

+ 17 - 0
ruoyi-common/src/main/java/com/ruoyi/common/translation/TranslationInterface.java

@@ -0,0 +1,17 @@
+package com.ruoyi.common.translation;
+
+/**
+ * 翻译接口 (实现类需标注 {@link com.ruoyi.common.annotation.TranslationType} 注解标明翻译类型)
+ *
+ * @author Lion Li
+ */
+public interface TranslationInterface<T> {
+
+    /**
+     * 翻译
+     *
+     * @param key 需要被翻译的键(不为空)
+     * @return 返回键对应的值
+     */
+    T translation(Object key, String other);
+}

+ 29 - 0
ruoyi-common/src/main/java/com/ruoyi/common/translation/handler/TranslationBeanSerializerModifier.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.translation.handler;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+
+import java.util.List;
+
+/**
+ * Bean 序列化修改器 解决 Null 被单独处理问题
+ *
+ * @author Lion Li
+ */
+public class TranslationBeanSerializerModifier extends BeanSerializerModifier {
+
+    @Override
+    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
+                                                     List<BeanPropertyWriter> beanProperties) {
+        for (BeanPropertyWriter writer : beanProperties) {
+            // 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理
+            if (writer.getSerializer() instanceof TranslationHandler) {
+                writer.assignNullSerializer(writer.getSerializer());
+            }
+        }
+        return beanProperties;
+    }
+
+}

+ 65 - 0
ruoyi-common/src/main/java/com/ruoyi/common/translation/handler/TranslationHandler.java

@@ -0,0 +1,65 @@
+package com.ruoyi.common.translation.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.ruoyi.common.annotation.Translation;
+import com.ruoyi.common.translation.TranslationInterface;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 翻译处理器
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class TranslationHandler extends JsonSerializer<Object> implements ContextualSerializer {
+
+    /**
+     * 全局翻译实现类映射器
+     */
+    public static final Map<String, TranslationInterface<?>> TRANSLATION_MAPPER = new ConcurrentHashMap<>();
+
+    private Translation translation;
+
+    @Override
+    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        TranslationInterface<?> trans = TRANSLATION_MAPPER.get(translation.type());
+        if (ObjectUtil.isNotNull(trans)) {
+            // 如果映射字段不为空 则取映射字段的值
+            if (StringUtils.isNotBlank(translation.mapper())) {
+                value = ReflectUtils.invokeGetter(gen.getCurrentValue(), translation.mapper());
+            }
+            // 如果为 null 直接写出
+            if (ObjectUtil.isNull(value)) {
+                gen.writeNull();
+                return;
+            }
+            Object result = trans.translation(value, translation.other());
+            gen.writeObject(result);
+        } else {
+            gen.writeObject(value);
+        }
+    }
+
+    @Override
+    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
+        Translation translation = property.getAnnotation(Translation.class);
+        if (Objects.nonNull(translation)) {
+            this.translation = translation;
+            return this;
+        }
+        return prov.findValueSerializer(property.getType(), property);
+    }
+}

+ 25 - 0
ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/DeptNameTranslationImpl.java

@@ -0,0 +1,25 @@
+package com.ruoyi.common.translation.impl;
+
+import com.ruoyi.common.annotation.TranslationType;
+import com.ruoyi.common.constant.TransConstant;
+import com.ruoyi.common.core.service.DeptService;
+import com.ruoyi.common.translation.TranslationInterface;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+/**
+ * 部门翻译实现
+ *
+ * @author Lion Li
+ */
+@Component
+@AllArgsConstructor
+@TranslationType(type = TransConstant.DEPT_ID_TO_NAME)
+public class DeptNameTranslationImpl implements TranslationInterface<String> {
+
+    private final DeptService deptService;
+
+    public String translation(Object key, String other) {
+        return deptService.selectDeptNameByIds(key.toString());
+    }
+}

+ 29 - 0
ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/DictTypeTranslationImpl.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.translation.impl;
+
+import com.ruoyi.common.annotation.TranslationType;
+import com.ruoyi.common.constant.TransConstant;
+import com.ruoyi.common.core.service.DictService;
+import com.ruoyi.common.translation.TranslationInterface;
+import com.ruoyi.common.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字典翻译实现
+ *
+ * @author Lion Li
+ */
+@Component
+@AllArgsConstructor
+@TranslationType(type = TransConstant.DICT_TYPE_TO_LABEL)
+public class DictTypeTranslationImpl implements TranslationInterface<String> {
+
+    private final DictService dictService;
+
+    public String translation(Object key, String other) {
+        if (key instanceof String && StringUtils.isNotBlank(other)) {
+            return dictService.getDictLabel(other, key.toString());
+        }
+        return null;
+    }
+}

+ 25 - 0
ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/OssUrlTranslationImpl.java

@@ -0,0 +1,25 @@
+package com.ruoyi.common.translation.impl;
+
+import com.ruoyi.common.annotation.TranslationType;
+import com.ruoyi.common.constant.TransConstant;
+import com.ruoyi.common.core.service.OssService;
+import com.ruoyi.common.translation.TranslationInterface;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+/**
+ * OSS翻译实现
+ *
+ * @author Lion Li
+ */
+@Component
+@AllArgsConstructor
+@TranslationType(type = TransConstant.OSS_ID_TO_URL)
+public class OssUrlTranslationImpl implements TranslationInterface<String> {
+
+    private final OssService ossService;
+
+    public String translation(Object key, String other) {
+        return ossService.selectUrlByIds(key.toString());
+    }
+}

+ 28 - 0
ruoyi-common/src/main/java/com/ruoyi/common/translation/impl/UserNameTranslationImpl.java

@@ -0,0 +1,28 @@
+package com.ruoyi.common.translation.impl;
+
+import com.ruoyi.common.annotation.TranslationType;
+import com.ruoyi.common.constant.TransConstant;
+import com.ruoyi.common.core.service.UserService;
+import com.ruoyi.common.translation.TranslationInterface;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+/**
+ * 用户名翻译实现
+ *
+ * @author Lion Li
+ */
+@Component
+@AllArgsConstructor
+@TranslationType(type = TransConstant.USER_ID_TO_NAME)
+public class UserNameTranslationImpl implements TranslationInterface<String> {
+
+    private final UserService userService;
+
+    public String translation(Object key, String other) {
+        if (key instanceof Long) {
+            return userService.selectUserNameById((Long) key);
+        }
+        return null;
+    }
+}

+ 21 - 1
ruoyi-common/src/main/java/com/ruoyi/common/utils/BeanCopyUtils.java

@@ -12,6 +12,7 @@ import org.springframework.cglib.beans.BeanCopier;
 import org.springframework.cglib.beans.BeanMap;
 import org.springframework.cglib.core.Converter;
 
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -136,6 +137,25 @@ public class BeanCopyUtils {
     }
 
     /**
+     * map拷贝到map
+     *
+     * @param map   数据来源
+     * @param clazz 返回的对象类型
+     * @return map对象
+     */
+    public static <T, V> Map<String, V> mapToMap(Map<String, T> map, Class<V> clazz) {
+        if (MapUtil.isEmpty(map)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(clazz)) {
+            return null;
+        }
+        Map<String, V> copyMap = new LinkedHashMap<>(map.size());
+        map.forEach((k, v) -> copyMap.put(k, copy(v, clazz)));
+        return copyMap;
+    }
+
+    /**
      * BeanCopier属性缓存<br>
      * 缓存用于防止多次反射造成的性能问题
      *
@@ -174,7 +194,7 @@ public class BeanCopyUtils {
         private String genKey(Class<?> srcClass, Class<?> targetClass, Converter converter) {
             final StringBuilder key = StrUtil.builder()
                 .append(srcClass.getName()).append('#').append(targetClass.getName());
-            if(null != converter){
+            if (null != converter) {
                 key.append('#').append(converter.getClass().getName());
             }
             return key.toString();

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

@@ -94,7 +94,7 @@ public class ServletUtils extends ServletUtil {
     public static Map<String, String> getParamMap(ServletRequest request) {
         Map<String, String> params = new HashMap<>();
         for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
-            params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
+            params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
         }
         return params;
     }

+ 1 - 1
ruoyi-common/src/main/java/com/ruoyi/common/utils/StreamUtils.java

@@ -41,7 +41,7 @@ public class StreamUtils {
      * @return 拼接后的list
      */
     public static <E> String join(Collection<E> collection, Function<E, String> function) {
-        return join(collection, function, ",");
+        return join(collection, function, StringUtils.SEPARATOR);
     }
 
     /**

+ 62 - 10
ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java

@@ -1,16 +1,16 @@
 package com.ruoyi.common.utils;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Validator;
 import cn.hutool.core.util.StrUtil;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.springframework.util.AntPathMatcher;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 字符串工具类
@@ -20,6 +20,8 @@ import java.util.Set;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class StringUtils extends org.apache.commons.lang3.StringUtils {
 
+    public static final String SEPARATOR = ",";
+
     /**
      * 获取参数不为空值
      *
@@ -224,7 +226,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
      *
      * @param pattern 匹配规则
      * @param url     需要匹配的url
-     * @return
      */
     public static boolean isMatch(String pattern, String url) {
         AntPathMatcher matcher = new AntPathMatcher();
@@ -234,23 +235,23 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
     /**
      * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
      *
-     * @param num 数字对象
+     * @param num  数字对象
      * @param size 字符串指定长度
      * @return 返回数字的字符串格式,该字符串为指定长度。
      */
-    public static final String padl(final Number num, final int size) {
+    public static String padl(final Number num, final int size) {
         return padl(num.toString(), size, '0');
     }
 
     /**
      * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
      *
-     * @param s 原始字符串
+     * @param s    原始字符串
      * @param size 字符串指定长度
-     * @param c 用于补齐的字符
+     * @param c    用于补齐的字符
      * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
      */
-    public static final String padl(final String s, final int size, final char c) {
+    public static String padl(final String s, final int size, final char c) {
         final StringBuilder sb = new StringBuilder(size);
         if (s != null) {
             final int len = s.length();
@@ -270,4 +271,55 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
         return sb.toString();
     }
 
+    /**
+     * 切分字符串(分隔符默认逗号)
+     *
+     * @param str 被切分的字符串
+     * @return 分割后的数据列表
+     */
+    public static List<String> splitList(String str) {
+        return splitTo(str, Convert::toStr);
+    }
+
+    /**
+     * 切分字符串
+     *
+     * @param str       被切分的字符串
+     * @param separator 分隔符
+     * @return 分割后的数据列表
+     */
+    public static List<String> splitList(String str, String separator) {
+        return splitTo(str, separator, Convert::toStr);
+    }
+
+    /**
+     * 切分字符串自定义转换(分隔符默认逗号)
+     *
+     * @param str    被切分的字符串
+     * @param mapper 自定义转换
+     * @return 分割后的数据列表
+     */
+    public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) {
+        return splitTo(str, SEPARATOR, mapper);
+    }
+
+    /**
+     * 切分字符串自定义转换
+     *
+     * @param str       被切分的字符串
+     * @param separator 分隔符
+     * @param mapper    自定义转换
+     * @return 分割后的数据列表
+     */
+    public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) {
+        if (isBlank(str)) {
+            return new ArrayList<>(0);
+        }
+        return StrUtil.split(str, separator)
+            .stream()
+            .filter(Objects::nonNull)
+            .map(mapper)
+            .collect(Collectors.toList());
+    }
+
 }

+ 2 - 2
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java

@@ -270,7 +270,7 @@ public class ExcelUtil {
      */
     public static String convertByExp(String propertyValue, String converterExp, String separator) {
         StringBuilder propertyString = new StringBuilder();
-        String[] convertSource = converterExp.split(",");
+        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
         for (String item : convertSource) {
             String[] itemArray = item.split("=");
             if (StringUtils.containsAny(propertyValue, separator)) {
@@ -299,7 +299,7 @@ public class ExcelUtil {
      */
     public static String reverseByExp(String propertyValue, String converterExp, String separator) {
         StringBuilder propertyString = new StringBuilder();
-        String[] convertSource = converterExp.split(",");
+        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
         for (String item : convertSource) {
             String[] itemArray = item.split("=");
             if (StringUtils.containsAny(propertyValue, separator)) {

+ 1 - 1
ruoyi-demo/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 55 - 0
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestEncryptController.java

@@ -0,0 +1,55 @@
+package com.ruoyi.demo.controller;
+
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.demo.domain.TestDemoEncrypt;
+import com.ruoyi.demo.mapper.TestDemoEncryptMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * 测试数据库加解密功能
+ *
+ * @author Lion Li
+ */
+@Validated
+@RestController
+@RequestMapping("/demo/encrypt")
+public class TestEncryptController {
+
+    @Autowired
+    private TestDemoEncryptMapper mapper;
+    @Value("${mybatis-encryptor.enable}")
+    private Boolean encryptEnable;
+
+    /**
+     * 测试数据库加解密
+     *
+     * @param key   测试key
+     * @param value 测试value
+     */
+    @GetMapping()
+    public R<Map<String, TestDemoEncrypt>> test(String key, String value) {
+        if (!encryptEnable) {
+            throw new RuntimeException("加密功能未开启!");
+        }
+        Map<String, TestDemoEncrypt> map = new HashMap<>(2);
+        TestDemoEncrypt demo = new TestDemoEncrypt();
+        demo.setTestKey(key);
+        demo.setValue(value);
+        mapper.insert(demo);
+        map.put("加密", demo);
+        TestDemoEncrypt testDemo = mapper.selectById(demo.getId());
+        map.put("解密", testDemo);
+        return R.ok(map);
+    }
+
+
+}

+ 29 - 0
ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestDemoEncrypt.java

@@ -0,0 +1,29 @@
+package com.ruoyi.demo.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.annotation.EncryptField;
+import com.ruoyi.common.enums.AlgorithmType;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("test_demo")
+public class TestDemoEncrypt extends TestDemo {
+
+    /**
+     * key键
+     */
+    // @EncryptField(algorithm=AlgorithmType.SM2, privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgZSlOvw8FBiH+aFJWLYZP/VRjg9wjfRarTkGBZd/T3N+gCgYIKoEcz1UBgi2hRANCAAR5DGuQwJqkxnbCsP+iPSDoHWIF4RwcR5EsSvT8QPxO1wRkR2IhCkzvRb32x2CUgJFdvoqVqfApFDPZzShqzBwX", publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEeQxrkMCapMZ2wrD/oj0g6B1iBeEcHEeRLEr0/ED8TtcEZEdiIQpM70W99sdglICRXb6KlanwKRQz2c0oaswcFw==")
+    @EncryptField(algorithm = AlgorithmType.RSA, privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANBBEeueWlXlkkj2+WY5l+IWe42d8b5K28g+G/CFKC/yYAEHtqGlCsBOrb+YBkG9mPzmuYA/n9k0NFIc8E8yY5vZQaroyFBrTTWEzG9RY2f7Y3svVyybs6jpXSUs4xff8abo7wL1Y/wUaeatTViamxYnyTvdTmLm3d+JjRij68rxAgMBAAECgYAB0TnhXraSopwIVRfmboea1b0upl+BUdTJcmci412UjrKr5aE695ZLPkXbFXijVu7HJlyyv94NVUdaMACV7Ku/S2RuNB70M7YJm8rAjHFC3/i2ZeIM60h1Ziy4QKv0XM3pRATlDCDNhC1WUrtQCQSgU8kcp6eUUppruOqDzcY04QJBAPm9+sBP9CwDRgy3e5+V8aZtJkwDstb0lVVV/KY890cydVxiCwvX3fqVnxKMlb+x0YtH0sb9v+71xvK2lGobaRECQQDVePU6r/cCEfpc+nkWF6osAH1f8Mux3rYv2DoBGvaPzV2BGfsLed4neRfCwWNCKvGPCdW+L0xMJg8+RwaoBUPhAkAT5kViqXxFPYWJYd1h2+rDXhMdH3ZSlm6HvDBDdrwlWinr0Iwcx3iSjPV93uHXwm118aUj4fg3LDJMCKxOwBxhAkByrQXfvwOMYygBprRBf/j0plazoWFrbd6lGR0f1uI5IfNnFRPdeFw1DEINZ2Hw+6zEUF44SqRMC+4IYJNc02dBAkBCgy7RvfyV/A7N6kKXxTHauY0v6XwSSvpeKtRJkbIcRWOdIYvaHO9L7cklj3vIEdwjSUp9K4VTBYYlmAz1xh03", publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQQRHrnlpV5ZJI9vlmOZfiFnuNnfG+StvIPhvwhSgv8mABB7ahpQrATq2/mAZBvZj85rmAP5/ZNDRSHPBPMmOb2UGq6MhQa001hMxvUWNn+2N7L1csm7Oo6V0lLOMX3/Gm6O8C9WP8FGnmrU1YmpsWJ8k73U5i5t3fiY0Yo+vK8QIDAQAB")
+    private String testKey;
+
+    /**
+     * 值
+     */
+    // @EncryptField // 什么也不写走默认yml配置
+    // @EncryptField(algorithm = AlgorithmType.SM4, password = "10rfylhtccpuyke5")
+    @EncryptField(algorithm = AlgorithmType.AES, password = "10rfylhtccpuyke5")
+    private String value;
+
+}

+ 13 - 0
ruoyi-demo/src/main/java/com/ruoyi/demo/mapper/TestDemoEncryptMapper.java

@@ -0,0 +1,13 @@
+package com.ruoyi.demo.mapper;
+
+import com.ruoyi.common.core.mapper.BaseMapperPlus;
+import com.ruoyi.demo.domain.TestDemoEncrypt;
+
+/**
+ * 测试加密功能
+ *
+ * @author Lion Li
+ */
+public interface TestDemoEncryptMapper extends BaseMapperPlus<TestDemoEncryptMapper, TestDemoEncrypt, TestDemoEncrypt> {
+
+}

+ 1 - 1
ruoyi-extend/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>ruoyi-extend</artifactId>

+ 1 - 1
ruoyi-extend/ruoyi-monitor-admin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-extend</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>

+ 1 - 1
ruoyi-extend/ruoyi-xxl-job-admin/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <artifactId>ruoyi-extend</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <artifactId>ruoyi-xxl-job-admin</artifactId>
     <packaging>jar</packaging>

+ 1 - 1
ruoyi-framework/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 9 - 6
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java

@@ -112,7 +112,7 @@ public class LogAspect {
         // 是否需要保存request,参数和值
         if (log.isSaveRequestData()) {
             // 获取参数的信息,传入到数据库中。
-            setRequestValue(joinPoint, operLog);
+            setRequestValue(joinPoint, operLog, log.excludeParamNames());
         }
         // 是否需要保存response,参数和值
         if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
@@ -126,14 +126,16 @@ public class LogAspect {
      * @param operLog 操作日志
      * @throws Exception 异常
      */
-    private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog) throws Exception {
+    private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {
+        Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
         String requestMethod = operLog.getRequestMethod();
-        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
-            String params = argsArrayToString(joinPoint.getArgs());
+        if (MapUtil.isEmpty(paramsMap)
+            && HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
+            String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
             operLog.setOperParam(StringUtils.substring(params, 0, 2000));
         } else {
-            Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
             MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
+            MapUtil.removeAny(paramsMap, excludeParamNames);
             operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));
         }
     }
@@ -141,7 +143,7 @@ public class LogAspect {
     /**
      * 参数拼装
      */
-    private String argsArrayToString(Object[] paramsArray) {
+    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
         StringBuilder params = new StringBuilder();
         if (paramsArray != null && paramsArray.length > 0) {
             for (Object o : paramsArray) {
@@ -151,6 +153,7 @@ public class LogAspect {
                         Dict dict = JsonUtils.parseMap(str);
                         if (MapUtil.isNotEmpty(dict)) {
                             MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
+                            MapUtil.removeAny(dict, excludeParamNames);
                             str = JsonUtils.toJsonString(dict);
                         }
                         params.append(str).append(" ");

+ 67 - 12
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java

@@ -1,10 +1,13 @@
 package com.ruoyi.framework.aspectj;
 
+import cn.hutool.core.util.ArrayUtil;
 import com.ruoyi.common.annotation.RateLimiter;
+import com.ruoyi.common.constant.CacheConstants;
 import com.ruoyi.common.enums.LimitType;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.MessageUtils;
 import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.redis.RedisUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
@@ -12,6 +15,14 @@ import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
 import org.aspectj.lang.reflect.MethodSignature;
 import org.redisson.api.RateType;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.ParserContext;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.stereotype.Component;
 
 import java.lang.reflect.Method;
@@ -26,6 +37,23 @@ import java.lang.reflect.Method;
 @Component
 public class RateLimiterAspect {
 
+    /**
+     * 定义spel表达式解析器
+     */
+    private final ExpressionParser parser = new SpelExpressionParser();
+    /**
+     * 定义spel解析模版
+     */
+    private final ParserContext parserContext = new TemplateParserContext();
+    /**
+     * 定义spel上下文对象进行解析
+     */
+    private final EvaluationContext context = new StandardEvaluationContext();
+    /**
+     * 方法参数解析器
+     */
+    private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
+
     @Before("@annotation(rateLimiter)")
     public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
         int time = rateLimiter.time();
@@ -38,29 +66,56 @@ public class RateLimiterAspect {
             }
             long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
             if (number == -1) {
-                throw new ServiceException(MessageUtils.message("rate.limiter.message"));
+                String message = rateLimiter.message();
+                if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
+                    message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
+                }
+                throw new ServiceException(message);
             }
             log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
-        } catch (ServiceException e) {
-            throw e;
         } catch (Exception e) {
-            throw new RuntimeException("服务器限流异常,请稍候再试");
+            if (e instanceof ServiceException) {
+                throw e;
+            } else {
+                throw new RuntimeException("服务器限流异常,请稍候再试");
+            }
         }
     }
 
     public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
-        StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
+        String key = rateLimiter.key();
+        // 获取方法(通过方法签名来获取)
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        // 判断是否是spel格式
+        if (StringUtils.containsAny(key, "#")) {
+            // 获取参数值
+            Object[] args = point.getArgs();
+            // 获取方法上参数的名称
+            String[] parameterNames = pnd.getParameterNames(method);
+            if (ArrayUtil.isEmpty(parameterNames)) {
+                throw new ServiceException("限流key解析异常!请联系管理员!");
+            }
+            for (int i = 0; i < parameterNames.length; i++) {
+                context.setVariable(parameterNames[i], args[i]);
+            }
+            // 解析返回给key
+            try {
+                key = parser.parseExpression(key, parserContext).getValue(context, String.class) + ":";
+            } catch (Exception e) {
+                throw new ServiceException("限流key解析异常!请联系管理员!");
+            }
+        }
+        StringBuilder stringBuffer = new StringBuilder(CacheConstants.RATE_LIMIT_KEY);
+        stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
         if (rateLimiter.limitType() == LimitType.IP) {
             // 获取请求ip
-            stringBuffer.append(ServletUtils.getClientIP()).append("-");
+            stringBuffer.append(ServletUtils.getClientIP()).append(":");
         } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
             // 获取客户端实例id
-            stringBuffer.append(RedisUtils.getClient().getId()).append("-");
+            stringBuffer.append(RedisUtils.getClient().getId()).append(":");
         }
-        MethodSignature signature = (MethodSignature) point.getSignature();
-        Method method = signature.getMethod();
-        Class<?> targetClass = method.getDeclaringClass();
-        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
-        return stringBuffer.toString();
+        return stringBuffer.append(key).toString();
     }
 }

+ 39 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/EncryptorConfig.java

@@ -0,0 +1,39 @@
+package com.ruoyi.framework.config;
+
+import com.ruoyi.framework.config.properties.EncryptorProperties;
+import com.ruoyi.framework.manager.EncryptorManager;
+import com.ruoyi.framework.encrypt.MybatisDecryptInterceptor;
+import com.ruoyi.framework.encrypt.MybatisEncryptInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 加解密配置
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Configuration
+@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true")
+public class EncryptorConfig {
+
+    @Autowired
+    private EncryptorProperties properties;
+
+    @Bean
+    public EncryptorManager encryptorManager() {
+        return new EncryptorManager();
+    }
+
+    @Bean
+    public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) {
+        return new MybatisEncryptInterceptor(encryptorManager, properties);
+    }
+
+    @Bean
+    public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) {
+        return new MybatisDecryptInterceptor(encryptorManager, properties);
+    }
+}

+ 1 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java

@@ -32,7 +32,7 @@ public class FilterConfig {
         FilterRegistrationBean registration = new FilterRegistrationBean();
         registration.setDispatcherTypes(DispatcherType.REQUEST);
         registration.setFilter(new XssFilter());
-        registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), ","));
+        registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
         registration.setName("xssFilter");
         registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
         Map<String, String> initParameters = new HashMap<String, String>();

+ 24 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SaTokenConfig.java

@@ -1,11 +1,17 @@
 package com.ruoyi.framework.config;
 
+import cn.dev33.satoken.dao.SaTokenDao;
 import cn.dev33.satoken.interceptor.SaInterceptor;
 import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
 import cn.dev33.satoken.router.SaRouter;
+import cn.dev33.satoken.stp.StpInterface;
 import cn.dev33.satoken.stp.StpLogic;
 import cn.dev33.satoken.stp.StpUtil;
+import com.ruoyi.common.utils.spring.SpringUtils;
 import com.ruoyi.framework.config.properties.SecurityProperties;
+import com.ruoyi.framework.handler.AllUrlHandler;
+import com.ruoyi.framework.satoken.dao.PlusSaTokenDao;
+import com.ruoyi.framework.satoken.service.SaPermissionImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Bean;
@@ -32,10 +38,11 @@ public class SaTokenConfig implements WebMvcConfigurer {
     public void addInterceptors(InterceptorRegistry registry) {
         // 注册路由拦截器,自定义验证规则
         registry.addInterceptor(new SaInterceptor(handler -> {
+            AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
             // 登录验证 -- 排除多个路径
             SaRouter
                 // 获取所有的
-                .match("/**")
+                .match(allUrlHandler.getUrls())
                 // 对未排除的路径进行检查
                 .check(() -> {
                     // 检查是否登录 是否有token
@@ -59,4 +66,20 @@ public class SaTokenConfig implements WebMvcConfigurer {
         return new StpLogicJwtForSimple();
     }
 
+    /**
+     * 权限接口实现(使用bean注入方便用户替换)
+     */
+    @Bean
+    public StpInterface stpInterface() {
+        return new SaPermissionImpl();
+    }
+
+    /**
+     * 自定义dao层存储
+     */
+    @Bean
+    public SaTokenDao saTokenDao() {
+        return new PlusSaTokenDao();
+    }
+
 }

+ 1 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SwaggerConfig.java

@@ -33,7 +33,7 @@ import java.util.Set;
 @RequiredArgsConstructor
 @Configuration
 @AutoConfigureBefore(SpringDocConfiguration.class)
-@ConditionalOnProperty(name = "swagger.enabled", havingValue = "true", matchIfMissing = true)
+@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
 public class SwaggerConfig {
 
     private final SwaggerProperties swaggerProperties;

+ 50 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/TranslationConfig.java

@@ -0,0 +1,50 @@
+package com.ruoyi.framework.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.annotation.TranslationType;
+import com.ruoyi.common.translation.TranslationInterface;
+import com.ruoyi.common.translation.handler.TranslationBeanSerializerModifier;
+import com.ruoyi.common.translation.handler.TranslationHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 翻译模块配置类
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Configuration
+public class TranslationConfig {
+
+    @Autowired
+    private List<TranslationInterface<?>> list;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @PostConstruct
+    public void init() {
+        Map<String, TranslationInterface<?>> map = new HashMap<>(list.size());
+        for (TranslationInterface<?> trans : list) {
+            if (trans.getClass().isAnnotationPresent(TranslationType.class)) {
+                TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class);
+                map.put(annotation.type(), trans);
+            } else {
+                log.warn(trans.getClass().getName() + " 翻译实现类未标注 TranslationType 注解!");
+            }
+        }
+        TranslationHandler.TRANSLATION_MAPPER.putAll(map);
+        // 设置 Bean 序列化修改器
+        objectMapper.setSerializerFactory(
+            objectMapper.getSerializerFactory()
+                .withSerializerModifier(new TranslationBeanSerializerModifier()));
+    }
+
+}

+ 50 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/EncryptorProperties.java

@@ -0,0 +1,50 @@
+package com.ruoyi.framework.config.properties;
+
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 加解密属性配置类
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "mybatis-encryptor")
+public class EncryptorProperties {
+
+    /**
+     * 过滤开关
+     */
+    private Boolean enable;
+
+    /**
+     * 默认算法
+     */
+    private AlgorithmType algorithm;
+
+    /**
+     * 安全秘钥
+     */
+    private String password;
+
+    /**
+     * 公钥
+     */
+    private String publicKey;
+
+    /**
+     * 私钥
+     */
+    private String privateKey;
+
+    /**
+     * 编码方式,base64/hex
+     */
+    private EncodeType encode;
+
+}

+ 0 - 5
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SwaggerProperties.java

@@ -24,11 +24,6 @@ import java.util.List;
 public class SwaggerProperties {
 
     /**
-     * 是否开启 openApi 文档
-     */
-    private Boolean enabled = true;
-
-    /**
      * 文档基本信息
      */
     @NestedConfigurationProperty

+ 113 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/encrypt/MybatisDecryptInterceptor.java

@@ -0,0 +1,113 @@
+package com.ruoyi.framework.encrypt;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.EncryptField;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.config.properties.EncryptorProperties;
+import com.ruoyi.framework.manager.EncryptorManager;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.resultset.ResultSetHandler;
+import org.apache.ibatis.plugin.*;
+
+import java.lang.reflect.Field;
+import java.sql.Statement;
+import java.util.*;
+
+/**
+ * 出参解密拦截器
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+    type = ResultSetHandler.class,
+    method = "handleResultSets",
+    args = {Statement.class})
+})
+@AllArgsConstructor
+public class MybatisDecryptInterceptor implements Interceptor {
+
+    private final EncryptorManager encryptorManager;
+    private final EncryptorProperties defaultProperties;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        // 获取执行mysql执行结果
+        Object result = invocation.proceed();
+        if (result == null) {
+            return null;
+        }
+        decryptHandler(result);
+        return result;
+    }
+
+    /**
+     * 解密对象
+     *
+     * @param sourceObject 待加密对象
+     */
+    private void decryptHandler(Object sourceObject) {
+        if (ObjectUtil.isNull(sourceObject)) {
+            return;
+        }
+        if (sourceObject instanceof Map<?, ?>) {
+            new HashSet<>(((Map<?, ?>) sourceObject).values()).forEach(this::decryptHandler);
+            return;
+        }
+        if (sourceObject instanceof List<?>) {
+            List<?> sourceList = (List<?>) sourceObject;
+            if(CollectionUtil.isEmpty(sourceList)) {
+                return;
+            }
+            // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
+            Object firstItem = sourceList.get(0);
+            if (CollectionUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+                return;
+            }
+            ((List<?>) sourceObject).forEach(this::decryptHandler);
+            return;
+        }
+        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
+        try {
+            for (Field field : fields) {
+                field.set(sourceObject, this.decryptField(String.valueOf(field.get(sourceObject)), field));
+            }
+        } catch (Exception e) {
+            log.error("处理解密字段时出错", e);
+        }
+    }
+
+    /**
+     * 字段值进行加密。通过字段的批注注册新的加密算法
+     *
+     * @param value 待加密的值
+     * @param field 待加密字段
+     * @return 加密后结果
+     */
+    private String decryptField(String value, Field field) {
+        EncryptField encryptField = field.getAnnotation(EncryptField.class);
+        EncryptContext encryptContext = new EncryptContext();
+        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+        encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+        encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+        encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+        encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+        return this.encryptorManager.decrypt(value, encryptContext);
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        return Plugin.wrap(target, this);
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+
+    }
+}

+ 118 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/encrypt/MybatisEncryptInterceptor.java

@@ -0,0 +1,118 @@
+package com.ruoyi.framework.encrypt;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.EncryptField;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.config.properties.EncryptorProperties;
+import com.ruoyi.framework.manager.EncryptorManager;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.parameter.ParameterHandler;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+
+import java.lang.reflect.Field;
+import java.sql.PreparedStatement;
+import java.util.*;
+
+/**
+ * 入参加密拦截器
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+    type = ParameterHandler.class,
+    method = "setParameters",
+    args = {PreparedStatement.class})
+})
+@AllArgsConstructor
+public class MybatisEncryptInterceptor implements Interceptor {
+
+    private final EncryptorManager encryptorManager;
+    private final EncryptorProperties defaultProperties;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        return invocation;
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        if (target instanceof ParameterHandler) {
+            // 进行加密操作
+            ParameterHandler parameterHandler = (ParameterHandler) target;
+            Object parameterObject = parameterHandler.getParameterObject();
+            if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
+                this.encryptHandler(parameterObject);
+            }
+        }
+        return target;
+    }
+
+    /**
+     * 加密对象
+     *
+     * @param sourceObject 待加密对象
+     */
+    private void encryptHandler(Object sourceObject) {
+        if (ObjectUtil.isNull(sourceObject)) {
+            return;
+        }
+        if (sourceObject instanceof Map<?, ?>) {
+            new HashSet<>(((Map<?, ?>) sourceObject).values()).forEach(this::encryptHandler);
+            return;
+        }
+        if (sourceObject instanceof List<?>) {
+            List<?> sourceList = (List<?>) sourceObject;
+            if(CollectionUtil.isEmpty(sourceList)) {
+                return;
+            }
+            // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
+            Object firstItem = sourceList.get(0);
+            if (CollectionUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+                return;
+            }
+            ((List<?>) sourceObject).forEach(this::encryptHandler);
+            return;
+        }
+        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
+        try {
+            for (Field field : fields) {
+                field.set(sourceObject, this.encryptField(String.valueOf(field.get(sourceObject)), field));
+            }
+        } catch (Exception e) {
+            log.error("处理加密字段时出错", e);
+        }
+    }
+
+    /**
+     * 字段值进行加密。通过字段的批注注册新的加密算法
+     *
+     * @param value 待加密的值
+     * @param field 待加密字段
+     * @return 加密后结果
+     */
+    private String encryptField(String value, Field field) {
+        EncryptField encryptField = field.getAnnotation(EncryptField.class);
+        EncryptContext encryptContext = new EncryptContext();
+        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+        encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+        encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+        encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+        encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+        return this.encryptorManager.encrypt(value, encryptContext);
+    }
+
+
+    @Override
+    public void setProperties(Properties properties) {
+    }
+}

+ 41 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/handler/AllUrlHandler.java

@@ -0,0 +1,41 @@
+package com.ruoyi.framework.handler;
+
+import cn.hutool.core.util.ReUtil;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import lombok.Data;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * 获取所有Url配置
+ *
+ * @author Lion Li
+ */
+@Data
+@Component
+public class AllUrlHandler implements InitializingBean {
+
+    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
+
+    private List<String> urls = new ArrayList<>();
+
+    @Override
+    public void afterPropertiesSet() {
+        Set<String> set = new HashSet<>();
+        RequestMappingHandlerMapping mapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
+        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
+        map.keySet().forEach(info -> {
+            // 获取注解上边的 path 替代 path variable 为 *
+            Objects.requireNonNull(info.getPathPatternsCondition().getPatterns())
+                .forEach(url -> set.add(ReUtil.replaceAll(url.getPatternString(), PATTERN, "*")));
+        });
+        urls.addAll(set);
+    }
+
+}

+ 96 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/manager/EncryptorManager.java

@@ -0,0 +1,96 @@
+package com.ruoyi.framework.manager;
+
+import cn.hutool.core.util.ReflectUtil;
+import com.ruoyi.common.annotation.EncryptField;
+import com.ruoyi.common.encrypt.EncryptContext;
+import com.ruoyi.common.encrypt.IEncryptor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * 加密管理类
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Slf4j
+public class EncryptorManager {
+
+    /**
+     * 缓存加密器
+     */
+    Map<EncryptContext, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
+
+    /**
+     * 类加密字段缓存
+     */
+    Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();
+
+    /**
+     * 获取类加密字段缓存
+     */
+    public Set<Field> getFieldCache(Class<?> sourceClazz) {
+        return fieldCache.computeIfAbsent(sourceClazz, clazz -> {
+            Field[] declaredFields = clazz.getDeclaredFields();
+            Set<Field> fieldSet = Arrays.stream(declaredFields).filter(field ->
+                    field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
+                .collect(Collectors.toSet());
+            for (Field field : fieldSet) {
+                field.setAccessible(true);
+            }
+            return fieldSet;
+        });
+    }
+
+    /**
+     * 注册加密执行者到缓存
+     *
+     * @param encryptContext 加密执行者需要的相关配置参数
+     */
+    public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
+        if (encryptorMap.containsKey(encryptContext)) {
+            return encryptorMap.get(encryptContext);
+        }
+        IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
+        encryptorMap.put(encryptContext, encryptor);
+        return encryptor;
+    }
+
+    /**
+     * 移除缓存中的加密执行者
+     *
+     * @param encryptContext 加密执行者需要的相关配置参数
+     */
+    public void removeEncryptor(EncryptContext encryptContext) {
+        this.encryptorMap.remove(encryptContext);
+    }
+
+    /**
+     * 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。
+     *
+     * @param value          待加密的值
+     * @param encryptContext 加密相关的配置信息
+     */
+    public String encrypt(String value, EncryptContext encryptContext) {
+        IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+        return encryptor.encrypt(value, encryptContext.getEncode());
+    }
+
+    /**
+     * 根据配置进行解密
+     *
+     * @param value          待解密的值
+     * @param encryptContext 加密相关的配置信息
+     */
+    public String decrypt(String value, EncryptContext encryptContext) {
+        IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+        return encryptor.decrypt(value);
+    }
+
+}

+ 0 - 2
ruoyi-framework/src/main/java/com/ruoyi/framework/satoken/dao/PlusSaTokenDao.java

@@ -3,7 +3,6 @@ package com.ruoyi.framework.satoken.dao;
 import cn.dev33.satoken.dao.SaTokenDao;
 import cn.dev33.satoken.util.SaFoxUtil;
 import com.ruoyi.common.utils.redis.RedisUtils;
-import org.springframework.stereotype.Component;
 
 import java.time.Duration;
 import java.util.ArrayList;
@@ -15,7 +14,6 @@ import java.util.List;
  *
  * @author Lion Li
  */
-@Component
 public class PlusSaTokenDao implements SaTokenDao {
 
     /**

+ 0 - 2
ruoyi-framework/src/main/java/com/ruoyi/framework/satoken/service/SaPermissionImpl.java

@@ -4,7 +4,6 @@ import cn.dev33.satoken.stp.StpInterface;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.enums.UserType;
 import com.ruoyi.common.helper.LoginHelper;
-import org.springframework.stereotype.Component;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -14,7 +13,6 @@ import java.util.List;
  *
  * @author Lion Li
  */
-@Component
 public class SaPermissionImpl implements StpInterface {
 
     /**

+ 1 - 1
ruoyi-generator/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java

@@ -212,7 +212,7 @@ public class GenTableColumn extends BaseEntity {
                 if (StringUtils.isNotEmpty(value)) {
                     Object startStr = value.subSequence(0, 1);
                     String endStr = value.substring(1);
-                    sb.append("").append(startStr).append("=").append(endStr).append(",");
+                    sb.append(StringUtils.EMPTY).append(startStr).append("=").append(endStr).append(StringUtils.SEPARATOR);
                 }
             }
             return sb.deleteCharAt(sb.length() - 1).toString();

+ 3 - 3
ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java

@@ -58,7 +58,7 @@ public class GenUtils {
             column.setHtmlType(GenConstants.HTML_INPUT);
 
             // 如果是浮点型 统一用BigDecimal
-            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
+            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), StringUtils.SEPARATOR);
             if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
                 column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
             }
@@ -167,7 +167,7 @@ public class GenUtils {
         boolean autoRemovePre = GenConfig.getAutoRemovePre();
         String tablePrefix = GenConfig.getTablePrefix();
         if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
-            String[] searchList = StringUtils.split(tablePrefix, ",");
+            String[] searchList = StringUtils.split(tablePrefix, StringUtils.SEPARATOR);
             tableName = replaceFirst(tableName, searchList);
         }
         return StringUtils.convertToCamelCase(tableName);
@@ -184,7 +184,7 @@ public class GenUtils {
         String text = replacementm;
         for (String searchString : searchList) {
             if (replacementm.startsWith(searchString)) {
-                text = replacementm.replaceFirst(searchString, "");
+                text = replacementm.replaceFirst(searchString, StringUtils.EMPTY);
                 break;
             }
         }

+ 1 - 1
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java

@@ -232,7 +232,7 @@ public class VelocityUtils {
     public static HashSet<String> getImportList(GenTable genTable) {
         List<GenTableColumn> columns = genTable.getColumns();
         GenTable subGenTable = genTable.getSubTable();
-        HashSet<String> importList = new HashSet<String>();
+        HashSet<String> importList = new HashSet<>();
         if (ObjectUtil.isNotNull(subGenTable)) {
             importList.add("java.util.List");
         }

+ 1 - 0
ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml

@@ -93,6 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 ORDER BY c.relname, a.attnum
             ) temp
             WHERE table_name = (#{tableName})
+                AND column_type <![CDATA[ <> ]]> '-'
         </if>
         <if test="@com.ruoyi.common.helper.DataBaseHelper@isSqlServer()">
             SELECT

+ 2 - 1
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm

@@ -51,11 +51,12 @@
         <el-date-picker
           v-model="daterange${AttrName}"
           style="width: 240px"
-          value-format="yyyy-MM-dd"
+          value-format="yyyy-MM-dd HH:mm:ss"
           type="daterange"
           range-separator="-"
           start-placeholder="开始日期"
           end-placeholder="结束日期"
+          :default-time="['00:00:00', '23:59:59']"
         ></el-date-picker>
       </el-form-item>
 #end

+ 2 - 1
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm

@@ -51,11 +51,12 @@
         <el-date-picker
           v-model="daterange${AttrName}"
           style="width: 240px"
-          value-format="yyyy-MM-dd"
+          value-format="yyyy-MM-dd HH:mm:ss"
           type="daterange"
           range-separator="-"
           start-placeholder="开始日期"
           end-placeholder="结束日期"
+          :default-time="['00:00:00', '23:59:59']"
         ></el-date-picker>
       </el-form-item>
 #end

+ 2 - 1
ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm

@@ -50,11 +50,12 @@
       <el-form-item label="${comment}" style="width: 308px">
         <el-date-picker
           v-model="daterange${AttrName}"
-          value-format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD HH:mm:ss"
           type="daterange"
           range-separator="-"
           start-placeholder="开始日期"
           end-placeholder="结束日期"
+          :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
         ></el-date-picker>
       </el-form-item>
 #end

+ 3 - 3
ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm

@@ -50,11 +50,11 @@
       <el-form-item label="${comment}" style="width: 308px">
         <el-date-picker
           v-model="daterange${AttrName}"
-          value-format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD HH:mm:ss"
           type="daterange"
           range-separator="-"
           start-placeholder="开始日期"
-          end-placeholder="结束日期"
+          :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
         ></el-date-picker>
       </el-form-item>
 #end
@@ -540,7 +540,7 @@ function submitForm() {
 /** 删除按钮操作 */
 function handleDelete(row) {
   const _${pkColumn.javaField}s = row.${pkColumn.javaField} || ids.value;
-  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(function() {
+  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?').then(function() {
     loading.value = true;
     return del${BusinessName}(_${pkColumn.javaField}s);
   }).then(() => {

+ 1 - 1
ruoyi-job/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.5.0</version>
+        <version>4.6.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>

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