Selaa lähdekoodia

!691 发布 5.4.0 正式版
Merge pull request !691 from 疯狂的狮子Li/dev

疯狂的狮子Li 1 vuosi sitten
vanhempi
commit
49ef8378fe
100 muutettua tiedostoa jossa 1511 lisäystä ja 623 poistoa
  1. 1 1
      .run/ruoyi-monitor-admin.run.xml
  2. 1 1
      .run/ruoyi-server.run.xml
  3. 1 1
      .run/ruoyi-snailjob-server.run.xml
  4. 9 7
      README.md
  5. 15 21
      pom.xml
  6. 4 3
      ruoyi-admin/Dockerfile
  7. 10 12
      ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java
  8. 1 1
      ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java
  9. 3 3
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
  10. 4 4
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
  11. 3 3
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
  12. 3 3
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
  13. 3 3
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java
  14. 7 0
      ruoyi-admin/src/main/resources/application-dev.yml
  15. 7 0
      ruoyi-admin/src/main/resources/application-prod.yml
  16. 2 5
      ruoyi-admin/src/main/resources/application.yml
  17. 0 1
      ruoyi-admin/zhFonts/.uuid
  18. BIN
      ruoyi-admin/zhFonts/SIMSUN.TTC
  19. 0 4
      ruoyi-admin/zhFonts/fonts.dir
  20. 0 4
      ruoyi-admin/zhFonts/fonts.scale
  21. 1 1
      ruoyi-common/ruoyi-common-bom/pom.xml
  22. 3 2
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  23. 17 2
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java
  24. 18 3
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java
  25. 2 2
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java
  26. 4 2
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java
  27. 7 5
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java
  28. 28 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PermissionService.java
  29. 33 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
  30. 10 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java
  31. 19 6
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
  32. 84 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/NetUtils.java
  33. 23 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
  34. 15 5
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
  35. 14 30
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
  36. 40 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPattern.java
  37. 55 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPatternValidator.java
  38. 37 37
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java
  39. 1 1
      ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java
  40. 4 4
      ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
  41. 2 2
      ruoyi-common/ruoyi-common-excel/pom.xml
  42. 6 6
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java
  43. 6 6
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java
  44. 6 6
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
  45. 12 6
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
  46. 4 4
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java
  47. 18 16
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
  48. 1 1
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java
  49. 9 9
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java
  50. 26 26
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java
  51. 4 1
      ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java
  52. 31 0
      ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java
  53. 16 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
  54. 5 4
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
  55. 16 6
      ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java
  56. 180 165
      ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java
  57. 21 5
      ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java
  58. 21 8
      ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java
  59. 3 3
      ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
  60. 6 11
      ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java
  61. 92 0
      ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaRequest.java
  62. 50 0
      ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaSource.java
  63. 4 2
      ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
  64. 1 0
      ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java
  65. 22 4
      ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java
  66. 11 1
      ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java
  67. 1 1
      ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java
  68. 22 0
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
  69. 1 1
      ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java
  70. 1 1
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java
  71. 1 1
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/WebSocketController.java
  72. 1 1
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoImportVo.java
  73. 2 2
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java
  74. 2 2
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java
  75. 2 2
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestTreeVo.java
  76. 1 1
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/listener/ExportDemoListener.java
  77. 1 7
      ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java
  78. 2 0
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
  79. 2 2
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm
  80. 1 0
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
  81. 1 1
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
  82. 30 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/entity/BillDto.java
  83. 42 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java
  84. 45 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java
  85. 4 2
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java
  86. 37 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java
  87. 53 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java
  88. 60 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java
  89. 36 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java
  90. 43 0
      ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java
  91. 6 7
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java
  92. 31 11
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java
  93. 10 10
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
  94. 7 4
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java
  95. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java
  96. 0 18
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/AvatarVo.java
  97. 0 23
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/CacheListInfoVo.java
  98. 0 26
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/DeptTreeSelectVo.java
  99. 0 26
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MenuTreeSelectVo.java
  100. 3 3
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java

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

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

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

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

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

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

+ 9 - 7
README.md

