Parcourir la source

项目初始化

HuRongxin il y a 1 semaine
commit
5c3a344ce4
100 fichiers modifiés avec 6878 ajouts et 0 suppressions
  1. 18 0
      .editorconfig
  2. 47 0
      .gitignore
  3. 21 0
      LICENSE
  4. 187 0
      README.md
  5. 534 0
      pom.xml
  6. 25 0
      ruoyi-api/pom.xml
  7. 46 0
      ruoyi-api/ruoyi-api-bom/pom.xml
  8. 28 0
      ruoyi-api/ruoyi-api-resource/pom.xml
  9. 38 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteFileService.java
  10. 53 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteFileServiceMock.java
  11. 21 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteMailService.java
  12. 26 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteMessageService.java
  13. 47 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteMessageServiceStub.java
  14. 145 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteSmsService.java
  15. 44 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteFile.java
  16. 36 0
      ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteSms.java
  17. 33 0
      ruoyi-api/ruoyi-api-system/pom.xml
  18. 20 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteClientService.java
  19. 17 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteConfigService.java
  20. 26 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDataScopeService.java
  21. 37 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java
  22. 31 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDictService.java
  23. 27 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteLogService.java
  24. 28 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemotePermissionService.java
  25. 52 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteSocialService.java
  26. 45 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteTaskAssigneeService.java
  27. 28 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteTenantService.java
  28. 192 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java
  29. 71 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/SysUserOnline.java
  30. 89 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteLogininforBo.java
  31. 119 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteOperLogBo.java
  32. 129 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteSocialBo.java
  33. 56 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteTaskAssigneeBo.java
  34. 124 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteUserBo.java
  35. 70 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteClientVo.java
  36. 37 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDeptVo.java
  37. 76 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictDataVo.java
  38. 46 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictTypeVo.java
  39. 135 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteSocialVo.java
  40. 104 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaskAssigneeVo.java
  41. 91 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTenantVo.java
  42. 73 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteUserVo.java
  43. 151 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/LoginUser.java
  44. 46 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/PostDTO.java
  45. 42 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/RoleDTO.java
  46. 27 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/XcxLoginUser.java
  47. 35 0
      ruoyi-api/ruoyi-api-workflow/pom.xml
  48. 88 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/RemoteWorkflowService.java
  49. 70 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/RemoteWorkflowServiceMock.java
  50. 71 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteCompleteTask.java
  51. 30 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteFlowCopy.java
  52. 45 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcess.java
  53. 30 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcessReturn.java
  54. 41 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessDeleteEvent.java
  55. 72 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessEvent.java
  56. 65 0
      ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessTaskEvent.java
  57. 27 0
      ruoyi-auth/Dockerfile
  58. 139 0
      ruoyi-auth/pom.xml
  59. 23 0
      ruoyi-auth/src/main/java/org/dromara/auth/RuoYiAuthApplication.java
  60. 88 0
      ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java
  61. 62 0
      ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java
  62. 89 0
      ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java
  63. 231 0
      ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java
  64. 16 0
      ruoyi-auth/src/main/java/org/dromara/auth/domain/convert/TenantVoConvert.java
  65. 25 0
      ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/CaptchaVo.java
  66. 25 0
      ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginTenantVo.java
  67. 54 0
      ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java
  68. 28 0
      ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/TenantListVo.java
  69. 35 0
      ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaCategory.java
  70. 29 0
      ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java
  71. 31 0
      ruoyi-auth/src/main/java/org/dromara/auth/form/EmailLoginBody.java
  72. 32 0
      ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java
  73. 37 0
      ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java
  74. 29 0
      ruoyi-auth/src/main/java/org/dromara/auth/form/SmsLoginBody.java
  75. 35 0
      ruoyi-auth/src/main/java/org/dromara/auth/form/SocialLoginBody.java
  76. 28 0
      ruoyi-auth/src/main/java/org/dromara/auth/form/XcxLoginBody.java
  77. 168 0
      ruoyi-auth/src/main/java/org/dromara/auth/listener/UserActionListener.java
  78. 46 0
      ruoyi-auth/src/main/java/org/dromara/auth/properties/CaptchaProperties.java
  79. 29 0
      ruoyi-auth/src/main/java/org/dromara/auth/properties/UserPasswordProperties.java
  80. 44 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java
  81. 265 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java
  82. 86 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java
  83. 107 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java
  84. 86 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java
  85. 102 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java
  86. 91 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java
  87. 33 0
      ruoyi-auth/src/main/resources/application.yml
  88. 10 0
      ruoyi-auth/src/main/resources/banner.txt
  89. 28 0
      ruoyi-auth/src/main/resources/logback-plus.xml
  90. 57 0
      ruoyi-common/pom.xml
  91. 181 0
      ruoyi-common/ruoyi-common-alibaba-bom/pom.xml
  92. 262 0
      ruoyi-common/ruoyi-common-bom/pom.xml
  93. 35 0
      ruoyi-common/ruoyi-common-bus/pom.xml
  94. 15 0
      ruoyi-common/ruoyi-common-bus/src/main/java/org/dromara/common/bus/config/BusCustomConfiguration.java
  95. 1 0
      ruoyi-common/ruoyi-common-bus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  96. 104 0
      ruoyi-common/ruoyi-common-core/pom.xml
  97. 17 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java
  98. 52 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java
  99. 70 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java
  100. 41 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java

+ 18 - 0
.editorconfig

@@ -0,0 +1,18 @@
+# http://editorconfig.org
+root = true
+
+# 空格替代Tab缩进在各种编辑工具下效果一致
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{json,yml,yaml}]
+indent_size = 2
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false

+ 47 - 0
.gitignore

@@ -0,0 +1,47 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
+
+.flattened-pom.xml

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 RuoYi-Cloud-Plus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 187 - 0
README.md

@@ -0,0 +1,187 @@
+<img src="https://foruda.gitee.com/images/1679673780944866919/d908a86f_1766278.png" width="56%" height="56%">
+<div style="height: 10px; clear: both;"></div>
+
+- - -
+## 平台简介
+
+[![码云Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
+[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Cloud-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Cloud-Plus)
+[![Star](https://gitcode.com/dromara/RuoYi-Cloud-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Cloud-Plus)
+[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus/blob/2.X/LICENSE)
+[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Cloud-Plus)
+<br>
+[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.4.1-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
+[![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-21](https://img.shields.io/badge/JDK-21-green.svg)]()
+
+> Dromara RuoYi-Cloud-Plus `微服务通用权限管理系统` 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
+
+> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
+活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源
+
+> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
+
+> 官方前端项目地址: [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) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
+
+## 赞助商
+
+MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
+CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
+数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
+引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <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)
+
+# 本框架与RuoYi的功能差异
+
+| 功能          | 本框架                                                                                                               | RuoYi                                                                              |
+|-------------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
+| 前端项目        | 采用 Vue3 + TS + ElementPlus 重写                                                                                     | 基于Vue2/Vue3 + JS                                                                   | 
+| 后端项目结构      | 采用插件化 + 扩展包形式 结构解耦 易于扩展                                                                                           | 模块相互注入耦合严重难以扩展                                                                     | 
+| 后端代码风格      | 严格遵守Alibaba规范与项目统一配置的代码格式化                                                                                        | 代码书写与常规结构不同阅读障碍大                                                                   |
+| 分布式注册中心     | 采用 Alibaba Nacos 源码集成便于调试扩展与二次开发 框架还为其增加了各种监控                                                                     | 采用 Alibaba Nacos 自行搭建纯官方版本不可靠                                                      | 
+| 分布式配置中心     | 采用 Alibaba Nacos 源码集成便于调试扩展与二次开发 框架还为其增加了各种监控                                                                     | 采用 Alibaba Nacos 自行搭建纯官方版本不可靠                                                      | 
+| 服务网关        | 采用 SpringCloud Gateway 框架扩展了多种功能<br/>例如:内网鉴权、请求体缓存、跨域配置、请求响应日志等                                                   | 采用 SpringCloud Gateway 功能单一                                                        | 
+| 负载均衡        | 采用 SpringCloud Loadbalancer 扩展支持了开发团队路由 便于多团队开发调试                                                                 | 采用 SpringCloud Loadbalancer 功能单一                                                   |
+| RPC远程调用     | 采用 全新 Apache Dubbo 3.X 历史悠远不用多说                                                                                   | 采用 feign 功能有限编写方式 网络波动大 不稳定                                                        | 
+| 分布式限流熔断     | 采用 Alibaba Sentinel 源码集成便于调试扩展与二次开发 框架还为其增加了各种监控                                                                  | 采用 Alibaba Sentinel 自行搭建纯官方版本不可靠                                                   | 
+| 分布式事务       | 采用 Alibaba Seata 源码集成对接了Nacos与各种监控 简化了搭建部署流程                                                                      | 采用 Alibaba Seata 自行搭建纯官方版本 搭建繁琐与Nacos不挂钩 代码内使用方式怪异等                                |
+| Web容器       | 采用 Undertow 基于 XNIO 的高性能容器                                                                                        | 采用 Tomcat                                                                          |
+| 权限认证        | 采用 Sa-Token、Jwt 静态使用功能齐全 低耦合 高扩展                                                                                  | Spring Security 配置繁琐扩展性极差                                                          |
+| 权限注解        | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式        | 只支持是否存在匹配                                                                          |
+| 关系数据库支持     | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例)      | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换                                                    |
+| 缓存数据库       | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列                                                                             | Redis 简单 get set 支持                                                                |
+| Redis客户端    | 采用 Redisson Redis官方推荐 基于Netty的客户端工具<br/>支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan<br/>支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐<br/>连接池采用 common-pool Bug多经常性出问题              |
+| 缓存注解        | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能<br/>例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存                                      | 需手动编写Redis代码逻辑                                                                     |
+| ORM框架       | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多<br/>例如多租户插件 分页插件 乐观锁插件等等                                             | 采用 Mybatis 基于XML需要手写SQL                                                            |
+| SQL监控       | 采用 p6spy 可输出完整SQL与执行时间监控                                                                                          | log输出 需手动拼接sql与参数无法快速查看调试问题                                                        |
+| 数据分页        | 采用 Mybatis-Plus 分页插件<br/>框架对其进行了扩展 对象化分页对象 支持多种方式传参 支持前端多排序 复杂排序                                                  | 采用 PageHelper 仅支持单查询分页 参数只能从param传 只能单排序 功能扩展性差 体验不好                               |
+| 数据权限        | 采用 Mybatis-Plus 插件 自行分析拼接SQL 无感式过滤<br/>只需为Mapper设置好注解条件 支持多种自定义 不限于部门角色                                           | 采用 注解+aop 实现 基于部门角色 生成的sql兼容性差 不支持其他业务扩展<br/>生成sql后需手动拼接到具体业务sql上 对于多个Mapper查询不起作用 |
+| 数据脱敏        | 采用 注解 + jackson 序列化期间脱敏 支持不同模块不同的脱敏条件<br/>支持多种策略 如身份证、手机号、地址、邮箱、银行卡等 可自行扩展                                        | 无                                                                                  |
+| 数据加解密       | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密<br/>支持多种策略 如BASE64、AES、RSA、SM2、SM4等                                              | 无                                                                                  |
+| 数据翻译        | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译<br/>支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现                   | 无                                                                                  |
+| 多数据源框架      | 采用 dynamic-datasource 支持市面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源            | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差                                                     |
+| 多数据源事务      | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚                                                                          | 不支持                                                                                |
+| 数据库连接池      | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下                                                                        | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般                                               |
+| 数据库主键       | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁                                                                  | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一                                                     |
+| WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物                                                         | 无                                                                                  |
+| SSE推送       | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步                                                                               | 无                                                                                  |
+| 序列化         | 采用 Jackson Spring官方内置序列化 靠谱!!!                                                                                    | 采用 fastjson bugjson 远近闻名                                                           | 
+| 分布式幂等       | 参考美团GTIS防重系统简化实现(细节可看文档)                                                                                          | 手动编写注解基于aop实现                                                                      |
+| 分布式任务调度     | 采用 SnailJob 天生支持分布式 统一的管理中心 支持多种数据库 支持分片重试DAG任务流等                                                                 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造                                                   | 
+| 分布式日志中心     | 采用 ELK 业界成熟解决方案 实时收集所有服务的运行日志 快速发现定位问题                                                                            | 无                                                                                  | 
+| 分布式搜索引擎     | 采用 ElasticSearch、Easy-Es 以 Mybatis-Plus 方式操作 ElasticSearch                                                        | 无                                                                                  | 
+| 分布式消息队列     | 采用 支持 Kafka、RocketMQ、RabbitMQ 各种 延迟消息 事务消息 流消息                                                                    | 无                                                                                  | 
+| 分布式消息总线     | 采用 SpringCloud Bus 实现事件总线 跨服务通知 支持 Kafka、RocketMQ、RabbitMQ                                                        | 无                                                                                  |
+| 分库分表功能      | 采用 Apache Sharding-Proxy 代理服务无入侵支持分库分表 只需编写分库分表规则即可                                                               | 无                                                                                  |
+| 文件存储        | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储                                                     | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应                                                    |
+| 云存储         | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家                                                                          | 不支持                                                                                |
+| 短信          | 支持 阿里、腾讯 只需在yml配置好厂家密钥即可使用 接口化支持扩展其他厂家                                                                            | 不支持                                                                                |
+| 邮件          | 采用 mail-api 通用协议支持大部分邮件厂商                                                                                         | 不支持                                                                                |
+| 接口文档        | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了                                                     | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成                                                | 
+| 校验框架        | 采用 Validation 支持注解与工具类校验 注解支持国际化                                                                                  | 仅支持注解 且注解不支持国际化                                                                    |
+| Excel框架     | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                   | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           |
+| 工作流支持       | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能                                                                                   | 无                                                                                  |
+| 工具类框架       | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码                                                       | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等                                            | 
+| 服务监控框架      | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控                                    | 无                                                                                  | 
+| 全方位监控报警     | 采用 Prometheus、Grafana 多样化采集 多模板大屏展示 实时报警监控 提供详细的搭建文档                                                              | 无                                                                                  | 
+| 链路追踪        | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗<br/>用了它即可实时查看请求经过的每一处每一个节点                                            | 无                                                                                  |
+| 代码生成器       | 只需设计好表结构 一键生成所有crud代码与页面<br/>降低80%的开发量 把精力都投入到业务设计上<br/>框架为其适配MP、SpringDoc规范化代码 同时支持动态多数据源代码生成                    | 代码生成原生结构 只支持单数据源生成                                                                 |
+| 部署方式        | 支持 Docker 编排 一键搭建所有环境 让开发人员从此不再为搭建环境而烦恼                                                                           | 原生jar部署 其他环境需手动下载安装 自行搭建                                                           | 
+| 项目路径修改      | 提供详细的修改方案文档 并为其做了一些改动 非常简单即可修改成自己想要的                                                                              | 需要做很多改造 文档说明有限                                                                     |
+| 国际化         | 基于请求头动态返回不同语种的文本内容 开发难度低 有对应的工具类 支持大部分注解内容国际化                                                                     | 只提供基础功能 其他需自行编写扩展                                                                  |
+| 代码单例测试      | 提供单例测试 使用方式编写方法与maven多环境单测插件                                                                                      | 只提供基础功能 其他需自行编写扩展                                                                  |
+| Demo案例      | 提供框架功能的实际使用案例 单独一个模块提供了很多很全                                                                                       | 无                                                                                  |
+
+## 本框架与RuoYi的业务差异
+
+| 业务     | 功能说明                                    | 本框架 | RuoYi            |
+|--------|-----------------------------------------|-----|------------------|
+| 租户管理   | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等         | 支持  | 无                |
+| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等             | 支持  | 无                |
+| 用户管理   | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等          | 支持  | 支持               |
+| 部门管理   | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限          | 支持  | 支持               |
+| 岗位管理   | 配置系统用户所属担任职务                            | 支持  | 支持               |
+| 菜单管理   | 配置系统菜单、操作权限、按钮权限标识等                     | 支持  | 支持               |
+| 角色管理   | 角色菜单权限分配、设置角色按机构进行数据范围权限划分              | 支持  | 支持               |
+| 字典管理   | 对系统中经常使用的一些较为固定的数据进行维护                  | 支持  | 支持               |
+| 参数管理   | 对系统动态配置常用参数                             | 支持  | 支持               |
+| 通知公告   | 系统通知公告信息发布维护                            | 支持  | 支持               |
+| 操作日志   | 系统正常操作日志记录和查询 系统异常信息日志记录和查询             | 支持  | 支持               |
+| 登录日志   | 系统登录日志记录查询包含登录异常                        | 支持  | 支持               |
+| 文件管理   | 系统文件展示、上传、下载、删除等管理                      | 支持  | 无                |
+| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理          | 支持  | 无                |
+| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作                   | 支持  | 支持               |
+| 定时任务   | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等         | 支持  | 仅支持任务与日志管理       |
+| 代码生成   | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持  | 仅支持单数据源          |
+| 系统接口   | 根据业务代码自动生成相关的api接口文档                    | 支持  | 支持               |
+| 服务监控   | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等     | 支持  | 仅支持单机CPU、内存、磁盘监控 |
+| 缓存监控   | 对系统的缓存信息查询,命令统计等。                       | 支持  | 支持               |
+| 使用案例   | 系统的一些功能案例                               | 支持  | 不支持              |
+
+## 参考文档
+
+使用框架前请仔细阅读文档重点注意事项
+<br>
+>[初始化项目 必看](https://plus-doc.dromara.org/#/ruoyi-cloud-plus/quickstart/init)
+>>[https://plus-doc.dromara.org/#/ruoyi-cloud-plus/quickstart/init](https://plus-doc.dromara.org/#/ruoyi-cloud-plus/quickstart/init)
+>
+>[专栏与视频 入门必看](https://plus-doc.dromara.org/#/common/column)
+>>[https://plus-doc.dromara.org/#/common/column](https://plus-doc.dromara.org/#/common/column)
+>
+>[部署项目 必看](https://plus-doc.dromara.org/#/ruoyi-cloud-plus/quickstart/deploy)
+>>[https://plus-doc.dromara.org/#/ruoyi-cloud-plus/quickstart/deploy](https://plus-doc.dromara.org/#/ruoyi-cloud-plus/quickstart/deploy)
+>
+>[如何加群](https://plus-doc.dromara.org/#/common/add_group)
+>>[https://plus-doc.dromara.org/#/common/add_group](https://plus-doc.dromara.org/#/common/add_group)
+>
+>[参考文档 Wiki](https://plus-doc.dromara.org)
+>>[https://plus-doc.dromara.org](https://plus-doc.dromara.org)
+
+
+## 软件架构图
+
+![Plus部署架构图](https://foruda.gitee.com/images/1678980131147747524/5c2d5a5c_1766278.png "Plus部署架构图.png")
+
+## 贡献代码
+
+[参与贡献的方式 https://plus-doc.dromara.org/#/common/contribution](https://plus-doc.dromara.org/#/common/contribution)
+
+## 捐献作者
+
+作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭  
+<img src="https://foruda.gitee.com/images/1678975784848381069/d8661ed9_1766278.png" width="300px" height="450px" />
+<img src="https://foruda.gitee.com/images/1678975801230205215/6f96229d_1766278.png" width="300px" height="450px" />
+
+## 演示图例
+
+|                                                                                            |                                                                                            |
+|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
+| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1735829153637063344/3c21fd4c_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1735829181303499815/4522cefa_1419627.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1735829377205259767/76a705d7_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1722959592856812900/e2d0d342_1419627.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |
+
+

+ 534 - 0
pom.xml

@@ -0,0 +1,534 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.dromara</groupId>
+    <artifactId>ruoyi-cloud-plus</artifactId>
+    <version>${revision}</version>
+
+    <name>RuoYi-Cloud-Plus</name>
+    <url>https://gitee.com/dromara/RuoYi-Cloud-Plus</url>
+    <description>Dromara RuoYi-Cloud-Plus微服务系统</description>
+
+    <properties>
+        <revision>2.4.1</revision>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>17</java.version>
+        <spring-boot.version>3.4.7</spring-boot.version>
+        <spring-cloud.version>2024.0.0</spring-cloud.version>
+        <spring-boot-admin.version>3.4.7</spring-boot-admin.version>
+        <mybatis.version>3.5.16</mybatis.version>
+        <mybatis-plus.version>3.5.12</mybatis-plus.version>
+        <p6spy.version>3.9.1</p6spy.version>
+        <dynamic-ds.version>4.3.1</dynamic-ds.version>
+        <velocity.version>2.3</velocity.version>
+        <swagger.core.version>2.2.30</swagger.core.version>
+        <springdoc.version>2.8.8</springdoc.version>
+        <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
+        <fastexcel.version>1.2.0</fastexcel.version>
+        <hutool.version>5.8.38</hutool.version>
+        <redisson.version>3.50.0</redisson.version>
+        <lock4j.version>2.2.7</lock4j.version>
+        <snailjob.version>1.5.0</snailjob.version>
+        <satoken.version>1.44.0</satoken.version>
+        <lombok.version>1.18.36</lombok.version>
+        <logstash.version>7.4</logstash.version>
+        <easy-es.version>3.0.0</easy-es.version>
+        <skywalking-toolkit.version>9.3.0</skywalking-toolkit.version>
+        <bouncycastle.version>1.80</bouncycastle.version>
+        <mapstruct-plus.version>1.4.8</mapstruct-plus.version>
+        <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
+        <justauth.version>1.16.7</justauth.version>
+        <!-- 离线IP地址定位库 -->
+        <ip2region.version>2.7.0</ip2region.version>
+        <!-- 临时修复 fastjson 漏洞 -->
+        <fastjson.version>1.2.83</fastjson.version>
+        <!-- OSS 配置 -->
+        <aws.sdk.version>2.28.22</aws.sdk.version>
+        <!-- SMS 配置 -->
+        <sms4j.version>3.3.4</sms4j.version>
+        <!-- 面向运行时的D-ORM依赖 -->
+        <anyline.version>8.7.2-20250603</anyline.version>
+        <!-- 工作流配置 -->
+        <warm-flow.version>1.7.4</warm-flow.version>
+        <!-- mq配置 -->
+        <rocketmq.version>2.3.0</rocketmq.version>
+
+        <!-- 插件版本 -->
+        <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
+        <maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
+        <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
+        <!-- 打包默认跳过测试 -->
+        <skipTests>true</skipTests>
+    </properties>
+
+    <profiles>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>dev</profiles.active>
+                <nacos.server>127.0.0.1:8848</nacos.server>
+                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
+                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
+                <nacos.username>nacos</nacos.username>
+                <nacos.password>nacos</nacos.password>
+                <logstash.address>127.0.0.1:4560</logstash.address>
+            </properties>
+        </profile>
+        <profile>
+            <id>hrx</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>5346d34f-9daf-4259-bc2f-5219f814987f</profiles.active>
+                <nacos.server>127.0.0.1:8848</nacos.server>
+                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
+                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
+                <nacos.username>nacos</nacos.username>
+                <nacos.password>nacos</nacos.password>
+                <logstash.address>127.0.0.1:4560</logstash.address>
+            </properties>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>prod</id>
+            <properties>
+                <profiles.active>prod</profiles.active>
+                <nacos.server>127.0.0.1:8848</nacos.server>
+                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
+                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
+                <nacos.username>nacos</nacos.username>
+                <nacos.password>nacos</nacos.password>
+                <logstash.address>127.0.0.1:4560</logstash.address>
+            </properties>
+        </profile>
+    </profiles>
+
+    <!-- 依赖声明 -->
+    <dependencyManagement>
+        <dependencies>
+
+            <!-- SpringCloud 微服务 -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- SpringCloud Alibaba 微服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-alibaba-bom</artifactId>
+                <version>${revision}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- SpringBoot 依赖配置 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- hutool 的依赖配置-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-bom</artifactId>
+                <version>${hutool.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- JustAuth 的依赖配置-->
+            <dependency>
+                <groupId>me.zhyd.oauth</groupId>
+                <artifactId>JustAuth</artifactId>
+                <version>${justauth.version}</version>
+            </dependency>
+
+            <!-- common 的依赖配置-->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-bom</artifactId>
+                <version>${revision}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- api 的依赖配置-->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-api-bom</artifactId>
+                <version>${revision}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-core</artifactId>
+                <version>${satoken.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-spring-boot3-starter</artifactId>
+                <version>${satoken.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.mybatis</groupId>
+                <artifactId>mybatis</artifactId>
+                <version>${mybatis.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-jsqlparser</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-annotation</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+            <!-- sql性能分析插件 -->
+            <dependency>
+                <groupId>p6spy</groupId>
+                <artifactId>p6spy</artifactId>
+                <version>${p6spy.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.swagger.core.v3</groupId>
+                <artifactId>swagger-annotations</artifactId>
+                <version>${swagger.core.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.therapi</groupId>
+                <artifactId>therapi-runtime-javadoc</artifactId>
+                <version>${therapi-javadoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>cn.idev.excel</groupId>
+                <artifactId>fastexcel</artifactId>
+                <version>${fastexcel.version}</version>
+            </dependency>
+
+            <!-- 代码生成使用模板 -->
+            <dependency>
+                <groupId>org.apache.velocity</groupId>
+                <artifactId>velocity-engine-core</artifactId>
+                <version>${velocity.version}</version>
+            </dependency>
+
+            <!--redisson-->
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-boot-starter</artifactId>
+                <version>${redisson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+                <version>${lock4j.version}</version>
+            </dependency>
+
+            <!--  SnailJob Client -->
+            <dependency>
+                <groupId>com.aizuda</groupId>
+                <artifactId>snail-job-client-starter</artifactId>
+                <version>${snailjob.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.aizuda</groupId>
+                <artifactId>snail-job-client-job-core</artifactId>
+                <version>${snailjob.version}</version>
+            </dependency>
+
+            <!-- 加密包引入 -->
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15to18</artifactId>
+                <version>${bouncycastle.version}</version>
+            </dependency>
+
+            <!-- logstash -->
+            <dependency>
+                <groupId>net.logstash.logback</groupId>
+                <artifactId>logstash-logback-encoder</artifactId>
+                <version>${logstash.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara.easy-es</groupId>
+                <artifactId>easy-es-boot-starter</artifactId>
+                <version>${easy-es.version}</version>
+            </dependency>
+
+            <!-- skywalking 整合 logback -->
+            <dependency>
+                <groupId>org.apache.skywalking</groupId>
+                <artifactId>apm-toolkit-logback-1.x</artifactId>
+                <version>${skywalking-toolkit.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.skywalking</groupId>
+                <artifactId>apm-toolkit-trace</artifactId>
+                <version>${skywalking-toolkit.version}</version>
+            </dependency>
+
+            <!--  AWS SDK for Java 2.x  -->
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>s3</artifactId>
+                <version>${aws.sdk.version}</version>
+            </dependency>
+            <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>s3-transfer-manager</artifactId>
+                <version>${aws.sdk.version}</version>
+            </dependency>
+            <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>netty-nio-client</artifactId>
+                <version>${aws.sdk.version}</version>
+            </dependency>
+
+            <!--短信sms4j-->
+            <dependency>
+                <groupId>org.dromara.sms4j</groupId>
+                <artifactId>sms4j-spring-boot-starter</artifactId>
+                <version>${sms4j.version}</version>
+            </dependency>
+
+            <!-- 离线IP地址定位库 ip2region -->
+            <dependency>
+                <groupId>org.lionsoul</groupId>
+                <artifactId>ip2region</artifactId>
+                <version>${ip2region.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+            <!-- dubbo 专用 -->
+            <dependency>
+                <groupId>redis.clients</groupId>
+                <artifactId>jedis</artifactId>
+                <version>5.1.0</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.github.linpeilie</groupId>
+                <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+                <version>${mapstruct-plus.version}</version>
+            </dependency>
+
+            <!--消息队列-->
+            <dependency>
+                <groupId>org.apache.rocketmq</groupId>
+                <artifactId>rocketmq-spring-boot-starter</artifactId>
+                <version>${rocketmq.version}</version>
+            </dependency>
+
+            <!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ -->
+            <dependency>
+                <groupId>org.dromara.warm</groupId>
+                <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
+                <version>${warm-flow.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.dromara.warm</groupId>
+                <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
+                <version>${warm-flow.version}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+
+    <modules>
+        <module>ruoyi-auth</module>
+        <module>ruoyi-gateway</module>
+        <module>ruoyi-visual</module>
+        <module>ruoyi-modules</module>
+        <module>ruoyi-api</module>
+        <module>ruoyi-common</module>
+        <module>ruoyi-example</module>
+    </modules>
+    <packaging>pom</packaging>
+
+    <dependencies>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>${maven-compiler-plugin.version}</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>com.github.therapi</groupId>
+                            <artifactId>therapi-runtime-javadoc-scribe</artifactId>
+                            <version>0.15.0</version>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.springframework.boot</groupId>
+                            <artifactId>spring-boot-configuration-processor</artifactId>
+                            <version>${spring-boot.version}</version>
+                        </path>
+                        <path>
+                            <groupId>io.github.linpeilie</groupId>
+                            <artifactId>mapstruct-plus-processor</artifactId>
+                            <version>${mapstruct-plus.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok-mapstruct-binding</artifactId>
+                            <version>${mapstruct-plus.lombok.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                    <compilerArgs>
+                        <arg>-parameters</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+            <!-- 单元测试使用 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${maven-surefire-plugin.version}</version>
+                <configuration>
+                    <argLine>-Dfile.encoding=UTF-8</argLine>
+                    <!-- 根据打包环境执行对应的@Tag测试方法 -->
+                    <groups>${profiles.active}</groups>
+                    <!-- 排除标签 -->
+                    <excludedGroups>exclude</excludedGroups>
+                </configuration>
+            </plugin>
+            <!-- 统一版本号管理 -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>flatten-maven-plugin</artifactId>
+                <version>${flatten-maven-plugin.version}</version>
+                <configuration>
+                    <updatePomFile>true</updatePomFile>
+                    <flattenMode>resolveCiFriendliesOnly</flattenMode>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>flatten</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>flatten</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>flatten.clean</id>
+                        <phase>clean</phase>
+                        <goals>
+                            <goal>clean</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!-- 关闭过滤 -->
+                <filtering>false</filtering>
+            </resource>
+            <resource>
+                <directory>src/main/webapp/</directory>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!-- 引入所有 匹配文件进行过滤 -->
+                <includes>
+                    <include>application*</include>
+                    <include>bootstrap*</include>
+                    <include>logback*</include>
+                </includes>
+                <!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 -->
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>huawei nexus</name>
+            <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>huawei nexus</name>
+            <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+</project>
+

+ 25 - 0
ruoyi-api/pom.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-cloud-plus</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <modules>
+        <module>ruoyi-api-bom</module>
+        <module>ruoyi-api-system</module>
+        <module>ruoyi-api-resource</module>
+        <module>ruoyi-api-workflow</module>
+    </modules>
+
+    <artifactId>ruoyi-api</artifactId>
+    <packaging>pom</packaging>
+
+    <description>
+        ruoyi-api系统接口
+    </description>
+
+</project>

+ 46 - 0
ruoyi-api/ruoyi-api-bom/pom.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.dromara</groupId>
+    <artifactId>ruoyi-api-bom</artifactId>
+    <packaging>pom</packaging>
+    <version>${revision}</version>
+
+    <description>
+        ruoyi-api-bom api依赖项
+    </description>
+
+    <properties>
+        <revision>2.4.1</revision>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- 系统接口 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-api-system</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 资源服务接口 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-api-resource</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- workflow接口 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-api-workflow</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+</project>

+ 28 - 0
ruoyi-api/ruoyi-api-resource/pom.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-api</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-api-resource</artifactId>
+
+    <description>
+        ruoyi-api-resource 资源服务接口模块
+    </description>
+
+    <dependencies>
+
+        <!-- RuoYi Common Core-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 38 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteFileService.java

@@ -0,0 +1,38 @@
+package org.dromara.resource.api;
+
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.resource.api.domain.RemoteFile;
+
+import java.util.List;
+
+/**
+ * 文件服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteFileService {
+
+    /**
+     * 上传文件
+     *
+     * @param file 文件信息
+     * @return 结果
+     */
+    RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) throws ServiceException;
+
+    /**
+     * 通过ossId查询对应的url
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return url串逗号分隔
+     */
+    String selectUrlByIds(String ossIds);
+
+    /**
+     * 通过ossId查询列表
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return 列表
+     */
+    List<RemoteFile> selectByIds(String ossIds);
+}

+ 53 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteFileServiceMock.java

@@ -0,0 +1,53 @@
+package org.dromara.resource.api;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.resource.api.domain.RemoteFile;
+
+import java.util.List;
+
+/**
+ * 文件服务(降级处理)
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class RemoteFileServiceMock implements RemoteFileService {
+
+    /**
+     * 上传文件
+     *
+     * @param file 文件信息
+     * @return 结果
+     */
+    @Override
+    public RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) {
+        log.warn("服务调用异常 -> 降级处理");
+        return null;
+    }
+
+    /**
+     * 通过ossId查询对应的url
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return url串逗号分隔
+     */
+    @Override
+    public String selectUrlByIds(String ossIds) {
+        log.warn("服务调用异常 -> 降级处理");
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 通过ossId查询列表
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return 列表
+     */
+    @Override
+    public List<RemoteFile> selectByIds(String ossIds) {
+        log.warn("服务调用异常 -> 降级处理");
+        return List.of();
+    }
+
+}

+ 21 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteMailService.java

@@ -0,0 +1,21 @@
+package org.dromara.resource.api;
+
+import org.dromara.common.core.exception.ServiceException;
+
+/**
+ * 邮件服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteMailService {
+
+    /**
+     * 发送邮件
+     *
+     * @param to      接收人
+     * @param subject 标题
+     * @param text    内容
+     */
+    void send(String to, String subject, String text) throws ServiceException;
+
+}

+ 26 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteMessageService.java

@@ -0,0 +1,26 @@
+package org.dromara.resource.api;
+
+import java.util.List;
+
+/**
+ * 消息服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteMessageService {
+
+    /**
+     * 发送消息
+     *
+     * @param sessionKey session主键 一般为用户id
+     * @param message    消息文本
+     */
+    void publishMessage(List<Long> sessionKey, String message);
+
+    /**
+     * 发布订阅的消息(群发)
+     *
+     * @param message 消息内容
+     */
+    void publishAll(String message);
+}

+ 47 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteMessageServiceStub.java

@@ -0,0 +1,47 @@
+package org.dromara.resource.api;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+/**
+ * 消息服务
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class RemoteMessageServiceStub implements RemoteMessageService {
+
+    private final RemoteMessageService remoteMessageService;
+
+    /**
+     * 发送消息
+     *
+     * @param sessionKey session主键 一般为用户id
+     * @param message    消息文本
+     */
+    @Override
+    public void publishMessage(List<Long> sessionKey, String message) {
+        try {
+            remoteMessageService.publishMessage(sessionKey, message);
+        } catch (Exception e) {
+            log.warn("推送功能未开启或服务未找到");
+        }
+    }
+
+    /**
+     * 发布订阅的消息(群发)
+     *
+     * @param message 消息内容
+     */
+    @Override
+    public void publishAll(String message) {
+        try {
+            remoteMessageService.publishAll(message);
+        } catch (Exception e) {
+            log.warn("推送功能未开启或服务未找到");
+        }
+    }
+}

+ 145 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/RemoteSmsService.java

@@ -0,0 +1,145 @@
+package org.dromara.resource.api;
+
+import org.dromara.resource.api.domain.RemoteSms;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * 短信服务
+ *
+ * @author Feng
+ */
+public interface RemoteSmsService {
+
+    /**
+     * 同步方法:发送固定消息模板短信
+     *
+     * @param phone   目标手机号
+     * @param message 短信内容
+     * @return 封装了短信发送结果的 RemoteSms 对象
+     */
+    RemoteSms sendMessage(String phone, String message);
+
+    /**
+     * 同步方法:发送固定消息模板多模板参数短信
+     *
+     * @param phone    目标手机号
+     * @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
+     * @return 封装了短信发送结果的 RemoteSms 对象
+     */
+    RemoteSms sendMessage(String phone, LinkedHashMap<String, String> messages);
+
+    /**
+     * 同步方法:使用自定义模板发送短信
+     *
+     * @param phone      目标手机号
+     * @param templateId 短信模板ID
+     * @param messages   短信模板参数,使用 LinkedHashMap 以保持参数顺序
+     * @return 封装了短信发送结果的 RemoteSms 对象
+     */
+    RemoteSms sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages);
+
+    /**
+     * 同步方法:群发固定模板短信
+     *
+     * @param phones  目标手机号列表(1~1000)
+     * @param message 短信内容
+     * @return 封装了短信发送结果的 RemoteSms 对象
+     */
+    RemoteSms messageTexting(List<String> phones, String message);
+
+    /**
+     * 同步方法:使用自定义模板群发短信
+     *
+     * @param phones     目标手机号列表(1~1000)(1~1000)
+     * @param templateId 短信模板ID
+     * @param messages   短信模板参数,使用 LinkedHashMap 以保持参数顺序
+     * @return 封装了短信发送结果的 RemoteSms 对象
+     */
+    RemoteSms messageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages);
+
+    /**
+     * 异步方法:发送固定消息模板短信
+     *
+     * @param phone   目标手机号
+     * @param message 短信内容
+     */
+    void sendMessageAsync(String phone, String message);
+
+    /**
+     * 异步方法:使用自定义模板发送短信
+     *
+     * @param phone      目标手机号
+     * @param templateId 短信模板ID
+     * @param messages   短信模板参数,使用 LinkedHashMap 以保持参数顺序
+     */
+    void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages);
+
+    /**
+     * 延迟发送:发送固定消息模板短信
+     *
+     * @param phone       目标手机号
+     * @param message     短信内容
+     * @param delayedTime 延迟发送时间(毫秒)
+     */
+    void delayMessage(String phone, String message, Long delayedTime);
+
+    /**
+     * 延迟发送:使用自定义模板发送定时短信
+     *
+     * @param phone       目标手机号
+     * @param templateId  短信模板ID
+     * @param messages    短信模板参数,使用 LinkedHashMap 以保持参数顺序
+     * @param delayedTime 延迟发送时间(毫秒)
+     */
+    void delayMessage(String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
+
+    /**
+     * 延迟群发:群发延迟短信
+     *
+     * @param phones      目标手机号列表(1~1000)
+     * @param message     短信内容
+     * @param delayedTime 延迟发送时间(毫秒)
+     */
+    void delayMessageTexting(List<String> phones, String message, Long delayedTime);
+
+    /**
+     * 延迟群发:使用自定义模板发送群体延迟短信
+     *
+     * @param phones      目标手机号列表(1~1000)
+     * @param templateId  短信模板ID
+     * @param messages    短信模板参数,使用 LinkedHashMap 以保持参数顺序
+     * @param delayedTime 延迟发送时间(毫秒)
+     */
+    void delayMessageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
+
+    /**
+     * 加入黑名单
+     *
+     * @param phone 手机号
+     */
+    void addBlacklist(String phone);
+
+    /**
+     * 加入黑名单
+     *
+     * @param phones 手机号列表
+     */
+    void addBlacklist(List<String> phones);
+
+    /**
+     * 移除黑名单
+     *
+     * @param phone 手机号
+     */
+    void removeBlacklist(String phone);
+
+    /**
+     * 移除黑名单
+     *
+     * @param phones 手机号
+     */
+    void removeBlacklist(List<String> phones);
+
+}

+ 44 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteFile.java

@@ -0,0 +1,44 @@
+package org.dromara.resource.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 文件信息
+ *
+ * @author ruoyi
+ */
+@Data
+public class RemoteFile implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * oss主键
+     */
+    private Long ossId;
+
+    /**
+     * 文件名称
+     */
+    private String name;
+
+    /**
+     * 文件地址
+     */
+    private String url;
+
+    /**
+     * 原名
+     */
+    private String originalName;
+
+    /**
+     * 文件后缀名
+     */
+    private String fileSuffix;
+
+}

+ 36 - 0
ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteSms.java

@@ -0,0 +1,36 @@
+package org.dromara.resource.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 文件信息
+ *
+ * @author ruoyi
+ */
+@Data
+public class RemoteSms implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 是否成功
+     */
+    private Boolean success;
+
+    /**
+     * 配置标识名 如未配置取对应渠道名例如 Alibaba
+     */
+    private String configId;
+
+    /**
+     * 厂商原返回体
+     * <p>
+     * 可自行转换为 SDK 对应的 SendSmsResponse
+     */
+    private String response;
+
+}

+ 33 - 0
ruoyi-api/ruoyi-api-system/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-api</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-api-system</artifactId>
+
+    <description>
+        ruoyi-api-system系统接口模块
+    </description>
+
+    <dependencies>
+
+        <!-- RuoYi Common Core-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-excel</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 20 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteClientService.java

@@ -0,0 +1,20 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+
+/**
+ * 客户端服务
+ *
+ * @author Michelle.Chung
+ */
+public interface RemoteClientService {
+
+    /**
+     * 根据客户端id获取客户端详情
+     *
+     * @param clientId 客户端id
+     * @return 客户端对象
+     */
+    RemoteClientVo queryByClientId(String clientId);
+
+}

+ 17 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteConfigService.java

@@ -0,0 +1,17 @@
+package org.dromara.system.api;
+
+/**
+ * 配置服务
+ *
+ * @author Michelle.Chung
+ */
+public interface RemoteConfigService {
+
+    /**
+     * 获取注册开关
+     * @param tenantId 租户id
+     * @return true开启,false关闭
+     */
+    boolean selectRegisterEnabled(String tenantId);
+
+}

+ 26 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDataScopeService.java

@@ -0,0 +1,26 @@
+package org.dromara.system.api;
+
+/**
+ * 数据权限服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteDataScopeService {
+
+    /**
+     * 获取角色自定义权限语句
+     *
+     * @param roleId 角色ID
+     * @return 返回角色的自定义权限语句,如果没有找到则返回 null
+     */
+    String getRoleCustom(Long roleId);
+
+    /**
+     * 获取部门和下级权限语句
+     *
+     * @param deptId 部门ID
+     * @return 返回部门及其下级的权限语句,如果没有找到则返回 null
+     */
+    String getDeptAndChild(Long deptId);
+
+}

+ 37 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java

@@ -0,0 +1,37 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.vo.RemoteDeptVo;
+
+import java.util.List;
+
+/**
+ * 部门服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteDeptService {
+
+    /**
+     * 通过部门ID查询部门名称
+     *
+     * @param deptIds 部门ID串逗号分隔
+     * @return 部门名称串逗号分隔
+     */
+    String selectDeptNameByIds(String deptIds);
+
+    /**
+     * 根据部门ID查询部门负责人
+     *
+     * @param deptId 部门ID,用于指定需要查询的部门
+     * @return 返回该部门的负责人ID
+     */
+    Long selectDeptLeaderById(Long deptId);
+
+    /**
+     * 查询部门
+     *
+     * @return 部门列表
+     */
+    List<RemoteDeptVo> selectDeptsByList();
+
+}

+ 31 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDictService.java

@@ -0,0 +1,31 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+import org.dromara.system.api.domain.vo.RemoteDictTypeVo;
+
+import java.util.List;
+
+/**
+ * 字典服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteDictService {
+
+    /**
+     * 根据字典类型查询信息
+     *
+     * @param dictType 字典类型
+     * @return 字典类型
+     */
+    RemoteDictTypeVo selectDictTypeByType(String dictType);
+
+    /**
+     * 根据字典类型查询字典数据
+     *
+     * @param dictType 字典类型
+     * @return 字典数据集合信息
+     */
+    List<RemoteDictDataVo> selectDictDataByType(String dictType);
+
+}

+ 27 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteLogService.java

@@ -0,0 +1,27 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.bo.RemoteLogininforBo;
+import org.dromara.system.api.domain.bo.RemoteOperLogBo;
+
+/**
+ * 日志服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteLogService {
+
+    /**
+     * 保存系统日志
+     *
+     * @param sysOperLog 日志实体
+     */
+    void saveLog(RemoteOperLogBo sysOperLog);
+
+    /**
+     * 保存访问记录
+     *
+     * @param sysLogininfor 访问实体
+     */
+    void saveLogininfor(RemoteLogininforBo sysLogininfor);
+
+}

+ 28 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemotePermissionService.java

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

+ 52 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteSocialService.java

@@ -0,0 +1,52 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.bo.RemoteSocialBo;
+import org.dromara.system.api.domain.vo.RemoteSocialVo;
+
+import java.util.List;
+
+/**
+ * 社会化关系服务
+ *
+ * @author Michelle.Chung
+ */
+public interface RemoteSocialService {
+
+    /**
+     * 根据 authId 查询用户授权信息
+     *
+     * @param authId 认证id
+     * @return 授权信息
+     */
+    List<RemoteSocialVo> selectByAuthId(String authId);
+
+    /**
+     * 查询列表
+     *
+     * @param bo 社会化关系业务对象
+     */
+    List<RemoteSocialVo> queryList(RemoteSocialBo bo);
+
+    /**
+     * 保存社会化关系
+     *
+     * @param bo 社会化关系业务对象
+     */
+    void insertByBo(RemoteSocialBo bo);
+
+    /**
+     * 更新社会化关系
+     *
+     * @param bo 社会化关系业务对象
+     */
+    void updateByBo(RemoteSocialBo bo);
+
+    /**
+     * 删除社会化关系
+     *
+     * @param socialId 社会化关系ID
+     * @return 结果
+     */
+    Boolean deleteWithValidById(Long socialId);
+
+}

+ 45 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteTaskAssigneeService.java

@@ -0,0 +1,45 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.bo.RemoteTaskAssigneeBo;
+import org.dromara.system.api.domain.vo.RemoteTaskAssigneeVo;
+
+/**
+ * 工作流设计器获取任务执行人
+ *
+ * @author Lion Li
+ */
+public interface RemoteTaskAssigneeService {
+
+    /**
+     * 查询角色并返回任务指派的列表,支持分页
+     *
+     * @param taskQuery 查询条件
+     * @return 办理人
+     */
+    RemoteTaskAssigneeVo selectRolesByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
+
+    /**
+     * 查询岗位并返回任务指派的列表,支持分页
+     *
+     * @param taskQuery 查询条件
+     * @return 办理人
+     */
+    RemoteTaskAssigneeVo selectPostsByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
+
+    /**
+     * 查询部门并返回任务指派的列表,支持分页
+     *
+     * @param taskQuery 查询条件
+     * @return 办理人
+     */
+    RemoteTaskAssigneeVo selectDeptsByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
+
+    /**
+     * 查询用户并返回任务指派的列表,支持分页
+     *
+     * @param taskQuery 查询条件
+     * @return 办理人
+     */
+    RemoteTaskAssigneeVo selectUsersByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
+
+}

+ 28 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteTenantService.java

@@ -0,0 +1,28 @@
+package org.dromara.system.api;
+
+
+import org.dromara.system.api.domain.vo.RemoteTenantVo;
+
+import java.util.List;
+
+/**
+ * 租户服务
+ *
+ * @author zhujie
+ */
+public interface RemoteTenantService {
+
+    /**
+     * 根据租户id获取租户详情
+     * @param tenantId 租户id
+     * @return 结果
+     */
+    RemoteTenantVo queryByTenantId(String tenantId);
+
+    /**
+     * 获取租户列表
+     * @return 结果
+     */
+    List<RemoteTenantVo> queryList();
+
+}

+ 192 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java

@@ -0,0 +1,192 @@
+package org.dromara.system.api;
+
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.system.api.domain.bo.RemoteUserBo;
+import org.dromara.system.api.domain.vo.RemoteUserVo;
+import org.dromara.system.api.model.LoginUser;
+import org.dromara.system.api.model.XcxLoginUser;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 用户服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteUserService {
+
+    /**
+     * 通过用户名查询用户信息
+     *
+     * @param username 用户名
+     * @param tenantId 租户id
+     * @return 结果
+     */
+    LoginUser getUserInfo(String username, String tenantId) throws UserException;
+
+    /**
+     * 通过用户id查询用户信息
+     *
+     * @param userId   用户id
+     * @param tenantId 租户id
+     * @return 结果
+     */
+    LoginUser getUserInfo(Long userId, String tenantId) throws UserException;
+
+    /**
+     * 通过手机号查询用户信息
+     *
+     * @param phonenumber 手机号
+     * @param tenantId    租户id
+     * @return 结果
+     */
+    LoginUser getUserInfoByPhonenumber(String phonenumber, String tenantId) throws UserException;
+
+    /**
+     * 通过邮箱查询用户信息
+     *
+     * @param email    邮箱
+     * @param tenantId 租户id
+     * @return 结果
+     */
+    LoginUser getUserInfoByEmail(String email, String tenantId) throws UserException;
+
+    /**
+     * 通过openid查询用户信息
+     *
+     * @param openid openid
+     * @return 结果
+     */
+    XcxLoginUser getUserInfoByOpenid(String openid) throws UserException;
+
+    /**
+     * 注册用户信息
+     *
+     * @param remoteUserBo 用户信息
+     * @return 结果
+     */
+    Boolean registerUserInfo(RemoteUserBo remoteUserBo) throws UserException, ServiceException;
+
+    /**
+     * 通过userId查询用户账户
+     *
+     * @param userId 用户id
+     * @return 结果
+     */
+    String selectUserNameById(Long userId);
+
+    /**
+     * 通过用户ID查询用户昵称
+     *
+     * @param userId 用户id
+     * @return 结果
+     */
+    String selectNicknameById(Long userId);
+
+    /**
+     * 通过用户ID查询用户账户
+     *
+     * @param userIds 用户ID 多个用逗号隔开
+     * @return 用户名称
+     */
+    String selectNicknameByIds(String userIds);
+
+    /**
+     * 通过用户ID查询用户手机号
+     *
+     * @param userId 用户id
+     * @return 用户手机号
+     */
+    String selectPhonenumberById(Long userId);
+
+    /**
+     * 通过用户ID查询用户邮箱
+     *
+     * @param userId 用户id
+     * @return 用户邮箱
+     */
+    String selectEmailById(Long userId);
+
+    /**
+     * 更新用户信息
+     *
+     * @param userId 用户ID
+     * @param ip     IP地址
+     */
+    void recordLoginInfo(Long userId, String ip);
+
+    /**
+     * 通过用户ID查询用户列表
+     *
+     * @param userIds 用户ids
+     * @return 用户列表
+     */
+    List<RemoteUserVo> selectListByIds(List<Long> userIds);
+
+    /**
+     * 通过角色ID查询用户ID
+     *
+     * @param roleIds 角色ids
+     * @return 用户ids
+     */
+    List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
+
+    /**
+     * 通过角色ID查询用户
+     *
+     * @param roleIds 角色ids
+     * @return 用户
+     */
+    List<RemoteUserVo> selectUsersByRoleIds(List<Long> roleIds);
+
+    /**
+     * 通过部门ID查询用户
+     *
+     * @param deptIds 部门ids
+     * @return 用户
+     */
+    List<RemoteUserVo> selectUsersByDeptIds(List<Long> deptIds);
+
+    /**
+     * 通过岗位ID查询用户
+     *
+     * @param postIds 岗位ids
+     * @return 用户
+     */
+    List<RemoteUserVo> 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);
+
+}

+ 71 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/SysUserOnline.java

@@ -0,0 +1,71 @@
+package org.dromara.system.api.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 当前在线会话
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class SysUserOnline implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 会话编号
+     */
+    private String tokenId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 客户端
+     */
+    private String clientKey;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地址
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+}

+ 89 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteLogininforBo.java

@@ -0,0 +1,89 @@
+package org.dromara.system.api.domain.bo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 系统访问记录表 sys_logininfor
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class RemoteLogininforBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 访问ID
+     */
+    private Long infoId;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 客户端
+     */
+    private String clientKey;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 登录状态(0成功 1失败)
+     */
+    private String status;
+
+    /**
+     * 提示消息
+     */
+    private String msg;
+
+    /**
+     * 访问时间
+     */
+    private Date loginTime;
+
+    /**
+     * 请求参数
+     */
+    private Map<String, Object> params = new HashMap<>();
+
+}

+ 119 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteOperLogBo.java

@@ -0,0 +1,119 @@
+package org.dromara.system.api.domain.bo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 操作日志记录表 oper_log
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class RemoteOperLogBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 日志主键
+     */
+    private Long operId;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 模块标题
+     */
+    private String title;
+
+    /**
+     * 业务类型(0其它 1新增 2修改 3删除)
+     */
+    private Integer businessType;
+
+    /**
+     * 方法名称
+     */
+    private String method;
+
+    /**
+     * 请求方式
+     */
+    private String requestMethod;
+
+    /**
+     * 操作类别(0其它 1后台用户 2手机端用户)
+     */
+    private Integer operatorType;
+
+    /**
+     * 操作人员
+     */
+    private String operName;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 请求URL
+     */
+    private String operUrl;
+
+    /**
+     * 主机地址
+     */
+    private String operIp;
+
+    /**
+     * 操作地点
+     */
+    private String operLocation;
+
+    /**
+     * 请求参数
+     */
+    private String operParam;
+
+    /**
+     * 返回参数
+     */
+    private String jsonResult;
+
+    /**
+     * 操作状态(0正常 1异常)
+     */
+    private Integer status;
+
+    /**
+     * 错误消息
+     */
+    private String errorMsg;
+
+    /**
+     * 操作时间
+     */
+    private Date operTime;
+
+    /**
+     * 消耗时间
+     */
+    private Long costTime;
+
+    /**
+     * 请求参数
+     */
+    private Map<String, Object> params = new HashMap<>();
+
+}

+ 129 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteSocialBo.java

@@ -0,0 +1,129 @@
+package org.dromara.system.api.domain.bo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 社会化关系业务对象 sys_social
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@NoArgsConstructor
+public class RemoteSocialBo implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 的唯一ID
+     */
+    private String authId;
+
+    /**
+     * 用户来源
+     */
+    private String source;
+
+    /**
+     * 用户的授权令牌
+     */
+    private String accessToken;
+
+    /**
+     * 用户的授权令牌的有效期,部分平台可能没有
+     */
+    private int expireIn;
+
+    /**
+     * 刷新令牌,部分平台可能没有
+     */
+    private String refreshToken;
+
+    /**
+     * 平台唯一id
+     */
+    private String openId;
+
+    /**
+     * 用户的 ID
+     */
+    private Long userId;
+
+    /**
+     * 平台的授权信息,部分平台可能没有
+     */
+    private String accessCode;
+
+    /**
+     * 用户的 unionid
+     */
+    private String unionId;
+
+    /**
+     * 授予的权限,部分平台可能没有
+     */
+    private String scope;
+
+    /**
+     * 授权的第三方账号
+     */
+    private String userName;
+
+    /**
+     * 授权的第三方昵称
+     */
+    private String nickName;
+
+    /**
+     * 授权的第三方邮箱
+     */
+    private String email;
+
+    /**
+     * 授权的第三方头像地址
+     */
+    private String avatar;
+
+    /**
+     * 个别平台的授权信息,部分平台可能没有
+     */
+    private String tokenType;
+
+    /**
+     * id token,部分平台可能没有
+     */
+    private String idToken;
+
+    /**
+     * 小米平台用户的附带属性,部分平台可能没有
+     */
+    private String macAlgorithm;
+
+    /**
+     * 小米平台用户的附带属性,部分平台可能没有
+     */
+    private String macKey;
+
+    /**
+     * 用户的授权code,部分平台可能没有
+     */
+    private String code;
+
+    /**
+     * Twitter平台用户的附带属性,部分平台可能没有
+     */
+    private String oauthToken;
+
+    /**
+     * Twitter平台用户的附带属性,部分平台可能没有
+     */
+    private String oauthTokenSecret;
+
+}

+ 56 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteTaskAssigneeBo.java

@@ -0,0 +1,56 @@
+package org.dromara.system.api.domain.bo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 任务受让人
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class RemoteTaskAssigneeBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 权限编码
+     */
+    private String handlerCode;
+
+    /**
+     * 权限名称
+     */
+    private String handlerName;
+
+    /**
+     * 权限分组
+     */
+    private String groupId;
+
+    /**
+     * 开始时间
+     */
+    private String beginTime;
+
+    /**
+     * 结束时间
+     */
+    private String endTime;
+
+    /**
+     * 当前页
+     */
+    private Integer pageNum = 1;
+
+    /**
+     * 每页显示条数
+     */
+    private Integer pageSize = 10;
+
+}

+ 124 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteUserBo.java

@@ -0,0 +1,124 @@
+package org.dromara.system.api.domain.bo;
+
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.xss.Xss;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户信息业务对象 sys_user
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@NoArgsConstructor
+public class RemoteUserBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户账号
+     */
+    @Xss(message = "用户账号不能包含脚本字符")
+    @NotBlank(message = "用户账号不能为空")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符")
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    @Xss(message = "用户昵称不能包含脚本字符")
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
+    private String nickName;
+
+    /**
+     * 用户类型(sys_user系统用户)
+     */
+    private String userType;
+
+    /**
+     * 用户邮箱
+     */
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String phonenumber;
+
+    /**
+     * 用户性别(0男 1女 2未知)
+     */
+    private String sex;
+
+    /**
+     * 头像地址
+     */
+    private Long avatar;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 帐号状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 最后登录IP
+     */
+    private String loginIp;
+
+    /**
+     * 最后登录时间
+     */
+    private Date loginDate;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 数据权限 当前角色ID
+     */
+    private Long roleId;
+
+    public RemoteUserBo(Long userId) {
+        this.userId = userId;
+    }
+
+    public boolean isSuperAdmin() {
+        return SystemConstants.SUPER_ADMIN_ID.equals(this.userId);
+    }
+
+}

+ 70 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteClientVo.java

@@ -0,0 +1,70 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * 授权管理视图对象 sys_client
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class RemoteClientVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 客户端id
+     */
+    private String clientId;
+
+    /**
+     * 客户端key
+     */
+    private String clientKey;
+
+    /**
+     * 客户端秘钥
+     */
+    private String clientSecret;
+
+    /**
+     * 授权类型
+     */
+    private List<String> grantTypeList;
+
+    /**
+     * 授权类型
+     */
+    private String grantType;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * token活跃超时时间
+     */
+    private Long activeTimeout;
+
+    /**
+     * token固定超时时间
+     */
+    private Long timeout;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+}

+ 37 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDeptVo.java

@@ -0,0 +1,37 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 部门
+ *
+ * @author AprilWind
+ */
+
+@Data
+@NoArgsConstructor
+public class RemoteDeptVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 父部门ID
+     */
+    private Long parentId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+}

+ 76 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictDataVo.java

@@ -0,0 +1,76 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 字典数据视图对象 sys_dict_data
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class RemoteDictDataVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典编码
+     */
+    private Long dictCode;
+
+    /**
+     * 字典排序
+     */
+    private Integer dictSort;
+
+    /**
+     * 字典标签
+     */
+    private String dictLabel;
+
+    /**
+     * 字典键值
+     */
+    private String dictValue;
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 样式属性(其他样式扩展)
+     */
+    private String cssClass;
+
+    /**
+     * 表格回显样式
+     */
+    private String listClass;
+
+    /**
+     * 是否默认(Y是 N否)
+     */
+    private String isDefault;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 46 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictTypeVo.java

@@ -0,0 +1,46 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 字典类型视图对象 sys_dict_type
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class RemoteDictTypeVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典主键
+     */
+    private Long dictId;
+
+    /**
+     * 字典名称
+     */
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 135 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteSocialVo.java

@@ -0,0 +1,135 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 社会化关系视图对象 sys_social
+ *
+ * @author thiszhc
+ */
+@Data
+public class RemoteSocialVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 认证唯一ID
+     */
+    private String authId;
+
+    /**
+     * 用户来源
+     */
+    private String source;
+
+    /**
+     * 用户的授权令牌
+     */
+    private String accessToken;
+
+    /**
+     * 用户的授权令牌的有效期,部分平台可能没有
+     */
+    private int expireIn;
+
+    /**
+     * 刷新令牌,部分平台可能没有
+     */
+    private String refreshToken;
+
+    /**
+     * 用户的 open id
+     */
+    private String openId;
+
+    /**
+     * 授权的第三方账号
+     */
+    private String userName;
+
+    /**
+     * 授权的第三方昵称
+     */
+    private String nickName;
+
+    /**
+     * 授权的第三方邮箱
+     */
+    private String email;
+
+    /**
+     * 授权的第三方头像地址
+     */
+    private String avatar;
+
+
+    /**
+     * 平台的授权信息,部分平台可能没有
+     */
+    private String accessCode;
+
+    /**
+     * 用户的 unionid
+     */
+    private String unionId;
+
+    /**
+     * 授予的权限,部分平台可能没有
+     */
+    private String scope;
+
+    /**
+     * 个别平台的授权信息,部分平台可能没有
+     */
+    private String tokenType;
+
+    /**
+     * id token,部分平台可能没有
+     */
+    private String idToken;
+
+    /**
+     * 小米平台用户的附带属性,部分平台可能没有
+     */
+    private String macAlgorithm;
+
+    /**
+     * 小米平台用户的附带属性,部分平台可能没有
+     */
+    private String macKey;
+
+    /**
+     * 用户的授权code,部分平台可能没有
+     */
+    private String code;
+
+    /**
+     * Twitter平台用户的附带属性,部分平台可能没有
+     */
+    private String oauthToken;
+
+    /**
+     * Twitter平台用户的附带属性,部分平台可能没有
+     */
+    private String oauthTokenSecret;
+
+}

+ 104 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaskAssigneeVo.java

@@ -0,0 +1,104 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 任务受让人
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class RemoteTaskAssigneeVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 总大小
+     */
+    private Long total = 0L;
+
+    /**
+     *
+     */
+    private List<TaskHandler> list;
+
+    public RemoteTaskAssigneeVo(Long total, List<TaskHandler> list) {
+        this.total = total;
+        this.list = list;
+    }
+
+    /**
+     * 将源列表转换为 TaskHandler 列表
+     *
+     * @param <T>              通用类型
+     * @param sourceList       待转换的源列表
+     * @param storageId        提取 storageId 的函数
+     * @param handlerCode      提取 handlerCode 的函数
+     * @param handlerName      提取 handlerName 的函数
+     * @param groupName        提取 groupName 的函数
+     * @param createTimeMapper 提取 createTime 的函数
+     * @return 转换后的 TaskHandler 列表
+     */
+    public static <T> List<TaskHandler> convertToHandlerList(
+        List<T> sourceList,
+        Function<T, Long> storageId,
+        Function<T, String> handlerCode,
+        Function<T, String> handlerName,
+        Function<T, Long> groupName,
+        Function<T, Date> createTimeMapper) {
+        return sourceList.stream()
+            .map(item -> new TaskHandler(
+                String.valueOf(storageId.apply(item)),
+                handlerCode.apply(item),
+                handlerName.apply(item),
+                groupName != null ? String.valueOf(groupName.apply(item)) : null,
+                createTimeMapper.apply(item)
+            )).collect(Collectors.toList());
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TaskHandler implements Serializable {
+
+        @Serial
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * 主键
+         */
+        private String storageId;
+
+        /**
+         * 权限编码
+         */
+        private String handlerCode;
+
+        /**
+         * 权限名称
+         */
+        private String handlerName;
+
+        /**
+         * 权限分组
+         */
+        private String groupName;
+
+        /**
+         * 创建时间
+         */
+        private Date createTime;
+    }
+
+}

+ 91 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTenantVo.java

@@ -0,0 +1,91 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 租户视图对象
+ *
+ * @author zhujie
+ */
+@Data
+public class RemoteTenantVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 联系人
+     */
+    private String contactUserName;
+
+    /**
+     * 联系电话
+     */
+    private String contactPhone;
+
+    /**
+     * 企业名称
+     */
+    private String companyName;
+
+    /**
+     * 统一社会信用代码
+     */
+    private String licenseNumber;
+
+    /**
+     * 地址
+     */
+    private String address;
+
+    /**
+     * 域名
+     */
+    private String domain;
+
+    /**
+     * 企业简介
+     */
+    private String intro;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 租户套餐编号
+     */
+    private Long packageId;
+
+    /**
+     * 过期时间
+     */
+    private Date expireTime;
+
+    /**
+     * 用户数量(-1不限制)
+     */
+    private Long accountCount;
+
+    /**
+     * 租户状态(0正常 1停用)
+     */
+    private String status;
+
+}

+ 73 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteUserVo.java

@@ -0,0 +1,73 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 用户
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@NoArgsConstructor
+public class RemoteUserVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 用户类型(sys_user系统用户)
+     */
+    private String userType;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String phonenumber;
+
+    /**
+     * 用户性别(0男 1女 2未知)
+     */
+    private String sex;
+
+    /**
+     * 帐号状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 151 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/LoginUser.java

@@ -0,0 +1,151 @@
+package org.dromara.system.api.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 用户信息
+ *
+ * @author ruoyi
+ */
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 部门类别编码
+     */
+    private String deptCategory;
+
+    /**
+     * 部门名
+     */
+    private String deptName;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 用户类型
+     */
+    private String userType;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 菜单权限
+     */
+    private Set<String> menuPermission;
+
+    /**
+     * 角色权限
+     */
+    private Set<String> rolePermission;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 角色对象
+     */
+    private List<RoleDTO> roles;
+
+    /**
+     * 岗位对象
+     */
+    private List<PostDTO> posts;
+
+    /**
+     * 数据权限 当前角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 客户端
+     */
+    private String clientKey;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * 获取登录id
+     */
+    public String getLoginId() {
+        if (userType == null) {
+            throw new IllegalArgumentException("用户类型不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        return userType + ":" + userId;
+    }
+
+}

+ 46 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/PostDTO.java

@@ -0,0 +1,46 @@
+package org.dromara.system.api.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 岗位
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class PostDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位ID
+     */
+    private Long postId;
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+    /**
+     * 岗位编码
+     */
+    private String postCode;
+
+    /**
+     * 岗位名称
+     */
+    private String postName;
+
+    /**
+     * 岗位类别编码
+     */
+    private String postCategory;
+
+}

+ 42 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/RoleDTO.java

@@ -0,0 +1,42 @@
+package org.dromara.system.api.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 角色
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class RoleDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    private String roleName;
+
+    /**
+     * 角色权限
+     */
+    private String roleKey;
+
+    /**
+     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
+     */
+    private String dataScope;
+
+}

+ 27 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/model/XcxLoginUser.java

@@ -0,0 +1,27 @@
+package org.dromara.system.api.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 小程序登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * openid
+     */
+    private String openid;
+
+}

+ 35 - 0
ruoyi-api/ruoyi-api-workflow/pom.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-api</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-api-workflow</artifactId>
+
+    <description>
+        ruoyi-api-workflow 工作流接口模块
+    </description>
+
+    <dependencies>
+
+        <!-- RuoYi Common Core-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <!-- 非必需模块 如果需要跟工作流同步数据 则需要在对应服务内引入bus模块 如果只是调用工作流api则不需要 -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-bus</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 88 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/RemoteWorkflowService.java

@@ -0,0 +1,88 @@
+package org.dromara.workflow.api;
+
+import org.dromara.workflow.api.domain.RemoteCompleteTask;
+import org.dromara.workflow.api.domain.RemoteStartProcess;
+import org.dromara.workflow.api.domain.RemoteStartProcessReturn;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用 工作流服务
+ *
+ * @Author ZETA
+ * @Date 2024/6/3
+ */
+public interface RemoteWorkflowService {
+
+    /**
+     * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
+     *
+     * @param businessIds 业务id
+     * @return 结果
+     */
+    boolean deleteInstance(List<Long> businessIds);
+
+    /**
+     * 获取当前流程状态
+     *
+     * @param taskId 任务id
+     * @return 状态
+     */
+    String getBusinessStatusByTaskId(Long taskId);
+
+    /**
+     * 获取当前流程状态
+     *
+     * @param businessId 业务id
+     * @return 状态
+     */
+    String getBusinessStatus(String businessId);
+
+    /**
+     * 设置流程变量
+     *
+     * @param instanceId 流程实例id
+     * @param variable   流程变量
+     */
+    void setVariable(Long instanceId, Map<String, Object> variable);
+
+    /**
+     * 获取流程变量
+     *
+     * @param instanceId 流程实例id
+     */
+    Map<String, Object> instanceVariable(Long instanceId);
+
+    /**
+     * 按照业务id查询流程实例id
+     *
+     * @param businessId 业务id
+     * @return 结果
+     */
+    Long getInstanceIdByBusinessId(String businessId);
+
+    /**
+     * 新增租户流程定义
+     *
+     * @param tenantId 租户id
+     */
+    void syncDef(String tenantId);
+
+    /**
+     * 启动流程
+     *
+     * @param startProcess 参数
+     * @return 结果
+     */
+    RemoteStartProcessReturn startWorkFlow(RemoteStartProcess startProcess);
+
+    /**
+     * 办理任务
+     *
+     * @param completeTask 参数
+     * @return 结果
+     */
+    boolean completeTask(RemoteCompleteTask completeTask);
+
+}

+ 70 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/RemoteWorkflowServiceMock.java

@@ -0,0 +1,70 @@
+package org.dromara.workflow.api;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.workflow.api.domain.RemoteCompleteTask;
+import org.dromara.workflow.api.domain.RemoteStartProcess;
+import org.dromara.workflow.api.domain.RemoteStartProcessReturn;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 工作流服务(降级处理)
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class RemoteWorkflowServiceMock implements RemoteWorkflowService {
+
+    @Override
+    public boolean deleteInstance(List<Long> businessIds) {
+        log.warn("服务调用异常 -> 降级处理");
+        return false;
+    }
+
+    @Override
+    public String getBusinessStatusByTaskId(Long taskId) {
+        log.warn("服务调用异常 -> 降级处理");
+        return null;
+    }
+
+    @Override
+    public String getBusinessStatus(String businessId) {
+        log.warn("服务调用异常 -> 降级处理");
+        return null;
+    }
+
+    @Override
+    public void setVariable(Long instanceId, Map<String, Object> variable) {
+        log.warn("服务调用异常 -> 降级处理");
+    }
+
+    @Override
+    public Map<String, Object> instanceVariable(Long instanceId) {
+        log.warn("服务调用异常 -> 降级处理");
+        return null;
+    }
+
+    @Override
+    public Long getInstanceIdByBusinessId(String businessId) {
+        log.warn("服务调用异常 -> 降级处理");
+        return null;
+    }
+
+    @Override
+    public void syncDef(String tenantId) {
+        log.warn("服务调用异常 -> 降级处理");
+    }
+
+    @Override
+    public RemoteStartProcessReturn startWorkFlow(RemoteStartProcess startProcess) {
+        log.warn("服务调用异常 -> 降级处理");
+        return null;
+    }
+
+    @Override
+    public boolean completeTask(RemoteCompleteTask completeTask) {
+        log.warn("服务调用异常 -> 降级处理");
+        return false;
+    }
+}

+ 71 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteCompleteTask.java

@@ -0,0 +1,71 @@
+package org.dromara.workflow.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 办理任务请求对象
+ *
+ * @author may
+ */
+@Data
+public class RemoteCompleteTask implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+    /**
+     * 附件id
+     */
+    private String fileId;
+
+    /**
+     * 抄送人员
+     */
+    private List<RemoteFlowCopy> flowCopyList;
+
+    /**
+     * 消息类型
+     */
+    private List<String> messageType;
+
+    /**
+     * 办理意见
+     */
+    private String message;
+
+    /**
+     * 消息通知
+     */
+    private String notice;
+
+    /**
+     * 流程变量
+     */
+    private Map<String, Object> variables;
+
+    /**
+     * 扩展变量(此处为逗号分隔的ossId)
+     */
+    private String ext;
+
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            return new HashMap<>(16);
+        }
+        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+        return variables;
+    }
+
+}

+ 30 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteFlowCopy.java

@@ -0,0 +1,30 @@
+package org.dromara.workflow.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 抄送
+ *
+ * @author may
+ */
+@Data
+public class RemoteFlowCopy implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+}

+ 45 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcess.java

@@ -0,0 +1,45 @@
+package org.dromara.workflow.api.domain;
+
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 启动流程对象
+ *
+ * @author may
+ */
+@Data
+public class RemoteStartProcess implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 业务唯一值id
+     */
+    private String businessId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
+     */
+    private Map<String, Object> variables;
+
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            return new HashMap<>(16);
+        }
+        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+        return variables;
+    }
+}

+ 30 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcessReturn.java

@@ -0,0 +1,30 @@
+package org.dromara.workflow.api.domain;
+
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 启动流程返回对象
+ *
+ * @author Lion Li
+ */
+@Data
+public class RemoteStartProcessReturn implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 流程实例id
+     */
+    private Long processInstanceId;
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+}

+ 41 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessDeleteEvent.java

@@ -0,0 +1,41 @@
+package org.dromara.workflow.api.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.cloud.bus.event.RemoteApplicationEvent;
+
+import java.io.Serial;
+
+/**
+ * 删除流程监听
+ *
+ * @author AprilWind
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProcessDeleteEvent extends RemoteApplicationEvent {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 业务id
+     */
+    private String businessId;
+
+    public ProcessDeleteEvent() {
+        super(new Object(), SpringUtils.getApplicationName(), DEFAULT_DESTINATION_FACTORY.getDestination(null));
+    }
+
+}

+ 72 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessEvent.java

@@ -0,0 +1,72 @@
+package org.dromara.workflow.api.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.cloud.bus.event.RemoteApplicationEvent;
+
+import java.io.Serial;
+import java.util.Map;
+
+/**
+ * 总体流程监听
+ *
+ * @author may
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProcessEvent extends RemoteApplicationEvent {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 业务id
+     */
+    private String businessId;
+
+    /**
+     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+     */
+    private Integer nodeType;
+
+    /**
+     * 流程节点编码
+     */
+    private String nodeCode;
+
+    /**
+     * 流程节点名称
+     */
+    private String nodeName;
+
+    /**
+     * 流程状态
+     */
+    private String status;
+
+    /**
+     * 办理参数
+     */
+    private Map<String, Object> params;
+
+    /**
+     * 当为true时为申请人节点办理
+     */
+    private Boolean submit;
+
+    public ProcessEvent() {
+        super(new Object(), SpringUtils.getApplicationName(), DEFAULT_DESTINATION_FACTORY.getDestination(null));
+    }
+
+}

+ 65 - 0
ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessTaskEvent.java

@@ -0,0 +1,65 @@
+package org.dromara.workflow.api.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.cloud.bus.event.RemoteApplicationEvent;
+
+import java.io.Serial;
+
+/**
+ * 流程任务监听
+ *
+ * @author may
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProcessTaskEvent extends RemoteApplicationEvent {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+     */
+    private Integer nodeType;
+
+    /**
+     * 流程节点编码
+     */
+    private String nodeCode;
+
+    /**
+     * 流程节点名称
+     */
+    private String nodeName;
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+    /**
+     * 业务id
+     */
+    private String businessId;
+
+    /**
+     * 流程状态
+     */
+    private String status;
+
+    public ProcessTaskEvent() {
+        super(new Object(), SpringUtils.getApplicationName(), DEFAULT_DESTINATION_FACTORY.getDestination(null));
+    }
+}

+ 27 - 0
ruoyi-auth/Dockerfile

@@ -0,0 +1,27 @@
+# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
+FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
+#FROM findepi/graalvm:java17-native
+
+LABEL maintainer="Lion Li"
+
+RUN mkdir -p /ruoyi/auth/logs  \
+    /ruoyi/auth/temp  \
+    /ruoyi/skywalking/agent
+
+WORKDIR /ruoyi/auth
+
+ENV SERVER_PORT=9210 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+
+EXPOSE ${SERVER_PORT}
+
+ADD ./target/ruoyi-auth.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
+           #-Dskywalking.agent.service_name=ruoyi-auth \
+           #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
+           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+           -jar app.jar
+

+ 139 - 0
ruoyi-auth/pom.xml

@@ -0,0 +1,139 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-cloud-plus</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-auth</artifactId>
+
+    <description>
+        ruoyi-auth 认证授权中心
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-nacos</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-captcha</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sentinel</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common Security-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-social</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common Log -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-log</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-ratelimiter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-encrypt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-dubbo</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-seata</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-tenant</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-service-impl</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-resource</artifactId>
+        </dependency>
+
+        <!-- 自定义负载均衡(多团队开发使用) -->
+<!--        <dependency>-->
+<!--            <groupId>org.dromara</groupId>-->
+<!--            <artifactId>ruoyi-common-loadbalancer</artifactId>-->
+<!--        </dependency>-->
+
+        <!-- ELK 日志收集 -->
+<!--        <dependency>-->
+<!--            <groupId>org.dromara</groupId>-->
+<!--            <artifactId>ruoyi-common-logstash</artifactId>-->
+<!--        </dependency>-->
+
+        <!-- skywalking 日志收集 -->
+<!--        <dependency>-->
+<!--            <groupId>org.dromara</groupId>-->
+<!--            <artifactId>ruoyi-common-skylog</artifactId>-->
+<!--        </dependency>-->
+
+        <!-- prometheus 监控 -->
+<!--        <dependency>-->
+<!--            <groupId>org.dromara</groupId>-->
+<!--            <artifactId>ruoyi-common-prometheus</artifactId>-->
+<!--        </dependency>-->
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 23 - 0
ruoyi-auth/src/main/java/org/dromara/auth/RuoYiAuthApplication.java

@@ -0,0 +1,23 @@
+package org.dromara.auth;
+
+import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+
+/**
+ * 认证授权中心
+ *
+ * @author ruoyi
+ */
+@EnableDubbo
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+public class RuoYiAuthApplication {
+    public static void main(String[] args) {
+        SpringApplication application = new SpringApplication(RuoYiAuthApplication.class);
+        application.setApplicationStartup(new BufferingApplicationStartup(2048));
+        application.run(args);
+        System.out.println("(♥◠‿◠)ノ゙  认证授权中心启动成功   ლ(´ڡ`ლ)゙  ");
+    }
+}

+ 88 - 0
ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java

@@ -0,0 +1,88 @@
+package org.dromara.auth.captcha;
+
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.core.math.Calculator;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.RandomUtil;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.io.Serial;
+
+/**
+ * 无符号计算生成器
+ *
+ * @author Lion Li
+ */
+public class UnsignedMathGenerator implements CodeGenerator {
+
+    @Serial
+    private static final long serialVersionUID = -5514819971774091076L;
+
+    private static final String OPERATORS = "+-*";
+
+    /**
+     * 参与计算数字最大长度
+     */
+    private final int numberLength;
+
+    /**
+     * 构造
+     */
+    public UnsignedMathGenerator() {
+        this(2);
+    }
+
+    /**
+     * 构造
+     *
+     * @param numberLength 参与计算最大数字位数
+     */
+    public UnsignedMathGenerator(int numberLength) {
+        this.numberLength = numberLength;
+    }
+
+    @Override
+    public String generate() {
+        final int limit = getLimit();
+        int a = RandomUtil.randomInt(limit);
+        int b = RandomUtil.randomInt(limit);
+        String max = Integer.toString(Math.max(a,b));
+        String min = Integer.toString(Math.min(a,b));
+        max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE);
+        min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE);
+
+        return max + RandomUtil.randomChar(OPERATORS) + min + '=';
+    }
+
+    @Override
+    public boolean verify(String code, String userInputCode) {
+        int result;
+        try {
+            result = Integer.parseInt(userInputCode);
+        } catch (NumberFormatException e) {
+            // 用户输入非数字
+            return false;
+        }
+
+        final int calculateResult = (int) Calculator.conversion(code);
+        return result == calculateResult;
+    }
+
+    /**
+     * 获取验证码长度
+     *
+     * @return 验证码长度
+     */
+    public int getLength() {
+        return this.numberLength * 2 + 2;
+    }
+
+    /**
+     * 根据长度获取参与计算数字最大值
+     *
+     * @return 最大值
+     */
+    private int getLimit() {
+        return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength));
+    }
+}

+ 62 - 0
ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java

@@ -0,0 +1,62 @@
+package org.dromara.auth.config;
+
+import cn.hutool.captcha.CaptchaUtil;
+import cn.hutool.captcha.CircleCaptcha;
+import cn.hutool.captcha.LineCaptcha;
+import cn.hutool.captcha.ShearCaptcha;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import java.awt.*;
+
+/**
+ * 验证码配置
+ *
+ * @author Lion Li
+ */
+@Configuration
+public class CaptchaConfig {
+
+    private static final int WIDTH = 160;
+    private static final int HEIGHT = 60;
+    private static final Color BACKGROUND = Color.LIGHT_GRAY;
+    private static final Font FONT = new Font("Arial", Font.BOLD, 48);
+
+    /**
+     * 圆圈干扰验证码
+     */
+    @Lazy
+    @Bean
+    public CircleCaptcha circleCaptcha() {
+        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
+        captcha.setBackground(BACKGROUND);
+        captcha.setFont(FONT);
+        return captcha;
+    }
+
+    /**
+     * 线段干扰的验证码
+     */
+    @Lazy
+    @Bean
+    public LineCaptcha lineCaptcha() {
+        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
+        captcha.setBackground(BACKGROUND);
+        captcha.setFont(FONT);
+        return captcha;
+    }
+
+    /**
+     * 扭曲干扰验证码
+     */
+    @Lazy
+    @Bean
+    public ShearCaptcha shearCaptcha() {
+        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
+        captcha.setBackground(BACKGROUND);
+        captcha.setFont(FONT);
+        return captcha;
+    }
+
+}

+ 89 - 0
ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java

@@ -0,0 +1,89 @@
+package org.dromara.auth.controller;
+
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.core.util.IdUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.auth.domain.vo.CaptchaVo;
+import org.dromara.auth.enums.CaptchaType;
+import org.dromara.auth.properties.CaptchaProperties;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.context.PlatformContext;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
+import org.dromara.common.ratelimiter.enums.LimitType;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Duration;
+
+/**
+ * 验证码操作处理
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class CaptchaController {
+
+    private final CaptchaProperties captchaProperties;
+
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/code")
+    public R<CaptchaVo> getCode() {
+        CaptchaVo captchaVo = new CaptchaVo();
+        boolean captchaEnabled = captchaProperties.getEnabled();
+        String platform = PlatformContext.getPlatform();
+        if (!captchaEnabled) {
+            captchaVo.setCaptchaEnabled(false);
+            return R.ok(captchaVo);
+        }
+        return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
+    }
+
+    /**
+     * 生成验证码
+     * 独立方法避免验证码关闭之后仍然走限流
+     */
+    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+    public CaptchaVo getCodeImpl() {
+        // 保存验证码信息
+        String uuid = IdUtil.simpleUUID();
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
+        // 生成验证码
+        CaptchaType captchaType = captchaProperties.getType();
+        boolean isMath = CaptchaType.MATH == captchaType;
+        Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
+        CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
+        AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
+        captcha.setGenerator(codeGenerator);
+        captcha.createCode();
+        // 如果是数学验证码,使用SpEL表达式处理验证码结果
+        String code = captcha.getCode();
+        if (isMath) {
+            ExpressionParser parser = new SpelExpressionParser();
+            Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
+            code = exp.getValue(String.class);
+        }
+        RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        CaptchaVo captchaVo = new CaptchaVo();
+        captchaVo.setUuid(uuid);
+        captchaVo.setImg(captcha.getImageBase64());
+        return captchaVo;
+    }
+
+}

+ 231 - 0
ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java

@@ -0,0 +1,231 @@
+package org.dromara.auth.controller;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.domain.vo.LoginTenantVo;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.domain.vo.TenantListVo;
+import org.dromara.auth.form.RegisterBody;
+import org.dromara.auth.form.SocialLoginBody;
+import org.dromara.auth.service.IAuthStrategy;
+import org.dromara.auth.service.SysLoginService;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.utils.*;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.SocialUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.resource.api.RemoteMessageService;
+import org.dromara.system.api.RemoteClientService;
+import org.dromara.system.api.RemoteConfigService;
+import org.dromara.system.api.RemoteSocialService;
+import org.dromara.system.api.RemoteTenantService;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.dromara.system.api.domain.vo.RemoteTenantVo;
+import org.springframework.web.bind.annotation.*;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * token 控制
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+public class TokenController {
+
+    private final SocialProperties socialProperties;
+    private final SysLoginService sysLoginService;
+    private final ScheduledExecutorService scheduledExecutorService;
+
+    @DubboReference
+    private final RemoteConfigService remoteConfigService;
+    @DubboReference
+    private final RemoteTenantService remoteTenantService;
+    @DubboReference
+    private final RemoteClientService remoteClientService;
+    @DubboReference
+    private final RemoteSocialService remoteSocialService;
+    @DubboReference(stub = "true")
+    private final RemoteMessageService remoteMessageService;
+
+    /**
+     * 登录方法
+     *
+     * @param body 登录信息
+     * @return 结果
+     */
+    @ApiEncrypt
+    @PostMapping("/login")
+    public R<LoginVo> login(@RequestBody String body) {
+        LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        // 授权类型和客户端id
+        String clientId = loginBody.getClientId();
+        String grantType = loginBody.getGrantType();
+        RemoteClientVo clientVo = remoteClientService.queryByClientId(clientId);
+
+        // 查询不到 client 或 client 内不包含 grantType
+        if (ObjectUtil.isNull(clientVo) || !StringUtils.contains(clientVo.getGrantType(), grantType)) {
+            log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
+            return R.fail(MessageUtils.message("auth.grant.type.error"));
+        } else if (!SystemConstants.NORMAL.equals(clientVo.getStatus())) {
+            return R.fail(MessageUtils.message("auth.grant.type.blocked"));
+        }
+        // 校验租户
+        sysLoginService.checkTenant(loginBody.getTenantId());
+        // 登录
+        LoginVo loginVo = IAuthStrategy.login(body, clientVo, grantType);
+
+        Long userId = LoginHelper.getUserId();
+        scheduledExecutorService.schedule(() -> {
+            remoteMessageService.publishMessage(List.of(userId), "欢迎登录RuoYi-Cloud-Plus微服务管理系统");
+        }, 5, TimeUnit.SECONDS);
+        return R.ok(loginVo);
+    }
+
+    /**
+     * 第三方登录请求
+     *
+     * @param source 登录来源
+     * @return 结果
+     */
+    @GetMapping("/binding/{source}")
+    public R<String> authBinding(@PathVariable("source") String source,
+                                 @RequestParam String tenantId, @RequestParam String domain) {
+        SocialLoginConfigProperties obj = socialProperties.getType().get(source);
+        if (ObjectUtil.isNull(obj)) {
+            return R.fail(source + "平台账号暂不支持");
+        }
+        AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
+        Map<String, String> map = new HashMap<>();
+        map.put("tenantId", tenantId);
+        map.put("domain", domain);
+        map.put("state", AuthStateUtils.createState());
+        String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
+        return R.ok("操作成功", authorizeUrl);
+    }
+
+    /**
+     * 第三方登录回调业务处理 绑定授权
+     *
+     * @param loginBody 请求体
+     * @return 结果
+     */
+    @PostMapping("/social/callback")
+    public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
+        // 获取第三方登录信息
+        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+            loginBody.getSource(), loginBody.getSocialCode(),
+            loginBody.getSocialState(), socialProperties);
+        AuthUser authUserData = response.getData();
+        // 判断授权响应是否成功
+        if (!response.ok()) {
+            return R.fail(response.getMsg());
+        }
+        sysLoginService.socialRegister(authUserData);
+        return R.ok();
+    }
+
+
+    /**
+     * 取消授权
+     *
+     * @param socialId socialId
+     */
+    @DeleteMapping(value = "/unlock/{socialId}")
+    public R<Void> unlockSocial(@PathVariable Long socialId) {
+        Boolean rows = remoteSocialService.deleteWithValidById(socialId);
+        return rows ? R.ok() : R.fail("取消授权失败");
+    }
+
+    /**
+     * 登出方法
+     */
+    @PostMapping("logout")
+    public R<Void> logout() {
+        sysLoginService.logout();
+        return R.ok();
+    }
+
+    /**
+     * 用户注册
+     */
+    @ApiEncrypt
+    @PostMapping("register")
+    public R<Void> register(@RequestBody RegisterBody registerBody) {
+        if (!remoteConfigService.selectRegisterEnabled(registerBody.getTenantId())) {
+            return R.fail("当前系统没有开启注册功能!");
+        }
+        // 用户注册
+        sysLoginService.register(registerBody);
+        return R.ok();
+    }
+
+    /**
+     * 登录页面租户下拉框
+     *
+     * @return 租户列表
+     */
+    @GetMapping("/tenant/list")
+    public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
+        // 返回对象
+        LoginTenantVo result = new LoginTenantVo();
+        boolean enable = TenantHelper.isEnable();
+        result.setTenantEnabled(enable);
+        // 如果未开启租户这直接返回
+        if (!enable) {
+            return R.ok(result);
+        }
+
+        List<RemoteTenantVo> tenantList = remoteTenantService.queryList();
+        List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
+        try {
+            // 如果只超管返回所有租户
+            if (LoginHelper.isSuperAdmin()) {
+                result.setVoList(voList);
+                return R.ok(result);
+            }
+        } catch (NotLoginException ignored) {
+        }
+
+        // 获取域名
+        String host;
+        String referer = request.getHeader("referer");
+        if (StringUtils.isNotBlank(referer)) {
+            // 这里从referer中取值是为了本地使用hosts添加虚拟域名,方便本地环境调试
+            host = referer.split("//")[1].split("/")[0];
+        } else {
+            host = new URL(request.getRequestURL().toString()).getHost();
+        }
+        // 根据域名进行筛选
+        List<TenantListVo> list = StreamUtils.filter(voList, vo ->
+            StringUtils.equalsIgnoreCase(vo.getDomain(), host));
+        result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
+        return R.ok(result);
+    }
+
+}

+ 16 - 0
ruoyi-auth/src/main/java/org/dromara/auth/domain/convert/TenantVoConvert.java

@@ -0,0 +1,16 @@
+package org.dromara.auth.domain.convert;
+
+import io.github.linpeilie.BaseMapper;
+import org.dromara.auth.domain.vo.TenantListVo;
+import org.dromara.system.api.domain.vo.RemoteTenantVo;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+
+/**
+ * 租户vo转换器
+ * @author zhujie
+ */
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
+public interface TenantVoConvert extends BaseMapper<RemoteTenantVo, TenantListVo> {
+
+}

+ 25 - 0
ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/CaptchaVo.java

@@ -0,0 +1,25 @@
+package org.dromara.auth.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 验证码信息
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class CaptchaVo {
+
+    /**
+     * 是否开启验证码
+     */
+    private Boolean captchaEnabled = true;
+
+    private String uuid;
+
+    /**
+     * 验证码图片
+     */
+    private String img;
+
+}

+ 25 - 0
ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginTenantVo.java

@@ -0,0 +1,25 @@
+package org.dromara.auth.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 登录租户对象
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class LoginTenantVo {
+
+    /**
+     * 租户开关
+     */
+    private Boolean tenantEnabled;
+
+    /**
+     * 租户对象列表
+     */
+    private List<TenantListVo> voList;
+
+}

+ 54 - 0
ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java

@@ -0,0 +1,54 @@
+package org.dromara.auth.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 登录验证信息
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class LoginVo {
+
+    /**
+     * 授权令牌
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+
+    /**
+     * 刷新令牌
+     */
+    @JsonProperty("refresh_token")
+    private String refreshToken;
+
+    /**
+     * 授权令牌 access_token 的有效期
+     */
+    @JsonProperty("expire_in")
+    private Long expireIn;
+
+    /**
+     * 刷新令牌 refresh_token 的有效期
+     */
+    @JsonProperty("refresh_expire_in")
+    private Long refreshExpireIn;
+
+    /**
+     * 应用id
+     */
+    @JsonProperty("client_id")
+    private String clientId;
+
+    /**
+     * 令牌权限
+     */
+    private String scope;
+
+    /**
+     * 用户 openid
+     */
+    private String openid;
+
+}

+ 28 - 0
ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/TenantListVo.java

@@ -0,0 +1,28 @@
+package org.dromara.auth.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 租户列表
+ *
+ * @author zhujie
+ */
+@Data
+public class TenantListVo {
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 企业名称
+     */
+    private String companyName;
+
+    /**
+     * 域名
+     */
+    private String domain;
+
+}

+ 35 - 0
ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaCategory.java

@@ -0,0 +1,35 @@
+package org.dromara.auth.enums;
+
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.CircleCaptcha;
+import cn.hutool.captcha.LineCaptcha;
+import cn.hutool.captcha.ShearCaptcha;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 验证码类别
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum CaptchaCategory {
+
+    /**
+     * 线段干扰
+     */
+    LINE(LineCaptcha.class),
+
+    /**
+     * 圆圈干扰
+     */
+    CIRCLE(CircleCaptcha.class),
+
+    /**
+     * 扭曲干扰
+     */
+    SHEAR(ShearCaptcha.class);
+
+    private final Class<? extends AbstractCaptcha> clazz;
+}

+ 29 - 0
ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java

@@ -0,0 +1,29 @@
+package org.dromara.auth.enums;
+
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
+import org.dromara.auth.captcha.UnsignedMathGenerator;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 验证码类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum CaptchaType {
+
+    /**
+     * 数字
+     */
+    MATH(UnsignedMathGenerator.class),
+
+    /**
+     * 字符
+     */
+    CHAR(RandomGenerator.class);
+
+    private final Class<? extends CodeGenerator> clazz;
+}

+ 31 - 0
ruoyi-auth/src/main/java/org/dromara/auth/form/EmailLoginBody.java

@@ -0,0 +1,31 @@
+package org.dromara.auth.form;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.domain.model.LoginBody;
+
+/**
+ * 邮件登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class EmailLoginBody extends LoginBody {
+
+    /**
+     * 邮箱
+     */
+    @NotBlank(message = "{user.email.not.blank}")
+    @Email(message = "{user.email.not.valid}")
+    private String email;
+
+    /**
+     * 邮箱code
+     */
+    @NotBlank(message = "{email.code.not.blank}")
+    private String emailCode;
+
+}

+ 32 - 0
ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java

@@ -0,0 +1,32 @@
+package org.dromara.auth.form;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 密码登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PasswordLoginBody extends LoginBody {
+
+    /**
+     * 用户名
+     */
+    @NotBlank(message = "{user.username.not.blank}")
+    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @NotBlank(message = "{user.password.not.blank}")
+    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+    private String password;
+
+}

+ 37 - 0
ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java

@@ -0,0 +1,37 @@
+package org.dromara.auth.form;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 用户注册对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterBody extends LoginBody {
+
+    /**
+     * 用户名
+     */
+    @NotBlank(message = "{user.username.not.blank}")
+    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @NotBlank(message = "{user.password.not.blank}")
+    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+    private String password;
+
+    /**
+     * 用户类型
+     */
+    private String userType;
+
+}

+ 29 - 0
ruoyi-auth/src/main/java/org/dromara/auth/form/SmsLoginBody.java

@@ -0,0 +1,29 @@
+package org.dromara.auth.form;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.domain.model.LoginBody;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SmsLoginBody extends LoginBody {
+
+    /**
+     * 手机号
+     */
+    @NotBlank(message = "{user.phonenumber.not.blank}")
+    private String phonenumber;
+
+    /**
+     * 短信code
+     */
+    @NotBlank(message = "{sms.code.not.blank}")
+    private String smsCode;
+
+}

+ 35 - 0
ruoyi-auth/src/main/java/org/dromara/auth/form/SocialLoginBody.java

@@ -0,0 +1,35 @@
+package org.dromara.auth.form;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.domain.model.LoginBody;
+
+/**
+ * 三方登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SocialLoginBody extends LoginBody {
+
+    /**
+     * 第三方登录平台
+     */
+    @NotBlank(message = "{social.source.not.blank}")
+    private String source;
+
+    /**
+     * 第三方登录code
+     */
+    @NotBlank(message = "{social.code.not.blank}")
+    private String socialCode;
+
+    /**
+     * 第三方登录socialState
+     */
+    @NotBlank(message = "{social.state.not.blank}")
+    private String socialState;
+
+}

+ 28 - 0
ruoyi-auth/src/main/java/org/dromara/auth/form/XcxLoginBody.java

@@ -0,0 +1,28 @@
+package org.dromara.auth.form;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.domain.model.LoginBody;
+
+/**
+ * 三方登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class XcxLoginBody extends LoginBody {
+
+    /**
+     * 小程序id(多个小程序时使用)
+     */
+    private String appid;
+
+    /**
+     * 小程序code
+     */
+    @NotBlank(message = "{xcx.code.not.blank}")
+    private String xcxCode;
+
+}

+ 168 - 0
ruoyi-auth/src/main/java/org/dromara/auth/listener/UserActionListener.java

