浏览代码

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

疯狂的狮子li 3 年之前
父节点
当前提交
0700c60636
共有 100 个文件被更改,包括 1566 次插入804 次删除
  1. 1 1
      .gitee/ISSUE_TEMPLATE.zh-CN.md
  2. 5 2
      README.md
  3. 43 18
      pom.xml
  4. 16 1
      ruoyi-admin/pom.xml
  5. 34 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
  6. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssConfigController.java
  7. 2 3
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java
  8. 70 66
      ruoyi-admin/src/main/resources/application-dev.yml
  9. 72 72
      ruoyi-admin/src/main/resources/application-prod.yml
  10. 4 44
      ruoyi-admin/src/main/resources/application.yml
  11. 7 0
      ruoyi-admin/src/main/resources/i18n/messages.properties
  12. 7 0
      ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
  13. 7 0
      ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  14. 22 2
      ruoyi-admin/src/main/resources/logback.xml
  15. 1 6
      ruoyi-common/pom.xml
  16. 2 2
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
  17. 2 2
      ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
  18. 20 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
  19. 2 2
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java
  20. 2 2
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
  21. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
  22. 2 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
  23. 5 5
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
  24. 5 5
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
  25. 33 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java
  26. 24 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/XcxLoginUser.java
  27. 49 0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/DataBaseType.java
  28. 6 1
      ruoyi-common/src/main/java/com/ruoyi/common/enums/DeviceType.java
  29. 39 0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/LoginType.java
  30. 0 26
      ruoyi-common/src/main/java/com/ruoyi/common/enums/ThreadPoolRejectedPolicy.java
  31. 72 0
      ruoyi-common/src/main/java/com/ruoyi/common/helper/DataBaseHelper.java
  32. 68 7
      ruoyi-common/src/main/java/com/ruoyi/common/utils/BeanCopyUtils.java
  33. 16 17
      ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
  34. 1 1
      ruoyi-demo/pom.xml
  35. 2 2
      ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestBatchController.java
  36. 1 1
      ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestDemoController.java
  37. 1 1
      ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestDemo.java
  38. 1 1
      ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestTree.java
  39. 1 1
      ruoyi-demo/src/main/java/com/ruoyi/demo/domain/bo/TestTreeBo.java
  40. 1 1
      ruoyi-extend/pom.xml
  41. 1 1
      ruoyi-extend/ruoyi-monitor-admin/pom.xml
  42. 2 5
      ruoyi-extend/ruoyi-xxl-job-admin/pom.xml
  43. 1 1
      ruoyi-framework/pom.xml
  44. 2 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
  45. 42 5
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RepeatSubmitAspect.java
  46. 13 13
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java
  47. 1 15
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
  48. 11 9
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SaTokenConfig.java
  49. 9 7
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java
  50. 0 31
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java
  51. 0 22
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RepeatSubmitProperties.java
  52. 0 16
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/ThreadPoolProperties.java
  53. 4 4
      ruoyi-framework/src/main/java/com/ruoyi/framework/listener/UserActionListener.java
  54. 14 3
      ruoyi-framework/src/main/java/com/ruoyi/framework/satoken/service/SaPermissionImpl.java
  55. 27 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java
  56. 1 1
      ruoyi-generator/pom.xml
  57. 7 9
      ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java
  58. 6 0
      ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java
  59. 9 9
      ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java
  60. 2 15
      ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java
  61. 0 68
      ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java
  62. 46 31
      ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
  63. 0 44
      ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java
  64. 10 7
      ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java
  65. 3 0
      ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
  66. 3 0
      ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java
  67. 13 1
      ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
  68. 79 3
      ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
  69. 155 72
      ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
  70. 1 1
      ruoyi-generator/src/main/resources/vm/java/bo.java.vm
  71. 1 1
      ruoyi-generator/src/main/resources/vm/java/domain.java.vm
  72. 19 0
      ruoyi-generator/src/main/resources/vm/sql/oracle/sql.vm
  73. 20 0
      ruoyi-generator/src/main/resources/vm/sql/postgres/sql.vm
  74. 12 15
      ruoyi-generator/src/main/resources/vm/sql/sql.vm
  75. 19 0
      ruoyi-generator/src/main/resources/vm/sql/sqlserver/sql.vm
  76. 28 8
      ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
  77. 5 7
      ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
  78. 21 1
      ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm
  79. 1 1
      ruoyi-job/pom.xml
  80. 5 1
      ruoyi-oss/pom.xml
  81. 8 1
      ruoyi-oss/src/main/java/com/ruoyi/oss/service/IOssStrategy.java
  82. 5 0
      ruoyi-oss/src/main/java/com/ruoyi/oss/service/abstractd/AbstractOssStrategy.java
  83. 2 0
      ruoyi-oss/src/main/java/com/ruoyi/oss/service/impl/MinioOssStrategy.java
  84. 1 1
      ruoyi-system/pom.xml
  85. 1 2
      ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOss.java
  86. 3 2
      ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java
  87. 5 11
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java
  88. 14 3
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java
  89. 5 3
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java
  90. 17 7
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
  91. 4 0
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java
  92. 1 1
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssConfigService.java
  93. 8 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
  94. 123 27
      ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java
  95. 15 3
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDataScopeServiceImpl.java
  96. 21 7
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
  97. 15 7
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
  98. 1 1
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssConfigServiceImpl.java
  99. 20 2
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
  100. 56 4
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

+ 1 - 1
.gitee/ISSUE_TEMPLATE.zh-CN.md

@@ -1,4 +1,4 @@
-### 使用版本
+### 使用版本(未按照模板填写直接删除)
 
 
 ### 问题描述

+ 5 - 2
README.md

@@ -4,8 +4,8 @@
 [![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.0.1-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
-[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.5-blue.svg)]()
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.1.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
+[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.6-blue.svg)]()
 [![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
 [![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
 
@@ -25,6 +25,9 @@
 | 容器框架     | Undertow            | [Undertow官网](https://undertow.io/)                                                                | 基于 XNIO 的高性能容器             |
 | 权限认证框架   | Sa-Token、Jwt        | [Sa-Token官网](https://sa-token.dev33.cn/)                                                          | 强解耦、强扩展                    |
 | 关系数据库    | MySQL               | [MySQL官网](https://dev.mysql.com/)                                                                 | 适配 8.X 最低 5.7              |
+| 关系数据库    | Oracle              | [Oracle官网](https://www.oracle.com/cn/database/)                                                   | 适配 11g 12c                 |
+| 关系数据库    | PostgreSQL          | [PostgreSQL官网](https://www.postgresql.org/)                                                       | 适配 13 14                   |
+| 关系数据库    | SQLServer           | [SQLServer官网](https://docs.microsoft.com/zh-cn/sql/sql-server)                                    | 适配 2017 2019               |
 | 缓存数据库    | Redis               | [Redis官网](https://redis.io/)                                                                      | 适配 6.X 最低 4.X              |
 | 数据库框架    | Mybatis-Plus        | [Mybatis-Plus文档](https://baomidou.com/guide/)                                                     | 快速 CRUD 增加开发效率             |
 | 数据库框架    | p6spy               | [p6spy官网](https://p6spy.readthedocs.io/)                                                          | 更强劲的 SQL 分析                |

+ 43 - 18
pom.xml

@@ -6,46 +6,49 @@
 
     <groupId>com.ruoyi</groupId>
     <artifactId>ruoyi-vue-plus</artifactId>
-    <version>4.0.1</version>
+    <version>4.1.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.0.1</ruoyi-vue-plus.version>
-        <spring-boot.version>2.6.4</spring-boot.version>
+        <ruoyi-vue-plus.version>4.1.0</ruoyi-vue-plus.version>
+        <spring-boot.version>2.6.7</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.0</spring-boot.mybatis>
         <druid.version>1.2.8</druid.version>
         <knife4j.version>3.0.3</knife4j.version>
         <swagger-annotations.version>1.5.22</swagger-annotations.version>
         <poi.version>4.1.2</poi.version>
+        <commons-compress.version>1.21</commons-compress.version>
         <easyexcel.version>3.0.5</easyexcel.version>
-        <cglib.version>3.3.0</cglib.version>
         <velocity.version>2.3</velocity.version>
         <satoken.version>1.29.0</satoken.version>
         <mybatis-plus.version>3.5.1</mybatis-plus.version>
         <p6spy.version>3.9.1</p6spy.version>
-        <hutool.version>5.7.21</hutool.version>
-        <okhttp.version>4.9.2</okhttp.version>
-        <spring-boot-admin.version>2.6.2</spring-boot-admin.version>
-        <redisson.version>3.16.8</redisson.version>
+        <hutool.version>5.7.22</hutool.version>
+        <okhttp.version>4.9.3</okhttp.version>
+        <spring-boot-admin.version>2.6.6</spring-boot-admin.version>
+        <redisson.version>3.17.0</redisson.version>
         <lock4j.version>2.2.1</lock4j.version>
-        <dynamic-ds.version>3.5.0</dynamic-ds.version>
+        <dynamic-ds.version>3.5.1</dynamic-ds.version>
         <tlog.version>1.3.6</tlog.version>
         <xxl-job.version>2.3.0</xxl-job.version>
 
         <!-- jdk11 缺失依赖 jaxb-->
         <jaxb.version>3.0.1</jaxb.version>
+        <!-- 统一 guava 版本 解决隐式漏洞问题 -->
+        <guava.version>30.0-jre</guava.version>
 
         <!-- OSS 配置 -->
-        <qiniu.version>7.9.3</qiniu.version>
+        <qiniu.version>7.9.5</qiniu.version>
         <aliyun.oss.version>3.14.0</aliyun.oss.version>
-        <qcloud.cos.version>5.6.68</qcloud.cos.version>
-        <minio.version>8.3.7</minio.version>
+        <qcloud.cos.version>5.6.72</qcloud.cos.version>
+        <minio.version>8.3.8</minio.version>
 
         <!-- docker 配置 -->
         <docker.registry.url>localhost</docker.registry.url>
@@ -109,6 +112,13 @@
                 <version>${poi.version}</version>
             </dependency>
 
+            <!-- 修复poi漏洞 -->
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-compress</artifactId>
+                <version>${commons-compress.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>com.alibaba</groupId>
                 <artifactId>easyexcel</artifactId>
@@ -125,12 +135,6 @@
                 </exclusions>
             </dependency>
 
-            <dependency>
-                <groupId>cglib</groupId>
-                <artifactId>cglib</artifactId>
-                <version>${cglib.version}</version>
-            </dependency>
-
             <!-- velocity代码生成使用模板 -->
             <dependency>
                 <groupId>org.apache.velocity</groupId>
@@ -222,6 +226,20 @@
                 <groupId>com.yomahub</groupId>
                 <artifactId>tlog-web-spring-boot-starter</artifactId>
                 <version>${tlog.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>log4j</artifactId>
+                        <groupId>log4j</groupId>
+                    </exclusion>
+                    <exclusion>
+                        <artifactId>dom4j</artifactId>
+                        <groupId>dom4j</groupId>
+                    </exclusion>
+                    <exclusion>
+                        <artifactId>commons-beanutils</artifactId>
+                        <groupId>commons-beanutils</groupId>
+                    </exclusion>
+                </exclusions>
             </dependency>
 
             <dependency>
@@ -230,6 +248,13 @@
                 <version>${tlog.version}</version>
             </dependency>
 
+            <!-- 统一 guava 版本 解决隐式漏洞问题 -->
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
+
             <!-- 定时任务 -->
             <dependency>
                 <groupId>com.ruoyi</groupId>

+ 16 - 1
ruoyi-admin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.0.1</version>
+        <version>4.1.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>
@@ -29,6 +29,21 @@
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
         </dependency>
+        <!-- Oracle -->
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc8</artifactId>
+        </dependency>
+        <!-- PostgreSql -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <!-- SqlServer -->
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+        </dependency>
 
         <!-- 核心模块-->
         <dependency>

+ 34 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -7,6 +7,7 @@ import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.domain.entity.SysMenu;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginBody;
+import com.ruoyi.common.core.domain.model.SmsLoginBody;
 import com.ruoyi.common.helper.LoginHelper;
 import com.ruoyi.system.domain.vo.RouterVo;
 import com.ruoyi.system.service.ISysMenuService;
@@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.validation.constraints.NotBlank;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -60,6 +62,38 @@ public class SysLoginController {
         return R.ok(ajax);
     }
 
+    /**
+     * 短信登录(示例)
+     *
+     * @param smsLoginBody 登录信息
+     * @return 结果
+     */
+    @ApiOperation("短信登录(示例)")
+    @PostMapping("/smsLogin")
+    public R<Map<String, Object>> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode());
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 小程序登录(示例)
+     *
+     * @param xcxCode 小程序code
+     * @return 结果
+     */
+    @ApiOperation("小程序登录(示例)")
+    @PostMapping("/xcxLogin")
+    public R<Map<String, Object>> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.xcxLogin(xcxCode);
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
     @ApiOperation("登出方法")
     @PostMapping("/logout")
     public R<Void> logout() {

+ 1 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssConfigController.java

@@ -59,7 +59,7 @@ public class SysOssConfigController extends BaseController {
     @GetMapping("/{ossConfigId}")
     public R<SysOssConfigVo> getInfo(@ApiParam("OSS配置ID")
                                               @NotNull(message = "主键不能为空")
-                                              @PathVariable("ossConfigId") Integer ossConfigId) {
+                                              @PathVariable("ossConfigId") Long ossConfigId) {
         return R.ok(iSysOssConfigService.queryById(ossConfigId));
     }
 

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

@@ -18,7 +18,6 @@ import com.ruoyi.common.utils.file.FileUtils;
 import com.ruoyi.system.domain.SysOss;
 import com.ruoyi.system.domain.bo.SysOssBo;
 import com.ruoyi.system.domain.vo.SysOssVo;
-import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysOssService;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
@@ -48,7 +47,6 @@ import java.util.Map;
 public class SysOssController extends BaseController {
 
     private final ISysOssService iSysOssService;
-    private final ISysConfigService iSysConfigService;
 
     /**
      * 查询OSS对象存储列表
@@ -77,7 +75,8 @@ public class SysOssController extends BaseController {
         SysOss oss = iSysOssService.upload(file);
         Map<String, String> map = new HashMap<>(2);
         map.put("url", oss.getUrl());
-        map.put("fileName", oss.getFileName());
+        map.put("fileName", oss.getOriginalName());
+        map.put("ossId", oss.getOssId().toString());
         return R.ok(map);
     }
 

+ 70 - 66
ruoyi-admin/src/main/resources/application-dev.yml

@@ -1,41 +1,34 @@
---- # 监控配置
-spring:
-  boot:
-    admin:
-      # Spring Boot Admin Client 客户端的相关配置
-      client:
-        # 增加客户端开关
-        enabled: true
-        # 设置 Spring Boot Admin Server 地址
-        url: http://localhost:9090/admin
-        instance:
-          service-host-type: IP
-        username: ruoyi
-        password: 123456
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: true
+  url: http://localhost:9090/admin
+  instance:
+    service-host-type: IP
+  username: ruoyi
+  password: 123456
 
 --- # xxl-job 配置
-xxl:
-  job:
-    # 执行器开关
-    enabled: true
-    # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
-    admin-addresses: http://localhost:9100/xxl-job-admin
-    # 执行器通讯TOKEN:非空时启用
-    access-token: xxl-job
-    # 执行器配置
-    executor:
-      # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
-      appname: xxl-job-executor
-      # 执行器端口号 执行器从9101开始往后写
-      port: 9101
-      # 执行器注册:默认IP:PORT
-      address:
-      # 执行器IP:默认自动获取IP
-      ip:
-      # 执行器运行日志文件存储磁盘路径
-      logpath: ./logs/xxl-job
-      # 执行器日志文件保存天数:大于3生效
-      logretentiondays: 30
+xxl.job:
+  # 执行器开关
+  enabled: true
+  # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
+  admin-addresses: http://localhost:9100/xxl-job-admin
+  # 执行器通讯TOKEN:非空时启用
+  access-token: xxl-job
+  executor:
+    # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
+    appname: xxl-job-executor
+    # 执行器端口号 执行器从9101开始往后写
+    port: 9101
+    # 执行器注册:默认IP:PORT
+    address:
+    # 执行器IP:默认自动获取IP
+    ip:
+    # 执行器运行日志文件存储磁盘路径
+    logpath: ./logs/xxl-job
+    # 执行器日志文件保存天数:大于3生效
+    logretentiondays: 30
 
 --- # 数据源配置
 spring:
@@ -47,6 +40,8 @@ spring:
       p6spy: true
       # 设置默认的数据源或者数据源组,默认值即为 master
       primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
       datasource:
         # 主库数据源
         master:
@@ -63,6 +58,23 @@ spring:
           url:
           username:
           password:
+#        oracle:
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//localhost:1521/XE
+#          username: ROOT
+#          password: root
+#          druid:
+#            validationQuery: SELECT 1 FROM DUAL
+#        postgres:
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
       druid:
         # 初始连接数
         initialSize: 5
@@ -79,7 +91,7 @@ spring:
         # 配置一个连接在池中最大生存的时间,单位是毫秒
         maxEvictableIdleTimeMillis: 900000
         # 配置检测连接是否有效
-        validationQuery: SELECT 1 FROM DUAL
+        validationQuery: SELECT 1
         testWhileIdle: true
         testOnBorrow: false
         testOnReturn: false
@@ -87,29 +99,27 @@ spring:
         filters: stat
 
 --- # druid 配置
-spring:
-  datasource:
-    druid:
-      webStatFilter:
-        enabled: true
-      statViewServlet:
-        enabled: true
-        # 设置白名单,不填则允许所有访问
-        allow:
-        url-pattern: /druid/*
-        # 控制台管理用户名和密码
-        login-username: ruoyi
-        login-password: 123456
-      filter:
-        stat:
-          enabled: true
-          # 慢SQL记录
-          log-slow-sql: true
-          slow-sql-millis: 1000
-          merge-sql: true
-        wall:
-          config:
-            multi-statement-allow: true
+spring.datasource.druid:
+  webStatFilter:
+    enabled: true
+  statViewServlet:
+    enabled: true
+    # 设置白名单,不填则允许所有访问
+    allow:
+    url-pattern: /druid/*
+    # 控制台管理用户名和密码
+    login-username: ruoyi
+    login-password: 123456
+  filter:
+    stat:
+      enabled: true
+      # 慢SQL记录
+      log-slow-sql: true
+      slow-sql-millis: 1000
+      merge-sql: true
+    wall:
+      config:
+        multi-statement-allow: true
 
 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring:
@@ -132,8 +142,6 @@ redisson:
   threads: 4
   # Netty线程池数量
   nettyThreads: 8
-  # 传输模式
-  transportMode: "NIO"
   # 单节点配置
   singleServerConfig:
     # 客户端名称
@@ -146,9 +154,5 @@ redisson:
     idleConnectionTimeout: 10000
     # 命令等待超时,单位:毫秒
     timeout: 3000
-    # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
-    retryAttempts: 3
-    # 命令重试发送时间间隔,单位:毫秒
-    retryInterval: 1500
     # 发布和订阅连接池大小
     subscriptionConnectionPoolSize: 50

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

@@ -1,48 +1,37 @@
---- # 配置临时路径存储
-spring:
-  servlet:
-    multipart:
-      # 临时文件存储位置 避免临时文件被系统清理报错
-      location: /ruoyi/server/temp
+--- # 临时文件存储位置 避免临时文件被系统清理报错
+spring.servlet.multipart.location: /ruoyi/server/temp
 
---- # 监控配置
-spring:
-  boot:
-    admin:
-      # Spring Boot Admin Client 客户端的相关配置
-      client:
-        # 增加客户端开关
-        enabled: true
-        # 设置 Spring Boot Admin Server 地址
-        url: http://172.30.0.90:9090/admin
-        instance:
-          service-host-type: IP
-        username: ruoyi
-        password: 123456
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: true
+  url: http://172.30.0.90:9090/admin
+  instance:
+    service-host-type: IP
+  username: ruoyi
+  password: 123456
 
 --- # xxl-job 配置
-xxl:
-  job:
-    # 执行器开关
-    enabled: true
-    # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
-    admin-addresses: http://172.30.0.92:9100/xxl-job-admin
-    # 执行器通讯TOKEN:非空时启用
-    access-token: xxl-job
-    # 执行器配置
-    executor:
-      # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
-      appname: xxl-job-executor
-      # 执行器端口号 执行器从9101开始往后写
-      port: 9101
-      # 执行器注册:默认IP:PORT
-      address:
-      # 执行器IP:默认自动获取IP
-      ip:
-      # 执行器运行日志文件存储磁盘路径
-      logpath: ./logs/xxl-job
-      # 执行器日志文件保存天数:大于3生效
-      logretentiondays: 30
+xxl.job:
+  # 执行器开关
+  enabled: true
+  # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
+  admin-addresses: http://172.30.0.92:9100/xxl-job-admin
+  # 执行器通讯TOKEN:非空时启用
+  access-token: xxl-job
+  executor:
+    # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
+    appname: xxl-job-executor
+    # 执行器端口号 执行器从9101开始往后写
+    port: 9101
+    # 执行器注册:默认IP:PORT
+    address:
+    # 执行器IP:默认自动获取IP
+    ip:
+    # 执行器运行日志文件存储磁盘路径
+    logpath: ./logs/xxl-job
+    # 执行器日志文件保存天数:大于3生效
+    logretentiondays: 30
 
 --- # 数据源配置
 spring:
@@ -54,6 +43,8 @@ spring:
       p6spy: false
       # 设置默认的数据源或者数据源组,默认值即为 master
       primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
       datasource:
         # 主库数据源
         master:
@@ -70,6 +61,23 @@ spring:
           url:
           username:
           password:
+#        oracle:
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//172.30.0.36:1521/XE
+#          username: ROOT
+#          password: root
+#          druid:
+#            validationQuery: SELECT 1 FROM DUAL
+#        postgres:
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://172.30.0.36:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://172.30.0.36:1433;DatabaseName=tempdb;SelectMethod=cursor;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
       druid:
         # 初始连接数
         initialSize: 5
@@ -86,7 +94,7 @@ spring:
         # 配置一个连接在池中最大生存的时间,单位是毫秒
         maxEvictableIdleTimeMillis: 900000
         # 配置检测连接是否有效
-        validationQuery: SELECT 1 FROM DUAL
+        validationQuery: SELECT 1
         testWhileIdle: true
         testOnBorrow: false
         testOnReturn: false
@@ -94,29 +102,27 @@ spring:
         filters: stat
 
 --- # druid 配置
-spring:
-  datasource:
-    druid:
-      webStatFilter:
-        enabled: true
-      statViewServlet:
-        enabled: true
-        # 设置白名单,不填则允许所有访问
-        allow:
-        url-pattern: /druid/*
-        # 控制台管理用户名和密码
-        login-username: ruoyi
-        login-password: 123456
-      filter:
-        stat:
-          enabled: true
-          # 慢SQL记录
-          log-slow-sql: true
-          slow-sql-millis: 1000
-          merge-sql: true
-        wall:
-          config:
-            multi-statement-allow: true
+spring.datasource.druid:
+  webStatFilter:
+    enabled: true
+  statViewServlet:
+    enabled: true
+    # 设置白名单,不填则允许所有访问
+    allow:
+    url-pattern: /druid/*
+    # 控制台管理用户名和密码
+    login-username: ruoyi
+    login-password: 123456
+  filter:
+    stat:
+      enabled: true
+      # 慢SQL记录
+      log-slow-sql: true
+      slow-sql-millis: 1000
+      merge-sql: true
+    wall:
+      config:
+        multi-statement-allow: true
 
 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring:
@@ -139,8 +145,6 @@ redisson:
   threads: 16
   # Netty线程池数量
   nettyThreads: 32
-  # 传输模式
-  transportMode: "NIO"
   # 单节点配置
   singleServerConfig:
     # 客户端名称
@@ -153,9 +157,5 @@ redisson:
     idleConnectionTimeout: 10000
     # 命令等待超时,单位:毫秒
     timeout: 3000
-    # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
-    retryAttempts: 3
-    # 命令重试发送时间间隔,单位:毫秒
-    retryInterval: 1500
     # 发布和订阅连接池大小
     subscriptionConnectionPoolSize: 50

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

@@ -75,10 +75,6 @@ spring:
     restart:
       # 热部署开关
       enabled: true
-  # 与vue整合部署使用
-  thymeleaf:
-    # 将系统模板放置到最前面 否则会与 springboot-admin 页面冲突
-    template-resolver-order: 1
   mvc:
     pathmatch:
       # 适配 boot 2.6 路由与 springfox 兼容
@@ -107,16 +103,12 @@ sa-token:
   is-concurrent: true
   # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
   is-share: false
-  # 是否尝试从请求体里读取token
-  is-read-body: false
   # 是否尝试从header里读取token
   is-read-head: true
   # 是否尝试从cookie里读取token
   is-read-cookie: false
   # token前缀
   token-prefix: "Bearer"
-  # token风格
-  token-style: uuid
   # jwt秘钥
   jwt-secret-key: abcdefghijklmnopqrstuvwxyz
   # 是否输出操作日志
@@ -127,6 +119,8 @@ security:
   # 排除路径
   excludes:
     - /login
+    - /smsLogin
+    - /xcxLogin
     - /logout
     - /register
     - /captchaImage
@@ -136,23 +130,17 @@ security:
     - /**/*.css
     - /**/*.js
     # swagger 文档配置
+    - /favicon.ico
     - /doc.html
     - /swagger-resources/**
     - /webjars/**
     - /*/api-docs
     # druid 监控配置
     - /druid/**
-  # 用户放行
-  permit-all:
     # actuator 监控配置
     - /actuator
     - /actuator/**
 
-# 重复提交
-repeat-submit:
-  # 全局间隔时间(毫秒)
-  interval: 5000
-
 # MyBatisPlus配置
 # https://baomidou.com/config/
 mybatis-plus:
@@ -165,25 +153,15 @@ mybatis-plus:
   typeAliasesPackage: com.ruoyi.**.domain
   # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
   checkConfigLocation: false
-  # 通过该属性可指定 MyBatis 的执行器,MyBatis 的执行器总共有三种:
-  # SIMPLE:每个语句创建新的预处理器 REUSE:会复用预处理器 BATCH:批量执行所有的更新
-  executorType: SIMPLE
   configuration:
     # 自动驼峰命名规则(camel case)映射
     mapUnderscoreToCamelCase: true
-    # 当设置为 true 的时候,懒加载的对象可能被任何懒属性全部加载,否则,每个属性都按需加载。需要和 lazyLoadingEnabled 一起使用。
-    aggressiveLazyLoading: true
     # MyBatis 自动映射策略
     # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
     autoMappingBehavior: PARTIAL
     # MyBatis 自动映射时未知列或未知属性处理策
     # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
     autoMappingUnknownColumnBehavior: NONE
-    # Mybatis一级缓存,默认为 SESSION
-    # SESSION session级别缓存 STATEMENT 关闭一级缓存
-    localCacheScope: SESSION
-    # 开启Mybatis二级缓存,默认为 true
-    cacheEnabled: false
     # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
     # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
     # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
@@ -191,16 +169,10 @@ mybatis-plus:
   global-config:
     # 是否打印 Logo banner
     banner: true
-    # 是否初始化 SqlRunner
-    enableSqlRunner: false
     dbConfig:
       # 主键类型
       # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
-      idType: AUTO
-      # 表名是否使用驼峰转下划线命名,只对表名生效
-      tableUnderline: true
-      # 大写命名,对表名和字段名均生效
-      capitalMode: false
+      idType: ASSIGN_ID
       # 逻辑已删除值
       logicDeleteValue: 2
       # 逻辑未删除值
@@ -271,20 +243,10 @@ xss:
 thread-pool:
   # 是否开启线程池
   enabled: false
-  # 核心线程池大小
-  corePoolSize: 8
-  # 最大可创建的线程数
-  maxPoolSize: 16
   # 队列最大长度
   queueCapacity: 128
   # 线程池维护线程所允许的空闲时间
   keepAliveSeconds: 300
-  # 线程池对拒绝任务(无线程可用)的处理策略
-  # CALLER_RUNS_POLICY 调用方执行
-  # DISCARD_OLDEST_POLICY 放弃最旧的
-  # DISCARD_POLICY 丢弃
-  # ABORT_POLICY 中止
-  rejectedExecutionHandler: CALLER_RUNS_POLICY
 
 --- # redisson 缓存配置
 redisson:
@@ -312,8 +274,6 @@ lock4j:
 management:
   endpoints:
     web:
-      # Actuator 提供的 API 接口的根目录。默认为 /actuator
-      base-path: /actuator
       exposure:
         # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
         # 生产环境不建议放开所有 根据项目需求放开即可

+ 7 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
 user.password.not.valid=* 5-50个字符
 user.email.not.valid=邮箱格式错误
+user.phonenumber.not.blank=用户手机号不能为空
 user.mobile.phone.number.not.valid=手机号格式错误
 user.login.success=登录成功
 user.register.success=注册成功
@@ -36,3 +37,9 @@ no.update.permission=您没有修改数据的权限,请联系管理员添加
 no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
 no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
 no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码错误次数过多,帐户锁定{0}分钟
+xcx.code.not.blank=小程序code不能为空

+ 7 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -18,6 +18,7 @@ user.password.not.blank=Password cannot be empty
 user.password.length.valid=Password length must be between {min} and {max} characters
 user.password.not.valid=* 5-50 characters
 user.email.not.valid=Mailbox format error
+user.phonenumber.not.blank=Phone number cannot be blank
 user.mobile.phone.number.not.valid=Phone number format error
 user.login.success=Login successful
 user.register.success=Register successful
@@ -36,3 +37,9 @@ no.update.permission=You do not have permission to modify data,please contact
 no.delete.permission=You do not have permission to delete data,please contact your administrator to add permissions [{0}]
 no.export.permission=You do not have permission to export data,please contact your administrator to add permissions [{0}]
 no.view.permission=You do not have permission to view data,please contact your administrator to add permissions [{0}]
+repeat.submit.message=Repeat submit is not allowed, please try again later
+rate.limiter.message=Visit too frequently, please try again later
+sms.code.not.blank=Sms code cannot be blank
+sms.code.retry.limit.count=Sms code input error {0} times
+sms.code.retry.limit.exceed=Too many sms code errors, account locked for {0} minutes
+xcx.code.not.blank=Mini program code cannot be blank

+ 7 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
 user.password.not.valid=* 5-50个字符
 user.email.not.valid=邮箱格式错误
+user.phonenumber.not.blank=用户手机号不能为空
 user.mobile.phone.number.not.valid=手机号格式错误
 user.login.success=登录成功
 user.register.success=注册成功
@@ -36,3 +37,9 @@ no.update.permission=您没有修改数据的权限,请联系管理员添加
 no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
 no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
 no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码错误次数过多,帐户锁定{0}分钟
+xcx.code.not.blank=小程序code不能为空

+ 22 - 2
ruoyi-admin/src/main/resources/logback.xml

@@ -77,6 +77,26 @@
         </filter>
     </appender>
 
+    <!-- info异步输出 -->
+    <appender name="async_info" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_info"/>
+    </appender>
+
+    <!-- error异步输出 -->
+    <appender name="async_error" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_error"/>
+    </appender>
+
     <!-- 系统模块日志级别控制  -->
     <logger name="com.ruoyi" level="info" />
     <!-- Spring日志级别控制  -->
@@ -88,8 +108,8 @@
 
     <!--系统操作日志-->
     <root level="info">
-        <appender-ref ref="file_info" />
-        <appender-ref ref="file_error" />
+        <appender-ref ref="async_info" />
+        <appender-ref ref="async_error" />
         <appender-ref ref="file_console" />
     </root>
 

+ 1 - 6
ruoyi-common/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.0.1</version>
+        <version>4.1.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -69,11 +69,6 @@
             <artifactId>easyexcel</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>cglib</groupId>
-            <artifactId>cglib</artifactId>
-        </dependency>
-
         <!-- yml解析器 -->
         <dependency>
             <groupId>org.yaml</groupId>

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

@@ -22,8 +22,8 @@ public @interface RepeatSubmit {
     TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
 
     /**
-     * 提示消息
+     * 提示消息 支持国际化 格式为 {code}
      */
-    String message() default "不允许重复提交,请稍候再试";
+    String message() default "{repeat.submit.message}";
 
 }

+ 2 - 2
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -65,12 +65,12 @@ public interface Constants {
     /**
      * 登录用户 redis key
      */
-    public static final String LOGIN_TOKEN_KEY = "Authorization:login:token:";
+    String LOGIN_TOKEN_KEY = "Authorization:login:token:";
 
     /**
      * 在线用户 redis key
      */
-    public static final String ONLINE_TOKEN_KEY = "online_tokens:";
+    String ONLINE_TOKEN_KEY = "online_tokens:";
 
     /**
      * 防重提交 redis key

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

@@ -23,11 +23,21 @@ public interface UserConstants {
     String EXCEPTION = "1";
 
     /**
+     * 用户正常状态
+     */
+    String USER_NORMAL = "0";
+
+    /**
      * 用户封禁状态
      */
     String USER_DISABLE = "1";
 
     /**
+     * 角色正常状态
+     */
+    String ROLE_NORMAL = "0";
+
+    /**
      * 角色封禁状态
      */
     String ROLE_DISABLE = "1";
@@ -63,6 +73,16 @@ public interface UserConstants {
     String NO_FRAME = "1";
 
     /**
+     * 菜单正常状态
+     */
+    String MENU_NORMAL = "0";
+
+    /**
+     * 菜单停用状态
+     */
+    String MENU_DISABLE = "1";
+
+    /**
      * 菜单类型(目录)
      */
     String TYPE_DIR = "M";

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

@@ -16,7 +16,7 @@ import java.util.List;
 
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class TreeEntity extends BaseEntity {
+public class TreeEntity<T> extends BaseEntity {
 
     private static final long serialVersionUID = 1L;
 
@@ -38,6 +38,6 @@ public class TreeEntity extends BaseEntity {
      */
     @TableField(exist = false)
     @ApiModelProperty(value = "子部门")
-    private List<?> children = new ArrayList<>();
+    private List<T> children = new ArrayList<>();
 
 }

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

@@ -24,7 +24,7 @@ import javax.validation.constraints.Size;
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_dept")
 @ApiModel("部门业务对象")
-public class SysDept extends TreeEntity {
+public class SysDept extends TreeEntity<SysDept> {
     private static final long serialVersionUID = 1L;
 
     /**
@@ -47,7 +47,7 @@ public class SysDept extends TreeEntity {
      */
     @ApiModelProperty(value = "显示顺序")
     @NotNull(message = "显示顺序不能为空")
-    private Long orderNum;
+    private Integer orderNum;
 
     /**
      * 负责人

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

@@ -42,7 +42,7 @@ public class SysDictData extends BaseEntity {
      */
     @ApiModelProperty(value = "字典排序")
     @ExcelProperty(value = "字典排序")
-    private Long dictSort;
+    private Integer dictSort;
 
     /**
      * 字典标签

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

@@ -14,6 +14,7 @@ import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
 import javax.validation.constraints.Size;
 
 /**
@@ -53,6 +54,7 @@ public class SysDictType extends BaseEntity {
     @ExcelProperty(value = "字典类型")
     @NotBlank(message = "字典类型不能为空")
     @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符")
+    @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
     private String dictType;
 
     /**

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

@@ -1,8 +1,8 @@
 package com.ruoyi.common.core.domain.entity;
 
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.ruoyi.common.core.domain.TreeEntity;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
@@ -23,7 +23,7 @@ import javax.validation.constraints.Size;
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_menu")
 @ApiModel("菜单权限业务对象")
-public class SysMenu extends TreeEntity {
+public class SysMenu extends TreeEntity<SysMenu> {
 
     /**
      * 菜单ID
@@ -45,7 +45,7 @@ public class SysMenu extends TreeEntity {
      */
     @ApiModelProperty(value = "显示顺序")
     @NotNull(message = "显示顺序不能为空")
-    private Long orderNum;
+    private Integer orderNum;
 
     /**
      * 路由地址
@@ -65,8 +65,7 @@ public class SysMenu extends TreeEntity {
      * 路由参数
      */
     @ApiModelProperty(value = "路由参数")
-    @TableField("`query`")
-    private String query;
+    private String queryParam;
 
     /**
      * 是否为外链(0是 1否)
@@ -103,6 +102,7 @@ public class SysMenu extends TreeEntity {
      * 权限字符串
      */
     @ApiModelProperty(value = "权限字符串")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
     @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符")
     private String perms;
 

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

@@ -16,6 +16,7 @@ import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
 
 /**
@@ -62,8 +63,8 @@ public class SysRole extends BaseEntity {
      */
     @ApiModelProperty(value = "角色排序")
     @ExcelProperty(value = "角色排序")
-    @NotBlank(message = "显示顺序不能为空")
-    private String roleSort;
+    @NotNull(message = "显示顺序不能为空")
+    private Integer roleSort;
 
     /**
      * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
@@ -77,13 +78,13 @@ public class SysRole extends BaseEntity {
      * 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)
      */
     @ApiModelProperty(value = "菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)")
-    private boolean menuCheckStrictly;
+    private Boolean menuCheckStrictly;
 
     /**
      * 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 )
      */
     @ApiModelProperty(value = "部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 )")
-    private boolean deptCheckStrictly;
+    private Boolean deptCheckStrictly;
 
     /**
      * 角色状态(0正常 1停用)
@@ -135,5 +136,4 @@ public class SysRole extends BaseEntity {
     public boolean isAdmin() {
         return UserConstants.ADMIN_ID.equals(this.roleId);
     }
-
 }

+ 33 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java

@@ -0,0 +1,33 @@
+package com.ruoyi.common.core.domain.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+@ApiModel("短信登录对象")
+public class SmsLoginBody {
+
+    /**
+     * 用户名
+     */
+    @NotBlank(message = "{user.phonenumber.not.blank}")
+    @ApiModelProperty(value = "用户手机号")
+    private String phonenumber;
+
+    /**
+     * 用户密码
+     */
+    @NotBlank(message = "{sms.code.not.blank}")
+    @ApiModelProperty(value = "短信验证码")
+    private String smsCode;
+
+}

+ 24 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/XcxLoginUser.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 小程序登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * openid
+     */
+    private String openid;
+
+}

+ 49 - 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/DataBaseType.java

@@ -0,0 +1,49 @@
+package com.ruoyi.common.enums;
+
+import com.ruoyi.common.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 数据库类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DataBaseType {
+
+    /**
+     * MySQL
+     */
+    MY_SQL("MySQL"),
+
+    /**
+     * Oracle
+     */
+    ORACLE("Oracle"),
+
+    /**
+     * PostgreSQL
+     */
+    POSTGRE_SQL("PostgreSQL"),
+
+    /**
+     * SQL Server
+     */
+    SQL_SERVER("Microsoft SQL Server");
+
+    private final String type;
+
+    public static DataBaseType find(String databaseProductName) {
+        if (StringUtils.isBlank(databaseProductName)) {
+            return null;
+        }
+        for (DataBaseType type : values()) {
+            if (type.getType().equals(databaseProductName)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}

+ 6 - 1
ruoyi-common/src/main/java/com/ruoyi/common/enums/DeviceType.java

@@ -21,7 +21,12 @@ public enum DeviceType {
     /**
      * app端
      */
-    APP("app");
+    APP("app"),
+
+    /**
+     * 小程序端
+     */
+    XCX("xcx");
 
     private final String device;
 }

+ 39 - 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/LoginType.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 登录类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginType {
+
+    /**
+     * 密码登录
+     */
+    PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
+
+    /**
+     * 短信登录
+     */
+    SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
+
+    /**
+     * 小程序登录
+     */
+    XCX("", "");
+
+    /**
+     * 登录重试超出限制提示
+     */
+    final String retryLimitExceed;
+
+    /**
+     * 登录重试限制计数提示
+     */
+    final String retryLimitCount;
+}

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

@@ -1,26 +0,0 @@
-package com.ruoyi.common.enums;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ThreadPoolExecutor;
-
-/**
- * 线程池 拒绝策略 泛型
- *
- * @author Lion Li
- */
-@Getter
-@AllArgsConstructor
-public enum ThreadPoolRejectedPolicy {
-
-    CALLER_RUNS_POLICY("调用方执行", ThreadPoolExecutor.CallerRunsPolicy.class),
-    DISCARD_OLDEST_POLICY("放弃最旧的", ThreadPoolExecutor.DiscardOldestPolicy.class),
-    DISCARD_POLICY("丢弃", ThreadPoolExecutor.DiscardPolicy.class),
-    ABORT_POLICY("中止", ThreadPoolExecutor.AbortPolicy.class);
-
-    private final String name;
-    private final Class<? extends RejectedExecutionHandler> clazz;
-
-}

+ 72 - 0
ruoyi-common/src/main/java/com/ruoyi/common/helper/DataBaseHelper.java

@@ -0,0 +1,72 @@
+package com.ruoyi.common.helper;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import com.ruoyi.common.enums.DataBaseType;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * 数据库助手
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DataBaseHelper {
+
+    private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
+
+    /**
+     * 获取当前数据库类型
+     */
+    public static DataBaseType getDataBaseType() {
+        DataSource dataSource = DS.determineDataSource();
+        try (Connection conn = dataSource.getConnection()) {
+            DatabaseMetaData metaData = conn.getMetaData();
+            String databaseProductName = metaData.getDatabaseProductName();
+            return DataBaseType.find(databaseProductName);
+        } catch (SQLException e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    public static boolean isMySql() {
+        return DataBaseType.MY_SQL == getDataBaseType();
+    }
+
+    public static boolean isOracle() {
+        return DataBaseType.ORACLE == getDataBaseType();
+    }
+
+    public static boolean isPostgerSql() {
+        return DataBaseType.POSTGRE_SQL == getDataBaseType();
+    }
+
+    public static boolean isSqlServer() {
+        return DataBaseType.SQL_SERVER == getDataBaseType();
+    }
+
+    public static String findInSet(Object var1, String var2) {
+        DataBaseType dataBasyType = getDataBaseType();
+        String var = Convert.toStr(var1);
+        if (dataBasyType == DataBaseType.SQL_SERVER) {
+            // charindex(',100,' , ',0,100,101,') <> 0
+            return "charindex('," + var + ",' , ','+" + var2 + "+',') <> 0";
+        } else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
+            // (select position(',100,' in ',0,100,101,')) <> 0
+            return "(select position('," + var + ",' in ','||" + var2 + "||',')) <> 0";
+        } else if (dataBasyType == DataBaseType.ORACLE) {
+            // instr(',0,100,101,' , ',100,') <> 0
+            return "instr(','||" + var2 + "||',' , '," + var + ",') <> 0";
+        }
+        // find_in_set(100 , '0,100,101')
+        return "find_in_set(" + var + " , " + var2 + ") <> 0";
+    }
+}

+ 68 - 7
ruoyi-common/src/main/java/com/ruoyi/common/utils/BeanCopyUtils.java

@@ -1,15 +1,20 @@
 package com.ruoyi.common.utils;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.SimpleCache;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ReflectUtil;
-import cn.hutool.extra.cglib.CglibUtil;
+import cn.hutool.core.util.StrUtil;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import org.springframework.cglib.beans.BeanCopier;
+import org.springframework.cglib.beans.BeanMap;
+import org.springframework.cglib.core.Converter;
 
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * bean深拷贝工具(基于 cglib 性能优异)
@@ -37,7 +42,8 @@ public class BeanCopyUtils {
         if (ObjectUtil.isNull(desc)) {
             return null;
         }
-        return CglibUtil.copy(source, desc);
+        final V target = ReflectUtil.newInstanceIfPossible(desc);
+        return copy(source, target);
     }
 
     /**
@@ -54,7 +60,8 @@ public class BeanCopyUtils {
         if (ObjectUtil.isNull(desc)) {
             return null;
         }
-        CglibUtil.copy(source, desc);
+        BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(source.getClass(), desc.getClass(), null);
+        beanCopier.copy(source, desc, null);
         return desc;
     }
 
@@ -72,7 +79,11 @@ public class BeanCopyUtils {
         if (CollUtil.isEmpty(sourceList)) {
             return CollUtil.newArrayList();
         }
-        return CglibUtil.copyList(sourceList, () -> ReflectUtil.newInstanceIfPossible(desc));
+        return sourceList.stream().map(source -> {
+            V target = ReflectUtil.newInstanceIfPossible(desc);
+            copy(source, target);
+            return target;
+        }).collect(Collectors.toList());
     }
 
     /**
@@ -81,11 +92,12 @@ public class BeanCopyUtils {
      * @param bean 数据来源实体
      * @return map对象
      */
+    @SuppressWarnings("unchecked")
     public static <T> Map<String, Object> copyToMap(T bean) {
         if (ObjectUtil.isNull(bean)) {
             return null;
         }
-        return CglibUtil.toMap(bean);
+        return BeanMap.create(bean);
     }
 
     /**
@@ -102,7 +114,8 @@ public class BeanCopyUtils {
         if (ObjectUtil.isNull(beanClass)) {
             return null;
         }
-        return CglibUtil.toBean(map, beanClass);
+        T bean = ReflectUtil.newInstanceIfPossible(beanClass);
+        return mapToBean(map, bean);
     }
 
     /**
@@ -119,6 +132,54 @@ public class BeanCopyUtils {
         if (ObjectUtil.isNull(bean)) {
             return null;
         }
-        return CglibUtil.fillBean(map, bean);
+        BeanMap.create(bean).putAll(map);
+        return bean;
     }
+
+    /**
+     * BeanCopier属性缓存<br>
+     * 缓存用于防止多次反射造成的性能问题
+     *
+     * @author Looly
+     * @since 5.4.1
+     */
+    public enum BeanCopierCache {
+        /**
+         * BeanCopier属性缓存单例
+         */
+        INSTANCE;
+
+        private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>();
+
+        /**
+         * 获得类与转换器生成的key在{@link BeanCopier}的Map中对应的元素
+         *
+         * @param srcClass    源Bean的类
+         * @param targetClass 目标Bean的类
+         * @param converter   转换器
+         * @return Map中对应的BeanCopier
+         */
+        public BeanCopier get(Class<?> srcClass, Class<?> targetClass, Converter converter) {
+            final String key = genKey(srcClass, targetClass, converter);
+            return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, converter != null));
+        }
+
+        /**
+         * 获得类与转换器生成的key
+         *
+         * @param srcClass    源Bean的类
+         * @param targetClass 目标Bean的类
+         * @param converter   转换器
+         * @return 属性名和Map映射的key
+         */
+        private String genKey(Class<?> srcClass, Class<?> targetClass, Converter converter) {
+            final StringBuilder key = StrUtil.builder()
+                .append(srcClass.getName()).append('#').append(targetClass.getName());
+            if(null != converter){
+                key.append('#').append(converter.getClass().getName());
+            }
+            return key.toString();
+        }
+    }
+
 }

+ 16 - 17
ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java

@@ -22,17 +22,17 @@ import java.util.Date;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
 
-    public static String YYYY = "yyyy";
+    public static final String YYYY = "yyyy";
 
-    public static String YYYY_MM = "yyyy-MM";
+    public static final String YYYY_MM = "yyyy-MM";
 
-    public static String YYYY_MM_DD = "yyyy-MM-dd";
+    public static final String YYYY_MM_DD = "yyyy-MM-dd";
 
-    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
 
-    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
 
-    private static String[] parsePatterns = {
+    private static final String[] PARSE_PATTERNS = {
         "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
         "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
         "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
@@ -55,27 +55,27 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
         return dateTimeNow(YYYY_MM_DD);
     }
 
-    public static final String getTime() {
+    public static String getTime() {
         return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
     }
 
-    public static final String dateTimeNow() {
+    public static String dateTimeNow() {
         return dateTimeNow(YYYYMMDDHHMMSS);
     }
 
-    public static final String dateTimeNow(final String format) {
+    public static String dateTimeNow(final String format) {
         return parseDateToStr(format, new Date());
     }
 
-    public static final String dateTime(final Date date) {
+    public static String dateTime(final Date date) {
         return parseDateToStr(YYYY_MM_DD, date);
     }
 
-    public static final String parseDateToStr(final String format, final Date date) {
+    public static String parseDateToStr(final String format, final Date date) {
         return new SimpleDateFormat(format).format(date);
     }
 
-    public static final Date dateTime(final String format, final String ts) {
+    public static Date dateTime(final String format, final String ts) {
         try {
             return new SimpleDateFormat(format).parse(ts);
         } catch (ParseException e) {
@@ -86,7 +86,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
     /**
      * 日期路径 即年/月/日 如2018/08/08
      */
-    public static final String datePath() {
+    public static String datePath() {
         Date now = new Date();
         return DateFormatUtils.format(now, "yyyy/MM/dd");
     }
@@ -94,7 +94,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
     /**
      * 日期路径 即年/月/日 如20180808
      */
-    public static final String dateTime() {
+    public static String dateTime() {
         Date now = new Date();
         return DateFormatUtils.format(now, "yyyyMMdd");
     }
@@ -107,7 +107,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
             return null;
         }
         try {
-            return parseDate(str.toString(), parsePatterns);
+            return parseDate(str.toString(), PARSE_PATTERNS);
         } catch (ParseException e) {
             return null;
         }
@@ -124,8 +124,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
     /**
      * 计算相差天数
      */
-    public static int differentDaysByMillisecond(Date date1, Date date2)
-    {
+    public static int differentDaysByMillisecond(Date date1, Date date2) {
         return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
     }
 

+ 1 - 1
ruoyi-demo/pom.xml

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

+ 2 - 2
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestBatchController.java

@@ -45,7 +45,7 @@ public class TestBatchController extends BaseController {
         List<TestDemo> list = new ArrayList<>();
         for (int i = 0; i < 1000; i++) {
             TestDemo testDemo = new TestDemo();
-            testDemo.setOrderNum(-1L);
+            testDemo.setOrderNum(-1);
             testDemo.setTestKey("批量新增");
             testDemo.setValue("测试新增");
             list.add(testDemo);
@@ -65,7 +65,7 @@ public class TestBatchController extends BaseController {
         List<TestDemo> list = new ArrayList<>();
         for (int i = 0; i < 1000; i++) {
             TestDemo testDemo = new TestDemo();
-            testDemo.setOrderNum(-1L);
+            testDemo.setOrderNum(-1);
             testDemo.setTestKey("批量新增");
             testDemo.setValue("测试新增");
             list.add(testDemo);        }

+ 1 - 1
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestDemoController.java

@@ -117,7 +117,7 @@ public class TestDemoController extends BaseController {
     @ApiOperation("新增测试单表")
     @SaCheckPermission("demo:demo:add")
     @Log(title = "测试单表", businessType = BusinessType.INSERT)
-    @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "不允许重复提交")
+    @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "{repeat.submit.message}")
     @PostMapping()
     public R<Void> add(@RequestBody TestDemoBo bo) {
         // 使用校验工具对标 @Validated(AddGroup.class) 注解

+ 1 - 1
ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestDemo.java

@@ -39,7 +39,7 @@ public class TestDemo extends BaseEntity {
      * 排序号
      */
     @OrderBy(asc = false, sort = 1)
-    private Long orderNum;
+    private Integer orderNum;
 
     /**
      * key键

+ 1 - 1
ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestTree.java

@@ -17,7 +17,7 @@ import lombok.EqualsAndHashCode;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("test_tree")
-public class TestTree extends TreeEntity {
+public class TestTree extends TreeEntity<TestTree> {
 
     private static final long serialVersionUID = 1L;
 

+ 1 - 1
ruoyi-demo/src/main/java/com/ruoyi/demo/domain/bo/TestTreeBo.java

@@ -21,7 +21,7 @@ import javax.validation.constraints.NotNull;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ApiModel("测试树表业务对象")
-public class TestTreeBo extends TreeEntity {
+public class TestTreeBo extends TreeEntity<TestTreeBo> {
 
     /**
      * 主键

+ 1 - 1
ruoyi-extend/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.0.1</version>
+        <version>4.1.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.0.1</version>
+        <version>4.1.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>

+ 2 - 5
ruoyi-extend/ruoyi-xxl-job-admin/pom.xml

@@ -4,14 +4,12 @@
     <parent>
         <artifactId>ruoyi-extend</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.0.1</version>
+        <version>4.1.0</version>
     </parent>
     <artifactId>ruoyi-xxl-job-admin</artifactId>
     <packaging>jar</packaging>
 
     <properties>
-        <mybatis-spring-boot-starter.version>2.1.4</mybatis-spring-boot-starter.version>
-        <mysql-connector-java.version>8.0.23</mysql-connector-java.version>
     </properties>
 
     <dependencyManagement>
@@ -62,13 +60,12 @@
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
-            <version>${mybatis-spring-boot-starter.version}</version>
+            <version>${spring-boot.mybatis}</version>
         </dependency>
         <!-- mysql -->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
-            <version>${mysql-connector-java.version}</version>
         </dependency>
 
         <dependency>

+ 1 - 1
ruoyi-framework/pom.xml

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

+ 2 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java

@@ -3,6 +3,7 @@ package com.ruoyi.framework.aspectj;
 import com.ruoyi.common.annotation.RateLimiter;
 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.redis.RedisUtils;
 import lombok.extern.slf4j.Slf4j;
@@ -37,7 +38,7 @@ public class RateLimiterAspect {
             }
             long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
             if (number == -1) {
-                throw new ServiceException("访问过于频繁,请稍候再试");
+                throw new ServiceException(MessageUtils.message("rate.limiter.message"));
             }
             log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
         } catch (ServiceException e) {

+ 42 - 5
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RepeatSubmitAspect.java

@@ -5,15 +5,18 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.crypto.SecureUtil;
 import com.ruoyi.common.annotation.RepeatSubmit;
 import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.JsonUtils;
+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 com.ruoyi.framework.config.properties.RepeatSubmitProperties;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
 import org.springframework.stereotype.Component;
@@ -27,7 +30,7 @@ import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /**
- * 防止重复提交
+ * 防止重复提交(参考美团GTIS防重系统)
  *
  * @author Lion Li
  */
@@ -37,12 +40,12 @@ import java.util.concurrent.TimeUnit;
 @Component
 public class RepeatSubmitAspect {
 
-    private final RepeatSubmitProperties repeatSubmitProperties;
+    private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
 
     @Before("@annotation(repeatSubmit)")
     public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
         // 如果注解不为0 则使用注解数值
-        long interval = repeatSubmitProperties.getInterval();
+        long interval = 0;
         if (repeatSubmit.interval() > 0) {
             interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
         }
@@ -64,12 +67,46 @@ public class RepeatSubmitAspect {
         String key = RedisUtils.getCacheObject(cacheRepeatKey);
         if (key == null) {
             RedisUtils.setCacheObject(cacheRepeatKey, "", interval, TimeUnit.MILLISECONDS);
+            KEY_CACHE.set(cacheRepeatKey);
         } else {
-            throw new ServiceException(repeatSubmit.message());
+            String message = repeatSubmit.message();
+            if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
+                message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
+            }
+            throw new ServiceException(message);
         }
     }
 
     /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
+        if (jsonResult instanceof R) {
+            R<?> r = (R<?>) jsonResult;
+            if (r.getCode() == R.SUCCESS) {
+                return;
+            }
+            RedisUtils.deleteObject(KEY_CACHE.get());
+            KEY_CACHE.remove();
+        }
+    }
+
+    /**
+     * 拦截异常操作
+     *
+     * @param joinPoint 切点
+     * @param e         异常
+     */
+    @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
+        RedisUtils.deleteObject(KEY_CACHE.get());
+        KEY_CACHE.remove();
+    }
+
+    /**
      * 参数拼装
      */
     private String argsArrayToString(Object[] paramsArray) {

+ 13 - 13
ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java

@@ -18,10 +18,10 @@ import java.awt.*;
 @Configuration
 public class CaptchaConfig {
 
-    private final int width = 160;
-    private final int height = 60;
-    private final Color background = Color.PINK;
-    private final Font font = new Font("Arial", Font.BOLD, 48);
+    private static final int WIDTH = 160;
+    private static final int HEIGHT = 60;
+    private static final Color BACKGROUND = Color.PINK;
+    private static final Font FONT = new Font("Arial", Font.BOLD, 48);
 
     /**
      * 圆圈干扰验证码
@@ -29,9 +29,9 @@ public class CaptchaConfig {
     @Lazy
     @Bean
     public CircleCaptcha circleCaptcha() {
-        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(width, height);
-        captcha.setBackground(background);
-        captcha.setFont(font);
+        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
+        captcha.setBackground(BACKGROUND);
+        captcha.setFont(FONT);
         return captcha;
     }
 
@@ -41,9 +41,9 @@ public class CaptchaConfig {
     @Lazy
     @Bean
     public LineCaptcha lineCaptcha() {
-        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(width, height);
-        captcha.setBackground(background);
-        captcha.setFont(font);
+        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
+        captcha.setBackground(BACKGROUND);
+        captcha.setFont(FONT);
         return captcha;
     }
 
@@ -53,9 +53,9 @@ public class CaptchaConfig {
     @Lazy
     @Bean
     public ShearCaptcha shearCaptcha() {
-        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(width, height);
-        captcha.setBackground(background);
-        captcha.setFont(font);
+        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
+        captcha.setBackground(BACKGROUND);
+        captcha.setFont(FONT);
         return captcha;
     }
 

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

@@ -53,8 +53,7 @@ public class RedisConfig extends CachingConfigurerSupport {
         Config config = new Config();
         config.setThreads(redissonProperties.getThreads())
             .setNettyThreads(redissonProperties.getNettyThreads())
-            .setCodec(JsonJacksonCodec.INSTANCE)
-            .setTransportMode(redissonProperties.getTransportMode());
+            .setCodec(JsonJacksonCodec.INSTANCE);
 
         RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
         if (ObjectUtil.isNotNull(singleServerConfig)) {
@@ -65,8 +64,6 @@ public class RedisConfig extends CachingConfigurerSupport {
                 .setDatabase(redisProperties.getDatabase())
                 .setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
                 .setTimeout(singleServerConfig.getTimeout())
-                .setRetryAttempts(singleServerConfig.getRetryAttempts())
-                .setRetryInterval(singleServerConfig.getRetryInterval())
                 .setClientName(singleServerConfig.getClientName())
                 .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
                 .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
@@ -87,11 +84,8 @@ public class RedisConfig extends CachingConfigurerSupport {
                 .setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
                 .setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
                 .setTimeout(clusterServersConfig.getTimeout())
-                .setRetryAttempts(clusterServersConfig.getRetryAttempts())
-                .setRetryInterval(clusterServersConfig.getRetryInterval())
                 .setClientName(clusterServersConfig.getClientName())
                 .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
-                .setPingConnectionInterval(clusterServersConfig.getPingConnectionInterval())
                 .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
                 .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
                 .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
@@ -144,8 +138,6 @@ public class RedisConfig extends CachingConfigurerSupport {
      *   threads: 16
      *   # Netty线程池数量
      *   nettyThreads: 32
-     *   # 传输模式
-     *   transportMode: "NIO"
      *   # 集群配置
      *   clusterServersConfig:
      *     # 客户端名称
@@ -160,14 +152,8 @@ public class RedisConfig extends CachingConfigurerSupport {
      *     slaveConnectionPoolSize: 64
      *     # 连接空闲超时,单位:毫秒
      *     idleConnectionTimeout: 10000
-     *     # ping连接间隔
-     *     pingConnectionInterval: 1000
      *     # 命令等待超时,单位:毫秒
      *     timeout: 3000
-     *     # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
-     *     retryAttempts: 3
-     *     # 命令重试发送时间间隔,单位:毫秒
-     *     retryInterval: 1500
      *     # 发布和订阅连接池大小
      *     subscriptionConnectionPoolSize: 50
      *     # 读取模式

+ 11 - 9
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SaTokenConfig.java

@@ -5,7 +5,7 @@ import cn.dev33.satoken.interceptor.SaRouteInterceptor;
 import cn.dev33.satoken.jwt.StpLogicJwtForStyle;
 import cn.dev33.satoken.router.SaRouter;
 import cn.dev33.satoken.stp.StpLogic;
-import cn.hutool.core.util.ObjectUtil;
+import cn.dev33.satoken.stp.StpUtil;
 import com.ruoyi.common.helper.LoginHelper;
 import com.ruoyi.framework.config.properties.SecurityProperties;
 import lombok.RequiredArgsConstructor;
@@ -43,15 +43,17 @@ public class SaTokenConfig implements WebMvcConfigurer {
                 .match("/**")
                 // 排除下不需要拦截的
                 .notMatch(securityProperties.getExcludes())
+                // 对未排除的路径进行检查
                 .check(() -> {
-                    Long userId = LoginHelper.getUserId();
-                    if (ObjectUtil.isNotNull(userId)) {
-                        // 有效率影响 用于临时测试
-                        // if (log.isDebugEnabled()) {
-                        //     log.debug("剩余有效时间: {}", StpUtil.getTokenTimeout());
-                        //     log.debug("临时有效时间: {}", StpUtil.getTokenActivityTimeout());
-                        // }
-                    }
+                    // 检查是否登录 是否有token
+                    StpUtil.checkLogin();
+
+                    // 有效率影响 用于临时测试
+                    // if (log.isDebugEnabled()) {
+                    //     log.debug("剩余有效时间: {}", StpUtil.getTokenTimeout());
+                    //     log.debug("临时有效时间: {}", StpUtil.getTokenActivityTimeout());
+                    // }
+
                 });
         }) {
             @SuppressWarnings("all")

+ 9 - 7
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java

@@ -1,7 +1,6 @@
 package com.ruoyi.framework.config;
 
 import com.ruoyi.common.utils.Threads;
-import com.ruoyi.common.utils.reflect.ReflectUtils;
 import com.ruoyi.framework.config.properties.ThreadPoolProperties;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -10,7 +9,6 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
-import java.util.concurrent.RejectedExecutionHandler;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -23,6 +21,11 @@ import java.util.concurrent.ThreadPoolExecutor;
 @Configuration
 public class ThreadPoolConfig {
 
+    /**
+     * 核心线程数 = cpu 核心数 + 1
+     */
+    private final int core = Runtime.getRuntime().availableProcessors() + 1;
+
     @Autowired
     private ThreadPoolProperties threadPoolProperties;
 
@@ -30,12 +33,11 @@ public class ThreadPoolConfig {
     @ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
     public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-        executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
-        executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
+        executor.setMaxPoolSize(core);
+        executor.setCorePoolSize(core * 2);
         executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
         executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
-        RejectedExecutionHandler handler = ReflectUtils.newInstance(threadPoolProperties.getRejectedExecutionHandler().getClazz());
-        executor.setRejectedExecutionHandler(handler);
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
         return executor;
     }
 
@@ -44,7 +46,7 @@ public class ThreadPoolConfig {
      */
     @Bean(name = "scheduledExecutorService")
     protected ScheduledExecutorService scheduledExecutorService() {
-        return new ScheduledThreadPoolExecutor(threadPoolProperties.getCorePoolSize(),
+        return new ScheduledThreadPoolExecutor(core,
             new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
             new ThreadPoolExecutor.CallerRunsPolicy()) {
             @Override

+ 0 - 31
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java

@@ -4,7 +4,6 @@ import lombok.Data;
 import lombok.NoArgsConstructor;
 import org.redisson.config.ReadMode;
 import org.redisson.config.SubscriptionMode;
-import org.redisson.config.TransportMode;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
@@ -31,11 +30,6 @@ public class RedissonProperties {
     private int nettyThreads;
 
     /**
-     * 传输模式
-     */
-    private TransportMode transportMode;
-
-    /**
      * 单机服务配置
      */
     private SingleServerConfig singleServerConfig;
@@ -80,16 +74,6 @@ public class RedissonProperties {
         private int timeout;
 
         /**
-         * 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
-         */
-        private int retryAttempts;
-
-        /**
-         * 命令重试发送时间间隔,单位:毫秒
-         */
-        private int retryInterval;
-
-        /**
          * 发布和订阅连接池大小
          */
         private int subscriptionConnectionPoolSize;
@@ -131,26 +115,11 @@ public class RedissonProperties {
         private int idleConnectionTimeout;
 
         /**
-         * ping超时
-         */
-        private int pingConnectionInterval;
-
-        /**
          * 命令等待超时,单位:毫秒
          */
         private int timeout;
 
         /**
-         * 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
-         */
-        private int retryAttempts;
-
-        /**
-         * 命令重试发送时间间隔,单位:毫秒
-         */
-        private int retryInterval;
-
-        /**
          * 发布和订阅连接池大小
          */
         private int subscriptionConnectionPoolSize;

+ 0 - 22
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RepeatSubmitProperties.java

@@ -1,22 +0,0 @@
-package com.ruoyi.framework.config.properties;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * 重复提交 配置属性
- *
- * @author Lion Li
- */
-@Data
-@Component
-@ConfigurationProperties(prefix = "repeat-submit")
-public class RepeatSubmitProperties {
-
-    /**
-     * 间隔时间(毫秒)
-     */
-    private int interval;
-
-}

+ 0 - 16
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/ThreadPoolProperties.java

@@ -1,6 +1,5 @@
 package com.ruoyi.framework.config.properties;
 
-import com.ruoyi.common.enums.ThreadPoolRejectedPolicy;
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
@@ -21,16 +20,6 @@ public class ThreadPoolProperties {
     private boolean enabled;
 
     /**
-     * 核心线程池大小
-     */
-    private int corePoolSize;
-
-    /**
-     * 最大可创建的线程数
-     */
-    private int maxPoolSize;
-
-    /**
      * 队列最大长度
      */
     private int queueCapacity;
@@ -40,9 +29,4 @@ public class ThreadPoolProperties {
      */
     private int keepAliveSeconds;
 
-    /**
-     * 线程池对拒绝任务(无线程可用)的处理策略
-     */
-    private ThreadPoolRejectedPolicy rejectedExecutionHandler;
-
 }

+ 4 - 4
ruoyi-framework/src/main/java/com/ruoyi/framework/listener/UserActionListener.java

@@ -53,7 +53,7 @@ public class UserActionListener implements SaTokenListener {
             dto.setUserName(user.getUsername());
             dto.setDeptName(user.getDeptName());
             RedisUtils.setCacheObject(Constants.ONLINE_TOKEN_KEY + tokenValue, dto, tokenConfig.getTimeout(), TimeUnit.SECONDS);
-            log.info("user doLogin, useId:{}, token:{}", loginId, tokenValue);
+            log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
         } else if (userType == UserType.APP_USER) {
             // app端 自行根据业务编写
         }
@@ -65,7 +65,7 @@ public class UserActionListener implements SaTokenListener {
     @Override
     public void doLogout(String loginType, Object loginId, String tokenValue) {
         RedisUtils.deleteObject(Constants.ONLINE_TOKEN_KEY + tokenValue);
-        log.info("user doLogout, useId:{}, token:{}", loginId, tokenValue);
+        log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
     }
 
     /**
@@ -74,7 +74,7 @@ public class UserActionListener implements SaTokenListener {
     @Override
     public void doKickout(String loginType, Object loginId, String tokenValue) {
         RedisUtils.deleteObject(Constants.ONLINE_TOKEN_KEY + tokenValue);
-        log.info("user doLogoutByLoginId, useId:{}, token:{}", loginId, tokenValue);
+        log.info("user doLogoutByLoginId, userId:{}, token:{}", loginId, tokenValue);
     }
 
     /**
@@ -83,7 +83,7 @@ public class UserActionListener implements SaTokenListener {
     @Override
     public void doReplaced(String loginType, Object loginId, String tokenValue) {
         RedisUtils.deleteObject(Constants.ONLINE_TOKEN_KEY + tokenValue);
-        log.info("user doReplaced, useId:{}, token:{}", loginId, tokenValue);
+        log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
     }
 
     /**

+ 14 - 3
ruoyi-framework/src/main/java/com/ruoyi/framework/satoken/service/SaInterfaceImpl.java → ruoyi-framework/src/main/java/com/ruoyi/framework/satoken/service/SaPermissionImpl.java

@@ -9,9 +9,17 @@ import org.springframework.stereotype.Component;
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * sa-token 权限管理实现类
+ *
+ * @author Lion Li
+ */
 @Component
-public class SaInterfaceImpl implements StpInterface {
+public class SaPermissionImpl implements StpInterface {
 
+    /**
+     * 获取菜单权限列表
+     */
     @Override
     public List<String> getPermissionList(Object loginId, String loginType) {
         LoginUser loginUser = LoginHelper.getLoginUser();
@@ -19,11 +27,14 @@ public class SaInterfaceImpl implements StpInterface {
         if (userType == UserType.SYS_USER) {
             return new ArrayList<>(loginUser.getMenuPermission());
         } else if (userType == UserType.APP_USER) {
-            // app端权限返回 自行根据业务编写
+            // 其他端 自行根据业务编写
         }
         return new ArrayList<>();
     }
 
+    /**
+     * 获取角色权限列表
+     */
     @Override
     public List<String> getRoleList(Object loginId, String loginType) {
         LoginUser loginUser = LoginHelper.getLoginUser();
@@ -31,7 +42,7 @@ public class SaInterfaceImpl implements StpInterface {
         if (userType == UserType.SYS_USER) {
             return new ArrayList<>(loginUser.getRolePermission());
         } else if (userType == UserType.APP_USER) {
-            // app端权限返回 自行根据业务编写
+            // 其他端 自行根据业务编写
         }
         return new ArrayList<>();
     }

+ 27 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java

@@ -9,7 +9,9 @@ import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.exception.DemoModeException;
 import com.ruoyi.common.exception.ServiceException;
 import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.MyBatisSystemException;
 import org.springframework.context.support.DefaultMessageSourceResolvable;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.validation.BindException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -72,6 +74,31 @@ public class GlobalExceptionHandler {
     }
 
     /**
+     * 主键或UNIQUE索引,数据重复异常
+     */
+    @ExceptionHandler(DuplicateKeyException.class)
+    public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
+        return R.fail("数据库中已存在该记录,请联系管理员确认");
+    }
+
+    /**
+     * Mybatis系统异常 通用处理
+     */
+    @ExceptionHandler(MyBatisSystemException.class)
+    public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        String message = e.getMessage();
+        if (message.contains("CannotFindDataSourceException")) {
+            log.error("请求地址'{}', 未找到数据源", requestURI);
+            return R.fail("未找到数据源,请联系管理员确认");
+        }
+        log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
+        return R.fail(message);
+    }
+
+    /**
      * 业务异常
      */
     @ExceptionHandler(ServiceException.class)

+ 1 - 1
ruoyi-generator/pom.xml

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

+ 7 - 9
ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java

@@ -5,13 +5,12 @@ import cn.hutool.core.convert.Convert;
 import cn.hutool.core.io.IoUtil;
 import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.enums.BusinessType;
 import com.ruoyi.generator.domain.GenTable;
 import com.ruoyi.generator.domain.GenTableColumn;
-import com.ruoyi.generator.service.IGenTableColumnService;
 import com.ruoyi.generator.service.IGenTableService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -38,7 +37,6 @@ import java.util.Map;
 public class GenController extends BaseController {
 
     private final IGenTableService genTableService;
-    private final IGenTableColumnService genTableColumnService;
 
     /**
      * 查询代码生成列表
@@ -55,11 +53,11 @@ public class GenController extends BaseController {
      */
     @ApiOperation("修改代码生成业务")
     @SaCheckPermission("tool:gen:query")
-    @GetMapping(value = "/{talbleId}")
-    public R<Map<String, Object>> getInfo(@PathVariable Long talbleId) {
-        GenTable table = genTableService.selectGenTableById(talbleId);
+    @GetMapping(value = "/{tableId}")
+    public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
+        GenTable table = genTableService.selectGenTableById(tableId);
         List<GenTable> tables = genTableService.selectGenTableAll();
-        List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(talbleId);
+        List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("info", table);
         map.put("rows", list);
@@ -82,10 +80,10 @@ public class GenController extends BaseController {
      */
     @ApiOperation("查询数据表字段列表")
     @SaCheckPermission("tool:gen:list")
-    @GetMapping(value = "/column/{talbleId}")
+    @GetMapping(value = "/column/{tableId}")
     public TableDataInfo<GenTableColumn> columnList(Long tableId) {
         TableDataInfo<GenTableColumn> dataInfo = new TableDataInfo<>();
-        List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId);
+        List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
         dataInfo.setRows(list);
         dataInfo.setTotal(list.size());
         return dataInfo;

+ 6 - 0
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java

@@ -150,6 +150,12 @@ public class GenTable extends BaseEntity {
     @TableField(exist = false)
     private String treeName;
 
+    /*
+     * 菜单id列表
+     */
+    @TableField(exist = false)
+    private List<Long> menuIds;
+
     /**
      * 上级菜单ID字段
      */

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

@@ -8,7 +8,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
 import com.ruoyi.common.utils.StringUtils;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import lombok.experimental.Accessors;
+import org.apache.ibatis.type.JdbcType;
 
 import javax.validation.constraints.NotBlank;
 
@@ -42,7 +42,7 @@ public class GenTableColumn extends BaseEntity {
     /**
      * 列描述
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String columnComment;
 
     /**
@@ -64,43 +64,43 @@ public class GenTableColumn extends BaseEntity {
     /**
      * 是否主键(1是)
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String isPk;
 
     /**
      * 是否自增(1是)
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String isIncrement;
 
     /**
      * 是否必填(1是)
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String isRequired;
 
     /**
      * 是否为插入字段(1是)
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String isInsert;
 
     /**
      * 是否编辑字段(1是)
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String isEdit;
 
     /**
      * 是否列表字段(1是)
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String isList;
 
     /**
      * 是否查询字段(1是)
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
     private String isQuery;
 
     /**

+ 2 - 15
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java

@@ -16,26 +16,13 @@ import java.util.List;
 @InterceptorIgnore(dataPermission = "true")
 public interface GenTableMapper extends BaseMapperPlus<GenTableMapper, GenTable, GenTable> {
 
-
-    Page<GenTable> selectPageGenTableList(@Param("page") Page<GenTable> page, @Param("genTable") GenTable genTable);
-
-    Page<GenTable> selectPageDbTableList(@Param("page") Page<GenTable> page, @Param("genTable") GenTable genTable);
-
-    /**
-     * 查询业务列表
-     *
-     * @param genTable 业务信息
-     * @return 业务集合
-     */
-    List<GenTable> selectGenTableList(GenTable genTable);
-
     /**
      * 查询据库列表
      *
-     * @param genTable 业务信息
+     * @param genTable 查询条件
      * @return 数据库表集合
      */
-    List<GenTable> selectDbTableList(GenTable genTable);
+    Page<GenTable> selectPageDbTableList(@Param("page") Page<GenTable> page, @Param("genTable") GenTable genTable);
 
     /**
      * 查询据库列表

+ 0 - 68
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java

@@ -1,68 +0,0 @@
-package com.ruoyi.generator.service;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.ruoyi.generator.domain.GenTableColumn;
-import com.ruoyi.generator.mapper.GenTableColumnMapper;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Service;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * 业务字段 服务层实现
- *
- * @author Lion Li
- */
-@RequiredArgsConstructor
-@Service
-public class GenTableColumnServiceImpl implements IGenTableColumnService {
-
-    private final GenTableColumnMapper baseMapper;
-
-    /**
-     * 查询业务字段列表
-     *
-     * @param tableId 业务字段编号
-     * @return 业务字段集合
-     */
-    @Override
-    public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId) {
-        return baseMapper.selectList(new LambdaQueryWrapper<GenTableColumn>()
-            .eq(GenTableColumn::getTableId, tableId)
-            .orderByAsc(GenTableColumn::getSort));
-    }
-
-    /**
-     * 新增业务字段
-     *
-     * @param genTableColumn 业务字段信息
-     * @return 结果
-     */
-    @Override
-    public int insertGenTableColumn(GenTableColumn genTableColumn) {
-        return baseMapper.insert(genTableColumn);
-    }
-
-    /**
-     * 修改业务字段
-     *
-     * @param genTableColumn 业务字段信息
-     * @return 结果
-     */
-    @Override
-    public int updateGenTableColumn(GenTableColumn genTableColumn) {
-        return baseMapper.updateById(genTableColumn);
-    }
-
-    /**
-     * 删除业务字段对象
-     *
-     * @param ids 需要删除的数据ID
-     * @return 结果
-     */
-    @Override
-    public int deleteGenTableColumnByIds(String ids) {
-        return baseMapper.deleteBatchIds(Arrays.asList(ids.split(",")));
-    }
-}

+ 46 - 31
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java

@@ -3,8 +3,13 @@ package com.ruoyi.generator.service;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.lang.Dict;
+import cn.hutool.core.lang.Snowflake;
+import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.constant.GenConstants;
@@ -28,7 +33,6 @@ import org.apache.velocity.Template;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.Velocity;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -46,6 +50,7 @@ import java.util.zip.ZipOutputStream;
  *
  * @author Lion Li
  */
+@DS("#header.datasource")
 @Slf4j
 @RequiredArgsConstructor
 @Service
@@ -55,6 +60,19 @@ public class GenTableServiceImpl implements IGenTableService {
     private final GenTableColumnMapper genTableColumnMapper;
 
     /**
+     * 查询业务字段列表
+     *
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+    @Override
+    public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId) {
+        return genTableColumnMapper.selectList(new LambdaQueryWrapper<GenTableColumn>()
+            .eq(GenTableColumn::getTableId, tableId)
+            .orderByAsc(GenTableColumn::getSort));
+    }
+
+    /**
      * 查询业务信息
      *
      * @param id 业务ID
@@ -69,36 +87,25 @@ public class GenTableServiceImpl implements IGenTableService {
 
     @Override
     public TableDataInfo<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery) {
-        Page<GenTable> page = baseMapper.selectPageGenTableList(pageQuery.build(), genTable);
+        Page<GenTable> page = baseMapper.selectPage(pageQuery.build(), this.buildGenTableQueryWrapper(genTable));
         return TableDataInfo.build(page);
     }
 
-    @Override
-    public TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery) {
-        Page<GenTable> page = baseMapper.selectPageDbTableList(pageQuery.build(), genTable);
-        return TableDataInfo.build(page);
+    private QueryWrapper<GenTable> buildGenTableQueryWrapper(GenTable genTable) {
+        Map<String, Object> params = genTable.getParams();
+        QueryWrapper<GenTable> wrapper = Wrappers.query();
+        wrapper.like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName()))
+            .like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
+            .between(params.get("beginTime") != null && params.get("endTime") != null,
+                "create_time", params.get("beginTime"), params.get("endTime"));
+        return wrapper;
     }
 
-    /**
-     * 查询业务列表
-     *
-     * @param genTable 业务信息
-     * @return 业务集合
-     */
-    @Override
-    public List<GenTable> selectGenTableList(GenTable genTable) {
-        return baseMapper.selectGenTableList(genTable);
-    }
 
-    /**
-     * 查询据库列表
-     *
-     * @param genTable 业务信息
-     * @return 数据库表集合
-     */
     @Override
-    public List<GenTable> selectDbTableList(GenTable genTable) {
-        return baseMapper.selectDbTableList(genTable);
+    public TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery) {
+        Page<GenTable> page = baseMapper.selectPageDbTableList(pageQuery.build(), genTable);
+        return TableDataInfo.build(page);
     }
 
     /**
@@ -129,7 +136,6 @@ public class GenTableServiceImpl implements IGenTableService {
      * @return 结果
      */
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void updateGenTable(GenTable genTable) {
         String options = JsonUtils.toJsonString(genTable.getParams());
         genTable.setOptions(options);
@@ -148,7 +154,6 @@ public class GenTableServiceImpl implements IGenTableService {
      * @return 结果
      */
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void deleteGenTableByIds(Long[] tableIds) {
         List<Long> ids = Arrays.asList(tableIds);
         baseMapper.deleteBatchIds(ids);
@@ -161,7 +166,6 @@ public class GenTableServiceImpl implements IGenTableService {
      * @param tableList 导入表列表
      */
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void importGenTable(List<GenTable> tableList) {
         String operName = LoginHelper.getUsername();
         try {
@@ -198,6 +202,12 @@ public class GenTableServiceImpl implements IGenTableService {
         Map<String, String> dataMap = new LinkedHashMap<>();
         // 查询表信息
         GenTable table = baseMapper.selectGenTableById(tableId);
+        Snowflake snowflake = IdUtil.getSnowflake();
+        List<Long> menuIds = new ArrayList<>();
+        for (int i = 0; i < 6; i++) {
+            menuIds.add(snowflake.nextId());
+        }
+        table.setMenuIds(menuIds);
         // 设置主子表信息
         setSubTable(table);
         // 设置主键列信息
@@ -275,7 +285,6 @@ public class GenTableServiceImpl implements IGenTableService {
      * @param tableName 表名称
      */
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void synchDb(String tableName) {
         GenTable table = baseMapper.selectGenTableByName(tableName);
         List<GenTableColumn> tableColumns = table.getColumns();
@@ -299,9 +308,8 @@ public class GenTableServiceImpl implements IGenTableService {
                     column.setQueryType(prevColumn.getQueryType());
                 }
                 if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
-                        && (column.isInsert() || column.isEdit())
-                        && ((column.isUsableColumn()) || (!column.isSuperColumn())))
-                {
+                    && (column.isInsert() || column.isEdit())
+                    && ((column.isUsableColumn()) || (!column.isSuperColumn()))) {
                     // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项
                     column.setIsRequired(prevColumn.getIsRequired());
                     column.setHtmlType(prevColumn.getHtmlType());
@@ -345,6 +353,12 @@ public class GenTableServiceImpl implements IGenTableService {
     private void generatorCode(String tableName, ZipOutputStream zip) {
         // 查询表信息
         GenTable table = baseMapper.selectGenTableByName(tableName);
+        Snowflake snowflake = IdUtil.getSnowflake();
+        List<Long> menuIds = new ArrayList<>();
+        for (int i = 0; i < 6; i++) {
+            menuIds.add(snowflake.nextId());
+        }
+        table.setMenuIds(menuIds);
         // 设置主子表信息
         setSubTable(table);
         // 设置主键列信息
@@ -477,3 +491,4 @@ public class GenTableServiceImpl implements IGenTableService {
         return genPath + File.separator + VelocityUtils.getFileName(template, table);
     }
 }
+

+ 0 - 44
ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java

@@ -1,44 +0,0 @@
-package com.ruoyi.generator.service;
-
-import com.ruoyi.generator.domain.GenTableColumn;
-
-import java.util.List;
-
-/**
- * 业务字段 服务层
- *
- * @author Lion Li
- */
-public interface IGenTableColumnService {
-    /**
-     * 查询业务字段列表
-     *
-     * @param tableId 业务字段编号
-     * @return 业务字段集合
-     */
-    List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
-
-    /**
-     * 新增业务字段
-     *
-     * @param genTableColumn 业务字段信息
-     * @return 结果
-     */
-    int insertGenTableColumn(GenTableColumn genTableColumn);
-
-    /**
-     * 修改业务字段
-     *
-     * @param genTableColumn 业务字段信息
-     * @return 结果
-     */
-    int updateGenTableColumn(GenTableColumn genTableColumn);
-
-    /**
-     * 删除业务字段信息
-     *
-     * @param ids 需要删除的数据ID
-     * @return 结果
-     */
-    int deleteGenTableColumnByIds(String ids);
-}

+ 10 - 7
ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java

@@ -3,6 +3,7 @@ package com.ruoyi.generator.service;
 import com.ruoyi.common.core.domain.PageQuery;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.generator.domain.GenTable;
+import com.ruoyi.generator.domain.GenTableColumn;
 
 import java.util.List;
 import java.util.Map;
@@ -14,11 +15,13 @@ import java.util.Map;
  */
 public interface IGenTableService {
 
-
-    TableDataInfo<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery);
-
-
-    TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery);
+    /**
+     * 查询业务字段列表
+     *
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+    List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
 
     /**
      * 查询业务列表
@@ -26,7 +29,7 @@ public interface IGenTableService {
      * @param genTable 业务信息
      * @return 业务集合
      */
-    List<GenTable> selectGenTableList(GenTable genTable);
+    TableDataInfo<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery);
 
     /**
      * 查询据库列表
@@ -34,7 +37,7 @@ public interface IGenTableService {
      * @param genTable 业务信息
      * @return 数据库表集合
      */
-    List<GenTable> selectDbTableList(GenTable genTable);
+    TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery);
 
     /**
      * 查询据库列表

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

@@ -5,6 +5,8 @@ import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.generator.config.GenConfig;
 import com.ruoyi.generator.domain.GenTable;
 import com.ruoyi.generator.domain.GenTableColumn;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
 import org.apache.commons.lang3.RegExUtils;
 
 import java.util.Arrays;
@@ -14,6 +16,7 @@ import java.util.Arrays;
  *
  * @author ruoyi
  */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class GenUtils {
 
     /**

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

@@ -1,6 +1,8 @@
 package com.ruoyi.generator.util;
 
 import com.ruoyi.common.constant.Constants;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
 import org.apache.velocity.app.Velocity;
 
 import java.util.Properties;
@@ -10,6 +12,7 @@ import java.util.Properties;
  *
  * @author ruoyi
  */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class VelocityInitializer {
 
     /**

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

@@ -5,11 +5,14 @@ import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Dict;
 import cn.hutool.core.util.ObjectUtil;
 import com.ruoyi.common.constant.GenConstants;
+import com.ruoyi.common.helper.DataBaseHelper;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.JsonUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.generator.domain.GenTable;
 import com.ruoyi.generator.domain.GenTableColumn;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
 import org.apache.velocity.VelocityContext;
 
 import java.util.*;
@@ -19,6 +22,7 @@ import java.util.*;
  *
  * @author ruoyi
  */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class VelocityUtils {
 
     /**
@@ -135,7 +139,15 @@ public class VelocityUtils {
         templates.add("vm/java/serviceImpl.java.vm");
         templates.add("vm/java/controller.java.vm");
         templates.add("vm/xml/mapper.xml.vm");
-        templates.add("vm/sql/sql.vm");
+        if (DataBaseHelper.isOracle()) {
+            templates.add("vm/sql/oracle/sql.vm");
+        } else if (DataBaseHelper.isPostgerSql()) {
+            templates.add("vm/sql/postgres/sql.vm");
+        } else if (DataBaseHelper.isSqlServer()) {
+            templates.add("vm/sql/sqlserver/sql.vm");
+        } else {
+            templates.add("vm/sql/sql.vm");
+        }
         templates.add("vm/js/api.js.vm");
         if (GenConstants.TPL_CRUD.equals(tplCategory)) {
             templates.add("vm/vue/index.vue.vm");

+ 79 - 3
ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml

@@ -30,9 +30,85 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
-        select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type
-        from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
-        order by ordinal_position
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">
+            select column_name,
+                   (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required,
+                   (case when column_key = 'PRI' then '1' else '0' end) as is_pk,
+                   ordinal_position as sort,
+                   column_comment,
+                   (case when extra = 'auto_increment' then '1' else '0' end) as is_increment,
+                   column_type
+            from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
+            order by ordinal_position
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">
+            select lower(temp.column_name) as column_name,
+                    (case when (temp.nullable = 'N'  and  temp.constraint_type != 'P') then '1' else null end) as is_required,
+                    (case when temp.constraint_type = 'P' then '1' else '0' end) as is_pk,
+                    temp.column_id as sort,
+                    temp.comments as column_comment,
+                    (case when temp.constraint_type = 'P' then '1' else '0' end) as is_increment,
+                    lower(temp.data_type) as column_type
+            from (
+                select col.column_id, col.column_name,col.nullable, col.data_type, colc.comments, uc.constraint_type, row_number()
+                    over (partition by col.column_name order by uc.constraint_type desc) as row_flg
+                from user_tab_columns col
+                left join user_col_comments colc on colc.table_name = col.table_name and colc.column_name = col.column_name
+                left join user_cons_columns ucc on ucc.table_name = col.table_name and ucc.column_name = col.column_name
+                left join user_constraints uc on uc.constraint_name = ucc.constraint_name
+                where col.table_name = upper(#{tableName})
+            ) temp
+            WHERE temp.row_flg = 1
+            ORDER BY temp.column_id
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isPostgerSql()">
+            SELECT column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
+            FROM (
+                SELECT c.relname AS table_name,
+                       a.attname AS column_name,
+                       d.description AS column_comment,
+                       CASE WHEN a.attnotnull AND con.conname IS NULL THEN 1 ELSE 0
+                       END AS is_required,
+                       CASE WHEN con.conname IS NOT NULL THEN 1 ELSE 0
+                       END AS is_pk,
+                       a.attnum AS sort,
+                       CASE WHEN "position"(pg_get_expr(ad.adbin, ad.adrelid),
+                           ((c.relname::text || '_'::text) || a.attname::text) || '_seq'::text) > 0 THEN 1 ELSE 0
+                       END AS is_increment,
+                       btrim(
+                           CASE WHEN t.typelem <![CDATA[ <> ]]> 0::oid AND t.typlen = '-1'::integer THEN 'ARRAY'::text ELSE
+                                CASE WHEN t.typtype = 'd'::"char" THEN format_type(t.typbasetype, NULL::integer)
+                                ELSE format_type(a.atttypid, NULL::integer) END
+                           END, '"'::text
+                       ) AS column_type
+                FROM pg_attribute a
+                    JOIN (pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid) ON a.attrelid = c.oid
+                    LEFT JOIN pg_description d ON d.objoid = c.oid AND a.attnum = d.objsubid
+                    LEFT JOIN pg_constraint con ON con.conrelid = c.oid AND (a.attnum = ANY (con.conkey))
+                    LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
+                    LEFT JOIN pg_type t ON a.atttypid = t.oid
+                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
+                    AND a.attnum > 0
+                    AND n.nspname = 'public'::name
+                ORDER BY c.relname, a.attnum
+            ) temp
+            WHERE table_name = (#{tableName})
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isSqlServer()">
+            SELECT
+                cast(A.NAME as nvarchar) as column_name,
+                cast(B.NAME as nvarchar) + (case when B.NAME = 'numeric' then '(' + cast(A.prec as nvarchar) + ',' + cast(A.scale as nvarchar) + ')' else '' end) as column_type,
+                cast(G.[VALUE] as nvarchar) as column_comment,
+                (SELECT 1 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE Z WHERE TABLE_NAME = D.NAME and A.NAME = Z.column_name  ) as is_pk,
+                colorder as sort
+            FROM SYSCOLUMNS A
+                LEFT JOIN SYSTYPES B ON A.XTYPE = B.XUSERTYPE
+                INNER JOIN SYSOBJECTS D ON A.ID = D.ID AND D.XTYPE='U' AND D.NAME != 'DTPROPERTIES'
+                LEFT JOIN SYS.EXTENDED_PROPERTIES G ON A.ID = G.MAJOR_ID AND A.COLID = G.MINOR_ID
+                LEFT JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID AND F.MINOR_ID = 0
+            WHERE D.NAME = #{tableName}
+            ORDER BY A.COLORDER
+        </if>
     </select>
 
 </mapper>

+ 155 - 72
ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml

@@ -53,100 +53,183 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="updateTime"     column="update_time"    />
     </resultMap>
 
-    <sql id="selectGenTableVo">
-        select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table
-    </sql>
-
-    <select id="selectPageGenTableList" parameterType="GenTable" resultMap="GenTableResult">
-        <include refid="selectGenTableVo"/>
-        <where>
+    <select id="selectPageDbTableList" resultMap="GenTableResult">
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">
+            select table_name, table_comment, create_time, update_time
+            from information_schema.tables
+            where table_schema = (select database())
+            AND table_name NOT LIKE 'xxl_job_%' AND table_name NOT LIKE 'gen_%'
+            AND table_name NOT IN (select table_name from gen_table)
             <if test="genTable.tableName != null and genTable.tableName != ''">
                 AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
             </if>
             <if test="genTable.tableComment != null and genTable.tableComment != ''">
                 AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
             </if>
-            <if test="genTable.params.beginTime != null and genTable.params.beginTime != ''"><!-- 开始时间检索 -->
-                AND date_format(create_time,'%y%m%d') &gt;= date_format(#{genTable.params.beginTime},'%y%m%d')
+            order by create_time desc
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">
+            select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
+            from user_tables dt, user_tab_comments dtc, user_objects uo
+            where dt.table_name = dtc.table_name
+            and dt.table_name = uo.object_name
+            and uo.object_type = 'TABLE'
+            AND dt.table_name NOT LIKE 'XXL_JOB_%' AND dt.table_name NOT LIKE 'GEN_%'
+            AND lower(dt.table_name) NOT IN (select table_name from gen_table)
+            <if test="genTable.tableName != null and genTable.tableName != ''">
+                AND lower(dt.table_name) like lower(concat(concat('%', #{genTable.tableName}), '%'))
             </if>
-            <if test="genTable.params.endTime != null and genTable.params.endTime != ''"><!-- 结束时间检索 -->
-                AND date_format(create_time,'%y%m%d') &lt;= date_format(#{genTable.params.endTime},'%y%m%d')
+            <if test="genTable.tableComment != null and genTable.tableComment != ''">
+                AND lower(dtc.comments) like lower(concat(concat('%', #{genTable.tableComment}), '%'))
             </if>
-        </where>
-    </select>
-
-    <select id="selectPageDbTableList" parameterType="GenTable" resultMap="GenTableResult">
-        select table_name, table_comment, create_time, update_time from information_schema.tables
-        where table_schema = (select database())
-        AND table_name NOT LIKE 'xxl_job_%' AND table_name NOT LIKE 'gen_%'
-        AND table_name NOT IN (select table_name from gen_table)
-        <if test="genTable.tableName != null and genTable.tableName != ''">
-            AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
-        </if>
-        <if test="genTable.tableComment != null and genTable.tableComment != ''">
-            AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
+            order by create_time desc
         </if>
-        <if test="genTable.params.beginTime != null and genTable.params.beginTime != ''"><!-- 开始时间检索 -->
-            AND date_format(create_time,'%y%m%d') &gt;= date_format(#{genTable.params.beginTime},'%y%m%d')
-        </if>
-        <if test="genTable.params.endTime != null and genTable.params.endTime != ''"><!-- 结束时间检索 -->
-            AND date_format(create_time,'%y%m%d') &lt;= date_format(#{genTable.params.endTime},'%y%m%d')
-        </if>
-        order by create_time desc
-    </select>
-
-
-    <select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult">
-        <include refid="selectGenTableVo"/>
-        <where>
-            <if test="tableName != null and tableName != ''">
-                AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isPostgerSql()">
+            select table_name, table_comment, create_time, update_time
+            from (
+                SELECT c.relname AS table_name,
+                        obj_description(c.oid) AS table_comment,
+                        CURRENT_TIMESTAMP AS create_time,
+                        CURRENT_TIMESTAMP AS update_time
+                FROM pg_class c
+                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
+                    AND c.relname != 'spatial_%'::text
+                    AND n.nspname = 'public'::name
+                    AND n.nspname <![CDATA[ <> ]]> ''::name
+            ) list_table
+            where table_name NOT LIKE 'xxl_job_%' AND table_name NOT LIKE 'gen_%'
+            AND table_name NOT IN (select table_name from gen_table)
+            <if test="genTable.tableName != null and genTable.tableName != ''">
+                AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
             </if>
-            <if test="tableComment != null and tableComment != ''">
-                AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
+            <if test="genTable.tableComment != null and genTable.tableComment != ''">
+                AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
             </if>
-            <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
-                AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
+            order by create_time desc
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isSqlServer()">
+            SELECT cast(D.NAME as nvarchar) as table_name,
+                   cast(F.VALUE as nvarchar) as table_comment,
+                   crdate as create_time,
+                   refdate as update_time
+            FROM SYSOBJECTS D
+                INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
+                    AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
+                    AND D.NAME NOT LIKE 'xxl_job_%' AND D.NAME NOT LIKE 'gen_%'
+                    AND D.NAME NOT IN (select table_name from gen_table)
+            <if test="genTable.tableName != null and genTable.tableName != ''">
+                AND lower(D.NAME) like lower(concat(N'%', N'${genTable.tableName}', N'%'))
             </if>
-            <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
-                AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
+            <if test="genTable.tableComment != null and genTable.tableComment != ''">
+                AND lower(CAST(F.VALUE AS nvarchar)) like lower(concat(N'%', N'${genTable.tableComment}', N'%'))
             </if>
-        </where>
+            order by crdate desc
+        </if>
     </select>
 
-    <select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult">
-        select table_name, table_comment, create_time, update_time from information_schema.tables
-        where table_schema = (select database())
-        AND table_name NOT LIKE 'xxl_job_%' AND table_name NOT LIKE 'gen_%'
-        AND table_name NOT IN (select table_name from gen_table)
-        <if test="tableName != null and tableName != ''">
-            AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
+    <select id="selectDbTableListByNames" resultMap="GenTableResult">
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">
+            select table_name, table_comment, create_time, update_time from information_schema.tables
+            where table_name NOT LIKE 'xxl_job_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database())
+            and table_name in
+            <foreach collection="array" item="name" open="(" separator="," close=")">
+                 #{name}
+            </foreach>
         </if>
-        <if test="tableComment != null and tableComment != ''">
-            AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">
+            select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
+            from user_tables dt, user_tab_comments dtc, user_objects uo
+            where dt.table_name = dtc.table_name
+            and dt.table_name = uo.object_name
+            and uo.object_type = 'TABLE'
+            AND dt.table_name NOT LIKE 'XXL_JOB_%' AND dt.table_name NOT LIKE 'GEN_%'
+            AND dt.table_name NOT IN (select table_name from gen_table)
+            and lower(dt.table_name) in
+            <foreach collection="array" item="name" open="(" separator="," close=")">
+                #{name}
+            </foreach>
         </if>
-        <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
-            AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isPostgerSql()">
+            select table_name, table_comment, create_time, update_time
+            from (
+                SELECT c.relname AS table_name,
+                        obj_description(c.oid) AS table_comment,
+                        CURRENT_TIMESTAMP AS create_time,
+                        CURRENT_TIMESTAMP AS update_time
+                FROM pg_class c
+                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
+                    AND c.relname != 'spatial_%'::text
+                    AND n.nspname = 'public'::name
+                    AND n.nspname <![CDATA[ <> ]]> ''::name
+            ) list_table
+            where table_name NOT LIKE 'xxl_job_%' and table_name NOT LIKE 'gen_%'
+            and table_name in
+            <foreach collection="array" item="name" open="(" separator="," close=")">
+                #{name}
+            </foreach>
         </if>
-        <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
-            AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isSqlServer()">
+            SELECT cast(D.NAME as nvarchar) as table_name,
+                   cast(F.VALUE as nvarchar) as table_comment,
+                   crdate as create_time,
+                   refdate as update_time
+            FROM SYSOBJECTS D
+                INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
+                    AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
+                    AND D.NAME NOT LIKE 'xxl_job_%' AND D.NAME NOT LIKE 'gen_%'
+                    AND D.NAME in
+            <foreach collection="array" item="name" open="(" separator="," close=")">
+                #{name}
+            </foreach>
         </if>
-        order by create_time desc
-    </select>
-
-    <select id="selectDbTableListByNames" resultMap="GenTableResult">
-        select table_name, table_comment, create_time, update_time from information_schema.tables
-        where table_name NOT LIKE 'xxl_job_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database())
-        and table_name in
-        <foreach collection="array" item="name" open="(" separator="," close=")">
-             #{name}
-        </foreach>
     </select>
 
     <select id="selectTableByName" parameterType="String" resultMap="GenTableResult">
-        select table_name, table_comment, create_time, update_time from information_schema.tables
-        where table_comment <![CDATA[ <> ]]> '' and table_schema = (select database())
-        and table_name = #{tableName}
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">
+            select table_name, table_comment, create_time, update_time from information_schema.tables
+            where table_name NOT LIKE 'xxl_job_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database())
+            and table_name = #{tableName}
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">
+            select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
+            from user_tables dt, user_tab_comments dtc, user_objects uo
+            where dt.table_name = dtc.table_name
+            and dt.table_name = uo.object_name
+            and uo.object_type = 'TABLE'
+            AND dt.table_name NOT LIKE 'XXL_JOB_%' AND dt.table_name NOT LIKE 'GEN_%'
+            AND dt.table_name NOT IN (select table_name from gen_table)
+            and lower(dt.table_name) = #{tableName}
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isPostgerSql()">
+            select table_name, table_comment, create_time, update_time
+            from (
+                SELECT c.relname AS table_name,
+                        obj_description(c.oid) AS table_comment,
+                        CURRENT_TIMESTAMP AS create_time,
+                        CURRENT_TIMESTAMP AS update_time
+                FROM pg_class c
+                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
+                    AND c.relname != 'spatial_%'::text
+                    AND n.nspname = 'public'::name
+                    AND n.nspname <![CDATA[ <> ]]> ''::name
+            ) list_table
+            where table_name NOT LIKE 'xxl_job_%' and table_name NOT LIKE 'gen_%'
+            and table_name = #{tableName}
+        </if>
+        <if test="@com.ruoyi.common.helper.DataBaseHelper@isSqlServer()">
+            SELECT cast(D.NAME as nvarchar) as table_name,
+                   cast(F.VALUE as nvarchar) as table_comment,
+                   crdate as create_time,
+                   refdate as update_time
+            FROM SYSOBJECTS D
+                INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
+                    AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
+                    AND D.NAME NOT LIKE 'xxl_job_%' AND D.NAME NOT LIKE 'gen_%'
+                    AND D.NAME = #{tableName}
+        </if>
     </select>
 
     <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">

+ 1 - 1
ruoyi-generator/src/main/resources/vm/java/bo.java.vm

@@ -28,7 +28,7 @@ import com.ruoyi.common.core.domain.TreeEntity;
 #if($table.crud || $table.sub)
 #set($Entity="BaseEntity")
 #elseif($table.tree)
-#set($Entity="TreeEntity")
+#set($Entity="TreeEntity<${ClassName}Bo>")
 #end
 
 @Data

+ 1 - 1
ruoyi-generator/src/main/resources/vm/java/domain.java.vm

@@ -25,7 +25,7 @@ import com.ruoyi.common.core.domain.TreeEntity;
 #if($table.crud || $table.sub)
     #set($Entity="BaseEntity")
 #elseif($table.tree)
-    #set($Entity="TreeEntity")
+    #set($Entity="TreeEntity<${ClassName}>")
 #end
 @Data
 @TableName("${tableName}")

+ 19 - 0
ruoyi-generator/src/main/resources/vm/sql/oracle/sql.vm

@@ -0,0 +1,19 @@
+-- 菜单 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate, '', null, '${functionName}菜单');
+
+-- 按钮 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1',  '#', '', 1,  0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 'admin', sysdate, '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2',  '#', '', 1,  0, 'F', '0', '0', '${permissionPrefix}:add',          '#', 'admin', sysdate, '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3',  '#', '', 1,  0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 'admin', sysdate, '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4',  '#', '', 1,  0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 'admin', sysdate, '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5',  '#', '', 1,  0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 'admin', sysdate, '', null, '');

+ 20 - 0
ruoyi-generator/src/main/resources/vm/sql/postgres/sql.vm

@@ -0,0 +1,20 @@
+-- 菜单 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', now(), '', null, '${functionName}菜单');
+
+-- 按钮 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 'admin', now(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add',          '#', 'admin', now(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 'admin', now(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 'admin', now(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 'admin', now(), '', null, '');
+

+ 12 - 15
ruoyi-generator/src/main/resources/vm/sql/sql.vm

@@ -1,22 +1,19 @@
 -- 菜单 SQL
-insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单');
-
--- 按钮父菜单ID
-SELECT @parentId := LAST_INSERT_ID();
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单');
 
 -- 按钮 SQL
-insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-values('${functionName}查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 'admin', sysdate(), '', null, '');
 
-insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-values('${functionName}新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add',          '#', 'admin', sysdate(), '', null, '');
 
-insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-values('${functionName}修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 'admin', sysdate(), '', null, '');
 
-insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-values('${functionName}删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 'admin', sysdate(), '', null, '');
 
-insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-values('${functionName}导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 'admin', sysdate(), '', null, '');

+ 19 - 0
ruoyi-generator/src/main/resources/vm/sql/sqlserver/sql.vm

@@ -0,0 +1,19 @@
+-- 菜单 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', getdate(), '', null, '${functionName}菜单');
+
+-- 按钮 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 'admin', getdate(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add',          '#', 'admin', getdate(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 'admin', getdate(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 'admin', getdate(), '', null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 'admin', getdate(), '', null, '');

+ 28 - 8
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
 #foreach($column in $columns)
 #if($column.query)
 #set($dictType=$column.dictType)
@@ -17,13 +17,12 @@
           v-model="queryParams.${column.javaField}"
           placeholder="请输入${comment}"
           clearable
-          size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
 #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
       <el-form-item label="${comment}" prop="${column.javaField}">
-        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
           <el-option
             v-for="dict in dict.type.${dictType}"
             :key="dict.value"
@@ -34,13 +33,13 @@
       </el-form-item>
 #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
       <el-form-item label="${comment}" prop="${column.javaField}">
-        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
           <el-option label="请选择字典生成" value="" />
         </el-select>
       </el-form-item>
 #elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
       <el-form-item label="${comment}" prop="${column.javaField}">
-        <el-date-picker clearable size="small"
+        <el-date-picker clearable
           v-model="queryParams.${column.javaField}"
           type="date"
           value-format="yyyy-MM-dd"
@@ -51,7 +50,6 @@
       <el-form-item label="${comment}">
         <el-date-picker
           v-model="daterange${AttrName}"
-          size="small"
           style="width: 240px"
           value-format="yyyy-MM-dd"
           type="daterange"
@@ -80,14 +78,24 @@
           v-hasPermi="['${moduleName}:${businessName}:add']"
         >新增</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-sort"
+          size="mini"
+          @click="toggleExpandAll"
+        >展开/折叠</el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
     <el-table
+      v-if="refreshTable"
       v-loading="loading"
       :data="${businessName}List"
       row-key="${treeCode}"
-      default-expand-all
+      :default-expand-all="isExpandAll"
       :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
     >
 #foreach($column in $columns)
@@ -243,7 +251,7 @@
         </el-form-item>
 #elseif($column.htmlType == "datetime")
         <el-form-item label="${comment}" prop="${field}">
-          <el-date-picker clearable size="small"
+          <el-date-picker clearable
             v-model="form.${field}"
             type="datetime"
             value-format="yyyy-MM-dd HH:mm:ss"
@@ -295,6 +303,10 @@ export default {
       title: "",
       // 是否显示弹出层
       open: false,
+      // 是否展开,默认全部展开
+      isExpandAll: true,
+      // 重新渲染表格状态
+      refreshTable: true,
 #foreach ($column in $columns)
 #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
 #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
@@ -425,6 +437,14 @@ export default {
       this.open = true;
       this.title = "添加${functionName}";
     },
+    /** 展开/折叠操作 */
+    toggleExpandAll() {
+      this.refreshTable = false;
+      this.isExpandAll = !this.isExpandAll;
+      this.$nextTick(() => {
+        this.refreshTable = true;
+      });
+    },
     /** 修改按钮操作 */
     handleUpdate(row) {
 	  this.loading = true;

+ 5 - 7
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
 #foreach($column in $columns)
 #if($column.query)
 #set($dictType=$column.dictType)
@@ -17,13 +17,12 @@
           v-model="queryParams.${column.javaField}"
           placeholder="请输入${comment}"
           clearable
-          size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
 #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
       <el-form-item label="${comment}" prop="${column.javaField}">
-        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
           <el-option
             v-for="dict in dict.type.${dictType}"
             :key="dict.value"
@@ -34,13 +33,13 @@
       </el-form-item>
 #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
       <el-form-item label="${comment}" prop="${column.javaField}">
-        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
           <el-option label="请选择字典生成" value="" />
         </el-select>
       </el-form-item>
 #elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
       <el-form-item label="${comment}" prop="${column.javaField}">
-        <el-date-picker clearable size="small"
+        <el-date-picker clearable
           v-model="queryParams.${column.javaField}"
           type="date"
           value-format="yyyy-MM-dd"
@@ -51,7 +50,6 @@
       <el-form-item label="${comment}">
         <el-date-picker
           v-model="daterange${AttrName}"
-          size="small"
           style="width: 240px"
           value-format="yyyy-MM-dd"
           type="daterange"
@@ -264,7 +262,7 @@
         </el-form-item>
 #elseif($column.htmlType == "datetime")
         <el-form-item label="${comment}" prop="${field}">
-          <el-date-picker clearable size="small"
+          <el-date-picker clearable
             v-model="form.${field}"
             type="datetime"
             value-format="yyyy-MM-dd HH:mm:ss"

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

@@ -76,14 +76,23 @@
           v-hasPermi="['${moduleName}:${businessName}:add']"
         >新增</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="Sort"
+          @click="toggleExpandAll"
+        >展开/折叠</el-button>
+      </el-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
     <el-table
+      v-if="refreshTable"
       v-loading="loading"
       :data="${businessName}List"
       row-key="${treeCode}"
-      default-expand-all
+      :default-expand-all="isExpandAll"
       :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
     >
 #foreach($column in $columns)
@@ -282,6 +291,8 @@ const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
 const title = ref("");
+const isExpandAll = ref(true);
+const refreshTable = ref(true);
 #foreach ($column in $columns)
 #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
 #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
@@ -404,6 +415,15 @@ async function handleAdd(row) {
   title.value = "添加${functionName}";
 }
 
+/** 展开/折叠操作 */
+function toggleExpandAll() {
+  refreshTable.value = false;
+  isExpandAll.value = !isExpandAll.value;
+  nextTick(() => {
+    refreshTable.value = true;
+  });
+}
+
 /** 修改按钮操作 */
 async function handleUpdate(row) {
   loading.value = true;

+ 1 - 1
ruoyi-job/pom.xml

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

+ 5 - 1
ruoyi-oss/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>4.0.1</version>
+        <version>4.1.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -42,6 +42,10 @@
                     <groupId>org.slf4j</groupId>
                     <artifactId>slf4j-log4j12</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcprov-jdk15on</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <dependency>

+ 8 - 1
ruoyi-oss/src/main/java/com/ruoyi/oss/service/IOssStrategy.java

@@ -12,11 +12,14 @@ import java.io.InputStream;
  */
 public interface IOssStrategy {
 
+    /**
+     * 创建存储桶
+     */
     void createBucket();
 
     /**
      * 获取服务商类型
-     * @return
+     * @return 对象存储服务商枚举
      */
     OssEnumd getServiceType();
 
@@ -25,6 +28,7 @@ public interface IOssStrategy {
      *
      * @param data 文件字节数组
      * @param path 文件路径,包含文件名
+     * @param contentType 文件类型
      * @return 返回http地址
      */
     UploadResult upload(byte[] data, String path, String contentType);
@@ -41,6 +45,7 @@ public interface IOssStrategy {
      *
      * @param data   文件字节数组
      * @param suffix 后缀
+     * @param contentType 文件类型
      * @return 返回http地址
      */
     UploadResult uploadSuffix(byte[] data, String suffix, String contentType);
@@ -50,6 +55,7 @@ public interface IOssStrategy {
      *
      * @param inputStream 字节流
      * @param path        文件路径,包含文件名
+     * @param contentType 文件类型
      * @return 返回http地址
      */
     UploadResult upload(InputStream inputStream, String path, String contentType);
@@ -59,6 +65,7 @@ public interface IOssStrategy {
      *
      * @param inputStream 字节流
      * @param suffix      后缀
+     * @param contentType 文件类型
      * @return 返回http地址
      */
     UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);

+ 5 - 0
ruoyi-oss/src/main/java/com/ruoyi/oss/service/abstractd/AbstractOssStrategy.java

@@ -60,5 +60,10 @@ public abstract class AbstractOssStrategy implements IOssStrategy {
     @Override
     public abstract UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);
 
+    /**
+     * 获取域名访问链接
+     *
+     * @return 域名访问链接
+     */
     public abstract String getEndpointLink();
 }

+ 2 - 0
ruoyi-oss/src/main/java/com/ruoyi/oss/service/impl/MinioOssStrategy.java

@@ -79,6 +79,8 @@ public class MinioOssStrategy extends AbstractOssStrategy {
     @Override
     public UploadResult upload(InputStream inputStream, String path, String contentType) {
         try {
+            // 解决 inputStream.available() 再 socket 下传输延迟问题 导致获取数值不精确
+            Thread.sleep(1000);
             minioClient.putObject(PutObjectArgs.builder()
                 .bucket(properties.getBucketName())
                 .object(path)

+ 1 - 1
ruoyi-system/pom.xml

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

+ 1 - 2
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOss.java

@@ -1,6 +1,5 @@
 package com.ruoyi.system.domain;
 
-import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.ruoyi.common.core.domain.BaseEntity;
@@ -20,7 +19,7 @@ public class SysOss extends BaseEntity {
     /**
      * 对象存储主键
      */
-    @TableId(value = "oss_id", type = IdType.AUTO)
+    @TableId(value = "oss_id")
     private Long ossId;
 
     /**

+ 3 - 2
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java

@@ -14,6 +14,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
 
 /**
@@ -60,8 +61,8 @@ public class SysPost extends BaseEntity {
      */
     @ApiModelProperty(value = "岗位排序")
     @ExcelProperty(value = "岗位排序")
-    @NotBlank(message = "显示顺序不能为空")
-    private String postSort;
+    @NotNull(message = "显示顺序不能为空")
+    private Integer postSort;
 
     /**
      * 状态(0正常 1停用)

+ 5 - 11
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java

@@ -1,5 +1,7 @@
 package com.ruoyi.system.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.ruoyi.common.annotation.DataColumn;
 import com.ruoyi.common.annotation.DataPermission;
 import com.ruoyi.common.core.domain.entity.SysDept;
@@ -18,13 +20,13 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDeptMapper, SysDept, Sy
     /**
      * 查询部门管理数据
      *
-     * @param dept 部门信息
+     * @param queryWrapper 查询条件
      * @return 部门信息集合
      */
     @DataPermission({
-        @DataColumn(key = "deptName", value = "d.dept_id")
+        @DataColumn(key = "deptName", value = "dept_id")
     })
-    List<SysDept> selectDeptList(SysDept dept);
+    List<SysDept> selectDeptList(@Param(Constants.WRAPPER) Wrapper<SysDept> queryWrapper);
 
     /**
      * 根据角色ID查询部门树信息
@@ -35,12 +37,4 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDeptMapper, SysDept, Sy
      */
     List<Long> selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly);
 
-    /**
-     * 修改子元素关系
-     *
-     * @param depts 子元素
-     * @return 结果
-     */
-    int updateDeptChildren(@Param("depts") List<SysDept> depts);
-
 }

+ 14 - 3
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java

@@ -1,5 +1,9 @@
 package com.ruoyi.system.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.domain.entity.SysMenu;
 import com.ruoyi.common.core.mapper.BaseMapperPlus;
 import org.apache.ibatis.annotations.Param;
@@ -23,10 +27,10 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenuMapper, SysMenu, Sy
     /**
      * 根据用户查询系统菜单列表
      *
-     * @param menu 菜单信息
+     * @param queryWrapper 查询条件
      * @return 菜单列表
      */
-    List<SysMenu> selectMenuListByUserId(SysMenu menu);
+    List<SysMenu> selectMenuListByUserId(@Param(Constants.WRAPPER) Wrapper<SysMenu> queryWrapper);
 
     /**
      * 根据用户ID查询权限
@@ -41,7 +45,14 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenuMapper, SysMenu, Sy
      *
      * @return 菜单列表
      */
-    List<SysMenu> selectMenuTreeAll();
+    default List<SysMenu> selectMenuTreeAll() {
+        LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<SysMenu>()
+            .in(SysMenu::getMenuType, UserConstants.TYPE_DIR, UserConstants.TYPE_MENU)
+            .eq(SysMenu::getStatus, UserConstants.MENU_NORMAL)
+            .orderByAsc(SysMenu::getParentId)
+            .orderByAsc(SysMenu::getOrderNum);
+        return this.selectList(lqw);
+    }
 
     /**
      * 根据用户ID查询菜单

+ 5 - 3
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java

@@ -1,5 +1,7 @@
 package com.ruoyi.system.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.annotation.DataColumn;
 import com.ruoyi.common.annotation.DataPermission;
@@ -19,18 +21,18 @@ public interface SysRoleMapper extends BaseMapperPlus<SysRoleMapper, SysRole, Sy
     @DataPermission({
         @DataColumn(key = "deptName", value = "d.dept_id")
     })
-    Page<SysRole> selectPageRoleList(@Param("page") Page<SysRole> page, @Param("role") SysRole role);
+    Page<SysRole> selectPageRoleList(@Param("page") Page<SysRole> page, @Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper);
 
     /**
      * 根据条件分页查询角色数据
      *
-     * @param role 角色信息
+     * @param queryWrapper 查询条件
      * @return 角色数据集合信息
      */
     @DataPermission({
         @DataColumn(key = "deptName", value = "d.dept_id")
     })
-    List<SysRole> selectRoleList(SysRole role);
+    List<SysRole> selectRoleList(@Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper);
 
     /**
      * 根据用户ID查询角色

+ 17 - 7
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java

@@ -1,5 +1,7 @@
 package com.ruoyi.system.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.annotation.DataColumn;
 import com.ruoyi.common.annotation.DataPermission;
@@ -20,43 +22,43 @@ public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, Sy
         @DataColumn(key = "deptName", value = "d.dept_id"),
         @DataColumn(key = "userName", value = "u.user_id")
     })
-    Page<SysUser> selectPageUserList(@Param("page") Page<SysUser> page, @Param("user") SysUser user);
+    Page<SysUser> selectPageUserList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
 
     /**
      * 根据条件分页查询用户列表
      *
-     * @param sysUser 用户信息
+     * @param queryWrapper 查询条件
      * @return 用户信息集合信息
      */
     @DataPermission({
         @DataColumn(key = "deptName", value = "d.dept_id"),
         @DataColumn(key = "userName", value = "u.user_id")
     })
-    List<SysUser> selectUserList(SysUser sysUser);
+    List<SysUser> selectUserList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
 
     /**
      * 根据条件分页查询已配用户角色列表
      *
-     * @param user 用户信息
+     * @param queryWrapper 查询条件
      * @return 用户信息集合信息
      */
     @DataPermission({
         @DataColumn(key = "deptName", value = "d.dept_id"),
         @DataColumn(key = "userName", value = "u.user_id")
     })
-    Page<SysUser> selectAllocatedList(@Param("page") Page<SysUser> page, @Param("user") SysUser user);
+    Page<SysUser> selectAllocatedList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
 
     /**
      * 根据条件分页查询未分配用户角色列表
      *
-     * @param user 用户信息
+     * @param queryWrapper 查询条件
      * @return 用户信息集合信息
      */
     @DataPermission({
         @DataColumn(key = "deptName", value = "d.dept_id"),
         @DataColumn(key = "userName", value = "u.user_id")
     })
-    Page<SysUser> selectUnallocatedList(@Param("page") Page<SysUser> page, @Param("user") SysUser user);
+    Page<SysUser> selectUnallocatedList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
 
     /**
      * 通过用户名查询用户
@@ -67,6 +69,14 @@ public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, Sy
     SysUser selectUserByUserName(String userName);
 
     /**
+     * 通过手机号查询用户
+     *
+     * @param phonenumber 手机号
+     * @return 用户对象信息
+     */
+    SysUser selectUserByPhonenumber(String phonenumber);
+
+    /**
      * 通过用户ID查询用户
      *
      * @param userId 用户ID

+ 4 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java

@@ -3,6 +3,8 @@ package com.ruoyi.system.mapper;
 import com.ruoyi.common.core.mapper.BaseMapperPlus;
 import com.ruoyi.system.domain.SysUserRole;
 
+import java.util.List;
+
 /**
  * 用户与角色关联表 数据层
  *
@@ -10,4 +12,6 @@ import com.ruoyi.system.domain.SysUserRole;
  */
 public interface SysUserRoleMapper extends BaseMapperPlus<SysUserRoleMapper, SysUserRole, SysUserRole> {
 
+    List<Long> selectUserIdsByRoleId(Long roleId);
+
 }

+ 1 - 1
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssConfigService.java

@@ -24,7 +24,7 @@ public interface ISysOssConfigService {
     /**
      * 查询单个
      */
-    SysOssConfigVo queryById(Integer ossConfigId);
+    SysOssConfigVo queryById(Long ossConfigId);
 
     /**
      * 查询列表

+ 8 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java

@@ -49,6 +49,14 @@ public interface ISysUserService {
     SysUser selectUserByUserName(String userName);
 
     /**
+     * 通过手机号查询用户
+     *
+     * @param phonenumber 手机号
+     * @return 用户对象信息
+     */
+    SysUser selectUserByPhonenumber(String phonenumber);
+
+    /**
      * 通过用户ID查询用户
      *
      * @param userId 用户ID

+ 123 - 27
ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java

@@ -8,8 +8,10 @@ import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.dto.RoleDTO;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.domain.model.XcxLoginUser;
 import com.ruoyi.common.core.service.LogininforService;
 import com.ruoyi.common.enums.DeviceType;
+import com.ruoyi.common.enums.LoginType;
 import com.ruoyi.common.enums.UserStatus;
 import com.ruoyi.common.exception.user.CaptchaException;
 import com.ruoyi.common.exception.user.CaptchaExpireException;
@@ -27,6 +29,7 @@ import org.springframework.stereotype.Service;
 import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
 /**
  * 登录校验方法
@@ -59,34 +62,9 @@ public class SysLoginService {
         if (captchaOnOff) {
             validateCaptcha(username, code, uuid, request);
         }
-        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
-        Integer errorNumber = RedisUtils.getCacheObject(Constants.LOGIN_ERROR + username);
-        // 锁定时间内登录 则踢出
-        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(Constants.LOGIN_ERROR_NUMBER)) {
-            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME), request);
-            throw new UserException("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME);
-        }
-
         SysUser user = loadUserByUsername(username);
-
-        if (!BCrypt.checkpw(password, user.getPassword())) {
-            // 是否第一次
-            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
-            // 达到规定错误次数 则锁定登录
-            if (errorNumber.equals(Constants.LOGIN_ERROR_NUMBER)) {
-                RedisUtils.setCacheObject(Constants.LOGIN_ERROR + username, errorNumber, Constants.LOGIN_ERROR_LIMIT_TIME, TimeUnit.MINUTES);
-                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME), request);
-                throw new UserException("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME);
-            } else {
-                // 未达到规定错误次数 则递增
-                RedisUtils.setCacheObject(Constants.LOGIN_ERROR + username, errorNumber);
-                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", errorNumber), request);
-                throw new UserException("user.password.retry.limit.count", errorNumber);
-            }
-        }
-
-        // 登录成功 清空错误次数
-        RedisUtils.deleteObject(Constants.LOGIN_ERROR + username);
+        checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
         LoginUser loginUser = buildLoginUser(user);
         // 生成token
         LoginHelper.loginByDevice(loginUser, DeviceType.PC);
@@ -96,11 +74,59 @@ public class SysLoginService {
         return StpUtil.getTokenValue();
     }
 
+    public String smsLogin(String phonenumber, String smsCode) {
+        // 通过手机号查找用户
+        SysUser user = loadUserByPhonenumber(phonenumber);
+
+        HttpServletRequest request = ServletUtils.getRequest();
+        checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
+        LoginUser loginUser = buildLoginUser(user);
+        // 生成token
+        LoginHelper.loginByDevice(loginUser, DeviceType.APP);
+
+        asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
+        recordLoginInfo(user.getUserId(), user.getUserName());
+        return StpUtil.getTokenValue();
+    }
+
+
+    public String xcxLogin(String xcxCode) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        // xcxCode 为 小程序调用 wx.login 授权后获取
+        // todo 以下自行实现
+        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
+        String openid = "";
+        SysUser user = loadUserByOpenid(openid);
+
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
+        XcxLoginUser loginUser = new XcxLoginUser();
+        loginUser.setUserId(user.getUserId());
+        loginUser.setUsername(user.getUserName());
+        loginUser.setUserType(user.getUserType());
+        loginUser.setOpenid(openid);
+        // 生成token
+        LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
+
+        asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
+        recordLoginInfo(user.getUserId(), user.getUserName());
+        return StpUtil.getTokenValue();
+    }
+
+
     public void logout(String loginName) {
         asyncService.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success"), ServletUtils.getRequest());
     }
 
     /**
+     * 校验短信验证码
+     */
+    private boolean validateSmsCode(String phonenumber, String smsCode) {
+        // todo 此处使用手机号查询redis验证码与参数验证码是否一致 用户自行实现
+        return true;
+    }
+
+    /**
      * 校验验证码
      *
      * @param username 用户名
@@ -136,6 +162,38 @@ public class SysLoginService {
         return user;
     }
 
+    private SysUser loadUserByPhonenumber(String phonenumber) {
+        SysUser user = userService.selectUserByPhonenumber(phonenumber);
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", phonenumber);
+            throw new UserException("user.not.exists", phonenumber);
+        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
+            log.info("登录用户:{} 已被删除.", phonenumber);
+            throw new UserException("user.password.delete", phonenumber);
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", phonenumber);
+            throw new UserException("user.blocked", phonenumber);
+        }
+        return user;
+    }
+
+    private SysUser loadUserByOpenid(String openid) {
+        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
+        // todo 自行实现 userService.selectUserByOpenid(openid);
+        SysUser user = new SysUser();
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", openid);
+            // todo 用户不存在 业务逻辑自行实现
+        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
+            log.info("登录用户:{} 已被删除.", openid);
+            // todo 用户已被删除 业务逻辑自行实现
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", openid);
+            // todo 用户已被停用 业务逻辑自行实现
+        }
+        return user;
+    }
+
     /**
      * 构建登录用户
      */
@@ -166,4 +224,42 @@ public class SysLoginService {
         sysUser.setUpdateBy(username);
         userService.updateUserProfile(sysUser);
     }
+
+    /**
+     * 登录校验
+     */
+    private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        String errorKey = Constants.LOGIN_ERROR + username;
+        Integer errorLimitTime = Constants.LOGIN_ERROR_LIMIT_TIME;
+        Integer setErrorNumber = Constants.LOGIN_ERROR_NUMBER;
+        String loginFail = Constants.LOGIN_FAIL;
+
+        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
+        Integer errorNumber = RedisUtils.getCacheObject(errorKey);
+        // 锁定时间内登录 则踢出
+        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(setErrorNumber)) {
+            asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), errorLimitTime), request);
+            throw new UserException(loginType.getRetryLimitExceed(), errorLimitTime);
+        }
+
+        if (supplier.get()) {
+            // 是否第一次
+            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
+            // 达到规定错误次数 则锁定登录
+            if (errorNumber.equals(setErrorNumber)) {
+                RedisUtils.setCacheObject(errorKey, errorNumber, errorLimitTime, TimeUnit.MINUTES);
+                asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), errorLimitTime), request);
+                throw new UserException(loginType.getRetryLimitExceed(), errorLimitTime);
+            } else {
+                // 未达到规定错误次数 则递增
+                RedisUtils.setCacheObject(errorKey, errorNumber);
+                asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber), request);
+                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
+            }
+        }
+
+        // 登录成功 清空错误次数
+        RedisUtils.deleteObject(errorKey);
+    }
 }

+ 15 - 3
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDataScopeServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.helper.DataBaseHelper;
 import com.ruoyi.system.domain.SysRoleDept;
 import com.ruoyi.system.mapper.SysDeptMapper;
 import com.ruoyi.system.mapper.SysRoleDeptMapper;
@@ -14,6 +15,14 @@ import org.springframework.stereotype.Service;
 import java.util.List;
 import java.util.stream.Collectors;
 
+/**
+ * 数据权限 实现
+ * <p>
+ * 注意: 此Service内不允许调用标注`数据权限`注解的方法
+ * 例如: deptMapper.selectList 此 selectList 方法标注了`数据权限`注解 会出现循环解析的问题
+ *
+ * @author Lion Li
+ */
 @RequiredArgsConstructor
 @Service("sdss")
 public class SysDataScopeServiceImpl implements ISysDataScopeService {
@@ -35,11 +44,14 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
 
     @Override
     public String getDeptAndChild(Long deptId) {
+        List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
+            .select(SysDept::getDeptId)
+            .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
+        List<Long> ids = deptList.stream().map(SysDept::getDeptId).collect(Collectors.toList());
+        ids.add(deptId);
         List<SysDept> list = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
             .select(SysDept::getDeptId)
-            .eq(SysDept::getDeptId, deptId)
-            .or()
-            .apply("find_in_set({0},ancestors)", deptId));
+            .in(SysDept::getDeptId, ids));
         if (CollUtil.isNotEmpty(list)) {
             return list.stream().map(d -> Convert.toStr(d.getDeptId())).collect(Collectors.joining(","));
         }

+ 21 - 7
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java

@@ -11,6 +11,7 @@ import com.ruoyi.common.core.domain.entity.SysDept;
 import com.ruoyi.common.core.domain.entity.SysRole;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.helper.DataBaseHelper;
 import com.ruoyi.common.helper.LoginHelper;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.TreeBuildUtils;
@@ -21,6 +22,7 @@ import com.ruoyi.system.service.ISysDeptService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -45,7 +47,15 @@ public class SysDeptServiceImpl implements ISysDeptService {
      */
     @Override
     public List<SysDept> selectDeptList(SysDept dept) {
-        return baseMapper.selectDeptList(dept);
+        LambdaQueryWrapper<SysDept> lqw = new LambdaQueryWrapper<>();
+        lqw.eq(SysDept::getDelFlag, "0")
+            .eq(ObjectUtil.isNotNull(dept.getDeptId()), SysDept::getDeptId, dept.getDeptId())
+            .eq(ObjectUtil.isNotNull(dept.getParentId()), SysDept::getParentId, dept.getParentId())
+            .like(StringUtils.isNotBlank(dept.getDeptName()), SysDept::getDeptName, dept.getDeptName())
+            .eq(StringUtils.isNotBlank(dept.getStatus()), SysDept::getStatus, dept.getStatus())
+            .orderByAsc(SysDept::getParentId)
+            .orderByAsc(SysDept::getOrderNum);
+        return baseMapper.selectDeptList(lqw);
     }
 
     /**
@@ -75,7 +85,7 @@ public class SysDeptServiceImpl implements ISysDeptService {
     @Override
     public List<Long> selectDeptListByRoleId(Long roleId) {
         SysRole role = roleMapper.selectById(roleId);
-        return baseMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly());
+        return baseMapper.selectDeptListByRoleId(roleId, role.getDeptCheckStrictly());
     }
 
     /**
@@ -99,7 +109,7 @@ public class SysDeptServiceImpl implements ISysDeptService {
     public long selectNormalChildrenDeptById(Long deptId) {
         return baseMapper.selectCount(new LambdaQueryWrapper<SysDept>()
             .eq(SysDept::getStatus, UserConstants.DEPT_NORMAL)
-            .apply("find_in_set({0}, ancestors)", deptId));
+            .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
     }
 
     /**
@@ -225,12 +235,16 @@ public class SysDeptServiceImpl implements ISysDeptService {
      */
     public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) {
         List<SysDept> children = baseMapper.selectList(new LambdaQueryWrapper<SysDept>()
-            .apply("find_in_set({0},ancestors)", deptId));
+            .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
+        List<SysDept> list = new ArrayList<>();
         for (SysDept child : children) {
-            child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
+            SysDept dept = new SysDept();
+            dept.setDeptId(child.getDeptId());
+            dept.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
+            list.add(dept);
         }
-        if (children.size() > 0) {
-            baseMapper.updateDeptChildren(children);
+        if (list.size() > 0) {
+            baseMapper.updateBatchById(list);
         }
     }
 

+ 15 - 7
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java

@@ -4,6 +4,8 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.tree.Tree;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.domain.entity.SysMenu;
@@ -65,8 +67,14 @@ public class SysMenuServiceImpl implements ISysMenuService {
                 .orderByAsc(SysMenu::getParentId)
                 .orderByAsc(SysMenu::getOrderNum));
         } else {
-            menu.getParams().put("userId", userId);
-            menuList = baseMapper.selectMenuListByUserId(menu);
+            QueryWrapper<SysMenu> wrapper = Wrappers.query();
+            wrapper.eq("ur.user_id", userId)
+                .like(StringUtils.isNotBlank(menu.getMenuName()), "m.menu_name", menu.getMenuName())
+                .eq(StringUtils.isNotBlank(menu.getVisible()), "m.visible", menu.getVisible())
+                .eq(StringUtils.isNotBlank(menu.getStatus()), "m.status", menu.getStatus())
+                .orderByAsc("m.parent_id")
+                .orderByAsc("m.order_num");
+            menuList = baseMapper.selectMenuListByUserId(wrapper);
         }
         return menuList;
     }
@@ -115,7 +123,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
     @Override
     public List<Long> selectMenuListByRoleId(Long roleId) {
         SysRole role = roleMapper.selectById(roleId);
-        return baseMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly());
+        return baseMapper.selectMenuListByRoleId(roleId, role.getMenuCheckStrictly());
     }
 
     /**
@@ -133,9 +141,9 @@ public class SysMenuServiceImpl implements ISysMenuService {
             router.setName(getRouteName(menu));
             router.setPath(getRouterPath(menu));
             router.setComponent(getComponent(menu));
-            router.setQuery(menu.getQuery());
+            router.setQuery(menu.getQueryParam());
             router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
-            List<SysMenu> cMenus = (List<SysMenu>) menu.getChildren();
+            List<SysMenu> cMenus = menu.getChildren();
             if (!cMenus.isEmpty() && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {
                 router.setAlwaysShow(true);
                 router.setRedirect("noRedirect");
@@ -148,12 +156,12 @@ public class SysMenuServiceImpl implements ISysMenuService {
                 children.setComponent(menu.getComponent());
                 children.setName(StringUtils.capitalize(menu.getPath()));
                 children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
-                children.setQuery(menu.getQuery());
+                children.setQuery(menu.getQueryParam());
                 childrenList.add(children);
                 router.setChildren(childrenList);
             } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) {
                 router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
-                router.setPath("/inner");
+                router.setPath("/");
                 List<RouterVo> childrenList = new ArrayList<RouterVo>();
                 RouterVo children = new RouterVo();
                 String routerPath = innerLinkReplaceEach(menu.getPath());

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

@@ -63,7 +63,7 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
     }
 
     @Override
-    public SysOssConfigVo queryById(Integer ossConfigId) {
+    public SysOssConfigVo queryById(Long ossConfigId) {
         return baseMapper.selectVoById(ossConfigId);
     }
 

+ 20 - 2
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java

@@ -2,7 +2,11 @@ package com.ruoyi.system.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.domain.PageQuery;
@@ -40,7 +44,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
 
     @Override
     public TableDataInfo<SysRole> selectPageRoleList(SysRole role, PageQuery pageQuery) {
-        Page<SysRole> page = baseMapper.selectPageRoleList(pageQuery.build(), role);
+        Page<SysRole> page = baseMapper.selectPageRoleList(pageQuery.build(), this.buildQueryWrapper(role));
         return TableDataInfo.build(page);
     }
 
@@ -52,7 +56,21 @@ public class SysRoleServiceImpl implements ISysRoleService {
      */
     @Override
     public List<SysRole> selectRoleList(SysRole role) {
-        return baseMapper.selectRoleList(role);
+        return baseMapper.selectRoleList(this.buildQueryWrapper(role));
+    }
+
+    private Wrapper<SysRole> buildQueryWrapper(SysRole role) {
+        Map<String, Object> params = role.getParams();
+        QueryWrapper<SysRole> wrapper = Wrappers.query();
+        wrapper.eq("r.del_flag", UserConstants.ROLE_NORMAL)
+            .eq(ObjectUtil.isNotNull(role.getRoleId()), "r.role_id", role.getRoleId())
+            .like(StringUtils.isNotBlank(role.getRoleName()), "r.role_name", role.getRoleName())
+            .eq(StringUtils.isNotBlank(role.getStatus()), "r.status", role.getStatus())
+            .like(StringUtils.isNotBlank(role.getRoleKey()), "r.role_key", role.getRoleKey())
+            .between(params.get("beginTime") != null && params.get("endTime") != null,
+                "r.create_time", params.get("beginTime"), params.get("endTime"))
+            .orderByAsc("r.role_sort");
+        return wrapper;
     }
 
     /**

+ 56 - 4
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

@@ -2,15 +2,20 @@ package com.ruoyi.system.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.entity.SysDept;
 import com.ruoyi.common.core.domain.entity.SysRole;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.helper.DataBaseHelper;
 import com.ruoyi.common.helper.LoginHelper;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.system.domain.SysPost;
@@ -26,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -39,6 +45,7 @@ import java.util.stream.Collectors;
 public class SysUserServiceImpl implements ISysUserService {
 
     private final SysUserMapper baseMapper;
+    private final SysDeptMapper deptMapper;
     private final SysRoleMapper roleMapper;
     private final SysPostMapper postMapper;
     private final SysUserRoleMapper userRoleMapper;
@@ -46,7 +53,7 @@ public class SysUserServiceImpl implements ISysUserService {
 
     @Override
     public TableDataInfo<SysUser> selectPageUserList(SysUser user, PageQuery pageQuery) {
-        Page<SysUser> page = baseMapper.selectPageUserList(pageQuery.build(), user);
+        Page<SysUser> page = baseMapper.selectPageUserList(pageQuery.build(), this.buildQueryWrapper(user));
         return TableDataInfo.build(page);
     }
 
@@ -58,7 +65,28 @@ public class SysUserServiceImpl implements ISysUserService {
      */
     @Override
     public List<SysUser> selectUserList(SysUser user) {
-        return baseMapper.selectUserList(user);
+        return baseMapper.selectUserList(this.buildQueryWrapper(user));
+    }
+
+    private Wrapper<SysUser> buildQueryWrapper(SysUser user) {
+        Map<String, Object> params = user.getParams();
+        QueryWrapper<SysUser> wrapper = Wrappers.query();
+        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
+            .eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId())
+            .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
+            .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
+            .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
+            .between(params.get("beginTime") != null && params.get("endTime") != null,
+                "u.create_time", params.get("beginTime"), params.get("endTime"))
+            .and(ObjectUtil.isNotNull(user.getDeptId()), w -> {
+                List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
+                    .select(SysDept::getDeptId)
+                    .apply(DataBaseHelper.findInSet(user.getDeptId(), "ancestors")));
+                List<Long> ids = deptList.stream().map(SysDept::getDeptId).collect(Collectors.toList());
+                ids.add(user.getDeptId());
+                w.in("u.dept_id", ids);
+            });
+        return wrapper;
     }
 
     /**
@@ -69,7 +97,13 @@ public class SysUserServiceImpl implements ISysUserService {
      */
     @Override
     public TableDataInfo<SysUser> selectAllocatedList(SysUser user, PageQuery pageQuery) {
-        Page<SysUser> page = baseMapper.selectAllocatedList(pageQuery.build(), user);
+        QueryWrapper<SysUser> wrapper = Wrappers.query();
+        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
+            .eq(ObjectUtil.isNotNull(user.getRoleId()), "r.role_id", user.getRoleId())
+            .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
+            .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
+            .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber());
+        Page<SysUser> page = baseMapper.selectAllocatedList(pageQuery.build(), wrapper);
         return TableDataInfo.build(page);
     }
 
@@ -81,7 +115,14 @@ public class SysUserServiceImpl implements ISysUserService {
      */
     @Override
     public TableDataInfo<SysUser> selectUnallocatedList(SysUser user, PageQuery pageQuery) {
-        Page<SysUser> page = baseMapper.selectUnallocatedList(pageQuery.build(), user);
+        List<Long> userId = userRoleMapper.selectUserIdsByRoleId(user.getRoleId());
+        QueryWrapper<SysUser> wrapper = Wrappers.query();
+        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
+            .and(w -> w.ne("r.role_id", user.getRoleId()).or().isNull("r.role_id"))
+            .notIn("u.user_id", userId)
+            .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
+            .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber());
+        Page<SysUser> page = baseMapper.selectUnallocatedList(pageQuery.build(), wrapper);
         return TableDataInfo.build(page);
     }
 
@@ -97,6 +138,17 @@ public class SysUserServiceImpl implements ISysUserService {
     }
 
     /**
+     * 通过手机号查询用户
+     *
+     * @param phonenumber 手机号
+     * @return 用户对象信息
+     */
+    @Override
+    public SysUser selectUserByPhonenumber(String phonenumber) {
+        return baseMapper.selectUserByPhonenumber(phonenumber);
+    }
+
+    /**
      * 通过用户ID查询用户
      *
      * @param userId 用户ID

部分文件因为文件数量过多而无法显示