Browse Source

Merge remote-tracking branch 'origin/dev'

# Conflicts:
#	README.md
疯狂的狮子li 3 năm trước cách đây
mục cha
commit
85762f7c92
100 tập tin đã thay đổi với 1499 bổ sung1473 xóa
  1. 9 3
      README.md
  2. 3 3
      docker/docker-compose.yml
  3. 49 51
      pom.xml
  4. 8 3
      ruoyi-admin/pom.xml
  5. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
  6. 16 16
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssConfigController.java
  7. 23 13
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java
  8. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  9. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java
  10. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
  11. 62 1
      ruoyi-admin/src/main/resources/application-dev.yml
  12. 62 1
      ruoyi-admin/src/main/resources/application-prod.yml
  13. 29 2
      ruoyi-admin/src/main/resources/application.yml
  14. 6 13
      ruoyi-common/pom.xml
  15. 5 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
  16. 7 2
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
  17. 8 3
      ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
  18. 2 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
  19. 107 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/OperLogDTO.java
  20. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
  21. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
  22. 5 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
  23. 0 5
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
  24. 0 260
      ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
  25. 9 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/LogininforService.java
  26. 9 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/OperLogService.java
  27. 69 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/TokenService.java
  28. 6 1
      ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java
  29. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/enums/ThreadPoolRejectedPolicy.java
  30. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/properties/TokenProperties.java
  31. 26 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/RedisUtils.java
  32. 1 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
  33. 3 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java
  34. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
  35. 1 1
      ruoyi-demo/pom.xml
  36. 3 0
      ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java
  37. 58 0
      ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisRateLimiterController.java
  38. 1 1
      ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestDemoController.java
  39. 5 1
      ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/TestDemoServiceImpl.java
  40. 5 1
      ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/TestTreeServiceImpl.java
  41. 1 1
      ruoyi-extend/pom.xml
  42. 1 1
      ruoyi-extend/ruoyi-monitor-admin/pom.xml
  43. 14 3
      ruoyi-framework/pom.xml
  44. 4 32
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java
  45. 3 27
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java
  46. 53 104
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
  47. 27 78
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
  48. 69 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RepeatSubmitAspect.java
  49. 8 4
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/AsyncConfig.java
  50. 47 47
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
  51. 5 22
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
  52. 10 14
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
  53. 0 32
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java
  54. 87 75
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SwaggerConfig.java
  55. 103 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java
  56. 1 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RepeatSubmitProperties.java
  57. 32 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SecurityProperties.java
  58. 29 4
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SwaggerProperties.java
  59. 0 50
      ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java
  60. 0 114
      ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java
  61. 1 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java
  62. 2 3
      ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
  63. 3 3
      ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java
  64. 0 97
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/AsyncService.java
  65. 1 1
      ruoyi-generator/pom.xml
  66. 18 19
      ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
  67. 24 2
      ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
  68. 0 2
      ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
  69. 6 1
      ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
  70. 1 7
      ruoyi-generator/src/main/resources/vm/java/vo.java.vm
  71. 31 48
      ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
  72. 34 51
      ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
  73. 1 1
      ruoyi-oss/pom.xml
  74. 2 2
      ruoyi-oss/src/main/java/com/ruoyi/oss/constant/CloudConstant.java
  75. 1 1
      ruoyi-oss/src/main/java/com/ruoyi/oss/enumd/CloudServiceEnumd.java
  76. 1 1
      ruoyi-oss/src/main/java/com/ruoyi/oss/properties/CloudStorageProperties.java
  77. 1 1
      ruoyi-oss/src/main/java/com/ruoyi/oss/service/ICloudStorageStrategy.java
  78. 1 1
      ruoyi-oss/src/main/java/com/ruoyi/oss/service/abstractd/AbstractCloudStorageStrategy.java
  79. 2 2
      ruoyi-quartz/pom.xml
  80. 2 2
      ruoyi-system/pom.xml
  81. 2 2
      ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOss.java
  82. 1 1
      ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOssConfig.java
  83. 2 2
      ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssBo.java
  84. 2 2
      ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssConfigBo.java
  85. 5 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java
  86. 2 2
      ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssConfigVo.java
  87. 4 4
      ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssVo.java
  88. 1 1
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssConfigMapper.java
  89. 5 5
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssConfigService.java
  90. 30 48
      ruoyi-system/src/main/java/com/ruoyi/system/service/PermissionService.java
  91. 45 52
      ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java
  92. 17 26
      ruoyi-system/src/main/java/com/ruoyi/system/service/SysPermissionService.java
  93. 26 46
      ruoyi-system/src/main/java/com/ruoyi/system/service/SysRegisterService.java
  94. 64 1
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java
  95. 1 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
  96. 21 2
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java
  97. 1 1
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssConfigServiceImpl.java
  98. 14 5
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TokenServiceImpl.java
  99. 15 23
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserDetailsServiceImpl.java
  100. 7 3
      ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml

+ 9 - 3
README.md

@@ -4,16 +4,19 @@
 [![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-3.1.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-3.2.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
 [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.5-blue.svg)]()
-[![JDK-8+](https://img.shields.io/badge/JDK-8+-green.svg)]()
+[![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
 [![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
+[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
 
 RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼容原框架)
 
 | 功能介绍 | 使用技术 | 文档地址 | 特性注意事项 |
 |---|---|---|---|
 | 当前框架 | RuoYi-Vue-Plus | [RuoYi-Vue-Plus文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) | 重写RuoYi-Vue全方位升级(不兼容原框架) |
+| satoken分支 | RuoYi-Vue-Plus-satoken | [satoken分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/) | 使用satoken重构权限鉴权(仅供学习不推荐上生产) |
+| 单体分支 | RuoYi-Vue-Plus-fast | [fast分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/) | 单体应用结构 |
 | 原框架 | RuoYi-Vue | [RuoYi-Vue官网](http://ruoyi.vip/) | 定期同步需要的功能 |
 | 前端开发框架 | Vue、Element UI | [Element UI官网](https://element.eleme.cn/#/zh-CN) | |
 | 后端开发框架 | SpringBoot | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn) | |
@@ -24,10 +27,12 @@ RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼
 | 数据库框架 | Mybatis-Plus | [Mybatis-Plus文档](https://baomidou.com/guide/) | 快速 CRUD 增加开发效率 |
 | 数据库框架 | p6spy | [p6spy官网](https://p6spy.readthedocs.io/) | 更强劲的 SQL 分析 |
 | 多数据源框架 | dynamic-datasource | [dynamic-ds文档](https://www.kancloud.cn/tracy5546/dynamic-datasource/content) | 支持主从与多种类数据库异构 |
-| Redis客户端 | Redisson | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) | 性能强劲、API丰富 |
 | 序列化框架 | Jackson | [Jackson官网](https://github.com/FasterXML/jackson) | 统一使用 jackson 高效可靠 |
 | 网络框架 | Feign、OkHttp3 | [Feign官网](https://github.com/OpenFeign/feign) | 接口化管理 HTTP 请求 |
+| Redis客户端 | Redisson | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) | 支持单机、集群配置 |
+| 分布式限流 | Redisson | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) | 全局、请求IP、集群ID 多种限流 |
 | 分布式锁 | Lock4j | [Lock4j官网](https://gitee.com/baomidou/lock4j) | 注解锁、工具锁 多种多样 |
+| 分布式幂等 | Lock4j | [Lock4j文档](https://gitee.com/baomidou/lock4j) | 基于分布式锁实现 |
 | 文件存储 | Minio | [Minio文档](https://docs.min.io/) | 本地存储 |
 | 文件存储 | 七牛、阿里、腾讯 | [OSS使用文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4359146&doc_id=1469725) | 云存储 |
 | 监控框架 | SpringBoot-Admin | [SpringBoot-Admin文档](https://codecentric.github.io/spring-boot-admin/current/) | 全方位服务监控 |
@@ -67,6 +72,7 @@ RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼
 * 同步升级 RuoYi-Vue
 * GitHub 地址 [RuoYi-Vue-Plus-github](https://github.com/JavaLionLi/RuoYi-Vue-Plus)
 * 单模块 fast 分支 [RuoYi-Vue-Plus-fast](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/)
+* satoken 分支 [RuoYi-Vue-Plus-satoken](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/)
 * 用户扩展项目 [扩展项目列表](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
 
 ## 加群与捐献

+ 3 - 3
docker/docker-compose.yml

@@ -103,7 +103,7 @@ services:
         ipv4_address: 172.30.0.54
 
   ruoyi-server1:
-    image: "ruoyi/ruoyi-server:3.1.0"
+    image: "ruoyi/ruoyi-server:3.2.0"
     container_name: ruoyi-server1
     environment:
       # 时区上海
@@ -118,7 +118,7 @@ services:
         ipv4_address: 172.30.0.60
 
   ruoyi-server2:
-    image: "ruoyi/ruoyi-server:3.1.0"
+    image: "ruoyi/ruoyi-server:3.2.0"
     container_name: ruoyi-server2
     environment:
       # 时区上海
@@ -133,7 +133,7 @@ services:
         ipv4_address: 172.30.0.61
 
   ruoyi-monitor-admin:
-    image: "ruoyi/ruoyi-monitor-admin:3.1.0"
+    image: "ruoyi/ruoyi-monitor-admin:3.2.0"
     container_name: ruoyi-monitor-admin
     environment:
       # 时区上海

+ 49 - 51
pom.xml

@@ -6,40 +6,44 @@
 
     <groupId>com.ruoyi</groupId>
     <artifactId>ruoyi-vue-plus</artifactId>
-    <version>3.1.0</version>
+    <version>3.2.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>3.1.0</ruoyi-vue-plus.version>
-        <spring-boot.version>2.5.4</spring-boot.version>
+        <ruoyi-vue-plus.version>3.2.0</ruoyi-vue-plus.version>
+        <spring-boot.version>2.5.5</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.0</maven-jar-plugin.version>
         <druid.version>1.2.6</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>
-        <easyexcel.version>2.2.10</easyexcel.version>
+        <easyexcel.version>2.2.11</easyexcel.version>
         <velocity.version>1.7</velocity.version>
         <jwt.version>0.9.1</jwt.version>
-        <mybatis-plus.version>3.4.3.3</mybatis-plus.version>
+        <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
         <p6spy.version>3.9.1</p6spy.version>
-        <hutool.version>5.7.11</hutool.version>
+        <hutool.version>5.7.13</hutool.version>
         <feign.version>3.0.3</feign.version>
         <feign-okhttp.version>11.6</feign-okhttp.version>
         <okhttp.version>4.9.1</okhttp.version>
         <spring-boot-admin.version>2.5.1</spring-boot-admin.version>
-        <redisson.version>3.16.2</redisson.version>
+        <redisson.version>3.16.3</redisson.version>
         <lock4j.version>2.2.1</lock4j.version>
         <dynamic-ds.version>3.4.1</dynamic-ds.version>
 
+        <!-- jdk11 缺失依赖 jaxb-->
+        <jaxb.version>3.0.1</jaxb.version>
+
         <!-- OSS 配置 -->
         <qiniu.version>7.8.0</qiniu.version>
         <aliyun.oss.version>3.13.1</aliyun.oss.version>
-        <qcloud.cos.version>5.6.51</qcloud.cos.version>
+        <qcloud.cos.version>5.6.55</qcloud.cos.version>
         <minio.version>8.3.0</minio.version>
 
         <!-- docker 配置 -->
@@ -48,7 +52,7 @@
         <docker.namespace>ruoyi</docker.namespace>
         <docker.plugin.version>1.2.2</docker.plugin.version>
     </properties>
-
+	
     <!-- 依赖声明 -->
     <dependencyManagement>
         <dependencies>
@@ -73,6 +77,18 @@
                 <groupId>com.github.xiaoymin</groupId>
                 <artifactId>knife4j-spring-boot-starter</artifactId>
                 <version>${knife4j.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>swagger-annotations</artifactId>
+                        <groupId>io.swagger</groupId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <dependency>
+                <groupId>io.swagger</groupId>
+                <artifactId>swagger-annotations</artifactId>
+                <version>${swagger-annotations.version}</version>
             </dependency>
 
             <!-- excel工具 -->
@@ -86,6 +102,16 @@
                 <groupId>com.alibaba</groupId>
                 <artifactId>easyexcel</artifactId>
                 <version>${easyexcel.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.apache.poi</groupId>
+                        <artifactId>poi</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.apache.poi</groupId>
+                        <artifactId>poi-ooxml-schemas</artifactId>
+                    </exclusion>
+                </exclusions>
             </dependency>
 
             <!-- velocity代码生成使用模板 -->
@@ -136,6 +162,12 @@
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-starter-openfeign</artifactId>
                 <version>${feign.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>feign-core</artifactId>
+                        <groupId>io.github.openfeign</groupId>
+                    </exclusion>
+                </exclusions>
             </dependency>
 
             <dependency>
@@ -239,7 +271,12 @@
 
 
     <dependencies>
-
+        <!-- jdk11 缺失依赖 jaxb-->
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-impl</artifactId>
+            <version>${jaxb.version}</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -318,48 +355,9 @@
             <properties>
                 <profiles.active>prod</profiles.active>
                 <logging.level>warn</logging.level>
-                <endpoints.include>health,info</endpoints.include>
-            </properties>
-        </profile>
-
-        <!-- jdk多版本配置 -->
-        <profile>
-            <id>jdk8</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-                <jdk>1.8</jdk>
-            </activation>
-            <properties>
-                <java.version>1.8</java.version>
-            </properties>
-        </profile>
-        <profile>
-            <id>jdk11</id>
-            <activation>
-                <jdk>11</jdk>
-            </activation>
-            <properties>
-                <java.version>11</java.version>
-                <jaxb.version>3.0.1</jaxb.version>
+                <endpoints.include>health, info, logfile</endpoints.include>
             </properties>
-            <dependencyManagement>
-                <dependencies>
-                    <!-- jdk11 缺失依赖 jaxb-->
-                    <dependency>
-                        <groupId>com.sun.xml.bind</groupId>
-                        <artifactId>jaxb-impl</artifactId>
-                        <version>${jaxb.version}</version>
-                    </dependency>
-                </dependencies>
-            </dependencyManagement>
-            <dependencies>
-                <!--jaxb-->
-                <dependency>
-                    <groupId>com.sun.xml.bind</groupId>
-                    <artifactId>jaxb-impl</artifactId>
-                </dependency>
-            </dependencies>
         </profile>
     </profiles>
 
-</project>
+</project>

+ 8 - 3
ruoyi-admin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.1.0</version>
+        <version>3.2.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>
@@ -36,6 +36,11 @@
             <artifactId>ruoyi-framework</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-system</artifactId>
+        </dependency>
+
         <!-- 定时任务-->
         <dependency>
             <groupId>com.ruoyi</groupId>
@@ -82,7 +87,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
-                <version>3.1.0</version>
+                <version>3.2.0</version>
                 <configuration>
                     <failOnMissingWebXml>false</failOnMissingWebXml>
                     <warName>${project.artifactId}</warName>
@@ -110,4 +115,4 @@
         </plugins>
     </build>
 
-</project>
+</project>

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

@@ -6,8 +6,8 @@ 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.utils.SecurityUtils;
-import com.ruoyi.framework.web.service.SysLoginService;
-import com.ruoyi.framework.web.service.SysPermissionService;
+import com.ruoyi.system.service.SysLoginService;
+import com.ruoyi.system.service.SysPermissionService;
 import com.ruoyi.system.service.ISysMenuService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;

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

@@ -25,14 +25,14 @@ import javax.validation.constraints.NotNull;
 import java.util.Arrays;
 
 /**
- * 存储配置Controller
+ * 对象存储配置Controller
  *
  * @author Lion Li
  * @author 孤舟烟雨
  * @date 2021-08-13
  */
 @Validated
-@Api(value = "云存储配置控制器", tags = {"云存储配置管理"})
+@Api(value = "对象存储配置控制器", tags = {"对象存储配置管理"})
 @RequiredArgsConstructor(onConstructor_ = @Autowired)
 @RestController
 @RequestMapping("/system/oss/config")
@@ -41,9 +41,9 @@ public class SysOssConfigController extends BaseController {
 	private final ISysOssConfigService iSysOssConfigService;
 
 	/**
-	 * 查询存储配置列表
+	 * 查询对象存储配置列表
 	 */
-	@ApiOperation("查询存储配置列表")
+	@ApiOperation("查询对象存储配置列表")
 	@PreAuthorize("@ss.hasPermi('system:oss:list')")
 	@GetMapping("/list")
 	public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo) {
@@ -51,9 +51,9 @@ public class SysOssConfigController extends BaseController {
 	}
 
 	/**
-	 * 获取存储配置详细信息
+	 * 获取对象存储配置详细信息
 	 */
-	@ApiOperation("获取存储配置详细信息")
+	@ApiOperation("获取对象存储配置详细信息")
 	@PreAuthorize("@ss.hasPermi('system:oss:query')")
 	@GetMapping("/{ossConfigId}")
 	public AjaxResult<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
@@ -62,11 +62,11 @@ public class SysOssConfigController extends BaseController {
 	}
 
 	/**
-	 * 新增存储配置
+	 * 新增对象存储配置
 	 */
-	@ApiOperation("新增存储配置")
+	@ApiOperation("新增对象存储配置")
 	@PreAuthorize("@ss.hasPermi('system:oss:add')")
-	@Log(title = "存储配置", businessType = BusinessType.INSERT)
+	@Log(title = "对象存储配置", businessType = BusinessType.INSERT)
 	@RepeatSubmit()
 	@PostMapping()
 	public AjaxResult<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
@@ -74,11 +74,11 @@ public class SysOssConfigController extends BaseController {
 	}
 
 	/**
-	 * 修改存储配置
+	 * 修改对象存储配置
 	 */
-	@ApiOperation("修改存储配置")
+	@ApiOperation("修改对象存储配置")
 	@PreAuthorize("@ss.hasPermi('system:oss:edit')")
-	@Log(title = "存储配置", businessType = BusinessType.UPDATE)
+	@Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
 	@RepeatSubmit()
 	@PutMapping()
 	public AjaxResult<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
@@ -86,11 +86,11 @@ public class SysOssConfigController extends BaseController {
 	}
 
 	/**
-	 * 删除存储配置
+	 * 删除对象存储配置
 	 */
-	@ApiOperation("删除存储配置")
+	@ApiOperation("删除对象存储配置")
 	@PreAuthorize("@ss.hasPermi('system:oss:remove')")
-	@Log(title = "存储配置", businessType = BusinessType.DELETE)
+	@Log(title = "对象存储配置", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ossConfigIds}")
 	public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空")
 								   @PathVariable Long[] ossConfigIds) {
@@ -101,7 +101,7 @@ public class SysOssConfigController extends BaseController {
 	 * 状态修改
 	 */
 	@PreAuthorize("@ss.hasPermi('system:oss:edit')")
-	@Log(title = "存储状态修改", businessType = BusinessType.UPDATE)
+	@Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
 	@PutMapping("/changeStatus")
 	public AjaxResult changeStatus(@RequestBody SysOssConfigBo bo) {
 		return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));

+ 23 - 13
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java

@@ -3,6 +3,7 @@ package com.ruoyi.web.controller.system;
 
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpException;
 import cn.hutool.http.HttpUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.ruoyi.common.annotation.Log;
@@ -49,7 +50,7 @@ import java.util.Map;
  * @author Lion Li
  */
 @Validated
-@Api(value = "OSS云存储控制器", tags = {"OSS云存储管理"})
+@Api(value = "OSS对象存储控制器", tags = {"OSS对象存储管理"})
 @RequiredArgsConstructor(onConstructor_ = @Autowired)
 @RestController
 @RequestMapping("/system/oss")
@@ -59,9 +60,9 @@ public class SysOssController extends BaseController {
 	private final ISysConfigService iSysConfigService;
 
 	/**
-	 * 查询OSS存储列表
+	 * 查询OSS对象存储列表
 	 */
-	@ApiOperation("查询OSS存储列表")
+	@ApiOperation("查询OSS对象存储列表")
 	@PreAuthorize("@ss.hasPermi('system:oss:list')")
 	@GetMapping("/list")
 	public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo) {
@@ -69,14 +70,14 @@ public class SysOssController extends BaseController {
 	}
 
 	/**
-	 * 上传OSS存储
+	 * 上传OSS对象存储
 	 */
-	@ApiOperation("上传OSS存储")
+	@ApiOperation("上传OSS对象存储")
 	@ApiImplicitParams({
 		@ApiImplicitParam(name = "file", value = "文件", dataType = "java.io.File", required = true),
 	})
 	@PreAuthorize("@ss.hasPermi('system:oss:upload')")
-	@Log(title = "OSS存储", businessType = BusinessType.INSERT)
+	@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
 	@RepeatSubmit
 	@PostMapping("/upload")
 	public AjaxResult<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
@@ -90,7 +91,7 @@ public class SysOssController extends BaseController {
 		return AjaxResult.success(map);
 	}
 
-	@ApiOperation("下载OSS存储")
+	@ApiOperation("下载OSS对象存储")
 	@PreAuthorize("@ss.hasPermi('system:oss:download')")
 	@GetMapping("/download/{ossId}")
 	public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
@@ -101,18 +102,27 @@ public class SysOssController extends BaseController {
 		response.reset();
 		response.addHeader("Access-Control-Allow-Origin", "*");
 		response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
-		FileUtils.setAttachmentResponseHeader(response, URLEncoder.encode(sysOss.getOriginalName(), StandardCharsets.UTF_8.toString()));
+		FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
 		response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
-		long data = HttpUtil.download(sysOss.getUrl(), response.getOutputStream(), false);
+		long data;
+		try {
+			data = HttpUtil.download(sysOss.getUrl(), response.getOutputStream(), false);
+		} catch (HttpException e) {
+			if (e.getMessage().contains("403")) {
+				throw new ServiceException("无读取权限, 请在对应的OSS开启'公有读'权限!");
+			} else {
+				throw new ServiceException(e.getMessage());
+			}
+		}
 		response.setContentLength(Convert.toInt(data));
 	}
 
 	/**
-	 * 删除OSS云存储
+	 * 删除OSS对象存储
 	 */
-	@ApiOperation("删除OSS云存储")
+	@ApiOperation("删除OSS对象存储")
 	@PreAuthorize("@ss.hasPermi('system:oss:remove')")
-	@Log(title = "OSS云存储" , businessType = BusinessType.DELETE)
+	@Log(title = "OSS对象存储" , businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ossIds}")
 	public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空")
 								   @PathVariable Long[] ossIds) {
@@ -124,7 +134,7 @@ public class SysOssController extends BaseController {
 	 */
 	@ApiOperation("变更图片列表预览状态")
 	@PreAuthorize("@ss.hasPermi('system:oss:edit')")
-	@Log(title = "OSS存储" , businessType = BusinessType.UPDATE)
+	@Log(title = "OSS对象存储" , businessType = BusinessType.UPDATE)
 	@PutMapping("/changePreviewListResource")
 	public AjaxResult<Void> changePreviewListResource(@RequestBody String body) {
 		Map<String, Boolean> map = JsonUtils.parseMap(body);

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

@@ -7,9 +7,9 @@ import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.core.service.TokenService;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.web.service.TokenService;
 import com.ruoyi.system.domain.SysOss;
 import com.ruoyi.system.service.ISysOssService;
 import com.ruoyi.system.service.ISysUserService;

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

@@ -4,7 +4,7 @@ import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.domain.model.RegisterBody;
 import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.web.service.SysRegisterService;
+import com.ruoyi.system.service.SysRegisterService;
 import com.ruoyi.system.service.ISysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;

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

@@ -9,13 +9,13 @@ import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.core.service.TokenService;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.framework.web.service.SysPermissionService;
-import com.ruoyi.framework.web.service.TokenService;
 import com.ruoyi.system.domain.SysUserRole;
 import com.ruoyi.system.service.ISysRoleService;
 import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.SysPermissionService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;

+ 62 - 1
ruoyi-admin/src/main/resources/application-dev.yml

@@ -70,7 +70,7 @@ spring:
           config:
             multi-statement-allow: true
 
---- # redis 配置
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring:
   redis:
     # 地址
@@ -118,6 +118,67 @@ redisson:
     # DNS监测时间间隔,单位:毫秒
     dnsMonitoringInterval: 5000
 
+#--- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
+#spring:
+#  redis:
+#    cluster:
+#      nodes:
+#        - 192.168.0.100:6379
+#        - 192.168.0.101:6379
+#        - 192.168.0.102:6379
+#    # 密码
+#    password:
+#    # 连接超时时间
+#    timeout: 10s
+#    # 是否开启ssl
+#    ssl: false
+#
+#redisson:
+#  # 线程池数量
+#  threads: 16
+#  # Netty线程池数量
+#  nettyThreads: 32
+#  # 传输模式
+#  transportMode: "NIO"
+#  # 集群配置
+#  clusterServersConfig:
+#    # 客户端名称
+#    clientName: ${ruoyi.name}
+#    # master最小空闲连接数
+#    masterConnectionMinimumIdleSize: 32
+#    # master连接池大小
+#    masterConnectionPoolSize: 64
+#    # slave最小空闲连接数
+#    slaveConnectionMinimumIdleSize: 32
+#    # slave连接池大小
+#    slaveConnectionPoolSize: 64
+#    # 连接空闲超时,单位:毫秒
+#    idleConnectionTimeout: 10000
+#    # ping连接间隔
+#    pingConnectionInterval: 1000
+#    # 命令等待超时,单位:毫秒
+#    timeout: 3000
+#    # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+#    retryAttempts: 3
+#    # 命令重试发送时间间隔,单位:毫秒
+#    retryInterval: 1500
+#    # 从可用服务器的内部列表中排除 Redis Slave 重新连接尝试的间隔。
+#    failedSlaveReconnectionInterval: 3000
+#    # 发布和订阅连接池最小空闲连接数
+#    subscriptionConnectionMinimumIdleSize: 1
+#    # 发布和订阅连接池大小
+#    subscriptionConnectionPoolSize: 50
+#    # 单个连接最大订阅数量
+#    subscriptionsPerConnection: 5
+#    # 扫描间隔
+#    scanInterval: 1000
+#    # DNS监测时间间隔,单位:毫秒
+#    dnsMonitoringInterval: 5000
+#    # 读取模式
+#    readMode: "SLAVE"
+#    # 订阅模式
+#    subscriptionMode: "MASTER"
+
 --- # 监控配置
 spring:
   boot:

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

@@ -70,7 +70,7 @@ spring:
           config:
             multi-statement-allow: true
 
---- # redis 配置
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring:
   redis:
     # 地址
@@ -118,6 +118,67 @@ redisson:
     # DNS监测时间间隔,单位:毫秒
     dnsMonitoringInterval: 5000
 
+#--- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
+#spring:
+#  redis:
+#    cluster:
+#      nodes:
+#        - 192.168.0.100:6379
+#        - 192.168.0.101:6379
+#        - 192.168.0.102:6379
+#    # 密码
+#    password:
+#    # 连接超时时间
+#    timeout: 10s
+#    # 是否开启ssl
+#    ssl: false
+#
+#redisson:
+#  # 线程池数量
+#  threads: 16
+#  # Netty线程池数量
+#  nettyThreads: 32
+#  # 传输模式
+#  transportMode: "NIO"
+#  # 集群配置
+#  clusterServersConfig:
+#    # 客户端名称
+#    clientName: ${ruoyi.name}
+#    # master最小空闲连接数
+#    masterConnectionMinimumIdleSize: 32
+#    # master连接池大小
+#    masterConnectionPoolSize: 64
+#    # slave最小空闲连接数
+#    slaveConnectionMinimumIdleSize: 32
+#    # slave连接池大小
+#    slaveConnectionPoolSize: 64
+#    # 连接空闲超时,单位:毫秒
+#    idleConnectionTimeout: 10000
+#    # ping连接间隔
+#    pingConnectionInterval: 1000
+#    # 命令等待超时,单位:毫秒
+#    timeout: 3000
+#    # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+#    retryAttempts: 3
+#    # 命令重试发送时间间隔,单位:毫秒
+#    retryInterval: 1500
+#    # 从可用服务器的内部列表中排除 Redis Slave 重新连接尝试的间隔。
+#    failedSlaveReconnectionInterval: 3000
+#    # 发布和订阅连接池最小空闲连接数
+#    subscriptionConnectionMinimumIdleSize: 1
+#    # 发布和订阅连接池大小
+#    subscriptionConnectionPoolSize: 50
+#    # 单个连接最大订阅数量
+#    subscriptionsPerConnection: 5
+#    # 扫描间隔
+#    scanInterval: 1000
+#    # DNS监测时间间隔,单位:毫秒
+#    dnsMonitoringInterval: 5000
+#    # 读取模式
+#    readMode: "SLAVE"
+#    # 订阅模式
+#    subscriptionMode: "MASTER"
+
 --- # 监控配置
 spring:
   boot:

+ 29 - 2
ruoyi-admin/src/main/resources/application.yml

@@ -106,10 +106,32 @@ token:
   # 令牌有效期(默认30分钟)
   expireTime: 30
 
+# security配置
+security:
+  # 登出路径
+  logout-url: /logout
+  # 匿名路径
+  anonymous:
+    - /login
+    - /register
+    - /captchaImage
+    # swagger 文档配置
+    - /doc.html
+    - /swagger-resources/**
+    - /webjars/**
+    - /*/api-docs
+    # druid 监控配置
+    - /druid/**
+    # actuator 监控配置
+    - /actuator
+    - /actuator/**
+  # 用户放行
+  permit-all:
+
 # 重复提交
 repeat-submit:
   # 全局间隔时间(毫秒)
-  intervalTime: 1000
+  interval: 5000
 
 # MyBatisPlus配置
 # https://baomidou.com/config/
@@ -221,6 +243,11 @@ swagger:
     name: Lion Li
     email: crazylionli@163.com
     url: https://gitee.com/JavaLionLi/RuoYi-Vue-Plus
+  groups:
+    - name: 演示案例
+      basePackage: com.ruoyi.demo
+    - name: 系统模块
+      basePackage: com.ruoyi.admin
 
 # 防止XSS攻击
 xss:
@@ -244,7 +271,7 @@ thread-pool:
   # 线程池维护线程所允许的空闲时间
   keepAliveSeconds: 300
   # 线程池对拒绝任务(无线程可用)的处理策略
-  # CALLER_RUNS_POLICY 等待
+  # CALLER_RUNS_POLICY 调用方执行
   # DISCARD_OLDEST_POLICY 放弃最旧的
   # DISCARD_POLICY 丢弃
   # ABORT_POLICY 中止

+ 6 - 13
ruoyi-common/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.1.0</version>
+        <version>3.2.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -132,6 +132,11 @@
         </dependency>
 
         <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
@@ -147,22 +152,10 @@
             <artifactId>redisson-spring-boot-starter</artifactId>
         </dependency>
 
-        <!-- dynamic-datasource 多数据源-->
-        <dependency>
-            <groupId>com.baomidou</groupId>
-            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
-        </dependency>
-        <!-- sql性能分析插件 -->
-        <dependency>
-            <groupId>p6spy</groupId>
-            <artifactId>p6spy</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
         </dependency>
-
     </dependencies>
 
 </project>

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

@@ -38,4 +38,9 @@ public @interface Log
      * 是否保存请求的参数
      */
     public boolean isSaveRequestData() default true;
+
+    /**
+     * 是否保存响应的参数
+     */
+    public boolean isSaveResponseData() default true;
 }

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

@@ -20,10 +20,15 @@ import java.util.concurrent.TimeUnit;
 public @interface RepeatSubmit {
 
 	/**
-	 * 默认使用全局配置
+	 * 间隔时间(ms),小于此时间视为重复提交
 	 */
-	int intervalTime() default 0;
+	int interval() default 5000;
 
 	TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
 
+    /**
+     * 提示消息
+     */
+    String message() default "不允许重复提交,请稍后再试";
+
 }

+ 8 - 3
ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java

@@ -44,16 +44,21 @@ public class GenConstants
     public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
             "bit", "bigint", "float", "double", "decimal" };
 
+    /** 页面不需要添加字段 */
+    public static final String[] COLUMNNAME_NOT_ADD = { "create_by", "create_time", "del_flag", "update_by",
+            "update_time", "version" };
+
     /** 页面不需要编辑字段 */
-    public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };
+    public static final String[] COLUMNNAME_NOT_EDIT = { "create_by", "create_time", "del_flag", "update_by",
+            "update_time", "version" };
 
     /** 页面不需要显示的列表字段 */
     public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by",
-            "update_time" };
+            "update_time", "version" };
 
     /** 页面不需要查询字段 */
     public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by",
-            "update_time", "remark" };
+            "update_time", "remark", "version" };
 
     /** Entity基类字段 */
     public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };

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

@@ -1,5 +1,6 @@
 package com.ruoyi.common.core.domain;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -62,6 +63,7 @@ public class BaseEntity implements Serializable {
 	/**
 	 * 请求参数
 	 */
+	@JsonIgnore
 	@ApiModelProperty(value = "请求参数")
 	private Map<String, Object> params = new HashMap<>();
 

+ 107 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/OperLogDTO.java

@@ -0,0 +1,107 @@
+package com.ruoyi.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 操作日志记录表 oper_log
+ *
+ * @author ruoyi
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class OperLogDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 日志主键
+     */
+    private Long operId;
+
+    /**
+     * 操作模块
+     */
+    private String title;
+
+    /**
+     * 业务类型(0其它 1新增 2修改 3删除)
+     */
+    private Integer businessType;
+
+    /**
+     * 业务类型数组
+     */
+    private Integer[] businessTypes;
+
+    /**
+     * 请求方法
+     */
+    private String method;
+
+    /**
+     * 请求方式
+     */
+    private String requestMethod;
+
+    /**
+     * 操作类别(0其它 1后台用户 2手机端用户)
+     */
+    private Integer operatorType;
+
+    /**
+     * 操作人员
+     */
+    private String operName;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 请求url
+     */
+    private String operUrl;
+
+    /**
+     * 操作地址
+     */
+    private String operIp;
+
+    /**
+     * 操作地点
+     */
+    private String operLocation;
+
+    /**
+     * 请求参数
+     */
+    private String operParam;
+
+    /**
+     * 返回参数
+     */
+    private String jsonResult;
+
+    /**
+     * 操作状态(0正常 1异常)
+     */
+    private Integer status;
+
+    /**
+     * 错误消息
+     */
+    private String errorMsg;
+
+    /**
+     * 操作时间
+     */
+    private Date operTime;
+
+}

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

@@ -90,7 +90,7 @@ public class SysDictData implements Serializable {
 	 * 状态(0正常 1停用)
 	 */
 	@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
-	@ExcelDictFormat(dictType = "sys_common_status")
+	@ExcelDictFormat(dictType = "sys_normal_disable")
 	private String status;
 
 	/**

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

@@ -57,7 +57,7 @@ public class SysDictType implements Serializable {
 	 * 状态(0正常 1停用)
 	 */
 	@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
-	@ExcelDictFormat(dictType = "sys_common_status")
+	@ExcelDictFormat(dictType = "sys_normal_disable")
 	private String status;
 
 	/**

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

@@ -66,6 +66,11 @@ public class SysMenu implements Serializable {
 	@Size(min = 0, max = 200, message = "组件路径不能超过255个字符")
 	private String component;
 
+    /**
+     * 路由参数
+     */
+    private String query;
+
 	/**
 	 * 是否为外链(0是 1否)
 	 */

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

@@ -92,11 +92,6 @@ public class SysUser implements Serializable {
 	}
 
 	/**
-	 * 盐加密
-	 */
-	private String salt;
-
-	/**
 	 * 帐号状态(0正常 1停用)
 	 */
 	private String status;

+ 0 - 260
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java

@@ -1,260 +0,0 @@
-package com.ruoyi.common.core.redis;
-
-import com.google.common.collect.Lists;
-import org.redisson.api.*;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * spring redis 工具类
- *
- * @author shenxinquan
- * @see com.ruoyi.common.utils.RedisUtils
- * @deprecated 3.2.0 删除此类
- **/
-@SuppressWarnings(value = {"unchecked", "rawtypes"})
-@Component
-@Deprecated
-public class RedisCache {
-
-	@Autowired
-	private RedissonClient redissonClient;
-
-	/**
-	 * 发布通道消息
-	 *
-	 * @param channelKey 通道key
-	 * @param msg 发送数据
-	 * @param consumer 自定义处理
-	 */
-	public <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
-		RTopic topic = redissonClient.getTopic(channelKey);
-		topic.publish(msg);
-		consumer.accept(msg);
-	}
-
-	public <T> void publish(String channelKey, T msg) {
-		RTopic topic = redissonClient.getTopic(channelKey);
-		topic.publish(msg);
-	}
-
-	/**
-	 * 订阅通道接收消息
-	 *
-	 * @param channelKey 通道key
-	 * @param clazz 消息类型
-	 * @param consumer 自定义处理
-	 */
-	public <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
-		RTopic topic = redissonClient.getTopic(channelKey);
-		topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
-	}
-
-	/**
-	 * 缓存基本的对象,Integer、String、实体类等
-	 *
-	 * @param key   缓存的键值
-	 * @param value 缓存的值
-	 */
-	public <T> void setCacheObject(final String key, final T value) {
-		redissonClient.getBucket(key).set(value);
-	}
-
-	/**
-	 * 缓存基本的对象,Integer、String、实体类等
-	 *
-	 * @param key      缓存的键值
-	 * @param value    缓存的值
-	 * @param timeout  时间
-	 * @param timeUnit 时间颗粒度
-	 */
-	public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
-		RBucket<T> result = redissonClient.getBucket(key);
-		result.set(value);
-		result.expire(timeout, timeUnit);
-	}
-
-	/**
-	 * 设置有效时间
-	 *
-	 * @param key     Redis键
-	 * @param timeout 超时时间
-	 * @return true=设置成功;false=设置失败
-	 */
-	public boolean expire(final String key, final long timeout) {
-		return expire(key, timeout, TimeUnit.SECONDS);
-	}
-
-	/**
-	 * 设置有效时间
-	 *
-	 * @param key     Redis键
-	 * @param timeout 超时时间
-	 * @param unit    时间单位
-	 * @return true=设置成功;false=设置失败
-	 */
-	public boolean expire(final String key, final long timeout, final TimeUnit unit) {
-		RBucket rBucket = redissonClient.getBucket(key);
-		return rBucket.expire(timeout, unit);
-	}
-
-	/**
-	 * 获得缓存的基本对象。
-	 *
-	 * @param key 缓存键值
-	 * @return 缓存键值对应的数据
-	 */
-	public <T> T getCacheObject(final String key) {
-		RBucket<T> rBucket = redissonClient.getBucket(key);
-		return rBucket.get();
-	}
-
-	/**
-	 * 删除单个对象
-	 *
-	 * @param key
-	 */
-	public boolean deleteObject(final String key) {
-		return redissonClient.getBucket(key).delete();
-	}
-
-	/* */
-
-	/**
-	 * 删除集合对象
-	 *
-	 * @param collection 多个对象
-	 * @return
-	 */
-	public void deleteObject(final Collection collection) {
-		RBatch batch = redissonClient.createBatch();
-		collection.forEach(t->{
-			batch.getBucket(t.toString()).deleteAsync();
-		});
-		batch.execute();
-	}
-
-	/**
-	 * 缓存List数据
-	 *
-	 * @param key      缓存的键值
-	 * @param dataList 待缓存的List数据
-	 * @return 缓存的对象
-	 */
-	public <T> boolean setCacheList(final String key, final List<T> dataList) {
-		RList<T> rList = redissonClient.getList(key);
-		return rList.addAll(dataList);
-	}
-
-	/**
-	 * 获得缓存的list对象
-	 *
-	 * @param key 缓存的键值
-	 * @return 缓存键值对应的数据
-	 */
-	public <T> List<T> getCacheList(final String key) {
-		RList<T> rList = redissonClient.getList(key);
-		return rList.readAll();
-	}
-
-	/**
-	 * 缓存Set
-	 *
-	 * @param key     缓存键值
-	 * @param dataSet 缓存的数据
-	 * @return 缓存数据的对象
-	 */
-	public <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
-		RSet<T> rSet = redissonClient.getSet(key);
-		return rSet.addAll(dataSet);
-	}
-
-	/**
-	 * 获得缓存的set
-	 *
-	 * @param key
-	 * @return
-	 */
-	public <T> Set<T> getCacheSet(final String key) {
-		RSet<T> rSet = redissonClient.getSet(key);
-		return rSet.readAll();
-	}
-
-	/**
-	 * 缓存Map
-	 *
-	 * @param key
-	 * @param dataMap
-	 */
-	public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
-		if (dataMap != null) {
-			RMap<String, T> rMap = redissonClient.getMap(key);
-			rMap.putAll(dataMap);
-		}
-	}
-
-	/**
-	 * 获得缓存的Map
-	 *
-	 * @param key
-	 * @return
-	 */
-	public <T> Map<String, T> getCacheMap(final String key) {
-		RMap<String, T> rMap = redissonClient.getMap(key);
-		return rMap.getAll(rMap.keySet());
-	}
-
-	/**
-	 * 往Hash中存入数据
-	 *
-	 * @param key   Redis键
-	 * @param hKey  Hash键
-	 * @param value 值
-	 */
-	public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
-		RMap<String, T> rMap = redissonClient.getMap(key);
-		rMap.put(hKey, value);
-	}
-
-	/**
-	 * 获取Hash中的数据
-	 *
-	 * @param key  Redis键
-	 * @param hKey Hash键
-	 * @return Hash中的对象
-	 */
-	public <T> T getCacheMapValue(final String key, final String hKey) {
-		RMap<String, T> rMap = redissonClient.getMap(key);
-		return rMap.get(hKey);
-	}
-
-	/**
-	 * 获取多个Hash中的数据
-	 *
-	 * @param key   Redis键
-	 * @param hKeys Hash键集合
-	 * @return Hash对象集合
-	 */
-	public <K,V> Map<K,V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
-		RMap<K,V>  rMap = redissonClient.getMap(key);
-		return rMap.getAll(hKeys);
-	}
-
-	/**
-	 * 获得缓存的基本对象列表
-	 *
-	 * @param pattern 字符串前缀
-	 * @return 对象列表
-	 */
-	public Collection<String> keys(final String pattern) {
-		Iterable<String> iterable = redissonClient.getKeys().getKeysByPattern(pattern);
-		return Lists.newArrayList(iterable);
-	}
-}

+ 9 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/LogininforService.java

@@ -0,0 +1,9 @@
+package com.ruoyi.common.core.service;
+
+import javax.servlet.http.HttpServletRequest;
+
+public interface LogininforService {
+
+    void recordLogininfor(String username, String status, String message,
+                          HttpServletRequest request, Object... args);
+}

+ 9 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/OperLogService.java

@@ -0,0 +1,9 @@
+package com.ruoyi.common.core.service;
+
+import com.ruoyi.common.core.domain.dto.OperLogDTO;
+import org.springframework.scheduling.annotation.Async;
+
+public interface OperLogService {
+    @Async
+    void recordOper(OperLogDTO operLogDTO);
+}

+ 69 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/TokenService.java

@@ -0,0 +1,69 @@
+package com.ruoyi.common.core.service;
+
+import com.ruoyi.common.core.domain.model.LoginUser;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * token验证处理
+ *
+ * @author Lion Li
+ */
+public interface TokenService {
+
+    /**
+     * 获取用户身份信息
+     *
+     * @return 用户信息
+     */
+     LoginUser getLoginUser(HttpServletRequest request);
+
+    /**
+     * 设置用户身份信息
+     */
+    void setLoginUser(LoginUser loginUser);
+
+    /**
+     * 删除用户身份信息
+     */
+    void delLoginUser(String token);
+
+    /**
+     * 创建令牌
+     *
+     * @param loginUser 用户信息
+     * @return 令牌
+     */
+    String createToken(LoginUser loginUser);
+
+    /**
+     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
+     *
+     * @param loginUser
+     * @return 令牌
+     */
+    void verifyToken(LoginUser loginUser);
+
+    /**
+     * 刷新令牌有效期
+     *
+     * @param loginUser 登录信息
+     */
+    void refreshToken(LoginUser loginUser);
+
+    /**
+     * 设置用户代理信息
+     *
+     * @param loginUser 登录信息
+     */
+    void setUserAgent(LoginUser loginUser);
+
+    /**
+     * 从令牌中获取用户名
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    String getUsernameFromToken(String token);
+
+}

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

@@ -16,5 +16,10 @@ public enum LimitType
     /**
      * 根据请求者IP进行限流
      */
-    IP
+    IP,
+
+    /**
+     * 实例限流(集群多后端实例)
+     */
+    CLUSTER
 }

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

@@ -15,7 +15,7 @@ import java.util.concurrent.ThreadPoolExecutor;
 @AllArgsConstructor
 public enum ThreadPoolRejectedPolicy {
 
-    CALLER_RUNS_POLICY("等待", ThreadPoolExecutor.CallerRunsPolicy.class),
+    CALLER_RUNS_POLICY("调用方执行", ThreadPoolExecutor.CallerRunsPolicy.class),
     DISCARD_OLDEST_POLICY("放弃最旧的", ThreadPoolExecutor.DiscardOldestPolicy.class),
     DISCARD_POLICY("丢弃", ThreadPoolExecutor.DiscardPolicy.class),
     ABORT_POLICY("中止", ThreadPoolExecutor.AbortPolicy.class);

+ 1 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/TokenProperties.java → ruoyi-common/src/main/java/com/ruoyi/common/properties/TokenProperties.java

@@ -1,4 +1,4 @@
-package com.ruoyi.framework.config.properties;
+package com.ruoyi.common.properties;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;

+ 26 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/RedisUtils.java

@@ -26,6 +26,32 @@ public class RedisUtils {
     private static RedissonClient client = SpringUtils.getBean(RedissonClient.class);
 
     /**
+     * 限流
+     *
+     * @param key          限流key
+     * @param rateType     限流类型
+     * @param rate         速率
+     * @param rateInterval 速率间隔
+     * @return -1 表示失败
+     */
+    public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
+        RRateLimiter rateLimiter = client.getRateLimiter(key);
+        rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
+        if (rateLimiter.tryAcquire()) {
+            return rateLimiter.availablePermits();
+        } else {
+            return -1L;
+        }
+    }
+
+    /**
+     * 获取实例id
+     */
+    public static String getClientId() {
+        return client.getId();
+    }
+
+    /**
      * 发布通道消息
      *
      * @param channelKey 通道key

+ 1 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java

@@ -35,6 +35,7 @@ public class FileUtils extends FileUtil
                 .append(percentEncodedFileName);
 
         response.setHeader("Content-disposition", contentDispositionValue.toString());
+        response.setHeader("download-filename", percentEncodedFileName);
     }
 
     /**

+ 3 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java

@@ -27,6 +27,9 @@ public class AddressUtils {
 
 	public static String getRealAddressByIP(String ip) {
 		String address = UNKNOWN;
+		if (StringUtils.isBlank(ip)){
+			return address;
+		}
 		// 内网不查询
 		ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
 		if (NetUtil.isInnerIP(ip)) {

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

@@ -46,7 +46,7 @@ public class ExcelUtil {
 			response.reset();
 			response.addHeader("Access-Control-Allow-Origin", "*");
 			response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
-			FileUtils.setAttachmentResponseHeader(response, URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()));
+			FileUtils.setAttachmentResponseHeader(response, filename);
 			response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
 			ServletOutputStream os = response.getOutputStream();
 			EasyExcel.write(os, clazz)

+ 1 - 1
ruoyi-demo/pom.xml

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

+ 3 - 0
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java

@@ -34,6 +34,9 @@ public class RedisCacheController {
 	 * 如果没有,就调用方法,然后把结果缓存起来
 	 * 这个注解「一般用在查询方法上」
 	 *
+	 * 重点说明: 缓存注解严谨与其他筛选数据功能一起使用
+	 * 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题
+	 *
 	 * cacheNames 为配置文件内 groupId
 	 */
 	@ApiOperation("测试 @Cacheable")

+ 58 - 0
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisRateLimiterController.java

@@ -0,0 +1,58 @@
+package com.ruoyi.demo.controller;
+
+import com.ruoyi.common.annotation.RateLimiter;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.LimitType;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * 测试分布式限流样例
+ *
+ * @author Lion Li
+ */
+@Api(value = "测试分布式限流样例", tags = {"测试分布式限流样例"})
+@Slf4j
+@RestController
+@RequestMapping("/demo/rateLimiter")
+public class RedisRateLimiterController {
+
+	/**
+	 * 测试全局限流
+	 * 全局影响
+	 */
+	@ApiOperation("测试全局限流")
+	@RateLimiter(count = 2, time = 10)
+	@GetMapping("/test")
+	public  AjaxResult<String> test(String value){
+		return AjaxResult.success("操作成功",value);
+	}
+
+	/**
+	 * 测试请求IP限流
+	 * 同一IP请求受影响
+	 */
+	@ApiOperation("测试请求IP限流")
+	@RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
+	@GetMapping("/testip")
+	public  AjaxResult<String> testip(String value){
+		return AjaxResult.success("操作成功",value);
+	}
+
+	/**
+	 * 测试集群实例限流
+	 * 启动两个后端服务互不影响
+	 */
+	@ApiOperation("测试集群实例限流")
+	@RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
+	@GetMapping("/testcluster")
+	public  AjaxResult<String> testcluster(String value){
+		return AjaxResult.success("操作成功",value);
+	}
+
+}

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

@@ -96,7 +96,7 @@ public class TestDemoController extends BaseController {
     @ApiOperation("新增测试单表")
     @PreAuthorize("@ss.hasPermi('demo:demo:add')")
     @Log(title = "测试单表", businessType = BusinessType.INSERT)
-    @RepeatSubmit(intervalTime = 2, timeUnit = TimeUnit.SECONDS)
+    @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "不允许重复提交")
     @PostMapping()
     public AjaxResult<Void> add(@Validated(AddGroup.class) @RequestBody TestDemoBo bo) {
         return toAjax(iTestDemoService.insertByBo(bo) ? 1 : 0);

+ 5 - 1
ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/TestDemoServiceImpl.java

@@ -75,7 +75,11 @@ public class TestDemoServiceImpl extends ServicePlusImpl<TestDemoMapper, TestDem
 	public Boolean insertByBo(TestDemoBo bo) {
 		TestDemo add = BeanUtil.toBean(bo, TestDemo.class);
 		validEntityBeforeSave(add);
-		return save(add);
+		boolean flag = save(add);
+		if (flag) {
+			bo.setId(add.getId());
+		}
+		return flag;
 	}
 
 	@Override

+ 5 - 1
ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/TestTreeServiceImpl.java

@@ -54,7 +54,11 @@ public class TestTreeServiceImpl extends ServicePlusImpl<TestTreeMapper, TestTre
 	public Boolean insertByBo(TestTreeBo bo) {
 		TestTree add = BeanUtil.toBean(bo, TestTree.class);
 		validEntityBeforeSave(add);
-		return save(add);
+		boolean flag = save(add);
+		if (flag) {
+			bo.setId(add.getId());
+		}
+		return flag;
 	}
 
 	@Override

+ 1 - 1
ruoyi-extend/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.1.0</version>
+        <version>3.2.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>3.1.0</version>
+        <version>3.2.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>

+ 14 - 3
ruoyi-framework/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.1.0</version>
+        <version>3.2.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -61,12 +61,23 @@
             <artifactId>druid-spring-boot-starter</artifactId>
         </dependency>
 
+        <!-- dynamic-datasource 多数据源-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+        </dependency>
+        <!-- sql性能分析插件 -->
+        <dependency>
+            <groupId>p6spy</groupId>
+            <artifactId>p6spy</artifactId>
+        </dependency>
+
         <!-- 系统模块-->
         <dependency>
             <groupId>com.ruoyi</groupId>
-            <artifactId>ruoyi-system</artifactId>
+            <artifactId>ruoyi-common</artifactId>
         </dependency>
 
     </dependencies>
 
-</project>
+</project>

+ 4 - 32
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java

@@ -9,14 +9,10 @@ import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.reflect.ReflectUtils;
 import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
-import org.aspectj.lang.annotation.Pointcut;
-import org.aspectj.lang.reflect.MethodSignature;
 import org.springframework.stereotype.Component;
 
-import java.lang.reflect.Method;
 import java.util.Map;
 
 /**
@@ -58,23 +54,13 @@ public class DataScopeAspect {
 	 */
 	public static final String DATA_SCOPE = "dataScope";
 
-	// 配置织入点
-	@Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)")
-	public void dataScopePointCut() {
-	}
-
-	@Before("dataScopePointCut()")
-	public void doBefore(JoinPoint point) throws Throwable {
+	@Before("@annotation(controllerDataScope)")
+	public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable {
 		clearDataScope(point);
-		handleDataScope(point);
+		handleDataScope(point, controllerDataScope);
 	}
 
-	protected void handleDataScope(final JoinPoint joinPoint) {
-		// 获得注解
-		DataScope controllerDataScope = getAnnotationLog(joinPoint);
-		if (controllerDataScope == null) {
-			return;
-		}
+	protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
 		// 获取当前的用户
 		LoginUser loginUser = SecurityUtils.getLoginUser();
 		if (StringUtils.isNotNull(loginUser)) {
@@ -134,20 +120,6 @@ public class DataScopeAspect {
 	}
 
 	/**
-	 * 是否存在注解,如果存在就获取
-	 */
-	private DataScope getAnnotationLog(JoinPoint joinPoint) {
-		Signature signature = joinPoint.getSignature();
-		MethodSignature methodSignature = (MethodSignature) signature;
-		Method method = methodSignature.getMethod();
-
-		if (method != null) {
-			return method.getAnnotation(DataScope.class);
-		}
-		return null;
-	}
-
-	/**
 	 * 拼接权限sql前先清空params.dataScope参数防止注入
 	 */
 	private void clearDataScope(final JoinPoint joinPoint) {

+ 3 - 27
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java

@@ -6,33 +6,21 @@ import com.ruoyi.common.utils.StringUtils;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Pointcut;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
-import java.util.Objects;
-
 /**
  * 多数据源处理
  *
- * @author ruoyi
+ * @author Lion Li
  */
 @Aspect
 @Order(-500)
 @Component
 public class DataSourceAspect {
 
-	@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
-		+ "|| @within(com.ruoyi.common.annotation.DataSource)")
-	public void dsPointCut() {
-	}
-
-	@Around("dsPointCut()")
-	public Object around(ProceedingJoinPoint point) throws Throwable {
-		DataSource dataSource = getDataSource(point);
-
+	@Around("@annotation(dataSource) || @within(dataSource)")
+	public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
 		if (StringUtils.isNotNull(dataSource)) {
 			DynamicDataSourceContextHolder.poll();
 			String source = dataSource.value().getSource();
@@ -47,16 +35,4 @@ public class DataSourceAspect {
 		}
 	}
 
-	/**
-	 * 获取需要切换的数据源
-	 */
-	public DataSource getDataSource(ProceedingJoinPoint point) {
-		MethodSignature signature = (MethodSignature) point.getSignature();
-		DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
-		if (Objects.nonNull(dataSource)) {
-			return dataSource;
-		}
-
-		return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
-	}
 }

+ 53 - 104
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java

@@ -1,7 +1,9 @@
 package com.ruoyi.framework.aspectj;
 
 import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.domain.dto.OperLogDTO;
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.service.OperLogService;
 import com.ruoyi.common.enums.BusinessStatus;
 import com.ruoyi.common.enums.HttpMethod;
 import com.ruoyi.common.utils.JsonUtils;
@@ -9,17 +11,11 @@ import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.ServletUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.spring.SpringUtils;
-import com.ruoyi.framework.web.service.AsyncService;
-import com.ruoyi.system.domain.SysOperLog;
+import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.AfterThrowing;
 import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Pointcut;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 import org.springframework.validation.BindingResult;
 import org.springframework.web.multipart.MultipartFile;
@@ -27,81 +23,58 @@ import org.springframework.web.servlet.HandlerMapping;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.Map;
 
 /**
  * 操作日志记录处理
  *
- * @author ruoyi
+ * @author Lion Li
  */
+@Slf4j
 @Aspect
 @Component
-public class LogAspect
-{
-    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
-
-    // 配置织入点
-    @Pointcut("@annotation(com.ruoyi.common.annotation.Log)")
-    public void logPointCut()
-    {
-    }
+public class LogAspect {
 
     /**
      * 处理完请求后执行
      *
      * @param joinPoint 切点
      */
-    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
-    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
-    {
-        handleLog(joinPoint, null, jsonResult);
+    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
+        handleLog(joinPoint, controllerLog, null, jsonResult);
     }
 
     /**
      * 拦截异常操作
      *
      * @param joinPoint 切点
-     * @param e 异常
+     * @param e         异常
      */
-    @AfterThrowing(value = "logPointCut()", throwing = "e")
-    public void doAfterThrowing(JoinPoint joinPoint, Exception e)
-    {
-        handleLog(joinPoint, e, null);
+    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
+        handleLog(joinPoint, controllerLog, e, null);
     }
 
-    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
-    {
-        try
-        {
-            // 获得注解
-            Log controllerLog = getAnnotationLog(joinPoint);
-            if (controllerLog == null)
-            {
-                return;
-            }
+    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
+        try {
 
             // 获取当前的用户
             LoginUser loginUser = SecurityUtils.getLoginUser();
 
             // *========数据库日志=========*//
-            SysOperLog operLog = new SysOperLog();
+            OperLogDTO operLog = new OperLogDTO();
             operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
             // 请求的地址
             String ip = ServletUtils.getClientIP();
             operLog.setOperIp(ip);
-            // 返回参数
-            operLog.setJsonResult(JsonUtils.toJsonString(jsonResult));
-
             operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
-            if (loginUser != null)
-            {
+            if (loginUser != null) {
                 operLog.setOperName(loginUser.getUsername());
             }
 
-            if (e != null)
-            {
+            if (e != null) {
                 operLog.setStatus(BusinessStatus.FAIL.ordinal());
                 operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
             }
@@ -112,12 +85,10 @@ public class LogAspect
             // 设置请求方式
             operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
             // 处理设置注解上的参数
-            getControllerMethodDescription(joinPoint, controllerLog, operLog);
+            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
             // 保存数据库
-			SpringUtils.getBean(AsyncService.class).recordOper(operLog);
-        }
-        catch (Exception exp)
-        {
+            SpringUtils.getBean(OperLogService.class).recordOper(operLog);
+        } catch (Exception exp) {
             // 记录本地异常日志
             log.error("==前置通知异常==");
             log.error("异常信息:{}", exp.getMessage());
@@ -128,12 +99,11 @@ public class LogAspect
     /**
      * 获取注解中对方法的描述信息 用于Controller层注解
      *
-     * @param log 日志
+     * @param log     日志
      * @param operLog 操作日志
      * @throws Exception
      */
-    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception
-    {
+    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogDTO operLog, Object jsonResult) throws Exception {
         // 设置action动作
         operLog.setBusinessType(log.businessType().ordinal());
         // 设置标题
@@ -141,11 +111,14 @@ public class LogAspect
         // 设置操作人类别
         operLog.setOperatorType(log.operatorType().ordinal());
         // 是否需要保存request,参数和值
-        if (log.isSaveRequestData())
-        {
+        if (log.isSaveRequestData()) {
             // 获取参数的信息,传入到数据库中。
             setRequestValue(joinPoint, operLog);
         }
+        // 是否需要保存response,参数和值
+        if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
+            operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
+        }
     }
 
     /**
@@ -154,50 +127,32 @@ public class LogAspect
      * @param operLog 操作日志
      * @throws Exception 异常
      */
-    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
-    {
+    private void setRequestValue(JoinPoint joinPoint, OperLogDTO operLog) throws Exception {
         String requestMethod = operLog.getRequestMethod();
-        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
-        {
+        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
             String params = argsArrayToString(joinPoint.getArgs());
             operLog.setOperParam(StringUtils.substring(params, 0, 2000));
-        }
-        else
-        {
+        } else {
             Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
             operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
         }
     }
 
     /**
-     * 是否存在注解,如果存在就获取
-     */
-    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
-    {
-        Signature signature = joinPoint.getSignature();
-        MethodSignature methodSignature = (MethodSignature) signature;
-        Method method = methodSignature.getMethod();
-
-        if (method != null)
-        {
-            return method.getAnnotation(Log.class);
-        }
-        return null;
-    }
-
-    /**
      * 参数拼装
      */
-    private String argsArrayToString(Object[] paramsArray)
-    {
+    private String argsArrayToString(Object[] paramsArray) {
         StringBuilder params = new StringBuilder();
-        if (paramsArray != null && paramsArray.length > 0)
-        {
-			for (Object o : paramsArray) {
-				if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
-					params.append(JsonUtils.toJsonString(o)).append(" ");
-				}
-			}
+        if (paramsArray != null && paramsArray.length > 0) {
+            for (Object o : paramsArray) {
+                if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
+                    try {
+                        params.append(JsonUtils.toJsonString(o)).append(" ");
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
         }
         return params.toString().trim();
     }
@@ -209,27 +164,21 @@ public class LogAspect
      * @return 如果是需要过滤的对象,则返回true;否则返回false。
      */
     @SuppressWarnings("rawtypes")
-    public boolean isFilterObject(final Object o)
-    {
+    public boolean isFilterObject(final Object o) {
         Class<?> clazz = o.getClass();
-        if (clazz.isArray())
-        {
+        if (clazz.isArray()) {
             return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
-        }
-        else if (Collection.class.isAssignableFrom(clazz))
-        {
+        } else if (Collection.class.isAssignableFrom(clazz)) {
             Collection collection = (Collection) o;
-			for (Object value : collection) {
-				return value instanceof MultipartFile;
-			}
-        }
-        else if (Map.class.isAssignableFrom(clazz))
-        {
+            for (Object value : collection) {
+                return value instanceof MultipartFile;
+            }
+        } else if (Map.class.isAssignableFrom(clazz)) {
             Map map = (Map) o;
-			for (Object value : map.entrySet()) {
-				Map.Entry entry = (Map.Entry) value;
-				return entry.getValue() instanceof MultipartFile;
-			}
+            for (Object value : map.entrySet()) {
+                Map.Entry entry = (Map.Entry) value;
+                return entry.getValue() instanceof MultipartFile;
+            }
         }
         return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                 || o instanceof BindingResult;

+ 27 - 78
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java

@@ -3,114 +3,63 @@ 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.RedisUtils;
 import com.ruoyi.common.utils.ServletUtils;
-import com.ruoyi.common.utils.StringUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
-import org.aspectj.lang.annotation.Pointcut;
 import org.aspectj.lang.reflect.MethodSignature;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.script.RedisScript;
+import org.redisson.api.RateType;
 import org.springframework.stereotype.Component;
 
 import java.lang.reflect.Method;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * 限流处理
  *
- * @author ruoyi
+ * @author Lion Li
  */
+@Slf4j
 @Aspect
 @Component
-public class RateLimiterAspect
-{
-    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
+public class RateLimiterAspect {
 
-    private RedisTemplate<Object, Object> redisTemplate;
-
-    private RedisScript<Long> limitScript;
-
-    @Autowired
-    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
-    {
-        this.redisTemplate = redisTemplate;
-    }
-
-    @Autowired
-    public void setLimitScript(RedisScript<Long> limitScript)
-    {
-        this.limitScript = limitScript;
-    }
-
-    // 配置织入点
-    @Pointcut("@annotation(com.ruoyi.common.annotation.RateLimiter)")
-    public void rateLimiterPointCut()
-    {
-    }
-
-    @Before("rateLimiterPointCut()")
-    public void doBefore(JoinPoint point) throws Throwable
-    {
-        RateLimiter rateLimiter = getAnnotationRateLimiter(point);
-        String key = rateLimiter.key();
+    @Before("@annotation(rateLimiter)")
+    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
         int time = rateLimiter.time();
         int count = rateLimiter.count();
-
         String combineKey = getCombineKey(rateLimiter, point);
-        List<Object> keys = Collections.singletonList(combineKey);
-        try
-        {
-            Long number = redisTemplate.execute(limitScript, keys, count, time);
-            if (StringUtils.isNull(number) || number.intValue() > count)
-            {
+        try {
+            RateType rateType = RateType.OVERALL;
+            if (rateLimiter.limitType() == LimitType.CLUSTER) {
+                rateType = RateType.PER_CLIENT;
+            }
+            long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
+            if (number == -1) {
                 throw new ServiceException("访问过于频繁,请稍后再试");
             }
-            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
-        }
-        catch (ServiceException e)
-        {
+            log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
+        } catch (ServiceException e) {
             throw e;
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             throw new RuntimeException("服务器限流异常,请稍后再试");
         }
     }
 
-    /**
-     * 是否存在注解,如果存在就获取
-     */
-    private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint)
-    {
-        Signature signature = joinPoint.getSignature();
-        MethodSignature methodSignature = (MethodSignature) signature;
-        Method method = methodSignature.getMethod();
-
-        if (method != null)
-        {
-            return method.getAnnotation(RateLimiter.class);
-        }
-        return null;
-    }
-
-    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
-    {
-        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
-        if (rateLimiter.limitType() == LimitType.IP)
-        {
-            stringBuffer.append(ServletUtils.getClientIP());
+    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
+        StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
+        if (rateLimiter.limitType() == LimitType.IP) {
+            // 获取请求ip
+            stringBuffer.append(ServletUtils.getClientIP()).append("-");
+        } else if (rateLimiter.limitType() == LimitType.CLUSTER){
+            // 获取客户端实例id
+            stringBuffer.append(RedisUtils.getClientId()).append("-");
         }
         MethodSignature signature = (MethodSignature) point.getSignature();
         Method method = signature.getMethod();
         Class<?> targetClass = method.getDeclaringClass();
-        stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName());
+        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
         return stringBuffer.toString();
     }
 }

+ 69 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RepeatSubmitAspect.java

@@ -0,0 +1,69 @@
+package com.ruoyi.framework.aspectj;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import com.baomidou.lock.LockInfo;
+import com.baomidou.lock.LockTemplate;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.properties.TokenProperties;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+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.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 防止重复提交
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Aspect
+@Component
+public class RepeatSubmitAspect {
+
+    private final TokenProperties tokenProperties;
+    private final RepeatSubmitProperties repeatSubmitProperties;
+    private final LockTemplate lockTemplate;
+
+    @Before("@annotation(repeatSubmit)")
+    public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
+        // 如果注解不为0 则使用注解数值
+        long interval = repeatSubmitProperties.getInterval();
+        if (repeatSubmit.interval() > 0) {
+            interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
+        }
+        if (interval < 1000) {
+            throw new ServiceException("重复提交间隔时间不能小于'1'秒");
+        }
+        HttpServletRequest request = ServletUtils.getRequest();
+        String nowParams = StrUtil.join(",", point.getArgs());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = request.getHeader(tokenProperties.getHeader());
+        if (StringUtils.isEmpty(submitKey)) {
+            submitKey = url;
+        }
+        submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
+        // 唯一标识(指定key + 消息头)
+        String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
+        LockInfo lock = lockTemplate.lock(cacheRepeatKey, interval, interval / 2);
+        if (lock == null) {
+            throw new ServiceException(repeatSubmit.message());
+        }
+    }
+
+}

+ 8 - 4
ruoyi-framework/src/main/java/com/ruoyi/framework/config/AsyncConfig.java

@@ -1,5 +1,6 @@
 package com.ruoyi.framework.config;
 
+import cn.hutool.core.util.ArrayUtil;
 import com.ruoyi.common.exception.ServiceException;
 import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,10 +42,13 @@ public class AsyncConfig extends AsyncConfigurerSupport {
     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
         return (throwable, method, objects) -> {
             throwable.printStackTrace();
-            throw new ServiceException(
-                    "Exception message - " + throwable.getMessage()
-                    + ", Method name - " + method.getName()
-                    + ", Parameter value - " + Arrays.toString(objects));
+            StringBuilder sb = new StringBuilder();
+            sb.append("Exception message - ").append(throwable.getMessage())
+                    .append(", Method name - ").append(method.getName());
+            if (ArrayUtil.isNotEmpty(objects)) {
+                sb.append(", Parameter value - ").append(Arrays.toString(objects));
+            }
+            throw new ServiceException(sb.toString());
         };
     }
 

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

@@ -1,6 +1,6 @@
 package com.ruoyi.framework.config;
 
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.ObjectUtil;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.framework.config.properties.RedissonProperties;
 import lombok.extern.slf4j.Slf4j;
@@ -18,7 +18,6 @@ import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.core.script.DefaultRedisScript;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -58,23 +57,52 @@ public class RedisConfig extends CachingConfigurerSupport {
 			.setTransportMode(redissonProperties.getTransportMode());
 
 		RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
-		// 使用单机模式
-		config.useSingleServer()
-			.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
-			.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
-			.setDatabase(redisProperties.getDatabase())
-			.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
-			.setTimeout(singleServerConfig.getTimeout())
-			.setRetryAttempts(singleServerConfig.getRetryAttempts())
-			.setRetryInterval(singleServerConfig.getRetryInterval())
-			.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
-			.setClientName(singleServerConfig.getClientName())
-			.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
-			.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
-			.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
-			.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
-			.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
-			.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
+		if (ObjectUtil.isNotNull(singleServerConfig)) {
+			// 使用单机模式
+			config.useSingleServer()
+					.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
+					.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
+					.setDatabase(redisProperties.getDatabase())
+					.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
+					.setTimeout(singleServerConfig.getTimeout())
+					.setRetryAttempts(singleServerConfig.getRetryAttempts())
+					.setRetryInterval(singleServerConfig.getRetryInterval())
+					.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
+					.setClientName(singleServerConfig.getClientName())
+					.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
+					.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
+					.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
+					.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
+					.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
+					.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
+		}
+
+		RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
+		if (ObjectUtil.isNotNull(clusterServersConfig)) {
+			// 使用集群模式
+			config.useClusterServers()
+					.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
+					.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
+					.setTimeout(clusterServersConfig.getTimeout())
+					.setRetryAttempts(clusterServersConfig.getRetryAttempts())
+					.setRetryInterval(clusterServersConfig.getRetryInterval())
+					.setSubscriptionsPerConnection(clusterServersConfig.getSubscriptionsPerConnection())
+					.setClientName(clusterServersConfig.getClientName())
+					.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
+					.setPingConnectionInterval(clusterServersConfig.getPingConnectionInterval())
+					.setSubscriptionConnectionMinimumIdleSize(clusterServersConfig.getSubscriptionConnectionMinimumIdleSize())
+					.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
+					.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
+					.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
+					.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
+					.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
+					.setDnsMonitoringInterval(clusterServersConfig.getDnsMonitoringInterval())
+					.setFailedSlaveReconnectionInterval(clusterServersConfig.getFailedSlaveReconnectionInterval())
+					.setScanInterval(clusterServersConfig.getScanInterval())
+					.setReadMode(clusterServersConfig.getReadMode())
+					.setSubscriptionMode(clusterServersConfig.getSubscriptionMode())
+					.setNodeAddresses(redisProperties.getCluster().getNodes());
+		}
 		RedissonClient redissonClient = Redisson.create(config);
 		log.info("初始化 redis 配置");
 		return redissonClient;
@@ -95,32 +123,4 @@ public class RedisConfig extends CachingConfigurerSupport {
 		return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
 	}
 
-	@Bean
-	public DefaultRedisScript<Long> limitScript() {
-		DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
-		redisScript.setScriptText(limitScriptText());
-		redisScript.setResultType(Long.class);
-		return redisScript;
-	}
-
-	/**
-	 * 限流脚本
-	 */
-	private String limitScriptText() {
-		return StrUtil.builder()
-			.append("local key = KEYS[1]\n")
-			.append("local count = tonumber(ARGV[1])\n")
-			.append("local time = tonumber(ARGV[2])\n")
-			.append("local current = redis.call('get', key);\n")
-			.append("if current and tonumber(current) > count then\n")
-			.append("    return current;\n")
-			.append("end\n")
-			.append("current = redis.call('incr', key)\n")
-			.append("if tonumber(current) == 1 then\n")
-			.append("    redis.call('expire', key, time)\n")
-			.append("end\n")
-			.append("return current;")
-			.toString();
-	}
-
 }

+ 5 - 22
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java

@@ -1,52 +1,35 @@
 package com.ruoyi.framework.config;
 
-import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.filter.CorsFilter;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 /**
  * 通用配置
  *
- * @author ruoyi
+ * @author Lion Li
  */
 @Configuration
-public class ResourcesConfig implements WebMvcConfigurer
-{
-    @Autowired
-    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+public class ResourcesConfig implements WebMvcConfigurer {
 
     @Override
-    public void addResourceHandlers(ResourceHandlerRegistry registry)
-    {
-    }
-
-    /**
-     * 自定义拦截规则
-     */
-    @Override
-    public void addInterceptors(InterceptorRegistry registry)
-    {
-        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
     }
 
     /**
      * 跨域配置
      */
     @Bean
-    public CorsFilter corsFilter()
-    {
+    public CorsFilter corsFilter() {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         CorsConfiguration config = new CorsConfiguration();
         config.setAllowCredentials(true);
         // 设置访问源地址
-		config.addAllowedOriginPattern("*");
+        config.addAllowedOriginPattern("*");
         // 设置访问源请求头
         config.addAllowedHeader("*");
         // 设置访问源请求方法

+ 10 - 14
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

@@ -1,5 +1,6 @@
 package com.ruoyi.framework.config;
 
+import com.ruoyi.framework.config.properties.SecurityProperties;
 import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
 import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
 import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
@@ -20,7 +21,7 @@ import org.springframework.web.filter.CorsFilter;
 
 /**
  * spring security配置
- *
+ * 
  * @author ruoyi
  */
 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@@ -31,7 +32,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
      */
     @Autowired
     private UserDetailsService userDetailsService;
-
+    
     /**
      * 认证失败处理类
      */
@@ -49,13 +50,16 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
      */
     @Autowired
     private JwtAuthenticationTokenFilter authenticationTokenFilter;
-
+    
     /**
      * 跨域过滤器
      */
     @Autowired
     private CorsFilter corsFilter;
 
+    @Autowired
+    private SecurityProperties securityProperties;
+
     /**
      * 解决 无法直接注入 AuthenticationManager
      *
@@ -96,8 +100,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                 // 过滤请求
                 .authorizeRequests()
-                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/login", "/register", "/captchaImage").anonymous()
                 .antMatchers(
                         HttpMethod.GET,
                         "/",
@@ -106,19 +108,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                         "/**/*.css",
                         "/**/*.js"
                 ).permitAll()
-                .antMatchers("/doc.html").anonymous()
-                .antMatchers("/swagger-resources/**").anonymous()
-                .antMatchers("/webjars/**").anonymous()
-                .antMatchers("/*/api-docs").anonymous()
-                .antMatchers("/druid/**").anonymous()
-                // Spring Boot Actuator 的安全配置
-                .antMatchers("/actuator").anonymous()
-                .antMatchers("/actuator/**").anonymous()
+                .antMatchers(securityProperties.getAnonymous()).anonymous()
+                .antMatchers(securityProperties.getPermitAll()).permitAll()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()
                 .headers().frameOptions().disable();
-        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
+        httpSecurity.logout().logoutUrl(securityProperties.getLogoutUrl()).logoutSuccessHandler(logoutSuccessHandler);
         // 添加JWT filter
         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
         // 添加CORS filter

+ 0 - 32
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java

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

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

@@ -1,11 +1,12 @@
 package com.ruoyi.framework.config;
 
 import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+import com.ruoyi.common.properties.TokenProperties;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
 import com.ruoyi.framework.config.properties.SwaggerProperties;
-import io.swagger.annotations.ApiOperation;
 import io.swagger.models.auth.In;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import springfox.documentation.builders.ApiInfoBuilder;
 import springfox.documentation.builders.PathSelectors;
@@ -15,6 +16,7 @@ import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spi.service.contexts.SecurityContext;
 import springfox.documentation.spring.web.plugins.Docket;
 
+import javax.annotation.PostConstruct;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -27,82 +29,92 @@ import java.util.List;
 @EnableKnife4j
 public class SwaggerConfig {
 
-	@Autowired
-	private SwaggerProperties swaggerProperties;
+    @Autowired
+    private SwaggerProperties swaggerProperties;
 
-	/**
-	 * 创建API
-	 */
-	@Bean
-	public Docket createRestApi() {
-		return new Docket(DocumentationType.OAS_30)
-			.enable(swaggerProperties.getEnabled())
-			// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
-			.apiInfo(apiInfo())
-			// 设置哪些接口暴露给Swagger展示
-			.select()
-			// 扫描所有有注解的api,用这种方式更灵活
-			.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
-			// 扫描指定包中的swagger注解
-			// .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
-			// 扫描所有 .apis(RequestHandlerSelectors.any())
-			.paths(PathSelectors.any())
-			.build()
-			/* 设置安全模式,swagger可以设置访问token */
-			.securitySchemes(securitySchemes())
-			.securityContexts(securityContexts())
-			.pathMapping(swaggerProperties.getPathMapping());
-	}
+    @Autowired
+	private TokenProperties tokenProperties;
 
-	/**
-	 * 安全模式,这里指定token通过Authorization头请求头传递
-	 */
-	private List<SecurityScheme> securitySchemes() {
-		List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
-		apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
-		return apiKeyList;
-	}
+    /**
+     * 创建API
+     */
+    @PostConstruct
+    public void createRestApi() {
+		for (SwaggerProperties.Groups group : swaggerProperties.getGroups()) {
+			String basePackage = group.getBasePackage();
+			Docket docket = new Docket(DocumentationType.OAS_30)
+					.enable(swaggerProperties.getEnabled())
+					// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+					.apiInfo(apiInfo())
+					// 设置哪些接口暴露给Swagger展示
+					.select()
+					// 扫描所有有注解的api,用这种方式更灵活
+					//.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+					// 扫描指定包中的swagger注解
+					.apis(RequestHandlerSelectors.basePackage(basePackage))
+					// 扫描所有 .apis(RequestHandlerSelectors.any())
+					.paths(PathSelectors.any())
+					.build()
+					.groupName(group.getName())
+					// 设置安全模式,swagger可以设置访问token
+					.securitySchemes(securitySchemes())
+					.securityContexts(securityContexts())
+					.pathMapping(swaggerProperties.getPathMapping());
+			String beanName = StringUtils.substringAfterLast(basePackage, ".") + "Docket";
+			SpringUtils.registerBean(beanName, docket);
+		}
+    }
 
-	/**
-	 * 安全上下文
-	 */
-	private List<SecurityContext> securityContexts() {
-		List<SecurityContext> securityContexts = new ArrayList<>();
-		securityContexts.add(
-			SecurityContext.builder()
-				.securityReferences(defaultAuth())
-				.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
-				.build());
-		return securityContexts;
-	}
+    /**
+     * 安全模式,这里指定token通过Authorization头请求头传递
+     */
+    private List<SecurityScheme> securitySchemes() {
+        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
+		String header = tokenProperties.getHeader();
+		apiKeyList.add(new ApiKey(header, header, In.HEADER.toValue()));
+        return apiKeyList;
+    }
 
-	/**
-	 * 默认的安全上引用
-	 */
-	private List<SecurityReference> defaultAuth() {
-		AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
-		AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
-		authorizationScopes[0] = authorizationScope;
-		List<SecurityReference> securityReferences = new ArrayList<>();
-		securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
-		return securityReferences;
-	}
+    /**
+     * 安全上下文
+     */
+    private List<SecurityContext> securityContexts() {
+        List<SecurityContext> securityContexts = new ArrayList<>();
+        securityContexts.add(
+                SecurityContext.builder()
+                        .securityReferences(defaultAuth())
+                        .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
+                        .build());
+        return securityContexts;
+    }
 
-	/**
-	 * 添加摘要信息
-	 */
-	private ApiInfo apiInfo() {
-		// 用ApiInfoBuilder进行定制
-		SwaggerProperties.Contact contact = swaggerProperties.getContact();
-		return new ApiInfoBuilder()
-			// 设置标题
-			.title(swaggerProperties.getTitle())
-			// 描述
-			.description(swaggerProperties.getDescription())
-			// 作者信息
-			.contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail()))
-			// 版本
-			.version(swaggerProperties.getVersion())
-			.build();
-	}
+    /**
+     * 默认的安全上引用
+     */
+    private List<SecurityReference> defaultAuth() {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        List<SecurityReference> securityReferences = new ArrayList<>();
+        securityReferences.add(new SecurityReference(tokenProperties.getHeader(), authorizationScopes));
+        return securityReferences;
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo() {
+        // 用ApiInfoBuilder进行定制
+        SwaggerProperties.Contact contact = swaggerProperties.getContact();
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title(swaggerProperties.getTitle())
+                // 描述
+                .description(swaggerProperties.getDescription())
+                // 作者信息
+                .contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail()))
+                // 版本
+                .version(swaggerProperties.getVersion())
+                .build();
+    }
 }

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

@@ -2,6 +2,8 @@ package com.ruoyi.framework.config.properties;
 
 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;
@@ -39,6 +41,11 @@ public class RedissonProperties {
 	private SingleServerConfig singleServerConfig;
 
 	/**
+	 * 集群服务配置
+	 */
+	private ClusterServersConfig clusterServersConfig;
+
+	/**
 	 * 缓存组
 	 */
 	private List<CacheGroup> cacheGroup;
@@ -106,6 +113,102 @@ public class RedissonProperties {
 
 	@Data
 	@NoArgsConstructor
+	public static class ClusterServersConfig {
+
+		/**
+		 * 客户端名称
+		 */
+		private String clientName;
+
+		/**
+		 * master最小空闲连接数
+		 */
+		private int masterConnectionMinimumIdleSize;
+
+		/**
+		 * master连接池大小
+		 */
+		private int masterConnectionPoolSize;
+
+		/**
+		 * slave最小空闲连接数
+		 */
+		private int slaveConnectionMinimumIdleSize;
+
+		/**
+		 * slave连接池大小
+		 */
+		private int slaveConnectionPoolSize;
+
+		/**
+		 * 连接空闲超时,单位:毫秒
+		 */
+		private int idleConnectionTimeout;
+
+		/**
+		 * ping超时
+		 */
+		private int pingConnectionInterval;
+
+		/**
+		 * 命令等待超时,单位:毫秒
+		 */
+		private int timeout;
+
+		/**
+		 * 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
+		 */
+		private int retryAttempts;
+
+		/**
+		 * 命令重试发送时间间隔,单位:毫秒
+		 */
+		private int retryInterval;
+
+		/**
+		 * 错误重试次数
+		 */
+		private int failedSlaveReconnectionInterval;
+
+		/**
+		 * 发布和订阅连接池最小空闲连接数
+		 */
+		private int subscriptionConnectionMinimumIdleSize;
+
+		/**
+		 * 发布和订阅连接池大小
+		 */
+		private int subscriptionConnectionPoolSize;
+
+		/**
+		 * 单个连接最大订阅数量
+		 */
+		private int subscriptionsPerConnection;
+
+		/**
+		 * 扫描间隔
+		 */
+		private int scanInterval;
+
+		/**
+		 * DNS监测时间间隔,单位:毫秒
+		 */
+		private int dnsMonitoringInterval;
+
+		/**
+		 * 读取模式
+		 */
+		private ReadMode readMode;
+
+		/**
+		 * 订阅模式
+		 */
+		private SubscriptionMode subscriptionMode;
+
+	}
+
+	@Data
+	@NoArgsConstructor
 	public static class CacheGroup {
 
 		/**

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

@@ -17,6 +17,6 @@ public class RepeatSubmitProperties {
     /**
      * 间隔时间(毫秒)
      */
-    private int intervalTime;
+    private int interval;
 
 }

+ 32 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SecurityProperties.java

@@ -0,0 +1,32 @@
+package com.ruoyi.framework.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * Security 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "security")
+public class SecurityProperties {
+
+    /**
+     * 退出登录url
+     */
+    private String logoutUrl;
+
+    /**
+     * 匿名放行路径
+     */
+    private String[] anonymous;
+
+    /**
+     * 用户任意访问放行路径
+     */
+    private String[] permitAll;
+
+}

+ 29 - 4
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SwaggerProperties.java

@@ -5,6 +5,8 @@ import lombok.NoArgsConstructor;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
+import java.util.List;
+
 /**
  * swagger 配置属性
  *
@@ -41,23 +43,46 @@ public class SwaggerProperties {
 	 */
     private Contact contact;
 
+	/**
+	 * 组配置
+	 */
+	private List<Groups> groups;
+
     @Data
 	@NoArgsConstructor
-	public static class Contact{
+	public static class Contact {
 
 		/**
 		 * 联系人
-		 **/
+		 */
 		private String name;
+
 		/**
 		 * 联系人url
-		 **/
+		 */
 		private String url;
+
 		/**
 		 * 联系人email
-		 **/
+		 */
 		private String email;
 
 	}
 
+	@Data
+	@NoArgsConstructor
+	public static class Groups {
+
+		/**
+		 * 组名
+		 */
+		private String name;
+
+		/**
+		 * 基础包路径
+		 */
+		private String basePackage;
+
+	}
+
 }

+ 0 - 50
ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java

@@ -1,50 +0,0 @@
-package com.ruoyi.framework.interceptor;
-
-import com.ruoyi.common.annotation.RepeatSubmit;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.utils.JsonUtils;
-import com.ruoyi.common.utils.ServletUtils;
-import org.springframework.stereotype.Component;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.HandlerInterceptor;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.lang.reflect.Method;
-
-/**
- * 防止重复提交拦截器
- *
- * 移除继承 HandlerInterceptorAdapter 过期类
- * 改为实现 HandlerInterceptor 接口(官方推荐写法)
- *
- * @author Lion Li
- */
-@Component
-public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
-
-	@Override
-	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
-		throws Exception {
-		if (handler instanceof HandlerMethod) {
-			HandlerMethod handlerMethod = (HandlerMethod) handler;
-			Method method = handlerMethod.getMethod();
-			RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
-			if (annotation != null) {
-				if (this.isRepeatSubmit(annotation, request)) {
-					AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
-					ServletUtils.renderString(response, JsonUtils.toJsonString(ajaxResult));
-					return false;
-				}
-			}
-			return true;
-		} else {
-			return HandlerInterceptor.super.preHandle(request, response, handler);
-		}
-	}
-
-	/**
-	 * 验证是否重复提交由子类实现具体的防重复提交的规则
-	 */
-	public abstract boolean isRepeatSubmit(RepeatSubmit annotation, HttpServletRequest request);
-}

+ 0 - 114
ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java

@@ -1,114 +0,0 @@
-package com.ruoyi.framework.interceptor.impl;
-
-import cn.hutool.core.convert.Convert;
-import cn.hutool.core.io.IoUtil;
-import com.ruoyi.common.annotation.RepeatSubmit;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
-import com.ruoyi.common.utils.JsonUtils;
-import com.ruoyi.common.utils.RedisUtils;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.config.properties.RepeatSubmitProperties;
-import com.ruoyi.framework.config.properties.TokenProperties;
-import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 判断请求url和数据是否和上一次相同,
- * 如果和上次相同,则是重复提交表单。
- *
- * @author Lion Li
- */
-@Slf4j
-@RequiredArgsConstructor(onConstructor_ = @Autowired)
-@Component
-public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
-	public final String REPEAT_PARAMS = "repeatParams";
-
-	public final String REPEAT_TIME = "repeatTime";
-
-	private final TokenProperties tokenProperties;
-	private final RepeatSubmitProperties repeatSubmitProperties;
-
-
-	@SuppressWarnings("unchecked")
-	@Override
-	public boolean isRepeatSubmit(RepeatSubmit repeatSubmit, HttpServletRequest request) {
-		// 如果注解不为0 则使用注解数值
-		long intervalTime = repeatSubmitProperties.getIntervalTime();
-		if (repeatSubmit.intervalTime() > 0) {
-			intervalTime = repeatSubmit.timeUnit().toMillis(repeatSubmit.intervalTime());
-		}
-		String nowParams = "";
-		if (request instanceof RepeatedlyRequestWrapper) {
-			RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
-			try {
-				nowParams = IoUtil.readUtf8(repeatedlyRequest.getInputStream());
-			} catch (IOException e) {
-				log.warn("读取流出现问题!");
-			}
-		}
-
-		// body参数为空,获取Parameter的数据
-		if (StringUtils.isEmpty(nowParams)) {
-			nowParams = JsonUtils.toJsonString(request.getParameterMap());
-		}
-		Map<String, Object> nowDataMap = new HashMap<String, Object>();
-		nowDataMap.put(REPEAT_PARAMS, nowParams);
-		nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
-
-		// 请求地址(作为存放cache的key值)
-		String url = request.getRequestURI();
-
-		// 唯一值(没有消息头则使用请求地址)
-		String submitKey = request.getHeader(tokenProperties.getHeader());
-		if (StringUtils.isEmpty(submitKey)) {
-			submitKey = url;
-		}
-
-		// 唯一标识(指定key + 消息头)
-		String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
-
-		Object sessionObj = RedisUtils.getCacheObject(cacheRepeatKey);
-		if (sessionObj != null) {
-			Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
-			if (sessionMap.containsKey(url)) {
-				Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
-				if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, intervalTime)) {
-					return true;
-				}
-			}
-		}
-		Map<String, Object> cacheMap = new HashMap<String, Object>();
-		cacheMap.put(url, nowDataMap);
-		RedisUtils.setCacheObject(cacheRepeatKey, cacheMap, Convert.toInt(intervalTime), TimeUnit.MILLISECONDS);
-		return false;
-	}
-
-	/**
-	 * 判断参数是否相同
-	 */
-	private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
-		String nowParams = (String) nowMap.get(REPEAT_PARAMS);
-		String preParams = (String) preMap.get(REPEAT_PARAMS);
-		return nowParams.equals(preParams);
-	}
-
-	/**
-	 * 判断两次间隔时间
-	 */
-	private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, long intervalTime) {
-		long time1 = (Long) nowMap.get(REPEAT_TIME);
-		long time2 = (Long) preMap.get(REPEAT_TIME);
-		return (time1 - time2) < intervalTime;
-	}
-}

+ 1 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java

@@ -57,7 +57,7 @@ public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
 		try {
 			loginUser = SecurityUtils.getLoginUser();
 		} catch (Exception e) {
-			log.error("自动注入警告 => 用户未登录");
+			log.warn("自动注入警告 => 用户未登录");
 			return null;
 		}
 		return loginUser.getUsername();

+ 2 - 3
ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java

@@ -1,9 +1,9 @@
 package com.ruoyi.framework.security.filter;
 
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.service.TokenService;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.web.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -33,8 +33,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
             throws ServletException, IOException
     {
         LoginUser loginUser = tokenService.getLoginUser(request);
-        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
-        {
+        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
             tokenService.verifyToken(loginUser);
             UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
             authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

+ 3 - 3
ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java

@@ -4,11 +4,11 @@ import cn.hutool.http.HttpStatus;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.service.LogininforService;
+import com.ruoyi.common.core.service.TokenService;
 import com.ruoyi.common.utils.JsonUtils;
 import com.ruoyi.common.utils.ServletUtils;
 import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.web.service.AsyncService;
-import com.ruoyi.framework.web.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.core.Authentication;
@@ -31,7 +31,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
 	private TokenService tokenService;
 
 	@Autowired
-	private AsyncService asyncService;
+	private LogininforService asyncService;
 
 	/**
 	 * 退出处理

+ 0 - 97
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/AsyncService.java

@@ -1,97 +0,0 @@
-package com.ruoyi.framework.web.service;
-
-import com.ruoyi.common.utils.StringUtils;
-import cn.hutool.http.useragent.UserAgent;
-import cn.hutool.http.useragent.UserAgentUtil;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.common.utils.ServletUtils;
-import com.ruoyi.common.utils.ip.AddressUtils;
-import com.ruoyi.system.domain.SysLogininfor;
-import com.ruoyi.system.domain.SysOperLog;
-import com.ruoyi.system.service.ISysLogininforService;
-import com.ruoyi.system.service.ISysOperLogService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * 异步工厂(产生任务用)
- *
- * @author Lion Li
- */
-@Slf4j(topic = "sys-user")
-@Async
-@Component
-public class AsyncService {
-
-	@Autowired
-	private ISysLogininforService iSysLogininforService;
-
-	@Autowired
-	private ISysOperLogService iSysOperLogService;
-
-	/**
-	 * 记录登录信息
-	 *
-	 * @param username 用户名
-	 * @param status   状态
-	 * @param message  消息
-	 * @param args     列表
-	 */
-	public void recordLogininfor(final String username, final String status, final String message,
-								 HttpServletRequest request, final Object... args) {
-		final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
-		final String ip = ServletUtils.getClientIP(request);
-
-		String address = AddressUtils.getRealAddressByIP(ip);
-		StringBuilder s = new StringBuilder();
-		s.append(getBlock(ip));
-		s.append(address);
-		s.append(getBlock(username));
-		s.append(getBlock(status));
-		s.append(getBlock(message));
-		// 打印信息到日志
-		log.info(s.toString(), args);
-		// 获取客户端操作系统
-		String os = userAgent.getOs().getName();
-		// 获取客户端浏览器
-		String browser = userAgent.getBrowser().getName();
-		// 封装对象
-		SysLogininfor logininfor = new SysLogininfor();
-		logininfor.setUserName(username);
-		logininfor.setIpaddr(ip);
-		logininfor.setLoginLocation(address);
-		logininfor.setBrowser(browser);
-		logininfor.setOs(os);
-		logininfor.setMsg(message);
-		// 日志状态
-		if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
-			logininfor.setStatus(Constants.SUCCESS);
-		} else if (Constants.LOGIN_FAIL.equals(status)) {
-			logininfor.setStatus(Constants.FAIL);
-		}
-		// 插入数据
-		iSysLogininforService.insertLogininfor(logininfor);
-	}
-
-	/**
-	 * 操作日志记录
-	 *
-	 * @param operLog 操作日志信息
-	 */
-	public void recordOper(final SysOperLog operLog) {
-		// 远程查询操作地点
-		operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
-		iSysOperLogService.insertOperlog(operLog);
-	}
-
-	private String getBlock(Object msg) {
-		if (msg == null) {
-			msg = "";
-		}
-		return "[" + msg.toString() + "]";
-	}
-}

+ 1 - 1
ruoyi-generator/pom.xml

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

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

@@ -33,8 +33,7 @@ public class GenUtils
     /**
      * 初始化列属性字段
      */
-    public static void initColumnField(GenTableColumn column, GenTable table)
-    {
+    public static void initColumnField(GenTableColumn column, GenTable table) {
         String dataType = getDbType(column.getColumnType());
         String columnName = column.getColumnName();
         column.setTableId(table.getTableId());
@@ -44,48 +43,48 @@ public class GenUtils
         // 设置默认类型
         column.setJavaType(GenConstants.TYPE_STRING);
 
-        if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType))
-        {
+        if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) {
             // 字符串长度超过500设置为文本域
             Integer columnLength = getColumnLength(column.getColumnType());
             String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
             column.setHtmlType(htmlType);
-        }
-        else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType))
-        {
+        } else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
             column.setJavaType(GenConstants.TYPE_DATE);
             column.setHtmlType(GenConstants.HTML_DATETIME);
-        }
-        else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType))
-        {
+        } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
             column.setHtmlType(GenConstants.HTML_INPUT);
 
             // 如果是浮点型 统一用BigDecimal
             String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
-            if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0)
-            {
+            if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
                 column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
             }
             // 如果是整形
-            else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10)
-            {
+            else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) {
                 column.setJavaType(GenConstants.TYPE_INTEGER);
             }
             // 长整形
-            else
-            {
+            else {
                 column.setJavaType(GenConstants.TYPE_LONG);
             }
         }
 
         // 插入字段(默认所有字段都需要插入)
-        column.setIsInsert(GenConstants.REQUIRE);
-
+        // 主键不需要添加
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_ADD, columnName) && !column.isPk()) {
+            column.setIsInsert(GenConstants.REQUIRE);
+        }
         // 编辑字段
-        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk())
+        // 编辑需要主键
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName))
         {
             column.setIsEdit(GenConstants.REQUIRE);
         }
+        // 编辑需要的设置必选
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName))
+        {
+            column.setIsRequired(GenConstants.REQUIRE);
+        }
         // 列表字段
         if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk())
         {

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

@@ -16,7 +16,7 @@ import java.util.Map;
 
 /**
  * 模板处理工具类
- *
+ * 
  * @author ruoyi
  */
 public class VelocityUtils
@@ -61,6 +61,7 @@ public class VelocityUtils
         velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
         velocityContext.put("columns", genTable.getColumns());
         velocityContext.put("table", genTable);
+        velocityContext.put("dicts", getDicts(genTable));
         setMenuVelocityContext(velocityContext, genTable);
         if (GenConstants.TPL_TREE.equals(tplCategory))
         {
@@ -245,7 +246,7 @@ public class VelocityUtils
 
     /**
      * 根据列类型获取导入包
-     *
+     * 
      * @param genTable 业务表对象
      * @return 返回需要导入的包列表
      */
@@ -274,6 +275,27 @@ public class VelocityUtils
     }
 
     /**
+     * 根据列类型获取字典组
+     *
+     * @param genTable 业务表对象
+     * @return 返回字典组
+     */
+    public static String getDicts(GenTable genTable)
+    {
+        List<GenTableColumn> columns = genTable.getColumns();
+        List<String> dicts = new ArrayList<String>();
+        for (GenTableColumn column : columns)
+        {
+            if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+                    column.getHtmlType(), new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO }))
+            {
+                dicts.add("'" + column.getDictType() + "'");
+            }
+        }
+        return StringUtils.join(dicts, ", ");
+    }
+
+    /**
      * 获取权限前缀
      *
      * @param moduleName 模块名称

+ 0 - 2
ruoyi-generator/src/main/resources/vm/java/mapper.java.vm

@@ -2,8 +2,6 @@ package ${packageName}.mapper;
 
 import ${packageName}.domain.${ClassName};
 import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
-import com.ruoyi.common.core.mybatisplus.cache.MybatisPlusRedisCache;
-import org.apache.ibatis.annotations.CacheNamespace;
 
 /**
  * ${functionName}Mapper接口

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

@@ -79,7 +79,12 @@ public class ${ClassName}ServiceImpl extends ServicePlusImpl<${ClassName}Mapper,
     public Boolean insertByBo(${ClassName}Bo bo) {
         ${ClassName} add = BeanUtil.toBean(bo, ${ClassName}.class);
         validEntityBeforeSave(add);
-        return save(add);
+        boolean flag = save(add);
+#set($pk=$pkColumn.javaField.substring(0,1).toUpperCase() + ${pkColumn.javaField.substring(1)})
+        if (flag) {
+            bo.set$pk(add.get$pk());
+        }
+        return flag;
     }
 
     @Override

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

@@ -27,14 +27,8 @@ public class ${ClassName}Vo {
 
 	private static final long serialVersionUID = 1L;
 
-	/**
-     *  $pkColumn.columnComment
-     */
-	@ApiModelProperty("$pkColumn.columnComment")
-	private ${pkColumn.javaType} ${pkColumn.javaField};
-
 #foreach ($column in $columns)
-#if($column.isList && $column.isPk!=1)
+#if($column.isList)
     /**
      * $column.columnComment
      */

+ 31 - 48
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm

@@ -25,10 +25,10 @@
       <el-form-item label="${comment}" prop="${column.javaField}">
         <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
           <el-option
-            v-for="dict in ${column.javaField}Options"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
+            v-for="dict in dict.type.${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
           />
         </el-select>
       </el-form-item>
@@ -108,7 +108,7 @@
 #elseif($column.list && $column.dictType && "" != $column.dictType)
       <el-table-column label="${comment}" align="center" prop="${javaField}">
         <template slot-scope="scope">
-          <dict-tag :options="${javaField}Options" :value="scope.row.${javaField}"/>
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
         </template>
       </el-table-column>
 #elseif($column.list && "" != $javaField)
@@ -183,10 +183,10 @@
         <el-form-item label="${comment}" prop="${field}">
           <el-select v-model="form.${field}" placeholder="请选择${comment}">
             <el-option
-              v-for="dict in ${field}Options"
-              :key="dict.dictValue"
-              :label="dict.dictLabel"
-              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.dictValue)"#else:value="dict.dictValue"#end
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
 
             ></el-option>
           </el-select>
@@ -201,10 +201,10 @@
         <el-form-item label="${comment}">
           <el-checkbox-group v-model="form.${field}">
             <el-checkbox
-              v-for="dict in ${field}Options"
-              :key="dict.dictValue"
-              :label="dict.dictValue">
-              {{dict.dictLabel}}
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
             </el-checkbox>
           </el-checkbox-group>
         </el-form-item>
@@ -218,11 +218,11 @@
         <el-form-item label="${comment}">
           <el-radio-group v-model="form.${field}">
             <el-radio
-              v-for="dict in ${field}Options"
-              :key="dict.dictValue"
-              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.dictValue)"#else:label="dict.dictValue"#end
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
 
-            >{{dict.dictLabel}}</el-radio>
+            >{{dict.label}}</el-radio>
           </el-radio-group>
         </el-form-item>
 #elseif($column.htmlType == "radio" && $dictType)
@@ -263,6 +263,9 @@ import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 
 export default {
   name: "${BusinessName}",
+#if(${dicts} != '')
+  dicts: [${dicts}],
+#end
   components: {
     Treeselect
   },
@@ -283,16 +286,7 @@ export default {
       // 是否显示弹出层
       open: false,
 #foreach ($column in $columns)
-#set($parentheseIndex=$column.columnComment.indexOf("("))
-#if($parentheseIndex != -1)
-#set($comment=$column.columnComment.substring(0, $parentheseIndex))
-#else
-#set($comment=$column.columnComment)
-#end
-#if(${column.dictType} && ${column.dictType} != '')
-      // $comment字典
-      ${column.javaField}Options: [],
-#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
 #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
       // $comment时间范围
       daterange${AttrName}: [],
@@ -330,13 +324,6 @@ export default {
   },
   created() {
     this.getList();
-#foreach ($column in $columns)
-#if(${column.dictType} && ${column.dictType} != '')
-    this.getDicts("${column.dictType}").then(response => {
-      this.${column.javaField}Options = response.data;
-    });
-#end
-#end
   },
   methods: {
     /** 查询${functionName}列表 */
@@ -464,7 +451,7 @@ export default {
 #end
           if (this.form.${pkColumn.javaField} != null) {
             update${BusinessName}(this.form).then(response => {
-              this.msgSuccess("修改成功");
+              this.#[[$modal]]#.msgSuccess("修改成功");
               this.open = false;
               this.getList();
             }).finally(() => {
@@ -472,7 +459,7 @@ export default {
             });
           } else {
             add${BusinessName}(this.form).then(response => {
-              this.msgSuccess("新增成功");
+              this.#[[$modal]]#.msgSuccess("新增成功");
               this.open = false;
               this.getList();
             }).finally(() => {
@@ -484,19 +471,15 @@ export default {
     },
     /** 删除按钮操作 */
     handleDelete(row) {
-      this.$confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(() => {
-		  this.loading = true;
-          return del${BusinessName}(row.${pkColumn.javaField});
-        }).then(() => {
-		  this.loading = false;
-          this.getList();
-          this.msgSuccess("删除成功");
+      this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(() => {
+        this.loading = true;
+        return del${BusinessName}(row.${pkColumn.javaField});
+      }).then(() => {
+        this.loading = false;
+        this.getList();
+        this.#[[$modal]]#.msgSuccess("删除成功");
       }).finally(() => {
-              this.loading = false;
+        this.loading = false;
       });
     }
   }

+ 34 - 51
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm

@@ -25,10 +25,10 @@
       <el-form-item label="${comment}" prop="${column.javaField}">
         <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
           <el-option
-            v-for="dict in ${column.javaField}Options"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
+            v-for="dict in dict.type.${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
           />
         </el-select>
       </el-form-item>
@@ -137,7 +137,7 @@
 #elseif($column.list && $column.dictType && "" != $column.dictType)
       <el-table-column label="${comment}" align="center" prop="${javaField}">
         <template slot-scope="scope">
-          <dict-tag :options="${javaField}Options" :value="scope.row.${javaField}"/>
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
         </template>
       </el-table-column>
 #elseif($column.list && "" != $javaField)
@@ -163,7 +163,7 @@
         </template>
       </el-table-column>
     </el-table>
-
+    
     <pagination
       v-show="total>0"
       :total="total"
@@ -205,10 +205,10 @@
         <el-form-item label="${comment}" prop="${field}">
           <el-select v-model="form.${field}" placeholder="请选择${comment}">
             <el-option
-              v-for="dict in ${field}Options"
-              :key="dict.dictValue"
-              :label="dict.dictLabel"
-              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.dictValue)"#else:value="dict.dictValue"#end
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
 
             ></el-option>
           </el-select>
@@ -223,10 +223,10 @@
         <el-form-item label="${comment}">
           <el-checkbox-group v-model="form.${field}">
             <el-checkbox
-              v-for="dict in ${field}Options"
-              :key="dict.dictValue"
-              :label="dict.dictValue">
-              {{dict.dictLabel}}
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
             </el-checkbox>
           </el-checkbox-group>
         </el-form-item>
@@ -240,11 +240,11 @@
         <el-form-item label="${comment}">
           <el-radio-group v-model="form.${field}">
             <el-radio
-              v-for="dict in ${field}Options"
-              :key="dict.dictValue"
-              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.dictValue)"#else:label="dict.dictValue"#end
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
 
-            >{{dict.dictLabel}}</el-radio>
+            >{{dict.label}}</el-radio>
           </el-radio-group>
         </el-form-item>
 #elseif($column.htmlType == "radio" && $dictType)
@@ -315,6 +315,9 @@ import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${Busin
 
 export default {
   name: "${BusinessName}",
+#if(${dicts} != '')
+  dicts: [${dicts}],
+#end
   data() {
     return {
       // 按钮loading
@@ -348,16 +351,7 @@ export default {
       // 是否显示弹出层
       open: false,
 #foreach ($column in $columns)
-#set($parentheseIndex=$column.columnComment.indexOf("("))
-#if($parentheseIndex != -1)
-#set($comment=$column.columnComment.substring(0, $parentheseIndex))
-#else
-#set($comment=$column.columnComment)
-#end
-#if(${column.dictType} && ${column.dictType} != '')
-      // $comment字典
-      ${column.javaField}Options: [],
-#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
 #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
       // $comment时间范围
       daterange${AttrName}: [],
@@ -397,13 +391,6 @@ export default {
   },
   created() {
     this.getList();
-#foreach ($column in $columns)
-#if(${column.dictType} && ${column.dictType} != '')
-    this.getDicts("${column.dictType}").then(response => {
-      this.${column.javaField}Options = response.data;
-    });
-#end
-#end
   },
   methods: {
     /** 查询${functionName}列表 */
@@ -519,7 +506,7 @@ export default {
 #end
           if (this.form.${pkColumn.javaField} != null) {
             update${BusinessName}(this.form).then(response => {
-              this.msgSuccess("修改成功");
+              this.#[[$modal]]#.msgSuccess("修改成功");
               this.open = false;
               this.getList();
             }).finally(() => {
@@ -527,7 +514,7 @@ export default {
             });
           } else {
             add${BusinessName}(this.form).then(response => {
-              this.msgSuccess("新增成功");
+              this.#[[$modal]]#.msgSuccess("新增成功");
               this.open = false;
               this.getList();
             }).finally(() => {
@@ -540,19 +527,15 @@ export default {
     /** 删除按钮操作 */
     handleDelete(row) {
       const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids;
-      this.$confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(() => {
-          this.loading = true;
-          return del${BusinessName}(${pkColumn.javaField}s);
-        }).then(() => {
-          this.loading = false;
-          this.getList();
-          this.msgSuccess("删除成功");
+      this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(() => {
+        this.loading = true;
+        return del${BusinessName}(${pkColumn.javaField}s);
+      }).then(() => {
+        this.loading = false;
+        this.getList();
+        this.#[[$modal]]#.msgSuccess("删除成功");
       }).finally(() => {
-          this.loading = false;
+        this.loading = false;
       });
     },
 #if($table.sub)
@@ -574,7 +557,7 @@ export default {
     /** ${subTable.functionName}删除按钮操作 */
     handleDelete${subClassName}() {
       if (this.checked${subClassName}.length == 0) {
-        this.msgError("请先选择要删除的${subTable.functionName}数据");
+        this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
       } else {
         const ${subclassName}List = this.${subclassName}List;
         const checked${subClassName} = this.checked${subClassName};
@@ -590,7 +573,7 @@ export default {
 #end
     /** 导出按钮操作 */
     handleExport() {
-      this.downLoadExcel('/${moduleName}/${businessName}/export', this.queryParams);
+        this.#[[$download]]#.excel('/${moduleName}/${businessName}/export', this.queryParams);
     }
   }
 };

+ 1 - 1
ruoyi-oss/pom.xml

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

+ 2 - 2
ruoyi-oss/src/main/java/com/ruoyi/oss/constant/CloudConstant.java

@@ -4,7 +4,7 @@ import java.util.Arrays;
 import java.util.List;
 
 /**
- * 存储常量
+ * 对象存储常量
  *
  * @author Lion Li
  */
@@ -16,7 +16,7 @@ public class CloudConstant {
 	public static final String SYS_OSS_KEY = "sys_oss:";
 
 	/**
-	 * 存储配置KEY
+	 * 对象存储配置KEY
 	 */
 	public static final String CLOUD_STORAGE_CONFIG_KEY = "CloudStorageConfig";
 

+ 1 - 1
ruoyi-oss/src/main/java/com/ruoyi/oss/enumd/CloudServiceEnumd.java

@@ -9,7 +9,7 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 /**
- * 存储服务商枚举
+ * 对象存储服务商枚举
  *
  * @author Lion Li
  */

+ 1 - 1
ruoyi-oss/src/main/java/com/ruoyi/oss/properties/CloudStorageProperties.java

@@ -5,7 +5,7 @@ import lombok.Data;
 import java.util.Date;
 
 /**
- * OSS存储 配置属性
+ * OSS对象存储 配置属性
  *
  * @author Lion Li
  */

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

@@ -5,7 +5,7 @@ import com.ruoyi.oss.entity.UploadResult;
 import java.io.InputStream;
 
 /**
- * 存储策略
+ * 对象存储策略
  *
  * @author Lion Li
  */

+ 1 - 1
ruoyi-oss/src/main/java/com/ruoyi/oss/service/abstractd/AbstractCloudStorageStrategy.java

@@ -11,7 +11,7 @@ import com.ruoyi.oss.service.ICloudStorageStrategy;
 import java.io.InputStream;
 
 /**
- * 存储策略(支持七牛、阿里云、腾讯云、minio)
+ * 对象存储策略(支持七牛、阿里云、腾讯云、minio)
  *
  * @author Lion Li
  */

+ 2 - 2
ruoyi-quartz/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.1.0</version>
+        <version>3.2.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -37,4 +37,4 @@
 
     </dependencies>
 
-</project>
+</project>

+ 2 - 2
ruoyi-system/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi-vue-plus</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.1.0</version>
+        <version>3.2.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -31,4 +31,4 @@
 
     </dependencies>
 
-</project>
+</project>

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

@@ -9,7 +9,7 @@ import java.io.Serializable;
 import java.util.Date;
 
 /**
- * OSS存储对象
+ * OSS对象存储对象
  *
  * @author Lion Li
  */
@@ -23,7 +23,7 @@ public class SysOss implements Serializable {
 
 
 	/**
-	 * 存储主键
+	 * 对象存储主键
 	 */
 	@TableId(value = "oss_id", type = IdType.AUTO)
 	private Long ossId;

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

@@ -9,7 +9,7 @@ import java.util.Date;
 import java.math.BigDecimal;
 
 /**
- * 存储配置对象 sys_oss_config
+ * 对象存储配置对象 sys_oss_config
  *
  * @author ruoyi
  * @date 2021-08-11

+ 2 - 2
ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssBo.java

@@ -7,13 +7,13 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 /**
- * OSS存储分页查询对象 sys_oss
+ * OSS对象存储分页查询对象 sys_oss
  *
  * @author Lion Li
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
-@ApiModel("OSS存储分页查询对象")
+@ApiModel("OSS对象存储分页查询对象")
 public class SysOssBo extends BaseEntity {
 
 	/**

+ 2 - 2
ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssConfigBo.java

@@ -13,7 +13,7 @@ import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
 
 /**
- * 存储配置业务对象 sys_oss_config
+ * 对象存储配置业务对象 sys_oss_config
  *
  * @author Lion Li
  * @author 孤舟烟雨
@@ -22,7 +22,7 @@ import javax.validation.constraints.Size;
 
 @Data
 @EqualsAndHashCode(callSuper = true)
-@ApiModel("存储配置业务对象")
+@ApiModel("对象存储配置业务对象")
 public class SysOssConfigBo extends BaseEntity {
 
 	/**

+ 5 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java

@@ -42,6 +42,11 @@ public class RouterVo {
     private String component;
 
     /**
+     * 路由参数:如 {"id": 1, "name": "ry"}
+     */
+    private String query;
+
+    /**
      * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
      */
     private Boolean alwaysShow;

+ 2 - 2
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssConfigVo.java

@@ -8,14 +8,14 @@ import lombok.Data;
 
 
 /**
- * 存储配置视图对象 sys_oss_config
+ * 对象存储配置视图对象 sys_oss_config
  *
  * @author Lion Li
  * @author 孤舟烟雨
  * @date 2021-08-13
  */
 @Data
-@ApiModel("存储配置视图对象")
+@ApiModel("对象存储配置视图对象")
 @ExcelIgnoreUnannotated
 public class SysOssConfigVo {
 

+ 4 - 4
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssVo.java

@@ -7,20 +7,20 @@ import lombok.Data;
 import java.util.Date;
 
 /**
- * OSS存储视图对象 sys_oss
+ * OSS对象存储视图对象 sys_oss
  *
  * @author Lion Li
  */
 @Data
-@ApiModel("OSS存储视图对象")
+@ApiModel("OSS对象存储视图对象")
 public class SysOssVo {
 
 	private static final long serialVersionUID = 1L;
 
 	/**
-	 *  存储主键
+	 *  对象存储主键
 	 */
-	@ApiModelProperty("存储主键")
+	@ApiModelProperty("对象存储主键")
 	private Long ossId;
 
 	/**

+ 1 - 1
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssConfigMapper.java

@@ -4,7 +4,7 @@ import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
 import com.ruoyi.system.domain.SysOssConfig;
 
 /**
- * 存储配置Mapper接口
+ * 对象存储配置Mapper接口
  *
  * @author Lion Li
  * @author 孤舟烟雨

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

@@ -9,7 +9,7 @@ import com.ruoyi.system.domain.vo.SysOssConfigVo;
 import java.util.Collection;
 
 /**
- * 存储配置Service接口
+ * 对象存储配置Service接口
  *
  * @author Lion Li
  * @author 孤舟烟雨
@@ -29,15 +29,15 @@ public interface ISysOssConfigService extends IServicePlus<SysOssConfig, SysOssC
 
 
 	/**
-	 * 根据新增业务对象插入存储配置
-	 * @param bo 存储配置新增业务对象
+	 * 根据新增业务对象插入对象存储配置
+	 * @param bo 对象存储配置新增业务对象
 	 * @return
 	 */
 	Boolean insertByBo(SysOssConfigBo bo);
 
 	/**
-	 * 根据编辑业务对象修改存储配置
-	 * @param bo 存储配置编辑业务对象
+	 * 根据编辑业务对象修改对象存储配置
+	 * @param bo 对象存储配置编辑业务对象
 	 * @return
 	 */
 	Boolean updateByBo(SysOssConfigBo bo);

+ 30 - 48
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java → ruoyi-system/src/main/java/com/ruoyi/system/service/PermissionService.java

@@ -1,4 +1,4 @@
-package com.ruoyi.framework.web.service;
+package com.ruoyi.system.service;
 
 import com.ruoyi.common.core.domain.entity.SysRole;
 import com.ruoyi.common.core.domain.model.LoginUser;
@@ -15,12 +15,15 @@ import java.util.Set;
  * @author ruoyi
  */
 @Service("ss")
-public class PermissionService
-{
-    /** 所有权限标识 */
+public class PermissionService {
+    /**
+     * 所有权限标识
+     */
     private static final String ALL_PERMISSION = "*:*:*";
 
-    /** 管理员角色权限标识 */
+    /**
+     * 管理员角色权限标识
+     */
     private static final String SUPER_ADMIN = "admin";
 
     private static final String ROLE_DELIMETER = ",";
@@ -33,15 +36,12 @@ public class PermissionService
      * @param permission 权限字符串
      * @return 用户是否具备某权限
      */
-    public boolean hasPermi(String permission)
-    {
-        if (StringUtils.isEmpty(permission))
-        {
+    public boolean hasPermi(String permission) {
+        if (StringUtils.isEmpty(permission)) {
             return false;
         }
         LoginUser loginUser = SecurityUtils.getLoginUser();
-        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
-        {
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
             return false;
         }
         return hasPermissions(loginUser.getPermissions(), permission);
@@ -53,8 +53,7 @@ public class PermissionService
      * @param permission 权限字符串
      * @return 用户是否不具备某权限
      */
-    public boolean lacksPermi(String permission)
-    {
+    public boolean lacksPermi(String permission) {
         return hasPermi(permission) != true;
     }
 
@@ -64,22 +63,17 @@ public class PermissionService
      * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
      * @return 用户是否具有以下任意一个权限
      */
-    public boolean hasAnyPermi(String permissions)
-    {
-        if (StringUtils.isEmpty(permissions))
-        {
+    public boolean hasAnyPermi(String permissions) {
+        if (StringUtils.isEmpty(permissions)) {
             return false;
         }
         LoginUser loginUser = SecurityUtils.getLoginUser();
-        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
-        {
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
             return false;
         }
         Set<String> authorities = loginUser.getPermissions();
-        for (String permission : permissions.split(PERMISSION_DELIMETER))
-        {
-            if (permission != null && hasPermissions(authorities, permission))
-            {
+        for (String permission : permissions.split(PERMISSION_DELIMETER)) {
+            if (permission != null && hasPermissions(authorities, permission)) {
                 return true;
             }
         }
@@ -92,22 +86,17 @@ public class PermissionService
      * @param role 角色字符串
      * @return 用户是否具备某角色
      */
-    public boolean hasRole(String role)
-    {
-        if (StringUtils.isEmpty(role))
-        {
+    public boolean hasRole(String role) {
+        if (StringUtils.isEmpty(role)) {
             return false;
         }
         LoginUser loginUser = SecurityUtils.getLoginUser();
-        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
-        {
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
             return false;
         }
-        for (SysRole sysRole : loginUser.getUser().getRoles())
-        {
+        for (SysRole sysRole : loginUser.getUser().getRoles()) {
             String roleKey = sysRole.getRoleKey();
-            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
-            {
+            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {
                 return true;
             }
         }
@@ -120,8 +109,7 @@ public class PermissionService
      * @param role 角色名称
      * @return 用户是否不具备某角色
      */
-    public boolean lacksRole(String role)
-    {
+    public boolean lacksRole(String role) {
         return hasRole(role) != true;
     }
 
@@ -131,21 +119,16 @@ public class PermissionService
      * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
      * @return 用户是否具有以下任意一个角色
      */
-    public boolean hasAnyRoles(String roles)
-    {
-        if (StringUtils.isEmpty(roles))
-        {
+    public boolean hasAnyRoles(String roles) {
+        if (StringUtils.isEmpty(roles)) {
             return false;
         }
         LoginUser loginUser = SecurityUtils.getLoginUser();
-        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
-        {
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
             return false;
         }
-        for (String role : roles.split(ROLE_DELIMETER))
-        {
-            if (hasRole(role))
-            {
+        for (String role : roles.split(ROLE_DELIMETER)) {
+            if (hasRole(role)) {
                 return true;
             }
         }
@@ -156,11 +139,10 @@ public class PermissionService
      * 判断是否包含权限
      *
      * @param permissions 权限列表
-     * @param permission 权限字符串
+     * @param permission  权限字符串
      * @return 用户是否具备某权限
      */
-    private boolean hasPermissions(Set<String> permissions, String permission)
-    {
+    private boolean hasPermissions(Set<String> permissions, String permission) {
         return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
     }
 }

+ 45 - 52
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java → ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java

@@ -1,8 +1,10 @@
-package com.ruoyi.framework.web.service;
+package com.ruoyi.system.service;
 
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.service.LogininforService;
+import com.ruoyi.common.core.service.TokenService;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.exception.user.CaptchaException;
 import com.ruoyi.common.exception.user.CaptchaExpireException;
@@ -11,14 +13,12 @@ import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.MessageUtils;
 import com.ruoyi.common.utils.RedisUtils;
 import com.ruoyi.common.utils.ServletUtils;
-import com.ruoyi.system.service.ISysConfigService;
-import com.ruoyi.system.service.ISysUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
@@ -28,66 +28,57 @@ import javax.servlet.http.HttpServletRequest;
  *
  * @author ruoyi
  */
-@Component
-public class SysLoginService
-{
+@Service
+public class SysLoginService {
     @Autowired
     private TokenService tokenService;
 
     @Resource
     private AuthenticationManager authenticationManager;
 
-	@Autowired
+    @Autowired
     private ISysUserService userService;
 
-	@Autowired
-	private ISysConfigService configService;
+    @Autowired
+    private ISysConfigService configService;
 
-	@Autowired
-	private AsyncService asyncService;
+    @Autowired
+    private LogininforService asyncService;
 
     /**
      * 登录验证
      *
      * @param username 用户名
      * @param password 密码
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public String login(String username, String password, String code, String uuid)
-    {
-		HttpServletRequest request = ServletUtils.getRequest();
-		boolean captchaOnOff = configService.selectCaptchaOnOff();
+    public String login(String username, String password, String code, String uuid) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        boolean captchaOnOff = configService.selectCaptchaOnOff();
         // 验证码开关
-        if (captchaOnOff)
-        {
+        if (captchaOnOff) {
             validateCaptcha(username, code, uuid, request);
         }
         // 用户验证
         Authentication authentication = null;
-        try
-        {
+        try {
             // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
             authentication = authenticationManager
                     .authenticate(new UsernamePasswordAuthenticationToken(username, password));
-        }
-        catch (Exception e)
-        {
-            if (e instanceof BadCredentialsException)
-            {
-				asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
+        } catch (Exception e) {
+            if (e instanceof BadCredentialsException) {
+                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
                 throw new UserPasswordNotMatchException();
-            }
-            else
-            {
-				asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
+            } else {
+                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
                 throw new ServiceException(e.getMessage());
             }
         }
-		asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
+        asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
         LoginUser loginUser = (LoginUser) authentication.getPrincipal();
-        recordLoginInfo(loginUser.getUser());
+        recordLoginInfo(loginUser.getUserId());
         // 生成token
         return tokenService.createToken(loginUser);
     }
@@ -96,32 +87,34 @@ public class SysLoginService
      * 校验验证码
      *
      * @param username 用户名
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
     public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
-		String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
-		String captcha = RedisUtils.getCacheObject(verifyKey);
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = RedisUtils.getCacheObject(verifyKey);
         RedisUtils.deleteObject(verifyKey);
-		if (captcha == null) {
-			asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
-			throw new CaptchaExpireException();
-		}
-		if (!code.equalsIgnoreCase(captcha)) {
-			asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
-			throw new CaptchaException();
-		}
+        if (captcha == null) {
+            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha)) {
+            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
+            throw new CaptchaException();
+        }
     }
 
     /**
      * 记录登录信息
+     *
+     * @param userId 用户ID
      */
-    public void recordLoginInfo(SysUser user)
-    {
-        user.setLoginIp(ServletUtils.getClientIP());
-        user.setLoginDate(DateUtils.getNowDate());
-		user.setUpdateBy(user.getUserName());
-        userService.updateUserProfile(user);
+    public void recordLoginInfo(Long userId) {
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
+        sysUser.setLoginIp(ServletUtils.getClientIP());
+        sysUser.setLoginDate(DateUtils.getNowDate());
+        userService.updateUserProfile(sysUser);
     }
 }

+ 17 - 26
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java → ruoyi-system/src/main/java/com/ruoyi/system/service/SysPermissionService.java

@@ -1,21 +1,20 @@
-package com.ruoyi.framework.web.service;
+package com.ruoyi.system.service;
+
+import com.ruoyi.common.core.domain.entity.SysUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
 
 import java.util.HashSet;
 import java.util.Set;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-import com.ruoyi.common.core.domain.entity.SysUser;
-import com.ruoyi.system.service.ISysMenuService;
-import com.ruoyi.system.service.ISysRoleService;
 
 /**
  * 用户权限处理
- * 
+ *
  * @author ruoyi
  */
-@Component
-public class SysPermissionService
-{
+@Service
+public class SysPermissionService {
+
     @Autowired
     private ISysRoleService roleService;
 
@@ -24,20 +23,16 @@ public class SysPermissionService
 
     /**
      * 获取角色数据权限
-     * 
+     *
      * @param user 用户信息
      * @return 角色权限信息
      */
-    public Set<String> getRolePermission(SysUser user)
-    {
+    public Set<String> getRolePermission(SysUser user) {
         Set<String> roles = new HashSet<String>();
         // 管理员拥有所有权限
-        if (user.isAdmin())
-        {
+        if (user.isAdmin()) {
             roles.add("admin");
-        }
-        else
-        {
+        } else {
             roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
         }
         return roles;
@@ -45,20 +40,16 @@ public class SysPermissionService
 
     /**
      * 获取菜单数据权限
-     * 
+     *
      * @param user 用户信息
      * @return 菜单权限信息
      */
-    public Set<String> getMenuPermission(SysUser user)
-    {
+    public Set<String> getMenuPermission(SysUser user) {
         Set<String> perms = new HashSet<String>();
         // 管理员拥有所有权限
-        if (user.isAdmin())
-        {
+        if (user.isAdmin()) {
             perms.add("*:*:*");
-        }
-        else
-        {
+        } else {
             perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
         }
         return perms;

+ 26 - 46
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java → ruoyi-system/src/main/java/com/ruoyi/system/service/SysRegisterService.java

@@ -1,84 +1,67 @@
-package com.ruoyi.framework.web.service;
+package com.ruoyi.system.service;
 
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.constant.UserConstants;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.RegisterBody;
+import com.ruoyi.common.core.service.LogininforService;
 import com.ruoyi.common.exception.user.CaptchaException;
 import com.ruoyi.common.exception.user.CaptchaExpireException;
 import com.ruoyi.common.utils.*;
-import com.ruoyi.system.service.ISysConfigService;
-import com.ruoyi.system.service.ISysUserService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
 
 /**
  * 注册校验方法
  *
  * @author ruoyi
  */
-@Component
-public class SysRegisterService
-{
+@Service
+public class SysRegisterService {
+
     @Autowired
     private ISysUserService userService;
 
     @Autowired
     private ISysConfigService configService;
 
-	@Autowired
-	private AsyncService asyncService;
+    @Autowired
+    private LogininforService asyncService;
 
     /**
      * 注册
      */
-    public String register(RegisterBody registerBody)
-    {
+    public String register(RegisterBody registerBody) {
         String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
 
         boolean captchaOnOff = configService.selectCaptchaOnOff();
         // 验证码开关
-        if (captchaOnOff)
-        {
+        if (captchaOnOff) {
             validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
         }
 
-        if (StringUtils.isEmpty(username))
-        {
+        if (StringUtils.isEmpty(username)) {
             msg = "用户名不能为空";
-        }
-        else if (StringUtils.isEmpty(password))
-        {
+        } else if (StringUtils.isEmpty(password)) {
             msg = "用户密码不能为空";
-        }
-        else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
-                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
-        {
+        } else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
+                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
             msg = "账户长度必须在2到20个字符之间";
-        }
-        else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
-                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
-        {
+        } else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
+                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
             msg = "密码长度必须在5到20个字符之间";
-        }
-        else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username)))
-        {
+        } else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username))) {
             msg = "保存用户'" + username + "'失败,注册账号已存在";
-        }
-        else
-        {
+        } else {
             SysUser sysUser = new SysUser();
             sysUser.setUserName(username);
             sysUser.setNickName(username);
             sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword()));
             boolean regFlag = userService.registerUser(sysUser);
-            if (!regFlag)
-            {
+            if (!regFlag) {
                 msg = "注册失败,请联系系统管理人员";
-            }
-            else
-            {
-				asyncService.recordLogininfor(username, Constants.REGISTER,
+            } else {
+                asyncService.recordLogininfor(username, Constants.REGISTER,
                         MessageUtils.message("user.register.success"), ServletUtils.getRequest());
             }
         }
@@ -89,21 +72,18 @@ public class SysRegisterService
      * 校验验证码
      *
      * @param username 用户名
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public void validateCaptcha(String username, String code, String uuid)
-    {
+    public void validateCaptcha(String username, String code, String uuid) {
         String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
         String captcha = RedisUtils.getCacheObject(verifyKey);
         RedisUtils.deleteObject(verifyKey);
-        if (captcha == null)
-        {
+        if (captcha == null) {
             throw new CaptchaExpireException();
         }
-        if (!code.equalsIgnoreCase(captcha))
-        {
+        if (!code.equalsIgnoreCase(captcha)) {
             throw new CaptchaException();
         }
     }

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

@@ -1,15 +1,24 @@
 package com.ruoyi.system.service.impl;
 
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.service.LogininforService;
 import com.ruoyi.common.utils.PageUtils;
+import com.ruoyi.common.utils.ServletUtils;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.AddressUtils;
 import com.ruoyi.system.domain.SysLogininfor;
 import com.ruoyi.system.mapper.SysLogininforMapper;
 import com.ruoyi.system.service.ISysLogininforService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
@@ -20,8 +29,62 @@ import java.util.Map;
  *
  * @author ruoyi
  */
+@Slf4j
 @Service
-public class SysLogininforServiceImpl extends ServicePlusImpl<SysLogininforMapper, SysLogininfor, SysLogininfor> implements ISysLogininforService {
+public class SysLogininforServiceImpl extends ServicePlusImpl<SysLogininforMapper, SysLogininfor, SysLogininfor> implements ISysLogininforService, LogininforService {
+
+    /**
+     * 记录登录信息
+     *
+     * @param username 用户名
+     * @param status   状态
+     * @param message  消息
+     * @param args     列表
+     */
+    @Async
+    @Override
+    public void recordLogininfor(final String username, final String status, final String message,
+                                 HttpServletRequest request, final Object... args) {
+        final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
+        final String ip = ServletUtils.getClientIP(request);
+
+        String address = AddressUtils.getRealAddressByIP(ip);
+        StringBuilder s = new StringBuilder();
+        s.append(getBlock(ip));
+        s.append(address);
+        s.append(getBlock(username));
+        s.append(getBlock(status));
+        s.append(getBlock(message));
+        // 打印信息到日志
+        log.info(s.toString(), args);
+        // 获取客户端操作系统
+        String os = userAgent.getOs().getName();
+        // 获取客户端浏览器
+        String browser = userAgent.getBrowser().getName();
+        // 封装对象
+        SysLogininfor logininfor = new SysLogininfor();
+        logininfor.setUserName(username);
+        logininfor.setIpaddr(ip);
+        logininfor.setLoginLocation(address);
+        logininfor.setBrowser(browser);
+        logininfor.setOs(os);
+        logininfor.setMsg(message);
+        // 日志状态
+        if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
+            logininfor.setStatus(Constants.SUCCESS);
+        } else if (Constants.LOGIN_FAIL.equals(status)) {
+            logininfor.setStatus(Constants.FAIL);
+        }
+        // 插入数据
+        insertLogininfor(logininfor);
+    }
+
+    private String getBlock(Object msg) {
+        if (msg == null) {
+            msg = "";
+        }
+        return "[" + msg.toString() + "]";
+    }
 
     @Override
     public TableDataInfo<SysLogininfor> selectPageLogininforList(SysLogininfor logininfor) {

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

@@ -135,6 +135,7 @@ public class SysMenuServiceImpl extends ServicePlusImpl<SysMenuMapper, SysMenu,
             router.setName(getRouteName(menu));
             router.setPath(getRouterPath(menu));
             router.setComponent(getComponent(menu));
+            router.setQuery(menu.getQuery());
             router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
             List<SysMenu> cMenus = menu.getChildren();
             if (!cMenus.isEmpty() && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {

+ 21 - 2
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java

@@ -1,14 +1,19 @@
 package com.ruoyi.system.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.ArrayUtil;
-import com.ruoyi.common.utils.StringUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.service.OperLogService;
+import com.ruoyi.common.core.domain.dto.OperLogDTO;
 import com.ruoyi.common.utils.PageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.AddressUtils;
 import com.ruoyi.system.domain.SysOperLog;
 import com.ruoyi.system.mapper.SysOperLogMapper;
 import com.ruoyi.system.service.ISysOperLogService;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.util.Arrays;
@@ -22,7 +27,21 @@ import java.util.Map;
  * @author ruoyi
  */
 @Service
-public class SysOperLogServiceImpl extends ServicePlusImpl<SysOperLogMapper, SysOperLog, SysOperLog> implements ISysOperLogService {
+public class SysOperLogServiceImpl extends ServicePlusImpl<SysOperLogMapper, SysOperLog, SysOperLog> implements ISysOperLogService, OperLogService {
+
+    /**
+     * 操作日志记录
+     *
+     * @param operLogDTO 操作日志信息
+     */
+    @Async
+    @Override
+    public void recordOper(final OperLogDTO operLogDTO) {
+        SysOperLog operLog = BeanUtil.toBean(operLogDTO, SysOperLog.class);
+        // 远程查询操作地点
+        operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+        insertOperlog(operLog);
+    }
 
     @Override
     public TableDataInfo<SysOperLog> selectPageOperLogList(SysOperLog operLog) {

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

@@ -31,7 +31,7 @@ import java.util.Collection;
 import java.util.List;
 
 /**
- * 存储配置Service业务层处理
+ * 对象存储配置Service业务层处理
  *
  * @author Lion Li
  * @author 孤舟烟雨

+ 14 - 5
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java → ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TokenServiceImpl.java

@@ -1,20 +1,21 @@
-package com.ruoyi.framework.web.service;
+package com.ruoyi.system.service.impl;
 
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.http.useragent.UserAgent;
 import cn.hutool.http.useragent.UserAgentUtil;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.service.TokenService;
+import com.ruoyi.common.properties.TokenProperties;
 import com.ruoyi.common.utils.RedisUtils;
 import com.ruoyi.common.utils.ServletUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.ip.AddressUtils;
-import com.ruoyi.framework.config.properties.TokenProperties;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.HashMap;
@@ -26,8 +27,8 @@ import java.util.concurrent.TimeUnit;
  *
  * @author Lion Li
  */
-@Component
-public class TokenService {
+@Service
+public class TokenServiceImpl implements TokenService {
 
     protected static final long MILLIS_SECOND = 1000;
 
@@ -43,6 +44,7 @@ public class TokenService {
      *
      * @return 用户信息
      */
+    @Override
     public LoginUser getLoginUser(HttpServletRequest request) {
         // 获取请求携带的令牌
         String token = getToken(request);
@@ -64,6 +66,7 @@ public class TokenService {
     /**
      * 设置用户身份信息
      */
+    @Override
     public void setLoginUser(LoginUser loginUser) {
         if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
             refreshToken(loginUser);
@@ -73,6 +76,7 @@ public class TokenService {
     /**
      * 删除用户身份信息
      */
+    @Override
     public void delLoginUser(String token) {
         if (StringUtils.isNotEmpty(token)) {
             String userKey = getTokenKey(token);
@@ -86,6 +90,7 @@ public class TokenService {
      * @param loginUser 用户信息
      * @return 令牌
      */
+    @Override
     public String createToken(LoginUser loginUser) {
         String token = IdUtil.fastUUID();
         loginUser.setToken(token);
@@ -103,6 +108,7 @@ public class TokenService {
      * @param loginUser
      * @return 令牌
      */
+    @Override
     public void verifyToken(LoginUser loginUser) {
         long expireTime = loginUser.getExpireTime();
         long currentTime = System.currentTimeMillis();
@@ -116,6 +122,7 @@ public class TokenService {
      *
      * @param loginUser 登录信息
      */
+    @Override
     public void refreshToken(LoginUser loginUser) {
         loginUser.setLoginTime(System.currentTimeMillis());
         loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE);
@@ -129,6 +136,7 @@ public class TokenService {
      *
      * @param loginUser 登录信息
      */
+    @Override
     public void setUserAgent(LoginUser loginUser) {
         UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
         String ip = ServletUtils.getClientIP();
@@ -170,6 +178,7 @@ public class TokenService {
      * @param token 令牌
      * @return 用户名
      */
+    @Override
     public String getUsernameFromToken(String token) {
         Claims claims = parseToken(token);
         return claims.getSubject();

+ 15 - 23
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java → ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserDetailsServiceImpl.java

@@ -1,28 +1,27 @@
-package com.ruoyi.framework.web.service;
+package com.ruoyi.system.service.impl;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.enums.UserStatus;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.SysPermissionService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
 
 /**
  * 用户验证处理
  *
  * @author ruoyi
  */
+@Slf4j
 @Service
-public class UserDetailsServiceImpl implements UserDetailsService
-{
-    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
+public class UserDetailsServiceImpl implements UserDetailsService {
 
     @Autowired
     private ISysUserService userService;
@@ -31,21 +30,15 @@ public class UserDetailsServiceImpl implements UserDetailsService
     private SysPermissionService permissionService;
 
     @Override
-    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
-    {
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         SysUser user = userService.selectUserByUserName(username);
-        if (StringUtils.isNull(user))
-        {
+        if (StringUtils.isNull(user)) {
             log.info("登录用户:{} 不存在.", username);
             throw new ServiceException("登录用户:" + username + " 不存在");
-        }
-        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
-        {
+        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
             log.info("登录用户:{} 已被删除.", username);
             throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
-        }
-        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
-        {
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
             log.info("登录用户:{} 已被停用.", username);
             throw new ServiceException("对不起,您的账号:" + username + " 已停用");
         }
@@ -53,8 +46,7 @@ public class UserDetailsServiceImpl implements UserDetailsService
         return createLoginUser(user);
     }
 
-    public UserDetails createLoginUser(SysUser user)
-    {
+    public UserDetails createLoginUser(SysUser user) {
         return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
     }
 }

+ 7 - 3
ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml

@@ -12,7 +12,8 @@
         <result property="orderNum" column="order_num"/>
         <result property="path" column="path"/>
         <result property="component" column="component"/>
-        <result property="isFrame" column="is_frame"/>
+		<result property="query" column="query"/>
+		<result property="isFrame" column="is_frame"/>
         <result property="isCache" column="is_cache"/>
         <result property="menuType" column="menu_type"/>
         <result property="visible" column="visible"/>
@@ -33,6 +34,7 @@
                order_num,
                path,
                component,
+			   query,
                is_frame,
                is_cache,
                menu_type,
@@ -51,7 +53,8 @@
                         m.menu_name,
                         m.path,
                         m.component,
-                        m.visible,
+						m.query,
+						m.visible,
                         m.status,
                         ifnull(m.perms, '') as perms,
                         m.is_frame,
@@ -67,7 +70,7 @@
     </select>
 
     <select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult">
-        select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status,
+        select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.visible, m.status,
         ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
         from sys_menu m
         left join sys_role_menu rm on m.menu_id = rm.menu_id
@@ -92,6 +95,7 @@
                         m.menu_name,
                         m.path,
                         m.component,
+					    m.query,
                         m.visible,
                         m.status,
                         ifnull(m.perms, '') as perms,

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác