This commit is contained in:
2025-06-17 17:08:14 +08:00
commit 71179324c8
1205 changed files with 798946 additions and 0 deletions

18
.editorconfig Normal file
View File

@@ -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
.gitignore vendored Normal file
View File

@@ -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

12
.run/ruoyi-auth.run.xml Normal file
View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-auth" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-auth:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-auth/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-gateway" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-gateway:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-gateway/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

12
.run/ruoyi-gen.run.xml Normal file
View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-gen" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-gen:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-gen/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

12
.run/ruoyi-job.run.xml Normal file
View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-job" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-job:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-job/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-monitor" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-monitor/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

12
.run/ruoyi-nacos.run.xml Normal file
View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-nacos" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-nacos:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-nacos/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-resource" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-resource:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-resource/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-seata-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-seata-server:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-seata-server/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-sentinel-dashboard" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-sentinel-dashboard:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-sentinel-dashboard/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-snailjob-server/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

12
.run/ruoyi-system.run.xml Normal file
View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-system" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-system:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-system/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ruoyi-workflow" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-workflow:2.4.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-workflow/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

21
LICENSE Normal file
View File

@@ -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
README.md Normal file
View File

@@ -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.0-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 "屏幕截图") |

523
pom.xml Normal file
View File

@@ -0,0 +1,523 @@
<?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.0</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.6</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.35</hutool.version>
<redisson.version>3.45.1</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<snailjob.version>1.5.0</snailjob.version>
<satoken.version>1.42.0</satoken.version>
<lombok.version>1.18.36</lombok.version>
<logstash.version>7.4</logstash.version>
<easy-es.version>2.1.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-20250101</anyline.version>
<!-- 工作流配置 -->
<warm-flow.version>1.7.3</warm-flow.version>
<!-- mq配置 -->
<rocketmq.version>2.3.0</rocketmq.version>
<!-- 插件版本 -->
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.1.2</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>
<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
ruoyi-api/pom.xml Normal file
View File

@@ -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>

View File

@@ -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.0</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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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("推送功能未开启或服务未找到");
}
}
}

View File

@@ -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~10001~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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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 为用户 IDvalue 为对应的用户名称
*/
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
/**
* 根据角色 ID 列表查询角色名称映射关系
*
* @param roleIds 角色 ID 列表
* @return Map其中 key 为角色 IDvalue 为对应的角色名称
*/
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
/**
* 根据部门 ID 列表查询部门名称映射关系
*
* @param deptIds 部门 ID 列表
* @return Map其中 key 为部门 IDvalue 为对应的部门名称
*/
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
/**
* 根据岗位 ID 列表查询岗位名称映射关系
*
* @param postIds 岗位 ID 列表
* @return Map其中 key 为岗位 IDvalue 为对应的岗位名称
*/
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
}

View File

@@ -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;
}

View File

@@ -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<>();
}

View File

@@ -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<>();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,32 @@
<?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>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-bus</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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
ruoyi-auth/Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-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
ruoyi-auth/pom.xml Normal file
View File

@@ -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>

View File

@@ -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("(♥◠‿◠)ノ゙ 认证授权中心启动成功 ლ(´ڡ`ლ)゙ ");
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,87 @@
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.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();
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;
}
}

View File

@@ -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微服务管理系统");
}, 3, 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);
}
}

View File

@@ -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> {
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 tokenValue, Object loginId, long timeout) {
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,114 @@
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 cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
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();
if ("GITEE".equals(authUserData.getSource())) {
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
}
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

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

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