@@ -0,0 +1,168 @@
+package org.dromara.auth.listener;
+
+import cn.dev33.satoken.listener.SaTokenListener;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.constant.CacheConstants;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.ip.AddressUtils;
+import org.dromara.common.log.event.LogininforEvent;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.resource.api.RemoteMessageService;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.SysUserOnline;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+
+/**
+ * 用户行为 侦听器的实现
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Component
+@Slf4j
+public class UserActionListener implements SaTokenListener {
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+    @DubboReference
+    private RemoteMessageService remoteMessageService;
+
+    /**
+     * 每次登录时触发
+     */
+    @Override
+    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
+        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
+        String ip = ServletUtils.getClientIP();
+        SysUserOnline userOnline = new SysUserOnline();
+        userOnline.setIpaddr(ip);
+        userOnline.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+        userOnline.setBrowser(userAgent.getBrowser().getName());
+        userOnline.setOs(userAgent.getOs().getName());
+        userOnline.setLoginTime(System.currentTimeMillis());
+        userOnline.setTokenId(tokenValue);
+        String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
+        String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
+        userOnline.setUserName(username);
+        userOnline.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
+        userOnline.setDeviceType(loginParameter.getDeviceType());
+        userOnline.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            if (loginParameter.getTimeout() == -1) {
+                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, userOnline);
+            } else {
+                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, userOnline, Duration.ofSeconds(loginParameter.getTimeout()));
+            }
+        });
+        // 记录登录日志
+        LogininforEvent logininforEvent = new LogininforEvent();
+        logininforEvent.setTenantId(tenantId);
+        logininforEvent.setUsername(username);
+        logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
+        logininforEvent.setMessage(MessageUtils.message("user.login.success"));
+        SpringUtils.context().publishEvent(logininforEvent);
+        // 更新登录信息
+        remoteUserService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
+        log.info("user doLogin, useId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次注销时触发
+     */
+    @Override
+    public void doLogout(String loginType, Object loginId, String tokenValue) {
+        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+        });
+        log.info("user doLogout, useId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次被踢下线时触发
+     */
+    @Override
+    public void doKickout(String loginType, Object loginId, String tokenValue) {
+        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+        });
+        log.info("user doLogoutByLoginId, useId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次被顶下线时触发
+     */
+    @Override
+    public void doReplaced(String loginType, Object loginId, String tokenValue) {
+        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+        });
+        log.info("user doReplaced, useId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次被封禁时触发
+     */
+    @Override
+    public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
+    }
+
+    /**
+     * 每次被解封时触发
+     */
+    @Override
+    public void doUntieDisable(String loginType, Object loginId, String service) {
+    }
+
+    /**
+     * 每次打开二级认证时触发
+     */
+    @Override
+    public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
+    }
+
+    /**
+     * 每次创建Session时触发
+     */
+    @Override
+    public void doCloseSafe(String loginType, String tokenValue, String service) {
+    }
+
+    /**
+     * 每次创建Session时触发
+     */
+    @Override
+    public void doCreateSession(String id) {
+    }
+
+    /**
+     * 每次注销Session时触发
+     */
+    @Override
+    public void doLogoutSession(String id) {
+    }
+
+    /**
+     * 每次Token续期时触发
+     */
+    @Override
+    public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
+    }
+
+}

+ 46 - 0
ruoyi-auth/src/main/java/org/dromara/auth/properties/CaptchaProperties.java

@@ -0,0 +1,46 @@
+package org.dromara.auth.properties;
+
+import org.dromara.auth.enums.CaptchaCategory;
+import org.dromara.auth.enums.CaptchaType;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 验证码配置
+ *
+ * @author ruoyi
+ */
+@Data
+@Configuration
+@RefreshScope
+@ConfigurationProperties(prefix = "security.captcha")
+public class CaptchaProperties {
+
+    /**
+     * 验证码类型
+     */
+    private CaptchaType type;
+
+    /**
+     * 验证码类别
+     */
+    private CaptchaCategory category;
+
+    /**
+     * 数字验证码位数
+     */
+    private Integer numberLength;
+
+    /**
+     * 字符验证码长度
+     */
+    private Integer charLength;
+
+    /**
+     * 验证码开关
+     */
+    private Boolean enabled;
+
+}

+ 29 - 0
ruoyi-auth/src/main/java/org/dromara/auth/properties/UserPasswordProperties.java

@@ -0,0 +1,29 @@
+package org.dromara.auth.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 用户密码配置
+ *
+ * @author Lion Li
+ */
+@Data
+@Configuration
+@RefreshScope
+@ConfigurationProperties(prefix = "user.password")
+public class UserPasswordProperties {
+
+    /**
+     * 密码最大错误次数
+     */
+    private Integer maxRetryCount;
+
+    /**
+     * 密码锁定时间(默认10分钟)
+     */
+    private Integer lockTime;
+
+}

+ 44 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java

@@ -0,0 +1,44 @@
+package org.dromara.auth.service;
+
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+
+/**
+ * 授权策略
+ *
+ * @author Michelle.Chung
+ */
+public interface IAuthStrategy {
+
+    String BASE_NAME = "AuthStrategy";
+
+    /**
+     * 登录
+     *
+     * @param body      登录对象
+     * @param client    授权管理视图对象
+     * @param grantType 授权类型
+     * @return 登录验证信息
+     */
+    static LoginVo login(String body, RemoteClientVo client, String grantType) {
+        // 授权类型和客户端id
+        String beanName = grantType + BASE_NAME;
+        if (!SpringUtils.containsBean(beanName)) {
+            throw new ServiceException("授权类型不正确!");
+        }
+        IAuthStrategy instance = SpringUtils.getBean(beanName);
+        return instance.login(body, client);
+    }
+
+    /**
+     * 登录
+     *
+     * @param body   登录对象
+     * @param client 授权管理视图对象
+     * @return 登录验证信息
+     */
+    LoginVo login(String body, RemoteClientVo client);
+
+}

+ 265 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java

@@ -0,0 +1,265 @@
+package org.dromara.auth.service;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.hutool.crypto.digest.BCrypt;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.lock.annotation.Lock4j;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthUser;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.form.RegisterBody;
+import org.dromara.auth.properties.CaptchaProperties;
+import org.dromara.auth.properties.UserPasswordProperties;
+import org.dromara.common.core.constant.*;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.enums.UserType;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.exception.user.CaptchaException;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.log.event.LogininforEvent;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.exception.TenantException;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.api.RemoteSocialService;
+import org.dromara.system.api.RemoteTenantService;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.bo.RemoteSocialBo;
+import org.dromara.system.api.domain.bo.RemoteUserBo;
+import org.dromara.system.api.domain.vo.RemoteSocialVo;
+import org.dromara.system.api.domain.vo.RemoteTenantVo;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * 登录校验方法
+ *
+ * @author ruoyi
+ */
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class SysLoginService {
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+    @DubboReference
+    private RemoteTenantService remoteTenantService;
+    @DubboReference
+    private RemoteSocialService remoteSocialService;
+
+    @Autowired
+    private UserPasswordProperties userPasswordProperties;
+    @Autowired
+    private final CaptchaProperties captchaProperties;
+
+    /**
+     * 绑定第三方用户
+     *
+     * @param authUserData 授权响应实体
+     */
+    @Lock4j
+    public void socialRegister(AuthUser authUserData) {
+        String authId = authUserData.getSource() + authUserData.getUuid();
+        // 第三方用户信息
+        RemoteSocialBo bo = BeanUtil.toBean(authUserData, RemoteSocialBo.class);
+        BeanUtil.copyProperties(authUserData.getToken(), bo);
+        Long userId = LoginHelper.getUserId();
+        bo.setUserId(userId);
+        bo.setAuthId(authId);
+        bo.setOpenId(authUserData.getUuid());
+        bo.setUserName(authUserData.getUsername());
+        bo.setNickName(authUserData.getNickname());
+        List<RemoteSocialVo> checkList = remoteSocialService.selectByAuthId(authId);
+        if (CollUtil.isNotEmpty(checkList)) {
+            throw new ServiceException("此三方账号已经被绑定!");
+        }
+        // 查询是否已经绑定用户
+        RemoteSocialBo params = new RemoteSocialBo();
+        params.setUserId(userId);
+        params.setSource(bo.getSource());
+        List<RemoteSocialVo> list = remoteSocialService.queryList(params);
+        if (CollUtil.isEmpty(list)) {
+            // 没有绑定用户, 新增用户信息
+            remoteSocialService.insertByBo(bo);
+        } else {
+            // 更新用户信息
+            bo.setId(list.get(0).getId());
+            remoteSocialService.updateByBo(bo);
+            // 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
+            // throw new ServiceException("此平台账号已经被绑定!");
+        }
+    }
+
+    /**
+     * 退出登录
+     */
+    public void logout() {
+        try {
+            LoginUser loginUser = LoginHelper.getLoginUser();
+            if (ObjectUtil.isNull(loginUser)) {
+                return;
+            }
+            if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
+                // 超级管理员 登出清除动态租户
+                TenantHelper.clearDynamic();
+            }
+            recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
+        } catch (NotLoginException ignored) {
+        } finally {
+            try {
+                StpUtil.logout();
+            } catch (NotLoginException ignored) {
+            }
+        }
+    }
+
+    /**
+     * 注册
+     */
+    public void register(RegisterBody registerBody) {
+        String tenantId = registerBody.getTenantId();
+        String username = registerBody.getUsername();
+        String password = registerBody.getPassword();
+        // 校验用户类型是否存在
+        String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
+
+        boolean captchaEnabled = captchaProperties.getEnabled();
+        // 验证码开关
+        if (captchaEnabled) {
+            validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
+        }
+
+        // 注册用户信息
+        RemoteUserBo remoteUserBo = new RemoteUserBo();
+        remoteUserBo.setTenantId(tenantId);
+        remoteUserBo.setUserName(username);
+        remoteUserBo.setNickName(username);
+        remoteUserBo.setPassword(BCrypt.hashpw(password));
+        remoteUserBo.setUserType(userType);
+
+        boolean regFlag = remoteUserService.registerUserInfo(remoteUserBo);
+        if (!regFlag) {
+            throw new UserException("user.register.error");
+        }
+        recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param username 用户名
+     * @param code     验证码
+     * @param uuid     唯一标识
+     */
+    public void validateCaptcha(String tenantId, String username, String code, String uuid) {
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
+        String captcha = RedisUtils.getCacheObject(verifyKey);
+        RedisUtils.deleteObject(verifyKey);
+        if (captcha == null) {
+            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha)) {
+            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+            throw new CaptchaException();
+        }
+    }
+
+    /**
+     * 记录登录信息
+     *
+     * @param username 用户名
+     * @param status   状态
+     * @param message  消息内容
+     * @return
+     */
+    public void recordLogininfor(String tenantId, String username, String status, String message) {
+        // 封装对象
+        LogininforEvent logininforEvent = new LogininforEvent();
+        logininforEvent.setTenantId(tenantId);
+        logininforEvent.setUsername(username);
+        logininforEvent.setStatus(status);
+        logininforEvent.setMessage(message);
+        SpringUtils.context().publishEvent(logininforEvent);
+    }
+
+    /**
+     * 登录校验
+     */
+    public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
+        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
+        String loginFail = Constants.LOGIN_FAIL;
+        Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
+        Integer lockTime = userPasswordProperties.getLockTime();
+
+        // 获取用户登录错误次数,默认为0 (可自定义限制策略 例如: key + username + ip)
+        int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
+        // 锁定时间内登录 则踢出
+        if (errorNumber >= maxRetryCount) {
+            recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+            throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
+        }
+
+        if (supplier.get()) {
+            // 错误次数递增
+            errorNumber++;
+            RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
+            // 达到规定错误次数 则锁定登录
+            if (errorNumber >= maxRetryCount) {
+                recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+                throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
+            } else {
+                // 未达到规定错误次数
+                recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
+                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
+            }
+        }
+
+        // 登录成功 清空错误次数
+        RedisUtils.deleteObject(errorKey);
+    }
+
+    /**
+     * 校验租户
+     *
+     * @param tenantId 租户ID
+     */
+    public void checkTenant(String tenantId) {
+        if (!TenantHelper.isEnable()) {
+            return;
+        }
+        if (StringUtils.isBlank(tenantId)) {
+            throw new TenantException("tenant.number.not.blank");
+        }
+        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
+            return;
+        }
+        RemoteTenantVo tenant = remoteTenantService.queryByTenantId(tenantId);
+        if (ObjectUtil.isNull(tenant)) {
+            log.info("登录租户:{} 不存在.", tenantId);
+            throw new TenantException("tenant.not.exists");
+        } else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
+            log.info("登录租户:{} 已被停用.", tenantId);
+            throw new TenantException("tenant.blocked");
+        } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
+            && new Date().after(tenant.getExpireTime())) {
+            log.info("登录租户:{} 已超过有效期.", tenantId);
+            throw new TenantException("tenant.expired");
+        }
+    }
+}

+ 86 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java

@@ -0,0 +1,86 @@
+package org.dromara.auth.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.form.EmailLoginBody;
+import org.dromara.auth.service.IAuthStrategy;
+import org.dromara.auth.service.SysLoginService;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.stereotype.Service;
+
+/**
+ * 邮件认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("email" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class EmailAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    @Override
+    public LoginVo login(String body, RemoteClientVo client) {
+        EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        String tenantId = loginBody.getTenantId();
+        String email = loginBody.getEmail();
+        String emailCode = loginBody.getEmailCode();
+        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+            LoginUser user = remoteUserService.getUserInfoByEmail(email, tenantId);
+            loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUsername(), () -> !validateEmailCode(tenantId, email, emailCode));
+            return user;
+        });
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    /**
+     * 校验邮箱验证码
+     */
+    private boolean validateEmailCode(String tenantId, String email, String emailCode) {
+        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
+        if (StringUtils.isBlank(code)) {
+            loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        return code.equals(emailCode);
+    }
+
+}

+ 107 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java

@@ -0,0 +1,107 @@
+package org.dromara.auth.service.impl;
+
+import cn.hutool.crypto.digest.BCrypt;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.form.PasswordLoginBody;
+import org.dromara.auth.properties.CaptchaProperties;
+import org.dromara.auth.service.IAuthStrategy;
+import org.dromara.auth.service.SysLoginService;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.exception.user.CaptchaException;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.stereotype.Service;
+
+/**
+ * 密码认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("password" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class PasswordAuthStrategy implements IAuthStrategy {
+
+    private final CaptchaProperties captchaProperties;
+
+    private final SysLoginService loginService;
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    @Override
+    public LoginVo login(String body, RemoteClientVo client) {
+        PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        String tenantId = loginBody.getTenantId();
+        String username = loginBody.getUsername();
+        String password = loginBody.getPassword();
+        String code = loginBody.getCode();
+        String uuid = loginBody.getUuid();
+
+        // 验证码开关
+        if (captchaProperties.getEnabled()) {
+            validateCaptcha(tenantId, username, code, uuid);
+        }
+        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+            LoginUser user = remoteUserService.getUserInfo(username, tenantId);
+            loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+            return user;
+        });
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param username 用户名
+     * @param code     验证码
+     * @param uuid     唯一标识
+     */
+    private void validateCaptcha(String tenantId, String username, String code, String uuid) {
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
+        String captcha = RedisUtils.getCacheObject(verifyKey);
+        RedisUtils.deleteObject(verifyKey);
+        if (captcha == null) {
+            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha)) {
+            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+            throw new CaptchaException();
+        }
+    }
+
+}

+ 86 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java

@@ -0,0 +1,86 @@
+package org.dromara.auth.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.form.SmsLoginBody;
+import org.dromara.auth.service.IAuthStrategy;
+import org.dromara.auth.service.SysLoginService;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.stereotype.Service;
+
+/**
+ * 短信认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("sms" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SmsAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    @Override
+    public LoginVo login(String body, RemoteClientVo client) {
+        SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        String tenantId = loginBody.getTenantId();
+        String phonenumber = loginBody.getPhonenumber();
+        String smsCode = loginBody.getSmsCode();
+        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+            LoginUser user = remoteUserService.getUserInfoByPhonenumber(phonenumber, tenantId);
+            loginService.checkLogin(LoginType.SMS, tenantId, user.getUsername(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
+            return user;
+        });
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    /**
+     * 校验短信验证码
+     */
+    private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
+        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
+        if (StringUtils.isBlank(code)) {
+            loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        return code.equals(smsCode);
+    }
+
+}

+ 102 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java

@@ -0,0 +1,102 @@
+package org.dromara.auth.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.form.SocialLoginBody;
+import org.dromara.auth.service.IAuthStrategy;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.SocialUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.api.RemoteSocialService;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.dromara.system.api.domain.vo.RemoteSocialVo;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 第三方授权策略
+ *
+ * @author thiszhc is 三三
+ */
+@Slf4j
+@Service("social" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SocialAuthStrategy implements IAuthStrategy {
+
+    private final SocialProperties socialProperties;
+
+    @DubboReference
+    private RemoteSocialService remoteSocialService;
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    /**
+     * 登录-第三方授权登录
+     *
+     * @param body     登录信息
+     * @param client   客户端信息
+     */
+    @Override
+    public LoginVo login(String body, RemoteClientVo client) {
+        SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+            loginBody.getSource(), loginBody.getSocialCode(),
+            loginBody.getSocialState(), socialProperties);
+        if (!response.ok()) {
+            throw new ServiceException(response.getMsg());
+        }
+        AuthUser authUserData = response.getData();
+
+        List<RemoteSocialVo> list = remoteSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
+        if (CollUtil.isEmpty(list)) {
+            throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
+        }
+        RemoteSocialVo socialVo;
+        if (TenantHelper.isEnable()) {
+            Optional<RemoteSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
+            if (opt.isEmpty()) {
+                throw new ServiceException("对不起,你没有权限登录当前租户!");
+            }
+            socialVo = opt.get();
+        } else {
+            socialVo = list.get(0);
+        }
+
+        LoginUser loginUser = remoteUserService.getUserInfo(socialVo.getUserId(), socialVo.getTenantId());
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+}

+ 91 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java

@@ -0,0 +1,91 @@
+package org.dromara.auth.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.form.XcxLoginBody;
+import org.dromara.auth.service.IAuthStrategy;
+import org.dromara.auth.service.SysLoginService;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.dromara.system.api.model.XcxLoginUser;
+import org.springframework.stereotype.Service;
+
+/**
+ * 邮件认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("xcx" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class XcxAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    @Override
+    public LoginVo login(String body, RemoteClientVo client) {
+        XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        // xcxCode 为 小程序调用 wx.login 授权后获取
+        String xcxCode = loginBody.getXcxCode();
+        // 多个小程序识别使用
+        String appid = loginBody.getAppid();
+
+        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
+        AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
+            .clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
+            .ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
+        AuthCallback authCallback = new AuthCallback();
+        authCallback.setCode(xcxCode);
+        AuthResponse<AuthUser> resp = authRequest.login(authCallback);
+        String openid, unionId;
+        if (resp.ok()) {
+            AuthToken token = resp.getData().getToken();
+            openid = token.getOpenId();
+            // 微信小程序只有关联到微信开放平台下之后才能获取到 unionId,因此unionId不一定能返回。
+            unionId = token.getUnionId();
+        } else {
+            throw new ServiceException(resp.getMsg());
+        }
+        // todo getUserInfoByOpenid 方法内部查询逻辑需要自行根据业务实现
+        XcxLoginUser loginUser = remoteUserService.getUserInfoByOpenid(openid);
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        loginVo.setOpenid(openid);
+        return loginVo;
+    }
+
+}

+ 33 - 0
ruoyi-auth/src/main/resources/application.yml

@@ -0,0 +1,33 @@
+# Tomcat
+server:
+  port: 9210
+
+# Spring
+spring:
+  application:
+    # 应用名称
+    name: ruoyi-auth
+  profiles:
+    # 环境配置
+    active: @profiles.active@
+
+--- # nacos 配置
+spring:
+  cloud:
+    nacos:
+      # nacos 服务地址
+      server-addr: @nacos.server@
+      username: @nacos.username@
+      password: @nacos.password@
+      discovery:
+        # 注册组
+        group: @nacos.discovery.group@
+        namespace: ${spring.profiles.active}
+      config:
+        # 配置组
+        group: @nacos.config.group@
+        namespace: ${spring.profiles.active}
+  config:
+    import:
+      - optional:nacos:application-common.yml
+      - optional:nacos:${spring.application.name}.yml

+ 10 - 0
ruoyi-auth/src/main/resources/banner.txt

@@ -0,0 +1,10 @@
+Spring Boot Version: ${spring-boot.version}
+Spring Application Name: ${spring.application.name}
+                            _                        _    _     
+                           (_)                      | |  | |    
+ _ __  _   _   ___   _   _  _  ______   __ _  _   _ | |_ | |__  
+| '__|| | | | / _ \ | | | || ||______| / _` || | | || __|| '_ \ 
+| |   | |_| || (_) || |_| || |        | (_| || |_| || |_ | | | |
+|_|    \__,_| \___/  \__, ||_|         \__,_| \__,_| \__||_| |_|
+                      __/ |                                     
+                     |___/                                      

+ 28 - 0
ruoyi-auth/src/main/resources/logback-plus.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <!-- 日志存放路径 -->
+    <property name="log.path" value="logs/${project.artifactId}"/>
+    <!-- 日志输出格式 -->
+    <property name="console.log.pattern"
+              value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${console.log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+    </appender>
+
+    <include resource="logback-common.xml" />
+
+    <include resource="logback-logstash.xml" />
+
+    <!-- 开启 skywalking 日志收集 -->
+    <include resource="logback-skylog.xml" />
+
+    <!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="console"/>
+    </root>
+</configuration>

+ 57 - 0
ruoyi-common/pom.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-cloud-plus</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <modules>
+        <module>ruoyi-common-bom</module>
+        <module>ruoyi-common-alibaba-bom</module>
+        <module>ruoyi-common-log</module>
+        <module>ruoyi-common-service-impl</module>
+        <module>ruoyi-common-excel</module>
+        <module>ruoyi-common-core</module>
+        <module>ruoyi-common-redis</module>
+        <module>ruoyi-common-doc</module>
+        <module>ruoyi-common-security</module>
+        <module>ruoyi-common-satoken</module>
+        <module>ruoyi-common-web</module>
+        <module>ruoyi-common-mybatis</module>
+        <module>ruoyi-common-job</module>
+        <module>ruoyi-common-dubbo</module>
+        <module>ruoyi-common-seata</module>
+        <module>ruoyi-common-loadbalancer</module>
+        <module>ruoyi-common-oss</module>
+        <module>ruoyi-common-ratelimiter</module>
+        <module>ruoyi-common-idempotent</module>
+        <module>ruoyi-common-mail</module>
+        <module>ruoyi-common-sms</module>
+        <module>ruoyi-common-logstash</module>
+        <module>ruoyi-common-elasticsearch</module>
+        <module>ruoyi-common-sentinel</module>
+        <module>ruoyi-common-skylog</module>
+        <module>ruoyi-common-prometheus</module>
+        <module>ruoyi-common-translation</module>
+        <module>ruoyi-common-sensitive</module>
+        <module>ruoyi-common-json</module>
+        <module>ruoyi-common-encrypt</module>
+        <module>ruoyi-common-tenant</module>
+        <module>ruoyi-common-websocket</module>
+        <module>ruoyi-common-social</module>
+        <module>ruoyi-common-nacos</module>
+        <module>ruoyi-common-bus</module>
+        <module>ruoyi-common-sse</module>
+    </modules>
+
+    <artifactId>ruoyi-common</artifactId>
+    <packaging>pom</packaging>
+
+    <description>
+        ruoyi-common通用模块
+    </description>
+
+</project>

+ 181 - 0
ruoyi-common/ruoyi-common-alibaba-bom/pom.xml

@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.dromara</groupId>
+    <artifactId>ruoyi-common-alibaba-bom</artifactId>
+    <version>${revision}</version>
+    <packaging>pom</packaging>
+
+    <description>
+        ruoyi-common-alibaba-bom alibaba依赖项
+    </description>
+
+    <properties>
+        <revision>2.4.1</revision>
+        <spring-cloud-alibaba.version>2023.0.3.3</spring-cloud-alibaba.version>
+        <sentinel.version>1.8.8</sentinel.version>
+        <seata.version>2.4.0</seata.version>
+        <nacos.client.version>2.5.1</nacos.client.version>
+        <dubbo.version>3.3.5</dubbo.version>
+        <dubbo-extensions.version>3.3.1</dubbo-extensions.version>
+    </properties>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.nacos</groupId>
+                <artifactId>nacos-client</artifactId>
+                <version>${nacos.client.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-core</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-parameter-flow-control</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-extension</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-apollo</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-zookeeper</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-nacos</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-redis</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-consul</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-web-servlet</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-transport-simple-http</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-annotation-aspectj</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-reactor-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-cluster-server-default</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-cluster-client-default</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-spring-webflux-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-api-gateway-adapter-common</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-spring-webmvc-v6x-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-dubbo-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-apache-dubbo-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-apache-dubbo3-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.seata</groupId>
+                <artifactId>seata-spring-boot-starter</artifactId>
+                <version>${seata.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.seata</groupId>
+                <artifactId>seata-all</artifactId>
+                <version>${seata.version}</version>
+            </dependency>
+
+            <!-- Apache Dubbo 配置 -->
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-spring-boot-starter</artifactId>
+                <version>${dubbo.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-spring-boot-actuator</artifactId>
+                <version>${dubbo.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo</artifactId>
+                <version>${dubbo.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.dubbo.extensions</groupId>
+                <artifactId>dubbo-metadata-report-redis</artifactId>
+                <version>${dubbo-extensions.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>

+ 262 - 0
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -0,0 +1,262 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.dromara</groupId>
+    <artifactId>ruoyi-common-bom</artifactId>
+    <version>${revision}</version>
+    <packaging>pom</packaging>
+
+    <description>
+        ruoyi-common-bom common依赖项
+    </description>
+
+    <properties>
+        <revision>2.4.1</revision>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- 核心模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-core</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 接口模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-doc</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 安全模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-security</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 权限认证服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-satoken</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 日志记录 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-log</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 通用service实现模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-service-impl</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- excel -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-excel</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 缓存服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-redis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- web服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-web</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 数据库服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-mybatis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 定时任务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-job</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- RPC服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-dubbo</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 分布式事务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-seata</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 自定义负载均衡 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-loadbalancer</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- oss服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-oss</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 限流功能 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-ratelimiter</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 幂等功能 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-idempotent</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 邮件模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-mail</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 短信模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-sms</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- logstash日志推送模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-logstash</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- ES搜索引擎服务 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-elasticsearch</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 限流模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-sentinel</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- skywalking日志收集模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-skylog</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- prometheus监控 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-prometheus</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 通用翻译功能 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-translation</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 脱敏模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-sensitive</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 序列化模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-json</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 数据加解密模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-encrypt</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 租户模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-tenant</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- websocket模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-websocket</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 授权认证 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-social</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 配置中心 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-nacos</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 消息总线模块 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-bus</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- sse -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-sse</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+</project>

+ 35 - 0
ruoyi-common/ruoyi-common-bus/pom.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-bus</artifactId>
+
+    <description>
+        ruoyi-common-bus 消息总线模块
+    </description>
+
+    <dependencies>
+        <!-- rabbitmq -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
+        </dependency>
+        <!-- kafka -->
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.cloud</groupId>-->
+<!--            <artifactId>spring-cloud-starter-bus-kafka</artifactId>-->
+<!--        </dependency>-->
+        <!-- rocketmq -->
+<!--        <dependency>-->
+<!--            <groupId>com.alibaba.cloud</groupId>-->
+<!--            <artifactId>spring-cloud-starter-bus-rocketmq</artifactId>-->
+<!--        </dependency>-->
+    </dependencies>
+</project>

+ 15 - 0
ruoyi-common/ruoyi-common-bus/src/main/java/org/dromara/common/bus/config/BusCustomConfiguration.java

@@ -0,0 +1,15 @@
+package org.dromara.common.bus.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan;
+
+/**
+ * bus 配置
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@RemoteApplicationEventScan(basePackages = "${spring.cloud.bus.base-packages}")
+public class BusCustomConfiguration {
+
+}

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

@@ -0,0 +1 @@
+org.dromara.common.bus.config.BusCustomConfiguration

+ 104 - 0
ruoyi-common/ruoyi-common-core/pom.xml

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-core</artifactId>
+
+    <description>
+        ruoyi-common-core 核心模块
+    </description>
+
+    <dependencies>
+        <!-- Spring框架基本的核心工具 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+
+        <!-- SpringWeb模块 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+        <!-- Hibernate Validator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!--常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- servlet包 -->
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-extra</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!--  自动生成YML配置关联JSON文件  -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-properties-migrator</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.github.linpeilie</groupId>
+            <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 离线IP地址定位库 -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 17 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java

@@ -0,0 +1,17 @@
+package org.dromara.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * 程序注解配置
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@EnableAspectJAutoProxy
+@EnableAsync(proxyTargetClass = true)
+public class ApplicationConfig {
+
+}

+ 52 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java

@@ -0,0 +1,52 @@
+package org.dromara.common.core.config;
+
+import cn.hutool.core.util.ArrayUtil;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * 异步配置
+ * <p>
+ * 如果未使用虚拟线程则生效
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class AsyncConfig implements AsyncConfigurer {
+
+    /**
+     * 自定义 @Async 注解使用系统线程池
+     */
+    @Override
+    public Executor getAsyncExecutor() {
+        if(SpringUtils.isVirtual()) {
+            return new VirtualThreadTaskExecutor("async-");
+        }
+        return SpringUtils.getBean("scheduledExecutorService");
+    }
+
+    /**
+     * 异步执行异常处理
+     */
+    @Override
+    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+        return (throwable, method, objects) -> {
+            throwable.printStackTrace();
+            StringBuilder sb = new StringBuilder();
+            sb.append("Exception message - ").append(throwable.getMessage())
+                .append(", Method name - ").append(method.getName());
+            if (ArrayUtil.isNotEmpty(objects)) {
+                sb.append(", Parameter value - ").append(Arrays.toString(objects));
+            }
+            throw new ServiceException(sb.toString());
+        };
+    }
+
+}

+ 70 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java

@@ -0,0 +1,70 @@
+package org.dromara.common.core.config;
+
+import jakarta.annotation.PreDestroy;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.Threads;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+ * @author Lion Li
+ **/
+@Slf4j
+@AutoConfiguration
+public class ThreadPoolConfig {
+
+    /**
+     * 核心线程数 = cpu 核心数 + 1
+     */
+    private final int core = Runtime.getRuntime().availableProcessors() + 1;
+
+    private ScheduledExecutorService scheduledExecutorService;
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService() {
+        // daemon 必须为 true
+        BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
+        if (SpringUtils.isVirtual()) {
+            builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
+        } else {
+            builder.namingPattern("schedule-pool-%d");
+        }
+        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
+            builder.build(),
+            new ThreadPoolExecutor.CallerRunsPolicy()) {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t) {
+                super.afterExecute(r, t);
+                Threads.printException(r, t);
+            }
+        };
+        this.scheduledExecutorService = scheduledThreadPoolExecutor;
+        return scheduledThreadPoolExecutor;
+    }
+
+    /**
+     * 销毁事件
+     */
+    @PreDestroy
+    public void destroy() {
+        try {
+            log.info("====关闭后台任务任务线程池====");
+            Threads.shutdownAndAwaitTermination(scheduledExecutorService);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}

+ 41 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java

@@ -0,0 +1,41 @@
+package org.dromara.common.core.config;
+
+import jakarta.validation.Validator;
+import org.hibernate.validator.HibernateValidator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+import java.util.Properties;
+
+/**
+ * 校验框架配置类
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration(before = ValidationAutoConfiguration.class)
+public class ValidatorConfig {
+
+    /**
+     * 配置校验框架 快速失败模式
+     */
+    @Bean
+    public Validator validator(MessageSource messageSource) {
+        try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
+            // 国际化
+            factoryBean.setValidationMessageSource(messageSource);
+            // 设置使用 HibernateValidator 校验器
+            factoryBean.setProviderClass(HibernateValidator.class);
+            Properties properties = new Properties();
+            // 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
+            properties.setProperty("hibernate.validator.fail_fast", "true");
+            factoryBean.setValidationProperties(properties);
+            // 加载配置
+            factoryBean.afterPropertiesSet();
+            return factoryBean.getValidator();
+        }
+    }
+
+}

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff