wangtong пре 1 година
комит
9280e3cac2
100 измењених фајлова са 8022 додато и 0 уклоњено
  1. 18 0
      .editorconfig
  2. 46 0
      .gitignore
  3. 12 0
      .run/ruoyi-monitor-admin.run.xml
  4. 12 0
      .run/ruoyi-server.run.xml
  5. 12 0
      .run/ruoyi-xxl-job-admin.run.xml
  6. 20 0
      LICENSE
  7. 171 0
      README.md
  8. 431 0
      pom.xml
  9. 23 0
      ruoyi-admin/Dockerfile
  10. 131 0
      ruoyi-admin/pom.xml
  11. 24 0
      ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
  12. 18 0
      ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java
  13. 134 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java
  14. 168 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java
  15. 88 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java
  16. 74 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java
  17. 90 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java
  18. 137 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java
  19. 122 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
  20. 116 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java
  21. 125 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java
  22. 32 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
  23. 145 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
  24. 127 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
  25. 80 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java
  26. 105 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssConfigController.java
  27. 109 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java
  28. 120 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java
  29. 126 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  30. 40 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java
  31. 228 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
  32. 256 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
  33. 182 0
      ruoyi-admin/src/main/resources/application-dev.yml
  34. 185 0
      ruoyi-admin/src/main/resources/application-prod.yml
  35. 268 0
      ruoyi-admin/src/main/resources/application.yml
  36. 8 0
      ruoyi-admin/src/main/resources/banner.txt
  37. 50 0
      ruoyi-admin/src/main/resources/i18n/messages.properties
  38. 49 0
      ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
  39. 51 0
      ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  40. BIN
      ruoyi-admin/src/main/resources/ip2region.xdb
  41. 129 0
      ruoyi-admin/src/main/resources/logback-plus.xml
  42. 28 0
      ruoyi-admin/src/main/resources/spy.properties
  43. 45 0
      ruoyi-admin/src/test/java/com/ruoyi/test/AssertUnitTest.java
  44. 70 0
      ruoyi-admin/src/test/java/com/ruoyi/test/DemoUnitTest.java
  45. 72 0
      ruoyi-admin/src/test/java/com/ruoyi/test/ParamUnitTest.java
  46. 54 0
      ruoyi-admin/src/test/java/com/ruoyi/test/TagUnitTest.java
  47. 170 0
      ruoyi-common/pom.xml
  48. 24 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/CellMerge.java
  49. 28 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataColumn.java
  50. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataPermission.java
  51. 29 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java
  52. 44 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java
  53. 32 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java
  54. 30 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java
  55. 47 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
  56. 41 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
  57. 29 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
  58. 24 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java
  59. 39 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java
  60. 21 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java
  61. 85 0
      ruoyi-common/src/main/java/com/ruoyi/common/captcha/UnsignedMathGenerator.java
  62. 38 0
      ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
  63. 44 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
  64. 58 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheNames.java
  65. 76 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
  66. 193 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
  67. 93 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
  68. 30 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/TransConstant.java
  69. 147 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
  70. 52 0
      ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelBigNumberConvert.java
  71. 73 0
      ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelDictConvert.java
  72. 97 0
      ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java
  73. 69 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java
  74. 63 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
  75. 112 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/PageQuery.java
  76. 107 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java
  77. 39 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java
  78. 38 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/RoleDTO.java
  79. 60 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/UserOnlineDTO.java
  80. 80 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
  81. 100 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
  82. 65 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
  83. 104 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
  84. 124 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
  85. 169 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
  86. 44 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/LogininforEvent.java
  87. 104 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/OperLogEvent.java
  88. 30 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/EmailLoginBody.java
  89. 46 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
  90. 116 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
  91. 17 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java
  92. 28 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java
  93. 24 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/XcxLoginUser.java
  94. 192 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/mapper/BaseMapperPlus.java
  95. 78 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java
  96. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/ConfigService.java
  97. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/DeptService.java
  98. 66 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/DictService.java
  99. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/OssService.java
  100. 0 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/service/SensitiveService.java

+ 18 - 0
.editorconfig

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

+ 46 - 0
.gitignore

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

+ 12 - 0
.run/ruoyi-monitor-admin.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <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.8.2" />
+        <option name="buildOnly" value="true" />
+        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
+      </settings>
+    </deployment>
+    <method v="2" />
+  </configuration>
+</component>

+ 12 - 0
.run/ruoyi-server.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <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.8.2" />
+        <option name="buildOnly" value="true" />
+        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
+      </settings>
+    </deployment>
+    <method v="2" />
+  </configuration>
+</component>

+ 12 - 0
.run/ruoyi-xxl-job-admin.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <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.8.2" />
+        <option name="buildOnly" value="true" />
+        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" />
+      </settings>
+    </deployment>
+    <method v="2" />
+  </configuration>
+</component>

+ 20 - 0
LICENSE

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

+ 171 - 0
README.md

@@ -0,0 +1,171 @@
+<img src="https://foruda.gitee.com/images/1679673773341074847/178e8451_1766278.png" width="50%" height="50%">
+<div style="height: 10px; clear: both;"></div>
+
+- - -
+
+## 版本状态说明
+
+由于 springboot 2.X 与 vue 2.X 官方均宣布停止维护, 故而 框架 4.X 版本 进入维护状态(只处理问题不更新功能)
+
+停止维护时间预计: 2024年6-10月具体根据使用人数动态决定, 此版本已经相当稳定 即便不更新功能也不影响使用
+
+如果依旧选择使用 jdk8 或者 jdk11 可以放心使用此版本, 如果希望使用 jdk17 或者 jdk21 可以选择使用 5.X 分支
+
+## 平台简介
+
+[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![GitHub](https://img.shields.io/github/stars/JavaLionLi/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
+[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
+[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
+<br>
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.8.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.7-blue.svg)]()
+[![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
+[![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
+
+> RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群` 场景全方位升级(不兼容原框架)
+
+> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
+活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源
+
+> 系统演示: [传送门](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4836388&doc_id=1469725)
+
+# 本框架与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协议的厂家                                                                          | 不支持                                                                                |
+| 短信          | 采用 sms4j 短信融合包 支持数十种短信厂家 只需在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/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/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/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/dromara/RuoYi-Vue-Plus/wikis/pages)
+>>[https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages)
+
+## 软件架构图
+
+![Plus部署架构图](https://images.gitee.com/uploads/images/2021/1112/202137_673ac5d2_1766278.png "Plus部署架构图.png")
+## 贡献代码
+
+欢迎各路英雄豪杰 `PR` 代码 请提交到 `dev` 开发分支 统一测试发版
+
+框架定位为 `通用后台管理系统(分布式集群强化)` 原则上不接受业务 `PR`
+
+### 其他
+
+* 同步升级 RuoYi-Vue
+* 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/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
+
+## 加群与捐献
+>[加群与捐献](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" />
+
+## 演示图例
+
+|                                                                                            |                                                                                            |
+|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
+| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |
+
+

+ 431 - 0
pom.xml

@@ -0,0 +1,431 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.ruoyi</groupId>
+    <artifactId>ruoyi-vue-plus</artifactId>
+    <version>4.8.2</version>
+
+    <name>RuoYi-Vue-Plus</name>
+    <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
+    <description>RuoYi-Vue-Plus后台管理系统</description>
+
+    <properties>
+        <ruoyi-vue-plus.version>4.8.2</ruoyi-vue-plus.version>
+        <spring-boot.version>2.7.18</spring-boot.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
+        <spring-boot.mybatis>2.2.2</spring-boot.mybatis>
+        <springdoc.version>1.6.15</springdoc.version>
+        <poi.version>5.2.3</poi.version>
+        <easyexcel.version>3.3.2</easyexcel.version>
+        <velocity.version>2.3</velocity.version>
+        <satoken.version>1.37.0</satoken.version>
+        <mybatis-plus.version>3.5.4</mybatis-plus.version>
+        <p6spy.version>3.9.1</p6spy.version>
+        <hutool.version>5.8.22</hutool.version>
+        <okhttp.version>4.10.0</okhttp.version>
+        <spring-boot-admin.version>2.7.11</spring-boot-admin.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.4.0</xxl-job.version>
+        <lombok.version>1.18.30</lombok.version>
+        <bouncycastle.version>1.72</bouncycastle.version>
+        <!-- 离线IP地址定位库 -->
+        <ip2region.version>2.7.0</ip2region.version>
+
+        <!-- OSS 配置 -->
+        <aws-java-sdk-s3.version>1.12.540</aws-java-sdk-s3.version>
+        <!-- SMS 配置 -->
+        <sms4j.version>2.2.0</sms4j.version>
+    </properties>
+
+    <profiles>
+        <profile>
+            <id>local</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>local</profiles.active>
+                <logging.level>info</logging.level>
+            </properties>
+        </profile>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>dev</profiles.active>
+                <logging.level>info</logging.level>
+            </properties>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>prod</id>
+            <properties>
+                <profiles.active>prod</profiles.active>
+                <logging.level>warn</logging.level>
+            </properties>
+        </profile>
+    </profiles>
+
+    <!-- 依赖声明 -->
+    <dependencyManagement>
+        <dependencies>
+
+            <!-- SpringBoot的依赖配置-->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- hutool 的依赖配置-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-bom</artifactId>
+                <version>${hutool.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-webmvc-core</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-javadoc</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.poi</groupId>
+                <artifactId>poi</artifactId>
+                <version>${poi.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.poi</groupId>
+                <artifactId>poi-ooxml</artifactId>
+                <version>${poi.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>easyexcel</artifactId>
+                <version>${easyexcel.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.apache.poi</groupId>
+                        <artifactId>poi-ooxml-schemas</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <!-- velocity代码生成使用模板 -->
+            <dependency>
+                <groupId>org.apache.velocity</groupId>
+                <artifactId>velocity-engine-core</artifactId>
+                <version>${velocity.version}</version>
+            </dependency>
+
+            <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-spring-boot-starter</artifactId>
+                <version>${satoken.version}</version>
+            </dependency>
+            <!-- Sa-Token 整合 jwt -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-jwt</artifactId>
+                <version>${satoken.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>cn.hutool</groupId>
+                        <artifactId>hutool-all</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <!-- dynamic-datasource 多数据源-->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+                <version>${dynamic-ds.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <!-- sql性能分析插件 -->
+            <dependency>
+                <groupId>p6spy</groupId>
+                <artifactId>p6spy</artifactId>
+                <version>${p6spy.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp</artifactId>
+                <version>${okhttp.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.amazonaws</groupId>
+                <artifactId>aws-java-sdk-s3</artifactId>
+                <version>${aws-java-sdk-s3.version}</version>
+            </dependency>
+
+            <!--短信sms4j-->
+            <dependency>
+                <groupId>org.dromara.sms4j</groupId>
+                <artifactId>sms4j-spring-boot-starter</artifactId>
+                <version>${sms4j.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>de.codecentric</groupId>
+                <artifactId>spring-boot-admin-starter-server</artifactId>
+                <version>${spring-boot-admin.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>de.codecentric</groupId>
+                <artifactId>spring-boot-admin-starter-client</artifactId>
+                <version>${spring-boot-admin.version}</version>
+            </dependency>
+
+            <!--redisson-->
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-boot-starter</artifactId>
+                <version>${redisson.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.redisson</groupId>
+                        <artifactId>redisson-spring-data-30</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-data-27</artifactId>
+                <version>${redisson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+                <version>${lock4j.version}</version>
+            </dependency>
+
+            <!-- xxl-job-core -->
+            <dependency>
+                <groupId>com.xuxueli</groupId>
+                <artifactId>xxl-job-core</artifactId>
+                <version>${xxl-job.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>transmittable-thread-local</artifactId>
+                <version>${alibaba-ttl.version}</version>
+            </dependency>
+
+            <!-- 离线IP地址定位库 ip2region -->
+            <dependency>
+                <groupId>org.lionsoul</groupId>
+                <artifactId>ip2region</artifactId>
+                <version>${ip2region.version}</version>
+            </dependency>
+
+            <!-- 加密包引入 -->
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15to18</artifactId>
+                <version>${bouncycastle.version}</version>
+            </dependency>
+
+            <!-- 定时任务 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-job</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 代码生成-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-generator</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 核心模块-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-framework</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 系统模块-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-system</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 通用工具-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-common</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- OSS对象存储模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-oss</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- SMS短信模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-sms</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- demo模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-demo</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+
+    <modules>
+        <module>ruoyi-admin</module>
+        <module>ruoyi-framework</module>
+        <module>ruoyi-system</module>
+        <module>ruoyi-job</module>
+        <module>ruoyi-generator</module>
+        <module>ruoyi-common</module>
+        <module>ruoyi-demo</module>
+        <module>ruoyi-extend</module>
+        <module>ruoyi-oss</module>
+        <module>ruoyi-sms</module>
+    </modules>
+    <packaging>pom</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.9.0</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>com.github.therapi</groupId>
+                            <artifactId>therapi-runtime-javadoc-scribe</artifactId>
+                            <version>0.15.0</version>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.springframework.boot</groupId>
+                            <artifactId>spring-boot-configuration-processor</artifactId>
+                            <version>${spring-boot.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+            <!-- 单元测试使用 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.22.2</version>
+                <configuration>
+                    <argLine>-Dfile.encoding=UTF-8</argLine>
+                    <!-- 根据打包环境执行对应的@Tag测试方法 -->
+                    <groups>${profiles.active}</groups>
+                    <!-- 排除标签 -->
+                    <excludedGroups>exclude</excludedGroups>
+                </configuration>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!-- 关闭过滤 -->
+                <filtering>false</filtering>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!-- 引入所有 匹配文件进行过滤 -->
+                <includes>
+                    <include>application*</include>
+                    <include>bootstrap*</include>
+                    <include>banner*</include>
+                </includes>
+                <!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 -->
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>huawei nexus</name>
+            <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>huawei nexus</name>
+            <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+</project>
+
+

+ 23 - 0
ruoyi-admin/Dockerfile

@@ -0,0 +1,23 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER Lion Li
+
+RUN mkdir -p /ruoyi/server/logs \
+    /ruoyi/server/temp \
+    /ruoyi/skywalking/agent
+
+WORKDIR /ruoyi/server
+
+ENV SERVER_PORT=8080
+
+EXPOSE ${SERVER_PORT}
+
+ADD ./target/ruoyi-admin.jar ./app.jar
+
+ENTRYPOINT ["java", \
+            "-Djava.security.egd=file:/dev/./urandom", \
+            "-Dserver.port=${SERVER_PORT}", \
+            # 应用名称 如果想区分集群节点监控 改成不同的名称即可
+#            "-Dskywalking.agent.service_name=ruoyi-server", \
+#            "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \
+            "-jar", "app.jar"]

+ 131 - 0
ruoyi-admin/pom.xml

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi-vue-plus</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>4.8.2</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>ruoyi-admin</artifactId>
+
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <!-- Oracle -->
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc8</artifactId>
+        </dependency>
+        <!-- PostgreSql -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <!-- SqlServer -->
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-framework</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-system</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-job</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-oss</artifactId>
+        </dependency>
+
+        <!-- 代码生成-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-generator</artifactId>
+        </dependency>
+
+        <!--  demo模块  -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-demo</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- skywalking 整合 logback -->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.skywalking</groupId>-->
+<!--            <artifactId>apm-toolkit-logback-1.x</artifactId>-->
+<!--            <version>${与你的agent探针版本保持一致}</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.skywalking</groupId>-->
+<!--            <artifactId>apm-toolkit-trace</artifactId>-->
+<!--            <version>${与你的agent探针版本保持一致}</version>-->
+<!--        </dependency>-->
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.2.2</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 24 - 0
ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java

@@ -0,0 +1,24 @@
+package com.ruoyi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+
+/**
+ * 启动程序
+ *
+ * @author ruoyi
+ */
+
+@SpringBootApplication
+public class RuoYiApplication {
+
+    public static void main(String[] args) {
+        System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication application = new SpringApplication(RuoYiApplication.class);
+        application.setApplicationStartup(new BufferingApplicationStartup(2048));
+        application.run(args);
+        System.out.println("(♥◠‿◠)ノ゙  RuoYi-Vue-Plus启动成功   ლ(´ڡ`ლ)゙");
+    }
+
+}

+ 18 - 0
ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java

@@ -0,0 +1,18 @@
+package com.ruoyi;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ *
+ * @author ruoyi
+ */
+public class RuoYiServletInitializer extends SpringBootServletInitializer {
+
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+        return application.sources(RuoYiApplication.class);
+    }
+
+}

+ 134 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java

@@ -0,0 +1,134 @@
+package com.ruoyi.web.controller.common;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.ruoyi.common.constant.CacheConstants;
+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.system.service.ISysConfigService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.sms4j.api.SmsBlend;
+import org.dromara.sms4j.api.entity.SmsResponse;
+import org.dromara.sms4j.core.factory.SmsFactory;
+import org.dromara.sms4j.provider.enumerate.SupplierType;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.constraints.NotBlank;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 验证码操作处理
+ *
+ * @author Lion Li
+ */
+@SaIgnore
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class CaptchaController {
+
+    private final CaptchaProperties captchaProperties;
+    private final ISysConfigService configService;
+    private final MailProperties mailProperties;
+
+    /**
+     * 短信验证码
+     *
+     * @param phonenumber 用户手机号
+     */
+    @GetMapping("/captchaSms")
+    public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
+        String key = CacheConstants.CAPTCHA_CODE_KEY + phonenumber;
+        String code = RandomUtil.randomNumbers(4);
+        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        // 验证码模板id 自行处理 (查数据库或写死均可)
+        String templateId = "";
+        LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
+        map.put("code", code);
+        SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
+        SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
+        if (!"OK".equals(smsResponse.getCode())) {
+            log.error("验证码短信发送异常 => {}", smsResponse);
+            return R.fail(smsResponse.getMessage());
+        }
+        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();
+    }
+
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/captchaImage")
+    public R<Map<String, Object>> getCode() {
+        Map<String, Object> ajax = new HashMap<>();
+        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        ajax.put("captchaEnabled", captchaEnabled);
+        if (!captchaEnabled) {
+            return R.ok(ajax);
+        }
+        // 保存验证码信息
+        String uuid = IdUtil.simpleUUID();
+        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
+        // 生成验证码
+        CaptchaType captchaType = captchaProperties.getType();
+        boolean isMath = CaptchaType.MATH == captchaType;
+        Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
+        CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
+        AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
+        captcha.setGenerator(codeGenerator);
+        captcha.createCode();
+        String code = captcha.getCode();
+        if (isMath) {
+            ExpressionParser parser = new SpelExpressionParser();
+            Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
+            code = exp.getValue(String.class);
+        }
+        RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        ajax.put("uuid", uuid);
+        ajax.put("img", captcha.getImageBase64());
+        return R.ok(ajax);
+    }
+
+}

+ 168 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java

@@ -0,0 +1,168 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.collection.CollUtil;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.CacheNames;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.utils.JsonUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.redis.CacheUtils;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.system.domain.SysCache;
+import lombok.RequiredArgsConstructor;
+import org.redisson.spring.data.connection.RedissonConnectionFactory;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 缓存监控
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/cache")
+public class CacheController {
+
+    private final RedissonConnectionFactory connectionFactory;
+
+    private final static List<SysCache> CACHES = new ArrayList<>();
+
+    static {
+        CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户"));
+        CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息"));
+        CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典"));
+        CACHES.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
+        CACHES.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
+        CACHES.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
+        CACHES.add(new SysCache(CacheNames.SYS_OSS_CONFIG, "OSS配置"));
+        CACHES.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
+    }
+
+    /**
+     * 获取缓存监控列表
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping()
+    public R<Map<String, Object>> getInfo() throws Exception {
+        RedisConnection connection = connectionFactory.getConnection();
+        Properties info = connection.info();
+        Properties commandStats = connection.info("commandstats");
+        Long dbSize = connection.dbSize();
+
+        Map<String, Object> result = new HashMap<>(3);
+        result.put("info", info);
+        result.put("dbSize", dbSize);
+
+        List<Map<String, String>> pieList = new ArrayList<>();
+        if (commandStats != null) {
+            commandStats.stringPropertyNames().forEach(key -> {
+                Map<String, String> data = new HashMap<>(2);
+                String property = commandStats.getProperty(key);
+                data.put("name", StringUtils.removeStart(key, "cmdstat_"));
+                data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
+                pieList.add(data);
+            });
+        }
+        result.put("commandStats", pieList);
+        return R.ok(result);
+    }
+
+    /**
+     * 获取缓存监控缓存名列表
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping("/getNames")
+    public R<List<SysCache>> cache() {
+        return R.ok(CACHES);
+    }
+
+    /**
+     * 获取缓存监控Key列表
+     *
+     * @param cacheName 缓存名
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping("/getKeys/{cacheName}")
+    public R<Collection<String>> getCacheKeys(@PathVariable String cacheName) {
+        Collection<String> cacheKeys = new HashSet<>(0);
+        if (isCacheNames(cacheName)) {
+            Set<Object> keys = CacheUtils.keys(cacheName);
+            if (CollUtil.isNotEmpty(keys)) {
+                cacheKeys = keys.stream().map(Object::toString).collect(Collectors.toList());
+            }
+        } else {
+            cacheKeys = RedisUtils.keys(cacheName + "*");
+        }
+        return R.ok(cacheKeys);
+    }
+
+    /**
+     * 获取缓存监控缓存值详情
+     *
+     * @param cacheName 缓存名
+     * @param cacheKey  缓存key
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping("/getValue/{cacheName}/{cacheKey}")
+    public R<SysCache> getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) {
+        Object cacheValue;
+        if (isCacheNames(cacheName)) {
+            cacheValue = CacheUtils.get(cacheName, cacheKey);
+        } else {
+            cacheValue = RedisUtils.getCacheObject(cacheKey);
+        }
+        SysCache sysCache = new SysCache(cacheName, cacheKey, JsonUtils.toJsonString(cacheValue));
+        return R.ok(sysCache);
+    }
+
+    /**
+     * 清理缓存监控缓存名
+     *
+     * @param cacheName 缓存名
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @DeleteMapping("/clearCacheName/{cacheName}")
+    public R<Void> clearCacheName(@PathVariable String cacheName) {
+        if (isCacheNames(cacheName)) {
+            CacheUtils.clear(cacheName);
+        } else {
+            RedisUtils.deleteKeys(cacheName + "*");
+        }
+        return R.ok();
+    }
+
+    /**
+     * 清理缓存监控Key
+     *
+     * @param cacheKey key名
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @DeleteMapping("/clearCacheKey/{cacheName}/{cacheKey}")
+    public R<Void> clearCacheKey(@PathVariable String cacheName, @PathVariable String cacheKey) {
+        if (isCacheNames(cacheName)) {
+            CacheUtils.evict(cacheName, cacheKey);
+        } else {
+            RedisUtils.deleteObject(cacheKey);
+        }
+        return R.ok();
+    }
+
+    /**
+     * 清理全部缓存监控
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @DeleteMapping("/clearCacheAll")
+    public R<Void> clearCacheAll() {
+        RedisUtils.deleteKeys("*");
+        return R.ok();
+    }
+
+    private boolean isCacheNames(String cacheName) {
+        return !StringUtils.contains(cacheName, ":");
+    }
+}

+ 88 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java

@@ -0,0 +1,88 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.CacheConstants;
+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.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.system.domain.SysLogininfor;
+import com.ruoyi.system.service.ISysLogininforService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 系统访问记录
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/logininfor")
+public class SysLogininforController extends BaseController {
+
+    private final ISysLogininforService logininforService;
+
+    /**
+     * 获取系统访问记录列表
+     */
+    @SaCheckPermission("monitor:logininfor:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysLogininfor> list(SysLogininfor logininfor, PageQuery pageQuery) {
+        return logininforService.selectPageLogininforList(logininfor, pageQuery);
+    }
+
+    /**
+     * 导出系统访问记录列表
+     */
+    @Log(title = "登录日志", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("monitor:logininfor:export")
+    @PostMapping("/export")
+    public void export(SysLogininfor logininfor, HttpServletResponse response) {
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        ExcelUtil.exportExcel(list, "登录日志", SysLogininfor.class, response);
+    }
+
+    /**
+     * 批量删除登录日志
+     * @param infoIds 日志ids
+     */
+    @SaCheckPermission("monitor:logininfor:remove")
+    @Log(title = "登录日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{infoIds}")
+    public R<Void> remove(@PathVariable Long[] infoIds) {
+        return toAjax(logininforService.deleteLogininforByIds(infoIds));
+    }
+
+    /**
+     * 清理系统访问记录
+     */
+    @SaCheckPermission("monitor:logininfor:remove")
+    @Log(title = "登录日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/clean")
+    public R<Void> clean() {
+        logininforService.cleanLogininfor();
+        return R.ok();
+    }
+
+    @SaCheckPermission("monitor:logininfor:unlock")
+    @Log(title = "账户解锁", businessType = BusinessType.OTHER)
+    @GetMapping("/unlock/{userName}")
+    public R<Void> unlock(@PathVariable("userName") String userName) {
+        String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName;
+        if (RedisUtils.hasKey(loginName)) {
+            RedisUtils.deleteObject(loginName);
+        }
+        return R.ok();
+    }
+
+}

+ 74 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java

@@ -0,0 +1,74 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.service.ISysOperLogService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 操作日志记录
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/operlog")
+public class SysOperlogController extends BaseController {
+
+    private final ISysOperLogService operLogService;
+
+    /**
+     * 获取操作日志记录列表
+     */
+    @SaCheckPermission("monitor:operlog:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOperLog> list(SysOperLog operLog, PageQuery pageQuery) {
+        return operLogService.selectPageOperLogList(operLog, pageQuery);
+    }
+
+    /**
+     * 导出操作日志记录列表
+     */
+    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("monitor:operlog:export")
+    @PostMapping("/export")
+    public void export(SysOperLog operLog, HttpServletResponse response) {
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        ExcelUtil.exportExcel(list, "操作日志", SysOperLog.class, response);
+    }
+
+    /**
+     * 批量删除操作日志记录
+     * @param operIds 日志ids
+     */
+    @Log(title = "操作日志", businessType = BusinessType.DELETE)
+    @SaCheckPermission("monitor:operlog:remove")
+    @DeleteMapping("/{operIds}")
+    public R<Void> remove(@PathVariable Long[] operIds) {
+        return toAjax(operLogService.deleteOperLogByIds(operIds));
+    }
+
+    /**
+     * 清理操作日志记录
+     */
+    @Log(title = "操作日志", businessType = BusinessType.CLEAN)
+    @SaCheckPermission("monitor:operlog:remove")
+    @DeleteMapping("/clean")
+    public R<Void> clean() {
+        operLogService.cleanOperLog();
+        return R.ok();
+    }
+}

+ 90 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java

@@ -0,0 +1,90 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.dto.UserOnlineDTO;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StreamUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.system.domain.SysUserOnline;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 在线用户监控
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/online")
+public class SysUserOnlineController extends BaseController {
+
+    /**
+     * 获取在线用户监控列表
+     *
+     * @param ipaddr   IP地址
+     * @param userName 用户名
+     */
+    @SaCheckPermission("monitor:online:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysUserOnline> list(String ipaddr, String userName) {
+        // 获取所有未过期的 token
+        List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
+        List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
+        for (String key : keys) {
+            String token = StringUtils.substringAfterLast(key, ":");
+            // 如果已经过期则跳过
+            if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
+                continue;
+            }
+            userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
+        }
+        if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) {
+            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+                StringUtils.equals(ipaddr, userOnline.getIpaddr()) &&
+                    StringUtils.equals(userName, userOnline.getUserName())
+            );
+        } else if (StringUtils.isNotEmpty(ipaddr)) {
+            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+                StringUtils.equals(ipaddr, userOnline.getIpaddr())
+            );
+        } else if (StringUtils.isNotEmpty(userName)) {
+            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+                StringUtils.equals(userName, userOnline.getUserName())
+            );
+        }
+        Collections.reverse(userOnlineDTOList);
+        userOnlineDTOList.removeAll(Collections.singleton(null));
+        List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
+        return TableDataInfo.build(userOnlineList);
+    }
+
+    /**
+     * 强退用户
+     *
+     * @param tokenId token值
+     */
+    @SaCheckPermission("monitor:online:forceLogout")
+    @Log(title = "在线用户", businessType = BusinessType.FORCE)
+    @DeleteMapping("/{tokenId}")
+    public R<Void> forceLogout(@PathVariable String tokenId) {
+        try {
+            StpUtil.kickoutByTokenValue(tokenId);
+        } catch (NotLoginException ignored) {
+        }
+        return R.ok();
+    }
+}

+ 137 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java

@@ -0,0 +1,137 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysConfig;
+import com.ruoyi.system.service.ISysConfigService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 参数配置 信息操作处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/config")
+public class SysConfigController extends BaseController {
+
+    private final ISysConfigService configService;
+
+    /**
+     * 获取参数配置列表
+     */
+    @SaCheckPermission("system:config:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysConfig> list(SysConfig config, PageQuery pageQuery) {
+        return configService.selectPageConfigList(config, pageQuery);
+    }
+
+    /**
+     * 导出参数配置列表
+     */
+    @Log(title = "参数管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:config:export")
+    @PostMapping("/export")
+    public void export(SysConfig config, HttpServletResponse response) {
+        List<SysConfig> list = configService.selectConfigList(config);
+        ExcelUtil.exportExcel(list, "参数数据", SysConfig.class, response);
+    }
+
+    /**
+     * 根据参数编号获取详细信息
+     *
+     * @param configId 参数ID
+     */
+    @SaCheckPermission("system:config:query")
+    @GetMapping(value = "/{configId}")
+    public R<SysConfig> getInfo(@PathVariable Long configId) {
+        return R.ok(configService.selectConfigById(configId));
+    }
+
+    /**
+     * 根据参数键名查询参数值
+     *
+     * @param configKey 参数Key
+     */
+    @GetMapping(value = "/configKey/{configKey}")
+    public R<Void> getConfigKey(@PathVariable String configKey) {
+        return R.ok(configService.selectConfigByKey(configKey));
+    }
+
+    /**
+     * 新增参数配置
+     */
+    @SaCheckPermission("system:config:add")
+    @Log(title = "参数管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysConfig config) {
+        if (!configService.checkConfigKeyUnique(config)) {
+            return R.fail("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        configService.insertConfig(config);
+        return R.ok();
+    }
+
+    /**
+     * 修改参数配置
+     */
+    @SaCheckPermission("system:config:edit")
+    @Log(title = "参数管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysConfig config) {
+        if (!configService.checkConfigKeyUnique(config)) {
+            return R.fail("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        configService.updateConfig(config);
+        return R.ok();
+    }
+
+    /**
+     * 根据参数键名修改参数配置
+     */
+    @SaCheckPermission("system:config:edit")
+    @Log(title = "参数管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateByKey")
+    public R<Void> updateByKey(@RequestBody SysConfig config) {
+        configService.updateConfig(config);
+        return R.ok();
+    }
+
+    /**
+     * 删除参数配置
+     *
+     * @param configIds 参数ID串
+     */
+    @SaCheckPermission("system:config:remove")
+    @Log(title = "参数管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{configIds}")
+    public R<Void> remove(@PathVariable Long[] configIds) {
+        configService.deleteConfigByIds(configIds);
+        return R.ok();
+    }
+
+    /**
+     * 刷新参数缓存
+     */
+    @SaCheckPermission("system:config:remove")
+    @Log(title = "参数管理", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public R<Void> refreshCache() {
+        configService.resetConfigCache();
+        return R.ok();
+    }
+}

+ 122 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java

@@ -0,0 +1,122 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.convert.Convert;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysDeptService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 部门信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dept")
+public class SysDeptController extends BaseController {
+
+    private final ISysDeptService deptService;
+
+    /**
+     * 获取部门列表
+     */
+    @SaCheckPermission("system:dept:list")
+    @GetMapping("/list")
+    public R<List<SysDept>> list(SysDept dept) {
+        List<SysDept> depts = deptService.selectDeptList(dept);
+        return R.ok(depts);
+    }
+
+    /**
+     * 查询部门列表(排除节点)
+     *
+     * @param deptId 部门ID
+     */
+    @SaCheckPermission("system:dept:list")
+    @GetMapping("/list/exclude/{deptId}")
+    public R<List<SysDept>> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) {
+        List<SysDept> depts = deptService.selectDeptList(new SysDept());
+        depts.removeIf(d -> d.getDeptId().equals(deptId)
+            || StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId)));
+        return R.ok(depts);
+    }
+
+    /**
+     * 根据部门编号获取详细信息
+     *
+     * @param deptId 部门ID
+     */
+    @SaCheckPermission("system:dept:query")
+    @GetMapping(value = "/{deptId}")
+    public R<SysDept> getInfo(@PathVariable Long deptId) {
+        deptService.checkDeptDataScope(deptId);
+        return R.ok(deptService.selectDeptById(deptId));
+    }
+
+    /**
+     * 新增部门
+     */
+    @SaCheckPermission("system:dept:add")
+    @Log(title = "部门管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysDept dept) {
+        if (!deptService.checkDeptNameUnique(dept)) {
+            return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        return toAjax(deptService.insertDept(dept));
+    }
+
+    /**
+     * 修改部门
+     */
+    @SaCheckPermission("system:dept:edit")
+    @Log(title = "部门管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysDept dept) {
+        Long deptId = dept.getDeptId();
+        deptService.checkDeptDataScope(deptId);
+        if (!deptService.checkDeptNameUnique(dept)) {
+            return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        } else if (dept.getParentId().equals(deptId)) {
+            return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
+        } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())) {
+            if (deptService.selectNormalChildrenDeptById(deptId) > 0) {
+                return R.fail("该部门包含未停用的子部门!");
+            } else if (deptService.checkDeptExistUser(deptId)) {
+                return R.fail("该部门下存在已分配用户,不能禁用!");
+            }
+        }
+        return toAjax(deptService.updateDept(dept));
+    }
+
+    /**
+     * 删除部门
+     *
+     * @param deptId 部门ID
+     */
+    @SaCheckPermission("system:dept:remove")
+    @Log(title = "部门管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{deptId}")
+    public R<Void> remove(@PathVariable Long deptId) {
+        if (deptService.hasChildByDeptId(deptId)) {
+            return R.warn("存在下级部门,不允许删除");
+        }
+        if (deptService.checkDeptExistUser(deptId)) {
+            return R.warn("部门存在用户,不允许删除");
+        }
+        deptService.checkDeptDataScope(deptId);
+        return toAjax(deptService.deleteDeptById(deptId));
+    }
+}

+ 116 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java

@@ -0,0 +1,116 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.Log;
+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.SysDictData;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictDataService;
+import com.ruoyi.system.service.ISysDictTypeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 数据字典信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dict/data")
+public class SysDictDataController extends BaseController {
+
+    private final ISysDictDataService dictDataService;
+    private final ISysDictTypeService dictTypeService;
+
+    /**
+     * 查询字典数据列表
+     */
+    @SaCheckPermission("system:dict:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysDictData> list(SysDictData dictData, PageQuery pageQuery) {
+        return dictDataService.selectPageDictDataList(dictData, pageQuery);
+    }
+
+    /**
+     * 导出字典数据列表
+     */
+    @Log(title = "字典数据", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:dict:export")
+    @PostMapping("/export")
+    public void export(SysDictData dictData, HttpServletResponse response) {
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        ExcelUtil.exportExcel(list, "字典数据", SysDictData.class, response);
+    }
+
+    /**
+     * 查询字典数据详细
+     *
+     * @param dictCode 字典code
+     */
+    @SaCheckPermission("system:dict:query")
+    @GetMapping(value = "/{dictCode}")
+    public R<SysDictData> getInfo(@PathVariable Long dictCode) {
+        return R.ok(dictDataService.selectDictDataById(dictCode));
+    }
+
+    /**
+     * 根据字典类型查询字典数据信息
+     *
+     * @param dictType 字典类型
+     */
+    @GetMapping(value = "/type/{dictType}")
+    public R<List<SysDictData>> dictType(@PathVariable String dictType) {
+        List<SysDictData> data = dictTypeService.selectDictDataByType(dictType);
+        if (ObjectUtil.isNull(data)) {
+            data = new ArrayList<>();
+        }
+        return R.ok(data);
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @SaCheckPermission("system:dict:add")
+    @Log(title = "字典数据", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysDictData dict) {
+        dictDataService.insertDictData(dict);
+        return R.ok();
+    }
+
+    /**
+     * 修改保存字典类型
+     */
+    @SaCheckPermission("system:dict:edit")
+    @Log(title = "字典数据", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysDictData dict) {
+        dictDataService.updateDictData(dict);
+        return R.ok();
+    }
+
+    /**
+     * 删除字典类型
+     *
+     * @param dictCodes 字典code串
+     */
+    @SaCheckPermission("system:dict:remove")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictCodes}")
+    public R<Void> remove(@PathVariable Long[] dictCodes) {
+        dictDataService.deleteDictDataByIds(dictCodes);
+        return R.ok();
+    }
+}

+ 125 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java

@@ -0,0 +1,125 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+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.SysDictType;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictTypeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 数据字典信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dict/type")
+public class SysDictTypeController extends BaseController {
+
+    private final ISysDictTypeService dictTypeService;
+
+    /**
+     * 查询字典类型列表
+     */
+    @SaCheckPermission("system:dict:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysDictType> list(SysDictType dictType, PageQuery pageQuery) {
+        return dictTypeService.selectPageDictTypeList(dictType, pageQuery);
+    }
+
+    /**
+     * 导出字典类型列表
+     */
+    @Log(title = "字典类型", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:dict:export")
+    @PostMapping("/export")
+    public void export(SysDictType dictType, HttpServletResponse response) {
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        ExcelUtil.exportExcel(list, "字典类型", SysDictType.class, response);
+    }
+
+    /**
+     * 查询字典类型详细
+     *
+     * @param dictId 字典ID
+     */
+    @SaCheckPermission("system:dict:query")
+    @GetMapping(value = "/{dictId}")
+    public R<SysDictType> getInfo(@PathVariable Long dictId) {
+        return R.ok(dictTypeService.selectDictTypeById(dictId));
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @SaCheckPermission("system:dict:add")
+    @Log(title = "字典类型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysDictType dict) {
+        if (!dictTypeService.checkDictTypeUnique(dict)) {
+            return R.fail("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dictTypeService.insertDictType(dict);
+        return R.ok();
+    }
+
+    /**
+     * 修改字典类型
+     */
+    @SaCheckPermission("system:dict:edit")
+    @Log(title = "字典类型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysDictType dict) {
+        if (!dictTypeService.checkDictTypeUnique(dict)) {
+            return R.fail("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dictTypeService.updateDictType(dict);
+        return R.ok();
+    }
+
+    /**
+     * 删除字典类型
+     *
+     * @param dictIds 字典ID串
+     */
+    @SaCheckPermission("system:dict:remove")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictIds}")
+    public R<Void> remove(@PathVariable Long[] dictIds) {
+        dictTypeService.deleteDictTypeByIds(dictIds);
+        return R.ok();
+    }
+
+    /**
+     * 刷新字典缓存
+     */
+    @SaCheckPermission("system:dict:remove")
+    @Log(title = "字典类型", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public R<Void> refreshCache() {
+        dictTypeService.resetDictCache();
+        return R.ok();
+    }
+
+    /**
+     * 获取字典选择框列表
+     */
+    @GetMapping("/optionselect")
+    public R<List<SysDictType>> optionselect() {
+        List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll();
+        return R.ok(dictTypes);
+    }
+}

+ 32 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java

@@ -0,0 +1,32 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.utils.StringUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 首页
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+public class SysIndexController {
+
+    /**
+     * 系统基础配置
+     */
+    private final RuoYiConfig ruoyiConfig;
+
+    /**
+     * 访问首页,提示语
+     */
+    @SaIgnore
+    @GetMapping("/")
+    public String index() {
+        return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
+    }
+}

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

@@ -0,0 +1,145 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+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;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.system.domain.vo.RouterVo;
+import com.ruoyi.system.service.ISysMenuService;
+import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.SysLoginService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.constraints.NotBlank;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 登录验证
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class SysLoginController {
+
+    private final SysLoginService loginService;
+    private final ISysMenuService menuService;
+    private final ISysUserService userService;
+
+    /**
+     * 登录方法
+     *
+     * @param loginBody 登录信息
+     * @return 结果
+     */
+    @SaIgnore
+    @PostMapping("/login")
+    public R<Map<String, Object>> login(@Validated @RequestBody LoginBody loginBody) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
+            loginBody.getUuid(),loginBody.getType());
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 短信登录
+     *
+     * @param smsLoginBody 登录信息
+     * @return 结果
+     */
+    @SaIgnore
+    @PostMapping("/smsLogin")
+    public R<Map<String, Object>> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode());
+        ajax.put(Constants.TOKEN, token);
+        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);
+    }
+
+    /**
+     * 小程序登录(示例)
+     *
+     * @param  小程序code
+     * @return 结果
+     */
+    @SaIgnore
+    @PostMapping("/xcxLogin")
+    public R<Map<String, Object>> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") LoginBody loginBody) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.xcxLogin(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
+            loginBody.getUuid());
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 退出登录
+     */
+    @SaIgnore
+    @PostMapping("/logout")
+    public R<Void> logout() {
+        loginService.logout();
+        return R.ok("退出成功");
+    }
+
+    /**
+     * 获取用户信息
+     *
+     * @return 用户信息
+     */
+    @GetMapping("getInfo")
+    public R<Map<String, Object>> getInfo() {
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        SysUser user = userService.selectUserById(loginUser.getUserId());
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("user", user);
+        ajax.put("roles", loginUser.getRolePermission());
+        ajax.put("permissions", loginUser.getMenuPermission());
+        return R.ok(ajax);
+    }
+
+    /**
+     * 获取路由信息
+     *
+     * @return 路由信息
+     */
+    @GetMapping("getRouters")
+    public R<List<RouterVo>> getRouters() {
+        Long userId = LoginHelper.getUserId();
+        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
+        return R.ok(menuService.buildMenus(menus));
+    }
+}

+ 127 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java

@@ -0,0 +1,127 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysMenuService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 菜单信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/menu")
+public class SysMenuController extends BaseController {
+
+    private final ISysMenuService menuService;
+
+    /**
+     * 获取菜单列表
+     */
+    @SaCheckPermission("system:menu:list")
+    @GetMapping("/list")
+    public R<List<SysMenu>> list(SysMenu menu) {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return R.ok(menus);
+    }
+
+    /**
+     * 根据菜单编号获取详细信息
+     *
+     * @param menuId 菜单ID
+     */
+    @SaCheckPermission("system:menu:query")
+    @GetMapping(value = "/{menuId}")
+    public R<SysMenu> getInfo(@PathVariable Long menuId) {
+        return R.ok(menuService.selectMenuById(menuId));
+    }
+
+    /**
+     * 获取菜单下拉树列表
+     */
+    @GetMapping("/treeselect")
+    public R<List<Tree<Long>>> treeselect(SysMenu menu) {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return R.ok(menuService.buildMenuTreeSelect(menus));
+    }
+
+    /**
+     * 加载对应角色菜单列表树
+     *
+     * @param roleId 角色ID
+     */
+    @GetMapping(value = "/roleMenuTreeselect/{roleId}")
+    public R<Map<String, Object>> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
+        List<SysMenu> menus = menuService.selectMenuList(getUserId());
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
+        ajax.put("menus", menuService.buildMenuTreeSelect(menus));
+        return R.ok(ajax);
+    }
+
+    /**
+     * 新增菜单
+     */
+    @SaCheckPermission("system:menu:add")
+    @Log(title = "菜单管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysMenu menu) {
+        if (!menuService.checkMenuNameUnique(menu)) {
+            return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
+            return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        }
+        return toAjax(menuService.insertMenu(menu));
+    }
+
+    /**
+     * 修改菜单
+     */
+    @SaCheckPermission("system:menu:edit")
+    @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysMenu menu) {
+        if (!menuService.checkMenuNameUnique(menu)) {
+            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
+            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        } else if (menu.getMenuId().equals(menu.getParentId())) {
+            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
+        }
+        return toAjax(menuService.updateMenu(menu));
+    }
+
+    /**
+     * 删除菜单
+     *
+     * @param menuId 菜单ID
+     */
+    @SaCheckPermission("system:menu:remove")
+    @Log(title = "菜单管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{menuId}")
+    public R<Void> remove(@PathVariable("menuId") Long menuId) {
+        if (menuService.hasChildByMenuId(menuId)) {
+            return R.warn("存在子菜单,不允许删除");
+        }
+        if (menuService.checkMenuExistRole(menuId)) {
+            return R.warn("菜单已分配,不允许删除");
+        }
+        return toAjax(menuService.deleteMenuById(menuId));
+    }
+}

+ 80 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java

@@ -0,0 +1,80 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.SysNotice;
+import com.ruoyi.system.service.ISysNoticeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 公告 信息操作处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/notice")
+public class SysNoticeController extends BaseController {
+
+    private final ISysNoticeService noticeService;
+
+    /**
+     * 获取通知公告列表
+     */
+    @SaCheckPermission("system:notice:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysNotice> list(SysNotice notice, PageQuery pageQuery) {
+        return noticeService.selectPageNoticeList(notice, pageQuery);
+    }
+
+    /**
+     * 根据通知公告编号获取详细信息
+     *
+     * @param noticeId 公告ID
+     */
+    @SaCheckPermission("system:notice:query")
+    @GetMapping(value = "/{noticeId}")
+    public R<SysNotice> getInfo(@PathVariable Long noticeId) {
+        return R.ok(noticeService.selectNoticeById(noticeId));
+    }
+
+    /**
+     * 新增通知公告
+     */
+    @SaCheckPermission("system:notice:add")
+    @Log(title = "通知公告", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysNotice notice) {
+        return toAjax(noticeService.insertNotice(notice));
+    }
+
+    /**
+     * 修改通知公告
+     */
+    @SaCheckPermission("system:notice:edit")
+    @Log(title = "通知公告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysNotice notice) {
+        return toAjax(noticeService.updateNotice(notice));
+    }
+
+    /**
+     * 删除通知公告
+     *
+     * @param noticeIds 公告ID串
+     */
+    @SaCheckPermission("system:notice:remove")
+    @Log(title = "通知公告", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{noticeIds}")
+    public R<Void> remove(@PathVariable Long[] noticeIds) {
+        return toAjax(noticeService.deleteNoticeByIds(noticeIds));
+    }
+}

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

@@ -0,0 +1,105 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.annotation.RepeatSubmit;
+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.page.TableDataInfo;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.bo.SysOssConfigBo;
+import com.ruoyi.system.domain.vo.SysOssConfigVo;
+import com.ruoyi.system.service.ISysOssConfigService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+
+/**
+ * 对象存储配置
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @date 2021-08-13
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/oss/config")
+public class SysOssConfigController extends BaseController {
+
+    private final ISysOssConfigService iSysOssConfigService;
+
+    /**
+     * 查询对象存储配置列表
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
+        return iSysOssConfigService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取对象存储配置详细信息
+     *
+     * @param ossConfigId OSS配置ID
+     */
+    @SaCheckPermission("system:oss:query")
+    @GetMapping("/{ossConfigId}")
+    public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long ossConfigId) {
+        return R.ok(iSysOssConfigService.queryById(ossConfigId));
+    }
+
+    /**
+     * 新增对象存储配置
+     */
+    @SaCheckPermission("system:oss:add")
+    @Log(title = "对象存储配置", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
+        return toAjax(iSysOssConfigService.insertByBo(bo));
+    }
+
+    /**
+     * 修改对象存储配置
+     */
+    @SaCheckPermission("system:oss:edit")
+    @Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
+        return toAjax(iSysOssConfigService.updateByBo(bo));
+    }
+
+    /**
+     * 删除对象存储配置
+     *
+     * @param ossConfigIds OSS配置ID串
+     */
+    @SaCheckPermission("system:oss:remove")
+    @Log(title = "对象存储配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ossConfigIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ossConfigIds) {
+        return toAjax(iSysOssConfigService.deleteWithValidByIds(Arrays.asList(ossConfigIds), true));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:oss:edit")
+    @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
+        return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
+    }
+}

+ 109 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java

@@ -0,0 +1,109 @@
+package com.ruoyi.web.controller.system;
+
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.bo.SysOssBo;
+import com.ruoyi.system.domain.vo.SysOssVo;
+import com.ruoyi.system.service.ISysOssService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotEmpty;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 文件上传 控制层
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/oss")
+public class SysOssController extends BaseController {
+
+    private final ISysOssService iSysOssService;
+
+    /**
+     * 查询OSS对象存储列表
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
+        return iSysOssService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 查询OSS对象基于id串
+     *
+     * @param ossIds OSS对象ID串
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/listByIds/{ossIds}")
+    public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
+                                       @PathVariable Long[] ossIds) {
+        List<SysOssVo> list = iSysOssService.listByIds(Arrays.asList(ossIds));
+        return R.ok(list);
+    }
+
+    /**
+     * 上传OSS对象存储
+     *
+     * @param file 文件
+     */
+    @SaCheckPermission("system:oss:upload")
+    @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
+        if (ObjectUtil.isNull(file)) {
+            return R.fail("上传文件不能为空");
+        }
+        SysOssVo oss = iSysOssService.upload(file);
+        Map<String, String> map = new HashMap<>(2);
+        map.put("url", oss.getUrl());
+        map.put("fileName", oss.getOriginalName());
+        map.put("ossId", oss.getOssId().toString());
+        return R.ok(map);
+    }
+
+    /**
+     * 下载OSS对象
+     *
+     * @param ossId OSS对象ID
+     */
+    @SaCheckPermission("system:oss:download")
+    @GetMapping("/download/{ossId}")
+    public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
+        iSysOssService.download(ossId,response);
+    }
+
+    /**
+     * 删除OSS对象存储
+     *
+     * @param ossIds OSS对象ID串
+     */
+    @SaCheckPermission("system:oss:remove")
+    @Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ossIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ossIds) {
+        return toAjax(iSysOssService.deleteWithValidByIds(Arrays.asList(ossIds), true));
+    }
+
+}

+ 120 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java

@@ -0,0 +1,120 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysPost;
+import com.ruoyi.system.service.ISysPostService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 岗位信息操作处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/post")
+public class SysPostController extends BaseController {
+
+    private final ISysPostService postService;
+
+    /**
+     * 获取岗位列表
+     */
+    @SaCheckPermission("system:post:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysPost> list(SysPost post, PageQuery pageQuery) {
+        return postService.selectPagePostList(post, pageQuery);
+    }
+
+    /**
+     * 导出岗位列表
+     */
+    @Log(title = "岗位管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:post:export")
+    @PostMapping("/export")
+    public void export(SysPost post, HttpServletResponse response) {
+        List<SysPost> list = postService.selectPostList(post);
+        ExcelUtil.exportExcel(list, "岗位数据", SysPost.class, response);
+    }
+
+    /**
+     * 根据岗位编号获取详细信息
+     *
+     * @param postId 岗位ID
+     */
+    @SaCheckPermission("system:post:query")
+    @GetMapping(value = "/{postId}")
+    public R<SysPost> getInfo(@PathVariable Long postId) {
+        return R.ok(postService.selectPostById(postId));
+    }
+
+    /**
+     * 新增岗位
+     */
+    @SaCheckPermission("system:post:add")
+    @Log(title = "岗位管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysPost post) {
+        if (!postService.checkPostNameUnique(post)) {
+            return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        } else if (!postService.checkPostCodeUnique(post)) {
+            return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        }
+        return toAjax(postService.insertPost(post));
+    }
+
+    /**
+     * 修改岗位
+     */
+    @SaCheckPermission("system:post:edit")
+    @Log(title = "岗位管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysPost post) {
+        if (!postService.checkPostNameUnique(post)) {
+            return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        } else if (!postService.checkPostCodeUnique(post)) {
+            return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        } else if (UserConstants.POST_DISABLE.equals(post.getStatus())
+            && postService.countUserPostById(post.getPostId()) > 0) {
+            return R.fail("该岗位下存在已分配用户,不能禁用!");
+        }
+        return toAjax(postService.updatePost(post));
+    }
+
+    /**
+     * 删除岗位
+     *
+     * @param postIds 岗位ID串
+     */
+    @SaCheckPermission("system:post:remove")
+    @Log(title = "岗位管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{postIds}")
+    public R<Void> remove(@PathVariable Long[] postIds) {
+        return toAjax(postService.deletePostByIds(postIds));
+    }
+
+    /**
+     * 获取岗位选择框列表
+     */
+    @GetMapping("/optionselect")
+    public R<List<SysPost>> optionselect() {
+        SysPost post = new SysPost();
+        post.setStatus(UserConstants.POST_NORMAL);
+        List<SysPost> posts = postService.selectPostList(post);
+        return R.ok(posts);
+    }
+}

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

@@ -0,0 +1,126 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.secure.BCrypt;
+import cn.hutool.core.io.FileUtil;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.MimeTypeUtils;
+import com.ruoyi.system.domain.SysOss;
+import com.ruoyi.system.domain.vo.SysOssVo;
+import com.ruoyi.system.service.ISysOssService;
+import com.ruoyi.system.service.ISysUserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 个人信息 业务处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/user/profile")
+public class SysProfileController extends BaseController {
+
+    private final ISysUserService userService;
+    private final ISysOssService iSysOssService;
+
+    /**
+     * 个人信息
+     */
+    @GetMapping
+    public R<Map<String, Object>> profile() {
+        SysUser user = userService.selectUserById(getUserId());
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("user", user);
+        ajax.put("roleGroup", userService.selectUserRoleGroup(user.getUserName()));
+        ajax.put("postGroup", userService.selectUserPostGroup(user.getUserName()));
+        return R.ok(ajax);
+    }
+
+    /**
+     * 修改用户
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> updateProfile(@RequestBody SysUser user) {
+        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
+        }
+        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        user.setUserId(getUserId());
+        user.setUserName(null);
+        user.setPassword(null);
+        user.setAvatar(null);
+        user.setDeptId(null);
+        if (userService.updateUserProfile(user) > 0) {
+            return R.ok();
+        }
+        return R.fail("修改个人信息异常,请联系管理员");
+    }
+
+    /**
+     * 重置密码
+     *
+     * @param newPassword 新密码
+     * @param oldPassword 旧密码
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping("/updatePwd")
+    public R<Void> updatePwd(String oldPassword, String newPassword) {
+        SysUser user = userService.selectUserById(LoginHelper.getUserId());
+        String userName = user.getUserName();
+        String password = user.getPassword();
+        if (!BCrypt.checkpw(oldPassword, password)) {
+            return R.fail("修改密码失败,旧密码错误");
+        }
+        if (BCrypt.checkpw(newPassword, password)) {
+            return R.fail("新密码不能与旧密码相同");
+        }
+
+        if (userService.resetUserPwd(userName, BCrypt.hashpw(newPassword)) > 0) {
+            return R.ok();
+        }
+        return R.fail("修改密码异常,请联系管理员");
+    }
+
+    /**
+     * 头像上传
+     *
+     * @param avatarfile 用户头像
+     */
+    @Log(title = "用户头像", businessType = BusinessType.UPDATE)
+    @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Map<String, Object>> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) {
+        Map<String, Object> ajax = new HashMap<>();
+        if (!avatarfile.isEmpty()) {
+            String extension = FileUtil.extName(avatarfile.getOriginalFilename());
+            if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
+                return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
+            }
+            SysOssVo oss = iSysOssService.upload(avatarfile);
+            String avatar = oss.getUrl();
+            if (userService.updateUserAvatar(getUsername(), avatar)) {
+                ajax.put("imgUrl", avatar);
+                return R.ok(ajax);
+            }
+        }
+        return R.fail("上传图片异常,请联系管理员");
+    }
+}

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

@@ -0,0 +1,40 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.model.RegisterBody;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.SysRegisterService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 注册验证
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class SysRegisterController extends BaseController {
+
+    private final SysRegisterService registerService;
+    private final ISysConfigService configService;
+
+    /**
+     * 用户注册
+     */
+    @SaIgnore
+    @PostMapping("/register")
+    public R<Void> register(@Validated @RequestBody RegisterBody user) {
+        if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) {
+            return R.fail("当前系统没有开启注册功能!");
+        }
+        registerService.register(user);
+        return R.ok();
+    }
+}

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

@@ -0,0 +1,228 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysUserRole;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.SysPermissionService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 角色信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/role")
+public class SysRoleController extends BaseController {
+
+    private final ISysRoleService roleService;
+    private final ISysUserService userService;
+    private final ISysDeptService deptService;
+    private final SysPermissionService permissionService;
+
+    /**
+     * 获取角色信息列表
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysRole> list(SysRole role, PageQuery pageQuery) {
+        return roleService.selectPageRoleList(role, pageQuery);
+    }
+
+    /**
+     * 导出角色信息列表
+     */
+    @Log(title = "角色管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:role:export")
+    @PostMapping("/export")
+    public void export(SysRole role, HttpServletResponse response) {
+        List<SysRole> list = roleService.selectRoleList(role);
+        ExcelUtil.exportExcel(list, "角色数据", SysRole.class, response);
+    }
+
+    /**
+     * 根据角色编号获取详细信息
+     *
+     * @param roleId 角色ID
+     */
+    @SaCheckPermission("system:role:query")
+    @GetMapping(value = "/{roleId}")
+    public R<SysRole> getInfo(@PathVariable Long roleId) {
+        roleService.checkRoleDataScope(roleId);
+        return R.ok(roleService.selectRoleById(roleId));
+    }
+
+    /**
+     * 新增角色
+     */
+    @SaCheckPermission("system:role:add")
+    @Log(title = "角色管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        if (!roleService.checkRoleNameUnique(role)) {
+            return R.fail("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        } else if (!roleService.checkRoleKeyUnique(role)) {
+            return R.fail("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+        return toAjax(roleService.insertRole(role));
+
+    }
+
+    /**
+     * 修改保存角色
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        if (!roleService.checkRoleNameUnique(role)) {
+            return R.fail("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        } else if (!roleService.checkRoleKeyUnique(role)) {
+            return R.fail("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+
+        if (roleService.updateRole(role) > 0) {
+            roleService.cleanOnlineUserByRole(role.getRoleId());
+            return R.ok();
+        }
+        return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
+    }
+
+    /**
+     * 修改保存数据权限
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/dataScope")
+    public R<Void> dataScope(@RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        return toAjax(roleService.authDataScope(role));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        return toAjax(roleService.updateRoleStatus(role));
+    }
+
+    /**
+     * 删除角色
+     *
+     * @param roleIds 角色ID串
+     */
+    @SaCheckPermission("system:role:remove")
+    @Log(title = "角色管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{roleIds}")
+    public R<Void> remove(@PathVariable Long[] roleIds) {
+        return toAjax(roleService.deleteRoleByIds(roleIds));
+    }
+
+    /**
+     * 获取角色选择框列表
+     */
+    @SaCheckPermission("system:role:query")
+    @GetMapping("/optionselect")
+    public R<List<SysRole>> optionselect() {
+        return R.ok(roleService.selectRoleAll());
+    }
+
+    /**
+     * 查询已分配用户角色列表
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping("/authUser/allocatedList")
+    public TableDataInfo<SysUser> allocatedList(SysUser user, PageQuery pageQuery) {
+        return userService.selectAllocatedList(user, pageQuery);
+    }
+
+    /**
+     * 查询未分配用户角色列表
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping("/authUser/unallocatedList")
+    public TableDataInfo<SysUser> unallocatedList(SysUser user, PageQuery pageQuery) {
+        return userService.selectUnallocatedList(user, pageQuery);
+    }
+
+    /**
+     * 取消授权用户
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancel")
+    public R<Void> cancelAuthUser(@RequestBody SysUserRole userRole) {
+        return toAjax(roleService.deleteAuthUser(userRole));
+    }
+
+    /**
+     * 批量取消授权用户
+     *
+     * @param roleId  角色ID
+     * @param userIds 用户ID串
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancelAll")
+    public R<Void> cancelAuthUserAll(Long roleId, Long[] userIds) {
+        return toAjax(roleService.deleteAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 批量选择用户授权
+     *
+     * @param roleId  角色ID
+     * @param userIds 用户ID串
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/selectAll")
+    public R<Void> selectAuthUserAll(Long roleId, Long[] userIds) {
+        roleService.checkRoleDataScope(roleId);
+        return toAjax(roleService.insertAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 获取对应角色部门树列表
+     *
+     * @param roleId 角色ID
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping(value = "/deptTree/{roleId}")
+    public R<Map<String, Object>> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
+        ajax.put("depts", deptService.selectDeptTreeList(new SysDept()));
+        return R.ok(ajax);
+    }
+}

+ 256 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java

@@ -0,0 +1,256 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.secure.BCrypt;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.excel.ExcelResult;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.common.utils.StreamUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysPost;
+import com.ruoyi.system.domain.vo.SysUserExportVo;
+import com.ruoyi.system.domain.vo.SysUserImportVo;
+import com.ruoyi.system.listener.SysUserImportListener;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysPostService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 用户信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/user")
+public class SysUserController extends BaseController {
+
+    private final ISysUserService userService;
+    private final ISysRoleService roleService;
+    private final ISysPostService postService;
+    private final ISysDeptService deptService;
+
+    /**
+     * 获取用户列表
+     */
+    @SaCheckPermission("system:user:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysUser> list(SysUser user, PageQuery pageQuery) {
+        return userService.selectPageUserList(user, pageQuery);
+    }
+
+    /**
+     * 导出用户列表
+     */
+    @Log(title = "用户管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:user:export")
+    @PostMapping("/export")
+    public void export(SysUser user, HttpServletResponse response) {
+        List<SysUser> list = userService.selectUserList(user);
+        List<SysUserExportVo> listVo = BeanUtil.copyToList(list, SysUserExportVo.class);
+        for (int i = 0; i < list.size(); i++) {
+            SysDept dept = list.get(i).getDept();
+            SysUserExportVo vo = listVo.get(i);
+            if (ObjectUtil.isNotEmpty(dept)) {
+                vo.setDeptName(dept.getDeptName());
+                vo.setLeader(dept.getLeader());
+            }
+        }
+        ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response);
+    }
+
+    /**
+     * 导入数据
+     *
+     * @param file          导入文件
+     * @param updateSupport 是否更新已存在数据
+     */
+    @Log(title = "用户管理", businessType = BusinessType.IMPORT)
+    @SaCheckPermission("system:user:import")
+    @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
+        ExcelResult<SysUserImportVo> result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport));
+        return R.ok(result.getAnalysis());
+    }
+
+    /**
+     * 获取导入模板
+     */
+    @PostMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", SysUserImportVo.class, response);
+    }
+
+    /**
+     * 根据用户编号获取详细信息
+     *
+     * @param userId 用户ID
+     */
+    @SaCheckPermission("system:user:query")
+    @GetMapping(value = {"/", "/{userId}"})
+    public R<Map<String, Object>> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
+        userService.checkUserDataScope(userId);
+        Map<String, Object> ajax = new HashMap<>();
+        SysRole role = new SysRole();
+        role.setStatus(UserConstants.ROLE_NORMAL);
+        SysPost post = new SysPost();
+        post.setStatus(UserConstants.POST_NORMAL);
+        List<SysRole> roles = roleService.selectRoleList(role);
+        ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        ajax.put("posts", postService.selectPostList(post));
+        if (ObjectUtil.isNotNull(userId)) {
+            SysUser sysUser = userService.selectUserById(userId);
+            ajax.put("user", sysUser);
+            ajax.put("postIds", postService.selectPostListByUserId(userId));
+            ajax.put("roleIds", StreamUtils.toList(sysUser.getRoles(), SysRole::getRoleId));
+        }
+        return R.ok(ajax);
+    }
+
+    /**
+     * 新增用户
+     */
+    @SaCheckPermission("system:user:add")
+    @Log(title = "用户管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysUser user) {
+        deptService.checkDeptDataScope(user.getDeptId());
+        if (!userService.checkUserNameUnique(user)) {
+            return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
+        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+            return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
+        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+            return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        user.setPassword(BCrypt.hashpw(user.getPassword()));
+        return toAjax(userService.insertUser(user));
+    }
+
+    /**
+     * 修改用户
+     */
+    @SaCheckPermission("system:user:edit")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysUser user) {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        deptService.checkDeptDataScope(user.getDeptId());
+        if (!userService.checkUserNameUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
+        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
+        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        return toAjax(userService.updateUser(user));
+    }
+
+    /**
+     * 删除用户
+     *
+     * @param userIds 角色ID串
+     */
+    @SaCheckPermission("system:user:remove")
+    @Log(title = "用户管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public R<Void> remove(@PathVariable Long[] userIds) {
+        if (ArrayUtil.contains(userIds, getUserId())) {
+            return R.fail("当前用户不能删除");
+        }
+        return toAjax(userService.deleteUserByIds(userIds));
+    }
+
+    /**
+     * 重置密码
+     */
+    @SaCheckPermission("system:user:resetPwd")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resetPwd")
+    public R<Void> resetPwd(@RequestBody SysUser user) {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        user.setPassword(BCrypt.hashpw(user.getPassword()));
+        return toAjax(userService.resetPwd(user));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:user:edit")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysUser user) {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        return toAjax(userService.updateUserStatus(user));
+    }
+
+    /**
+     * 根据用户编号获取授权角色
+     *
+     * @param userId 用户ID
+     */
+    @SaCheckPermission("system:user:query")
+    @GetMapping("/authRole/{userId}")
+    public R<Map<String, Object>> authRole(@PathVariable Long userId) {
+        SysUser user = userService.selectUserById(userId);
+        List<SysRole> roles = roleService.selectRolesByUserId(userId);
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("user", user);
+        ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        return R.ok(ajax);
+    }
+
+    /**
+     * 用户授权角色
+     *
+     * @param userId  用户Id
+     * @param roleIds 角色ID串
+     */
+    @SaCheckPermission("system:user:edit")
+    @Log(title = "用户管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authRole")
+    public R<Void> insertAuthRole(Long userId, Long[] roleIds) {
+        userService.checkUserDataScope(userId);
+        userService.insertUserAuth(userId, roleIds);
+        return R.ok();
+    }
+
+    /**
+     * 获取部门树列表
+     */
+    @SaCheckPermission("system:user:list")
+    @GetMapping("/deptTree")
+    public R<List<Tree<Long>>> deptTree(SysDept dept) {
+        return R.ok(deptService.selectDeptTreeList(dept));
+    }
+
+}

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

@@ -0,0 +1,182 @@
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: true
+  url: http://localhost:9090/admin
+  instance:
+    service-host-type: IP
+  username: ruoyi
+  password: 123456
+
+--- # xxl-job 配置
+xxl.job:
+  # 执行器开关
+  enabled: true
+  # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
+  admin-addresses: http://localhost:9100/xxl-job-admin
+  # 执行器通讯TOKEN:非空时启用
+  access-token: xxl-job
+  executor:
+    # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
+    appname: xxl-job-executor
+    # 28080 端口 随着主应用端口飘逸 避免集群冲突
+    port: 2${server.port}
+    # 执行器注册:默认IP:PORT
+    address:
+    # 执行器IP:默认自动获取IP
+    ip:
+    # 执行器运行日志文件存储磁盘路径
+    logpath: ./logs/xxl-job
+    # 执行器日志文件保存天数:大于3生效
+    logretentiondays: 30
+
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+    dynamic:
+      # 性能分析插件(有性能损耗 不建议生产环境使用)
+      p6spy: true
+      # 设置默认的数据源或者数据源组,默认值即为 master
+      primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
+      datasource:
+        # 主库数据源
+        master:
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
+          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
+          url: jdbc:mysql://192.168.10.15:63306/sj_zdsz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username: root
+          password: sooka1a2b3c4d%...
+        # 从库数据源
+        slave:
+          lazy: true
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://192.168.10.15:63306/sj_zdsz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username: root
+          password: sooka1a2b3c4d%...
+#        oracle:
+#          type: ${spring.datasource.type}
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//localhost:1521/XE
+#          username: ROOT
+#          password: root
+#        postgres:
+#          type: ${spring.datasource.type}
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          type: ${spring.datasource.type}
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
+      hikari:
+        # 最大连接池数量
+        maxPoolSize: 20
+        # 最小空闲线程数量
+        minIdle: 10
+        # 配置获取连接等待超时的时间
+        connectionTimeout: 30000
+        # 校验超时时间
+        validationTimeout: 5000
+        # 空闲连接存活最大时间,默认10分钟
+        idleTimeout: 600000
+        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+        maxLifetime: 1800000
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
+
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
+spring:
+  redis:
+    # 地址
+    host: localhost
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 0
+    # 密码(如没有密码请注释掉)
+    # password:
+    # 连接超时时间
+    timeout: 10s
+    # 是否开启ssl
+    ssl: false
+
+redisson:
+  # redis key前缀
+  keyPrefix:
+  # 线程池数量
+  threads: 4
+  # Netty线程池数量
+  nettyThreads: 8
+  # 单节点配置
+  singleServerConfig:
+    # 客户端名称
+    clientName: ${ruoyi.name}
+    # 最小空闲连接数
+    connectionMinimumIdleSize: 8
+    # 连接池大小
+    connectionPoolSize: 32
+    # 连接空闲超时,单位:毫秒
+    idleConnectionTimeout: 10000
+    # 命令等待超时,单位:毫秒
+    timeout: 3000
+    # 发布和订阅连接池大小
+    subscriptionConnectionPoolSize: 50
+
+--- # mail 邮件发送
+mail:
+  enabled: false
+  host: smtp.163.com
+  port: 465
+  # 是否需要用户名密码验证
+  auth: true
+  # 发送方,遵循RFC-822标准
+  from: xxx@163.com
+  # 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
+  user: xxx@163.com
+  # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
+  pass: xxxxxxxxxx
+  # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
+  starttlsEnable: true
+  # 使用SSL安全连接
+  sslEnable: true
+  # SMTP超时时长,单位毫秒,缺省值不超时
+  timeout: 0
+  # Socket连接超时值,单位毫秒,缺省值不超时
+  connectionTimeout: 0
+
+--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
+# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
+sms:
+  # 阿里云 dysmsapi.aliyuncs.com
+  alibaba:
+    #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
+    requestUrl: dysmsapi.aliyuncs.com
+    #阿里云的accessKey
+    accessKeyId: xxxxxxx
+    #阿里云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+  tencent:
+    #请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
+    requestUrl: sms.tencentcloudapi.com
+    #腾讯云的accessKey
+    accessKeyId: xxxxxxx
+    #腾讯云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+    #短信sdkAppId
+    sdkAppId: appid
+    #地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
+    territory: ap-guangzhou

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

@@ -0,0 +1,185 @@
+--- # 临时文件存储位置 避免临时文件被系统清理报错
+spring.servlet.multipart.location: /ruoyi/server/temp
+
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: true
+  url: http://localhost:9090/admin
+  instance:
+    service-host-type: IP
+  username: ruoyi
+  password: 123456
+
+--- # xxl-job 配置
+xxl.job:
+  # 执行器开关
+  enabled: true
+  # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
+  admin-addresses: http://localhost:9100/xxl-job-admin
+  # 执行器通讯TOKEN:非空时启用
+  access-token: xxl-job
+  executor:
+    # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
+    appname: xxl-job-executor
+    # 28080 端口 随着主应用端口飘逸 避免集群冲突
+    port: 2${server.port}
+    # 执行器注册:默认IP:PORT
+    address:
+    # 执行器IP:默认自动获取IP
+    ip:
+    # 执行器运行日志文件存储磁盘路径
+    logpath: ./logs/xxl-job
+    # 执行器日志文件保存天数:大于3生效
+    logretentiondays: 30
+
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+    dynamic:
+      # 性能分析插件(有性能损耗 不建议生产环境使用)
+      p6spy: false
+      # 设置默认的数据源或者数据源组,默认值即为 master
+      primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
+      datasource:
+        # 主库数据源
+        master:
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
+          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username: root
+          password: root
+        # 从库数据源
+        slave:
+          lazy: true
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username:
+          password:
+#        oracle:
+#          type: ${spring.datasource.type}
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//localhost:1521/XE
+#          username: ROOT
+#          password: root
+#        postgres:
+#          type: ${spring.datasource.type}
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          type: ${spring.datasource.type}
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
+      hikari:
+        # 最大连接池数量
+        maxPoolSize: 20
+        # 最小空闲线程数量
+        minIdle: 10
+        # 配置获取连接等待超时的时间
+        connectionTimeout: 30000
+        # 校验超时时间
+        validationTimeout: 5000
+        # 空闲连接存活最大时间,默认10分钟
+        idleTimeout: 600000
+        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+        maxLifetime: 1800000
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
+
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
+spring:
+  redis:
+    # 地址
+    host: localhost
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 0
+    # 密码(如没有密码请注释掉)
+    # password:
+    # 连接超时时间
+    timeout: 10s
+    # 是否开启ssl
+    ssl: false
+
+redisson:
+  # redis key前缀
+  keyPrefix:
+  # 线程池数量
+  threads: 16
+  # Netty线程池数量
+  nettyThreads: 32
+  # 单节点配置
+  singleServerConfig:
+    # 客户端名称
+    clientName: ${ruoyi.name}
+    # 最小空闲连接数
+    connectionMinimumIdleSize: 32
+    # 连接池大小
+    connectionPoolSize: 64
+    # 连接空闲超时,单位:毫秒
+    idleConnectionTimeout: 10000
+    # 命令等待超时,单位:毫秒
+    timeout: 3000
+    # 发布和订阅连接池大小
+    subscriptionConnectionPoolSize: 50
+
+--- # mail 邮件发送
+mail:
+  enabled: false
+  host: smtp.163.com
+  port: 465
+  # 是否需要用户名密码验证
+  auth: true
+  # 发送方,遵循RFC-822标准
+  from: xxx@163.com
+  # 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
+  user: xxx@163.com
+  # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
+  pass: xxxxxxxxxx
+  # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
+  starttlsEnable: true
+  # 使用SSL安全连接
+  sslEnable: true
+  # SMTP超时时长,单位毫秒,缺省值不超时
+  timeout: 0
+  # Socket连接超时值,单位毫秒,缺省值不超时
+  connectionTimeout: 0
+
+--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
+# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
+sms:
+  # 阿里云 dysmsapi.aliyuncs.com
+  alibaba:
+    #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
+    requestUrl: dysmsapi.aliyuncs.com
+    #阿里云的accessKey
+    accessKeyId: xxxxxxx
+    #阿里云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+  tencent:
+    #请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
+    requestUrl: sms.tencentcloudapi.com
+    #腾讯云的accessKey
+    accessKeyId: xxxxxxx
+    #腾讯云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+    #短信sdkAppId
+    sdkAppId: appid
+    #地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
+    territory: ap-guangzhou

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

@@ -0,0 +1,268 @@
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: RuoYi-Vue-Plus
+  # 版本
+  version: ${ruoyi-vue-plus.version}
+  # 版权年份
+  copyrightYear: 2023
+  # 缓存懒加载
+  cacheLazy: false
+
+captcha:
+  # 页面 <参数设置> 可开启关闭 验证码校验
+  # 验证码类型 math 数组计算 char 字符验证
+  type: MATH
+  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
+  category: CIRCLE
+  # 数字验证码位数
+  numberLength: 1
+  # 字符验证码长度
+  charLength: 4
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 8080
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  # undertow 配置
+  undertow:
+    # HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
+    max-http-post-size: -1
+    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
+    # 每块buffer的空间大小,越小的空间被利用越充分
+    buffer-size: 512
+    # 是否分配的直接内存
+    direct-buffers: true
+    threads:
+      # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
+      io: 8
+      # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
+      worker: 256
+
+# 日志配置
+logging:
+  level:
+    com.ruoyi: @logging.level@
+    org.springframework: warn
+  config: classpath:logback-plus.xml
+
+# 用户配置
+user:
+  password:
+    # 密码最大错误次数
+    maxRetryCount: 5
+    # 密码锁定时间(默认10分钟)
+    lockTime: 10
+
+# Spring配置
+spring:
+  application:
+    name: ${ruoyi.name}
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: i18n/messages
+  profiles:
+    active: @profiles.active@
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size: 10MB
+      # 设置总上传的文件大小
+      max-request-size: 20MB
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
+  mvc:
+    format:
+      date-time: yyyy-MM-dd HH:mm:ss
+  jackson:
+    # 日期格式化
+    date-format: yyyy-MM-dd HH:mm:ss
+    serialization:
+      # 格式化输出
+      indent_output: false
+      # 忽略无法转换的对象
+      fail_on_empty_beans: false
+    deserialization:
+      # 允许对象忽略json中不存在的属性
+      fail_on_unknown_properties: false
+
+# Sa-Token配置
+sa-token:
+  # token名称 (同时也是cookie名称)
+  token-name: Authorization
+  # token有效期 设为一天 (必定过期) 单位: 秒
+  timeout: 86400
+  # 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
+  # token最低活跃时间 (指定时间无操作就过期) 单位: 秒
+  active-timeout: 1800
+  # 允许动态设置 token 有效期
+  dynamic-active-timeout: true
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # 是否尝试从header里读取token
+  is-read-header: true
+  # 是否尝试从cookie里读取token
+  is-read-cookie: false
+  # token前缀
+  token-prefix: "Bearer"
+  # jwt秘钥
+  jwt-secret-key: abcdefghijklmnopqrstuvwxyz
+
+# security配置
+security:
+  # 排除路径
+  excludes:
+    # 静态资源
+    - /*.html
+    - /**/*.html
+    - /**/*.css
+    - /**/*.js
+    # 公共路径
+    - /favicon.ico
+    - /error
+    # swagger 文档配置
+    - /*/api-docs
+    - /*/api-docs/**
+    # actuator 监控配置
+    - /actuator
+    - /actuator/**
+
+# MyBatisPlus配置
+# https://baomidou.com/config/
+mybatis-plus:
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
+  # 例如 com.**.**.mapper
+  mapperPackage: com.ruoyi.**.mapper
+  # 对应的 XML 文件位置
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 实体扫描,多个package用逗号或者分号分隔
+  typeAliasesPackage: com.ruoyi.**.domain
+  # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
+  checkConfigLocation: false
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    mapUnderscoreToCamelCase: true
+    # MyBatis 自动映射策略
+    # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
+    autoMappingBehavior: PARTIAL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    autoMappingUnknownColumnBehavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+  global-config:
+    # 是否打印 Logo banner
+    banner: true
+    dbConfig:
+      # 主键类型
+      # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
+      idType: ASSIGN_ID
+      # 逻辑已删除值
+      logicDeleteValue: 2
+      # 逻辑未删除值
+      logicNotDeleteValue: 0
+      # 字段验证策略之 insert,在 insert 的时候的字段验证策略
+      # IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
+      insertStrategy: NOT_NULL
+      # 字段验证策略之 update,在 update 的时候的字段验证策略
+      updateStrategy: NOT_NULL
+      # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
+      where-strategy: NOT_NULL
+
+# 数据加密
+mybatis-encryptor:
+  # 是否开启加密
+  enable: false
+  # 默认加密算法
+  algorithm: BASE64
+  # 编码方式 BASE64/HEX。默认BASE64
+  encode: BASE64
+  # 安全秘钥 对称算法的秘钥 如:AES,SM4
+  password:
+  # 公私钥 非对称算法的公私钥 如:SM2,RSA
+  publicKey:
+  privateKey:
+
+springdoc:
+  api-docs:
+    # 是否开启接口文档
+    enabled: true
+#  swagger-ui:
+#    # 持久化认证数据
+#    persistAuthorization: true
+  info:
+    # 标题
+    title: '标题:${ruoyi.name}后台管理系统_接口文档'
+    # 描述
+    description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
+    # 版本
+    version: '版本号: ${ruoyi-vue-plus.version}'
+    # 作者信息
+    contact:
+      name: Lion Li
+      email: crazylionli@163.com
+      url: https://gitee.com/dromara/RuoYi-Vue-Plus
+  components:
+    # 鉴权方式配置
+    security-schemes:
+      apiKey:
+        type: APIKEY
+        in: HEADER
+        name: ${sa-token.token-name}
+  #这里定义了两个分组,可定义多个,也可以不定义
+  group-configs:
+    - group: 1.演示模块
+      packages-to-scan: com.ruoyi.demo
+    - group: 2.系统模块
+      packages-to-scan: com.ruoyi.web
+    - group: 3.代码生成模块
+      packages-to-scan: com.ruoyi.generator
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /system/notice
+  # 匹配链接
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 全局线程池相关配置
+thread-pool:
+  # 是否开启线程池
+  enabled: false
+  # 队列最大长度
+  queueCapacity: 128
+  # 线程池维护线程所允许的空闲时间
+  keepAliveSeconds: 300
+
+--- # 分布式锁 lock4j 全局配置
+lock4j:
+  # 获取分布式锁超时时间,默认为 3000 毫秒
+  acquire-timeout: 3000
+  # 分布式锁的超时时间,默认为 30 秒
+  expire: 30000
+
+--- # Actuator 监控端点的配置项
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS
+    logfile:
+      external-file: ./logs/sys-console.log

+ 8 - 0
ruoyi-admin/src/main/resources/banner.txt

@@ -0,0 +1,8 @@
+Application Version: ${ruoyi-vue-plus.version}
+Spring Boot Version: ${spring-boot.version}
+__________            _____.___.__         ____   ____                     __________.__
+\______   \__ __  ____\__  |   |__|        \   \ /   /_ __   ____          \______   \  |  __ __  ______
+ |       _/  |  \/  _ \/   |   |  |  ______ \   Y   /  |  \_/ __ \   ______ |     ___/  | |  |  \/  ___/
+ |    |   \  |  (  <_> )____   |  | /_____/  \     /|  |  /\  ___/  /_____/ |    |   |  |_|  |  /\___ \
+ |____|_  /____/ \____// ______|__|           \___/ |____/  \___  >         |____|   |____/____//____  >
+        \/             \/                                       \/                                   \/

+ 50 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -0,0 +1,50 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.not.apptopc.exists=当前登录用户为app端无权登录后台管理
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+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=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+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不能为空

+ 49 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -0,0 +1,49 @@
+#错误消息
+not.null=* Required fill in
+user.jcaptcha.error=Captcha error
+user.jcaptcha.expire=Captcha invalid
+user.not.exists=Sorry, your account: {0} does not exist
+user.password.not.match=User does not exist/Password error
+user.password.retry.limit.count=Password input error {0} times
+user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
+user.password.delete=Sorry, your account:{0} has been deleted
+user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
+role.blocked=Role disabled,please contact administrators
+user.logout.success=Exit successful
+length.not.valid=The length must be between {min} and {max} characters
+user.username.not.blank=Username cannot be blank
+user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
+user.username.length.valid=Account length must be between {min} and {max} characters
+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
+user.register.success=Register successful
+user.register.save.error=Failed to save user {0}, The registered account already exists
+user.register.error=Register failed, please contact system administrator
+user.notfound=Please login again
+user.forcelogout=The administrator is forced to exit,please login again
+user.unknown.error=Unknown error, please login again
+##文件上传消息
+upload.exceed.maxSize=The uploaded file size exceeds the limit file size!<br/>the maximum allowed file size is:{0}MB!
+upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
+##权限
+no.permission=You do not have permission to the data,please contact your administrator to add permissions [{0}]
+no.create.permission=You do not have permission to create data,please contact your administrator to add permissions [{0}]
+no.update.permission=You do not have permission to modify data,please contact your administrator to add permissions [{0}]
+no.delete.permission=You do not have permission to delete data,please contact your administrator to add permissions [{0}]
+no.export.permission=You do not have permission to export data,please contact your administrator to add permissions [{0}]
+no.view.permission=You do not have permission to view data,please contact your administrator to add permissions [{0}]
+repeat.submit.message=Repeat submit is not allowed, please try again later
+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

+ 51 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -0,0 +1,51 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.not.apptopc.exists=当前登录用户为app端无权登录后台管理
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+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=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+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-admin/src/main/resources/ip2region.xdb


+ 129 - 0
ruoyi-admin/src/main/resources/logback-plus.xml

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <property name="log.path" value="./logs"/>
+    <property name="console.log.pattern"
+              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${console.log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- 控制台输出 -->
+    <appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-console.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大 1天 -->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <!-- 系统日志输出 -->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- info异步输出 -->
+    <appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_info"/>
+    </appender>
+
+    <!-- error异步输出 -->
+    <appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_error"/>
+    </appender>
+
+    <!-- 整合 skywalking 控制台输出 tid -->
+<!--    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">-->
+<!--        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!--            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!--                <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!--            </layout>-->
+<!--            <charset>utf-8</charset>-->
+<!--        </encoder>-->
+<!--    </appender>-->
+
+    <!-- 整合 skywalking 推送采集日志 -->
+<!--    <appender name="sky_log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">-->
+<!--        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!--            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!--                <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!--            </layout>-->
+<!--            <charset>utf-8</charset>-->
+<!--        </encoder>-->
+<!--    </appender>-->
+
+    <!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="console" />
+        <appender-ref ref="async_info" />
+        <appender-ref ref="async_error" />
+        <appender-ref ref="file_console" />
+<!--        <appender-ref ref="sky_log"/>-->
+    </root>
+
+</configuration>

+ 28 - 0
ruoyi-admin/src/main/resources/spy.properties

@@ -0,0 +1,28 @@
+# p6spy 性能分析插件配置文件
+modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
+# 自定义日志打印
+logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
+#日志输出到控制台
+appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
+# 使用日志系统记录 sql
+#appender=com.p6spy.engine.spy.appender.Slf4JLogger
+# 设置 p6spy driver 代理
+#deregisterdrivers=true
+# 取消JDBC URL前缀
+useprefix=true
+# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
+excludecategories=info,debug,result,commit,resultset
+# 日期格式
+dateformat=yyyy-MM-dd HH:mm:ss
+# SQL语句打印时间格式
+databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
+# 实际驱动可多个
+#driverlist=org.h2.Driver
+# 是否开启慢SQL记录
+outagedetection=true
+# 慢SQL记录标准 2 秒
+outagedetectioninterval=2
+# 是否过滤 Log
+filter=true
+# 过滤 Log 时所排除的 sql 关键字,以逗号分隔
+exclude=SELECT 1

+ 45 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/AssertUnitTest.java

@@ -0,0 +1,45 @@
+package com.ruoyi.test;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * 断言单元测试案例
+ *
+ * @author Lion Li
+ */
+@DisplayName("断言单元测试案例")
+public class AssertUnitTest {
+
+    @DisplayName("测试 assertEquals 方法")
+    @Test
+    public void testAssertEquals() {
+        Assertions.assertEquals("666", new String("666"));
+        Assertions.assertNotEquals("666", new String("666"));
+    }
+
+    @DisplayName("测试 assertSame 方法")
+    @Test
+    public void testAssertSame() {
+        Object obj = new Object();
+        Object obj1 = obj;
+        Assertions.assertSame(obj, obj1);
+        Assertions.assertNotSame(obj, obj1);
+    }
+
+    @DisplayName("测试 assertTrue 方法")
+    @Test
+    public void testAssertTrue() {
+        Assertions.assertTrue(true);
+        Assertions.assertFalse(true);
+    }
+
+    @DisplayName("测试 assertNull 方法")
+    @Test
+    public void testAssertNull() {
+        Assertions.assertNull(null);
+        Assertions.assertNotNull(null);
+    }
+
+}

+ 70 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/DemoUnitTest.java

@@ -0,0 +1,70 @@
+package com.ruoyi.test;
+
+import com.ruoyi.common.config.RuoYiConfig;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 单元测试案例
+ *
+ * @author Lion Li
+ */
+@SpringBootTest // 此注解只能在 springboot 主包下使用 需包含 main 方法与 yml 配置文件
+@DisplayName("单元测试案例")
+public class DemoUnitTest {
+
+    @Autowired
+    private RuoYiConfig ruoYiConfig;
+
+    @DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
+    @Test
+    public void testTest() {
+        System.out.println(ruoYiConfig);
+    }
+
+    @Disabled
+    @DisplayName("测试 @Disabled 注解")
+    @Test
+    public void testDisabled() {
+        System.out.println(ruoYiConfig);
+    }
+
+    @Timeout(value = 2L, unit = TimeUnit.SECONDS)
+    @DisplayName("测试 @Timeout 注解")
+    @Test
+    public void testTimeout() throws InterruptedException {
+        Thread.sleep(3000);
+        System.out.println(ruoYiConfig);
+    }
+
+
+    @DisplayName("测试 @RepeatedTest 注解")
+    @RepeatedTest(3)
+    public void testRepeatedTest() {
+        System.out.println(666);
+    }
+
+    @BeforeAll
+    public static void testBeforeAll() {
+        System.out.println("@BeforeAll ==================");
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+    @AfterAll
+    public static void testAfterAll() {
+        System.out.println("@AfterAll ==================");
+    }
+
+}

+ 72 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/ParamUnitTest.java

@@ -0,0 +1,72 @@
+package com.ruoyi.test;
+
+import com.ruoyi.common.enums.UserType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * 带参数单元测试案例
+ *
+ * @author Lion Li
+ */
+@DisplayName("带参数单元测试案例")
+public class ParamUnitTest {
+
+    @DisplayName("测试 @ValueSource 注解")
+    @ParameterizedTest
+    @ValueSource(strings = {"t1", "t2", "t3"})
+    public void testValueSource(String str) {
+        System.out.println(str);
+    }
+
+    @DisplayName("测试 @NullSource 注解")
+    @ParameterizedTest
+    @NullSource
+    public void testNullSource(String str) {
+        System.out.println(str);
+    }
+
+    @DisplayName("测试 @EnumSource 注解")
+    @ParameterizedTest
+    @EnumSource(UserType.class)
+    public void testEnumSource(UserType type) {
+        System.out.println(type.getUserType());
+    }
+
+    @DisplayName("测试 @MethodSource 注解")
+    @ParameterizedTest
+    @MethodSource("getParam")
+    public void testMethodSource(String str) {
+        System.out.println(str);
+    }
+
+    public static Stream<String> getParam() {
+        List<String> list = new ArrayList<>();
+        list.add("t1");
+        list.add("t2");
+        list.add("t3");
+        return list.stream();
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+
+}

+ 54 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/TagUnitTest.java

@@ -0,0 +1,54 @@
+package com.ruoyi.test;
+
+import org.junit.jupiter.api.*;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 标签单元测试案例
+ *
+ * @author Lion Li
+ */
+@SpringBootTest
+@DisplayName("标签单元测试案例")
+public class TagUnitTest {
+
+    @Tag("dev")
+    @DisplayName("测试 @Tag dev")
+    @Test
+    public void testTagDev() {
+        System.out.println("dev");
+    }
+
+    @Tag("prod")
+    @DisplayName("测试 @Tag prod")
+    @Test
+    public void testTagProd() {
+        System.out.println("prod");
+    }
+
+    @Tag("local")
+    @DisplayName("测试 @Tag local")
+    @Test
+    public void testTagLocal() {
+        System.out.println("local");
+    }
+
+    @Tag("exclude")
+    @DisplayName("测试 @Tag exclude")
+    @Test
+    public void testTagExclude() {
+        System.out.println("exclude");
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+
+}

+ 170 - 0
ruoyi-common/pom.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi-vue-plus</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>4.8.2</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common</artifactId>
+
+    <description>
+        common通用工具
+    </description>
+
+    <dependencies>
+
+        <!-- Spring框架基本的核心工具 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+
+        <!-- SpringWeb模块 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+        <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+        </dependency>
+        <!-- Sa-Token 整合 jwt -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-jwt</artifactId>
+        </dependency>
+
+        <!-- 自定义验证注解 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!--常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- JSON工具类 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
+
+        <!-- yml解析器 -->
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>
+
+        <!-- servlet包 -->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <!-- dynamic-datasource 多数据源-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-captcha</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-jwt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-extra</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-webmvc-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-javadoc</artifactId>
+        </dependency>
+
+        <!--  自动生成YML配置关联JSON文件  -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+
+        <!--redisson-->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-data-27</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 加密包引入 -->
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15to18</artifactId>
+        </dependency>
+
+        <!-- 离线IP地址定位库 -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 24 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/CellMerge.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.excel.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 列单元格合并(合并列相同项)
+ *
+ * 需搭配 {@link CellMergeStrategy} 策略使用
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+	/**
+	 * col index
+	 */
+	int index() default -1;
+
+}

+ 28 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataColumn.java

@@ -0,0 +1,28 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限
+ *
+ * 一个注解只能对应一个模板
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataColumn {
+
+    /**
+     * 占位符关键字
+     */
+    String[] key() default "deptName";
+
+    /**
+     * 占位符替换值
+     */
+    String[] value() default "dept_id";
+
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataPermission.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限组
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+    DataColumn[] value();
+
+}

+ 29 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.jackson.DictDataJsonSerializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 字典数据映射注解
+ *
+ * @author itino
+ * @deprecated 建议使用通用翻译注解
+ */
+@Deprecated
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@JacksonAnnotationsInside
+@JsonSerialize(using = DictDataJsonSerializer.class)
+public @interface DictDataMapper {
+
+    /**
+     * 设置字典的type值 (如: sys_user_sex)
+     */
+    String dictType() default "";
+}

+ 44 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java

@@ -0,0 +1,44 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+import java.lang.annotation.*;
+
+/**
+ * 字段加密注解
+ *
+ * @author 老马
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptField {
+
+    /**
+     * 加密算法
+     */
+    AlgorithmType algorithm() default AlgorithmType.DEFAULT;
+
+    /**
+     * 秘钥。AES、SM4需要
+     */
+    String password() default "";
+
+    /**
+     * 公钥。RSA、SM2需要
+     */
+    String publicKey() default "";
+
+    /**
+     * 私钥。RSA、SM2需要
+     */
+    String privateKey() default "";
+
+    /**
+     * 编码方式。对加密算法为BASE64的不起作用
+     */
+    EncodeType encode() default EncodeType.DEFAULT;
+
+}

+ 32 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java

@@ -0,0 +1,32 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 字典格式化
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+    /**
+     * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+     */
+    String dictType() default "";
+
+    /**
+     * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+     */
+    String readConverterExp() default "";
+
+    /**
+     * 分隔符,读取字符串组内容
+     */
+    String separator() default StringUtils.SEPARATOR;
+
+}

+ 30 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 枚举格式化
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+    /**
+     * 字典枚举类型
+     */
+    Class<? extends Enum<?>> enumClass();
+
+    /**
+     * 字典枚举类中对应的code属性名称,默认为code
+     */
+    String codeField() default "code";
+
+    /**
+     * 字典枚举类中对应的text属性名称,默认为text
+     */
+    String textField() default "text";
+
+}

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

@@ -0,0 +1,47 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.enums.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义操作日志记录注解
+ *
+ * @author ruoyi
+ */
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log {
+    /**
+     * 模块
+     */
+    String title() default "";
+
+    /**
+     * 功能
+     */
+    BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 操作人类别
+     */
+    OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 是否保存请求的参数
+     */
+    boolean isSaveRequestData() default true;
+
+    /**
+     * 是否保存响应的参数
+     */
+    boolean isSaveResponseData() default true;
+
+    /**
+     * 排除指定的请求参数
+     */
+    String[] excludeParamNames() default {};
+
+}

+ 41 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java

@@ -0,0 +1,41 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.LimitType;
+
+import java.lang.annotation.*;
+
+/**
+ * 限流注解
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RateLimiter {
+    /**
+     * 限流key,支持使用Spring el表达式来动态获取方法上的参数值
+     * 格式类似于  #code.id #{#code}
+     */
+    String key() default "";
+
+    /**
+     * 限流时间,单位秒
+     */
+    int time() default 60;
+
+    /**
+     * 限流次数
+     */
+    int count() default 100;
+
+    /**
+     * 限流类型
+     */
+    LimitType limitType() default LimitType.DEFAULT;
+
+    /**
+     * 提示消息 支持国际化 格式为 {code}
+     */
+    String message() default "{rate.limiter.message}";
+}

+ 29 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 自定义注解防止表单重复提交
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit {
+
+    /**
+     * 间隔时间(ms),小于此时间视为重复提交
+     */
+    int interval() default 5000;
+
+    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
+
+    /**
+     * 提示消息 支持国际化 格式为 {code}
+     */
+    String message() default "{repeat.submit.message}";
+
+}

+ 24 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.enums.SensitiveStrategy;
+import com.ruoyi.common.jackson.SensitiveJsonSerializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 数据脱敏注解
+ *
+ * @author zhujie
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveJsonSerializer.class)
+public @interface Sensitive {
+    SensitiveStrategy strategy();
+}

+ 39 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.translation.handler.TranslationHandler;
+
+import java.lang.annotation.*;
+
+/**
+ * 通用翻译注解
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Documented
+@JacksonAnnotationsInside
+@JsonSerialize(using = TranslationHandler.class)
+public @interface Translation {
+
+    /**
+     * 类型 (需与实现类上的 {@link com.ruoyi.common.annotation.TranslationType} 注解type对应)
+     * <p>
+     * 默认取当前字段的值 如果设置了 @{@link Translation#mapper()} 则取映射字段的值
+     */
+    String type();
+
+    /**
+     * 映射字段 (如果不为空则取此字段的值)
+     */
+    String mapper() default "";
+
+    /**
+     * 其他条件 例如: 字典type(sys_user_sex)
+     */
+    String other() default "";
+
+}

+ 21 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 翻译类型注解 (标注到{@link com.ruoyi.common.translation.TranslationInterface} 的实现类)
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+public @interface TranslationType {
+
+    /**
+     * 类型
+     */
+    String type();
+
+}

+ 85 - 0
ruoyi-common/src/main/java/com/ruoyi/common/captcha/UnsignedMathGenerator.java

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

+ 38 - 0
ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java

@@ -0,0 +1,38 @@
+package com.ruoyi.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ *
+ * @author Lion Li
+ */
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "ruoyi")
+public class RuoYiConfig {
+
+    /**
+     * 项目名称
+     */
+    private String name;
+
+    /**
+     * 版本
+     */
+    private String version;
+
+    /**
+     * 版权年份
+     */
+    private String copyrightYear;
+
+    /**
+     * 缓存懒加载
+     */
+    private boolean cacheLazy;
+
+}

+ 44 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java

@@ -0,0 +1,44 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 缓存的key 常量
+ *
+ * @author ruoyi
+ */
+public interface CacheConstants {
+
+    /**
+     * 在线用户 redis key
+     */
+    String ONLINE_TOKEN_KEY = "online_tokens:";
+
+    /**
+     * 验证码 redis key
+     */
+    String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 参数管理 cache key
+     */
+    String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 防重提交 redis key
+     */
+    String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 限流 redis key
+     */
+    String RATE_LIMIT_KEY = "rate_limit:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+}

+ 58 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheNames.java

@@ -0,0 +1,58 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 缓存组名称常量
+ * <p>
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
+ * <p>
+ * ttl 过期时间 如果设置为0则不过期 默认为0
+ * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
+ * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
+ * <p>
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
+ *
+ * @author Lion Li
+ */
+public interface CacheNames {
+
+    /**
+     * 演示案例
+     */
+    String DEMO_CACHE = "demo:cache#60s#10m#20";
+
+    /**
+     * 系统配置
+     */
+    String SYS_CONFIG = "sys_config";
+
+    /**
+     * 数据字典
+     */
+    String SYS_DICT = "sys_dict";
+
+    /**
+     * 用户账户
+     */
+    String SYS_USER_NAME = "sys_user_name#30d";
+
+    /**
+     * 部门
+     */
+    String SYS_DEPT = "sys_dept#30d";
+
+    /**
+     * OSS内容
+     */
+    String SYS_OSS = "sys_oss#30d";
+
+    /**
+     * OSS配置
+     */
+    String SYS_OSS_CONFIG = "sys_oss_config";
+
+    /**
+     * 在线用户
+     */
+    String ONLINE_TOKEN = "online_tokens";
+
+}

+ 76 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -0,0 +1,76 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public interface Constants {
+
+    /**
+     * UTF-8 字符集
+     */
+    String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    String GBK = "GBK";
+
+    /**
+     * www主域
+     */
+    String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    String TOKEN = "token";
+
+}
+

+ 193 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java

@@ -0,0 +1,193 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 代码生成通用常量
+ *
+ * @author ruoyi
+ */
+public interface GenConstants {
+    /**
+     * 单表(增删改查)
+     */
+    String TPL_CRUD = "crud";
+
+    /**
+     * 树表(增删改查)
+     */
+    String TPL_TREE = "tree";
+
+    /**
+     * 主子表(增删改查)
+     */
+    String TPL_SUB = "sub";
+
+    /**
+     * 树编码字段
+     */
+    String TREE_CODE = "treeCode";
+
+    /**
+     * 树父编码字段
+     */
+    String TREE_PARENT_CODE = "treeParentCode";
+
+    /**
+     * 树名称字段
+     */
+    String TREE_NAME = "treeName";
+
+    /**
+     * 上级菜单ID字段
+     */
+    String PARENT_MENU_ID = "parentMenuId";
+
+    /**
+     * 上级菜单名称字段
+     */
+    String PARENT_MENU_NAME = "parentMenuName";
+
+    /**
+     * 数据库字符串类型
+     */
+    String[] COLUMNTYPE_STR = {"char", "varchar", "nvarchar", "varchar2"};
+
+    /**
+     * 数据库文本类型
+     */
+    String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext"};
+
+    /**
+     * 数据库时间类型
+     */
+    String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp"};
+
+    /**
+     * 数据库数字类型
+     */
+    String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer",
+        "bit", "bigint", "float", "double", "decimal"};
+
+    /**
+     * BO对象 不需要添加字段
+     */
+    String[] COLUMNNAME_NOT_ADD = {"create_by", "create_time", "del_flag", "update_by",
+        "update_time", "version"};
+
+    /**
+     * BO对象 不需要编辑字段
+     */
+    String[] COLUMNNAME_NOT_EDIT = {"create_by", "create_time", "del_flag", "update_by",
+        "update_time", "version"};
+
+    /**
+     * VO对象 不需要返回字段
+     */
+    String[] COLUMNNAME_NOT_LIST = {"create_by", "create_time", "del_flag", "update_by",
+        "update_time", "version"};
+
+    /**
+     * BO对象 不需要查询字段
+     */
+    String[] COLUMNNAME_NOT_QUERY = {"id", "create_by", "create_time", "del_flag", "update_by",
+        "update_time", "remark", "version"};
+
+    /**
+     * Entity基类字段
+     */
+    String[] BASE_ENTITY = {"createBy", "createTime", "updateBy", "updateTime"};
+
+    /**
+     * Tree基类字段
+     */
+    String[] TREE_ENTITY = {"parentName", "parentId", "children"};
+
+    /**
+     * 文本框
+     */
+    String HTML_INPUT = "input";
+
+    /**
+     * 文本域
+     */
+    String HTML_TEXTAREA = "textarea";
+
+    /**
+     * 下拉框
+     */
+    String HTML_SELECT = "select";
+
+    /**
+     * 单选框
+     */
+    String HTML_RADIO = "radio";
+
+    /**
+     * 复选框
+     */
+    String HTML_CHECKBOX = "checkbox";
+
+    /**
+     * 日期控件
+     */
+    String HTML_DATETIME = "datetime";
+
+    /**
+     * 图片上传控件
+     */
+    String HTML_IMAGE_UPLOAD = "imageUpload";
+
+    /**
+     * 文件上传控件
+     */
+    String HTML_FILE_UPLOAD = "fileUpload";
+
+    /**
+     * 富文本控件
+     */
+    String HTML_EDITOR = "editor";
+
+    /**
+     * 字符串类型
+     */
+    String TYPE_STRING = "String";
+
+    /**
+     * 整型
+     */
+    String TYPE_INTEGER = "Integer";
+
+    /**
+     * 长整型
+     */
+    String TYPE_LONG = "Long";
+
+    /**
+     * 浮点型
+     */
+    String TYPE_DOUBLE = "Double";
+
+    /**
+     * 高精度计算类型
+     */
+    String TYPE_BIGDECIMAL = "BigDecimal";
+
+    /**
+     * 时间类型
+     */
+    String TYPE_DATE = "Date";
+
+    /**
+     * 模糊查询
+     */
+    String QUERY_LIKE = "LIKE";
+
+    /**
+     * 相等查询
+     */
+    String QUERY_EQ = "EQ";
+
+    /**
+     * 需要
+     */
+    String REQUIRE = "1";
+}

+ 93 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java

@@ -0,0 +1,93 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 返回状态码
+ *
+ * @author Lion Li
+ */
+public interface HttpStatus {
+    /**
+     * 操作成功
+     */
+    int SUCCESS = 200;
+
+    /**
+     * 对象创建成功
+     */
+    int CREATED = 201;
+
+    /**
+     * 请求已经被接受
+     */
+    int ACCEPTED = 202;
+
+    /**
+     * 操作已经执行成功,但是没有返回数据
+     */
+    int NO_CONTENT = 204;
+
+    /**
+     * 资源已被移除
+     */
+    int MOVED_PERM = 301;
+
+    /**
+     * 重定向
+     */
+    int SEE_OTHER = 303;
+
+    /**
+     * 资源没有被修改
+     */
+    int NOT_MODIFIED = 304;
+
+    /**
+     * 参数列表错误(缺少,格式不匹配)
+     */
+    int BAD_REQUEST = 400;
+
+    /**
+     * 未授权
+     */
+    int UNAUTHORIZED = 401;
+
+    /**
+     * 访问受限,授权过期
+     */
+    int FORBIDDEN = 403;
+
+    /**
+     * 资源,服务未找到
+     */
+    int NOT_FOUND = 404;
+
+    /**
+     * 不允许的http方法
+     */
+    int BAD_METHOD = 405;
+
+    /**
+     * 资源冲突,或者资源被锁
+     */
+    int CONFLICT = 409;
+
+    /**
+     * 不支持的数据,媒体类型
+     */
+    int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 系统内部错误
+     */
+    int ERROR = 500;
+
+    /**
+     * 接口未实现
+     */
+    int NOT_IMPLEMENTED = 501;
+
+    /**
+     * 系统警告消息
+     */
+    int WARN = 601;
+}

+ 30 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/TransConstant.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 翻译常量
+ *
+ * @author Lion Li
+ */
+public interface TransConstant {
+
+    /**
+     * 用户id转账号
+     */
+    String USER_ID_TO_NAME = "user_id_to_name";
+
+    /**
+     * 部门id转名称
+     */
+    String DEPT_ID_TO_NAME = "dept_id_to_name";
+
+    /**
+     * 字典type转label
+     */
+    String DICT_TYPE_TO_LABEL = "dict_type_to_label";
+
+    /**
+     * ossId转url
+     */
+    String OSS_ID_TO_URL = "oss_id_to_url";
+
+}

+ 147 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java

@@ -0,0 +1,147 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 用户常量信息
+ *
+ * @author ruoyi
+ */
+public interface UserConstants {
+
+    /**
+     * 平台内系统用户的唯一标志
+     */
+    String SYS_USER = "SYS_USER";
+
+    /**
+     * 正常状态
+     */
+    String NORMAL = "0";
+
+    /**
+     * 异常状态
+     */
+    String EXCEPTION = "1";
+
+    /**
+     * 用户正常状态
+     */
+    String USER_NORMAL = "0";
+
+    /**
+     * 用户封禁状态
+     */
+    String USER_DISABLE = "1";
+
+    /**
+     * 角色正常状态
+     */
+    String ROLE_NORMAL = "0";
+
+    /**
+     * 角色封禁状态
+     */
+    String ROLE_DISABLE = "1";
+
+    /**
+     * 部门正常状态
+     */
+    String DEPT_NORMAL = "0";
+
+    /**
+     * 部门停用状态
+     */
+    String DEPT_DISABLE = "1";
+
+    /**
+     * 岗位正常状态
+     */
+    String POST_NORMAL = "0";
+
+    /**
+     * 岗位停用状态
+     */
+    String POST_DISABLE = "1";
+
+    /**
+     * 字典正常状态
+     */
+    String DICT_NORMAL = "0";
+
+    /**
+     * 是否为系统默认(是)
+     */
+    String YES = "Y";
+
+    /**
+     * 是否菜单外链(是)
+     */
+    String YES_FRAME = "0";
+
+    /**
+     * 是否菜单外链(否)
+     */
+    String NO_FRAME = "1";
+
+    /**
+     * 菜单正常状态
+     */
+    String MENU_NORMAL = "0";
+
+    /**
+     * 菜单停用状态
+     */
+    String MENU_DISABLE = "1";
+
+    /**
+     * 菜单类型(目录)
+     */
+    String TYPE_DIR = "M";
+
+    /**
+     * 菜单类型(菜单)
+     */
+    String TYPE_MENU = "C";
+
+    /**
+     * 菜单类型(按钮)
+     */
+    String TYPE_BUTTON = "F";
+
+    /**
+     * Layout组件标识
+     */
+    String LAYOUT = "Layout";
+
+    /**
+     * ParentView组件标识
+     */
+    String PARENT_VIEW = "ParentView";
+
+    /**
+     * InnerLink组件标识
+     */
+    String INNER_LINK = "InnerLink";
+
+    /**
+     * 用户名长度限制
+     */
+    int USERNAME_MIN_LENGTH = 2;
+    int USERNAME_MAX_LENGTH = 20;
+
+    /**
+     * 密码长度限制
+     */
+    int PASSWORD_MIN_LENGTH = 5;
+    int PASSWORD_MAX_LENGTH = 20;
+
+    /**
+     * 管理员ID
+     */
+    Long ADMIN_ID = 1L;
+
+    /**
+     * 管理员角色key
+     */
+    String ADMIN_ROLE_KEY = "admin";
+
+}

+ 52 - 0
ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelBigNumberConvert.java

@@ -0,0 +1,52 @@
+package com.ruoyi.common.convert;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * 大数值转换
+ * Excel 数值长度位15位 大于15位的数值转换位字符串
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter<Long> {
+
+    @Override
+    public Class<Long> supportJavaTypeKey() {
+        return Long.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        return Convert.toLong(cellData.getData());
+    }
+
+    @Override
+    public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNotNull(object)) {
+            String str = Convert.toStr(object);
+            if (str.length() > 15) {
+                return new WriteCellData<>(str);
+            }
+        }
+        WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
+        cellData.setType(CellDataTypeEnum.NUMBER);
+        return cellData;
+    }
+
+}

+ 73 - 0
ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelDictConvert.java

@@ -0,0 +1,73 @@
+package com.ruoyi.common.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.core.service.DictService;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+
+/**
+ * 字典格式化转换处理
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelDictConvert implements Converter<Object> {
+
+    @Override
+    public Class<Object> supportJavaTypeKey() {
+        return Object.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return null;
+    }
+
+    @Override
+    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+        String type = anno.dictType();
+        String label = cellData.getStringValue();
+        String value;
+        if (StringUtils.isBlank(type)) {
+            value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
+        } else {
+            value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator());
+        }
+        return Convert.convert(contentProperty.getField().getType(), value);
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNull(object)) {
+            return new WriteCellData<>("");
+        }
+        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+        String type = anno.dictType();
+        String value = Convert.toStr(object);
+        String label;
+        if (StringUtils.isBlank(type)) {
+            label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
+        } else {
+            label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
+        }
+        return new WriteCellData<>(label);
+    }
+
+    private ExcelDictFormat getAnnotation(Field field) {
+        return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
+    }
+}

+ 97 - 0
ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java

@@ -0,0 +1,97 @@
+package com.ruoyi.common.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import com.ruoyi.common.annotation.ExcelEnumFormat;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 枚举格式化转换处理
+ *
+ * @author Liang
+ */
+@Slf4j
+public class ExcelEnumConvert implements Converter<Object> {
+
+    @Override
+    public Class<Object> supportJavaTypeKey() {
+        return Object.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return null;
+    }
+
+    @Override
+    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        cellData.checkEmpty();
+        // Excel中填入的是枚举中指定的描述
+        Object textValue = null;
+        switch (cellData.getType()) {
+            case STRING:
+            case DIRECT_STRING:
+            case RICH_TEXT_STRING:
+                textValue = cellData.getStringValue();
+                break;
+            case NUMBER:
+                textValue = cellData.getNumberValue();
+                break;
+            case BOOLEAN:
+                textValue = cellData.getBooleanValue();
+                break;
+            default:
+                throw new IllegalArgumentException("单元格类型异常!");
+        }
+        // 如果是空值
+        if (ObjectUtil.isNull(textValue)) {
+            return null;
+        }
+        Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
+        // 从Java输出至Excel是code转text
+        // 因此从Excel转Java应该将text与code对调
+        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
+        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
+        // 应该从text -> code中查找
+        Object codeValue = enumTextToCodeMap.get(textValue);
+        return Convert.convert(contentProperty.getField().getType(), codeValue);
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNull(object)) {
+            return new WriteCellData<>("");
+        }
+        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
+        String value = Convert.toStr(enumValueMap.get(object), "");
+        return new WriteCellData<>(value);
+    }
+
+    private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
+        ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
+        Map<Object, String> enumValueMap = new HashMap<>();
+        Enum<?>[] enumConstants = anno.enumClass().getEnumConstants();
+        for (Enum<?> enumConstant : enumConstants) {
+            Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
+            String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
+            enumValueMap.put(codeValue, textValue);
+        }
+        return enumValueMap;
+    }
+
+    private ExcelEnumFormat getAnnotation(Field field) {
+        return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
+    }
+}

+ 69 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java

@@ -0,0 +1,69 @@
+package com.ruoyi.common.core.controller;
+
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * web层通用数据处理
+ *
+ * @author Lion Li
+ */
+public class BaseController {
+
+    /**
+     * 响应返回结果
+     *
+     * @param rows 影响行数
+     * @return 操作结果
+     */
+    protected R<Void> toAjax(int rows) {
+        return rows > 0 ? R.ok() : R.fail();
+    }
+
+    /**
+     * 响应返回结果
+     *
+     * @param result 结果
+     * @return 操作结果
+     */
+    protected R<Void> toAjax(boolean result) {
+        return result ? R.ok() : R.fail();
+    }
+
+    /**
+     * 页面跳转
+     */
+    public String redirect(String url) {
+        return StringUtils.format("redirect:{}", url);
+    }
+
+    /**
+     * 获取用户缓存信息
+     */
+    public LoginUser getLoginUser() {
+        return LoginHelper.getLoginUser();
+    }
+
+    /**
+     * 获取登录用户id
+     */
+    public Long getUserId() {
+        return LoginHelper.getUserId();
+    }
+
+    /**
+     * 获取登录部门id
+     */
+    public Long getDeptId() {
+        return LoginHelper.getDeptId();
+    }
+
+    /**
+     * 获取登录用户名
+     */
+    public String getUsername() {
+        return LoginHelper.getUsername();
+    }
+}

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

@@ -0,0 +1,63 @@
+package com.ruoyi.common.core.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity基类
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 搜索值
+     */
+    @JsonIgnore
+    @TableField(exist = false)
+    private String searchValue;
+
+    /**
+     * 创建者
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /**
+     * 更新者
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /**
+     * 请求参数
+     */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+}

+ 112 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/PageQuery.java

@@ -0,0 +1,112 @@
+package com.ruoyi.common.core.domain;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.sql.SqlUtil;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 分页查询实体类
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class PageQuery implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 分页大小
+     */
+    private Integer pageSize;
+
+    /**
+     * 当前页数
+     */
+    private Integer pageNum;
+
+    /**
+     * 排序列
+     */
+    private String orderByColumn;
+
+    /**
+     * 排序的方向desc或者asc
+     */
+    private String isAsc;
+
+    /**
+     * 当前记录起始索引 默认值
+     */
+    public static final int DEFAULT_PAGE_NUM = 1;
+
+    /**
+     * 每页显示记录数 默认值 默认查全部
+     */
+    public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
+
+    public <T> Page<T> build() {
+        Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
+        Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
+        if (pageNum <= 0) {
+            pageNum = DEFAULT_PAGE_NUM;
+        }
+        Page<T> page = new Page<>(pageNum, pageSize);
+        List<OrderItem> orderItems = buildOrderItem();
+        if (CollUtil.isNotEmpty(orderItems)) {
+            page.addOrder(orderItems);
+        }
+        return page;
+    }
+
+    /**
+     * 构建排序
+     *
+     * 支持的用法如下:
+     * {isAsc:"asc",orderByColumn:"id"} order by id asc
+     * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
+     * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
+     * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
+     */
+    private List<OrderItem> buildOrderItem() {
+        if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
+            return null;
+        }
+        String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
+        orderBy = StringUtils.toUnderScoreCase(orderBy);
+
+        // 兼容前端排序类型
+        isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
+
+        String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
+        String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
+        if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
+            throw new ServiceException("排序参数有误");
+        }
+
+        List<OrderItem> list = new ArrayList<>();
+        // 每个字段各自排序
+        for (int i = 0; i < orderByArr.length; i++) {
+            String orderByStr = orderByArr[i];
+            String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
+            if ("asc".equals(isAscStr)) {
+                list.add(OrderItem.asc(orderByStr));
+            } else if ("desc".equals(isAscStr)) {
+                list.add(OrderItem.desc(orderByStr));
+            } else {
+                throw new ServiceException("排序参数有误");
+            }
+        }
+        return list;
+    }
+
+}

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

@@ -0,0 +1,107 @@
+package com.ruoyi.common.core.domain;
+
+import com.ruoyi.common.constant.HttpStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 响应信息主体
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class R<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 成功
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 失败
+     */
+    public static final int FAIL = 500;
+
+    private int code;
+
+    private String msg;
+
+    private T data;
+
+    public static <T> R<T> ok() {
+        return restResult(null, SUCCESS, "操作成功");
+    }
+
+    public static <T> R<T> ok(T data) {
+        return restResult(data, SUCCESS, "操作成功");
+    }
+
+    public static <T> R<T> ok(String msg) {
+        return restResult(null, SUCCESS, msg);
+    }
+
+    public static <T> R<T> ok(String msg, T data) {
+        return restResult(data, SUCCESS, msg);
+    }
+
+    public static <T> R<T> fail() {
+        return restResult(null, FAIL, "操作失败");
+    }
+
+    public static <T> R<T> fail(String msg) {
+        return restResult(null, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(T data) {
+        return restResult(data, FAIL, "操作失败");
+    }
+
+    public static <T> R<T> fail(String msg, T data) {
+        return restResult(data, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(int code, String msg) {
+        return restResult(null, code, msg);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static <T> R<T> warn(String msg) {
+        return restResult(null, HttpStatus.WARN, msg);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static <T> R<T> warn(String msg, T data) {
+        return restResult(data, HttpStatus.WARN, msg);
+    }
+
+    private static <T> R<T> restResult(T data, int code, String msg) {
+        R<T> r = new R<>();
+        r.setCode(code);
+        r.setData(data);
+        r.setMsg(msg);
+        return r;
+    }
+
+    public static <T> Boolean isError(R<T> ret) {
+        return !isSuccess(ret);
+    }
+
+    public static <T> Boolean isSuccess(R<T> ret) {
+        return R.SUCCESS == ret.getCode();
+    }
+}

+ 39 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.core.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tree基类
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TreeEntity<T> extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 父菜单名称
+     */
+    @TableField(exist = false)
+    private String parentName;
+
+    /**
+     * 父菜单ID
+     */
+    private Long parentId;
+
+    /**
+     * 子部门
+     */
+    @TableField(exist = false)
+    private List<T> children = new ArrayList<>();
+
+}

+ 38 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/RoleDTO.java

@@ -0,0 +1,38 @@
+package com.ruoyi.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 角色
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class RoleDTO implements Serializable {
+
+    /**
+     * 角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    private String roleName;
+
+    /**
+     * 角色权限
+     */
+    private String roleKey;
+
+    /**
+     * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
+     */
+    private String dataScope;
+
+}

+ 60 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/dto/UserOnlineDTO.java

@@ -0,0 +1,60 @@
+package com.ruoyi.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 当前在线会话
+ *
+ * @author ruoyi
+ */
+
+@Data
+@NoArgsConstructor
+public class UserOnlineDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 会话编号
+     */
+    private String tokenId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地址
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+}

+ 80 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java

@@ -0,0 +1,80 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.core.domain.TreeEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+/**
+ * 部门表 sys_dept
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dept")
+public class SysDept extends TreeEntity<SysDept> {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 部门ID
+     */
+    @TableId(value = "dept_id")
+    private Long deptId;
+
+    /**
+     * 部门名称
+     */
+    @NotBlank(message = "部门名称不能为空")
+    @Size(min = 0, max = 30, message = "部门名称长度不能超过{max}个字符")
+    private String deptName;
+
+    /**
+     * 显示顺序
+     */
+    @NotNull(message = "显示顺序不能为空")
+    private Integer orderNum;
+
+    /**
+     * 负责人
+     */
+    private String leader;
+
+    /**
+     * 联系电话
+     */
+    @Size(min = 0, max = 11, message = "联系电话长度不能超过{max}个字符")
+    private String phone;
+
+    /**
+     * 邮箱
+     */
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
+    private String email;
+
+    /**
+     * 部门状态:0正常,1停用
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 祖级列表
+     */
+    private String ancestors;
+
+}

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

@@ -0,0 +1,100 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+
+/**
+ * 字典数据表 sys_dict_data
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dict_data")
+@ExcelIgnoreUnannotated
+public class SysDictData extends BaseEntity {
+
+    /**
+     * 字典编码
+     */
+    @ExcelProperty(value = "字典编码")
+    @TableId(value = "dict_code")
+    private Long dictCode;
+
+    /**
+     * 字典排序
+     */
+    @ExcelProperty(value = "字典排序")
+    private Integer dictSort;
+
+    /**
+     * 字典标签
+     */
+    @ExcelProperty(value = "字典标签")
+    @NotBlank(message = "字典标签不能为空")
+    @Size(min = 0, max = 100, message = "字典标签长度不能超过{max}个字符")
+    private String dictLabel;
+
+    /**
+     * 字典键值
+     */
+    @ExcelProperty(value = "字典键值")
+    @NotBlank(message = "字典键值不能为空")
+    @Size(min = 0, max = 100, message = "字典键值长度不能超过{max}个字符")
+    private String dictValue;
+
+    /**
+     * 字典类型
+     */
+    @ExcelProperty(value = "字典类型")
+    @NotBlank(message = "字典类型不能为空")
+    @Size(min = 0, max = 100, message = "字典类型长度不能超过{max}个字符")
+    private String dictType;
+
+    /**
+     * 样式属性(其他样式扩展)
+     */
+    @Size(min = 0, max = 100, message = "样式属性长度不能超过{max}个字符")
+    private String cssClass;
+
+    /**
+     * 表格字典样式
+     */
+    private String listClass;
+
+    /**
+     * 是否默认(Y是 N否)
+     */
+    @ExcelProperty(value = "是否默认", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "sys_yes_no")
+    private String isDefault;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "sys_normal_disable")
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    public boolean getDefault() {
+        return UserConstants.YES.equals(this.isDefault);
+    }
+
+}

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

@@ -0,0 +1,65 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 字典类型表 sys_dict_type
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dict_type")
+@ExcelIgnoreUnannotated
+public class SysDictType extends BaseEntity {
+
+    /**
+     * 字典主键
+     */
+    @ExcelProperty(value = "字典主键")
+    @TableId(value = "dict_id")
+    private Long dictId;
+
+    /**
+     * 字典名称
+     */
+    @ExcelProperty(value = "字典名称")
+    @NotBlank(message = "字典名称不能为空")
+    @Size(min = 0, max = 100, message = "字典类型名称长度不能超过{max}个字符")
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    @ExcelProperty(value = "字典类型")
+    @NotBlank(message = "字典类型不能为空")
+    @Size(min = 0, max = 100, message = "字典类型类型长度不能超过{max}个字符")
+    @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
+    private String dictType;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "sys_normal_disable")
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

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

@@ -0,0 +1,104 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.ruoyi.common.core.domain.TreeEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+/**
+ * 菜单权限表 sys_menu
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_menu")
+public class SysMenu extends TreeEntity<SysMenu> {
+
+    /**
+     * 菜单ID
+     */
+    @TableId(value = "menu_id")
+    private Long menuId;
+
+    /**
+     * 菜单名称
+     */
+    @NotBlank(message = "菜单名称不能为空")
+    @Size(min = 0, max = 50, message = "菜单名称长度不能超过{max}个字符")
+    private String menuName;
+
+    /**
+     * 显示顺序
+     */
+    @NotNull(message = "显示顺序不能为空")
+    private Integer orderNum;
+
+    /**
+     * 路由地址
+     */
+    @Size(min = 0, max = 200, message = "路由地址不能超过{max}个字符")
+    private String path;
+
+    /**
+     * 组件路径
+     */
+    @Size(min = 0, max = 200, message = "组件路径不能超过{max}个字符")
+    private String component;
+
+    /**
+     * 路由参数
+     */
+    private String queryParam;
+
+    /**
+     * 是否为外链(0是 1否)
+     */
+    private String isFrame;
+
+    /**
+     * 是否缓存(0缓存 1不缓存)
+     */
+    private String isCache;
+
+    /**
+     * 类型(M目录 C菜单 F按钮)
+     */
+    @NotBlank(message = "菜单类型不能为空")
+    private String menuType;
+
+    /**
+     * 显示状态(0显示 1隐藏)
+     */
+    private String visible;
+
+    /**
+     * 菜单状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 权限字符串
+     */
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @Size(min = 0, max = 100, message = "权限标识长度不能超过{max}个字符")
+    private String perms;
+
+    /**
+     * 菜单图标
+     */
+    private String icon;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 124 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java

@@ -0,0 +1,124 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.annotation.ExcelDictFormat;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.convert.ExcelDictConvert;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+/**
+ * 角色表 sys_role
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_role")
+@ExcelIgnoreUnannotated
+public class SysRole extends BaseEntity {
+
+    /**
+     * 角色ID
+     */
+    @ExcelProperty(value = "角色序号")
+    @TableId(value = "role_id")
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    @ExcelProperty(value = "角色名称")
+    @NotBlank(message = "角色名称不能为空")
+    @Size(min = 0, max = 30, message = "角色名称长度不能超过{max}个字符")
+    private String roleName;
+
+    /**
+     * 角色权限
+     */
+    @ExcelProperty(value = "角色权限")
+    @NotBlank(message = "权限字符不能为空")
+    @Size(min = 0, max = 100, message = "权限字符长度不能超过{max}个字符")
+    private String roleKey;
+
+    /**
+     * 角色排序
+     */
+    @ExcelProperty(value = "角色排序")
+    @NotNull(message = "显示顺序不能为空")
+    private Integer roleSort;
+
+    /**
+     * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
+     */
+    @ExcelProperty(value = "数据范围", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
+    private String dataScope;
+
+    /**
+     * 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)
+     */
+    private Boolean menuCheckStrictly;
+
+    /**
+     * 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 )
+     */
+    private Boolean deptCheckStrictly;
+
+    /**
+     * 角色状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "角色状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "sys_normal_disable")
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 用户是否存在此角色标识 默认不存在
+     */
+    @TableField(exist = false)
+    private boolean flag = false;
+
+    /**
+     * 菜单组
+     */
+    @TableField(exist = false)
+    private Long[] menuIds;
+
+    /**
+     * 部门组(数据权限)
+     */
+    @TableField(exist = false)
+    private Long[] deptIds;
+
+    public SysRole(Long roleId) {
+        this.roleId = roleId;
+    }
+
+    public boolean isAdmin() {
+        return UserConstants.ADMIN_ID.equals(this.roleId);
+    }
+}

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

@@ -0,0 +1,169 @@
+package com.ruoyi.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.annotation.Sensitive;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.enums.SensitiveStrategy;
+import com.ruoyi.common.xss.Xss;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户对象 sys_user
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_user")
+public class SysUser extends BaseEntity {
+
+    /**
+     * 用户ID
+     */
+    @TableId(value = "user_id")
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户账号
+     */
+    @Xss(message = "用户账号不能包含脚本字符")
+    @NotBlank(message = "用户账号不能为空")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符")
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    @Xss(message = "用户昵称不能包含脚本字符")
+    @NotBlank(message = "用户昵称不能为空")
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
+    private String nickName;
+
+    /**
+     * 用户类型(sys_user系统用户)
+     */
+    private String userType;
+
+    /**
+     * 用户邮箱
+     */
+    @Sensitive(strategy = SensitiveStrategy.EMAIL)
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    @Sensitive(strategy = SensitiveStrategy.PHONE)
+    private String phonenumber;
+
+    /**
+     * 用户性别
+     */
+    private String sex;
+
+    /**
+     * 用户头像
+     */
+    private String avatar;
+
+    /**
+     * 密码
+     */
+    @TableField(
+        insertStrategy = FieldStrategy.NOT_EMPTY,
+        updateStrategy = FieldStrategy.NOT_EMPTY,
+        whereStrategy = FieldStrategy.NOT_EMPTY
+    )
+    private String password;
+
+    @JsonIgnore
+    @JsonProperty
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * 帐号状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 最后登录IP
+     */
+    private String loginIp;
+
+    /**
+     * 最后登录时间
+     */
+    private Date loginDate;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 部门对象
+     */
+    @TableField(exist = false)
+    private SysDept dept;
+
+    /**
+     * 角色对象
+     */
+    @TableField(exist = false)
+    private List<SysRole> roles;
+
+    /**
+     * 角色组
+     */
+    @TableField(exist = false)
+    private Long[] roleIds;
+
+    /**
+     * 岗位组
+     */
+    @TableField(exist = false)
+    private Long[] postIds;
+
+    /**
+     * 数据权限 当前角色ID
+     */
+    @TableField(exist = false)
+    private Long roleId;
+
+    public SysUser(Long userId) {
+        this.userId = userId;
+    }
+
+    public boolean isAdmin() {
+        return UserConstants.ADMIN_ID.equals(this.userId);
+    }
+
+}

+ 44 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/LogininforEvent.java

@@ -0,0 +1,44 @@
+package com.ruoyi.common.core.domain.event;
+
+import lombok.Data;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.Serializable;
+
+/**
+ * 登录事件
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LogininforEvent implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户账号
+     */
+    private String username;
+
+    /**
+     * 登录状态 0成功 1失败
+     */
+    private String status;
+
+    /**
+     * 提示消息
+     */
+    private String message;
+
+    /**
+     * 请求体
+     */
+    private HttpServletRequest request;
+
+    /**
+     * 其他参数
+     */
+    private Object[] args;
+
+}

+ 104 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/event/OperLogEvent.java

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

+ 30 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/EmailLoginBody.java

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

+ 46 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java

@@ -0,0 +1,46 @@
+package com.ruoyi.common.core.domain.model;
+
+import com.ruoyi.common.constant.UserConstants;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 用户登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LoginBody {
+
+    /**
+     * 用户名
+     */
+    @NotBlank(message = "{user.username.not.blank}")
+    @Length(min = UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @NotBlank(message = "{user.password.not.blank}")
+    @Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
+    private String password;
+
+    /**
+     * 验证码
+     */
+    private String code;
+
+    /**
+     * 唯一标识
+     */
+    private String uuid;
+    /**
+     * 登录端标识 APP ,PC
+     */
+    private String type;
+
+}

+ 116 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java

@@ -0,0 +1,116 @@
+package com.ruoyi.common.core.domain.model;
+
+import com.ruoyi.common.core.domain.dto.RoleDTO;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 部门名
+     */
+    private String deptName;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 用户类型
+     */
+    private String userType;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 菜单权限
+     */
+    private Set<String> menuPermission;
+
+    /**
+     * 角色权限
+     */
+    private Set<String> rolePermission;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 角色对象
+     */
+    private List<RoleDTO> roles;
+
+    /**
+     * 数据权限 当前角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 获取登录id
+     */
+    public String getLoginId() {
+        if (userType == null) {
+            throw new IllegalArgumentException("用户类型不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        return userType + ":" + userId;
+    }
+
+}

+ 17 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java

@@ -0,0 +1,17 @@
+package com.ruoyi.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户注册对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterBody extends LoginBody {
+
+    private String userType;
+
+}

+ 28 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java

@@ -0,0 +1,28 @@
+package com.ruoyi.common.core.domain.model;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class SmsLoginBody {
+
+    /**
+     * 手机号
+     */
+    @NotBlank(message = "{user.phonenumber.not.blank}")
+    private String phonenumber;
+
+    /**
+     * 短信code
+     */
+    @NotBlank(message = "{sms.code.not.blank}")
+    private String smsCode;
+
+}

+ 24 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/XcxLoginUser.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 小程序登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * openid
+     */
+    private String openid;
+
+}

+ 192 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/mapper/BaseMapperPlus.java

@@ -0,0 +1,192 @@
+package com.ruoyi.common.core.mapper;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.*;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import com.ruoyi.common.utils.BeanCopyUtils;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 自定义 Mapper 接口, 实现 自定义扩展
+ *
+ * @param <M> mapper 泛型
+ * @param <T> table 泛型
+ * @param <V> vo 泛型
+ * @author Lion Li
+ * @since 2021-05-13
+ */
+@SuppressWarnings("unchecked")
+public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
+
+    Log log = LogFactory.getLog(BaseMapperPlus.class);
+
+    default Class<V> currentVoClass() {
+        return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
+    }
+
+    default Class<T> currentModelClass() {
+        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
+    }
+
+    default Class<M> currentMapperClass() {
+        return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
+    }
+
+    default List<T> selectList() {
+        return this.selectList(new QueryWrapper<>());
+    }
+
+    /**
+     * 批量插入
+     */
+    default boolean insertBatch(Collection<T> entityList) {
+        return Db.saveBatch(entityList);
+    }
+
+    /**
+     * 批量更新
+     */
+    default boolean updateBatchById(Collection<T> entityList) {
+        return Db.updateBatchById(entityList);
+    }
+
+    /**
+     * 批量插入或更新
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList) {
+        return Db.saveOrUpdateBatch(entityList);
+    }
+
+    /**
+     * 批量插入(包含限制条数)
+     */
+    default boolean insertBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveBatch(entityList, batchSize);
+    }
+
+    /**
+     * 批量更新(包含限制条数)
+     */
+    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
+        return Db.updateBatchById(entityList, batchSize);
+    }
+
+    /**
+     * 批量插入或更新(包含限制条数)
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveOrUpdateBatch(entityList, batchSize);
+    }
+
+    /**
+     * 插入或更新(包含限制条数)
+     */
+    default boolean insertOrUpdate(T entity) {
+        return Db.saveOrUpdate(entity);
+    }
+
+    default V selectVoById(Serializable id) {
+        return selectVoById(id, this.currentVoClass());
+    }
+
+    /**
+     * 根据 ID 查询
+     */
+    default <C> C selectVoById(Serializable id, Class<C> voClass) {
+        T obj = this.selectById(id);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return BeanCopyUtils.copy(obj, voClass);
+    }
+
+    default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
+        return selectVoBatchIds(idList, this.currentVoClass());
+    }
+
+    /**
+     * 查询(根据ID 批量查询)
+     */
+    default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
+        List<T> list = this.selectBatchIds(idList);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return BeanCopyUtils.copyList(list, voClass);
+    }
+
+    default List<V> selectVoByMap(Map<String, Object> map) {
+        return selectVoByMap(map, this.currentVoClass());
+    }
+
+    /**
+     * 查询(根据 columnMap 条件)
+     */
+    default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
+        List<T> list = this.selectByMap(map);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return BeanCopyUtils.copyList(list, voClass);
+    }
+
+    default V selectVoOne(Wrapper<T> wrapper) {
+        return selectVoOne(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 根据 entity 条件,查询一条记录
+     */
+    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
+        T obj = this.selectOne(wrapper);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return BeanCopyUtils.copy(obj, voClass);
+    }
+
+    default List<V> selectVoList(Wrapper<T> wrapper) {
+        return selectVoList(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 根据 entity 条件,查询全部记录
+     */
+    default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
+        List<T> list = this.selectList(wrapper);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return BeanCopyUtils.copyList(list, voClass);
+    }
+
+    default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
+        return selectVoPage(page, wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 分页查询VO
+     */
+    default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
+        List<T> list = this.selectList(page, wrapper);
+        IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+        if (CollUtil.isEmpty(list)) {
+            return (P) voPage;
+        }
+        voPage.setRecords(BeanCopyUtils.copyList(list, voClass));
+        return (P) voPage;
+    }
+
+}

+ 78 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java

@@ -0,0 +1,78 @@
+package com.ruoyi.common.core.page;
+
+import cn.hutool.http.HttpStatus;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 表格分页数据对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class TableDataInfo<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 总记录数
+     */
+    private long total;
+
+    /**
+     * 列表数据
+     */
+    private List<T> rows;
+
+    /**
+     * 消息状态码
+     */
+    private int code;
+
+    /**
+     * 消息内容
+     */
+    private String msg;
+
+    /**
+     * 分页
+     *
+     * @param list  列表数据
+     * @param total 总记录数
+     */
+    public TableDataInfo(List<T> list, long total) {
+        this.rows = list;
+        this.total = total;
+    }
+
+    public static <T> TableDataInfo<T> build(IPage<T> page) {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("查询成功");
+        rspData.setRows(page.getRecords());
+        rspData.setTotal(page.getTotal());
+        return rspData;
+    }
+
+    public static <T> TableDataInfo<T> build(List<T> list) {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(list.size());
+        return rspData;
+    }
+
+    public static <T> TableDataInfo<T> build() {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("查询成功");
+        return rspData;
+    }
+
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/ConfigService.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.service;
+
+/**
+ * 通用 参数配置服务
+ *
+ * @author Lion Li
+ */
+public interface ConfigService {
+
+    /**
+     * 根据参数 key 获取参数值
+     *
+     * @param configKey 参数 key
+     * @return 参数值
+     */
+    String getConfigValue(String configKey);
+
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/DeptService.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.service;
+
+/**
+ * 通用 部门服务
+ *
+ * @author Lion Li
+ */
+public interface DeptService {
+
+    /**
+     * 通过部门ID查询部门名称
+     *
+     * @param deptIds 部门ID串逗号分隔
+     * @return 部门名称串逗号分隔
+     */
+    String selectDeptNameByIds(String deptIds);
+
+}

+ 66 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/DictService.java

@@ -0,0 +1,66 @@
+package com.ruoyi.common.core.service;
+
+import java.util.Map;
+
+/**
+ * 通用 字典服务
+ *
+ * @author Lion Li
+ */
+public interface DictService {
+
+    /**
+     * 分隔符
+     */
+    String SEPARATOR = ",";
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     *
+     * @param dictType  字典类型
+     * @param dictValue 字典值
+     * @return 字典标签
+     */
+    default String getDictLabel(String dictType, String dictValue) {
+        return getDictLabel(dictType, dictValue, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     *
+     * @param dictType  字典类型
+     * @param dictLabel 字典标签
+     * @return 字典值
+     */
+    default String getDictValue(String dictType, String dictLabel) {
+        return getDictValue(dictType, dictLabel, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     *
+     * @param dictType  字典类型
+     * @param dictValue 字典值
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    String getDictLabel(String dictType, String dictValue, String separator);
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     *
+     * @param dictType  字典类型
+     * @param dictLabel 字典标签
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    String getDictValue(String dictType, String dictLabel, String separator);
+
+    /**
+     * 获取字典下所有的字典值与标签
+     *
+     * @param dictType 字典类型
+     * @return dictValue为key,dictLabel为值组成的Map
+     */
+    Map<String, String> getAllDictByDictType(String dictType);
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/service/OssService.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.service;
+
+/**
+ * 通用 OSS服务
+ *
+ * @author Lion Li
+ */
+public interface OssService {
+
+    /**
+     * 通过ossId查询对应的url
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return url串逗号分隔
+     */
+    String selectUrlByIds(String ossIds);
+
+}

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


Неке датотеке нису приказане због велике количине промена