⚔ 发布 4.7.0 稳定性版本
@@ -2,7 +2,7 @@
 | 
			
		||||
  <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
 | 
			
		||||
    <deployment type="dockerfile">
 | 
			
		||||
      <settings>
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.6.0" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.7.0" />
 | 
			
		||||
        <option name="buildOnly" value="true" />
 | 
			
		||||
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
 | 
			
		||||
      </settings>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
 | 
			
		||||
    <deployment type="dockerfile">
 | 
			
		||||
      <settings>
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-server:4.6.0" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-server:4.7.0" />
 | 
			
		||||
        <option name="buildOnly" value="true" />
 | 
			
		||||
        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
 | 
			
		||||
      </settings>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  <configuration default="false" name="ruoyi-xxl-job-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
 | 
			
		||||
    <deployment type="dockerfile">
 | 
			
		||||
      <settings>
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.6.0" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.7.0" />
 | 
			
		||||
        <option name="buildOnly" value="true" />
 | 
			
		||||
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" />
 | 
			
		||||
      </settings>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										237
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,10 +1,16 @@
 | 
			
		||||
<img src="https://foruda.gitee.com/images/1679673773341074847/178e8451_1766278.png" width="50%" height="50%">
 | 
			
		||||
<div style="height: 10px; clear: both;"></div>
 | 
			
		||||
 | 
			
		||||
- - -
 | 
			
		||||
 | 
			
		||||
## 平台简介
 | 
			
		||||
[](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://github.com/JavaLionLi/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE)
 | 
			
		||||
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://github.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
 | 
			
		||||
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
 | 
			
		||||
<br>
 | 
			
		||||
[](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[]()
 | 
			
		||||
[]()
 | 
			
		||||
[]()
 | 
			
		||||
@@ -14,62 +20,95 @@
 | 
			
		||||
> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
 | 
			
		||||
活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源
 | 
			
		||||
 | 
			
		||||
> 系统演示: [传送门](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4836388&doc_id=1469725)
 | 
			
		||||
> 系统演示: [传送门](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4836388&doc_id=1469725)
 | 
			
		||||
 | 
			
		||||
| 功能介绍     | 使用技术                | 文档地址                                                                                              | 特性注意事项                     |
 | 
			
		||||
|----------|---------------------|---------------------------------------------------------------------------------------------------|----------------------------|
 | 
			
		||||
| 当前框架     | RuoYi-Vue-Plus      | [RuoYi-Vue-Plus文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages)                       | 重写RuoYi-Vue全方位升级(不兼容原框架)   |
 | 
			
		||||
| 微服务分支    | RuoYi-Cloud-Plus    | [微服务分支地址](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus)                                          | 重写RuoYi-Cloud全方位升级(不兼容原框架) |
 | 
			
		||||
| 单体分支     | RuoYi-Vue-Plus-fast | [fast分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/)                                | 单体应用结构                     |
 | 
			
		||||
| Vue3分支   | RuoYi-Vue-Plus-UI   | [UI地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus-UI)                                            | 由于组件还未完善 仅供学习              |
 | 
			
		||||
| 原框架      | RuoYi-Vue           | [RuoYi-Vue官网](http://ruoyi.vip/)                                                                  | 定期同步需要的功能                  |
 | 
			
		||||
| 前端开发框架   | Vue、Element UI      | [Element UI官网](https://element.eleme.cn/#/zh-CN)                                                  |                            |
 | 
			
		||||
| 后端开发框架   | SpringBoot          | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn)                                     |                            |
 | 
			
		||||
| 容器框架     | Undertow            | [Undertow官网](https://undertow.io/)                                                                | 基于 XNIO 的高性能容器             |
 | 
			
		||||
| 权限认证框架   | Sa-Token、Jwt        | [Sa-Token官网](https://sa-token.dev33.cn/)                                                          | 强解耦、强扩展                    |
 | 
			
		||||
| 关系数据库    | MySQL               | [MySQL官网](https://dev.mysql.com/)                                                                 | 适配 8.X 最低 5.7              |
 | 
			
		||||
| 关系数据库    | Oracle              | [Oracle官网](https://www.oracle.com/cn/database/)                                                   | 适配 11g 12c                 |
 | 
			
		||||
| 关系数据库    | PostgreSQL          | [PostgreSQL官网](https://www.postgresql.org/)                                                       | 适配 13 14                   |
 | 
			
		||||
| 关系数据库    | SQLServer           | [SQLServer官网](https://docs.microsoft.com/zh-cn/sql/sql-server)                                    | 适配 2017 2019               |
 | 
			
		||||
| 缓存数据库    | Redis               | [Redis官网](https://redis.io/)                                                                      | 适配 6.X 最低 4.X              |
 | 
			
		||||
| 数据库框架    | Mybatis-Plus        | [Mybatis-Plus文档](https://baomidou.com/guide/)                                                     | 快速 CRUD 增加开发效率             |
 | 
			
		||||
| 数据库框架    | p6spy               | [p6spy官网](https://p6spy.readthedocs.io/)                                                          | 更强劲的 SQL 分析                |
 | 
			
		||||
| 多数据源框架   | dynamic-datasource  | [dynamic-ds文档](https://www.kancloud.cn/tracy5546/dynamic-datasource/content)                      | 支持主从与多种类数据库异构              |
 | 
			
		||||
| 序列化框架    | Jackson             | [Jackson官网](https://github.com/FasterXML/jackson)                                                 | 统一使用 jackson 高效可靠          |
 | 
			
		||||
| Redis客户端 | Redisson            | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95)                        | 支持单机、集群配置                  |
 | 
			
		||||
| 分布式限流    | Redisson            | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95)                        | 全局、请求IP、集群ID 多种限流          |
 | 
			
		||||
| 分布式队列    | Redisson            | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95)                        | 普通队列、延迟队列、优先队列 等           |
 | 
			
		||||
| 分布式锁     | Lock4j              | [Lock4j官网](https://gitee.com/baomidou/lock4j)                                                     | 注解锁、工具锁 多种多样               |
 | 
			
		||||
| 分布式幂等    | Redisson            | [Lock4j文档](https://gitee.com/baomidou/lock4j)                                                     | 拦截重复提交                     |
 | 
			
		||||
| 分布式链路追踪  | Apache SkyWalking   | [Apache SkyWalking文档](https://skywalking.apache.org/docs/)                                        | 链路追踪、网格分析、度量聚合、可视化         |
 | 
			
		||||
| 分布式任务调度  | Xxl-Job             | [Xxl-Job官网](https://www.xuxueli.com/xxl-job/)                                                     | 高性能 高可靠 易扩展                |
 | 
			
		||||
| 文件存储     | Minio               | [Minio文档](https://docs.min.io/)                                                                   | 本地存储                       |
 | 
			
		||||
| 文件存储     | 七牛、阿里、腾讯            | [OSS使用文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4359146&doc_id=1469725) | 云存储                        |
 | 
			
		||||
| 短信模块     | 阿里、腾讯               | [短信使用文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=5578491&doc_id=1469725)  | 短信发送                       |
 | 
			
		||||
| 监控框架     | SpringBoot-Admin    | [SpringBoot-Admin文档](https://codecentric.github.io/spring-boot-admin/current/)                    | 全方位服务监控                    |
 | 
			
		||||
| 校验框架     | Validation          | [Validation文档](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/)    | 增强接口安全性、严谨性 支持国际化          |
 | 
			
		||||
| Excel框架  | Alibaba EasyExcel   | [EasyExcel文档](https://www.yuque.com/easyexcel/doc/easyexcel)                                      | 性能优异 扩展性强                  |
 | 
			
		||||
| 文档框架     | SpringDoc、javadoc   | [接口文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=5805266&doc_id=1469725)    | 无注解零入侵基于java注释             |
 | 
			
		||||
| 工具类框架    | Hutool、Lombok       | [Hutool文档](https://www.hutool.cn/docs/)                                                           | 减少代码冗余 增加安全性               |
 | 
			
		||||
| 代码生成器    | 适配MP、SpringDoc规范化代码 | [代码生成文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=5522329&doc_id=1469725)  | 一键生成前后端代码                  |
 | 
			
		||||
| 部署方式     | Docker              | [Docker文档](https://docs.docker.com/)                                                              | 容器编排 一键部署业务集群              |
 | 
			
		||||
| 国际化      | SpringMessage       | [SpringMVC文档](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc)   | Spring标准国际化方案              |
 | 
			
		||||
# 本框架与RuoYi的功能差异
 | 
			
		||||
 | 
			
		||||
| 功能          | 本框架                                                                                                               | RuoYi                                                                              |
 | 
			
		||||
|-------------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
 | 
			
		||||
| 前端项目        | 基于vue3-element-admin开源项目重写<br/>Vue3 + TS + ElementPlus                                                            | 基于Vue2/Vue3 + JS                                                                   | 
 | 
			
		||||
| 后端项目结构      | 采用插件化 + 扩展包形式 结构解耦 易于扩展                                                                                           | 模块相互注入耦合严重难以扩展                                                                     | 
 | 
			
		||||
| 后端代码风格      | 严格遵守Alibaba规范与项目统一配置的代码格式化                                                                                        | 代码书写与常规结构不同阅读障碍大                                                                   |
 | 
			
		||||
| Web容器       | 采用 Undertow 基于 XNIO 的高性能容器                                                                                        | 采用 Tomcat                                                                          |
 | 
			
		||||
| 权限认证        | 采用 Sa-Token、Jwt 静态使用功能齐全 低耦合 高扩展                                                                                  | Spring Security 配置繁琐扩展性极差                                                          |
 | 
			
		||||
| 权限注解        | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式        | 只支持是否存在匹配                                                                          |
 | 
			
		||||
| 关系数据库支持     | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换                                                              | 支持 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鉴权与分布式会话同步 不再只是基于单机的废物                                                         | 无                                                                                  |
 | 
			
		||||
| 序列化         | 采用 Jackson Spring官方内置序列化 靠谱!!!                                                                                    | 采用 fastjson bugjson 远近闻名                                                           | 
 | 
			
		||||
| 分布式幂等       | 参考美团GTIS防重系统简化实现(细节可看文档)                                                                                          | 手动编写注解基于aop实现                                                                      |
 | 
			
		||||
| 分布式任务调度     | 采用 Xxl-Job 天生支持分布式 统一的管理中心                                                                                        | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造                                                   | 
 | 
			
		||||
| 文件存储        | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储                                                     | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应                                                    |
 | 
			
		||||
| 云存储         | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家                                                                          | 不支持                                                                                |
 | 
			
		||||
| 短信          | 支持 阿里、腾讯 只需在yml配置好厂家密钥即可使用 接口化支持扩展其他厂家                                                                            | 不支持                                                                                |
 | 
			
		||||
| 邮件          | 采用 mail-api 通用协议支持大部分邮件厂商                                                                                         | 不支持                                                                                |
 | 
			
		||||
| 接口文档        | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了                                                     | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成                                                | 
 | 
			
		||||
| 校验框架        | 采用 Validation 支持注解与工具类校验 注解支持国际化                                                                                  | 仅支持注解 且注解不支持国际化                                                                    |
 | 
			
		||||
| Excel框架     | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                               | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           |
 | 
			
		||||
| 工具类框架       | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码                                                       | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等                                            | 
 | 
			
		||||
| 监控框架        | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控                                    | 无                                                                                  | 
 | 
			
		||||
| 链路追踪        | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗<br/>用了它即可实时查看请求经过的每一处每一个节点                                            | 无                                                                                  |
 | 
			
		||||
| 代码生成器       | 只需设计好表结构 一键生成所有crud代码与页面<br/>降低80%的开发量 把精力都投入到业务设计上<br/>框架为其适配MP、SpringDoc规范化代码 同时支持动态多数据源代码生成                    | 代码生成原生结构 只支持单数据源生成                                                                 |
 | 
			
		||||
| 部署方式        | 支持 Docker 编排 一键搭建所有环境 让开发人员从此不再为搭建环境而烦恼                                                                           | 原生jar部署 其他环境需手动下载安装 自行搭建                                                           | 
 | 
			
		||||
| 项目路径修改      | 提供详细的修改方案文档 并为其做了一些改动 非常简单即可修改成自己想要的                                                                              | 需要做很多改造 文档说明有限                                                                     |
 | 
			
		||||
| 国际化         | 基于请求头动态返回不同语种的文本内容 开发难度低 有对应的工具类 支持大部分注解内容国际化                                                                     | 只提供基础功能 其他需自行编写扩展                                                                  |
 | 
			
		||||
| 代码单例测试      | 提供单例测试 使用方式编写方法与maven多环境单测插件                                                                                      | 只提供基础功能 其他需自行编写扩展                                                                  |
 | 
			
		||||
| Demo案例      | 提供框架功能的实际使用案例 单独一个模块提供了很多很全                                                                                       | 无                                                                                  |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 本框架与RuoYi的业务差异
 | 
			
		||||
 | 
			
		||||
| 业务     | 功能说明                                    | 本框架 | RuoYi            |
 | 
			
		||||
|--------|-----------------------------------------|-----|------------------|
 | 
			
		||||
| 用户管理   | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等          | 支持  | 支持               |
 | 
			
		||||
| 部门管理   | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限          | 支持  | 支持               |
 | 
			
		||||
| 岗位管理   | 配置系统用户所属担任职务                            | 支持  | 支持               |
 | 
			
		||||
| 菜单管理   | 配置系统菜单、操作权限、按钮权限标识等                     | 支持  | 支持               |
 | 
			
		||||
| 角色管理   | 角色菜单权限分配、设置角色按机构进行数据范围权限划分              | 支持  | 支持               |
 | 
			
		||||
| 字典管理   | 对系统中经常使用的一些较为固定的数据进行维护                  | 支持  | 支持               |
 | 
			
		||||
| 参数管理   | 对系统动态配置常用参数                             | 支持  | 支持               |
 | 
			
		||||
| 通知公告   | 系统通知公告信息发布维护                            | 支持  | 支持               |
 | 
			
		||||
| 操作日志   | 系统正常操作日志记录和查询 系统异常信息日志记录和查询             | 支持  | 支持               |
 | 
			
		||||
| 登录日志   | 系统登录日志记录查询包含登录异常                        | 支持  | 支持               |
 | 
			
		||||
| 文件管理   | 系统文件展示、上传、下载、删除等管理                      | 支持  | 无                |
 | 
			
		||||
| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理          | 支持  | 无                |
 | 
			
		||||
| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作                   | 支持  | 支持               |
 | 
			
		||||
| 定时任务   | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等         | 支持  | 仅支持任务与日志管理       |
 | 
			
		||||
| 代码生成   | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持  | 仅支持单数据源          |
 | 
			
		||||
| 系统接口   | 根据业务代码自动生成相关的api接口文档                    | 支持  | 支持               |
 | 
			
		||||
| 服务监控   | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等     | 支持  | 仅支持单机CPU、内存、磁盘监控 |
 | 
			
		||||
| 缓存监控   | 对系统的缓存信息查询,命令统计等。                       | 支持  | 支持               |
 | 
			
		||||
| 在线构建器  | 拖动表单元素生成相应的HTML代码。                      | 支持  | 支持               |
 | 
			
		||||
| 使用案例   | 系统的一些功能案例                               | 支持  | 不支持              |
 | 
			
		||||
 | 
			
		||||
## 参考文档
 | 
			
		||||
 | 
			
		||||
使用框架前请仔细阅读文档重点注意事项
 | 
			
		||||
<br>
 | 
			
		||||
>[初始化项目 必看](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4164117&doc_id=1469725)
 | 
			
		||||
>>[https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4164117&doc_id=1469725](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4164117&doc_id=1469725)
 | 
			
		||||
>[初始化项目 必看](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4164117&doc_id=1469725)
 | 
			
		||||
>>[https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4164117&doc_id=1469725](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4164117&doc_id=1469725)
 | 
			
		||||
>
 | 
			
		||||
>[专栏与视频 入门必看](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=5473272&doc_id=1469725)
 | 
			
		||||
>>[https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=5473272&doc_id=1469725](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=5473272&doc_id=1469725)
 | 
			
		||||
>[专栏与视频 入门必看](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=5473272&doc_id=1469725)
 | 
			
		||||
>>[https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=5473272&doc_id=1469725](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=5473272&doc_id=1469725)
 | 
			
		||||
>
 | 
			
		||||
>[部署项目 必看](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4219382&doc_id=1469725)
 | 
			
		||||
>>[https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4219382&doc_id=1469725](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4219382&doc_id=1469725)
 | 
			
		||||
>[部署项目 必看](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4219382&doc_id=1469725)
 | 
			
		||||
>>[https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4219382&doc_id=1469725](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4219382&doc_id=1469725)
 | 
			
		||||
> 
 | 
			
		||||
>[参考文档 Wiki](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages)
 | 
			
		||||
>>[https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages)
 | 
			
		||||
>[参考文档 Wiki](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages)
 | 
			
		||||
>>[https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages)
 | 
			
		||||
 | 
			
		||||
## 软件架构图
 | 
			
		||||
 | 
			
		||||
@@ -83,81 +122,43 @@
 | 
			
		||||
### 其他
 | 
			
		||||
 | 
			
		||||
* 同步升级 RuoYi-Vue
 | 
			
		||||
* GitHub 地址 [RuoYi-Vue-Plus-github](https://github.com/JavaLionLi/RuoYi-Vue-Plus)
 | 
			
		||||
* 单模块 分支 [RuoYi-Vue-Plus-fast](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/)
 | 
			
		||||
* GitHub 地址 [RuoYi-Vue-Plus-github](https://github.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
* 单模块 分支 [RuoYi-Vue-Plus-fast](https://gitee.com/dromara/RuoYi-Vue-Plus/tree/fast/)
 | 
			
		||||
* 微服务 分支 [RuoYi-Cloud-Plus](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus)
 | 
			
		||||
* 用户扩展项目 [扩展项目列表](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
 | 
			
		||||
* Vue3 分支 [RuoYi-Vue-Plus-UI](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus-UI)
 | 
			
		||||
* 用户扩展项目 [扩展项目列表](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
 | 
			
		||||
 | 
			
		||||
## 加群与捐献
 | 
			
		||||
>[加群与捐献](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/加群与捐献?sort_id=4104598)
 | 
			
		||||
>>[https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/加群与捐献?sort_id=4104598](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/加群与捐献?sort_id=4104598)
 | 
			
		||||
>[加群与捐献](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/加群与捐献?sort_id=4104598)
 | 
			
		||||
>>[https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/加群与捐献?sort_id=4104598](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/加群与捐献?sort_id=4104598)
 | 
			
		||||
 | 
			
		||||
## 捐献作者
 | 
			
		||||
作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭  
 | 
			
		||||
<img src="https://images.gitee.com/uploads/images/2022/0218/213734_b1b8197f_1766278.jpeg" width="300px" height="450px" />
 | 
			
		||||
<img src="https://images.gitee.com/uploads/images/2021/0525/101713_3d18b119_1766278.jpeg" width="300px" height="450px" />
 | 
			
		||||
 | 
			
		||||
## 业务功能
 | 
			
		||||
 | 
			
		||||
| 功能 | 介绍 |
 | 
			
		||||
|---|---|
 | 
			
		||||
| 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置。 |
 | 
			
		||||
| 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 |
 | 
			
		||||
| 岗位管理 | 配置系统用户所属担任职务。 |
 | 
			
		||||
| 菜单管理 | 配置系统菜单,操作权限,按钮权限标识等。 |
 | 
			
		||||
| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分。 |
 | 
			
		||||
| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护。 |
 | 
			
		||||
| 参数管理 | 对系统动态配置常用参数。 |
 | 
			
		||||
| 通知公告 | 系统通知公告信息发布维护。 |
 | 
			
		||||
| 操作日志 | 系统正常操作日志记录和查询;系统异常信息日志记录和查询。 |
 | 
			
		||||
| 登录日志 | 系统登录日志记录查询包含登录异常。 |
 | 
			
		||||
| 文件管理 | 系统文件上传、下载等管理。 |
 | 
			
		||||
| 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志。 |
 | 
			
		||||
| 代码生成 | 前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 |
 | 
			
		||||
| 系统接口 | 根据业务代码自动生成相关的api接口文档。 |
 | 
			
		||||
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等。 |
 | 
			
		||||
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 |
 | 
			
		||||
| 在线构建器 | 拖动表单元素生成相应的HTML代码。 |
 | 
			
		||||
| 连接池监视 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 |
 | 
			
		||||
| 使用案例 | 系统的一些功能案例 |
 | 
			
		||||
 | 
			
		||||
## 演示图例
 | 
			
		||||
 | 
			
		||||
<table border="1" cellpadding="1" cellspacing="1" style="width:500px">
 | 
			
		||||
    <tbody>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-972235bcbe3518dedd351ff0e2ee7d1031c.png" width="1920" /></td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-5e0097702fa91e2e36391de8127676a7fa1.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
            <p><img src="https://oscimg.oschina.net/oscnet/up-e56e3828f48cd9886d88731766f06d5f3c1.png" width="1920" /></p>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-0715990ea1a9f254ec2138fcd063c1f556a.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-eaf5417ccf921bb64abb959e3d8e290467f.png" width="1920" /></td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-fc285cf33095ebf8318de6999af0f473861.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-60c83fd8bd61c29df6dbf47c88355e9c272.png" width="1920" /></td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-7f731948c8b73c7d90f67f9e1c7a534d5c3.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-e4de89b5e2d20c52d3c3a47f9eb88eb8526.png" width="1920" /></td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-8791d823a508eb90e67c604f36f57491a67.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-4589afd99982ead331785299b894174feb6.png" width="1920" /></td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-8ea177cdacaea20995daf2f596b15232561.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-32d1d04c55c11f74c9129fbbc58399728c4.png" width="1920" /></td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-04fa118f7631b7ae6fd72299ca0a1430a63.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-fe7e85b65827802bfaadf3acd42568b58c7.png" width="1920" /></td>
 | 
			
		||||
            <td><img src="https://oscimg.oschina.net/oscnet/up-eff2b02a54f8188022d8498cfe6af6fcc06.png" width="1920" /></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
|                                                                                            |                                                                                            |
 | 
			
		||||
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
|  |  |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						@@ -6,15 +6,15 @@
 | 
			
		||||
 | 
			
		||||
    <groupId>com.ruoyi</groupId>
 | 
			
		||||
    <artifactId>ruoyi-vue-plus</artifactId>
 | 
			
		||||
    <version>4.6.0</version>
 | 
			
		||||
    <version>4.7.0</version>
 | 
			
		||||
 | 
			
		||||
    <name>RuoYi-Vue-Plus</name>
 | 
			
		||||
    <url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
 | 
			
		||||
    <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
 | 
			
		||||
    <description>RuoYi-Vue-Plus后台管理系统</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <ruoyi-vue-plus.version>4.6.0</ruoyi-vue-plus.version>
 | 
			
		||||
        <spring-boot.version>2.7.9</spring-boot.version>
 | 
			
		||||
        <ruoyi-vue-plus.version>4.7.0</ruoyi-vue-plus.version>
 | 
			
		||||
        <spring-boot.version>2.7.11</spring-boot.version>
 | 
			
		||||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
			
		||||
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
@@ -27,16 +27,18 @@
 | 
			
		||||
        <satoken.version>1.34.0</satoken.version>
 | 
			
		||||
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
 | 
			
		||||
        <p6spy.version>3.9.1</p6spy.version>
 | 
			
		||||
        <hutool.version>5.8.15</hutool.version>
 | 
			
		||||
        <hutool.version>5.8.18</hutool.version>
 | 
			
		||||
        <okhttp.version>4.10.0</okhttp.version>
 | 
			
		||||
        <spring-boot-admin.version>2.7.10</spring-boot-admin.version>
 | 
			
		||||
        <redisson.version>3.20.0</redisson.version>
 | 
			
		||||
        <redisson.version>3.20.1</redisson.version>
 | 
			
		||||
        <lock4j.version>2.2.3</lock4j.version>
 | 
			
		||||
        <dynamic-ds.version>3.5.2</dynamic-ds.version>
 | 
			
		||||
        <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
 | 
			
		||||
        <xxl-job.version>2.3.1</xxl-job.version>
 | 
			
		||||
        <xxl-job.version>2.4.0</xxl-job.version>
 | 
			
		||||
        <lombok.version>1.18.26</lombok.version>
 | 
			
		||||
        <bouncycastle.version>1.72</bouncycastle.version>
 | 
			
		||||
        <!-- 离线IP地址定位库 -->
 | 
			
		||||
        <ip2region.version>2.7.0</ip2region.version>
 | 
			
		||||
 | 
			
		||||
        <!-- 临时修复 snakeyaml 漏洞 -->
 | 
			
		||||
        <snakeyaml.version>1.33</snakeyaml.version>
 | 
			
		||||
@@ -258,6 +260,13 @@
 | 
			
		||||
                <version>${alibaba-ttl.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- 离线IP地址定位库 ip2region -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.lionsoul</groupId>
 | 
			
		||||
                <artifactId>ip2region</artifactId>
 | 
			
		||||
                <version>${ip2region.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- 临时修复 snakeyaml 漏洞 -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.yaml</groupId>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>ruoyi-vue-plus</artifactId>
 | 
			
		||||
        <groupId>com.ruoyi</groupId>
 | 
			
		||||
        <version>4.6.0</version>
 | 
			
		||||
        <version>4.7.0</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <artifactId>ruoyi-extend</artifactId>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>ruoyi-extend</artifactId>
 | 
			
		||||
        <groupId>com.ruoyi</groupId>
 | 
			
		||||
        <version>4.6.0</version>
 | 
			
		||||
        <version>4.7.0</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <packaging>jar</packaging>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@ spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    active: @profiles.active@
 | 
			
		||||
 | 
			
		||||
logging:
 | 
			
		||||
  config: classpath:logback-plus.xml
 | 
			
		||||
 | 
			
		||||
--- # 监控中心服务端配置
 | 
			
		||||
spring:
 | 
			
		||||
  security:
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>ruoyi-extend</artifactId>
 | 
			
		||||
        <groupId>com.ruoyi</groupId>
 | 
			
		||||
        <version>4.6.0</version>
 | 
			
		||||
        <version>4.7.0</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>ruoyi-xxl-job-admin</artifactId>
 | 
			
		||||
    <packaging>jar</packaging>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package com.xxl.job.admin.controller;
 | 
			
		||||
 | 
			
		||||
import com.xxl.job.admin.controller.annotation.PermissionLimit;
 | 
			
		||||
import com.xxl.job.admin.core.model.XxlJobGroup;
 | 
			
		||||
import com.xxl.job.admin.core.model.XxlJobRegistry;
 | 
			
		||||
import com.xxl.job.admin.core.util.I18nUtil;
 | 
			
		||||
@@ -35,12 +36,14 @@ public class JobGroupController {
 | 
			
		||||
    private XxlJobRegistryDao xxlJobRegistryDao;
 | 
			
		||||
 | 
			
		||||
    @RequestMapping
 | 
			
		||||
    @PermissionLimit(adminuser = true)
 | 
			
		||||
    public String index(Model model) {
 | 
			
		||||
        return "jobgroup/jobgroup.index";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("/pageList")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PermissionLimit(adminuser = true)
 | 
			
		||||
    public Map<String, Object> pageList(HttpServletRequest request,
 | 
			
		||||
                                        @RequestParam(required = false, defaultValue = "0") int start,
 | 
			
		||||
                                        @RequestParam(required = false, defaultValue = "10") int length,
 | 
			
		||||
@@ -60,6 +63,7 @@ public class JobGroupController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("/save")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PermissionLimit(adminuser = true)
 | 
			
		||||
    public ReturnT<String> save(XxlJobGroup xxlJobGroup) {
 | 
			
		||||
 | 
			
		||||
        // valid
 | 
			
		||||
@@ -103,6 +107,7 @@ public class JobGroupController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("/update")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PermissionLimit(adminuser = true)
 | 
			
		||||
    public ReturnT<String> update(XxlJobGroup xxlJobGroup) {
 | 
			
		||||
        // valid
 | 
			
		||||
        if (xxlJobGroup.getAppname() == null || xxlJobGroup.getAppname().trim().length() == 0) {
 | 
			
		||||
@@ -171,6 +176,7 @@ public class JobGroupController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("/remove")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PermissionLimit(adminuser = true)
 | 
			
		||||
    public ReturnT<String> remove(int id) {
 | 
			
		||||
 | 
			
		||||
        // valid
 | 
			
		||||
@@ -190,6 +196,7 @@ public class JobGroupController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("/loadById")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PermissionLimit(adminuser = true)
 | 
			
		||||
    public ReturnT<XxlJobGroup> loadById(int id) {
 | 
			
		||||
        XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
 | 
			
		||||
        return jobGroup != null ? new ReturnT<XxlJobGroup>(jobGroup) : new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
 | 
			
		||||
 
 | 
			
		||||
@@ -130,22 +130,26 @@ public class JobLogController {
 | 
			
		||||
 | 
			
		||||
        model.addAttribute("triggerCode", jobLog.getTriggerCode());
 | 
			
		||||
        model.addAttribute("handleCode", jobLog.getHandleCode());
 | 
			
		||||
        model.addAttribute("executorAddress", jobLog.getExecutorAddress());
 | 
			
		||||
        model.addAttribute("triggerTime", jobLog.getTriggerTime().getTime());
 | 
			
		||||
        model.addAttribute("logId", jobLog.getId());
 | 
			
		||||
        return "joblog/joblog.detail";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("/logDetailCat")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum) {
 | 
			
		||||
    public ReturnT<LogResult> logDetailCat(long logId, int fromLineNum) {
 | 
			
		||||
        try {
 | 
			
		||||
            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
 | 
			
		||||
            ReturnT<LogResult> logResult = executorBiz.log(new LogParam(triggerTime, logId, fromLineNum));
 | 
			
		||||
            // valid
 | 
			
		||||
            XxlJobLog jobLog = xxlJobLogDao.load(logId);	// todo, need to improve performance
 | 
			
		||||
            if (jobLog == null) {
 | 
			
		||||
                return new ReturnT<LogResult>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_logid_unvalid"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // log cat
 | 
			
		||||
            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(jobLog.getExecutorAddress());
 | 
			
		||||
            ReturnT<LogResult> logResult = executorBiz.log(new LogParam(jobLog.getTriggerTime().getTime(), logId, fromLineNum));
 | 
			
		||||
 | 
			
		||||
            // is end
 | 
			
		||||
            if (logResult.getContent() != null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
 | 
			
		||||
                XxlJobLog jobLog = xxlJobLogDao.load(logId);
 | 
			
		||||
                if (jobLog.getHandleCode() > 0) {
 | 
			
		||||
                    logResult.getContent().setEnd(true);
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,9 @@ spring:
 | 
			
		||||
    resources:
 | 
			
		||||
      static-locations: classpath:/static/
 | 
			
		||||
 | 
			
		||||
logging:
 | 
			
		||||
  config: classpath:logback-plus.xml
 | 
			
		||||
 | 
			
		||||
--- # mybatis 配置
 | 
			
		||||
mybatis:
 | 
			
		||||
  mapper-locations: classpath:/mybatis-mapper/*Mapper.xml
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
admin_name=Scheduling Center
 | 
			
		||||
admin_name_full=Distributed Task Scheduling Platform XXL-JOB
 | 
			
		||||
admin_version=2.3.1
 | 
			
		||||
admin_version=2.4.0
 | 
			
		||||
admin_i18n=en
 | 
			
		||||
 | 
			
		||||
## system
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
admin_name=任务调度中心
 | 
			
		||||
admin_name_full=分布式任务调度平台XXL-JOB
 | 
			
		||||
admin_version=2.3.1
 | 
			
		||||
admin_version=2.4.0
 | 
			
		||||
admin_i18n=
 | 
			
		||||
 | 
			
		||||
## system
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
admin_name=任務調度中心
 | 
			
		||||
admin_name_full=分布式任務調度平臺XXL-JOB
 | 
			
		||||
admin_version=2.3.1
 | 
			
		||||
admin_version=2.4.0
 | 
			
		||||
admin_i18n=
 | 
			
		||||
 | 
			
		||||
## system
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,6 @@ $(function() {
 | 
			
		||||
            async: false,   // sync, make log ordered
 | 
			
		||||
            url : base_url + '/joblog/logDetailCat',
 | 
			
		||||
            data : {
 | 
			
		||||
                "executorAddress":executorAddress,
 | 
			
		||||
                "triggerTime":triggerTime,
 | 
			
		||||
                "logId":logId,
 | 
			
		||||
                "fromLineNum":fromLineNum
 | 
			
		||||
            },
 | 
			
		||||
 
 | 
			
		||||
@@ -62,11 +62,9 @@
 | 
			
		||||
    // 参数
 | 
			
		||||
    var triggerCode = '${triggerCode}';
 | 
			
		||||
    var handleCode = '${handleCode}';
 | 
			
		||||
    var executorAddress = '${executorAddress!}';
 | 
			
		||||
    var triggerTime = '${triggerTime?c}';
 | 
			
		||||
    var logId = '${logId}';
 | 
			
		||||
</script>
 | 
			
		||||
<script src="${request.contextPath}/static/js/joblog.detail.1.js"></script>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,16 @@ VUE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
 | 
			
		||||
# 生产环境配置
 | 
			
		||||
ENV = 'production'
 | 
			
		||||
 | 
			
		||||
# 若依管理系统/生产环境
 | 
			
		||||
VUE_APP_BASE_API = '/prod-api'
 | 
			
		||||
 | 
			
		||||
# 应用访问路径 例如使用前缀 /admin/
 | 
			
		||||
VUE_APP_CONTEXT_PATH = '/'
 | 
			
		||||
 | 
			
		||||
# 监控地址
 | 
			
		||||
VUE_APP_MONITRO_ADMIN = '/admin/login'
 | 
			
		||||
 | 
			
		||||
# 监控地址
 | 
			
		||||
# xxl-job 控制台地址
 | 
			
		||||
VUE_APP_XXL_JOB_ADMIN = '/xxl-job-admin'
 | 
			
		||||
 | 
			
		||||
# 若依管理系统/生产环境
 | 
			
		||||
VUE_APP_BASE_API = '/prod-api'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "ruoyi-vue-plus",
 | 
			
		||||
  "version": "4.6.0",
 | 
			
		||||
  "version": "4.7.0",
 | 
			
		||||
  "description": "RuoYi-Vue-Plus后台管理系统",
 | 
			
		||||
  "author": "LionLi",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
@@ -32,7 +32,7 @@
 | 
			
		||||
  ],
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "https://gitee.com/y_project/RuoYi-Vue.git"
 | 
			
		||||
    "url": "https://gitee.com/dromara/RuoYi-Vue-Plus.git"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@riophae/vue-treeselect": "0.4.0",
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 7.9 KiB  | 
| 
		 Before Width: | Height: | Size: 509 KiB After Width: | Height: | Size: 564 KiB  | 
| 
		 Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 269 KiB  | 
| 
		 Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 7.9 KiB  | 
@@ -180,12 +180,3 @@ aside {
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//refine vue-multiselect plugin
 | 
			
		||||
.multiselect {
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.multiselect--active {
 | 
			
		||||
  z-index: 1000 !important;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#app {
 | 
			
		||||
 | 
			
		||||
  .main-container {
 | 
			
		||||
    min-height: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    transition: margin-left .28s;
 | 
			
		||||
    margin-left: $base-sidebar-width;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,6 @@
 | 
			
		||||
  transition: all .5s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fade-transform-leave-active {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fade-transform-enter {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transform: translateX(-30px);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
          :key="item.value"
 | 
			
		||||
          :index="index"
 | 
			
		||||
          :class="item.raw.cssClass"
 | 
			
		||||
          >{{ item.label }}</span
 | 
			
		||||
          >{{ item.label + ' ' }}</span
 | 
			
		||||
        >
 | 
			
		||||
        <el-tag
 | 
			
		||||
          v-else
 | 
			
		||||
@@ -17,10 +17,13 @@
 | 
			
		||||
          :type="item.raw.listClass == 'primary' ? '' : item.raw.listClass"
 | 
			
		||||
          :class="item.raw.cssClass"
 | 
			
		||||
        >
 | 
			
		||||
          {{ item.label }}
 | 
			
		||||
          {{ item.label + ' ' }}
 | 
			
		||||
        </el-tag>
 | 
			
		||||
      </template>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template v-if="unmatch && showValue">
 | 
			
		||||
      {{ unmatchArray | handleArray }}
 | 
			
		||||
    </template>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -33,6 +36,16 @@ export default {
 | 
			
		||||
      default: null,
 | 
			
		||||
    },
 | 
			
		||||
    value: [Number, String, Array],
 | 
			
		||||
    // 当未找到匹配的数据时,显示value
 | 
			
		||||
    showValue: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: true,
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      unmatchArray: [], // 记录未匹配的项
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    values() {
 | 
			
		||||
@@ -42,11 +55,38 @@ export default {
 | 
			
		||||
        return [];
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    unmatch(){
 | 
			
		||||
      this.unmatchArray = [];
 | 
			
		||||
      if (this.value !== null && typeof this.value !== 'undefined') {
 | 
			
		||||
        // 传入值为非数组
 | 
			
		||||
        if(!Array.isArray(this.value)){
 | 
			
		||||
          if(this.options.some(v=> v.value == this.value )) return false;
 | 
			
		||||
          this.unmatchArray.push(this.value);
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
        // 传入值为Array
 | 
			
		||||
        this.value.forEach(item => {
 | 
			
		||||
          if (!this.options.some(v=> v.value == item )) this.unmatchArray.push(item)
 | 
			
		||||
        });
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      // 没有value不显示
 | 
			
		||||
      return false;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
  },
 | 
			
		||||
  filters: {
 | 
			
		||||
    handleArray(array) {
 | 
			
		||||
      if(array.length===0) return '';
 | 
			
		||||
      return array.reduce((pre, cur) => {
 | 
			
		||||
        return pre + ' ' + cur;
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped>
 | 
			
		||||
.el-tag + .el-tag {
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,17 @@
 | 
			
		||||
<!-- @author zhengjie -->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="icon-body">
 | 
			
		||||
    <el-input v-model="name" style="position: relative;" clearable placeholder="请输入图标名称" @clear="filterIcons" @input.native="filterIcons">
 | 
			
		||||
    <el-input v-model="name" class="icon-search" clearable placeholder="请输入图标名称" @clear="filterIcons" @input="filterIcons">
 | 
			
		||||
      <i slot="suffix" class="el-icon-search el-input__icon" />
 | 
			
		||||
    </el-input>
 | 
			
		||||
    <div class="icon-list">
 | 
			
		||||
      <div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
 | 
			
		||||
        <svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
 | 
			
		||||
        <span>{{ item }}</span>
 | 
			
		||||
      <div class="list-container">
 | 
			
		||||
        <div v-for="(item, index) in iconList" class="icon-item-wrapper" :key="index" @click="selectedIcon(item)">
 | 
			
		||||
          <div :class="['icon-item', { active: activeIcon === item }]">
 | 
			
		||||
            <svg-icon :icon-class="item" class-name="icon" style="height: 25px;width: 16px;"/>
 | 
			
		||||
            <span>{{ item }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -17,6 +21,11 @@
 | 
			
		||||
import icons from './requireIcons'
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'IconSelect',
 | 
			
		||||
  props: {
 | 
			
		||||
    activeIcon: {
 | 
			
		||||
      type: String
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      name: '',
 | 
			
		||||
@@ -46,22 +55,49 @@ export default {
 | 
			
		||||
  .icon-body {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    .icon-search {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      margin-bottom: 5px;
 | 
			
		||||
    }
 | 
			
		||||
    .icon-list {
 | 
			
		||||
      height: 200px;
 | 
			
		||||
      overflow-y: scroll;
 | 
			
		||||
      div {
 | 
			
		||||
        height: 30px;
 | 
			
		||||
        line-height: 30px;
 | 
			
		||||
        margin-bottom: -5px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        width: 33%;
 | 
			
		||||
        float: left;
 | 
			
		||||
      }
 | 
			
		||||
      span {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        vertical-align: -0.15em;
 | 
			
		||||
        fill: currentColor;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
      overflow: auto;
 | 
			
		||||
      .list-container {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
        .icon-item-wrapper {
 | 
			
		||||
          width: calc(100% / 3);
 | 
			
		||||
          height: 25px;
 | 
			
		||||
          line-height: 25px;
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          display: flex;
 | 
			
		||||
          .icon-item {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            padding: 0 5px;
 | 
			
		||||
            &:hover {
 | 
			
		||||
              background: #ececec;
 | 
			
		||||
              border-radius: 5px;
 | 
			
		||||
            }
 | 
			
		||||
            .icon {
 | 
			
		||||
              flex-shrink: 0;
 | 
			
		||||
            }
 | 
			
		||||
            span {
 | 
			
		||||
              display: inline-block;
 | 
			
		||||
              vertical-align: -0.15em;
 | 
			
		||||
              fill: currentColor;
 | 
			
		||||
              padding-left: 2px;
 | 
			
		||||
              overflow: hidden;
 | 
			
		||||
              text-overflow: ellipsis;
 | 
			
		||||
              white-space: nowrap;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          .icon-item.active {
 | 
			
		||||
            background: #ececec;
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ export default {
 | 
			
		||||
  name: 'RuoYiDoc',
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      url: 'https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages'
 | 
			
		||||
      url: 'https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ export default {
 | 
			
		||||
  name: 'RuoYiGit',
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      url: 'https://gitee.com/JavaLionLi/RuoYi-Vue-Plus'
 | 
			
		||||
      url: 'https://gitee.com/dromara/RuoYi-Vue-Plus'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
 
 | 
			
		||||
@@ -127,7 +127,13 @@ export default {
 | 
			
		||||
        window.open(key, "_blank");
 | 
			
		||||
      } else if (!route || !route.children) {
 | 
			
		||||
        // 没有子路由路径内部打开
 | 
			
		||||
        this.$router.push({ path: key });
 | 
			
		||||
        const routeMenu = this.childrenMenus.find(item => item.path === key);
 | 
			
		||||
        if (routeMenu && routeMenu.query) {
 | 
			
		||||
          let query = JSON.parse(routeMenu.query);
 | 
			
		||||
          this.$router.push({ path: key, query: query });
 | 
			
		||||
        } else {
 | 
			
		||||
          this.$router.push({ path: key });
 | 
			
		||||
        }
 | 
			
		||||
        this.$store.dispatch('app/toggleSideBarHide', true);
 | 
			
		||||
      } else {
 | 
			
		||||
        // 显示左侧联动菜单
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,21 @@ export default {
 | 
			
		||||
// fix css style bug in open el-dialog
 | 
			
		||||
.el-popup-parent--hidden {
 | 
			
		||||
  .fixed-header {
 | 
			
		||||
    padding-right: 17px;
 | 
			
		||||
    padding-right: 6px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar {
 | 
			
		||||
  width: 6px;
 | 
			
		||||
  height: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-track {
 | 
			
		||||
  background-color: #f1f1f1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
  background-color: #c0c0c0;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,7 @@ export default {
 | 
			
		||||
      bottom: 0px;
 | 
			
		||||
    }
 | 
			
		||||
    .el-scrollbar__wrap {
 | 
			
		||||
      height: 49px;
 | 
			
		||||
      height: 39px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -182,7 +182,7 @@ export default {
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    closeOthersTags() {
 | 
			
		||||
      this.$router.push(this.selectedTag).catch(()=>{});
 | 
			
		||||
      this.$router.push(this.selectedTag.fullPath).catch(()=>{});
 | 
			
		||||
      this.$tab.closeOtherPage(this.selectedTag).then(() => {
 | 
			
		||||
        this.moveToCurrentTag()
 | 
			
		||||
      })
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
 | 
			
		||||
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
 | 
			
		||||
    <sidebar v-if="!sidebar.hide" class="sidebar-container" />
 | 
			
		||||
    <sidebar v-if="!sidebar.hide" class="sidebar-container"/>
 | 
			
		||||
    <div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
 | 
			
		||||
      <div :class="{'fixed-header':fixedHeader}">
 | 
			
		||||
        <navbar />
 | 
			
		||||
        <tags-view v-if="needTagsView" />
 | 
			
		||||
        <navbar/>
 | 
			
		||||
        <tags-view v-if="needTagsView"/>
 | 
			
		||||
      </div>
 | 
			
		||||
      <app-main />
 | 
			
		||||
      <app-main/>
 | 
			
		||||
      <right-panel>
 | 
			
		||||
        <settings />
 | 
			
		||||
        <settings/>
 | 
			
		||||
      </right-panel>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -32,8 +32,12 @@ export default {
 | 
			
		||||
  // 关闭指定tab页签
 | 
			
		||||
  closePage(obj) {
 | 
			
		||||
    if (obj === undefined) {
 | 
			
		||||
      return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {
 | 
			
		||||
        return router.push(lastPath || '/');
 | 
			
		||||
      return store.dispatch('tagsView/delView', router.currentRoute).then(({ visitedViews }) => {
 | 
			
		||||
        const latestView = visitedViews.slice(-1)[0]
 | 
			
		||||
        if (latestView) {
 | 
			
		||||
          return router.push(latestView.fullPath)
 | 
			
		||||
        }
 | 
			
		||||
        return router.push('/');
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    return store.dispatch('tagsView/delView', obj);
 | 
			
		||||
 
 | 
			
		||||
@@ -166,9 +166,15 @@ export const dynamicRoutes = [
 | 
			
		||||
 | 
			
		||||
// 防止连续点击多次路由报错
 | 
			
		||||
let routerPush = Router.prototype.push;
 | 
			
		||||
let routerReplace = Router.prototype.replace;
 | 
			
		||||
// push
 | 
			
		||||
Router.prototype.push = function push(location) {
 | 
			
		||||
  return routerPush.call(this, location).catch(err => err)
 | 
			
		||||
}
 | 
			
		||||
// replace
 | 
			
		||||
Router.prototype.replace = function push(location) {
 | 
			
		||||
  return routerReplace.call(this, location).catch(err => err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Router({
 | 
			
		||||
  base: process.env.VUE_APP_CONTEXT_PATH,
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@
 | 
			
		||||
            size="mini"
 | 
			
		||||
            icon="el-icon-cloudy"
 | 
			
		||||
            plain
 | 
			
		||||
            @click="goTarget('https://gitee.com/JavaLionLi/RuoYi-Vue-Plus')"
 | 
			
		||||
            @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')"
 | 
			
		||||
            >访问码云</el-button
 | 
			
		||||
          >
 | 
			
		||||
          <el-button
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
            size="mini"
 | 
			
		||||
            icon="el-icon-cloudy"
 | 
			
		||||
            plain
 | 
			
		||||
            @click="goTarget('https://github.com/JavaLionLi/RuoYi-Vue-Plus')"
 | 
			
		||||
            @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')"
 | 
			
		||||
          >访问GitHub</el-button
 | 
			
		||||
          >
 | 
			
		||||
          <el-button
 | 
			
		||||
@@ -64,7 +64,7 @@
 | 
			
		||||
            size="mini"
 | 
			
		||||
            icon="el-icon-cloudy"
 | 
			
		||||
            plain
 | 
			
		||||
            @click="goTarget('https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4106467&doc_id=1469725')"
 | 
			
		||||
            @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4106467&doc_id=1469725')"
 | 
			
		||||
          >更新日志</el-button
 | 
			
		||||
          >
 | 
			
		||||
        </p>
 | 
			
		||||
@@ -114,7 +114,7 @@ export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      // 版本号
 | 
			
		||||
      version: "4.6.0",
 | 
			
		||||
      version: "4.7.0",
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
    </el-form>
 | 
			
		||||
    <!--  底部  -->
 | 
			
		||||
    <div class="el-login-footer">
 | 
			
		||||
      <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
 | 
			
		||||
      <span>Copyright © 2018-2023 疯狂的狮子Li All Rights Reserved.</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								ruoyi-ui/src/views/monitor/cache/list.vue
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -187,7 +187,7 @@ export default {
 | 
			
		||||
    /** 清理指定名称缓存 */
 | 
			
		||||
    handleClearCacheName(row) {
 | 
			
		||||
      clearCacheName(row.cacheName).then(response => {
 | 
			
		||||
        this.$modal.msgSuccess("清理缓存名称[" + this.nowCacheName + "]成功");
 | 
			
		||||
        this.$modal.msgSuccess("清理缓存名称[" + row.cacheName + "]成功");
 | 
			
		||||
        this.getCacheKeys();
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@
 | 
			
		||||
    </el-form>
 | 
			
		||||
    <!--  底部  -->
 | 
			
		||||
    <div class="el-register-footer">
 | 
			
		||||
      <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
 | 
			
		||||
      <span>Copyright © 2018-2023 疯狂的狮子Li All Rights Reserved.</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,14 +134,13 @@
 | 
			
		||||
                trigger="click"
 | 
			
		||||
                @show="$refs['iconSelect'].reset()"
 | 
			
		||||
              >
 | 
			
		||||
                <IconSelect ref="iconSelect" @selected="selected" />
 | 
			
		||||
                <IconSelect ref="iconSelect" @selected="selected" :active-icon="form.icon" />
 | 
			
		||||
                <el-input slot="reference" v-model="form.icon" placeholder="点击选择图标" readonly>
 | 
			
		||||
                  <svg-icon
 | 
			
		||||
                    v-if="form.icon"
 | 
			
		||||
                    slot="prefix"
 | 
			
		||||
                    :icon-class="form.icon"
 | 
			
		||||
                    class="el-input__icon"
 | 
			
		||||
                    style="height: 32px;width: 16px;"
 | 
			
		||||
                    style="width: 25px;"
 | 
			
		||||
                  />
 | 
			
		||||
                  <i v-else slot="prefix" class="el-icon-search el-input__icon" />
 | 
			
		||||
                </el-input>
 | 
			
		||||
 
 | 
			
		||||
@@ -406,9 +406,7 @@ export default {
 | 
			
		||||
      }).then(() => {
 | 
			
		||||
        this.getList()
 | 
			
		||||
        this.$modal.msgSuccess(text + "成功");
 | 
			
		||||
      }).catch(() => {
 | 
			
		||||
        this.previewListResource = previewListResource !== true;
 | 
			
		||||
      })
 | 
			
		||||
      }).catch(() => {})
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -371,19 +371,6 @@ export default {
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang='scss'>
 | 
			
		||||
body, html{
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  text-rendering: optimizeLegibility;
 | 
			
		||||
  font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input, textarea{
 | 
			
		||||
  font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.editor-tabs{
 | 
			
		||||
  background: #121315;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>ruoyi-vue-plus</artifactId>
 | 
			
		||||
        <groupId>com.ruoyi</groupId>
 | 
			
		||||
        <version>4.6.0</version>
 | 
			
		||||
        <version>4.7.0</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <packaging>jar</packaging>
 | 
			
		||||
@@ -244,6 +244,12 @@
 | 
			
		||||
            </exclusions>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <!-- 离线IP地址定位库 ip2region -->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.lionsoul</groupId>
 | 
			
		||||
            <artifactId>ip2region</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,6 @@ package com.ruoyi.common.constant;
 | 
			
		||||
 */
 | 
			
		||||
public interface CacheConstants {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录用户 redis key
 | 
			
		||||
     */
 | 
			
		||||
    String LOGIN_TOKEN_KEY = "Authorization:login:token:";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 在线用户 redis key
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -93,9 +93,13 @@ public class SysUser extends BaseEntity {
 | 
			
		||||
        updateStrategy = FieldStrategy.NOT_EMPTY,
 | 
			
		||||
        whereStrategy = FieldStrategy.NOT_EMPTY
 | 
			
		||||
    )
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private String password;
 | 
			
		||||
    public String getPassword() {
 | 
			
		||||
        return password;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 帐号状态(0正常 1停用)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package com.ruoyi.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.Email;
 | 
			
		||||
import javax.validation.constraints.NotBlank;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 短信登录对象
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class EmailLoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱
 | 
			
		||||
     */
 | 
			
		||||
    @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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -14,13 +14,13 @@ import javax.validation.constraints.NotBlank;
 | 
			
		||||
public class SmsLoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户名
 | 
			
		||||
     * 手机号
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.phonenumber.not.blank}")
 | 
			
		||||
    private String phonenumber;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户密码
 | 
			
		||||
     * 短信code
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{sms.code.not.blank}")
 | 
			
		||||
    private String smsCode;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,11 @@ public enum LoginType {
 | 
			
		||||
     */
 | 
			
		||||
    SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱登录
 | 
			
		||||
     */
 | 
			
		||||
    EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序登录
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
 | 
			
		||||
    private ExcelResult<T> excelResult;
 | 
			
		||||
 | 
			
		||||
    public DefaultExcelListener(boolean isValidate) {
 | 
			
		||||
        this.excelResult = new DefautExcelResult<>();
 | 
			
		||||
        this.excelResult = new DefaultExcelResult<>();
 | 
			
		||||
        this.isValidate = isValidate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ import java.util.List;
 | 
			
		||||
 * @author Yjoioooo
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
public class DefautExcelResult<T> implements ExcelResult<T> {
 | 
			
		||||
public class DefaultExcelResult<T> implements ExcelResult<T> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 数据对象list
 | 
			
		||||
@@ -26,17 +26,17 @@ public class DefautExcelResult<T> implements ExcelResult<T> {
 | 
			
		||||
    @Setter
 | 
			
		||||
    private List<String> errorList;
 | 
			
		||||
 | 
			
		||||
    public DefautExcelResult() {
 | 
			
		||||
    public DefaultExcelResult() {
 | 
			
		||||
        this.list = new ArrayList<>();
 | 
			
		||||
        this.errorList = new ArrayList<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DefautExcelResult(List<T> list, List<String> errorList) {
 | 
			
		||||
    public DefaultExcelResult(List<T> list, List<String> errorList) {
 | 
			
		||||
        this.list = list;
 | 
			
		||||
        this.errorList = errorList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DefautExcelResult(ExcelResult<T> excelResult) {
 | 
			
		||||
    public DefaultExcelResult(ExcelResult<T> excelResult) {
 | 
			
		||||
        this.list = excelResult.getList();
 | 
			
		||||
        this.errorList = excelResult.getErrorList();
 | 
			
		||||
    }
 | 
			
		||||
@@ -66,7 +66,7 @@ public class DataBaseHelper {
 | 
			
		||||
            // instr(',0,100,101,' , ',100,') <> 0
 | 
			
		||||
            return "instr(','||" + var2 + "||',' , '," + var + ",') <> 0";
 | 
			
		||||
        }
 | 
			
		||||
        // find_in_set(100 , '0,100,101')
 | 
			
		||||
        return "find_in_set(" + var + " , " + var2 + ") <> 0";
 | 
			
		||||
        // find_in_set('100' , '0,100,101')
 | 
			
		||||
        return "find_in_set('" + var + "' , " + var2 + ") <> 0";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据权限助手
 | 
			
		||||
@@ -61,4 +62,32 @@ public class DataPermissionHelper {
 | 
			
		||||
        InterceptorIgnoreHelper.clearIgnoreStrategy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 在忽略数据权限中执行
 | 
			
		||||
     *
 | 
			
		||||
     * @param handle 处理执行方法
 | 
			
		||||
     */
 | 
			
		||||
    public static void ignore(Runnable handle) {
 | 
			
		||||
        enableIgnore();
 | 
			
		||||
        try {
 | 
			
		||||
            handle.run();
 | 
			
		||||
        } finally {
 | 
			
		||||
            disableIgnore();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 在忽略数据权限中执行
 | 
			
		||||
     *
 | 
			
		||||
     * @param handle 处理执行方法
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> T ignore(Supplier<T> handle) {
 | 
			
		||||
        enableIgnore();
 | 
			
		||||
        try {
 | 
			
		||||
            return handle.get();
 | 
			
		||||
        } finally {
 | 
			
		||||
            disableIgnore();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,8 @@ import org.springframework.stereotype.Component;
 | 
			
		||||
public class DeptNameTranslationImpl implements TranslationInterface<String> {
 | 
			
		||||
 | 
			
		||||
    private final DeptService deptService;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public String translation(Object key, String other) {
 | 
			
		||||
        return deptService.selectDeptNameByIds(key.toString());
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ public class DictTypeTranslationImpl implements TranslationInterface<String> {
 | 
			
		||||
 | 
			
		||||
    private final DictService dictService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String translation(Object key, String other) {
 | 
			
		||||
        if (key instanceof String && StringUtils.isNotBlank(other)) {
 | 
			
		||||
            return dictService.getDictLabel(other, key.toString());
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ public class OssUrlTranslationImpl implements TranslationInterface<String> {
 | 
			
		||||
 | 
			
		||||
    private final OssService ossService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String translation(Object key, String other) {
 | 
			
		||||
        return ossService.selectUrlByIds(key.toString());
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ public class UserNameTranslationImpl implements TranslationInterface<String> {
 | 
			
		||||
 | 
			
		||||
    private final UserService userService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String translation(Object key, String other) {
 | 
			
		||||
        if (key instanceof Long) {
 | 
			
		||||
            return userService.selectUserNameById((Long) key);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										243
									
								
								ruoyi/src/main/java/com/ruoyi/common/utils/EncryptUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,243 @@
 | 
			
		||||
package com.ruoyi.common.utils;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.codec.Base64;
 | 
			
		||||
import cn.hutool.core.util.ArrayUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.hutool.crypto.SecureUtil;
 | 
			
		||||
import cn.hutool.crypto.SmUtil;
 | 
			
		||||
import cn.hutool.crypto.asymmetric.KeyType;
 | 
			
		||||
import cn.hutool.crypto.asymmetric.RSA;
 | 
			
		||||
import cn.hutool.crypto.asymmetric.SM2;
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 安全相关工具类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 老马
 | 
			
		||||
 */
 | 
			
		||||
public class EncryptUtils {
 | 
			
		||||
    /**
 | 
			
		||||
     * 公钥
 | 
			
		||||
     */
 | 
			
		||||
    public static final String PUBLIC_KEY = "publicKey";
 | 
			
		||||
    /**
 | 
			
		||||
     * 私钥
 | 
			
		||||
     */
 | 
			
		||||
    public static final String PRIVATE_KEY = "privateKey";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Base64加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data 待加密数据
 | 
			
		||||
     * @return 加密后字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptByBase64(String data) {
 | 
			
		||||
        return Base64.encode(data, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Base64解密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data 待解密数据
 | 
			
		||||
     * @return 解密后字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String decryptByBase64(String data) {
 | 
			
		||||
        return Base64.decodeStr(data, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AES加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data     待解密数据
 | 
			
		||||
     * @param password 秘钥字符串
 | 
			
		||||
     * @return 加密后字符串, 采用Base64编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptByAes(String data, String password) {
 | 
			
		||||
        if (StrUtil.isBlank(password)) {
 | 
			
		||||
            throw new IllegalArgumentException("AES需要传入秘钥信息");
 | 
			
		||||
        }
 | 
			
		||||
        // aes算法的秘钥要求是16位、24位、32位
 | 
			
		||||
        int[] array = {16, 24, 32};
 | 
			
		||||
        if (!ArrayUtil.contains(array, password.length())) {
 | 
			
		||||
            throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
 | 
			
		||||
        }
 | 
			
		||||
        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AES解密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data     待解密数据
 | 
			
		||||
     * @param password 秘钥字符串
 | 
			
		||||
     * @return 解密后字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String decryptByAes(String data, String password) {
 | 
			
		||||
        if (StrUtil.isBlank(password)) {
 | 
			
		||||
            throw new IllegalArgumentException("AES需要传入秘钥信息");
 | 
			
		||||
        }
 | 
			
		||||
        // aes算法的秘钥要求是16位、24位、32位
 | 
			
		||||
        int[] array = {16, 24, 32};
 | 
			
		||||
        if (!ArrayUtil.contains(array, password.length())) {
 | 
			
		||||
            throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
 | 
			
		||||
        }
 | 
			
		||||
        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sm4加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data     待加密数据
 | 
			
		||||
     * @param password 秘钥字符串
 | 
			
		||||
     * @return 加密后字符串, 采用Base64编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptBySm4(String data, String password) {
 | 
			
		||||
        if (StrUtil.isBlank(password)) {
 | 
			
		||||
            throw new IllegalArgumentException("SM4需要传入秘钥信息");
 | 
			
		||||
        }
 | 
			
		||||
        // sm4算法的秘钥要求是16位长度
 | 
			
		||||
        int sm4PasswordLength = 16;
 | 
			
		||||
        if (sm4PasswordLength != password.length()) {
 | 
			
		||||
            throw new IllegalArgumentException("SM4秘钥长度要求为16位");
 | 
			
		||||
        }
 | 
			
		||||
        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sm4解密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data     待解密数据
 | 
			
		||||
     * @param password 秘钥字符串
 | 
			
		||||
     * @return 解密后字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String decryptBySm4(String data, String password) {
 | 
			
		||||
        if (StrUtil.isBlank(password)) {
 | 
			
		||||
            throw new IllegalArgumentException("SM4需要传入秘钥信息");
 | 
			
		||||
        }
 | 
			
		||||
        // sm4算法的秘钥要求是16位长度
 | 
			
		||||
        int sm4PasswordLength = 16;
 | 
			
		||||
        if (sm4PasswordLength != password.length()) {
 | 
			
		||||
            throw new IllegalArgumentException("SM4秘钥长度要求为16位");
 | 
			
		||||
        }
 | 
			
		||||
        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 产生sm2加解密需要的公钥和私钥
 | 
			
		||||
     *
 | 
			
		||||
     * @return 公私钥Map
 | 
			
		||||
     */
 | 
			
		||||
    public static Map<String, String> generateSm2Key() {
 | 
			
		||||
        Map<String, String> keyMap = new HashMap<>(2);
 | 
			
		||||
        SM2 sm2 = SmUtil.sm2();
 | 
			
		||||
        keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64());
 | 
			
		||||
        keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64());
 | 
			
		||||
        return keyMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sm2公钥加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data      待加密数据
 | 
			
		||||
     * @param publicKey 公钥
 | 
			
		||||
     * @return 加密后字符串, 采用Base64编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptBySm2(String data, String publicKey) {
 | 
			
		||||
        if (StrUtil.isBlank(publicKey)) {
 | 
			
		||||
            throw new IllegalArgumentException("SM2需要传入公钥进行加密");
 | 
			
		||||
        }
 | 
			
		||||
        SM2 sm2 = SmUtil.sm2(null, publicKey);
 | 
			
		||||
        return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sm2私钥解密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data       待加密数据
 | 
			
		||||
     * @param privateKey 私钥
 | 
			
		||||
     * @return 解密后字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String decryptBySm2(String data, String privateKey) {
 | 
			
		||||
        if (StrUtil.isBlank(privateKey)) {
 | 
			
		||||
            throw new IllegalArgumentException("SM2需要传入私钥进行解密");
 | 
			
		||||
        }
 | 
			
		||||
        SM2 sm2 = SmUtil.sm2(privateKey, null);
 | 
			
		||||
        return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 产生RSA加解密需要的公钥和私钥
 | 
			
		||||
     *
 | 
			
		||||
     * @return 公私钥Map
 | 
			
		||||
     */
 | 
			
		||||
    public static Map<String, String> generateRsaKey() {
 | 
			
		||||
        Map<String, String> keyMap = new HashMap<>(2);
 | 
			
		||||
        RSA rsa = SecureUtil.rsa();
 | 
			
		||||
        keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64());
 | 
			
		||||
        keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64());
 | 
			
		||||
        return keyMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * rsa公钥加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data      待加密数据
 | 
			
		||||
     * @param publicKey 公钥
 | 
			
		||||
     * @return 加密后字符串, 采用Base64编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptByRsa(String data, String publicKey) {
 | 
			
		||||
        if (StrUtil.isBlank(publicKey)) {
 | 
			
		||||
            throw new IllegalArgumentException("RSA需要传入公钥进行加密");
 | 
			
		||||
        }
 | 
			
		||||
        RSA rsa = SecureUtil.rsa(null, publicKey);
 | 
			
		||||
        return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * rsa私钥解密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data       待加密数据
 | 
			
		||||
     * @param privateKey 私钥
 | 
			
		||||
     * @return 解密后字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String decryptByRsa(String data, String privateKey) {
 | 
			
		||||
        if (StrUtil.isBlank(privateKey)) {
 | 
			
		||||
            throw new IllegalArgumentException("RSA需要传入私钥进行解密");
 | 
			
		||||
        }
 | 
			
		||||
        RSA rsa = SecureUtil.rsa(privateKey, null);
 | 
			
		||||
        return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * md5加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data 待加密数据
 | 
			
		||||
     * @return 加密后字符串, 采用Hex编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptByMd5(String data) {
 | 
			
		||||
        return SecureUtil.md5(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sha256加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data 待加密数据
 | 
			
		||||
     * @return 加密后字符串, 采用Hex编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptBySha256(String data) {
 | 
			
		||||
        return SecureUtil.sha256(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sm3加密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data 待加密数据
 | 
			
		||||
     * @return 加密后字符串, 采用Hex编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptBySm3(String data) {
 | 
			
		||||
        return SmUtil.sm3(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,7 @@
 | 
			
		||||
package com.ruoyi.common.utils.ip;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.lang.Dict;
 | 
			
		||||
import cn.hutool.core.net.NetUtil;
 | 
			
		||||
import cn.hutool.http.HtmlUtil;
 | 
			
		||||
import cn.hutool.http.HttpUtil;
 | 
			
		||||
import com.ruoyi.common.config.RuoYiConfig;
 | 
			
		||||
import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.utils.JsonUtils;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
@@ -21,40 +16,18 @@ import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class AddressUtils {
 | 
			
		||||
 | 
			
		||||
    // IP地址查询
 | 
			
		||||
    public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
 | 
			
		||||
 | 
			
		||||
    // 未知地址
 | 
			
		||||
    public static final String UNKNOWN = "XX XX";
 | 
			
		||||
 | 
			
		||||
    public static String getRealAddressByIP(String ip) {
 | 
			
		||||
        String address = UNKNOWN;
 | 
			
		||||
        if (StringUtils.isBlank(ip)) {
 | 
			
		||||
            return address;
 | 
			
		||||
            return UNKNOWN;
 | 
			
		||||
        }
 | 
			
		||||
        // 内网不查询
 | 
			
		||||
        ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
 | 
			
		||||
        if (NetUtil.isInnerIP(ip)) {
 | 
			
		||||
            return "内网IP";
 | 
			
		||||
        }
 | 
			
		||||
        if (RuoYiConfig.isAddressEnabled()) {
 | 
			
		||||
            try {
 | 
			
		||||
                String rspStr = HttpUtil.createGet(IP_URL)
 | 
			
		||||
                    .body("ip=" + ip + "&json=true", Constants.GBK)
 | 
			
		||||
                    .execute()
 | 
			
		||||
                    .body();
 | 
			
		||||
                if (StringUtils.isEmpty(rspStr)) {
 | 
			
		||||
                    log.error("获取地理位置异常 {}", ip);
 | 
			
		||||
                    return UNKNOWN;
 | 
			
		||||
                }
 | 
			
		||||
                Dict obj = JsonUtils.parseMap(rspStr);
 | 
			
		||||
                String region = obj.getStr("pro");
 | 
			
		||||
                String city = obj.getStr("city");
 | 
			
		||||
                return String.format("%s %s", region, city);
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                log.error("获取地理位置异常 {}", ip);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return UNKNOWN;
 | 
			
		||||
        return RegionUtils.getCityInfo(ip);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,66 @@
 | 
			
		||||
package com.ruoyi.common.utils.ip;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.io.FileUtil;
 | 
			
		||||
import cn.hutool.core.io.resource.ClassPathResource;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.ruoyi.common.exception.ServiceException;
 | 
			
		||||
import com.ruoyi.common.utils.file.FileUtils;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.lionsoul.ip2region.xdb.Searcher;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据ip地址定位工具类,离线方式
 | 
			
		||||
 * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
 | 
			
		||||
 *
 | 
			
		||||
 * @author lishuyan
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RegionUtils {
 | 
			
		||||
 | 
			
		||||
    private static final Searcher SEARCHER;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        String fileName = "/ip2region.xdb";
 | 
			
		||||
        File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
 | 
			
		||||
        if (!FileUtils.exist(existFile)) {
 | 
			
		||||
            ClassPathResource fileStream = new ClassPathResource(fileName);
 | 
			
		||||
            if (ObjectUtil.isEmpty(fileStream.getStream())) {
 | 
			
		||||
                throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
 | 
			
		||||
            }
 | 
			
		||||
            FileUtils.writeFromStream(fileStream.getStream(), existFile);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String dbPath = existFile.getPath();
 | 
			
		||||
 | 
			
		||||
        // 1、从 dbPath 加载整个 xdb 到内存。
 | 
			
		||||
        byte[] cBuff;
 | 
			
		||||
        try {
 | 
			
		||||
            cBuff = Searcher.loadContentFromFile(dbPath);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
 | 
			
		||||
        try {
 | 
			
		||||
            SEARCHER = Searcher.newWithBuffer(cBuff);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据IP地址离线获取城市
 | 
			
		||||
     */
 | 
			
		||||
    public static String getCityInfo(String ip) {
 | 
			
		||||
        try {
 | 
			
		||||
            ip = ip.trim();
 | 
			
		||||
            // 3、执行查询
 | 
			
		||||
            String region = SEARCHER.search(ip);
 | 
			
		||||
            return region.replace("0|", "").replace("|0", "");
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("IP地址离线获取城市异常 {}", ip);
 | 
			
		||||
            return "未知";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -49,4 +49,16 @@ public class RedisRateLimiterController {
 | 
			
		||||
        return R.ok("操作成功", value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 测试请求IP限流(key基于参数获取)
 | 
			
		||||
     * 同一IP请求受影响
 | 
			
		||||
     *
 | 
			
		||||
     * 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0}
 | 
			
		||||
     */
 | 
			
		||||
    @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
 | 
			
		||||
    @GetMapping("/testObj")
 | 
			
		||||
    public R<String> testObj(String value) {
 | 
			
		||||
        return R.ok("操作成功", value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import org.redisson.api.RateType;
 | 
			
		||||
import org.springframework.core.DefaultParameterNameDiscoverer;
 | 
			
		||||
import org.springframework.core.ParameterNameDiscoverer;
 | 
			
		||||
import org.springframework.expression.EvaluationContext;
 | 
			
		||||
import org.springframework.expression.Expression;
 | 
			
		||||
import org.springframework.expression.ExpressionParser;
 | 
			
		||||
import org.springframework.expression.ParserContext;
 | 
			
		||||
import org.springframework.expression.common.TemplateParserContext;
 | 
			
		||||
@@ -102,7 +103,14 @@ public class RateLimiterAspect {
 | 
			
		||||
            }
 | 
			
		||||
            // 解析返回给key
 | 
			
		||||
            try {
 | 
			
		||||
                key = parser.parseExpression(key, parserContext).getValue(context, String.class) + ":";
 | 
			
		||||
                Expression expression;
 | 
			
		||||
                if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
 | 
			
		||||
                    && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
 | 
			
		||||
                    expression = parser.parseExpression(key, parserContext);
 | 
			
		||||
                } else {
 | 
			
		||||
                    expression = parser.parseExpression(key);
 | 
			
		||||
                }
 | 
			
		||||
                key = expression.getValue(context, String.class) + ":";
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                throw new ServiceException("限流key解析异常!请联系管理员!");
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package com.ruoyi.framework.encrypt;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollectionUtil;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.ruoyi.common.annotation.EncryptField;
 | 
			
		||||
import com.ruoyi.common.encrypt.EncryptContext;
 | 
			
		||||
@@ -62,12 +62,12 @@ public class MybatisDecryptInterceptor implements Interceptor {
 | 
			
		||||
        }
 | 
			
		||||
        if (sourceObject instanceof List<?>) {
 | 
			
		||||
            List<?> sourceList = (List<?>) sourceObject;
 | 
			
		||||
            if(CollectionUtil.isEmpty(sourceList)) {
 | 
			
		||||
            if(CollUtil.isEmpty(sourceList)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
 | 
			
		||||
            Object firstItem = sourceList.get(0);
 | 
			
		||||
            if (CollectionUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
 | 
			
		||||
            if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            ((List<?>) sourceObject).forEach(this::decryptHandler);
 | 
			
		||||
@@ -91,6 +91,9 @@ public class MybatisDecryptInterceptor implements Interceptor {
 | 
			
		||||
     * @return 加密后结果
 | 
			
		||||
     */
 | 
			
		||||
    private String decryptField(String value, Field field) {
 | 
			
		||||
        if (ObjectUtil.isNull(value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        EncryptField encryptField = field.getAnnotation(EncryptField.class);
 | 
			
		||||
        EncryptContext encryptContext = new EncryptContext();
 | 
			
		||||
        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package com.ruoyi.framework.encrypt;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollectionUtil;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.ruoyi.common.annotation.EncryptField;
 | 
			
		||||
import com.ruoyi.common.encrypt.EncryptContext;
 | 
			
		||||
@@ -72,12 +72,12 @@ public class MybatisEncryptInterceptor implements Interceptor {
 | 
			
		||||
        }
 | 
			
		||||
        if (sourceObject instanceof List<?>) {
 | 
			
		||||
            List<?> sourceList = (List<?>) sourceObject;
 | 
			
		||||
            if(CollectionUtil.isEmpty(sourceList)) {
 | 
			
		||||
            if(CollUtil.isEmpty(sourceList)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
 | 
			
		||||
            Object firstItem = sourceList.get(0);
 | 
			
		||||
            if (CollectionUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
 | 
			
		||||
            if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            ((List<?>) sourceObject).forEach(this::encryptHandler);
 | 
			
		||||
@@ -101,6 +101,9 @@ public class MybatisEncryptInterceptor implements Interceptor {
 | 
			
		||||
     * @return 加密后结果
 | 
			
		||||
     */
 | 
			
		||||
    private String encryptField(String value, Field field) {
 | 
			
		||||
        if (ObjectUtil.isNull(value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        EncryptField encryptField = field.getAnnotation(EncryptField.class);
 | 
			
		||||
        EncryptContext encryptContext = new EncryptContext();
 | 
			
		||||
        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
 | 
			
		||||
            log.warn("自动注入警告 => 用户未登录");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return loginUser.getUsername();
 | 
			
		||||
        return ObjectUtil.isNotNull(loginUser) ? loginUser.getUsername() : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -317,13 +317,11 @@ public class GenTableServiceImpl implements IGenTableService {
 | 
			
		||||
                    column.setIsRequired(prevColumn.getIsRequired());
 | 
			
		||||
                    column.setHtmlType(prevColumn.getHtmlType());
 | 
			
		||||
                }
 | 
			
		||||
                genTableColumnMapper.updateById(column);
 | 
			
		||||
            } else {
 | 
			
		||||
                genTableColumnMapper.insert(column);
 | 
			
		||||
            }
 | 
			
		||||
            saveColumns.add(column);
 | 
			
		||||
        });
 | 
			
		||||
        if (CollUtil.isNotEmpty(saveColumns)) {
 | 
			
		||||
            genTableColumnMapper.insertBatch(saveColumns);
 | 
			
		||||
            genTableColumnMapper.insertOrUpdateBatch(saveColumns);
 | 
			
		||||
        }
 | 
			
		||||
        List<GenTableColumn> delColumns = StreamUtils.filter(tableColumns, column -> !dbTableColumnNames.contains(column.getColumnName()));
 | 
			
		||||
        if (CollUtil.isNotEmpty(delColumns)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,14 @@ public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, Sy
 | 
			
		||||
     */
 | 
			
		||||
    SysUser selectUserByPhonenumber(String phonenumber);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通过邮箱查询用户
 | 
			
		||||
     *
 | 
			
		||||
     * @param email 邮箱
 | 
			
		||||
     * @return 用户对象信息
 | 
			
		||||
     */
 | 
			
		||||
    SysUser selectUserByEmail(String email);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通过用户ID查询用户
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -176,4 +176,6 @@ public interface ISysRoleService {
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    int insertAuthUsers(Long roleId, Long[] userIds);
 | 
			
		||||
 | 
			
		||||
    void cleanOnlineUserByRole(Long roleId);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,6 @@ import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
@@ -67,11 +66,10 @@ public class SysLoginService {
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    public String login(String username, String password, String code, String uuid) {
 | 
			
		||||
        HttpServletRequest request = ServletUtils.getRequest();
 | 
			
		||||
        boolean captchaEnabled = configService.selectCaptchaEnabled();
 | 
			
		||||
        // 验证码开关
 | 
			
		||||
        if (captchaEnabled) {
 | 
			
		||||
            validateCaptcha(username, code, uuid, request);
 | 
			
		||||
            validateCaptcha(username, code, uuid);
 | 
			
		||||
        }
 | 
			
		||||
        SysUser user = loadUserByUsername(username);
 | 
			
		||||
        checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
 | 
			
		||||
@@ -100,6 +98,20 @@ public class SysLoginService {
 | 
			
		||||
        return StpUtil.getTokenValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String emailLogin(String email, String emailCode) {
 | 
			
		||||
        // 通过手机号查找用户
 | 
			
		||||
        SysUser user = loadUserByEmail(email);
 | 
			
		||||
 | 
			
		||||
        checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
 | 
			
		||||
        // 此处可根据登录用户的数据不同 自行创建 loginUser
 | 
			
		||||
        LoginUser loginUser = buildLoginUser(user);
 | 
			
		||||
        // 生成token
 | 
			
		||||
        LoginHelper.loginByDevice(loginUser, DeviceType.APP);
 | 
			
		||||
 | 
			
		||||
        recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
 | 
			
		||||
        recordLoginInfo(user.getUserId(), user.getUserName());
 | 
			
		||||
        return StpUtil.getTokenValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String xcxLogin(String xcxCode) {
 | 
			
		||||
        // xcxCode 为 小程序调用 wx.login 授权后获取
 | 
			
		||||
@@ -140,7 +152,6 @@ public class SysLoginService {
 | 
			
		||||
     * @param username 用户名
 | 
			
		||||
     * @param status   状态
 | 
			
		||||
     * @param message  消息内容
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    private void recordLogininfor(String username, String status, String message) {
 | 
			
		||||
        LogininforEvent logininforEvent = new LogininforEvent();
 | 
			
		||||
@@ -163,6 +174,18 @@ public class SysLoginService {
 | 
			
		||||
        return code.equals(smsCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验邮箱验证码
 | 
			
		||||
     */
 | 
			
		||||
    private boolean validateEmailCode(String email, String emailCode) {
 | 
			
		||||
        String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + email);
 | 
			
		||||
        if (StringUtils.isBlank(code)) {
 | 
			
		||||
            recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
 | 
			
		||||
            throw new CaptchaExpireException();
 | 
			
		||||
        }
 | 
			
		||||
        return code.equals(emailCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验验证码
 | 
			
		||||
     *
 | 
			
		||||
@@ -170,7 +193,7 @@ public class SysLoginService {
 | 
			
		||||
     * @param code     验证码
 | 
			
		||||
     * @param uuid     唯一标识
 | 
			
		||||
     */
 | 
			
		||||
    public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
 | 
			
		||||
    public void validateCaptcha(String username, String code, String uuid) {
 | 
			
		||||
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
 | 
			
		||||
        String captcha = RedisUtils.getCacheObject(verifyKey);
 | 
			
		||||
        RedisUtils.deleteObject(verifyKey);
 | 
			
		||||
@@ -212,6 +235,20 @@ public class SysLoginService {
 | 
			
		||||
        return userMapper.selectUserByPhonenumber(phonenumber);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUser loadUserByEmail(String email) {
 | 
			
		||||
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
            .select(SysUser::getPhonenumber, SysUser::getStatus)
 | 
			
		||||
            .eq(SysUser::getEmail, email));
 | 
			
		||||
        if (ObjectUtil.isNull(user)) {
 | 
			
		||||
            log.info("登录用户:{} 不存在.", email);
 | 
			
		||||
            throw new UserException("user.not.exists", email);
 | 
			
		||||
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
 | 
			
		||||
            log.info("登录用户:{} 已被停用.", email);
 | 
			
		||||
            throw new UserException("user.blocked", email);
 | 
			
		||||
        }
 | 
			
		||||
        return userMapper.selectUserByEmail(email);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUser loadUserByOpenid(String openid) {
 | 
			
		||||
        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
 | 
			
		||||
        // todo 自行实现 userService.selectUserByOpenid(openid);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ package com.ruoyi.system.service;
 | 
			
		||||
import cn.dev33.satoken.secure.BCrypt;
 | 
			
		||||
import com.ruoyi.common.constant.CacheConstants;
 | 
			
		||||
import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.constant.UserConstants;
 | 
			
		||||
import com.ruoyi.common.core.domain.event.LogininforEvent;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.RegisterBody;
 | 
			
		||||
@@ -19,8 +18,6 @@ import com.ruoyi.common.utils.spring.SpringUtils;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 注册校验方法
 | 
			
		||||
 *
 | 
			
		||||
@@ -37,7 +34,6 @@ public class SysRegisterService {
 | 
			
		||||
     * 注册
 | 
			
		||||
     */
 | 
			
		||||
    public void register(RegisterBody registerBody) {
 | 
			
		||||
        HttpServletRequest request = ServletUtils.getRequest();
 | 
			
		||||
        String username = registerBody.getUsername();
 | 
			
		||||
        String password = registerBody.getPassword();
 | 
			
		||||
        // 校验用户类型是否存在
 | 
			
		||||
@@ -46,7 +42,7 @@ public class SysRegisterService {
 | 
			
		||||
        boolean captchaEnabled = configService.selectCaptchaEnabled();
 | 
			
		||||
        // 验证码开关
 | 
			
		||||
        if (captchaEnabled) {
 | 
			
		||||
            validateCaptcha(username, registerBody.getCode(), registerBody.getUuid(), request);
 | 
			
		||||
            validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
 | 
			
		||||
        }
 | 
			
		||||
        SysUser sysUser = new SysUser();
 | 
			
		||||
        sysUser.setUserName(username);
 | 
			
		||||
@@ -70,9 +66,8 @@ public class SysRegisterService {
 | 
			
		||||
     * @param username 用户名
 | 
			
		||||
     * @param code     验证码
 | 
			
		||||
     * @param uuid     唯一标识
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
 | 
			
		||||
    public void validateCaptcha(String username, String code, String uuid) {
 | 
			
		||||
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
 | 
			
		||||
        String captcha = RedisUtils.getCacheObject(verifyKey);
 | 
			
		||||
        RedisUtils.deleteObject(verifyKey);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
package com.ruoyi.system.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.exception.NotLoginException;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
 | 
			
		||||
@@ -10,6 +12,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.ruoyi.common.constant.UserConstants;
 | 
			
		||||
import com.ruoyi.common.core.domain.PageQuery;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysRole;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
			
		||||
import com.ruoyi.common.core.page.TableDataInfo;
 | 
			
		||||
import com.ruoyi.common.exception.ServiceException;
 | 
			
		||||
import com.ruoyi.common.helper.LoginHelper;
 | 
			
		||||
@@ -70,7 +73,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
 | 
			
		||||
            .like(StringUtils.isNotBlank(role.getRoleKey()), "r.role_key", role.getRoleKey())
 | 
			
		||||
            .between(params.get("beginTime") != null && params.get("endTime") != null,
 | 
			
		||||
                "r.create_time", params.get("beginTime"), params.get("endTime"))
 | 
			
		||||
            .orderByAsc("r.role_sort");
 | 
			
		||||
            .orderByAsc("r.role_sort").orderByAsc("r.create_time");
 | 
			
		||||
        return wrapper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -362,9 +365,13 @@ public class SysRoleServiceImpl implements ISysRoleService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int deleteAuthUser(SysUserRole userRole) {
 | 
			
		||||
        return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
 | 
			
		||||
        int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
 | 
			
		||||
            .eq(SysUserRole::getRoleId, userRole.getRoleId())
 | 
			
		||||
            .eq(SysUserRole::getUserId, userRole.getUserId()));
 | 
			
		||||
        if (rows > 0) {
 | 
			
		||||
            cleanOnlineUserByRole(userRole.getRoleId());
 | 
			
		||||
        }
 | 
			
		||||
        return rows;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -376,9 +383,13 @@ public class SysRoleServiceImpl implements ISysRoleService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int deleteAuthUsers(Long roleId, Long[] userIds) {
 | 
			
		||||
        return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
 | 
			
		||||
        int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
 | 
			
		||||
            .eq(SysUserRole::getRoleId, roleId)
 | 
			
		||||
            .in(SysUserRole::getUserId, Arrays.asList(userIds)));
 | 
			
		||||
        if (rows > 0) {
 | 
			
		||||
            cleanOnlineUserByRole(roleId);
 | 
			
		||||
        }
 | 
			
		||||
        return rows;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -401,6 +412,32 @@ public class SysRoleServiceImpl implements ISysRoleService {
 | 
			
		||||
        if (CollUtil.isNotEmpty(list)) {
 | 
			
		||||
            rows = userRoleMapper.insertBatch(list) ? list.size() : 0;
 | 
			
		||||
        }
 | 
			
		||||
        if (rows > 0) {
 | 
			
		||||
            cleanOnlineUserByRole(roleId);
 | 
			
		||||
        }
 | 
			
		||||
        return rows;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void cleanOnlineUserByRole(Long roleId) {
 | 
			
		||||
        List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
 | 
			
		||||
        if (CollUtil.isEmpty(keys)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作
 | 
			
		||||
        keys.parallelStream().forEach(key -> {
 | 
			
		||||
            String token = StringUtils.substringAfterLast(key, ":");
 | 
			
		||||
            // 如果已经过期则跳过
 | 
			
		||||
            if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            LoginUser loginUser = LoginHelper.getLoginUser(token);
 | 
			
		||||
            if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) {
 | 
			
		||||
                try {
 | 
			
		||||
                    StpUtil.logoutByTokenValue(token);
 | 
			
		||||
                } catch (NotLoginException ignored) {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,12 @@ import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.core.domain.R;
 | 
			
		||||
import com.ruoyi.common.enums.CaptchaType;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
import com.ruoyi.common.utils.email.MailUtils;
 | 
			
		||||
import com.ruoyi.common.utils.redis.RedisUtils;
 | 
			
		||||
import com.ruoyi.common.utils.reflect.ReflectUtils;
 | 
			
		||||
import com.ruoyi.common.utils.spring.SpringUtils;
 | 
			
		||||
import com.ruoyi.framework.config.properties.CaptchaProperties;
 | 
			
		||||
import com.ruoyi.framework.config.properties.MailProperties;
 | 
			
		||||
import com.ruoyi.sms.config.properties.SmsProperties;
 | 
			
		||||
import com.ruoyi.sms.core.SmsTemplate;
 | 
			
		||||
import com.ruoyi.sms.entity.SmsResult;
 | 
			
		||||
@@ -47,6 +49,7 @@ public class CaptchaController {
 | 
			
		||||
    private final CaptchaProperties captchaProperties;
 | 
			
		||||
    private final SmsProperties smsProperties;
 | 
			
		||||
    private final ISysConfigService configService;
 | 
			
		||||
    private final MailProperties mailProperties;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 短信验证码
 | 
			
		||||
@@ -54,8 +57,7 @@ public class CaptchaController {
 | 
			
		||||
     * @param phonenumber 用户手机号
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/captchaSms")
 | 
			
		||||
    public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}")
 | 
			
		||||
                              String phonenumber) {
 | 
			
		||||
    public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
 | 
			
		||||
        if (!smsProperties.getEnabled()) {
 | 
			
		||||
            return R.fail("当前系统没有开启短信功能!");
 | 
			
		||||
        }
 | 
			
		||||
@@ -75,6 +77,28 @@ public class CaptchaController {
 | 
			
		||||
        return R.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱验证码
 | 
			
		||||
     *
 | 
			
		||||
     * @param email 邮箱
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/captchaEmail")
 | 
			
		||||
    public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
 | 
			
		||||
        if (!mailProperties.getEnabled()) {
 | 
			
		||||
            return R.fail("当前系统没有开启邮箱功能!");
 | 
			
		||||
        }
 | 
			
		||||
        String key = CacheConstants.CAPTCHA_CODE_KEY + email;
 | 
			
		||||
        String code = RandomUtil.randomNumbers(4);
 | 
			
		||||
        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
 | 
			
		||||
        try {
 | 
			
		||||
            MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("验证码短信发送异常 => {}", e.getMessage());
 | 
			
		||||
            return R.fail(e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        return R.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 生成验证码
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@ public class CacheController {
 | 
			
		||||
    private final static List<SysCache> CACHES = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        CACHES.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息"));
 | 
			
		||||
        CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户"));
 | 
			
		||||
        CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息"));
 | 
			
		||||
        CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典"));
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ public class SysUserOnlineController extends BaseController {
 | 
			
		||||
        List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
 | 
			
		||||
        List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
 | 
			
		||||
        for (String key : keys) {
 | 
			
		||||
            String token = key.replace(CacheConstants.LOGIN_TOKEN_KEY, "");
 | 
			
		||||
            String token = StringUtils.substringAfterLast(key, ":");
 | 
			
		||||
            // 如果已经过期则跳过
 | 
			
		||||
            if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
 | 
			
		||||
                continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.core.domain.R;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysMenu;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.EmailLoginBody;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginBody;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.SmsLoginBody;
 | 
			
		||||
@@ -57,7 +58,7 @@ public class SysLoginController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 短信登录(示例)
 | 
			
		||||
     * 短信登录
 | 
			
		||||
     *
 | 
			
		||||
     * @param smsLoginBody 登录信息
 | 
			
		||||
     * @return 结果
 | 
			
		||||
@@ -72,6 +73,21 @@ public class SysLoginController {
 | 
			
		||||
        return R.ok(ajax);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮件登录
 | 
			
		||||
     *
 | 
			
		||||
     * @param body 登录信息
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    @PostMapping("/emailLogin")
 | 
			
		||||
    public R<Map<String, Object>> emailLogin(@Validated @RequestBody EmailLoginBody body) {
 | 
			
		||||
        Map<String, Object> ajax = new HashMap<>();
 | 
			
		||||
        // 生成令牌
 | 
			
		||||
        String token = loginService.emailLogin(body.getEmail(), body.getEmailCode());
 | 
			
		||||
        ajax.put(Constants.TOKEN, token);
 | 
			
		||||
        return R.ok(ajax);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序登录(示例)
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,7 @@ package com.ruoyi.web.controller.system;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.http.HttpException;
 | 
			
		||||
import cn.hutool.http.HttpUtil;
 | 
			
		||||
import com.ruoyi.common.annotation.Log;
 | 
			
		||||
import com.ruoyi.common.core.controller.BaseController;
 | 
			
		||||
import com.ruoyi.common.core.domain.PageQuery;
 | 
			
		||||
@@ -13,11 +10,6 @@ import com.ruoyi.common.core.domain.R;
 | 
			
		||||
import com.ruoyi.common.core.page.TableDataInfo;
 | 
			
		||||
import com.ruoyi.common.core.validate.QueryGroup;
 | 
			
		||||
import com.ruoyi.common.enums.BusinessType;
 | 
			
		||||
import com.ruoyi.common.exception.ServiceException;
 | 
			
		||||
import com.ruoyi.common.utils.file.FileUtils;
 | 
			
		||||
import com.ruoyi.oss.core.OssClient;
 | 
			
		||||
import com.ruoyi.oss.factory.OssFactory;
 | 
			
		||||
import com.ruoyi.system.domain.SysOss;
 | 
			
		||||
import com.ruoyi.system.domain.bo.SysOssBo;
 | 
			
		||||
import com.ruoyi.system.domain.vo.SysOssVo;
 | 
			
		||||
import com.ruoyi.system.service.ISysOssService;
 | 
			
		||||
@@ -80,7 +72,7 @@ public class SysOssController extends BaseController {
 | 
			
		||||
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
 | 
			
		||||
    public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
 | 
			
		||||
        if (ObjectUtil.isNull(file)) {
 | 
			
		||||
            throw new ServiceException("上传文件不能为空");
 | 
			
		||||
            return R.fail("上传文件不能为空");
 | 
			
		||||
        }
 | 
			
		||||
        SysOssVo oss = iSysOssService.upload(file);
 | 
			
		||||
        Map<String, String> map = new HashMap<>(2);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,15 @@
 | 
			
		||||
package com.ruoyi.web.controller.system;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import cn.dev33.satoken.exception.NotLoginException;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import com.ruoyi.common.annotation.Log;
 | 
			
		||||
import com.ruoyi.common.constant.CacheConstants;
 | 
			
		||||
import com.ruoyi.common.constant.UserConstants;
 | 
			
		||||
import com.ruoyi.common.core.controller.BaseController;
 | 
			
		||||
import com.ruoyi.common.core.domain.PageQuery;
 | 
			
		||||
import com.ruoyi.common.core.domain.R;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysDept;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysRole;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
			
		||||
import com.ruoyi.common.core.page.TableDataInfo;
 | 
			
		||||
import com.ruoyi.common.enums.BusinessType;
 | 
			
		||||
import com.ruoyi.common.helper.LoginHelper;
 | 
			
		||||
import com.ruoyi.common.utils.poi.ExcelUtil;
 | 
			
		||||
import com.ruoyi.system.domain.SysUserRole;
 | 
			
		||||
import com.ruoyi.system.service.ISysDeptService;
 | 
			
		||||
@@ -112,25 +105,7 @@ public class SysRoleController extends BaseController {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (roleService.updateRole(role) > 0) {
 | 
			
		||||
            List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
 | 
			
		||||
            if (CollUtil.isEmpty(keys)) {
 | 
			
		||||
                return R.ok();
 | 
			
		||||
            }
 | 
			
		||||
            // 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作
 | 
			
		||||
            keys.parallelStream().forEach(key -> {
 | 
			
		||||
                String token = key.replace(CacheConstants.LOGIN_TOKEN_KEY, "");
 | 
			
		||||
                // 如果已经过期则跳过
 | 
			
		||||
                if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                LoginUser loginUser = LoginHelper.getLoginUser(token);
 | 
			
		||||
                if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(role.getRoleId()))) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        StpUtil.logoutByTokenValue(token);
 | 
			
		||||
                    } catch (NotLoginException ignored) {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            roleService.cleanOnlineUserByRole(role.getRoleId());
 | 
			
		||||
            return R.ok();
 | 
			
		||||
        }
 | 
			
		||||
        return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ logging:
 | 
			
		||||
  level:
 | 
			
		||||
    com.ruoyi: @logging.level@
 | 
			
		||||
    org.springframework: warn
 | 
			
		||||
  config: classpath:logback.xml
 | 
			
		||||
  config: classpath:logback-plus.xml
 | 
			
		||||
 | 
			
		||||
# 用户配置
 | 
			
		||||
user:
 | 
			
		||||
@@ -209,7 +209,7 @@ swagger:
 | 
			
		||||
    contact:
 | 
			
		||||
      name: Lion Li
 | 
			
		||||
      email: crazylionli@163.com
 | 
			
		||||
      url: https://gitee.com/JavaLionLi/RuoYi-Vue-Plus
 | 
			
		||||
      url: https://gitee.com/dromara/RuoYi-Vue-Plus
 | 
			
		||||
  components:
 | 
			
		||||
    # 鉴权方式配置
 | 
			
		||||
    security-schemes:
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
 | 
			
		||||
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
 | 
			
		||||
user.password.not.valid=* 5-50个字符
 | 
			
		||||
user.email.not.valid=邮箱格式错误
 | 
			
		||||
user.email.not.blank=邮箱不能为空
 | 
			
		||||
user.phonenumber.not.blank=用户手机号不能为空
 | 
			
		||||
user.mobile.phone.number.not.valid=手机号格式错误
 | 
			
		||||
user.login.success=登录成功
 | 
			
		||||
@@ -42,4 +43,7 @@ rate.limiter.message=访问过于频繁,请稍候再试
 | 
			
		||||
sms.code.not.blank=短信验证码不能为空
 | 
			
		||||
sms.code.retry.limit.count=短信验证码输入错误{0}次
 | 
			
		||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
 | 
			
		||||
email.code.not.blank=邮箱验证码不能为空
 | 
			
		||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
 | 
			
		||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
 | 
			
		||||
xcx.code.not.blank=小程序code不能为空
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ user.password.not.blank=Password cannot be empty
 | 
			
		||||
user.password.length.valid=Password length must be between {min} and {max} characters
 | 
			
		||||
user.password.not.valid=* 5-50 characters
 | 
			
		||||
user.email.not.valid=Mailbox format error
 | 
			
		||||
user.email.not.blank=Mailbox cannot be blank
 | 
			
		||||
user.phonenumber.not.blank=Phone number cannot be blank
 | 
			
		||||
user.mobile.phone.number.not.valid=Phone number format error
 | 
			
		||||
user.login.success=Login successful
 | 
			
		||||
@@ -42,4 +43,7 @@ rate.limiter.message=Visit too frequently, please try again later
 | 
			
		||||
sms.code.not.blank=Sms code cannot be blank
 | 
			
		||||
sms.code.retry.limit.count=Sms code input error {0} times
 | 
			
		||||
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
 | 
			
		||||
email.code.not.blank=Email code cannot be blank
 | 
			
		||||
email.code.retry.limit.count=Email code input error {0} times
 | 
			
		||||
email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
 | 
			
		||||
xcx.code.not.blank=Mini program code cannot be blank
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
 | 
			
		||||
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
 | 
			
		||||
user.password.not.valid=* 5-50个字符
 | 
			
		||||
user.email.not.valid=邮箱格式错误
 | 
			
		||||
user.email.not.blank=邮箱不能为空
 | 
			
		||||
user.phonenumber.not.blank=用户手机号不能为空
 | 
			
		||||
user.mobile.phone.number.not.valid=手机号格式错误
 | 
			
		||||
user.login.success=登录成功
 | 
			
		||||
@@ -42,4 +43,7 @@ rate.limiter.message=访问过于频繁,请稍候再试
 | 
			
		||||
sms.code.not.blank=短信验证码不能为空
 | 
			
		||||
sms.code.retry.limit.count=短信验证码输入错误{0}次
 | 
			
		||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
 | 
			
		||||
email.code.not.blank=邮箱验证码不能为空
 | 
			
		||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
 | 
			
		||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
 | 
			
		||||
xcx.code.not.blank=小程序code不能为空
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								ruoyi/src/main/resources/ip2region.xdb
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -128,6 +128,11 @@
 | 
			
		||||
        where u.del_flag = '0' and u.phonenumber = #{phonenumber}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="selectUserByEmail" parameterType="String" resultMap="SysUserResult">
 | 
			
		||||
        <include refid="selectUserVo"/>
 | 
			
		||||
        where u.del_flag = '0' and u.email = #{email}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
 | 
			
		||||
        <include refid="selectUserVo"/>
 | 
			
		||||
        where u.del_flag = '0' and u.user_id = #{userId}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,18 +34,18 @@ import com.ruoyi.common.core.domain.TreeEntity;
 | 
			
		||||
public class ${ClassName}Bo extends ${Entity} {
 | 
			
		||||
 | 
			
		||||
#foreach ($column in $columns)
 | 
			
		||||
#if(!$table.isSuperColumn($column.javaField) && ($column.query || $column.isInsert || $column.isEdit))
 | 
			
		||||
#if(!$table.isSuperColumn($column.javaField) && ($column.query || $column.insert || $column.edit))
 | 
			
		||||
    /**
 | 
			
		||||
     * $column.columnComment
 | 
			
		||||
     */
 | 
			
		||||
#if($column.isInsert && $column.isEdit)
 | 
			
		||||
#if($column.insert && $column.edit)
 | 
			
		||||
#set($Group="AddGroup.class, EditGroup.class")
 | 
			
		||||
#elseif($column.isInsert)
 | 
			
		||||
#elseif($column.insert)
 | 
			
		||||
#set($Group="AddGroup.class")
 | 
			
		||||
#elseif($column.isEdit)
 | 
			
		||||
#elseif($column.edit)
 | 
			
		||||
#set($Group="EditGroup.class")
 | 
			
		||||
#end
 | 
			
		||||
#if($column.isRequired == 1)
 | 
			
		||||
#if($column.required)
 | 
			
		||||
#if($column.javaType == 'String')
 | 
			
		||||
    @NotBlank(message = "$column.columnComment不能为空", groups = { $Group })
 | 
			
		||||
#else
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ public class ${ClassName} extends ${Entity} {
 | 
			
		||||
#if($column.javaField=='version')
 | 
			
		||||
    @Version
 | 
			
		||||
#end
 | 
			
		||||
#if($column.isPk==1)
 | 
			
		||||
#if($column.pk)
 | 
			
		||||
    @TableId(value = "$column.columnName")
 | 
			
		||||
#end
 | 
			
		||||
    private $column.javaType $column.javaField;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ public class ${ClassName}Vo {
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
#foreach ($column in $columns)
 | 
			
		||||
#if($column.isList)
 | 
			
		||||
#if($column.list)
 | 
			
		||||
    /**
 | 
			
		||||
     * $column.columnComment
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ services:
 | 
			
		||||
    network_mode: "host"
 | 
			
		||||
 | 
			
		||||
  minio:
 | 
			
		||||
    image: minio/minio:RELEASE.2022-05-26T05-48-41Z
 | 
			
		||||
    image: minio/minio:RELEASE.2023-03-24T21-41-23Z
 | 
			
		||||
    container_name: minio
 | 
			
		||||
    ports:
 | 
			
		||||
      # api 端口
 | 
			
		||||
@@ -100,7 +100,7 @@ services:
 | 
			
		||||
    network_mode: "host"
 | 
			
		||||
 | 
			
		||||
  ruoyi-server1:
 | 
			
		||||
    image: ruoyi/ruoyi-server:4.6.0
 | 
			
		||||
    image: ruoyi/ruoyi-server:4.7.0
 | 
			
		||||
    container_name: ruoyi-server1
 | 
			
		||||
    environment:
 | 
			
		||||
      # 时区上海
 | 
			
		||||
@@ -115,7 +115,7 @@ services:
 | 
			
		||||
    network_mode: "host"
 | 
			
		||||
 | 
			
		||||
  ruoyi-server2:
 | 
			
		||||
    image: "ruoyi/ruoyi-server:4.6.0"
 | 
			
		||||
    image: "ruoyi/ruoyi-server:4.7.0"
 | 
			
		||||
    container_name: ruoyi-server2
 | 
			
		||||
    environment:
 | 
			
		||||
      # 时区上海
 | 
			
		||||
@@ -130,7 +130,7 @@ services:
 | 
			
		||||
    network_mode: "host"
 | 
			
		||||
 | 
			
		||||
  ruoyi-monitor-admin:
 | 
			
		||||
    image: ruoyi/ruoyi-monitor-admin:4.6.0
 | 
			
		||||
    image: ruoyi/ruoyi-monitor-admin:4.7.0
 | 
			
		||||
    container_name: ruoyi-monitor-admin
 | 
			
		||||
    environment:
 | 
			
		||||
      # 时区上海
 | 
			
		||||
@@ -142,7 +142,7 @@ services:
 | 
			
		||||
    network_mode: "host"
 | 
			
		||||
 | 
			
		||||
  ruoyi-xxl-job-admin:
 | 
			
		||||
    image: ruoyi/ruoyi-xxl-job-admin:4.6.0
 | 
			
		||||
    image: ruoyi/ruoyi-xxl-job-admin:4.7.0
 | 
			
		||||
    container_name: ruoyi-xxl-job-admin
 | 
			
		||||
    environment:
 | 
			
		||||
      # 时区上海
 | 
			
		||||
 
 | 
			
		||||
@@ -245,7 +245,7 @@ comment on column sys_menu.remark       is '备注';
 | 
			
		||||
insert into sys_menu values('1', '系统管理', '0', '1', 'system',           null, '', 1, 0, 'M', '0', '0', '', 'system',   'admin', sysdate, '', null, '系统管理目录');
 | 
			
		||||
insert into sys_menu values('2', '系统监控', '0', '2', 'monitor',          null, '', 1, 0, 'M', '0', '0', '', 'monitor',  'admin', sysdate, '', null, '系统监控目录');
 | 
			
		||||
insert into sys_menu values('3', '系统工具', '0', '3', 'tool',             null, '', 1, 0, 'M', '0', '0', '', 'tool',     'admin', sysdate, '', null, '系统工具目录');
 | 
			
		||||
insert into sys_menu values('4', 'PLUS官网', '0', '4', 'https://gitee.com/JavaLionLi/RuoYi-Vue-Plus', null, '', 0, 0, 'M', '0', '0', '', 'guide',    'admin', sysdate, '', null, 'RuoYi-Vue-Plus官网地址');
 | 
			
		||||
insert into sys_menu values('4', 'PLUS官网', '0', '4', 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', 0, 0, 'M', '0', '0', '', 'guide',    'admin', sysdate, '', null, 'RuoYi-Vue-Plus官网地址');
 | 
			
		||||
-- 二级菜单
 | 
			
		||||
insert into sys_menu values('100',  '用户管理', '1',   '1', 'user',       'system/user/index',        '', 1, 0, 'C', '0', '0', 'system:user:list',        'user',          'admin', sysdate, '', null, '用户管理菜单');
 | 
			
		||||
insert into sys_menu values('101',  '角色管理', '1',   '2', 'role',       'system/role/index',        '', 1, 0, 'C', '0', '0', 'system:role:list',        'peoples',       'admin', sysdate, '', null, '角色管理菜单');
 | 
			
		||||
@@ -531,9 +531,9 @@ create table sys_oper_log (
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
alter table sys_oper_log add constraint pk_sys_oper_log primary key (oper_id);
 | 
			
		||||
create unique index idx_sys_oper_log_bt on sys_oper_log (business_type);
 | 
			
		||||
create unique index idx_sys_oper_log_s on sys_oper_log (status);
 | 
			
		||||
create unique index idx_sys_oper_log_ot on sys_oper_log (oper_time);
 | 
			
		||||
create index idx_sys_oper_log_bt on sys_oper_log (business_type);
 | 
			
		||||
create index idx_sys_oper_log_s on sys_oper_log (status);
 | 
			
		||||
create index idx_sys_oper_log_ot on sys_oper_log (oper_time);
 | 
			
		||||
 | 
			
		||||
comment on table  sys_oper_log                is '操作日志记录';
 | 
			
		||||
comment on column sys_oper_log.oper_id        is '日志主键';
 | 
			
		||||
@@ -711,8 +711,8 @@ create table sys_logininfor (
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
alter table sys_logininfor add constraint pk_sys_logininfor primary key (info_id);
 | 
			
		||||
create unique index idx_sys_logininfor_s on sys_logininfor (status);
 | 
			
		||||
create unique index idx_sys_logininfor_lt on sys_logininfor (login_time);
 | 
			
		||||
create index idx_sys_logininfor_s on sys_logininfor (status);
 | 
			
		||||
create index idx_sys_logininfor_lt on sys_logininfor (login_time);
 | 
			
		||||
 | 
			
		||||
comment on table  sys_logininfor                is '系统访问记录';
 | 
			
		||||
comment on column sys_logininfor.info_id        is '访问ID';
 | 
			
		||||
@@ -938,7 +938,7 @@ comment on column sys_oss_config.domain is '自定义域名';
 | 
			
		||||
comment on column sys_oss_config.is_https is '是否https(Y=是,N=否)';
 | 
			
		||||
comment on column sys_oss_config.region is '域';
 | 
			
		||||
comment on column sys_oss_config.access_policy is '桶权限类型(0=private 1=public 2=custom)';
 | 
			
		||||
comment on column sys_oss_config.status is '状态(0=正常,1=停用)';
 | 
			
		||||
comment on column sys_oss_config.status is '是否默认(0=是,1=否)';
 | 
			
		||||
comment on column sys_oss_config.ext1 is '扩展字段';
 | 
			
		||||
comment on column sys_oss_config.remark is '备注';
 | 
			
		||||
comment on column sys_oss_config.create_by is '创建者';
 | 
			
		||||
 
 | 
			
		||||
@@ -248,7 +248,7 @@ comment on column sys_menu.remark is '备注';
 | 
			
		||||
insert into sys_menu values('1', '系统管理', '0', '1', 'system',           null, '', '1', '0', 'M', '0', '0', '', 'system',   'admin', now(), '', null, '系统管理目录');
 | 
			
		||||
insert into sys_menu values('2', '系统监控', '0', '2', 'monitor',          null, '', '1', '0', 'M', '0', '0', '', 'monitor',  'admin', now(), '', null, '系统监控目录');
 | 
			
		||||
insert into sys_menu values('3', '系统工具', '0', '3', 'tool',             null, '', '1', '0', 'M', '0', '0', '', 'tool',     'admin', now(), '', null, '系统工具目录');
 | 
			
		||||
insert into sys_menu values('4', 'PLUS官网', '0', '4', 'https://gitee.com/JavaLionLi/RuoYi-Vue-Plus', null, '', '0', '0', 'M', '0', '0', '', 'guide',    'admin', now(), '', null, 'RuoYi-Vue-Plus官网地址');
 | 
			
		||||
insert into sys_menu values('4', 'PLUS官网', '0', '4', 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', '0', '0', 'M', '0', '0', '', 'guide',    'admin', now(), '', null, 'RuoYi-Vue-Plus官网地址');
 | 
			
		||||
-- 二级菜单
 | 
			
		||||
insert into sys_menu values('100',  '用户管理', '1',   '1', 'user',       'system/user/index',        '', '1', '0', 'C', '0', '0', 'system:user:list',        'user',          'admin', now(), '', null, '用户管理菜单');
 | 
			
		||||
insert into sys_menu values('101',  '角色管理', '1',   '2', 'role',       'system/role/index',        '', '1', '0', 'C', '0', '0', 'system:role:list',        'peoples',       'admin', now(), '', null, '角色管理菜单');
 | 
			
		||||
@@ -540,9 +540,9 @@ create table if not exists sys_oper_log
 | 
			
		||||
    constraint sys_oper_log_pk primary key (oper_id)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create unique index idx_sys_oper_log_bt ON sys_oper_log (business_type);
 | 
			
		||||
create unique index idx_sys_oper_log_s ON sys_oper_log (status);
 | 
			
		||||
create unique index idx_sys_oper_log_ot ON sys_oper_log (oper_time);
 | 
			
		||||
create index idx_sys_oper_log_bt ON sys_oper_log (business_type);
 | 
			
		||||
create index idx_sys_oper_log_s ON sys_oper_log (status);
 | 
			
		||||
create index idx_sys_oper_log_ot ON sys_oper_log (oper_time);
 | 
			
		||||
 | 
			
		||||
comment on table sys_oper_log is '操作日志记录';
 | 
			
		||||
comment on column sys_oper_log.oper_id is '日志主键';
 | 
			
		||||
@@ -726,8 +726,8 @@ create table if not exists sys_logininfor
 | 
			
		||||
    constraint sys_logininfor_pk primary key (info_id)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create unique index idx_sys_logininfor_s ON sys_logininfor (status);
 | 
			
		||||
create unique index idx_sys_logininfor_lt ON sys_logininfor (login_time);
 | 
			
		||||
create index idx_sys_logininfor_s ON sys_logininfor (status);
 | 
			
		||||
create index idx_sys_logininfor_lt ON sys_logininfor (login_time);
 | 
			
		||||
 | 
			
		||||
comment on table sys_logininfor is '系统访问记录';
 | 
			
		||||
comment on column sys_logininfor.info_id is '访问ID';
 | 
			
		||||
@@ -954,7 +954,7 @@ comment on column sys_oss_config.domain is '自定义域名';
 | 
			
		||||
comment on column sys_oss_config.is_https is '是否https(Y=是,N=否)';
 | 
			
		||||
comment on column sys_oss_config.region is '域';
 | 
			
		||||
comment on column sys_oss_config.access_policy is '桶权限类型(0=private 1=public 2=custom)';
 | 
			
		||||
comment on column sys_oss_config.status is '状态(0=正常,1=停用)';
 | 
			
		||||
comment on column sys_oss_config.status is '是否默认(0=是,1=否)';
 | 
			
		||||
comment on column sys_oss_config.ext1 is '扩展字段';
 | 
			
		||||
comment on column sys_oss_config.create_by is '创建者';
 | 
			
		||||
comment on column sys_oss_config.create_time is '创建时间';
 | 
			
		||||
 
 | 
			
		||||
@@ -160,7 +160,7 @@ create table sys_menu (
 | 
			
		||||
insert into sys_menu values('1', '系统管理', '0', '1', 'system',           null, '', 1, 0, 'M', '0', '0', '', 'system',   'admin', sysdate(), '', null, '系统管理目录');
 | 
			
		||||
insert into sys_menu values('2', '系统监控', '0', '2', 'monitor',          null, '', 1, 0, 'M', '0', '0', '', 'monitor',  'admin', sysdate(), '', null, '系统监控目录');
 | 
			
		||||
insert into sys_menu values('3', '系统工具', '0', '3', 'tool',             null, '', 1, 0, 'M', '0', '0', '', 'tool',     'admin', sysdate(), '', null, '系统工具目录');
 | 
			
		||||
insert into sys_menu values('4', 'PLUS官网', '0', '4', 'https://gitee.com/JavaLionLi/RuoYi-Vue-Plus', null, '', 0, 0, 'M', '0', '0', '', 'guide',    'admin', sysdate(), '', null, 'RuoYi-Vue-Plus官网地址');
 | 
			
		||||
insert into sys_menu values('4', 'PLUS官网', '0', '4', 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', 0, 0, 'M', '0', '0', '', 'guide',    'admin', sysdate(), '', null, 'RuoYi-Vue-Plus官网地址');
 | 
			
		||||
-- 二级菜单
 | 
			
		||||
insert into sys_menu values('100',  '用户管理', '1',   '1', 'user',       'system/user/index',        '', 1, 0, 'C', '0', '0', 'system:user:list',        'user',          'admin', sysdate(), '', null, '用户管理菜单');
 | 
			
		||||
insert into sys_menu values('101',  '角色管理', '1',   '2', 'role',       'system/role/index',        '', 1, 0, 'C', '0', '0', 'system:role:list',        'peoples',       'admin', sysdate(), '', null, '角色管理菜单');
 | 
			
		||||
@@ -679,7 +679,7 @@ create table sys_oss_config (
 | 
			
		||||
  is_https         char(1)                default 'N'     comment '是否https(Y=是,N=否)',
 | 
			
		||||
  region           varchar(255)           default ''      comment '域',
 | 
			
		||||
  access_policy    char(1)     not null   default '1'     comment '桶权限类型(0=private 1=public 2=custom)',
 | 
			
		||||
  status           char(1)                default '1'     comment '状态(0=正常,1=停用)',
 | 
			
		||||
  status           char(1)                default '1'     comment '是否默认(0=是,1=否)',
 | 
			
		||||
  ext1             varchar(255)           default ''      comment '扩展字段',
 | 
			
		||||
  create_by       varchar(64)             default ''      comment '创建者',
 | 
			
		||||
  create_time     datetime                default null    comment '创建时间',
 | 
			
		||||
 
 | 
			
		||||
@@ -1054,6 +1054,8 @@ INSERT sys_menu VALUES (2, N'系统监控', 0, 2, N'monitor', NULL, N'', 1, 0, N
 | 
			
		||||
GO
 | 
			
		||||
INSERT sys_menu VALUES (3, N'系统工具', 0, 3, N'tool', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'tool', N'admin', getdate(), N'', NULL, N'系统工具目录')
 | 
			
		||||
GO
 | 
			
		||||
INSERT sys_menu VALUES (4, N'PLUS官网', 0, 5, N'https://gitee.com/dromara/RuoYi-Vue-Plus', null, N'', 0, 0, N'M', N'0', N'0', N'', N'guide', N'admin', getdate(), N'', null, N'RuoYi-Vue-Plus官网地址');
 | 
			
		||||
GO
 | 
			
		||||
INSERT sys_menu VALUES (100, N'用户管理', 1, 1, N'user', N'system/user/index', N'', 1, 0, N'C', N'0', N'0', N'system:user:list', N'user', N'admin', getdate(), N'', NULL, N'用户管理菜单')
 | 
			
		||||
GO
 | 
			
		||||
INSERT sys_menu VALUES (101, N'角色管理', 1, 2, N'role', N'system/role/index', N'', 1, 0, N'C', N'0', N'0', N'system:role:list', N'peoples', N'admin', getdate(), N'', NULL, N'角色管理菜单')
 | 
			
		||||
@@ -2283,7 +2285,7 @@ EXEC sp_addextendedproperty
 | 
			
		||||
     'COLUMN', N'access_policy'
 | 
			
		||||
GO
 | 
			
		||||
EXEC sp_addextendedproperty
 | 
			
		||||
    'MS_Description', N'状态(0=正常,1=停用)',
 | 
			
		||||
    'MS_Description', N'是否默认(0=是,1=否)',
 | 
			
		||||
    'SCHEMA', N'dbo',
 | 
			
		||||
    'TABLE', N'sys_oss_config',
 | 
			
		||||
    'COLUMN', N'status'
 | 
			
		||||
 
 | 
			
		||||