@@ -7,10 +7,10 @@
 [![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
 [![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
 [![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
 [![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
 [![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
 [![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
-[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
+[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
 [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
 [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
 <br>
 <br>
-[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.4.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
 [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
 [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
 [![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
 [![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
 [![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
 [![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@@ -22,10 +22,12 @@
 
 
 > 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
 > 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
 
 
-> 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
-> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
+> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
+> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)<br>
+> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
+> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
 
 
-> 文档地址: [plus-doc](https://plus-doc.dromara.org)
+> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
 
 
 ## 赞助商
 ## 赞助商
 
 
@@ -34,6 +36,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
 数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
 数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
 引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
 引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
 <font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
 <font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
+Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
 [如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
 [如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
 
 
 # 本框架与RuoYi的功能差异
 # 本框架与RuoYi的功能差异
@@ -75,7 +78,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
 | 邮件          | 采用 mail-api 通用协议支持大部分邮件厂商                                                                                         | 不支持                                                                                |
 | 邮件          | 采用 mail-api 通用协议支持大部分邮件厂商                                                                                         | 不支持                                                                                |
 | 接口文档        | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了                                                     | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成                                                | 
 | 接口文档        | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了                                                     | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成                                                | 
 | 校验框架        | 采用 Validation 支持注解与工具类校验 注解支持国际化                                                                                  | 仅支持注解 且注解不支持国际化                                                                    |
 | 校验框架        | 采用 Validation 支持注解与工具类校验 注解支持国际化                                                                                  | 仅支持注解 且注解不支持国际化                                                                    |
-| Excel框架     | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                               | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           |
+| Excel框架     | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                   | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           |
 | 工作流支持       | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能                                                                                   | 无                                                                                  |
 | 工作流支持       | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能                                                                                   | 无                                                                                  |
 | 工具类框架       | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码                                                       | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等                                            | 
 | 工具类框架       | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码                                                       | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等                                            | 
 | 监控框架        | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控                                    | 无                                                                                  | 
 | 监控框架        | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控                                    | 无                                                                                  | 
@@ -113,7 +116,6 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
 | 系统接口   | 根据业务代码自动生成相关的api接口文档                                                 | 支持  | 支持               |
 | 系统接口   | 根据业务代码自动生成相关的api接口文档                                                 | 支持  | 支持               |
 | 服务监控   | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等                                  | 支持  | 仅支持单机CPU、内存、磁盘监控 |
 | 服务监控   | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等                                  | 支持  | 仅支持单机CPU、内存、磁盘监控 |
 | 缓存监控   | 对系统的缓存信息查询,命令统计等。                                                    | 支持  | 支持               |
 | 缓存监控   | 对系统的缓存信息查询,命令统计等。                                                    | 支持  | 支持               |
-| 在线构建器  | 拖动表单元素生成相应的HTML代码。                                                   | 支持  | 支持               |
 | 使用案例   | 系统的一些功能案例                                                            | 支持  | 不支持              |
 | 使用案例   | 系统的一些功能案例                                                            | 支持  | 不支持              |
 
 
 ## 参考文档
 ## 参考文档

+ 15 - 21
pom.xml

@@ -13,29 +13,29 @@
     <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
     <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
 
 
     <properties>
     <properties>
-        <revision>5.3.1</revision>
-        <spring-boot.version>3.4.4</spring-boot.version>
+        <revision>5.4.0</revision>
+        <spring-boot.version>3.4.6</spring-boot.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>17</java.version>
         <java.version>17</java.version>
         <mybatis.version>3.5.16</mybatis.version>
         <mybatis.version>3.5.16</mybatis.version>
-        <springdoc.version>2.8.5</springdoc.version>
+        <springdoc.version>2.8.8</springdoc.version>
         <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
         <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
-        <easyexcel.version>4.0.3</easyexcel.version>
+        <fastexcel.version>1.2.0</fastexcel.version>
         <velocity.version>2.3</velocity.version>
         <velocity.version>2.3</velocity.version>
-        <satoken.version>1.40.0</satoken.version>
-        <mybatis-plus.version>3.5.11</mybatis-plus.version>
+        <satoken.version>1.42.0</satoken.version>
+        <mybatis-plus.version>3.5.12</mybatis-plus.version>
         <p6spy.version>3.9.1</p6spy.version>
         <p6spy.version>3.9.1</p6spy.version>
         <hutool.version>5.8.35</hutool.version>
         <hutool.version>5.8.35</hutool.version>
-        <spring-boot-admin.version>3.4.5</spring-boot-admin.version>
+        <spring-boot-admin.version>3.4.7</spring-boot-admin.version>
         <redisson.version>3.45.1</redisson.version>
         <redisson.version>3.45.1</redisson.version>
         <lock4j.version>2.2.7</lock4j.version>
         <lock4j.version>2.2.7</lock4j.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
-        <snailjob.version>1.4.0</snailjob.version>
-        <mapstruct-plus.version>1.4.6</mapstruct-plus.version>
+        <snailjob.version>1.5.0</snailjob.version>
+        <mapstruct-plus.version>1.4.8</mapstruct-plus.version>
         <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
         <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
         <lombok.version>1.18.36</lombok.version>
         <lombok.version>1.18.36</lombok.version>
-        <bouncycastle.version>1.76</bouncycastle.version>
+        <bouncycastle.version>1.80</bouncycastle.version>
         <justauth.version>1.16.7</justauth.version>
         <justauth.version>1.16.7</justauth.version>
         <!-- 离线IP地址定位库 -->
         <!-- 离线IP地址定位库 -->
         <ip2region.version>2.7.0</ip2region.version>
         <ip2region.version>2.7.0</ip2region.version>
@@ -48,8 +48,8 @@
         <fastjson.version>1.2.83</fastjson.version>
         <fastjson.version>1.2.83</fastjson.version>
         <!-- 面向运行时的D-ORM依赖 -->
         <!-- 面向运行时的D-ORM依赖 -->
         <anyline.version>8.7.2-20250101</anyline.version>
         <anyline.version>8.7.2-20250101</anyline.version>
-        <!--工作流配置-->
-        <warm-flow.version>1.6.8</warm-flow.version>
+        <!-- 工作流配置 -->
+        <warm-flow.version>1.7.3</warm-flow.version>
 
 
         <!-- 插件版本 -->
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
@@ -166,9 +166,9 @@
             </dependency>
             </dependency>
 
 
             <dependency>
             <dependency>
-                <groupId>com.alibaba</groupId>
-                <artifactId>easyexcel</artifactId>
-                <version>${easyexcel.version}</version>
+                <groupId>cn.idev.excel</groupId>
+                <artifactId>fastexcel</artifactId>
+                <version>${fastexcel.version}</version>
             </dependency>
             </dependency>
 
 
             <!-- velocity代码生成使用模板 -->
             <!-- velocity代码生成使用模板 -->
@@ -321,12 +321,6 @@
                 <version>${ip2region.version}</version>
                 <version>${ip2region.version}</version>
             </dependency>
             </dependency>
 
 
-            <dependency>
-                <groupId>commons-io</groupId>
-                <artifactId>commons-io</artifactId>
-                <version>2.15.0</version>
-            </dependency>
-
             <dependency>
             <dependency>
                 <groupId>com.alibaba</groupId>
                 <groupId>com.alibaba</groupId>
                 <artifactId>fastjson</artifactId>
                 <artifactId>fastjson</artifactId>

+ 4 - 3
ruoyi-admin/Dockerfile

@@ -11,17 +11,18 @@ RUN mkdir -p /ruoyi/server/logs \
 
 
 WORKDIR /ruoyi/server
 WORKDIR /ruoyi/server
 
 
-ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
 
 
 EXPOSE ${SERVER_PORT}
 EXPOSE ${SERVER_PORT}
+# 暴露 snail job 客户端端口 用于定时任务调度中心通信
+EXPOSE ${SNAIL_PORT}
 
 
 ADD ./target/ruoyi-admin.jar ./app.jar
 ADD ./target/ruoyi-admin.jar ./app.jar
-# 工作流字体文件
-ADD ./zhFonts/ /usr/share/fonts/zhFonts/
 
 
 SHELL ["/bin/bash", "-c"]
 SHELL ["/bin/bash", "-c"]
 
 
 ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
 ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
+           -Dsnail-job.port=${SNAIL_PORT} \
            # 应用名称 如果想区分集群节点监控 改成不同的名称即可
            # 应用名称 如果想区分集群节点监控 改成不同的名称即可
            #-Dskywalking.agent.service_name=ruoyi-server \
            #-Dskywalking.agent.service_name=ruoyi-server \
            #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
            #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \

+ 10 - 12
ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java

@@ -1,9 +1,8 @@
 package org.dromara.web.listener;
 package org.dromara.web.listener;
 
 
-import cn.dev33.satoken.config.SaTokenConfig;
 import cn.dev33.satoken.listener.SaTokenListener;
 import cn.dev33.satoken.listener.SaTokenListener;
-import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.http.useragent.UserAgent;
 import cn.hutool.http.useragent.UserAgent;
 import cn.hutool.http.useragent.UserAgentUtil;
 import cn.hutool.http.useragent.UserAgentUtil;
@@ -35,14 +34,13 @@ import java.time.Duration;
 @Slf4j
 @Slf4j
 public class UserActionListener implements SaTokenListener {
 public class UserActionListener implements SaTokenListener {
 
 
-    private final SaTokenConfig tokenConfig;
     private final SysLoginService loginService;
     private final SysLoginService loginService;
 
 
     /**
     /**
      * 每次登录时触发
      * 每次登录时触发
      */
      */
     @Override
     @Override
-    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
+    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
         UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
         UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
         String ip = ServletUtils.getClientIP();
         String ip = ServletUtils.getClientIP();
         UserOnlineDTO dto = new UserOnlineDTO();
         UserOnlineDTO dto = new UserOnlineDTO();
@@ -52,17 +50,17 @@ public class UserActionListener implements SaTokenListener {
         dto.setOs(userAgent.getOs().getName());
         dto.setOs(userAgent.getOs().getName());
         dto.setLoginTime(System.currentTimeMillis());
         dto.setLoginTime(System.currentTimeMillis());
         dto.setTokenId(tokenValue);
         dto.setTokenId(tokenValue);
-        String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
-        String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
+        String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
+        String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
         dto.setUserName(username);
         dto.setUserName(username);
-        dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
-        dto.setDeviceType(loginModel.getDevice());
-        dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
+        dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
+        dto.setDeviceType(loginParameter.getDeviceType());
+        dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
         TenantHelper.dynamic(tenantId, () -> {
         TenantHelper.dynamic(tenantId, () -> {
-            if(tokenConfig.getTimeout() == -1) {
+            if(loginParameter.getTimeout() == -1) {
                 RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
                 RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
             } else {
             } else {
-                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
+                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
             }
             }
         });
         });
         // 记录登录日志
         // 记录登录日志
@@ -74,7 +72,7 @@ public class UserActionListener implements SaTokenListener {
         logininforEvent.setRequest(ServletUtils.getRequest());
         logininforEvent.setRequest(ServletUtils.getRequest());
         SpringUtils.context().publishEvent(logininforEvent);
         SpringUtils.context().publishEvent(logininforEvent);
         // 更新登录信息
         // 更新登录信息
-        loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
+        loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
         log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
         log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
     }
     }
 
 

+ 1 - 1
ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java

@@ -1,6 +1,6 @@
 package org.dromara.web.service;
 package org.dromara.web.service;
 
 
-import cn.dev33.satoken.secure.BCrypt;
+import cn.hutool.crypto.digest.BCrypt;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.constant.Constants;
 import org.dromara.common.core.constant.Constants;

+ 3 - 3
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java

@@ -1,7 +1,7 @@
 package org.dromara.web.service.impl;
 package org.dromara.web.service.impl;
 
 
-import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
@@ -58,8 +58,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
         });
         });
         loginUser.setClientKey(client.getClientKey());
         loginUser.setClientKey(client.getClientKey());
         loginUser.setDeviceType(client.getDeviceType());
         loginUser.setDeviceType(client.getDeviceType());
-        SaLoginModel model = new SaLoginModel();
-        model.setDevice(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 例如: 后台用户30分钟过期 app用户1天过期
         // 例如: 后台用户30分钟过期 app用户1天过期
         model.setTimeout(client.getTimeout());
         model.setTimeout(client.getTimeout());

+ 4 - 4
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java

@@ -1,9 +1,9 @@
 package org.dromara.web.service.impl;
 package org.dromara.web.service.impl;
 
 
-import cn.dev33.satoken.secure.BCrypt;
-import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.digest.BCrypt;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -70,8 +70,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
         });
         });
         loginUser.setClientKey(client.getClientKey());
         loginUser.setClientKey(client.getClientKey());
         loginUser.setDeviceType(client.getDeviceType());
         loginUser.setDeviceType(client.getDeviceType());
-        SaLoginModel model = new SaLoginModel();
-        model.setDevice(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 例如: 后台用户30分钟过期 app用户1天过期
         // 例如: 后台用户30分钟过期 app用户1天过期
         model.setTimeout(client.getTimeout());
         model.setTimeout(client.getTimeout());

+ 3 - 3
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java

@@ -1,7 +1,7 @@
 package org.dromara.web.service.impl;
 package org.dromara.web.service.impl;
 
 
-import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
@@ -58,8 +58,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
         });
         });
         loginUser.setClientKey(client.getClientKey());
         loginUser.setClientKey(client.getClientKey());
         loginUser.setDeviceType(client.getDeviceType());
         loginUser.setDeviceType(client.getDeviceType());
-        SaLoginModel model = new SaLoginModel();
-        model.setDevice(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 例如: 后台用户30分钟过期 app用户1天过期
         // 例如: 后台用户30分钟过期 app用户1天过期
         model.setTimeout(client.getTimeout());
         model.setTimeout(client.getTimeout());

+ 3 - 3
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java

@@ -1,7 +1,7 @@
 package org.dromara.web.service.impl;
 package org.dromara.web.service.impl;
 
 
-import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
@@ -99,8 +99,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
         });
         });
         loginUser.setClientKey(client.getClientKey());
         loginUser.setClientKey(client.getClientKey());
         loginUser.setDeviceType(client.getDeviceType());
         loginUser.setDeviceType(client.getDeviceType());
-        SaLoginModel model = new SaLoginModel();
-        model.setDevice(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 例如: 后台用户30分钟过期 app用户1天过期
         // 例如: 后台用户30分钟过期 app用户1天过期
         model.setTimeout(client.getTimeout());
         model.setTimeout(client.getTimeout());

+ 3 - 3
ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java

@@ -1,7 +1,7 @@
 package org.dromara.web.service.impl;
 package org.dromara.web.service.impl;
 
 
-import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -76,8 +76,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
         loginUser.setDeviceType(client.getDeviceType());
         loginUser.setDeviceType(client.getDeviceType());
         loginUser.setOpenid(openid);
         loginUser.setOpenid(openid);
 
 
-        SaLoginModel model = new SaLoginModel();
-        model.setDevice(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
         // 例如: 后台用户30分钟过期 app用户1天过期
         // 例如: 后台用户30分钟过期 app用户1天过期
         model.setTimeout(client.getTimeout());
         model.setTimeout(client.getTimeout());

+ 7 - 0
ruoyi-admin/src/main/resources/application-dev.yml

@@ -263,3 +263,10 @@ justauth:
       client-id: 10**********6
       client-id: 10**********6
       client-secret: 1f7d08**********5b7**********29e
       client-secret: 1f7d08**********5b7**********29e
       redirect-uri: ${justauth.address}/social-callback?source=gitlab
       redirect-uri: ${justauth.address}/social-callback?source=gitlab
+    gitea:
+      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+      # gitea 服务器地址
+      server-url: https://demo.gitea.com
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitea

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

@@ -265,3 +265,10 @@ justauth:
       client-id: 10**********6
       client-id: 10**********6
       client-secret: 1f7d08**********5b7**********29e
       client-secret: 1f7d08**********5b7**********29e
       redirect-uri: ${justauth.address}/social-callback?source=gitlab
       redirect-uri: ${justauth.address}/social-callback?source=gitlab
+    gitea:
+      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+      # gitea 服务器地址
+      server-url: https://demo.gitea.com
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitea

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

@@ -21,8 +21,8 @@ server:
       worker: 256
       worker: 256
 
 
 captcha:
 captcha:
+  # 是否启用验证码校验
   enable: true
   enable: true
-  # 页面 <参数设置> 可开启关闭 验证码校验
   # 验证码类型 math 数组计算 char 字符验证
   # 验证码类型 math 数组计算 char 字符验证
   type: MATH
   type: MATH
   # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
   # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
@@ -110,7 +110,7 @@ security:
     - /error
     - /error
     - /*/api-docs
     - /*/api-docs
     - /*/api-docs/**
     - /*/api-docs/**
-    - /warm-flow-ui/token-name
+    - /warm-flow-ui/config
 
 
 # 多租户配置
 # 多租户配置
 tenant:
 tenant:
@@ -177,9 +177,6 @@ springdoc:
   api-docs:
   api-docs:
     # 是否开启接口文档
     # 是否开启接口文档
     enabled: true
     enabled: true
-#  swagger-ui:
-#    # 持久化认证数据
-#    persistAuthorization: true
   info:
   info:
     # 标题
     # 标题
     title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
     title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'

+ 0 - 1
ruoyi-admin/zhFonts/.uuid

@@ -1 +0,0 @@
-3f2ee348-0303-40ca-bf03-03f48d2d2141

BIN
ruoyi-admin/zhFonts/SIMSUN.TTC


+ 0 - 4
ruoyi-admin/zhFonts/fonts.dir

@@ -1,4 +0,0 @@
-3
-SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
-SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
-SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r

+ 0 - 4
ruoyi-admin/zhFonts/fonts.scale

@@ -1,4 +0,0 @@
-3
-SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
-SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
-SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r

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

@@ -14,7 +14,7 @@
     </description>
     </description>
 
 
     <properties>
     <properties>
-        <revision>5.3.1</revision>
+        <revision>5.4.0</revision>
     </properties>
     </properties>
 
 
     <dependencyManagement>
     <dependencyManagement>

+ 3 - 2
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -3,13 +3,14 @@ package org.dromara.common.core.constant;
 /**
 /**
  * 缓存组名称常量
  * 缓存组名称常量
  * <p>
  * <p>
- * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
  * <p>
  * <p>
  * ttl 过期时间 如果设置为0则不过期 默认为0
  * ttl 过期时间 如果设置为0则不过期 默认为0
  * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
  * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
  * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
  * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
+ * local 默认开启本地缓存为1 关闭本地缓存为0
  * <p>
  * <p>
- * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
  *
  *
  * @author Lion Li
  * @author Lion Li
  */
  */

+ 17 - 2
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java

@@ -33,7 +33,22 @@ public class ProcessEvent implements Serializable {
     private String businessId;
     private String businessId;
 
 
     /**
     /**
-     * 状态
+     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+     */
+    private Integer nodeType;
+
+    /**
+     * 流程节点编码
+     */
+    private String nodeCode;
+
+    /**
+     * 流程节点名称
+     */
+    private String nodeName;
+
+    /**
+     * 流程状态
      */
      */
     private String status;
     private String status;
 
 
@@ -45,6 +60,6 @@ public class ProcessEvent implements Serializable {
     /**
     /**
      * 当为true时为申请人节点办理
      * 当为true时为申请人节点办理
      */
      */
-    private boolean submit;
+    private Boolean submit;
 
 
 }
 }

+ 18 - 3
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessCreateTaskEvent.java → ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java

@@ -6,12 +6,12 @@ import java.io.Serial;
 import java.io.Serializable;
 import java.io.Serializable;
 
 
 /**
 /**
- * 流程创建任务监听
+ * 流程任务监听
  *
  *
  * @author may
  * @author may
  */
  */
 @Data
 @Data
-public class ProcessCreateTaskEvent implements Serializable {
+public class ProcessTaskEvent implements Serializable {
 
 
     @Serial
     @Serial
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
@@ -27,10 +27,20 @@ public class ProcessCreateTaskEvent implements Serializable {
     private String flowCode;
     private String flowCode;
 
 
     /**
     /**
-     * 审批节点编码
+     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+     */
+    private Integer nodeType;
+
+    /**
+     * 流程节点编码
      */
      */
     private String nodeCode;
     private String nodeCode;
 
 
+    /**
+     * 流程节点名称
+     */
+    private String nodeName;
+
     /**
     /**
      * 任务id
      * 任务id
      */
      */
@@ -41,4 +51,9 @@ public class ProcessCreateTaskEvent implements Serializable {
      */
      */
     private String businessId;
     private String businessId;
 
 
+    /**
+     * 流程状态
+     */
+    private String status;
+
 }
 }

+ 2 - 2
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java

@@ -18,14 +18,14 @@ public class RegisterBody extends LoginBody {
      * 用户名
      * 用户名
      */
      */
     @NotBlank(message = "{user.username.not.blank}")
     @NotBlank(message = "{user.username.not.blank}")
-    @Length(min = 2, max = 20, message = "{user.username.length.valid}")
+    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
     private String username;
     private String username;
 
 
     /**
     /**
      * 用户密码
      * 用户密码
      */
      */
     @NotBlank(message = "{user.password.not.blank}")
     @NotBlank(message = "{user.password.not.blank}")
-    @Length(min = 5, max = 20, message = "{user.password.length.valid}")
+    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
     private String password;
     private String password;
 
 
     private String userType;
     private String userType;

+ 4 - 2
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java

@@ -5,7 +5,6 @@ import lombok.Getter;
 
 
 /**
 /**
  * 设备类型
  * 设备类型
- * 针对一套 用户体系
  *
  *
  * @author Lion Li
  * @author Lion Li
  */
  */
@@ -29,9 +28,12 @@ public enum DeviceType {
     XCX("xcx"),
     XCX("xcx"),
 
 
     /**
     /**
-     * social第三方端
+     * 第三方社交登录平台
      */
      */
     SOCIAL("social");
     SOCIAL("social");
 
 
+    /**
+     * 设备标识
+     */
     private final String device;
     private final String device;
 }
 }

+ 7 - 5
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java

@@ -1,12 +1,11 @@
 package org.dromara.common.core.enums;
 package org.dromara.common.core.enums;
 
 
-import org.dromara.common.core.utils.StringUtils;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.Getter;
+import org.dromara.common.core.utils.StringUtils;
 
 
 /**
 /**
- * 设备类型
- * 针对多套 用户体系
+ * 用户类型
  *
  *
  * @author Lion Li
  * @author Lion Li
  */
  */
@@ -15,15 +14,18 @@ import lombok.Getter;
 public enum UserType {
 public enum UserType {
 
 
     /**
     /**
-     * pc端
+     * 后台系统用户
      */
      */
     SYS_USER("sys_user"),
     SYS_USER("sys_user"),
 
 
     /**
     /**
-     * app端
+     * 移动客户端用户
      */
      */
     APP_USER("app_user");
     APP_USER("app_user");
 
 
+    /**
+     * 用户类型标识(用于 token、权限识别等)
+     */
     private final String userType;
     private final String userType;
 
 
     public static UserType getUserType(String str) {
     public static UserType getUserType(String str) {

+ 28 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PermissionService.java

@@ -0,0 +1,28 @@
+package org.dromara.common.core.service;
+
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+ * @author Lion Li
+ */
+public interface PermissionService {
+
+    /**
+     * 获取角色数据权限
+     *
+     * @param userId  用户id
+     * @return 角色权限信息
+     */
+    Set<String> getRolePermission(Long userId);
+
+    /**
+     * 获取菜单数据权限
+     *
+     * @param userId  用户id
+     * @return 菜单权限信息
+     */
+    Set<String> getMenuPermission(Long userId);
+
+}

+ 33 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java

@@ -3,6 +3,7 @@ package org.dromara.common.core.service;
 import org.dromara.common.core.domain.dto.UserDTO;
 import org.dromara.common.core.domain.dto.UserDTO;
 
 
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 /**
 /**
  * 通用 用户服务
  * 通用 用户服务
@@ -91,4 +92,36 @@ public interface UserService {
      */
      */
     List<UserDTO> selectUsersByPostIds(List<Long> postIds);
     List<UserDTO> selectUsersByPostIds(List<Long> postIds);
 
 
+    /**
+     * 根据用户 ID 列表查询用户名称映射关系
+     *
+     * @param userIds 用户 ID 列表
+     * @return Map,其中 key 为用户 ID,value 为对应的用户名称
+     */
+    Map<Long, String> selectUserNamesByIds(List<Long> userIds);
+
+    /**
+     * 根据角色 ID 列表查询角色名称映射关系
+     *
+     * @param roleIds 角色 ID 列表
+     * @return Map,其中 key 为角色 ID,value 为对应的角色名称
+     */
+    Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
+
+    /**
+     * 根据部门 ID 列表查询部门名称映射关系
+     *
+     * @param deptIds 部门 ID 列表
+     * @return Map,其中 key 为部门 ID,value 为对应的部门名称
+     */
+    Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
+
+    /**
+     * 根据岗位 ID 列表查询岗位名称映射关系
+     *
+     * @param postIds 岗位 ID 列表
+     * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
+     */
+    Map<Long, String> selectPostNamesByIds(List<Long> postIds);
+
 }
 }

+ 10 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java

@@ -78,9 +78,18 @@ public interface WorkflowService {
 
 
     /**
     /**
      * 办理任务
      * 办理任务
+     * 系统后台发起审批 无用户信息 需要忽略权限
+     * completeTask.getVariables().put("ignore", true);
      *
      *
      * @param completeTask 参数
      * @param completeTask 参数
-     * @return 结果
      */
      */
     boolean completeTask(CompleteTaskDTO completeTask);
     boolean completeTask(CompleteTaskDTO completeTask);
+
+    /**
+     * 办理任务
+     *
+     * @param taskId  任务ID
+     * @param message 办理意见
+     */
+    boolean completeTask(Long taskId, String message);
 }
 }

+ 19 - 6
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java

@@ -175,14 +175,27 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
     }
     }
 
 
     /**
     /**
-     * 计算两个日期之间的天数差(以毫秒为单位
+     * 计算两个时间之间的时间差,并以指定单位返回(绝对值
      *
      *
-     * @param date1 第一个日期
-     * @param date2 第二个日期
-     * @return 两个日期之间的天数差的绝对值
+     * @param start 起始时间
+     * @param end   结束时间
+     * @param unit  所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS)
+     * @return 时间差的绝对值,以指定单位表示
      */
      */
-    public static int differentDaysByMillisecond(Date date1, Date date2) {
-        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+    public static long difference(Date start, Date end, TimeUnit unit) {
+        // 计算时间差,单位为毫秒,取绝对值避免负数
+        long diffInMillis = Math.abs(end.getTime() - start.getTime());
+
+        // 根据目标单位转换时间差
+        return switch (unit) {
+            case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
+            case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
+            case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
+            case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
+            case MILLISECONDS -> diffInMillis;
+            case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
+            case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
+        };
     }
     }
 
 
     /**
     /**

+ 84 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/NetUtils.java

@@ -0,0 +1,84 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.lang.PatternPool;
+import cn.hutool.core.net.NetUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.regex.RegexUtils;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 增强网络相关工具类
+ *
+ * @author 秋辞未寒
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class NetUtils extends NetUtil {
+
+    /**
+     * 判断是否为IPv6地址
+     *
+     * @param ip IP地址
+     * @return 是否为IPv6地址
+     */
+    public static boolean isIPv6(String ip) {
+        try {
+            // 判断是否为IPv6地址
+            return InetAddress.getByName(ip) instanceof Inet6Address;
+        } catch (UnknownHostException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 判断IPv6地址是否为内网地址
+     * <br><br>
+     * 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
+     * <pre>
+     * 通配符地址 0:0:0:0:0:0:0:0
+     * 链路本地地址 fe80::/10
+     * 唯一本地地址 fec0::/10
+     * 环回地址 ::1
+     * </pre>
+     *
+     * @param ip IP地址
+     * @return 是否为内网地址
+     */
+    public static boolean isInnerIPv6(String ip) {
+        try {
+            // 判断是否为IPv6地址
+            if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
+                // isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
+                // isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
+                // isLoopbackAddress 判断是否为环回地址,与IPv4的 127.0.0.1 同理,用于表示本机
+                // isSiteLocalAddress 判断是否为本地站点地址,IPv6唯一本地地址(Unique Local Addresses,简称ULA)
+                if (inet6Address.isAnyLocalAddress()
+                    || inet6Address.isLinkLocalAddress()
+                    || inet6Address.isLoopbackAddress()
+                    || inet6Address.isSiteLocalAddress()) {
+                    return true;
+                }
+            }
+        } catch (UnknownHostException e) {
+            // 注意,isInnerIPv6方法和isIPv6方法的适用范围不同,所以此处不能忽略其异常信息。
+            throw new IllegalArgumentException("Invalid IPv6 address!", e);
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否为IPv4地址
+     *
+     * @param ip IP地址
+     * @return 是否为IPv4地址
+     */
+    public static boolean isIPv4(String ip) {
+        return RegexUtils.isMatch(PatternPool.IPV4, ip);
+    }
+
+}

+ 23 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java

@@ -6,6 +6,7 @@ import cn.hutool.core.lang.Validator;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
 import org.springframework.util.AntPathMatcher;
 import org.springframework.util.AntPathMatcher;
 
 
+import java.nio.charset.Charset;
 import java.util.*;
 import java.util.*;
 import java.util.function.Function;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
@@ -339,4 +340,26 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
         return false;
         return false;
     }
     }
 
 
+    /**
+     * 将字符串从源字符集转换为目标字符集
+     *
+     * @param input       原始字符串
+     * @param fromCharset 源字符集
+     * @param toCharset   目标字符集
+     * @return 转换后的字符串
+     */
+    public static String convert(String input, Charset fromCharset, Charset toCharset) {
+        if (isBlank(input)) {
+            return input;
+        }
+        try {
+            // 从源字符集获取字节
+            byte[] bytes = input.getBytes(fromCharset);
+            // 使用目标字符集解码
+            return new String(bytes, toCharset);
+        } catch (Exception e) {
+            return input;
+        }
+    }
+
 }
 }

+ 15 - 5
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java

@@ -1,11 +1,11 @@
 package org.dromara.common.core.utils.ip;
 package org.dromara.common.core.utils.ip;
 
 
-import cn.hutool.core.net.NetUtil;
 import cn.hutool.http.HtmlUtil;
 import cn.hutool.http.HtmlUtil;
-import org.dromara.common.core.utils.StringUtils;
 import lombok.AccessLevel;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.NetUtils;
+import org.dromara.common.core.utils.StringUtils;
 
 
 /**
 /**
  * 获取地址类
  * 获取地址类
@@ -20,14 +20,24 @@ public class AddressUtils {
     public static final String UNKNOWN = "XX XX";
     public static final String UNKNOWN = "XX XX";
 
 
     public static String getRealAddressByIP(String ip) {
     public static String getRealAddressByIP(String ip) {
-        if (StringUtils.isBlank(ip)) {
+        // 处理空串并过滤HTML标签
+        ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
+        boolean isIPv6 = NetUtils.isIPv6(ip);
+        // 判断是否为IPv4或IPv6,如果不是则返回未知地址
+        if (!NetUtils.isIPv4(ip) && !isIPv6) {
             return UNKNOWN;
             return UNKNOWN;
         }
         }
         // 内网不查询
         // 内网不查询
-        ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
-        if (NetUtil.isInnerIP(ip)) {
+        if (NetUtils.isInnerIPv6(ip) || NetUtils.isInnerIP(ip)) {
             return "内网IP";
             return "内网IP";
         }
         }
+        // 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
+        if (isIPv6) {
+            log.warn("ip2region不支持IPV6地址解析:{}", ip);
+            // 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
+            return "未知";
+        }
         return RegionUtils.getCityInfo(ip);
         return RegionUtils.getCityInfo(ip);
     }
     }
+
 }
 }

+ 14 - 30
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java

@@ -1,15 +1,12 @@
 package org.dromara.common.core.utils.ip;
 package org.dromara.common.core.utils.ip;
 
 
-import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.resource.ClassPathResource;
-import cn.hutool.core.util.ObjectUtil;
-import org.dromara.common.core.exception.ServiceException;
-import org.dromara.common.core.utils.file.FileUtils;
+import cn.hutool.core.io.resource.NoResourceException;
+import cn.hutool.core.io.resource.ResourceUtil;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
 import org.lionsoul.ip2region.xdb.Searcher;
 import org.lionsoul.ip2region.xdb.Searcher;
 
 
-import java.io.File;
-
 /**
 /**
  * 根据ip地址定位工具类,离线方式
  * 根据ip地址定位工具类,离线方式
  * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
  * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
@@ -19,31 +16,19 @@ import java.io.File;
 @Slf4j
 @Slf4j
 public class RegionUtils {
 public class RegionUtils {
 
 
+    // IP地址库文件名称
+    public static final String IP_XDB_FILENAME = "ip2region.xdb";
+
     private static final Searcher SEARCHER;
     private static final Searcher SEARCHER;
 
 
     static {
     static {
-        String fileName = "/ip2region.xdb";
-        File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
-        if (!FileUtils.exist(existFile)) {
-            ClassPathResource fileStream = new ClassPathResource(fileName);
-            if (ObjectUtil.isEmpty(fileStream.getStream())) {
-                throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
-            }
-            FileUtils.writeFromStream(fileStream.getStream(), existFile);
-        }
-
-        String dbPath = existFile.getPath();
-
-        // 1、从 dbPath 加载整个 xdb 到内存。
-        byte[] cBuff;
-        try {
-            cBuff = Searcher.loadContentFromFile(dbPath);
-        } catch (Exception e) {
-            throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
-        }
-        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
         try {
         try {
-            SEARCHER = Searcher.newWithBuffer(cBuff);
+            // 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
+            // 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。
+            SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
+            log.info("RegionUtils初始化成功,加载IP地址库数据成功!");
+        } catch (NoResourceException e) {
+            throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
         } catch (Exception e) {
         } catch (Exception e) {
             throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
             throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
         }
         }
@@ -54,9 +39,8 @@ public class RegionUtils {
      */
      */
     public static String getCityInfo(String ip) {
     public static String getCityInfo(String ip) {
         try {
         try {
-            ip = ip.trim();
             // 3、执行查询
             // 3、执行查询
-            String region = SEARCHER.search(ip);
+            String region = SEARCHER.search(StringUtils.trim(ip));
             return region.replace("0|", "").replace("|0", "");
             return region.replace("0|", "").replace("|0", "");
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("IP地址离线获取城市异常 {}", ip);
             log.error("IP地址离线获取城市异常 {}", ip);

+ 40 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPattern.java

@@ -0,0 +1,40 @@
+package org.dromara.common.core.validate.dicts;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 字典项校验注解
+ *
+ * @author AprilWind
+ */
+@Constraint(validatedBy = DictPatternValidator.class)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DictPattern {
+
+    /**
+     * 字典类型,如 "sys_user_sex"
+     */
+    String dictType();
+
+    /**
+     * 分隔符
+     */
+    String separator();
+
+    /**
+     * 默认校验失败提示信息
+     */
+    String message() default "字典值无效";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}

+ 55 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPatternValidator.java

@@ -0,0 +1,55 @@
+package org.dromara.common.core.validate.dicts;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * 自定义字典值校验器
+ *
+ * @author AprilWind
+ */
+public class DictPatternValidator implements ConstraintValidator<DictPattern, String> {
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 分隔符
+     */
+    private String separator = ",";
+
+    /**
+     * 初始化校验器,提取注解上的字典类型
+     *
+     * @param annotation 注解实例
+     */
+    @Override
+    public void initialize(DictPattern annotation) {
+        this.dictType = annotation.dictType();
+        if (StringUtils.isNotBlank(annotation.separator())) {
+            this.separator = annotation.separator();
+        }
+    }
+
+    /**
+     * 校验字段值是否为指定字典类型中的合法值
+     *
+     * @param value   被校验的字段值
+     * @param context 校验上下文(可用于构建错误信息)
+     * @return true 表示校验通过(合法字典值),false 表示不通过
+     */
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        if (StringUtils.isBlank(dictType) || StringUtils.isBlank(value)) {
+            return false;
+        }
+        String dictLabel = SpringUtils.getBean(DictService.class).getDictLabel(dictType, value, separator);
+        return StringUtils.isNotBlank(dictLabel);
+    }
+
+}

+ 37 - 37
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java

@@ -1,37 +1,37 @@
-package org.dromara.common.core.validate.enumd;
-
-import jakarta.validation.ConstraintValidator;
-import jakarta.validation.ConstraintValidatorContext;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.core.utils.reflect.ReflectUtils;
-
-/**
- * 自定义枚举校验注解实现
- *
- * @author 秋辞未寒
- * @date 2024-12-09
- */
-public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
-
-    private EnumPattern annotation;;
-
-    @Override
-    public void initialize(EnumPattern annotation) {
-        ConstraintValidator.super.initialize(annotation);
-        this.annotation = annotation;
-    }
-
-    @Override
-    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
-        if (StringUtils.isNotBlank(value)) {
-            String fieldName = annotation.fieldName();
-            for (Object e : annotation.type().getEnumConstants()) {
-                if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-}
+package org.dromara.common.core.validate.enumd;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+
+/**
+ * 自定义枚举校验注解实现
+ *
+ * @author 秋辞未寒
+ * @date 2024-12-09
+ */
+public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
+
+    private EnumPattern annotation;
+
+    @Override
+    public void initialize(EnumPattern annotation) {
+        ConstraintValidator.super.initialize(annotation);
+        this.annotation = annotation;
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StringUtils.isNotBlank(value)) {
+            String fieldName = annotation.fieldName();
+            for (Object e : annotation.type().getEnumConstants()) {
+                if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+}

+ 1 - 1
ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java

@@ -30,7 +30,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 /**
 /**
- * Swagger 文档配置
+ * 接口文档配置
  *
  *
  * @author Lion Li
  * @author Lion Li
  */
  */

+ 4 - 4
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java

@@ -108,7 +108,7 @@ public class EncryptUtils {
     }
     }
 
 
     /**
     /**
-     * sm4加密
+     * SM4加密(Base64编码)
      *
      *
      * @param data     待加密数据
      * @param data     待加密数据
      * @param password 秘钥字符串
      * @param password 秘钥字符串
@@ -127,11 +127,11 @@ public class EncryptUtils {
     }
     }
 
 
     /**
     /**
-     * sm4加密
+     * SM4加密(Hex编码)
      *
      *
      * @param data     待加密数据
      * @param data     待加密数据
      * @param password 秘钥字符串
      * @param password 秘钥字符串
-     * @return 加密后字符串, 采用Base64编码
+     * @return 加密后字符串, 采用Hex编码
      */
      */
     public static String encryptBySm4Hex(String data, String password) {
     public static String encryptBySm4Hex(String data, String password) {
         if (StrUtil.isBlank(password)) {
         if (StrUtil.isBlank(password)) {
@@ -148,7 +148,7 @@ public class EncryptUtils {
     /**
     /**
      * sm4解密
      * sm4解密
      *
      *
-     * @param data     待解密数据
+     * @param data     待解密数据(可以是Base64或Hex编码)
      * @param password 秘钥字符串
      * @param password 秘钥字符串
      * @return 解密后字符串
      * @return 解密后字符串
      */
      */

+ 2 - 2
ruoyi-common/ruoyi-common-excel/pom.xml

@@ -22,8 +22,8 @@
         </dependency>
         </dependency>
 
 
         <dependency>
         <dependency>
-            <groupId>com.alibaba</groupId>
-            <artifactId>easyexcel</artifactId>
+            <groupId>cn.idev.excel</groupId>
+            <artifactId>fastexcel</artifactId>
         </dependency>
         </dependency>
     </dependencies>
     </dependencies>
 
 

+ 6 - 6
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java

@@ -2,12 +2,12 @@ package org.dromara.common.excel.convert;
 
 
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 
 
 import java.math.BigDecimal;
 import java.math.BigDecimal;

+ 6 - 6
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java

@@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
 import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
 import org.dromara.common.excel.annotation.ExcelDictFormat;
 import org.dromara.common.excel.annotation.ExcelDictFormat;
 import org.dromara.common.core.service.DictService;
 import org.dromara.common.core.service.DictService;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.SpringUtils;

+ 6 - 6
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java

@@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
 import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
 import org.dromara.common.core.utils.reflect.ReflectUtils;
 import org.dromara.common.core.utils.reflect.ReflectUtils;
 import org.dromara.common.excel.annotation.ExcelEnumFormat;
 import org.dromara.common.excel.annotation.ExcelEnumFormat;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;

+ 12 - 6
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java

@@ -3,11 +3,11 @@ package org.dromara.common.excel.core;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.excel.annotation.ExcelProperty;
-import com.alibaba.excel.metadata.Head;
-import com.alibaba.excel.write.handler.WorkbookWriteHandler;
-import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
-import com.alibaba.excel.write.merge.AbstractMergeStrategy;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.metadata.Head;
+import cn.idev.excel.write.handler.WorkbookWriteHandler;
+import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
+import cn.idev.excel.write.merge.AbstractMergeStrategy;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.Data;
 import lombok.SneakyThrows;
 import lombok.SneakyThrows;
@@ -112,7 +112,13 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
                         }
                         }
                         map.put(field, new RepeatCell(val, i));
                         map.put(field, new RepeatCell(val, i));
                     } else if (i == list.size() - 1) {
                     } else if (i == list.size() - 1) {
-                        if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
+                        if (!isMerge(list, i, field)) {
+                            // 如果最后一行不能合并,检查之前的数据是否需要合并
+                            if (i - repeatCell.getCurrent() > 1) {
+                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
+                            }
+                        } else if (i > repeatCell.getCurrent()) {
+                            // 如果最后一行可以合并,则直接合并到最后
                             cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
                             cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
                         }
                         }
                     } else if (!isMerge(list, i, field)) {
                     } else if (!isMerge(list, i, field)) {

+ 4 - 4
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java

@@ -1,10 +1,10 @@
 package org.dromara.common.excel.core;
 package org.dromara.common.excel.core;
 
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.excel.context.AnalysisContext;
-import com.alibaba.excel.event.AnalysisEventListener;
-import com.alibaba.excel.exception.ExcelAnalysisException;
-import com.alibaba.excel.exception.ExcelDataConvertException;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import cn.idev.excel.exception.ExcelAnalysisException;
+import cn.idev.excel.exception.ExcelDataConvertException;
 import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.core.utils.ValidatorUtils;
 import org.dromara.common.core.utils.ValidatorUtils;
 import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.json.utils.JsonUtils;

+ 18 - 16
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java

@@ -5,12 +5,12 @@ import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.excel.metadata.FieldCache;
-import com.alibaba.excel.metadata.FieldWrapper;
-import com.alibaba.excel.util.ClassUtils;
-import com.alibaba.excel.write.handler.SheetWriteHandler;
-import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
-import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
+import cn.idev.excel.metadata.FieldCache;
+import cn.idev.excel.metadata.FieldWrapper;
+import cn.idev.excel.util.ClassUtils;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddressList;
 import org.apache.poi.ss.util.CellRangeAddressList;
@@ -175,7 +175,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
         List<String> firstOptions = options.getOptions();
         List<String> firstOptions = options.getOptions();
         Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
         Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
 
 
-        // 采用按行填充数据的方式,避免EasyExcel出现数据无法写入的问题
+        // 采用按行填充数据的方式,避免出现数据无法写入的问题
         // Attempting to write a row in the range that is already written to disk
         // Attempting to write a row in the range that is already written to disk
 
 
         // 使用ArrayList记载数据,防止乱序
         // 使用ArrayList记载数据,防止乱序
@@ -291,9 +291,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
      * @param value    下拉选可选值
      * @param value    下拉选可选值
      */
      */
     private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
     private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
+        //由于poi的写出相关问题,超过100个会被临时写进硬盘,导致后续内存合并会出Attempting to write a row[] in the range [] that is already written to disk
+        String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
         // 创建下拉数据表
         // 创建下拉数据表
-        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
-            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
+        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
+            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
         // 将下拉表隐藏
         // 将下拉表隐藏
         workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
         workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
         // 完善纵向的一级选项数据表
         // 完善纵向的一级选项数据表
@@ -302,9 +304,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
             // 获取每一选项行,如果没有则创建
             // 获取每一选项行,如果没有则创建
             Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
             Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
                 .orElseGet(() -> simpleDataSheet.createRow(finalI));
                 .orElseGet(() -> simpleDataSheet.createRow(finalI));
-            // 获取本级选项对应的选项列,如果没有则创建
-            Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
-                .orElseGet(() -> row.createCell(currentOptionsColumnIndex));
+            // 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列
+            Cell cell = Optional.ofNullable(row.getCell(0))
+                .orElseGet(() -> row.createCell(0));
             // 设置值
             // 设置值
             cell.setCellValue(value.get(i));
             cell.setCellValue(value.get(i));
         }
         }
@@ -312,13 +314,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
         // 创建名称管理器
         // 创建名称管理器
         Name name = workbook.createName();
         Name name = workbook.createName();
         // 设置名称管理器的别名
         // 设置名称管理器的别名
-        String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
+        String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex);
         name.setNameName(nameName);
         name.setNameName(nameName);
         // 以纵向第一列创建一级下拉拼接引用位置
         // 以纵向第一列创建一级下拉拼接引用位置
         String function = String.format("%s!$%s$1:$%s$%d",
         String function = String.format("%s!$%s$1:$%s$%d",
-            OPTIONS_SHEET_NAME,
-            getExcelColumnName(currentOptionsColumnIndex),
-            getExcelColumnName(currentOptionsColumnIndex),
+            tmpOptionsSheetName,
+            getExcelColumnName(0),
+            getExcelColumnName(0),
             value.size());
             value.size());
         // 设置名称管理器的引用位置
         // 设置名称管理器的引用位置
         name.setRefersToFormula(function);
         name.setRefersToFormula(function);

+ 1 - 1
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java

@@ -1,6 +1,6 @@
 package org.dromara.common.excel.core;
 package org.dromara.common.excel.core;
 
 
-import com.alibaba.excel.read.listener.ReadListener;
+import cn.idev.excel.read.listener.ReadListener;
 
 
 /**
 /**
  * Excel 导入监听
  * Excel 导入监听

+ 9 - 9
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java

@@ -1,15 +1,15 @@
 package org.dromara.common.excel.handler;
 package org.dromara.common.excel.handler;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
-import com.alibaba.excel.metadata.data.DataFormatData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.util.StyleUtil;
-import com.alibaba.excel.write.handler.CellWriteHandler;
-import com.alibaba.excel.write.handler.SheetWriteHandler;
-import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
-import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
-import com.alibaba.excel.write.metadata.style.WriteCellStyle;
-import com.alibaba.excel.write.metadata.style.WriteFont;
+import cn.idev.excel.metadata.data.DataFormatData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.util.StyleUtil;
+import cn.idev.excel.write.handler.CellWriteHandler;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.style.WriteCellStyle;
+import cn.idev.excel.write.metadata.style.WriteFont;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
 import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
 import org.apache.poi.xssf.usermodel.XSSFRichTextString;
 import org.apache.poi.xssf.usermodel.XSSFRichTextString;

+ 26 - 26
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java

@@ -3,13 +3,13 @@ package org.dromara.common.excel.utils;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.resource.ClassPathResource;
 import cn.hutool.core.io.resource.ClassPathResource;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.IdUtil;
-import com.alibaba.excel.EasyExcel;
-import com.alibaba.excel.ExcelWriter;
-import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
-import com.alibaba.excel.write.metadata.WriteSheet;
-import com.alibaba.excel.write.metadata.fill.FillConfig;
-import com.alibaba.excel.write.metadata.fill.FillWrapper;
-import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import cn.idev.excel.FastExcel;
+import cn.idev.excel.ExcelWriter;
+import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
+import cn.idev.excel.write.metadata.WriteSheet;
+import cn.idev.excel.write.metadata.fill.FillConfig;
+import cn.idev.excel.write.metadata.fill.FillWrapper;
+import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 import jakarta.servlet.ServletOutputStream;
 import jakarta.servlet.ServletOutputStream;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.AccessLevel;
 import lombok.AccessLevel;
@@ -43,7 +43,7 @@ public class ExcelUtil {
      * @return 转换后集合
      * @return 转换后集合
      */
      */
     public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
     public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
-        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
+        return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
     }
     }
 
 
 
 
@@ -57,7 +57,7 @@ public class ExcelUtil {
      */
      */
     public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
     public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
         DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
         DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
-        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        FastExcel.read(is, clazz, listener).sheet().doRead();
         return listener.getExcelResult();
         return listener.getExcelResult();
     }
     }
 
 
@@ -70,7 +70,7 @@ public class ExcelUtil {
      * @return 转换后集合
      * @return 转换后集合
      */
      */
     public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
     public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
-        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        FastExcel.read(is, clazz, listener).sheet().doRead();
         return listener.getExcelResult();
         return listener.getExcelResult();
     }
     }
 
 
@@ -186,7 +186,7 @@ public class ExcelUtil {
      */
      */
     public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
     public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
                                        OutputStream os, List<DropDownOptions> options) {
                                        OutputStream os, List<DropDownOptions> options) {
-        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
+        ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz)
             .autoCloseStream(false)
             .autoCloseStream(false)
             // 自动适配
             // 自动适配
             .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
             .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
@@ -215,6 +215,9 @@ public class ExcelUtil {
      */
      */
     public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
     public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
         try {
         try {
+            if (CollUtil.isEmpty(data)) {
+                throw new IllegalArgumentException("数据为空");
+            }
             resetResponse(filename, response);
             resetResponse(filename, response);
             ServletOutputStream os = response.getOutputStream();
             ServletOutputStream os = response.getOutputStream();
             exportTemplate(data, templatePath, os);
             exportTemplate(data, templatePath, os);
@@ -233,18 +236,15 @@ public class ExcelUtil {
      * @param os           输出流
      * @param os           输出流
      */
      */
     public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
     public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
-        if (CollUtil.isEmpty(data)) {
-            throw new IllegalArgumentException("数据为空");
-        }
         ClassPathResource templateResource = new ClassPathResource(templatePath);
         ClassPathResource templateResource = new ClassPathResource(templatePath);
-        ExcelWriter excelWriter = EasyExcel.write(os)
+        ExcelWriter excelWriter = FastExcel.write(os)
             .withTemplate(templateResource.getStream())
             .withTemplate(templateResource.getStream())
             .autoCloseStream(false)
             .autoCloseStream(false)
             // 大数值自动转换 防止失真
             // 大数值自动转换 防止失真
             .registerConverter(new ExcelBigNumberConvert())
             .registerConverter(new ExcelBigNumberConvert())
             .registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
             .registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
             .build();
             .build();
-        WriteSheet writeSheet = EasyExcel.writerSheet().build();
+        WriteSheet writeSheet = FastExcel.writerSheet().build();
         FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
         FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
         // 单表多数据导出 模板格式为 {.属性}
         // 单表多数据导出 模板格式为 {.属性}
         for (T d : data) {
         for (T d : data) {
@@ -265,6 +265,9 @@ public class ExcelUtil {
      */
      */
     public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
     public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
         try {
         try {
+            if (CollUtil.isEmpty(data)) {
+                throw new IllegalArgumentException("数据为空");
+            }
             resetResponse(filename, response);
             resetResponse(filename, response);
             ServletOutputStream os = response.getOutputStream();
             ServletOutputStream os = response.getOutputStream();
             exportTemplateMultiList(data, templatePath, os);
             exportTemplateMultiList(data, templatePath, os);
@@ -285,6 +288,9 @@ public class ExcelUtil {
      */
      */
     public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
     public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
         try {
         try {
+            if (CollUtil.isEmpty(data)) {
+                throw new IllegalArgumentException("数据为空");
+            }
             resetResponse(filename, response);
             resetResponse(filename, response);
             ServletOutputStream os = response.getOutputStream();
             ServletOutputStream os = response.getOutputStream();
             exportTemplateMultiSheet(data, templatePath, os);
             exportTemplateMultiSheet(data, templatePath, os);
@@ -303,17 +309,14 @@ public class ExcelUtil {
      * @param os           输出流
      * @param os           输出流
      */
      */
     public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
     public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
-        if (CollUtil.isEmpty(data)) {
-            throw new IllegalArgumentException("数据为空");
-        }
         ClassPathResource templateResource = new ClassPathResource(templatePath);
         ClassPathResource templateResource = new ClassPathResource(templatePath);
-        ExcelWriter excelWriter = EasyExcel.write(os)
+        ExcelWriter excelWriter = FastExcel.write(os)
             .withTemplate(templateResource.getStream())
             .withTemplate(templateResource.getStream())
             .autoCloseStream(false)
             .autoCloseStream(false)
             // 大数值自动转换 防止失真
             // 大数值自动转换 防止失真
             .registerConverter(new ExcelBigNumberConvert())
             .registerConverter(new ExcelBigNumberConvert())
             .build();
             .build();
-        WriteSheet writeSheet = EasyExcel.writerSheet().build();
+        WriteSheet writeSheet = FastExcel.writerSheet().build();
         for (Map.Entry<String, Object> map : data.entrySet()) {
         for (Map.Entry<String, Object> map : data.entrySet()) {
             // 设置列表后续还有数据
             // 设置列表后续还有数据
             FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
             FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@@ -337,18 +340,15 @@ public class ExcelUtil {
      * @param os           输出流
      * @param os           输出流
      */
      */
     public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
     public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
-        if (CollUtil.isEmpty(data)) {
-            throw new IllegalArgumentException("数据为空");
-        }
         ClassPathResource templateResource = new ClassPathResource(templatePath);
         ClassPathResource templateResource = new ClassPathResource(templatePath);
-        ExcelWriter excelWriter = EasyExcel.write(os)
+        ExcelWriter excelWriter = FastExcel.write(os)
             .withTemplate(templateResource.getStream())
             .withTemplate(templateResource.getStream())
             .autoCloseStream(false)
             .autoCloseStream(false)
             // 大数值自动转换 防止失真
             // 大数值自动转换 防止失真
             .registerConverter(new ExcelBigNumberConvert())
             .registerConverter(new ExcelBigNumberConvert())
             .build();
             .build();
         for (int i = 0; i < data.size(); i++) {
         for (int i = 0; i < data.size(); i++) {
-            WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
+            WriteSheet writeSheet = FastExcel.writerSheet(i).build();
             for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
             for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
                 // 设置列表后续还有数据
                 // 设置列表后续还有数据
                 FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
                 FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

+ 4 - 1
ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java

@@ -4,8 +4,9 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
 import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
 import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
 import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
-import org.dromara.common.json.handler.BigNumberSerializer;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.json.handler.BigNumberSerializer;
+import org.dromara.common.json.handler.CustomDateDeserializer;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
 import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
 import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@@ -15,6 +16,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.BigInteger;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatter;
+import java.util.Date;
 import java.util.TimeZone;
 import java.util.TimeZone;
 
 
 /**
 /**
@@ -38,6 +40,7 @@ public class JacksonConfig {
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
             javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
             javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
             javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
             javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+            javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
             builder.modules(javaTimeModule);
             builder.modules(javaTimeModule);
             builder.timeZone(TimeZone.getDefault());
             builder.timeZone(TimeZone.getDefault());
             log.info("初始化 jackson 配置");
             log.info("初始化 jackson 配置");

+ 31 - 0
ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java

@@ -0,0 +1,31 @@
+package org.dromara.common.json.handler;
+
+import cn.hutool.core.date.DateUtil;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * 自定义 Date 类型反序列化处理器(支持多种格式)
+ *
+ * @author AprilWind
+ */
+public class CustomDateDeserializer extends JsonDeserializer<Date> {
+
+    /**
+     * 反序列化逻辑:将字符串转换为 Date 对象
+     *
+     * @param p    JSON 解析器,用于获取字符串值
+     * @param ctxt 上下文环境(可用于获取更多配置)
+     * @return 转换后的 Date 对象,若为空字符串返回 null
+     * @throws IOException 当字符串格式非法或转换失败时抛出
+     */
+    @Override
+    public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+        return DateUtil.parse(p.getText());
+    }
+
+}

+ 16 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java

@@ -1,5 +1,6 @@
 package org.dromara.common.mybatis.core.page;
 package org.dromara.common.mybatis.core.page;
 
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.http.HttpStatus;
 import cn.hutool.http.HttpStatus;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import lombok.Data;
 import lombok.Data;
@@ -88,4 +89,19 @@ public class TableDataInfo<T> implements Serializable {
         return rspData;
         return rspData;
     }
     }
 
 
+    /**
+     * 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
+     *
+     * @param list 原始数据列表(全部数据)
+     * @param page 分页参数对象(包含当前页码、每页大小等)
+     * @return 构造好的分页结果 TableDataInfo<T>
+     */
+    public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
+        if (CollUtil.isEmpty(list)) {
+            return TableDataInfo.build();
+        }
+        List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
+        return new TableDataInfo<>(pageList, list.size());
+    }
+
 }
 }

+ 5 - 4
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java

@@ -1,5 +1,6 @@
 package org.dromara.common.mybatis.handler;
 package org.dromara.common.mybatis.handler;
 
 
+import cn.hutool.http.HttpStatus;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.R;
@@ -25,7 +26,7 @@ public class MybatisExceptionHandler {
     public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
     public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
         String requestURI = request.getRequestURI();
         String requestURI = request.getRequestURI();
         log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
         log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
-        return R.fail("数据库中已存在该记录,请联系管理员确认");
+        return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认");
     }
     }
 
 
     /**
     /**
@@ -35,12 +36,12 @@ public class MybatisExceptionHandler {
     public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
     public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
         String requestURI = request.getRequestURI();
         String requestURI = request.getRequestURI();
         String message = e.getMessage();
         String message = e.getMessage();
-        if (StringUtils.contains("CannotFindDataSourceException", message)) {
+        if (StringUtils.contains(message, "CannotFindDataSourceException")) {
             log.error("请求地址'{}', 未找到数据源", requestURI);
             log.error("请求地址'{}', 未找到数据源", requestURI);
-            return R.fail("未找到数据源,请联系管理员确认");
+            return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
         }
         }
         log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
         log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
-        return R.fail(message);
+        return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message);
     }
     }
 
 
 }
 }

+ 16 - 6
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java

@@ -145,18 +145,25 @@ public class PlusSpringCacheManager implements CacheManager {
         if (array.length > 3) {
         if (array.length > 3) {
             config.setMaxSize(Integer.parseInt(array[3]));
             config.setMaxSize(Integer.parseInt(array[3]));
         }
         }
+        int local = 1;
+        if (array.length > 4) {
+            local = Integer.parseInt(array[4]);
+        }
 
 
         if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
         if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
-            return createMap(name, config);
+            return createMap(name, config, local);
         }
         }
 
 
-        return createMapCache(name, config);
+        return createMapCache(name, config, local);
     }
     }
 
 
-    private Cache createMap(String name, CacheConfig config) {
+    private Cache createMap(String name, CacheConfig config, int local) {
         RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
         RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
 
 
-        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, allowNullValues));
+        Cache cache = new RedissonCache(map, allowNullValues);
+        if (local == 1) {
+            cache = new CaffeineCacheDecorator(name, cache);
+        }
         if (transactionAware) {
         if (transactionAware) {
             cache = new TransactionAwareCacheDecorator(cache);
             cache = new TransactionAwareCacheDecorator(cache);
         }
         }
@@ -167,10 +174,13 @@ public class PlusSpringCacheManager implements CacheManager {
         return cache;
         return cache;
     }
     }
 
 
-    private Cache createMapCache(String name, CacheConfig config) {
+    private Cache createMapCache(String name, CacheConfig config, int local) {
         RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
         RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
 
 
-        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, config, allowNullValues));
+        Cache cache = new RedissonCache(map, config, allowNullValues);
+        if (local == 1) {
+            cache = new CaffeineCacheDecorator(name, cache);
+        }
         if (transactionAware) {
         if (transactionAware) {
             cache = new TransactionAwareCacheDecorator(cache);
             cache = new TransactionAwareCacheDecorator(cache);
         }
         }

+ 180 - 165
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java

@@ -1,165 +1,180 @@
-package org.dromara.common.redis.utils;
-
-import cn.hutool.core.date.DatePattern;
-import cn.hutool.core.date.DateUtil;
-import org.dromara.common.core.utils.SpringUtils;
-import org.dromara.common.core.utils.StringUtils;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-import org.redisson.api.RIdGenerator;
-import org.redisson.api.RedissonClient;
-
-import java.time.Duration;
-
-/**
- * 发号器工具类
- *
- * @author 秋辞未寒
- * @date 2024-12-10
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class SequenceUtils {
-
-    /**
-     * 默认初始值
-     */
-    public static final Long DEFAULT_INIT_VALUE = 1L;
-    /**
-     * 默认步长
-     */
-    public static final Long DEFAULT_STEP_VALUE = 1L;
-    /**
-     * 默认过期时间-天
-     */
-    public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
-    /**
-     * 默认过期时间-分钟
-     */
-    public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
-
-    /**
-     * 获取Redisson客户端实例
-     */
-    private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
-
-    /**
-     * 获取ID生成器
-     *
-     * @param key        业务key
-     * @param expireTime 过期时间
-     * @param initValue  ID初始值
-     * @param stepValue  ID步长
-     * @return ID生成器
-     */
-    private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
-        if (initValue == null || initValue <= 0) {
-            initValue = DEFAULT_INIT_VALUE;
-        }
-        if (stepValue == null || stepValue <= 0) {
-            stepValue = DEFAULT_STEP_VALUE;
-        }
-        RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
-        // 设置初始值和步长
-        idGenerator.tryInit(initValue, stepValue);
-        // 设置过期时间
-        idGenerator.expire(expireTime);
-        return idGenerator;
-    }
-
-    /**
-     * 获取指定业务key的唯一id
-     *
-     * @param key        业务key
-     * @param expireTime 过期时间
-     * @param initValue  ID初始值
-     * @param stepValue  ID步长
-     * @return 唯一id
-     */
-    public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
-        return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
-    }
-
-    /**
-     * 获取指定业务key的唯一id字符串
-     *
-     * @param key        业务key
-     * @param expireTime 过期时间
-     * @param initValue  ID初始值
-     * @param stepValue  ID步长
-     * @return 唯一id
-     */
-    public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
-        return String.valueOf(nextId(key, expireTime, initValue, stepValue));
-    }
-
-    /**
-     * 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
-     *
-     * @param key        业务key
-     * @param expireTime 过期时间
-     * @return 唯一id
-     */
-    public static long nextId(String key, Duration expireTime) {
-        return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
-    }
-
-    /**
-     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
-     *
-     * @param key        业务key
-     * @param expireTime 过期时间
-     * @return 唯一id
-     */
-    public static String nextIdStr(String key, Duration expireTime) {
-        return String.valueOf(nextId(key, expireTime));
-    }
-
-    /**
-     * 获取 yyyyMMdd 开头的唯一id
-     *
-     * @return 唯一id
-     */
-    public static String nextIdDate() {
-        return nextIdDate("");
-    }
-
-    /**
-     * 获取 prefix + yyyyMMdd 开头的唯一id
-     *
-     * @param prefix 业务前缀
-     * @return 唯一id
-     */
-    public static String nextIdDate(String prefix) {
-        // 前缀+日期 构建 prefixKey
-        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
-        // 获取下一个id
-        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
-        // 返回完整id
-        return StringUtils.format("{}{}", prefixKey, nextId);
-    }
-
-    /**
-     * 获取 yyyyMMddHHmmss 开头的唯一id
-     *
-     * @return 唯一id
-     */
-    public static String nextIdDateTime() {
-        return nextIdDateTime("");
-    }
-
-    /**
-     * 获取 prefix + yyyyMMddHHmmss 开头的唯一id
-     *
-     * @param prefix 业务前缀
-     * @return 唯一id
-     */
-    public static String nextIdDateTime(String prefix) {
-        // 前缀+日期时间 构建 prefixKey
-        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
-        // 获取下一个id
-        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
-        // 返回完整id
-        return StringUtils.format("{}{}", prefixKey, nextId);
-    }
-
-}
+package org.dromara.common.redis.utils;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.redisson.api.RIdGenerator;
+import org.redisson.api.RedissonClient;
+
+import java.time.Duration;
+
+/**
+ * 发号器工具类
+ *
+ * @author 秋辞未寒
+ * @date 2024-12-10
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SequenceUtils {
+
+    /**
+     * 默认初始值
+     */
+    public static final Long DEFAULT_INIT_VALUE = 1L;
+
+    /**
+     * 默认步长
+     */
+    public static final Long DEFAULT_STEP_VALUE = 1L;
+
+    /**
+     * 默认过期时间-天
+     */
+    public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
+
+    /**
+     * 默认过期时间-分钟
+     */
+    public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
+
+    /**
+     * 获取Redisson客户端实例
+     */
+    private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
+
+    /**
+     * 获取ID生成器
+     *
+     * @param key        业务key
+     * @param expireTime 过期时间
+     * @param initValue  ID初始值
+     * @param stepValue  ID步长
+     * @return ID生成器
+     */
+    private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
+        if (initValue == null || initValue <= 0) {
+            initValue = DEFAULT_INIT_VALUE;
+        }
+        if (stepValue == null || stepValue <= 0) {
+            stepValue = DEFAULT_STEP_VALUE;
+        }
+        RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
+        // 设置初始值和步长
+        idGenerator.tryInit(initValue, stepValue);
+        // 设置过期时间
+        idGenerator.expire(expireTime);
+        return idGenerator;
+    }
+
+    /**
+     * 获取指定业务key的唯一id
+     *
+     * @param key        业务key
+     * @param expireTime 过期时间
+     * @param initValue  ID初始值
+     * @param stepValue  ID步长
+     * @return 唯一id
+     */
+    public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
+        return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
+    }
+
+    /**
+     * 获取指定业务key的唯一id字符串
+     *
+     * @param key        业务key
+     * @param expireTime 过期时间
+     * @param initValue  ID初始值
+     * @param stepValue  ID步长
+     * @return 唯一id
+     */
+    public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
+        return String.valueOf(nextId(key, expireTime, initValue, stepValue));
+    }
+
+    /**
+     * 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
+     *
+     * @param key        业务key
+     * @param expireTime 过期时间
+     * @return 唯一id
+     */
+    public static long nextId(String key, Duration expireTime) {
+        return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
+    }
+
+    /**
+     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
+     *
+     * @param key        业务key
+     * @param expireTime 过期时间
+     * @return 唯一id
+     */
+    public static String nextIdStr(String key, Duration expireTime) {
+        return String.valueOf(nextId(key, expireTime));
+    }
+
+    /**
+     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1),不足位数自动补零
+     *
+     * @param key        业务key
+     * @param expireTime 过期时间
+     * @param width      位数,不足左补0
+     * @return 补零后的唯一id字符串
+     */
+    public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) {
+        return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0');
+    }
+
+    /**
+     * 获取 yyyyMMdd 开头的唯一id
+     *
+     * @return 唯一id
+     */
+    public static String nextIdDate() {
+        return nextIdDate("");
+    }
+
+    /**
+     * 获取 prefix + yyyyMMdd 开头的唯一id
+     *
+     * @param prefix 业务前缀
+     * @return 唯一id
+     */
+    public static String nextIdDate(String prefix) {
+        // 前缀+日期 构建 prefixKey
+        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
+        // 获取下一个id
+        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
+        // 返回完整id
+        return StringUtils.format("{}{}", prefixKey, nextId);
+    }
+
+    /**
+     * 获取 yyyyMMddHHmmss 开头的唯一id
+     *
+     * @return 唯一id
+     */
+    public static String nextIdDateTime() {
+        return nextIdDateTime("");
+    }
+
+    /**
+     * 获取 prefix + yyyyMMddHHmmss 开头的唯一id
+     *
+     * @param prefix 业务前缀
+     * @return 唯一id
+     */
+    public static String nextIdDateTime(String prefix) {
+        // 前缀+日期时间 构建 prefixKey
+        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
+        // 获取下一个id
+        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
+        // 返回完整id
+        return StringUtils.format("{}{}", prefixKey, nextId);
+    }
+
+}

+ 21 - 5
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java

@@ -1,6 +1,6 @@
 package org.dromara.common.satoken.core.dao;
 package org.dromara.common.satoken.core.dao;
 
 
-import cn.dev33.satoken.dao.SaTokenDao;
+import cn.dev33.satoken.dao.auto.SaTokenDaoBySessionFollowObject;
 import cn.dev33.satoken.util.SaFoxUtil;
 import cn.dev33.satoken.util.SaFoxUtil;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.Caffeine;
@@ -16,10 +16,12 @@ import java.util.concurrent.TimeUnit;
  * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
  * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
  * <p>
  * <p>
  * 采用 caffeine + redis 多级缓存 优化并发查询效率
  * 采用 caffeine + redis 多级缓存 优化并发查询效率
+ * <p>
+ * SaTokenDaoBySessionFollowObject 是 SaTokenDao 子集简化了session方法处理
  *
  *
  * @author Lion Li
  * @author Lion Li
  */
  */
-public class PlusSaTokenDao implements SaTokenDao {
+public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
 
 
     private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
     private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
         // 设置最后一次写入或访问后经过固定时间过期
         // 设置最后一次写入或访问后经过固定时间过期
@@ -85,7 +87,8 @@ public class PlusSaTokenDao implements SaTokenDao {
     @Override
     @Override
     public long getTimeout(String key) {
     public long getTimeout(String key) {
         long timeout = RedisUtils.getTimeToLive(key);
         long timeout = RedisUtils.getTimeToLive(key);
-        return timeout < 0 ? timeout : timeout / 1000;
+        // 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
+        return timeout < 0 ? timeout : timeout / 1000 + 1;
     }
     }
 
 
     /**
     /**
@@ -106,6 +109,19 @@ public class PlusSaTokenDao implements SaTokenDao {
         return o;
         return o;
     }
     }
 
 
+    /**
+     * 获取 Object (指定反序列化类型),如无返空
+     *
+     * @param key 键名称
+     * @return object
+     */
+    @SuppressWarnings("unchecked cast")
+    @Override
+    public <T> T getObject(String key, Class<T> classType) {
+        Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
+        return (T) o;
+    }
+
     /**
     /**
      * 写入Object,并设定存活时间 (单位: 秒)
      * 写入Object,并设定存活时间 (单位: 秒)
      */
      */
@@ -152,7 +168,8 @@ public class PlusSaTokenDao implements SaTokenDao {
     @Override
     @Override
     public long getObjectTimeout(String key) {
     public long getObjectTimeout(String key) {
         long timeout = RedisUtils.getTimeToLive(key);
         long timeout = RedisUtils.getTimeToLive(key);
-        return timeout < 0 ? timeout : timeout / 1000;
+        // 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
+        return timeout < 0 ? timeout : timeout / 1000 + 1;
     }
     }
 
 
     /**
     /**
@@ -163,7 +180,6 @@ public class PlusSaTokenDao implements SaTokenDao {
         RedisUtils.expire(key, Duration.ofSeconds(timeout));
         RedisUtils.expire(key, Duration.ofSeconds(timeout));
     }
     }
 
 
-
     /**
     /**
      * 搜索数据
      * 搜索数据
      */
      */

+ 21 - 8
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java

@@ -1,9 +1,13 @@
 package org.dromara.common.satoken.core.service;
 package org.dromara.common.satoken.core.service;
 
 
 import cn.dev33.satoken.stp.StpInterface;
 import cn.dev33.satoken.stp.StpInterface;
+import cn.hutool.core.util.ObjectUtil;
 import org.dromara.common.core.domain.model.LoginUser;
 import org.dromara.common.core.domain.model.LoginUser;
 import org.dromara.common.core.enums.UserType;
 import org.dromara.common.core.enums.UserType;
+import org.dromara.common.core.service.PermissionService;
+import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.beans.factory.annotation.Autowired;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
@@ -15,19 +19,25 @@ import java.util.List;
  */
  */
 public class SaPermissionImpl implements StpInterface {
 public class SaPermissionImpl implements StpInterface {
 
 
+    @Autowired
+    private PermissionService permissionService;
+
     /**
     /**
      * 获取菜单权限列表
      * 获取菜单权限列表
      */
      */
     @Override
     @Override
     public List<String> getPermissionList(Object loginId, String loginType) {
     public List<String> getPermissionList(Object loginId, String loginType) {
         LoginUser loginUser = LoginHelper.getLoginUser();
         LoginUser loginUser = LoginHelper.getLoginUser();
+        if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
+            List<String> list = StringUtils.splitList(loginId.toString(), ":");
+            return new ArrayList<>(permissionService.getMenuPermission(Long.parseLong(list.get(1))));
+        }
         UserType userType = UserType.getUserType(loginUser.getUserType());
         UserType userType = UserType.getUserType(loginUser.getUserType());
-        if (userType == UserType.SYS_USER) {
-            return new ArrayList<>(loginUser.getMenuPermission());
-        } else if (userType == UserType.APP_USER) {
+        if (userType == UserType.APP_USER) {
             // 其他端 自行根据业务编写
             // 其他端 自行根据业务编写
         }
         }
-        return new ArrayList<>();
+        // SYS_USER 默认返回权限
+        return new ArrayList<>(loginUser.getMenuPermission());
     }
     }
 
 
     /**
     /**
@@ -36,12 +46,15 @@ public class SaPermissionImpl implements StpInterface {
     @Override
     @Override
     public List<String> getRoleList(Object loginId, String loginType) {
     public List<String> getRoleList(Object loginId, String loginType) {
         LoginUser loginUser = LoginHelper.getLoginUser();
         LoginUser loginUser = LoginHelper.getLoginUser();
+        if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
+            List<String> list = StringUtils.splitList(loginId.toString(), ":");
+            return new ArrayList<>(permissionService.getRolePermission(Long.parseLong(list.get(1))));
+        }
         UserType userType = UserType.getUserType(loginUser.getUserType());
         UserType userType = UserType.getUserType(loginUser.getUserType());
-        if (userType == UserType.SYS_USER) {
-            return new ArrayList<>(loginUser.getRolePermission());
-        } else if (userType == UserType.APP_USER) {
+        if (userType == UserType.APP_USER) {
             // 其他端 自行根据业务编写
             // 其他端 自行根据业务编写
         }
         }
-        return new ArrayList<>();
+        // SYS_USER 默认返回权限
+        return new ArrayList<>(loginUser.getRolePermission());
     }
     }
 }
 }

+ 3 - 3
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java

@@ -1,8 +1,8 @@
 package org.dromara.common.satoken.utils;
 package org.dromara.common.satoken.utils;
 
 
 import cn.dev33.satoken.session.SaSession;
 import cn.dev33.satoken.session.SaSession;
-import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
@@ -47,8 +47,8 @@ public class LoginHelper {
      * @param loginUser 登录用户信息
      * @param loginUser 登录用户信息
      * @param model     配置参数
      * @param model     配置参数
      */
      */
-    public static void login(LoginUser loginUser, SaLoginModel model) {
-        model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
+    public static void login(LoginUser loginUser, SaLoginParameter model) {
+        model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
         StpUtil.login(loginUser.getLoginId(),
         StpUtil.login(loginUser.getLoginId(),
             model.setExtra(TENANT_KEY, loginUser.getTenantId())
             model.setExtra(TENANT_KEY, loginUser.getTenantId())
                 .setExtra(USER_KEY, loginUser.getUserId())
                 .setExtra(USER_KEY, loginUser.getUserId())

+ 6 - 11
ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java

@@ -11,13 +11,13 @@ import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.constant.HttpStatus;
 import org.dromara.common.core.constant.HttpStatus;
-import org.dromara.common.core.exception.SseException;
 import org.dromara.common.core.utils.ServletUtils;
 import org.dromara.common.core.utils.ServletUtils;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.security.config.properties.SecurityProperties;
 import org.dromara.common.security.config.properties.SecurityProperties;
 import org.dromara.common.security.handler.AllUrlHandler;
 import org.dromara.common.security.handler.AllUrlHandler;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
@@ -37,6 +37,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 public class SecurityConfig implements WebMvcConfigurer {
 public class SecurityConfig implements WebMvcConfigurer {
 
 
     private final SecurityProperties securityProperties;
     private final SecurityProperties securityProperties;
+    @Value("${sse.path}")
+    private String ssePath;
 
 
     /**
     /**
      * 注册sa-token的拦截器
      * 注册sa-token的拦截器
@@ -54,15 +56,7 @@ public class SecurityConfig implements WebMvcConfigurer {
                     .check(() -> {
                     .check(() -> {
                         HttpServletRequest request = ServletUtils.getRequest();
                         HttpServletRequest request = ServletUtils.getRequest();
                         // 检查是否登录 是否有token
                         // 检查是否登录 是否有token
-                        try {
-                            StpUtil.checkLogin();
-                        } catch (NotLoginException e) {
-                            if (request.getRequestURI().contains("sse")) {
-                                throw new SseException(e.getMessage(), e.getCode());
-                            } else {
-                                throw e;
-                            }
-                        }
+                        StpUtil.checkLogin();
 
 
                         // 检查 header 与 param 里的 clientid 与 token 里的是否一致
                         // 检查 header 与 param 里的 clientid 与 token 里的是否一致
                         String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
                         String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
@@ -84,7 +78,8 @@ public class SecurityConfig implements WebMvcConfigurer {
                     });
                     });
             })).addPathPatterns("/**")
             })).addPathPatterns("/**")
             // 排除不需要拦截的路径
             // 排除不需要拦截的路径
-            .excludePathPatterns(securityProperties.getExcludes());
+            .excludePathPatterns(securityProperties.getExcludes())
+            .excludePathPatterns(ssePath);
     }
     }
 
 
     /**
     /**

+ 92 - 0
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaRequest.java

@@ -0,0 +1,92 @@
+package org.dromara.common.social.gitea;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * @author lcry
+ */
+@Slf4j
+public class AuthGiteaRequest extends AuthDefaultRequest {
+
+    public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.gitea.server-url");
+
+    /**
+     * 设定归属域
+     */
+    public AuthGiteaRequest(AuthConfig config) {
+        super(config, AuthGiteaSource.GITEA);
+    }
+
+    public AuthGiteaRequest(AuthConfig config, AuthStateCache authStateCache) {
+        super(config, AuthGiteaSource.GITEA, authStateCache);
+    }
+
+    @Override
+    public AuthToken getAccessToken(AuthCallback authCallback) {
+        String body = doPostAuthorizationCode(authCallback.getCode());
+        Dict object = JsonUtils.parseMap(body);
+        // oauth/token 验证异常
+        if (object.containsKey("error")) {
+            throw new AuthException(object.getStr("error_description"));
+        }
+        // user 验证异常
+        if (object.containsKey("message")) {
+            throw new AuthException(object.getStr("message"));
+        }
+        return AuthToken.builder()
+                .accessToken(object.getStr("access_token"))
+                .refreshToken(object.getStr("refresh_token"))
+                .idToken(object.getStr("id_token"))
+                .tokenType(object.getStr("token_type"))
+                .scope(object.getStr("scope"))
+                .build();
+    }
+
+    @Override
+    protected String doPostAuthorizationCode(String code) {
+        HttpRequest request = HttpRequest.post(source.accessToken())
+                .form("client_id", config.getClientId())
+                .form("client_secret", config.getClientSecret())
+                .form("grant_type", "authorization_code")
+                .form("code", code)
+                .form("redirect_uri", config.getRedirectUri());
+        HttpResponse response = request.execute();
+        return response.body();
+    }
+
+    @Override
+    public AuthUser getUserInfo(AuthToken authToken) {
+        String body = doGetUserInfo(authToken);
+        Dict object = JsonUtils.parseMap(body);
+        // oauth/token 验证异常
+        if (object.containsKey("error")) {
+            throw new AuthException(object.getStr("error_description"));
+        }
+        // user 验证异常
+        if (object.containsKey("message")) {
+            throw new AuthException(object.getStr("message"));
+        }
+        return AuthUser.builder()
+                .uuid(object.getStr("sub"))
+                .username(object.getStr("name"))
+                .nickname(object.getStr("preferred_username"))
+                .avatar(object.getStr("picture"))
+                .email(object.getStr("email"))
+                .token(authToken)
+                .source(source.toString())
+                .build();
+    }
+
+}

+ 50 - 0
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaSource.java

@@ -0,0 +1,50 @@
+package org.dromara.common.social.gitea;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+
+/**
+ * gitea Oauth2 默认接口说明
+ *
+ * @author lcry
+ */
+public enum AuthGiteaSource implements AuthSource {
+
+    /**
+     * 自己搭建的 gitea 私服
+     */
+    GITEA {
+        /**
+         * 授权的api
+         */
+        @Override
+        public String authorize() {
+            return AuthGiteaRequest.SERVER_URL + "/login/oauth/authorize";
+        }
+
+        /**
+         * 获取accessToken的api
+         */
+        @Override
+        public String accessToken() {
+            return AuthGiteaRequest.SERVER_URL + "/login/oauth/access_token";
+        }
+
+        /**
+         * 获取用户信息的api
+         */
+        @Override
+        public String userInfo() {
+            return AuthGiteaRequest.SERVER_URL + "/login/oauth/userinfo";
+        }
+
+        /**
+         * 平台对应的 AuthRequest 实现类,必须继承自 {@link AuthDefaultRequest}
+         */
+        @Override
+        public Class<? extends AuthDefaultRequest> getTargetClass() {
+            return AuthGiteaRequest.class;
+        }
+
+    }
+}

+ 4 - 2
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java

@@ -10,6 +10,7 @@ import me.zhyd.oauth.request.*;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
 import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
 import org.dromara.common.social.config.properties.SocialProperties;
 import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.gitea.AuthGiteaRequest;
 import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
 import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
 import org.dromara.common.social.topiam.AuthTopIamRequest;
 import org.dromara.common.social.topiam.AuthTopIamRequest;
 
 
@@ -42,7 +43,7 @@ public class SocialUtils  {
             .redirectUri(obj.getRedirectUri())
             .redirectUri(obj.getRedirectUri())
             .scopes(obj.getScopes());
             .scopes(obj.getScopes());
         return switch (source.toLowerCase()) {
         return switch (source.toLowerCase()) {
-            case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE);
+            case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE);
             case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
             case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
             case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
             case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
             case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
             case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
@@ -60,12 +61,13 @@ public class SocialUtils  {
             case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
             case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
             case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
             case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
             case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);
             case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);
-            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
+            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeV2Request(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
             case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
             case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
             case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
             case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
             case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
             case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
             case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
             case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
             case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
             case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
+            case "gitea" -> new AuthGiteaRequest(builder.build(), STATE_CACHE);
             default -> throw new AuthException("未获取到有效的Auth配置");
             default -> throw new AuthException("未获取到有效的Auth配置");
         };
         };
     }
     }

+ 1 - 0
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java

@@ -33,6 +33,7 @@ public class SseController implements DisposableBean {
      */
      */
     @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
     @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
     public SseEmitter connect() {
     public SseEmitter connect() {
+        StpUtil.checkLogin();
         String tokenValue = StpUtil.getTokenValue();
         String tokenValue = StpUtil.getTokenValue();
         Long userId = LoginHelper.getUserId();
         Long userId = LoginHelper.getUserId();
         return sseEmitterManager.connect(userId, tokenValue);
         return sseEmitterManager.connect(userId, tokenValue);

+ 22 - 4
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java

@@ -44,9 +44,24 @@ public class SseEmitterManager {
         emitters.put(token, emitter);
         emitters.put(token, emitter);
 
 
         // 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token
         // 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token
-        emitter.onCompletion(() -> emitters.remove(token));
-        emitter.onTimeout(() -> emitters.remove(token));
-        emitter.onError((e) -> emitters.remove(token));
+        emitter.onCompletion(() -> {
+            SseEmitter remove = emitters.remove(token);
+            if (remove != null) {
+                remove.complete();
+            }
+        });
+        emitter.onTimeout(() -> {
+            SseEmitter remove = emitters.remove(token);
+            if (remove != null) {
+                remove.complete();
+            }
+        });
+        emitter.onError((e) -> {
+            SseEmitter remove = emitters.remove(token);
+            if (remove != null) {
+                remove.complete();
+            }
+        });
 
 
         try {
         try {
             // 向客户端发送一条连接成功的事件
             // 向客户端发送一条连接成功的事件
@@ -106,7 +121,10 @@ public class SseEmitterManager {
                         .name("message")
                         .name("message")
                         .data(message));
                         .data(message));
                 } catch (Exception e) {
                 } catch (Exception e) {
-                    emitters.remove(entry.getKey());
+                    SseEmitter remove = emitters.remove(entry.getKey());
+                    if (remove != null) {
+                        remove.complete();
+                    }
                 }
                 }
             }
             }
         } else {
         } else {

+ 11 - 1
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java

@@ -81,6 +81,17 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
         return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
         return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
     }
     }
 
 
+    /**
+     * 获取 Object (指定反序列化类型),如无返空
+     *
+     * @param key 键名称
+     * @return object
+     */
+    @Override
+    public <T> T getObject(String key, Class<T> classType) {
+        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
+    }
+
     /**
     /**
      * 写入Object,并设定存活时间 (单位: 秒)
      * 写入Object,并设定存活时间 (单位: 秒)
      */
      */
@@ -137,7 +148,6 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
         RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
         RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
     }
     }
 
 
-
     /**
     /**
      * 搜索数据
      * 搜索数据
      */
      */

+ 1 - 1
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java

@@ -48,7 +48,7 @@ public class PlusTenantLineHandler implements TenantLineHandler {
                 "gen_table_column"
                 "gen_table_column"
             );
             );
             tables.addAll(excludes);
             tables.addAll(excludes);
-            return tables.contains(tableName);
+            return StringUtils.containsAnyIgnoreCase(tableName, tables.toArray(new String[0]));
         }
         }
         return true;
         return true;
     }
     }

+ 22 - 0
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java

@@ -2,6 +2,7 @@ package org.dromara.common.web.handler;
 
 
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.http.HttpStatus;
 import cn.hutool.http.HttpStatus;
+import com.fasterxml.jackson.core.JsonParseException;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.ConstraintViolation;
 import jakarta.validation.ConstraintViolation;
@@ -14,6 +15,7 @@ import org.dromara.common.core.exception.base.BaseException;
 import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.json.utils.JsonUtils;
 import org.springframework.context.support.DefaultMessageSourceResolvable;
 import org.springframework.context.support.DefaultMessageSourceResolvable;
+import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.validation.BindException;
 import org.springframework.validation.BindException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -180,4 +182,24 @@ public class GlobalExceptionHandler {
         return R.fail(message);
         return R.fail(message);
     }
     }
 
 
+    /**
+     * JSON 解析异常(Jackson 在处理 JSON 格式出错时抛出)
+     * 可能是请求体格式非法,也可能是服务端反序列化失败
+     */
+    @ExceptionHandler(JsonParseException.class)
+    public R<Void> handleJsonParseException(JsonParseException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}' 发生 JSON 解析异常: {}", requestURI, e.getMessage());
+        return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求数据格式错误(JSON 解析失败):" + e.getMessage());
+    }
+
+    /**
+     * 请求体读取异常(通常是请求参数格式非法、字段类型不匹配等)
+     */
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    public R<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
+        log.error("请求地址'{}', 参数解析失败: {}", request.getRequestURI(), e.getMessage());
+        return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
+    }
+
 }
 }

+ 1 - 1
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java

@@ -44,7 +44,7 @@ public class ActuatorAuthFilter implements Filter {
             return;
             return;
         }
         }
         // 验证用户名和密码
         // 验证用户名和密码
-        if (!username.equals(split[0]) && password.equals(split[1])) {
+        if (!username.equals(split[0]) || !password.equals(split[1])) {
             response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
             response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
             return;
             return;

+ 1 - 1
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java

@@ -40,7 +40,7 @@ public class RedisCacheController {
      * <p>
      * <p>
      * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
      * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
      */
      */
-    @Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null")
+    @Cacheable(cacheNames = "demo:cache#60s#10m#20#1", key = "#key", condition = "#key != null")
     @GetMapping("/test1")
     @GetMapping("/test1")
     public R<String> test1(String key, String value) {
     public R<String> test1(String key, String value) {
         return R.ok("操作成功", value);
         return R.ok("操作成功", value);

+ 1 - 1
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/WeSocketController.java → ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/WebSocketController.java

@@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 @RestController
 @RequestMapping("/demo/websocket")
 @RequestMapping("/demo/websocket")
 @Slf4j
 @Slf4j
-public class WeSocketController {
+public class WebSocketController {
 
 
     /**
     /**
      * 发布消息
      * 发布消息

+ 1 - 1
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoImportVo.java

@@ -1,6 +1,6 @@
 package org.dromara.demo.domain.bo;
 package org.dromara.demo.domain.bo;
 
 
-import com.alibaba.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.ExcelProperty;
 import lombok.Data;
 import lombok.Data;
 
 
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotBlank;

+ 2 - 2
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java

@@ -1,7 +1,7 @@
 package org.dromara.demo.domain.vo;
 package org.dromara.demo.domain.vo;
 
 
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.NotNull;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;

+ 2 - 2
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java

@@ -1,7 +1,7 @@
 package org.dromara.demo.domain.vo;
 package org.dromara.demo.domain.vo;
 
 
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
 import org.dromara.common.excel.annotation.ExcelNotation;
 import org.dromara.common.excel.annotation.ExcelNotation;
 import org.dromara.common.excel.annotation.ExcelRequired;
 import org.dromara.common.excel.annotation.ExcelRequired;
 import org.dromara.common.translation.annotation.Translation;
 import org.dromara.common.translation.annotation.Translation;

+ 2 - 2
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestTreeVo.java

@@ -1,7 +1,7 @@
 package org.dromara.demo.domain.vo;
 package org.dromara.demo.domain.vo;
 
 
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
 import org.dromara.demo.domain.TestTree;
 import org.dromara.demo.domain.TestTree;
 import io.github.linpeilie.annotations.AutoMapper;
 import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
 import lombok.Data;

+ 1 - 1
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/listener/ExportDemoListener.java

@@ -1,7 +1,7 @@
 package org.dromara.demo.listener;
 package org.dromara.demo.listener;
 
 
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.NumberUtil;
-import com.alibaba.excel.context.AnalysisContext;
+import cn.idev.excel.context.AnalysisContext;
 import org.dromara.common.core.utils.ValidatorUtils;
 import org.dromara.common.core.utils.ValidatorUtils;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.core.validate.EditGroup;

+ 1 - 7
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java

@@ -9,7 +9,6 @@ import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
@@ -168,12 +167,7 @@ public class GenTableServiceImpl implements IGenTableService {
                 return gen;
                 return gen;
             }).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
             }).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
             .toList();
             .toList();
-
-        IPage<GenTable> page = pageQuery.build();
-        page.setTotal(tables.size());
-        // 手动分页 set数据
-        page.setRecords(CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tables));
-        return TableDataInfo.build(page);
+        return TableDataInfo.build(tables, pageQuery.build());
     }
     }
 
 
     /**
     /**

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

@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import ${packageName}.domain.bo.${ClassName}Bo;
 import ${packageName}.domain.bo.${ClassName}Bo;
 import ${packageName}.domain.vo.${ClassName}Vo;
 import ${packageName}.domain.vo.${ClassName}Vo;
@@ -27,6 +28,7 @@ import java.util.Collection;
  * @author ${author}
  * @author ${author}
  * @date ${datetime}
  * @date ${datetime}
  */
  */
+@Slf4j
 @RequiredArgsConstructor
 @RequiredArgsConstructor
 @Service
 @Service
 public class ${ClassName}ServiceImpl implements I${ClassName}Service {
 public class ${ClassName}ServiceImpl implements I${ClassName}Service {

+ 2 - 2
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm

@@ -4,8 +4,8 @@ package ${packageName}.domain.vo;
 import ${import};
 import ${import};
 #end
 #end
 import ${packageName}.domain.${ClassName};
 import ${packageName}.domain.${ClassName};
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
 import org.dromara.common.excel.annotation.ExcelDictFormat;
 import org.dromara.common.excel.annotation.ExcelDictFormat;
 import org.dromara.common.excel.convert.ExcelDictConvert;
 import org.dromara.common.excel.convert.ExcelDictConvert;
 import io.github.linpeilie.annotations.AutoMapper;
 import io.github.linpeilie.annotations.AutoMapper;

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

@@ -80,6 +80,7 @@
         v-loading="loading"
         v-loading="loading"
         :data="${businessName}List"
         :data="${businessName}List"
         row-key="${treeCode}"
         row-key="${treeCode}"
+        border
         :default-expand-all="isExpandAll"
         :default-expand-all="isExpandAll"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
       >
       >

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

@@ -82,7 +82,7 @@
         </el-row>
         </el-row>
       </template>
       </template>
 
 
-      <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="${businessName}List" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column type="selection" width="55" align="center" />
 #foreach($column in $columns)
 #foreach($column in $columns)
 #set($javaField=$column.javaField)
 #set($javaField=$column.javaField)

+ 30 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/entity/BillDto.java

@@ -0,0 +1,30 @@
+package org.dromara.job.entity;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class BillDto {
+
+    /**
+     * 账单ID
+     */
+    private Long billId;
+
+    /**
+     * 账单渠道
+     */
+    private String billChannel;
+
+    /**
+     * 账单日期
+     */
+    private String billDate;
+
+    /**
+     * 账单金额
+     */
+    private BigDecimal billAmount;
+
+}

+ 42 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java

@@ -0,0 +1,42 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.job.entity.BillDto;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+/**
+ * DAG工作流任务-模拟支付宝账单任务
+ * <a href="https://juejin.cn/post/7487860254114644019"></a>
+ *
+ * @author 老马
+ */
+@Component
+@JobExecutor(name = "alipayBillTask")
+public class AlipayBillTask {
+
+    public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
+        BillDto billDto = new BillDto();
+        billDto.setBillId(23456789L);
+        billDto.setBillChannel("alipay");
+        // 设置清算日期
+        String settlementDate = (String) jobArgs.getWfContext().get("settlementDate");
+        if (StrUtil.equals(settlementDate, "sysdate")) {
+            settlementDate = DateUtil.today();
+        }
+        billDto.setBillDate(settlementDate);
+        billDto.setBillAmount(new BigDecimal("2345.67"));
+        // 把billDto对象放入上下文进行传递
+        jobArgs.appendContext("alipay", JsonUtils.toJsonString(billDto));
+        SnailJobLog.REMOTE.info("上下文: {}", jobArgs.getWfContext());
+        return ExecuteResult.success(billDto);
+    }
+
+}

+ 45 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java

@@ -0,0 +1,45 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.job.entity.BillDto;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+/**
+ * DAG工作流任务-模拟汇总账单任务
+ * <a href="https://juejin.cn/post/7487860254114644019"></a>
+ *
+ * @author 老马
+ */
+@Component
+@JobExecutor(name = "summaryBillTask")
+public class SummaryBillTask {
+
+    public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
+        // 获得微信账单
+        BigDecimal wechatAmount = BigDecimal.valueOf(0);
+        String wechat = (String) jobArgs.getWfContext("wechat");
+        if (StrUtil.isNotBlank(wechat)) {
+            BillDto wechatBillDto = JsonUtils.parseObject(wechat, BillDto.class);
+            wechatAmount = wechatBillDto.getBillAmount();
+        }
+        // 获得支付宝账单
+        BigDecimal alipayAmount = BigDecimal.valueOf(0);
+        String alipay = (String) jobArgs.getWfContext("alipay");
+        if (StrUtil.isNotBlank(alipay)) {
+            BillDto alipayBillDto = JsonUtils.parseObject(alipay, BillDto.class);
+            alipayAmount = alipayBillDto.getBillAmount();
+        }
+        // 汇总账单
+        BigDecimal totalAmount = wechatAmount.add(alipayAmount);
+        SnailJobLog.REMOTE.info("总金额: {}", totalAmount);
+        return ExecuteResult.success(totalAmount);
+    }
+
+}

+ 4 - 2
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java

@@ -8,8 +8,10 @@ import com.aizuda.snailjob.common.log.SnailJobLog;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Component;
 
 
 /**
 /**
- * @author opensnail
- * @date 2024-05-17
+ * 正常任务
+ * <a href="https://juejin.cn/post/7418074037392293914"></a>
+ *
+ * @author 老马
  */
  */
 @Component
 @Component
 @JobExecutor(name = "testJobExecutor")
 @JobExecutor(name = "testJobExecutor")

+ 37 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java

@@ -0,0 +1,37 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.util.RandomUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * 广播任务
+ * <a href="https://juejin.cn/post/7422948006150438950"></a>
+ *
+ * @author 老马
+ */
+@Slf4j
+@Component
+@JobExecutor(name = "testBroadcastJob")
+public class TestBroadcastJob {
+
+    @Value("${snail-job.port}")
+    private int clientPort;
+
+    public ExecuteResult jobExecute(JobArgs jobArgs) {
+        int randomInt = RandomUtil.randomInt(100);
+        log.info("随机数: {}", randomInt);
+        SnailJobLog.REMOTE.info("随机数: {},客户端端口:{}", randomInt, clientPort);
+        if (randomInt < 50) {
+            throw new RuntimeException("随机数小于50,收集日志任务执行失败");
+        }
+        // 获得jobArgs 中传入的相加的两个数
+        return ExecuteResult.success("随机数大于50,收集日志任务执行成功");
+    }
+
+}

+ 53 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java

@@ -0,0 +1,53 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.aizuda.snailjob.client.job.core.MapHandler;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
+import com.aizuda.snailjob.client.job.core.dto.MapArgs;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Map任务 动态分配 只分片不关注结果
+ * <a href="https://juejin.cn/post/7446362500478894106"></a>
+ *
+ * @author 老马
+ */
+@Component
+@JobExecutor(name = "testMapJobAnnotation")
+public class TestMapJobAnnotation {
+
+    @MapExecutor
+    public ExecuteResult doJobMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
+        // 生成1~200数值并分片
+        int partitionSize = 50;
+        List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
+            .boxed()
+            .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
+            .values()
+            .stream()
+            .toList();
+        SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
+        return mapHandler.doMap(partition, "doCalc");
+    }
+
+    @MapExecutor(taskName = "doCalc")
+    public ExecuteResult doCalc(MapArgs mapArgs) {
+        List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
+        // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
+        int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
+        // 打印日志到服务器
+        ThreadUtil.sleep(3, TimeUnit.SECONDS);
+        SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
+        return ExecuteResult.success(partitionTotal);
+    }
+
+}

+ 60 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java

@@ -0,0 +1,60 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.aizuda.snailjob.client.job.core.MapHandler;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
+import com.aizuda.snailjob.client.job.core.annotation.ReduceExecutor;
+import com.aizuda.snailjob.client.job.core.dto.MapArgs;
+import com.aizuda.snailjob.client.job.core.dto.ReduceArgs;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * MapReduce任务 动态分配 分片后合并结果
+ * <a href="https://juejin.cn/post/7448551286506913802"></a>
+ *
+ * @author 老马
+ */
+@Component
+@JobExecutor(name = "testMapReduceAnnotation1")
+public class TestMapReduceAnnotation1 {
+
+    @MapExecutor
+    public ExecuteResult rootMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
+        int partitionSize = 50;
+        List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
+                .boxed()
+                .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
+                .values()
+                .stream()
+                .toList();
+        SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
+        return mapHandler.doMap(partition, "doCalc");
+    }
+
+    @MapExecutor(taskName = "doCalc")
+    public ExecuteResult doCalc(MapArgs mapArgs) {
+        List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
+        // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
+        int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
+        // 打印日志到服务器
+        ThreadUtil.sleep(3, TimeUnit.SECONDS);
+        SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
+        return ExecuteResult.success(partitionTotal);
+    }
+
+    @ReduceExecutor
+    public ExecuteResult reduceExecute(ReduceArgs reduceArgs) {
+        int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
+        SnailJobLog.REMOTE.info("端口:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal);
+        return ExecuteResult.success(reduceTotal);
+    }
+}

+ 36 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java

@@ -0,0 +1,36 @@
+package org.dromara.job.snailjob;
+
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import org.springframework.stereotype.Component;
+
+/**
+ * 静态分片 根据服务端任务参数分片
+ * <a href="https://juejin.cn/post/7426232375703896101"></a>
+ *
+ * @author 老马
+ */
+@Component
+@JobExecutor(name = "testStaticShardingJob")
+public class TestStaticShardingJob {
+
+    public ExecuteResult jobExecute(JobArgs jobArgs) {
+        String jobParams = String.valueOf(jobArgs.getJobParams());
+        SnailJobLog.LOCAL.info("开始执行分片任务,参数:{}", jobParams);
+        // 获得jobArgs 中传入的开始id和结束id
+        String[] split = jobParams.split(",");
+        Long fromId = Long.parseLong(split[0]);
+        Long toId = Long.parseLong(split[1]);
+        // 模拟数据库操作,对范围id,进行加密处理
+        try {
+            SnailJobLog.REMOTE.info("开始对id范围:{}进行加密处理", fromId + "-" + toId);
+            Thread.sleep(3000);
+            SnailJobLog.REMOTE.info("对id范围:{}进行加密处理完成", fromId + "-" + toId);
+        } catch (InterruptedException e) {
+            return ExecuteResult.failure("任务执行失败");
+        }
+        return ExecuteResult.success("执行分片任务完成");
+    }
+}

+ 43 - 0
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java

@@ -0,0 +1,43 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.job.entity.BillDto;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+/**
+ * DAG工作流任务-模拟微信账单任务
+ * <a href="https://juejin.cn/post/7487860254114644019"></a>
+ *
+ * @author 老马
+ */
+@Component
+@JobExecutor(name = "wechatBillTask")
+public class WechatBillTask {
+
+    public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
+        BillDto billDto = new BillDto();
+        billDto.setBillId(123456789L);
+        billDto.setBillChannel("wechat");
+        // 从上下文中获得清算日期并设置,如果上下文中清算日期
+        // 是sysdate设置为当前日期;否则取管理页面设置的值
+        String settlementDate = (String) jobArgs.getWfContext().get("settlementDate");
+        if (StrUtil.equals(settlementDate, "sysdate")) {
+            settlementDate = DateUtil.today();
+        }
+        billDto.setBillDate(settlementDate);
+        billDto.setBillAmount(new BigDecimal("1234.56"));
+        // 把billDto对象放入上下文进行传递
+        jobArgs.appendContext("wechat", JsonUtils.toJsonString(billDto));
+        SnailJobLog.REMOTE.info("上下文: {}", jobArgs.getWfContext());
+        return ExecuteResult.success(billDto);
+    }
+
+}

+ 6 - 7
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java

@@ -1,10 +1,9 @@
 package org.dromara.system.controller.monitor;
 package org.dromara.system.controller.monitor;
 
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.StringUtils;
-import org.dromara.system.domain.vo.CacheListInfoVo;
-import lombok.RequiredArgsConstructor;
 import org.redisson.spring.data.connection.RedissonConnectionFactory;
 import org.redisson.spring.data.connection.RedissonConnectionFactory;
 import org.springframework.data.redis.connection.RedisConnection;
 import org.springframework.data.redis.connection.RedisConnection;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -45,11 +44,11 @@ public class CacheController {
             });
             });
         }
         }
 
 
-        CacheListInfoVo infoVo = new CacheListInfoVo();
-        infoVo.setInfo(connection.commands().info());
-        infoVo.setDbSize(connection.commands().dbSize());
-        infoVo.setCommandStats(pieList);
-        return R.ok(infoVo);
+        return R.ok(new CacheListInfoVo(
+            connection.commands().info(),
+            connection.commands().dbSize(), pieList));
     }
     }
 
 
+    public record CacheListInfoVo(Properties info, Long dbSize, List<Map<String, String>> commandStats) {}
+
 }
 }

+ 31 - 11
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java

@@ -15,7 +15,6 @@ import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.system.domain.SysMenu;
 import org.dromara.system.domain.SysMenu;
 import org.dromara.system.domain.bo.SysMenuBo;
 import org.dromara.system.domain.bo.SysMenuBo;
-import org.dromara.system.domain.vo.MenuTreeSelectVo;
 import org.dromara.system.domain.vo.RouterVo;
 import org.dromara.system.domain.vo.RouterVo;
 import org.dromara.system.domain.vo.SysMenuVo;
 import org.dromara.system.domain.vo.SysMenuVo;
 import org.dromara.system.service.ISysMenuService;
 import org.dromara.system.service.ISysMenuService;
@@ -52,8 +51,8 @@ public class SysMenuController extends BaseController {
      * 获取菜单列表
      * 获取菜单列表
      */
      */
     @SaCheckRole(value = {
     @SaCheckRole(value = {
-            TenantConstants.SUPER_ADMIN_ROLE_KEY,
-            TenantConstants.TENANT_ADMIN_ROLE_KEY
+        TenantConstants.SUPER_ADMIN_ROLE_KEY,
+        TenantConstants.TENANT_ADMIN_ROLE_KEY
     }, mode = SaMode.OR)
     }, mode = SaMode.OR)
     @SaCheckPermission("system:menu:list")
     @SaCheckPermission("system:menu:list")
     @GetMapping("/list")
     @GetMapping("/list")
@@ -68,8 +67,8 @@ public class SysMenuController extends BaseController {
      * @param menuId 菜单ID
      * @param menuId 菜单ID
      */
      */
     @SaCheckRole(value = {
     @SaCheckRole(value = {
-            TenantConstants.SUPER_ADMIN_ROLE_KEY,
-            TenantConstants.TENANT_ADMIN_ROLE_KEY
+        TenantConstants.SUPER_ADMIN_ROLE_KEY,
+        TenantConstants.TENANT_ADMIN_ROLE_KEY
     }, mode = SaMode.OR)
     }, mode = SaMode.OR)
     @SaCheckPermission("system:menu:query")
     @SaCheckPermission("system:menu:query")
     @GetMapping(value = "/{menuId}")
     @GetMapping(value = "/{menuId}")
@@ -96,9 +95,9 @@ public class SysMenuController extends BaseController {
     @GetMapping(value = "/roleMenuTreeselect/{roleId}")
     @GetMapping(value = "/roleMenuTreeselect/{roleId}")
     public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
     public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
         List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
         List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
-        MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
-        selectVo.setCheckedKeys(menuService.selectMenuListByRoleId(roleId));
-        selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
+        MenuTreeSelectVo selectVo = new MenuTreeSelectVo(
+            menuService.selectMenuListByRoleId(roleId),
+            menuService.buildMenuTreeSelect(menus));
         return R.ok(selectVo);
         return R.ok(selectVo);
     }
     }
 
 
@@ -112,9 +111,9 @@ public class SysMenuController extends BaseController {
     @GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
     @GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
     public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
     public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
         List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
         List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
-        MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
-        selectVo.setCheckedKeys(menuService.selectMenuListByPackageId(packageId));
-        selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
+        MenuTreeSelectVo selectVo = new MenuTreeSelectVo(
+            menuService.selectMenuListByPackageId(packageId),
+            menuService.buildMenuTreeSelect(menus));
         return R.ok(selectVo);
         return R.ok(selectVo);
     }
     }
 
 
@@ -171,4 +170,25 @@ public class SysMenuController extends BaseController {
         return toAjax(menuService.deleteMenuById(menuId));
         return toAjax(menuService.deleteMenuById(menuId));
     }
     }
 
 
+    public record MenuTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> menus) {
+    }
+
+    /**
+     * 批量级联删除菜单
+     *
+     * @param menuIds 菜单ID串
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:menu:remove")
+    @Log(title = "菜单管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/cascade/{menuIds}")
+    public R<Void> remove(@PathVariable("menuIds") Long[] menuIds) {
+        List<Long> menuIdList = List.of(menuIds);
+        if (menuService.hasChildByMenuId(menuIdList)) {
+            return R.warn("存在子菜单,不允许删除");
+        }
+        menuService.deleteMenuById(menuIdList);
+        return R.ok();
+    }
+
 }
 }

+ 10 - 10
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java

@@ -1,8 +1,8 @@
 package org.dromara.system.controller.system;
 package org.dromara.system.controller.system;
 
 
-import cn.dev33.satoken.secure.BCrypt;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.crypto.digest.BCrypt;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.StringUtils;
@@ -17,8 +17,6 @@ import org.dromara.common.web.core.BaseController;
 import org.dromara.system.domain.bo.SysUserBo;
 import org.dromara.system.domain.bo.SysUserBo;
 import org.dromara.system.domain.bo.SysUserPasswordBo;
 import org.dromara.system.domain.bo.SysUserPasswordBo;
 import org.dromara.system.domain.bo.SysUserProfileBo;
 import org.dromara.system.domain.bo.SysUserProfileBo;
-import org.dromara.system.domain.vo.AvatarVo;
-import org.dromara.system.domain.vo.ProfileVo;
 import org.dromara.system.domain.vo.SysOssVo;
 import org.dromara.system.domain.vo.SysOssVo;
 import org.dromara.system.domain.vo.SysUserVo;
 import org.dromara.system.domain.vo.SysUserVo;
 import org.dromara.system.service.ISysOssService;
 import org.dromara.system.service.ISysOssService;
@@ -50,10 +48,9 @@ public class SysProfileController extends BaseController {
     @GetMapping
     @GetMapping
     public R<ProfileVo> profile() {
     public R<ProfileVo> profile() {
         SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
         SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
-        ProfileVo profileVo = new ProfileVo();
-        profileVo.setUser(user);
-        profileVo.setRoleGroup(userService.selectUserRoleGroup(user.getUserId()));
-        profileVo.setPostGroup(userService.selectUserPostGroup(user.getUserId()));
+        String roleGroup = userService.selectUserRoleGroup(user.getUserId());
+        String postGroup = userService.selectUserPostGroup(user.getUserId());
+        ProfileVo profileVo = new ProfileVo(user, roleGroup, postGroup);
         return R.ok(profileVo);
         return R.ok(profileVo);
     }
     }
 
 
@@ -123,11 +120,14 @@ public class SysProfileController extends BaseController {
             String avatar = oss.getUrl();
             String avatar = oss.getUrl();
             boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId()));
             boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId()));
             if (updateSuccess) {
             if (updateSuccess) {
-                AvatarVo avatarVo = new AvatarVo();
-                avatarVo.setImgUrl(avatar);
-                return R.ok(avatarVo);
+                return R.ok(new AvatarVo(avatar));
             }
             }
         }
         }
         return R.fail("上传图片异常,请联系管理员");
         return R.fail("上传图片异常,请联系管理员");
     }
     }
+
+    public record AvatarVo(String imgUrl) {}
+
+    public record ProfileVo(SysUserVo user, String roleGroup, String postGroup) {}
+
 }
 }

+ 7 - 4
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java

@@ -1,6 +1,7 @@
 package org.dromara.system.controller.system;
 package org.dromara.system.controller.system;
 
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.R;
@@ -14,7 +15,6 @@ import org.dromara.system.domain.SysUserRole;
 import org.dromara.system.domain.bo.SysDeptBo;
 import org.dromara.system.domain.bo.SysDeptBo;
 import org.dromara.system.domain.bo.SysRoleBo;
 import org.dromara.system.domain.bo.SysRoleBo;
 import org.dromara.system.domain.bo.SysUserBo;
 import org.dromara.system.domain.bo.SysUserBo;
-import org.dromara.system.domain.vo.DeptTreeSelectVo;
 import org.dromara.system.domain.vo.SysRoleVo;
 import org.dromara.system.domain.vo.SysRoleVo;
 import org.dromara.system.domain.vo.SysUserVo;
 import org.dromara.system.domain.vo.SysUserVo;
 import org.dromara.system.service.ISysDeptService;
 import org.dromara.system.service.ISysDeptService;
@@ -221,9 +221,12 @@ public class SysRoleController extends BaseController {
     @SaCheckPermission("system:role:list")
     @SaCheckPermission("system:role:list")
     @GetMapping(value = "/deptTree/{roleId}")
     @GetMapping(value = "/deptTree/{roleId}")
     public R<DeptTreeSelectVo> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
     public R<DeptTreeSelectVo> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
-        DeptTreeSelectVo selectVo = new DeptTreeSelectVo();
-        selectVo.setCheckedKeys(deptService.selectDeptListByRoleId(roleId));
-        selectVo.setDepts(deptService.selectDeptTreeList(new SysDeptBo()));
+        DeptTreeSelectVo selectVo = new DeptTreeSelectVo(
+            deptService.selectDeptListByRoleId(roleId),
+            deptService.selectDeptTreeList(new SysDeptBo()));
         return R.ok(selectVo);
         return R.ok(selectVo);
     }
     }
+
+    public record DeptTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> depts) {}
+
 }
 }

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java

@@ -1,10 +1,10 @@
 package org.dromara.system.controller.system;
 package org.dromara.system.controller.system;
 
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.dev33.satoken.secure.BCrypt;
 import cn.hutool.core.lang.tree.Tree;
 import cn.hutool.core.lang.tree.Tree;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.digest.BCrypt;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.NotNull;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;

+ 0 - 18
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/AvatarVo.java

@@ -1,18 +0,0 @@
-package org.dromara.system.domain.vo;
-
-import lombok.Data;
-
-/**
- * 用户头像信息
- *
- * @author Michelle.Chung
- */
-@Data
-public class AvatarVo {
-
-    /**
-     * 头像地址
-     */
-    private String imgUrl;
-
-}

+ 0 - 23
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/CacheListInfoVo.java

@@ -1,23 +0,0 @@
-package org.dromara.system.domain.vo;
-
-import lombok.Data;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * 缓存监控列表信息
- *
- * @author Michelle.Chung
- */
-@Data
-public class CacheListInfoVo {
-
-    private Properties info;
-
-    private Long dbSize;
-
-    private List<Map<String, String>> commandStats;
-
-}

+ 0 - 26
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/DeptTreeSelectVo.java

@@ -1,26 +0,0 @@
-package org.dromara.system.domain.vo;
-
-import cn.hutool.core.lang.tree.Tree;
-import lombok.Data;
-
-import java.util.List;
-
-/**
- * 角色部门列表树信息
- *
- * @author Michelle.Chung
- */
-@Data
-public class DeptTreeSelectVo {
-
-    /**
-     * 选中部门列表
-     */
-    private List<Long> checkedKeys;
-
-    /**
-     * 下拉树结构列表
-     */
-    private List<Tree<Long>> depts;
-
-}

+ 0 - 26
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MenuTreeSelectVo.java

@@ -1,26 +0,0 @@
-package org.dromara.system.domain.vo;
-
-import cn.hutool.core.lang.tree.Tree;
-import lombok.Data;
-
-import java.util.List;
-
-/**
- * 角色菜单列表树信息
- *
- * @author Michelle.Chung
- */
-@Data
-public class MenuTreeSelectVo {
-
-    /**
-     * 选中菜单列表
-     */
-    private List<Long> checkedKeys;
-
-    /**
-     * 菜单下拉树结构列表
-     */
-    private List<Tree<Long>> menus;
-
-}

+ 3 - 3
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java

@@ -25,7 +25,7 @@ public class MetaVo {
     /**
     /**
      * 设置为true,则不会被 <keep-alive>缓存
      * 设置为true,则不会被 <keep-alive>缓存
      */
      */
-    private boolean noCache;
+    private Boolean noCache;
 
 
     /**
     /**
      * 内链地址(http(s)://开头)
      * 内链地址(http(s)://开头)
@@ -37,7 +37,7 @@ public class MetaVo {
         this.icon = icon;
         this.icon = icon;
     }
     }
 
 
-    public MetaVo(String title, String icon, boolean noCache) {
+    public MetaVo(String title, String icon, Boolean noCache) {
         this.title = title;
         this.title = title;
         this.icon = icon;
         this.icon = icon;
         this.noCache = noCache;
         this.noCache = noCache;
@@ -49,7 +49,7 @@ public class MetaVo {
         this.link = link;
         this.link = link;
     }
     }
 
 
-    public MetaVo(String title, String icon, boolean noCache, String link) {
+    public MetaVo(String title, String icon, Boolean noCache, String link) {
         this.title = title;
         this.title = title;
         this.icon = icon;
         this.icon = icon;
         this.noCache = noCache;
         this.noCache = noCache;

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä