Pārlūkot izejas kodu

Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/wechat-mp

# Conflicts:
#	pom.xml
#	sql/mysql/ruoyi-vue-pro.sql
#	yudao-dependencies/pom.xml
#	yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
#	yudao-server/pom.xml
#	yudao-server/src/main/resources/admin-ui/static/js/4.js
#	yudao-server/src/main/resources/admin-ui/static/js/chunk-vendors.js
#	yudao-server/src/main/resources/application-local.yaml
#	yudao-ui-admin/yarn.lock
YunaiV 2 gadi atpakaļ
vecāks
revīzija
48520a456b
100 mainītis faili ar 5304 papildinājumiem un 1771 dzēšanām
  1. 4 0
      .gitignore
  2. 169 104
      README.md
  3. 9 0
      http-client.env.json
  4. 9 5
      pom.xml
  5. 266 0
      sql/mysql/optional/vue3-menu.sql
  6. 54 774
      sql/mysql/ruoyi-vue-pro.sql
  7. 85 0
      sql/optional/mall/coupon.sql
  8. 315 0
      sql/optional/mall/mall.sql
  9. 78 0
      sql/optional/mall/order.sql
  10. 357 0
      sql/optional/visualization/jimureport.mysql5.7.create.sql
  11. 211 188
      sql/oracle/ruoyi-vue-pro.sql
  12. 57 79
      sql/postgresql/ruoyi-vue-pro.sql
  13. 143 106
      sql/sqlserver/ruoyi-vue-pro.sql
  14. 121 93
      yudao-dependencies/pom.xml
  15. 22 0
      yudao-example/pom.xml
  16. 65 0
      yudao-example/yudao-sso-demo-by-code/pom.xml
  17. 13 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java
  18. 157 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
  19. 73 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java
  20. 28 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java
  21. 45 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java
  22. 59 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java
  23. 97 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java
  24. 35 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java
  25. 63 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
  26. 40 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
  27. 52 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
  28. 37 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java
  29. 66 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java
  30. 44 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java
  31. 36 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java
  32. 103 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java
  33. 28 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java
  34. 2 0
      yudao-example/yudao-sso-demo-by-code/src/main/resources/application.yaml
  35. 61 0
      yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html
  36. 159 0
      yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
  37. 65 0
      yudao-example/yudao-sso-demo-by-password/pom.xml
  38. 13 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java
  39. 127 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
  40. 73 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java
  41. 28 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java
  42. 45 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java
  43. 59 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java
  44. 97 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java
  45. 35 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java
  46. 50 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
  47. 40 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
  48. 52 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
  49. 37 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java
  50. 66 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java
  51. 44 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java
  52. 36 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java
  53. 103 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java
  54. 28 0
      yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java
  55. 2 0
      yudao-example/yudao-sso-demo-by-password/src/main/resources/application.yaml
  56. 154 0
      yudao-example/yudao-sso-demo-by-password/src/main/resources/static/index.html
  57. 74 0
      yudao-example/yudao-sso-demo-by-password/src/main/resources/static/login.html
  58. 6 3
      yudao-framework/pom.xml
  59. 5 0
      yudao-framework/yudao-common/pom.xml
  60. 11 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/CommonStatusEnum.java
  61. 2 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DocumentEnum.java
  62. 40 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
  63. 1 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java
  64. 60 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/ServerException.java
  65. 9 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java
  66. 5 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/ServiceErrorCodeRange.java
  67. 15 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java
  68. 2 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageParam.java
  69. 25 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
  70. 9 7
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java
  71. 17 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  72. 1 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/SetUtils.java
  73. 66 16
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
  74. 58 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  75. 23 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java
  76. 7 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
  77. 2 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/ObjectUtils.java
  78. 8 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java
  79. 4 5
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
  80. 0 48
      yudao-framework/yudao-spring-boot-starter-activiti/pom.xml
  81. 0 45
      yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java
  82. 0 109
      yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/core/util/ActivitiUtils.java
  83. 0 37
      yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/core/web/ActivitiWebFilter.java
  84. 0 1
      yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/package-info.java
  85. 0 2
      yudao-framework/yudao-spring-boot-starter-activiti/src/main/resources/META-INF/spring.factories
  86. 30 0
      yudao-framework/yudao-spring-boot-starter-banner/pom.xml
  87. 20 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/config/YudaoBannerAutoConfiguration.java
  88. 50 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
  89. 6 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/package-info.java
  90. 1 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  91. 0 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/resources/banner.txt
  92. 4 5
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDataPermissionAutoConfiguration.java
  93. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDeptDataPermissionAutoConfiguration.java
  94. 211 80
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java
  95. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
  96. 0 3
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring.factories
  97. 2 0
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  98. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java
  99. 205 42
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java
  100. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java

+ 4 - 0
.gitignore

@@ -45,3 +45,7 @@ nbdist/
 
 ### JRebel ###
 rebel.xml
+
+application-my.yaml
+
+/yudao-ui-app/unpackage/

+ 169 - 104
README.md

@@ -1,6 +1,4 @@
-**严肃声明:现在、未来都不会有商业版本,所有功能全部开源!**
-
-**拒绝虚假开源,售卖商业版,程序员不骗程序员!!**
+**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!**
 
 **「我喜欢写代码,乐此不疲」**  
 **「我喜欢做开源,以此为乐」**
@@ -9,6 +7,12 @@
 
 如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
 
+## 🐶 新手必读
+
+* 演示地址:<http://dashboard.yudao.iocoder.cn>
+* 启动文档:<https://doc.iocoder.cn/quick-start/>
+* 视频教程:<https://doc.iocoder.cn/video/>
+
 ## 🐯 平台简介
 
 **芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
@@ -17,41 +21,53 @@
 >
 > 😜 给项目点点 Star 吧,这对我们真的很重要!
 
-* 前端采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) ,正在支持 Vue 3 + ElementUI Plus 最新方案。
-* 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson。
+![架构图](https://static.iocoder.cn/ruoyi-vue-pro-architecture.png)
+
+* 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 
+* 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
+* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
 * 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
-* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统。
-* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能。
-* 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装。
-* 工作流使用 Activiti + Flowable,支持动态表单、在线设计流程、多种任务分配方式。
-* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。
-* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款。
-* 集成阿里云、腾讯云、云片等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务。
+* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
+* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
+* 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装
+* 工作流使用 Flowable,支持动态表单、在线设计流程、会签 / 或签、多种任务分配方式
+* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验
+* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款
+* 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务
+* 集成报表设计器,支持数据报表、图形报表、打印设计等
+
+| 项目名                  | 说明                     | 传送门                                                                                                                                 |
+|----------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
+| `ruoyi-vue-pro`      | Spring Boot 多模块        | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/ruoyi-vue-pro)     |
+| `yudao-cloud`        | Spring Cloud 微服务       | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/yudao-cloud)         |
+| `Spring-Boot-Labs`   | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/SpringBoot-Labs) |
+ | `ruoyi-vue-pro-mini` | 精简版:移除工作流、支付等模块        | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/mini)**                                                                |
+
+## 😎 开源协议
 
-| 项目名                | 说明                     | 传说门                                                                                                                                 |
-|--------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
-| `ruoyi-vue-pro`    | Spring Boot 多模块        | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/ruoyi-vue-pro)     |
-| `yudao-cloud`  | Spring Cloud 微服务       | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/yudao-cloud)         |
-| `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/SpringBoot-Labs) |
+**为什么推荐使用本项目?**
 
-## 🐶 在线体验
+① 本项目采用比 Apache 2.0 更宽松的 [MIT License](https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/LICENSE) 开源协议,个人与企业可 100% 免费使用,不用保留类作者、Copyright 信息。
 
-演示地址:<http://dashboard.yudao.iocoder.cn>
-* 账号密码:admin/admin123
+② 代码全部开源,不会像其他项目一样,只开源部分代码,让你无法了解整个项目的架构设计。[国产开源项目对比](https://www.yuque.com/xiatian-bsgny/lm0ec1/wqf8mn)
 
-文档地址:<http://www.iocoder.cn/categories/Yudao/>
-* [《如何搭建环境》](http://www.iocoder.cn/categories/Yudao/?yudao)
+![开源项目对比](https://static.iocoder.cn/project-vs.png?imageView2/2/format/webp/w/1280)
 
-> 未来会补充文档和视频,方便胖友冲冲冲!
+③ 代码整洁、架构整洁,遵循《阿里巴巴 Java 开发手册》规范,代码注释详细,57000 行 Java 代码,22000 行代码注释。
 
 ## 🐼 内置功能
 
-分成多种内置功能:
+系统内置多种多种业务功能,可以用于快速你的业务系统:
+
+![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png)
+
 * 系统功能
+* 基础设施
 * 工作流程
 * 支付系统
+* 会员中心
+* 数据报表
 * 商城系统
-* 基础设施
 
 > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
 >
@@ -73,11 +89,14 @@
 | 🚀  | 租户管理  | 配置系统租户,支持 SaaS 场景下的多租户功能        |
 | 🚀  | 租户套餐  | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限       |
 |     | 字典管理  | 对系统中经常使用的一些较为固定的数据进行维护          |
-| 🚀  | 短信管理  | 短信渠道、短息模板、短信日志,对接阿里云、云等主流短信平台  |
+| 🚀  | 短信管理  | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
 | 🚀  | 操作日志  | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
 | ⭐️  | 登录日志  | 系统登录日志记录查询,包含登录异常               |
 | 🚀  | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务     |
 |     | 通知公告  | 系统通知公告信息发布维护                    |
+| 🚀  | 敏感词   | 配置系统敏感词,支持标签分组                  |
+| 🚀  | 应用管理  | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
+| 🚀  | 地区管理  | 展示省份、城市、区镇等城市信息,支持 IP 对应城市      |
 
 ### 工作流程
 
@@ -102,17 +121,6 @@
 
 ps:核心功能已经实现,正在对接微信小程序中...
 
-### 商城系统
-
-正在开发中,大体计划如下:
-* 2022 Q2 => 完成对 <https://github.com/YunaiV/onemall> 的迁移,作为 onemall 的 Spring Boot 单体版本。
-* 2022 Q4 => 完成对 <https://github.com/YunaiV/onemall>> 的重构,作为 onemall 的 Spring Cloud 微服务版本。
-
-### 会员中心
-
-正在开发中,大体计划如下:
-* 2021 Q1 =》完成对 <https://github.com/YunaiV/onemall> 的迁移 
-
 ### 基础设施
 
 |     | 功能       | 描述                                           |
@@ -123,7 +131,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
 |     | 表单构建     | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件         |
 | 🚀  | 配置管理     | 对系统动态配置常用参数,支持 SpringBoot 加载                 |
 | ⭐️  | 定时任务     | 在线(添加、修改、删除)任务调度包含执行结果日志                     |
-| 🚀  | 文件服务     | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等      | 
+| 🚀  | 文件服务     | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等   | 
 | 🚀  | API 日志   | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题   |
 |     | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈              |
 |     | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理           |
@@ -137,97 +145,154 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 🚀  | 日志服务     | 轻量级日志中心,查看远程服务器的日志                           |
 | 🚀  | 单元测试     | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等    |
 
+### 数据报表
+
+|     | 功能       | 描述                                           |
+|-----|----------|----------------------------------------------|
+| 🚀  | 报表设计器     | 支持数据报表、图形报表、打印设计等       |
+| 🚀  | 大屏设计器     | 建设中... 拖拽式实现可视化数据大屏          |
+
+### 商城系统
+
+建设中...
+
+![功能图](http://static.iocoder.cn/mall%20%E5%8A%9F%E8%83%BD%E5%9B%BE-min.png)
+
+![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-admin-min.gif)
+
+![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-h5-min.gif)
+
+### 会员中心
+
+和「商城系统」一起开发
+
 ## 🐨 技术栈
 
-| 项目                    | 说明                 |
-|-----------------------|--------------------|
-| `yudao-dependencies`  | Maven 依赖版本管理       |
-| `yudao-framework`     | Java 框架拓展          |
-| `yudao-server`        | 管理后台 + 用户 APP 的服务端 |
-| `yudao-admin-ui`      | 管理后台的 UI 界面        |
-| `yudao-user-ui`       | 用户 APP 的 UI 界面     |
-| `yudao-module-system` | 系统功能的 Module 模块    |
-| `yudao-module-member` | 会员中心的 Module 模块    |
-| `yudao-module-infra`  | 基础设施的 Module 模块    |
-| `yudao-module-tool`   | 研发工具的 Module 模块    |
-| `yudao-module-bpm`    | 工作流程的 Module 模块    |
-| `yudao-module-pay`    | 支付系统的 Module 模块    |
+| 项目                           | 说明                 |
+|------------------------------|--------------------|
+| `yudao-dependencies`         | Maven 依赖版本管理       |
+| `yudao-framework`            | Java 框架拓展          |
+| `yudao-server`               | 管理后台 + 用户 APP 的服务端 |
+| `yudao-ui-admin`             | 管理后台的 Vue2 前端项目    |
+| `yudao-ui-admin-vue3`        | 管理后台的 Vue3 前端项目    |
+| `yudao-ui-admin-uniapp`      | 管理后台的 uni-app 多端项目 |
+| `yudao-ui-app`               | 用户 APP 的 UI 界面     |
+| `yudao-module-system`        | 系统功能的 Module 模块    |
+| `yudao-module-member`        | 会员中心的 Module 模块    |
+| `yudao-module-infra`         | 基础设施的 Module 模块    |
+| `yudao-module-bpm`           | 工作流程的 Module 模块    |
+| `yudao-module-pay`           | 支付系统的 Module 模块    |
+| `yudao-module-visualization` | 大屏报表 Module 模块     |
 
 ### 后端
 
-| 框架                                                                                          | 说明               | 版本       | 学习指南                                                           |
-|---------------------------------------------------------------------------------------------|------------------|----------|----------------------------------------------------------------|
-| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架           | 2.5.12   | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
-| [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器           | 5.7      |                                                                |
-| [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件    | 1.2.8    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
-| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包    | 3.5.1    | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
-| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源            | 3.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
-| [Redis](https://redis.io/)                                                                  | key-value 数据库    | 5.0      |                                                                |
-| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端        | 3.16.8   | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
-| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架           | 5.3.16   | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao)               |
-| [Spring Security](https://github.com/spring-projects/spring-security)                       | Spring 安全框架      | 5.5.5    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
-| [Hibernate Validator](https://github.com/hibernate/hibernate-validator)                     | 参数校验组件           | 6.2.2    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao)      |
-| [Activiti](https://github.com/Activiti/Activiti)                                            | 工作流引擎            | 7.1.0.M6 | [文档](TODO)                                                     |
-| [Quartz](https://github.com/quartz-scheduler)                                               | 任务调度组件           | 2.3.2    | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao)             |
-| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现 | 3.0.2    | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
-| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件           | 1.7.0    | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
-| [SkyWalking](https://skywalking.apache.org/)                                                | 分布式应用追踪系统        | 8.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao)      |
-| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台 | 2.4.2    | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
-| [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库         | 2.12.6   |                                                                |
-| [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换     | 1.4.1    | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
-| [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码    | 1.16.14  | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
-| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架      | 5.7.2    | -                                                              |
-| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架     | 3.9.0    | -                                                              |
-
-### 前端
+| 框架                                                                                          | 说明               | 版本          | 学习指南                                                           |
+|---------------------------------------------------------------------------------------------|------------------|-------------|----------------------------------------------------------------|
+| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架           | 2.7.6       | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
+| [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器           | 5.7 / 8.0+  |                                                                |
+| [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件    | 1.2.15      | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
+| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包    | 3.5.2       | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
+| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源            | 3.6.0       | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
+| [Redis](https://redis.io/)                                                                  | key-value 数据库    | 5.0 / 6.0   |                                                                |
+| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端        | 3.18.0      | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
+| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架           | 5.3.24      | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao)               |
+| [Spring Security](https://github.com/spring-projects/spring-security)                       | Spring 安全框架      | 5.7.5       | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
+| [Hibernate Validator](https://github.com/hibernate/hibernate-validator)                     | 参数校验组件           | 6.2.5       | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao)      |
+| [Flowable](https://github.com/flowable/flowable-engine)                                     | 工作流引擎            | 6.7.2       | [文档](https://doc.iocoder.cn/bpm/)                              |
+| [Quartz](https://github.com/quartz-scheduler)                                               | 任务调度组件           | 2.3.2       | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao)             |
+| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现 | 3.0.3       | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
+| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件           | 1.7.1       | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
+| [SkyWalking](https://skywalking.apache.org/)                                                | 分布式应用追踪系统        | 8.12.0      | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao)      |
+| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台 | 2.7.9       | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
+| [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库         | 2.13.3      |                                                                |
+| [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换     | 1.5.3.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
+| [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码    | 1.18.24     | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
+| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架      | 5.8.2       | -                                                              |
+| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架     | 4.8.0       | -                                                              |
+
+### [管理后台 Vue2 前端](./yudao-ui-admin)
 
 | 框架                                                                           | 说明            | 版本     |
 |------------------------------------------------------------------------------|---------------|--------|
-| [Vue](https://cn.vuejs.org/index.html)                                       | JavaScript 框架 | 2.6.12 |
+| [Vue](https://cn.vuejs.org/index.html)                                       | JavaScript 框架 | 2.7.14 |
 | [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案      | -      |
 
+### [管理后台 Vue3 前端](./yudao-ui-admin-vue3)
+
+| 框架                                                                   |      说明      |   版本   |
+|----------------------------------------------------------------------|:------------:|:------:|
+| [Vue](https://staging-cn.vuejs.org/)                                 |    Vue 框架    | 3.2.45 |
+| [Vite](https://cn.vitejs.dev//)                                      |   开发与构建工具    | 4.0.3  |
+| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus | 2.2.27 |
+| [TypeScript](https://www.typescriptlang.org/docs/)                   |  TypeScript  | 4.9.4  |
+| [pinia](https://pinia.vuejs.org/)                                    |    vuex5     | 2.0.28 |
+| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) |     国际化      | 9.2.2  |
+| [vxe-table](https://vxetable.cn/)                                    |   vue最强表单    | 4.3.7  |
+
+### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
+
+| 框架                                              | 说明                 | 版本     |
+|-------------------------------------------------|--------------------|--------|
+| [uni-app](hhttps://github.com/dcloudio/uni-app) | 跨平台框架              | 2.0.0  |
+| [uni-ui](https://github.com/dcloudio/uni-ui)    | 基于 uni-app 的 UI 框架 | 1.4.20 |
+
 ## 🐷 演示图
 
 ### 系统功能
 
 | 模块       | biu                                                                | biu                                                              | biu                                                              |
-|----------|--------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|
-| 登录 & 首页  | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg)       | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg)     | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg) |
-| 用户       | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg)   | ![在线用户](https://static.iocoder.cn/images/ruoyi-vue-pro/在线用户.jpg) | -                                                                |
-| 租户 & 套餐  | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg)   | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | -                                                                |
-| 部门 & 岗位  | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg)   | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg) | -                                                                |
-| 菜单 & 角色  | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg)   | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg) | -                                                                |
-| 审计日志     | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg)   | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg) | -                                                                |
-| 短信       | ![短信渠道](https://static.iocoder.cn/images/ruoyi-vue-pro/短信渠道.jpg)   | ![短信模板](https://static.iocoder.cn/images/ruoyi-vue-pro/短信模板.jpg) | ![短信日志](https://static.iocoder.cn/images/ruoyi-vue-pro/短信日志.jpg) |
-| 字典       | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg)   | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg) | -                                                                |
-| 错误码 & 通知 | ![错误码管理](https://static.iocoder.cn/images/ruoyi-vue-pro/错误码管理.jpg) | ![通知公告](https://static.iocoder.cn/images/ruoyi-vue-pro/通知公告.jpg) | -                                                                |
+|------------|--------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|
+| 登录 & 首页  | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg?imageView2/2/format/webp/w/1280)       | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg?imageView2/2/format/webp/w/1280)     | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg?imageView2/2/format/webp/w/1280) |
+| 用户 & 应用  | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg?imageView2/2/format/webp/w/1280)   | ![令牌管理](https://static.iocoder.cn/images/ruoyi-vue-pro/令牌管理.jpg?imageView2/2/format/webp/w/1280) | ![应用管理](https://static.iocoder.cn/images/ruoyi-vue-pro/应用管理.jpg?imageView2/2/format/webp/w/1280)                                                                |
+| 租户 & 套餐  | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg?imageView2/2/format/webp/w/1280)   | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | -                                                                |
+| 部门 & 岗位  | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg?imageView2/2/format/webp/w/1280)   | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 菜单 & 角色  | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg?imageView2/2/format/webp/w/1280)   | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 审计日志     | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg?imageView2/2/format/webp/w/1280)   | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 短信       | ![短信渠道](https://static.iocoder.cn/images/ruoyi-vue-pro/短信渠道.jpg?imageView2/2/format/webp/w/1280)   | ![短信模板](https://static.iocoder.cn/images/ruoyi-vue-pro/短信模板.jpg?imageView2/2/format/webp/w/1280) | ![短信日志](https://static.iocoder.cn/images/ruoyi-vue-pro/短信日志.jpg?imageView2/2/format/webp/w/1280) |
+| 字典 & 敏感词 | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg?imageView2/2/format/webp/w/1280)   | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg?imageView2/2/format/webp/w/1280) | ![敏感词](https://static.iocoder.cn/images/ruoyi-vue-pro/敏感词.jpg?imageView2/2/format/webp/w/1280)                                                                |
+| 错误码 & 通知 | ![错误码管理](https://static.iocoder.cn/images/ruoyi-vue-pro/错误码管理.jpg?imageView2/2/format/webp/w/1280) | ![通知公告](https://static.iocoder.cn/images/ruoyi-vue-pro/通知公告.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
 
 ### 工作流程
 
 | 模块      | biu                                                                    | biu                                                                    | biu                                                                    |
 |---------|------------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
-| 流程模型    | ![流程模型-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-列表.jpg) | ![流程模型-设计](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-设计.jpg) | ![流程模型-定义](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-定义.jpg) |
-| 表单 & 分组 | ![流程表单](https://static.iocoder.cn/images/ruoyi-vue-pro/流程表单.jpg)       | ![用户分组](https://static.iocoder.cn/images/ruoyi-vue-pro/用户分组.jpg)       | -                                                                      |
-| 我的流程    | ![我的流程-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-列表.jpg) | ![我的流程-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-发起.jpg) | ![我的流程-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-详情.jpg) |
-| 待办 & 已办 | ![任务列表-审批](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-审批.jpg) | ![任务列表-待办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-待办.jpg) | ![任务列表-已办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-已办.jpg) |
-| OA 请假   | ![OA请假-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-列表.jpg) | ![OA请假-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-发起.jpg) | ![OA请假-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-详情.jpg) |
+| 流程模型    | ![流程模型-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-列表.jpg?imageView2/2/format/webp/w/1280) | ![流程模型-设计](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-设计.jpg?imageView2/2/format/webp/w/1280) | ![流程模型-定义](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-定义.jpg?imageView2/2/format/webp/w/1280) |
+| 表单 & 分组 | ![流程表单](https://static.iocoder.cn/images/ruoyi-vue-pro/流程表单.jpg?imageView2/2/format/webp/w/1280)       | ![用户分组](https://static.iocoder.cn/images/ruoyi-vue-pro/用户分组.jpg?imageView2/2/format/webp/w/1280)       | -                                                                      |
+| 我的流程    | ![我的流程-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-列表.jpg?imageView2/2/format/webp/w/1280) | ![我的流程-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-发起.jpg?imageView2/2/format/webp/w/1280) | ![我的流程-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-详情.jpg?imageView2/2/format/webp/w/1280) |
+| 待办 & 已办 | ![任务列表-审批](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-审批.jpg?imageView2/2/format/webp/w/1280) | ![任务列表-待办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-待办.jpg?imageView2/2/format/webp/w/1280) | ![任务列表-已办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-已办.jpg?imageView2/2/format/webp/w/1280) |
+| OA 请假   | ![OA请假-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-列表.jpg?imageView2/2/format/webp/w/1280) | ![OA请假-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-发起.jpg?imageView2/2/format/webp/w/1280) | ![OA请假-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-详情.jpg?imageView2/2/format/webp/w/1280) |
+
+### 基础设施
+
+| 模块            | biu                                                                  | biu                                                                | biu                                                              |
+|---------------|----------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------|
+| 代码生成          | ![代码生成](https://static.iocoder.cn/images/ruoyi-vue-pro/代码生成.jpg?imageView2/2/format/webp/w/1280)     | ![生成效果](https://static.iocoder.cn/images/ruoyi-vue-pro/生成效果.jpg?imageView2/2/format/webp/w/1280)   | -                                                                |
+| 文档            | ![系统接口](https://static.iocoder.cn/images/ruoyi-vue-pro/系统接口.jpg?imageView2/2/format/webp/w/1280)     | ![数据库文档](https://static.iocoder.cn/images/ruoyi-vue-pro/数据库文档.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 文件 & 配置       | ![文件配置](https://static.iocoder.cn/images/ruoyi-vue-pro/文件配置.jpg?imageView2/2/format/webp/w/1280) | ![文件管理](https://static.iocoder.cn/images/ruoyi-vue-pro/文件管理2.jpg?imageView2/2/format/webp/w/1280)     | ![配置管理](https://static.iocoder.cn/images/ruoyi-vue-pro/配置管理.jpg?imageView2/2/format/webp/w/1280)   |
+| 定时任务          | ![定时任务](https://static.iocoder.cn/images/ruoyi-vue-pro/定时任务.jpg?imageView2/2/format/webp/w/1280)     | ![任务日志](https://static.iocoder.cn/images/ruoyi-vue-pro/任务日志.jpg?imageView2/2/format/webp/w/1280)   | -                                                                |
+| API 日志        | ![访问日志](https://static.iocoder.cn/images/ruoyi-vue-pro/访问日志.jpg?imageView2/2/format/webp/w/1280)     | ![错误日志](https://static.iocoder.cn/images/ruoyi-vue-pro/错误日志.jpg?imageView2/2/format/webp/w/1280)   | -                                                                |
+| MySQL & Redis | ![MySQL](https://static.iocoder.cn/images/ruoyi-vue-pro/MySQL.jpg?imageView2/2/format/webp/w/1280)   | ![Redis](https://static.iocoder.cn/images/ruoyi-vue-pro/Redis.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 监控平台          | ![Java监控](https://static.iocoder.cn/images/ruoyi-vue-pro/Java监控.jpg?imageView2/2/format/webp/w/1280) | ![链路追踪](https://static.iocoder.cn/images/ruoyi-vue-pro/链路追踪.jpg?imageView2/2/format/webp/w/1280)   | ![日志中心](https://static.iocoder.cn/images/ruoyi-vue-pro/日志中心.jpg?imageView2/2/format/webp/w/1280) |
 
 ### 支付系统
 
 | 模块      | biu                                                              | biu                                                                    | biu                                                                    |
 |---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
-| 商家 & 应用 | ![商户信息](https://static.iocoder.cn/images/ruoyi-vue-pro/商户信息.jpg) | ![应用信息-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-列表.jpg) | ![应用信息-编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-编辑.jpg) |
-| 支付 & 退款 | ![支付订单](https://static.iocoder.cn/images/ruoyi-vue-pro/支付订单.jpg) | ![退款订单](https://static.iocoder.cn/images/ruoyi-vue-pro/退款订单.jpg)       | ---                                                                    |
+| 商家 & 应用 | ![商户信息](https://static.iocoder.cn/images/ruoyi-vue-pro/商户信息.jpg?imageView2/2/format/webp/w/1280) | ![应用信息-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-列表.jpg?imageView2/2/format/webp/w/1280) | ![应用信息-编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-编辑.jpg?imageView2/2/format/webp/w/1280) |
+| 支付 & 退款 | ![支付订单](https://static.iocoder.cn/images/ruoyi-vue-pro/支付订单.jpg?imageView2/2/format/webp/w/1280) | ![退款订单](https://static.iocoder.cn/images/ruoyi-vue-pro/退款订单.jpg?imageView2/2/format/webp/w/1280)       | ---                                                                    |
 
-### 基础设施
+### 数据报表
 
-| 模块            | biu                                                                  | biu                                                                | biu                                                              |
-|---------------|----------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------|
-| 代码生成          | ![代码生成](https://static.iocoder.cn/images/ruoyi-vue-pro/代码生成.jpg)     | ![生成效果](https://static.iocoder.cn/images/ruoyi-vue-pro/生成效果.jpg)   | -                                                                |
-| 文档            | ![系统接口](https://static.iocoder.cn/images/ruoyi-vue-pro/系统接口.jpg)     | ![数据库文档](https://static.iocoder.cn/images/ruoyi-vue-pro/数据库文档.jpg) | -                                                                |
-| 文件 & 配置       | ![文件配置](https://static.iocoder.cn/images/ruoyi-vue-pro/文件配置.jpg) | ![文件管理](https://static.iocoder.cn/images/ruoyi-vue-pro/文件管理2.jpg)     | ![配置管理](https://static.iocoder.cn/images/ruoyi-vue-pro/配置管理.jpg)   |
-| 定时任务          | ![定时任务](https://static.iocoder.cn/images/ruoyi-vue-pro/定时任务.jpg)     | ![任务日志](https://static.iocoder.cn/images/ruoyi-vue-pro/任务日志.jpg)   | -                                                                |
-| API 日志        | ![访问日志](https://static.iocoder.cn/images/ruoyi-vue-pro/访问日志.jpg)     | ![错误日志](https://static.iocoder.cn/images/ruoyi-vue-pro/错误日志.jpg)   | -                                                                |
-| MySQL & Redis | ![MySQL](https://static.iocoder.cn/images/ruoyi-vue-pro/MySQL.jpg)   | ![Redis](https://static.iocoder.cn/images/ruoyi-vue-pro/Redis.jpg) | -                                                                |
-| 监控平台          | ![Java监控](https://static.iocoder.cn/images/ruoyi-vue-pro/Java监控.jpg) | ![链路追踪](https://static.iocoder.cn/images/ruoyi-vue-pro/链路追踪.jpg)   | ![日志中心](https://static.iocoder.cn/images/ruoyi-vue-pro/日志中心.jpg) |
+| 模块      | biu                                                              | biu                                                                    | biu                                                                    |
+|---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
+| 报表设计器 | ![数据报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-数据报表.jpg?imageView2/2/format/webp/w/1280) | ![图形报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-图形报表.jpg?imageView2/2/format/webp/w/1280) | ![报表设计器-打印设计](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-打印设计.jpg?imageView2/2/format/webp/w/1280) |
+
+### 移动端(管理后台)
+
+| biu                                                              | biu                                                                    | biu                                                                    |
+|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
+| ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/01.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/02.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/03.png?imageView2/2/format/webp) |
+| ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/04.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/05.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/06.png?imageView2/2/format/webp) |
+| ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/07.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/08.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/09.png?imageView2/2/format/webp) |
 
+目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。

+ 9 - 0
http-client.env.json

@@ -5,6 +5,15 @@
     "appTenantId": "1",
 
     "appApi": "http://127.0.0.1:48080/app-api",
+    "appToken": "test247",
+    "appTenentId": "1"
+  },
+  "gateway": {
+    "baseUrl": "http://127.0.0.1:8888/admin-api",
+    "token": "test1",
+    "adminTenentId": "1",
+
+    "appApi": "http://127.0.0.1:8888/app-api",
     "appToken": "test1",
     "appTenantId": "1"
   }

+ 9 - 5
pom.xml

@@ -12,13 +12,17 @@
         <module>yudao-framework</module>
         <!-- Server 主项目 -->
         <module>yudao-server</module>
-        <!--  各种 module 拓展 -->
+        <!-- 各种 module 拓展 -->
         <module>yudao-module-member</module>
-        <module>yudao-module-bpm</module>
         <module>yudao-module-system</module>
         <module>yudao-module-infra</module>
         <module>yudao-module-pay</module>
+<!--        <module>yudao-module-bpm</module>-->
+        <module>yudao-module-visualization</module>
         <module>yudao-module-mp</module>
+        <!--        <module>yudao-module-mall</module>-->
+        <!-- 示例项目 -->
+        <module>yudao-example</module>
     </modules>
 
     <name>${project.artifactId}</name>
@@ -26,7 +30,7 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.6.2-snapshot</revision>
+        <revision>1.6.5-snapshot</revision>
         <!-- Maven 相关 -->
         <java.version>1.8</java.version>
         <maven.compiler.source>${java.version}</maven.compiler.source>
@@ -34,8 +38,8 @@
         <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
         <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
         <!-- 看看咋放到 bom 里 -->
-        <lombok.version>1.18.20</lombok.version>
-        <mapstruct.version>1.4.1.Final</mapstruct.version>
+        <lombok.version>1.18.24</lombok.version>
+        <mapstruct.version>1.5.3.Final</mapstruct.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 

+ 266 - 0
sql/mysql/optional/vue3-menu.sql

@@ -0,0 +1,266 @@
+-- ----------------------------
+-- Table structure for system_menu
+-- icon 不兼容
+-- 注意!!!只有在使用 yudao-ui-admin-vue3 才进行导入!!!
+-- 注意!!!只有在使用 yudao-ui-admin-vue3 才进行导入!!!
+-- 注意!!!只有在使用 yudao-ui-admin-vue3 才进行导入!!!
+-- ----------------------------
+DROP TABLE IF EXISTS `system_menu`;
+CREATE TABLE `system_menu`  (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
+    `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单名称',
+    `permission` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '权限标识',
+    `type` tinyint NOT NULL COMMENT '菜单类型',
+    `sort` int NOT NULL DEFAULT 0 COMMENT '显示顺序',
+    `parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父菜单ID',
+    `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '路由地址',
+    `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '#' COMMENT '菜单图标',
+    `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '组件路径',
+    `status` tinyint NOT NULL DEFAULT 0 COMMENT '菜单状态',
+    `visible` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否可见',
+    `keep_alive` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否缓存',
+    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1268 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Records of system_menu
+-- ----------------------------
+INSERT INTO `system_menu` VALUES (1, '系统管理', '', 1, 10, 0, '/system', 'ep:tools', NULL, 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 13:10:54', b'0');
+INSERT INTO `system_menu` VALUES (2, '基础设施', '', 1, 20, 0, '/infra', 'ep:brush-filled', NULL, 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 13:11:55', b'0');
+INSERT INTO `system_menu` VALUES (5, 'OA 示例', '', 1, 40, 1185, 'oa', 'ep:guide', NULL, 0, b'1', b'1', 'admin', '2021-09-20 16:26:19', '1', '2022-07-20 14:51:03', b'0');
+INSERT INTO `system_menu` VALUES (100, '用户管理', 'system:user:list', 2, 1, 1, 'user', 'ep:avatar', 'system/user/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 13:13:17', b'0');
+INSERT INTO `system_menu` VALUES (101, '角色管理', '', 2, 2, 1, 'role', 'ep:user-filled', 'system/role/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 13:13:41', b'0');
+INSERT INTO `system_menu` VALUES (102, '菜单管理', '', 2, 3, 1, 'menu', 'ep:grid', 'system/menu/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 13:13:54', b'0');
+INSERT INTO `system_menu` VALUES (103, '部门管理', '', 2, 4, 1, 'dept', 'ep:office-building', 'system/dept/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:51:31', b'0');
+INSERT INTO `system_menu` VALUES (104, '岗位管理', '', 2, 5, 1, 'post', 'ep:briefcase', 'system/post/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:41:47', b'0');
+INSERT INTO `system_menu` VALUES (105, '字典管理', '', 2, 6, 1, 'dict', 'ep:list', 'system/dict/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:42:18', b'0');
+INSERT INTO `system_menu` VALUES (106, '配置管理', '', 2, 6, 2, 'config', 'ep:edit', 'infra/config/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:48:29', b'0');
+INSERT INTO `system_menu` VALUES (107, '通知公告', '', 2, 8, 1, 'notice', 'ep:bell-filled', 'system/notice/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:42:30', b'0');
+INSERT INTO `system_menu` VALUES (108, '审计日志', '', 1, 9, 1, 'log', 'ep:document-checked', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:42:52', b'0');
+INSERT INTO `system_menu` VALUES (109, '令牌管理', '', 2, 2, 1261, 'token', 'online', 'system/oauth2/token/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-05-11 23:31:42', b'0');
+INSERT INTO `system_menu` VALUES (110, '定时任务', '', 2, 12, 2, 'job', 'ep:alarm-clock', 'infra/job/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:49:09', b'0');
+INSERT INTO `system_menu` VALUES (111, 'MySQL 监控', '', 2, 9, 2, 'druid', 'ep:wind-power', 'infra/druid/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:50:06', b'0');
+INSERT INTO `system_menu` VALUES (112, 'Java 监控', '', 2, 11, 2, 'admin-server', 'ep:opportunity', 'infra/server/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:49:42', b'0');
+INSERT INTO `system_menu` VALUES (113, 'Redis 监控', '', 2, 10, 2, 'redis', 'ep:set-up', 'infra/redis/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:49:52', b'0');
+INSERT INTO `system_menu` VALUES (114, '表单构建', 'infra:build:list', 2, 2, 2, 'build', 'ep:calendar', 'infra/build/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:47:29', b'0');
+INSERT INTO `system_menu` VALUES (115, '代码生成', 'infra:codegen:query', 2, 1, 2, 'codegen', 'ep:connection', 'infra/codegen/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:46:41', b'0');
+INSERT INTO `system_menu` VALUES (116, '系统接口', 'infra:swagger:list', 2, 3, 2, 'swagger', 'ep:operation', 'infra/swagger/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-07-20 14:47:41', b'0');
+INSERT INTO `system_menu` VALUES (500, '操作日志', '', 2, 1, 108, 'operate-log', 'form', 'system/operatelog/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (501, '登录日志', '', 2, 2, 108, 'login-log', 'logininfor', 'system/loginlog/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1001, '用户查询', 'system:user:query', 3, 1, 100, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1002, '用户新增', 'system:user:create', 3, 2, 100, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1003, '用户修改', 'system:user:update', 3, 3, 100, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1004, '用户删除', 'system:user:delete', 3, 4, 100, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1005, '用户导出', 'system:user:export', 3, 5, 100, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1006, '用户导入', 'system:user:import', 3, 6, 100, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1007, '重置密码', 'system:user:update-password', 3, 7, 100, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1008, '角色查询', 'system:role:query', 3, 1, 101, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1009, '角色新增', 'system:role:create', 3, 2, 101, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1010, '角色修改', 'system:role:update', 3, 3, 101, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1011, '角色删除', 'system:role:delete', 3, 4, 101, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1012, '角色导出', 'system:role:export', 3, 5, 101, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1013, '菜单查询', 'system:menu:query', 3, 1, 102, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1014, '菜单新增', 'system:menu:create', 3, 2, 102, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1015, '菜单修改', 'system:menu:update', 3, 3, 102, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1016, '菜单删除', 'system:menu:delete', 3, 4, 102, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1017, '部门查询', 'system:dept:query', 3, 1, 103, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1018, '部门新增', 'system:dept:create', 3, 2, 103, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1019, '部门修改', 'system:dept:update', 3, 3, 103, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1020, '部门删除', 'system:dept:delete', 3, 4, 103, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1021, '岗位查询', 'system:post:query', 3, 1, 104, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1022, '岗位新增', 'system:post:create', 3, 2, 104, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1023, '岗位修改', 'system:post:update', 3, 3, 104, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1024, '岗位删除', 'system:post:delete', 3, 4, 104, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1025, '岗位导出', 'system:post:export', 3, 5, 104, '', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1026, '字典查询', 'system:dict:query', 3, 1, 105, '#', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1027, '字典新增', 'system:dict:create', 3, 2, 105, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1028, '字典修改', 'system:dict:update', 3, 3, 105, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1029, '字典删除', 'system:dict:delete', 3, 4, 105, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1030, '字典导出', 'system:dict:export', 3, 5, 105, '#', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1031, '配置查询', 'infra:config:query', 3, 1, 106, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1032, '配置新增', 'infra:config:create', 3, 2, 106, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1033, '配置修改', 'infra:config:update', 3, 3, 106, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1034, '配置删除', 'infra:config:delete', 3, 4, 106, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1035, '配置导出', 'infra:config:export', 3, 5, 106, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1036, '公告查询', 'system:notice:query', 3, 1, 107, '#', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1037, '公告新增', 'system:notice:create', 3, 2, 107, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1038, '公告修改', 'system:notice:update', 3, 3, 107, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1039, '公告删除', 'system:notice:delete', 3, 4, 107, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1040, '操作查询', 'system:operate-log:query', 3, 1, 500, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1042, '日志导出', 'system:operate-log:export', 3, 2, 500, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1043, '登录查询', 'system:login-log:query', 3, 1, 501, '#', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1045, '日志导出', 'system:login-log:export', 3, 3, 501, '#', '#', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1046, '令牌列表', 'system:oauth2-token:page', 3, 1, 109, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-05-09 23:54:42', b'0');
+INSERT INTO `system_menu` VALUES (1048, '令牌删除', 'system:oauth2-token:delete', 3, 2, 109, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-05-09 23:54:53', b'0');
+INSERT INTO `system_menu` VALUES (1050, '任务新增', 'infra:job:create', 3, 2, 110, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1051, '任务修改', 'infra:job:update', 3, 3, 110, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1052, '任务删除', 'infra:job:delete', 3, 4, 110, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1053, '状态修改', 'infra:job:update', 3, 5, 110, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1054, '任务导出', 'infra:job:export', 3, 7, 110, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1056, '生成修改', 'infra:codegen:update', 3, 2, 115, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1057, '生成删除', 'infra:codegen:delete', 3, 3, 115, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1058, '导入代码', 'infra:codegen:create', 3, 2, 115, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1059, '预览代码', 'infra:codegen:preview', 3, 4, 115, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1060, '生成代码', 'infra:codegen:download', 3, 5, 115, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1063, '设置角色菜单权限', 'system:permission:assign-role-menu', 3, 6, 101, '', '', '', 0, b'1', b'1', '', '2021-01-06 17:53:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1064, '设置角色数据权限', 'system:permission:assign-role-data-scope', 3, 7, 101, '', '', '', 0, b'1', b'1', '', '2021-01-06 17:56:31', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1065, '设置用户角色', 'system:permission:assign-user-role', 3, 8, 101, '', '', '', 0, b'1', b'1', '', '2021-01-07 10:23:28', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1066, '获得 Redis 监控信息', 'infra:redis:get-monitor-info', 3, 1, 113, '', '', '', 0, b'1', b'1', '', '2021-01-26 01:02:31', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1067, '获得 Redis Key 列表', 'infra:redis:get-key-list', 3, 2, 113, '', '', '', 0, b'1', b'1', '', '2021-01-26 01:02:52', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1070, '代码生成示例', 'infra:test-demo:query', 2, 1, 2, 'test-demo', 'ep:baseball', 'infra/testDemo/index', 0, b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-07-20 14:46:53', b'0');
+INSERT INTO `system_menu` VALUES (1071, '测试示例表创建', 'infra:test-demo:create', 3, 1, 1070, '', '', '', 0, b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1072, '测试示例表更新', 'infra:test-demo:update', 3, 2, 1070, '', '', '', 0, b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1073, '测试示例表删除', 'infra:test-demo:delete', 3, 3, 1070, '', '', '', 0, b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1074, '测试示例表导出', 'infra:test-demo:export', 3, 4, 1070, '', '', '', 0, b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1075, '任务触发', 'infra:job:trigger', 3, 8, 110, '', '', '', 0, b'1', b'1', '', '2021-02-07 13:03:10', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1076, '数据库文档', '', 2, 4, 2, 'db-doc', 'ep:grid', 'infra/dbDoc/index', 0, b'1', b'1', '', '2021-02-08 01:41:47', '1', '2022-07-20 14:47:56', b'0');
+INSERT INTO `system_menu` VALUES (1077, '监控平台', '', 2, 13, 2, 'skywalking', 'ep:aim', 'infra/skywalking/index', 0, b'1', b'1', '', '2021-02-08 20:41:31', '1', '2022-07-20 14:49:15', b'0');
+INSERT INTO `system_menu` VALUES (1078, '访问日志', '', 2, 1, 1083, 'api-access-log', 'log', 'infra/apiAccessLog/index', 0, b'1', b'1', '', '2021-02-26 01:32:59', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1082, '日志导出', 'infra:api-access-log:export', 3, 2, 1078, '', '', '', 0, b'1', b'1', '', '2021-02-26 01:32:59', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1083, 'API 日志', '', 2, 8, 2, 'log', 'ep:bell', NULL, 0, b'1', b'1', '', '2021-02-26 02:18:24', '1', '2022-07-20 14:48:36', b'0');
+INSERT INTO `system_menu` VALUES (1084, '错误日志', 'infra:api-error-log:query', 2, 2, 1083, 'api-error-log', 'log', 'infra/apiErrorLog/index', 0, b'1', b'1', '', '2021-02-26 07:53:20', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1085, '日志处理', 'infra:api-error-log:update-status', 3, 2, 1084, '', '', '', 0, b'1', b'1', '', '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1086, '日志导出', 'infra:api-error-log:export', 3, 3, 1084, '', '', '', 0, b'1', b'1', '', '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1087, '任务查询', 'infra:job:query', 3, 1, 110, '', '', '', 0, b'1', b'1', '1', '2021-03-10 01:26:19', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1088, '日志查询', 'infra:api-access-log:query', 3, 1, 1078, '', '', '', 0, b'1', b'1', '1', '2021-03-10 01:28:04', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1089, '日志查询', 'infra:api-error-log:query', 3, 1, 1084, '', '', '', 0, b'1', b'1', '1', '2021-03-10 01:29:09', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1090, '文件列表', '', 2, 5, 1243, 'file-list', 'upload', 'infra/fileList/index', 0, b'1', b'1', '', '2021-03-12 20:16:20', '1', '2022-07-20 12:10:47', b'0');
+INSERT INTO `system_menu` VALUES (1091, '文件查询', 'infra:file:query', 3, 1, 1090, '', '', '', 0, b'1', b'1', '', '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1092, '文件删除', 'infra:file:delete', 3, 4, 1090, '', '', '', 0, b'1', b'1', '', '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1093, '短信管理', '', 1, 11, 1, 'sms', 'ep:chat-dot-square', NULL, 0, b'1', b'1', '1', '2021-04-05 01:10:16', '1', '2022-07-20 14:43:32', b'0');
+INSERT INTO `system_menu` VALUES (1094, '短信渠道', '', 2, 0, 1093, 'sms-channel', 'phone', 'system/sms/smsChannel', 0, b'1', b'1', '', '2021-04-01 11:07:15', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1095, '短信渠道查询', 'system:sms-channel:query', 3, 1, 1094, '', '', '', 0, b'1', b'1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1096, '短信渠道创建', 'system:sms-channel:create', 3, 2, 1094, '', '', '', 0, b'1', b'1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1097, '短信渠道更新', 'system:sms-channel:update', 3, 3, 1094, '', '', '', 0, b'1', b'1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1098, '短信渠道删除', 'system:sms-channel:delete', 3, 4, 1094, '', '', '', 0, b'1', b'1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1100, '短信模板', '', 2, 1, 1093, 'sms-template', 'phone', 'system/sms/smsTemplate', 0, b'1', b'1', '', '2021-04-01 17:35:17', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1101, '短信模板查询', 'system:sms-template:query', 3, 1, 1100, '', '', '', 0, b'1', b'1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1102, '短信模板创建', 'system:sms-template:create', 3, 2, 1100, '', '', '', 0, b'1', b'1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1103, '短信模板更新', 'system:sms-template:update', 3, 3, 1100, '', '', '', 0, b'1', b'1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1104, '短信模板删除', 'system:sms-template:delete', 3, 4, 1100, '', '', '', 0, b'1', b'1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1105, '短信模板导出', 'system:sms-template:export', 3, 5, 1100, '', '', '', 0, b'1', b'1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1106, '发送测试短信', 'system:sms-template:send-sms', 3, 6, 1100, '', '', '', 0, b'1', b'1', '1', '2021-04-11 00:26:40', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1107, '短信日志', '', 2, 2, 1093, 'sms-log', 'phone', 'system/sms/smsLog', 0, b'1', b'1', '', '2021-04-11 08:37:05', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1108, '短信日志查询', 'system:sms-log:query', 3, 1, 1107, '', '', '', 0, b'1', b'1', '', '2021-04-11 08:37:05', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1109, '短信日志导出', 'system:sms-log:export', 3, 5, 1107, '', '', '', 0, b'1', b'1', '', '2021-04-11 08:37:05', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1110, '错误码管理', '', 2, 12, 1, 'error-code', 'ep:document-delete', 'system/errorCode/index', 0, b'1', b'1', '', '2021-04-13 21:46:42', '1', '2022-07-20 14:43:42', b'0');
+INSERT INTO `system_menu` VALUES (1111, '错误码查询', 'system:error-code:query', 3, 1, 1110, '', '', '', 0, b'1', b'1', '', '2021-04-13 21:46:42', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1112, '错误码创建', 'system:error-code:create', 3, 2, 1110, '', '', '', 0, b'1', b'1', '', '2021-04-13 21:46:42', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1113, '错误码更新', 'system:error-code:update', 3, 3, 1110, '', '', '', 0, b'1', b'1', '', '2021-04-13 21:46:42', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1114, '错误码删除', 'system:error-code:delete', 3, 4, 1110, '', '', '', 0, b'1', b'1', '', '2021-04-13 21:46:42', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1115, '错误码导出', 'system:error-code:export', 3, 5, 1110, '', '', '', 0, b'1', b'1', '', '2021-04-13 21:46:42', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1117, '支付管理', '', 1, 11, 0, '/pay', 'ep:goods-filled', NULL, 0, b'1', b'1', '1', '2021-12-25 16:43:41', '1', '2022-07-20 13:11:45', b'0');
+INSERT INTO `system_menu` VALUES (1118, '请假查询', '', 2, 0, 5, 'leave', 'user', 'bpm/oa/leave/index', 0, b'1', b'1', '', '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1119, '请假申请查询', 'bpm:oa-leave:query', 3, 1, 1118, '', '', '', 0, b'1', b'1', '', '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1120, '请假申请创建', 'bpm:oa-leave:create', 3, 2, 1118, '', '', '', 0, b'1', b'1', '', '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1126, '应用信息', '', 2, 1, 1117, 'app', 'ep:cellphone', 'pay/app/index', 0, b'1', b'1', '', '2021-11-10 01:13:30', '1', '2022-07-20 14:44:17', b'0');
+INSERT INTO `system_menu` VALUES (1127, '支付应用信息查询', 'pay:app:query', 3, 1, 1126, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1128, '支付应用信息创建', 'pay:app:create', 3, 2, 1126, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1129, '支付应用信息更新', 'pay:app:update', 3, 3, 1126, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1130, '支付应用信息删除', 'pay:app:delete', 3, 4, 1126, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1131, '支付应用信息导出', 'pay:app:export', 3, 5, 1126, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1132, '秘钥解析', 'pay:channel:parsing', 3, 6, 1129, '', '', '', 0, b'1', b'1', '1', '2021-11-08 15:15:47', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1133, '支付商户信息查询', 'pay:merchant:query', 3, 1, 1132, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1134, '支付商户信息创建', 'pay:merchant:create', 3, 2, 1132, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1135, '支付商户信息更新', 'pay:merchant:update', 3, 3, 1132, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1136, '支付商户信息删除', 'pay:merchant:delete', 3, 4, 1132, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1137, '支付商户信息导出', 'pay:merchant:export', 3, 5, 1132, '', '', '', 0, b'1', b'1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1138, '租户列表', '', 2, 0, 1224, 'list', 'peoples', 'system/tenant/index', 0, b'1', b'1', '', '2021-12-14 12:31:43', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1139, '租户查询', 'system:tenant:query', 3, 1, 1138, '', '', '', 0, b'1', b'1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1140, '租户创建', 'system:tenant:create', 3, 2, 1138, '', '', '', 0, b'1', b'1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1141, '租户更新', 'system:tenant:update', 3, 3, 1138, '', '', '', 0, b'1', b'1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1142, '租户删除', 'system:tenant:delete', 3, 4, 1138, '', '', '', 0, b'1', b'1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1143, '租户导出', 'system:tenant:export', 3, 5, 1138, '', '', '', 0, b'1', b'1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1150, '秘钥解析', '', 3, 6, 1129, '', '', '', 0, b'1', b'1', '1', '2021-11-08 15:15:47', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1161, '退款订单', '', 2, 3, 1117, 'refund', 'ep:message-box', 'pay/refund/index', 0, b'1', b'1', '', '2021-12-25 08:29:07', '1', '2022-07-20 14:45:23', b'0');
+INSERT INTO `system_menu` VALUES (1162, '退款订单查询', 'pay:refund:query', 3, 1, 1161, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1163, '退款订单创建', 'pay:refund:create', 3, 2, 1161, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1164, '退款订单更新', 'pay:refund:update', 3, 3, 1161, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1165, '退款订单删除', 'pay:refund:delete', 3, 4, 1161, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1166, '退款订单导出', 'pay:refund:export', 3, 5, 1161, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1173, '支付订单', '', 2, 2, 1117, 'order', 'ep:histogram', 'pay/order/index', 0, b'1', b'1', '', '2021-12-25 08:49:43', '1', '2022-07-20 14:44:36', b'0');
+INSERT INTO `system_menu` VALUES (1174, '支付订单查询', 'pay:order:query', 3, 1, 1173, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1175, '支付订单创建', 'pay:order:create', 3, 2, 1173, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1176, '支付订单更新', 'pay:order:update', 3, 3, 1173, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1177, '支付订单删除', 'pay:order:delete', 3, 4, 1173, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1178, '支付订单导出', 'pay:order:export', 3, 5, 1173, '', '', '', 0, b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1179, '商户信息', '', 2, 0, 1117, 'merchant', 'ep:goods', 'pay/merchant/index', 0, b'1', b'1', '', '2021-12-25 09:01:44', '1', '2022-07-20 14:44:58', b'0');
+INSERT INTO `system_menu` VALUES (1180, '支付商户信息查询', 'pay:merchant:query', 3, 1, 1179, '', '', '', 0, b'1', b'1', '', '2021-12-25 09:01:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1181, '支付商户信息创建', 'pay:merchant:create', 3, 2, 1179, '', '', '', 0, b'1', b'1', '', '2021-12-25 09:01:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1182, '支付商户信息更新', 'pay:merchant:update', 3, 3, 1179, '', '', '', 0, b'1', b'1', '', '2021-12-25 09:01:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1183, '支付商户信息删除', '', 3, 4, 1179, '', '', '', 0, b'1', b'1', '', '2021-12-25 09:01:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1184, '支付商户信息导出', 'pay:merchant:export', 3, 5, 1179, '', '', '', 0, b'1', b'1', '', '2021-12-25 09:01:44', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1185, '工作流程', '', 1, 50, 0, '/bpm', 'ep:checked', NULL, 0, b'1', b'1', '1', '2021-12-30 20:26:36', '1', '2022-07-20 13:12:35', b'0');
+INSERT INTO `system_menu` VALUES (1186, '流程管理', '', 1, 10, 1185, 'manager', 'ep:collection-tag', NULL, 0, b'1', b'1', '1', '2021-12-30 20:28:30', '1', '2022-07-20 14:50:31', b'0');
+INSERT INTO `system_menu` VALUES (1187, '流程表单', '', 2, 0, 1186, 'form', 'form', 'bpm/form/index', 0, b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1188, '表单查询', 'bpm:form:query', 3, 1, 1187, '', '', '', 0, b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1189, '表单创建', 'bpm:form:create', 3, 2, 1187, '', '', '', 0, b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1190, '表单更新', 'bpm:form:update', 3, 3, 1187, '', '', '', 0, b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1191, '表单删除', 'bpm:form:delete', 3, 4, 1187, '', '', '', 0, b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1192, '表单导出', 'bpm:form:export', 3, 5, 1187, '', '', '', 0, b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1193, '流程模型', '', 2, 5, 1186, 'model', 'guide', 'bpm/model/index', 0, b'1', b'1', '1', '2021-12-31 23:24:58', '103', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1194, '模型查询', 'bpm:model:query', 3, 1, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-03 19:01:10', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1195, '模型创建', 'bpm:model:create', 3, 2, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-03 19:01:24', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1196, '模型导入', 'bpm:model:import', 3, 3, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-03 19:01:35', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1197, '模型更新', 'bpm:model:update', 3, 4, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-03 19:02:28', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1198, '模型删除', 'bpm:model:delete', 3, 5, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-03 19:02:43', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1199, '模型发布', 'bpm:model:deploy', 3, 6, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-03 19:03:24', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1200, '任务管理', '', 1, 20, 1185, 'task', 'ep:calendar', NULL, 0, b'1', b'1', '1', '2022-01-07 23:51:48', '1', '2022-07-20 14:50:40', b'0');
+INSERT INTO `system_menu` VALUES (1201, '我的流程', '', 2, 0, 1200, 'my', 'people', 'bpm/processInstance/index', 0, b'1', b'1', '', '2022-01-07 15:53:44', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1202, '流程实例的查询', 'bpm:process-instance:query', 3, 1, 1201, '', '', '', 0, b'1', b'1', '', '2022-01-07 15:53:44', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1207, '待办任务', '', 2, 10, 1200, 'todo', 'eye-open', 'bpm/task/todo', 0, b'1', b'1', '1', '2022-01-08 10:33:37', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1208, '已办任务', '', 2, 20, 1200, 'done', 'eye', 'bpm/task/done', 0, b'1', b'1', '1', '2022-01-08 10:34:13', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1209, '用户分组', '', 2, 2, 1186, 'user-group', 'people', 'bpm/group/index', 0, b'1', b'1', '', '2022-01-14 02:14:20', '103', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1210, '用户组查询', 'bpm:user-group:query', 3, 1, 1209, '', '', '', 0, b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1211, '用户组创建', 'bpm:user-group:create', 3, 2, 1209, '', '', '', 0, b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1212, '用户组更新', 'bpm:user-group:update', 3, 3, 1209, '', '', '', 0, b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1213, '用户组删除', 'bpm:user-group:delete', 3, 4, 1209, '', '', '', 0, b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1215, '流程定义查询', 'bpm:process-definition:query', 3, 10, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:21:43', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1216, '流程任务分配规则查询', 'bpm:task-assign-rule:query', 3, 20, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:26:53', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1217, '流程任务分配规则创建', 'bpm:task-assign-rule:create', 3, 21, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:28:15', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1218, '流程任务分配规则更新', 'bpm:task-assign-rule:update', 3, 22, 1193, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:28:41', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1219, '流程实例的创建', 'bpm:process-instance:create', 3, 2, 1201, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:36:15', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1220, '流程实例的取消', 'bpm:process-instance:cancel', 3, 3, 1201, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:36:33', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1221, '流程任务的查询', 'bpm:task:query', 3, 1, 1207, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:38:52', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1222, '流程任务的更新', 'bpm:task:update', 3, 2, 1207, '', '', '', 0, b'1', b'1', '1', '2022-01-23 00:39:24', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1224, '租户管理', '', 2, 0, 1, 'tenant', 'ep:cherry', NULL, 0, b'1', b'1', '1', '2022-02-20 01:41:13', '1', '2022-07-20 14:51:46', b'0');
+INSERT INTO `system_menu` VALUES (1225, '租户套餐', '', 2, 0, 1224, 'package', 'eye', 'system/tenantPackage/index', 0, b'1', b'1', '', '2022-02-19 17:44:06', '1', '2022-04-21 01:21:25', b'0');
+INSERT INTO `system_menu` VALUES (1226, '租户套餐查询', 'system:tenant-package:query', 3, 1, 1225, '', '', '', 0, b'1', b'1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1227, '租户套餐创建', 'system:tenant-package:create', 3, 2, 1225, '', '', '', 0, b'1', b'1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1228, '租户套餐更新', 'system:tenant-package:update', 3, 3, 1225, '', '', '', 0, b'1', b'1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1229, '租户套餐删除', 'system:tenant-package:delete', 3, 4, 1225, '', '', '', 0, b'1', b'1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1237, '文件配置', '', 2, 0, 1243, 'file-config', 'config', 'infra/fileConfig/index', 0, b'1', b'1', '', '2022-03-15 14:35:28', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1238, '文件配置查询', 'infra:file-config:query', 3, 1, 1237, '', '', '', 0, b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1239, '文件配置创建', 'infra:file-config:create', 3, 2, 1237, '', '', '', 0, b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1240, '文件配置更新', 'infra:file-config:update', 3, 3, 1237, '', '', '', 0, b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', 0, b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', 0, b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1243, '文件管理', '', 2, 5, 2, 'file', 'ep:folder-opened', NULL, 0, b'1', b'1', '1', '2022-03-16 23:47:40', '1', '2022-07-20 14:48:19', b'0');
+INSERT INTO `system_menu` VALUES (1247, '敏感词管理', '', 2, 13, 1, 'sensitive-word', 'ep:document-copy', 'system/sensitiveWord/index', 0, b'1', b'1', '', '2022-04-07 16:55:03', '1', '2022-07-20 14:43:53', b'0');
+INSERT INTO `system_menu` VALUES (1248, '敏感词查询', 'system:sensitive-word:query', 3, 1, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1249, '敏感词创建', 'system:sensitive-word:create', 3, 2, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1250, '敏感词更新', 'system:sensitive-word:update', 3, 3, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1251, '敏感词删除', 'system:sensitive-word:delete', 3, 4, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1252, '敏感词导出', 'system:sensitive-word:export', 3, 5, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:bell-filled', NULL, 0, b'1', b'1', '1', '2022-04-23 01:03:15', '1', '2022-07-20 13:12:43', b'0');
+INSERT INTO `system_menu` VALUES (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'ep:coin', 'infra/dataSourceConfig/index', 0, b'1', b'1', '', '2022-04-27 14:37:32', '1', '2022-07-20 14:49:01', b'0');
+INSERT INTO `system_menu` VALUES (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', 0, b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
+INSERT INTO `system_menu` VALUES (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', 0, b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
+INSERT INTO `system_menu` VALUES (1258, '数据源配置更新', 'infra:data-source-config:update', 3, 3, 1255, '', '', '', 0, b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
+INSERT INTO `system_menu` VALUES (1259, '数据源配置删除', 'infra:data-source-config:delete', 3, 4, 1255, '', '', '', 0, b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
+INSERT INTO `system_menu` VALUES (1260, '数据源配置导出', 'infra:data-source-config:export', 3, 5, 1255, '', '', '', 0, b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
+INSERT INTO `system_menu` VALUES (1261, '授权管理', '', 1, 10, 1, 'oauth2', 'ep:connection', NULL, 0, b'1', b'1', '1', '2022-05-09 23:38:17', '1', '2022-07-20 14:43:23', b'0');
+INSERT INTO `system_menu` VALUES (1263, '应用管理', '', 2, 0, 1261, 'oauth2/application', 'tool', 'system/oauth2/client/index', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 23:31:36', b'0');
+INSERT INTO `system_menu` VALUES (1264, '客户端查询', 'system:oauth2-client:query', 3, 1, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:06', b'0');
+INSERT INTO `system_menu` VALUES (1265, '客户端创建', 'system:oauth2-client:create', 3, 2, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:23', b'0');
+INSERT INTO `system_menu` VALUES (1266, '客户端更新', 'system:oauth2-client:update', 3, 3, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:28', b'0');
+INSERT INTO `system_menu` VALUES (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', b'0');
+INSERT INTO `system_menu` VALUES (1281, '可视化报表', '', 1, 12, 0, '/visualization', 'ep:histogram', NULL, 0, b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2022-07-10 20:33:30', b'0');
+INSERT INTO `system_menu` VALUES (1282, '积木报表', '', 2, 1, 1281, 'jimu-report', 'ep:histogram', 'visualization/jmreport/index', 0, b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2022-07-28 21:17:34', b'0');
+
+SET FOREIGN_KEY_CHECKS = 1;

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 54 - 774
sql/mysql/ruoyi-vue-pro.sql


+ 85 - 0
sql/optional/mall/coupon.sql

@@ -0,0 +1,85 @@
+DROP TABLE IF EXISTS `coupon`;
+CREATE TABLE `coupon`
+(
+    `id`                        bigint                                                         NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `type`                      varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci  NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
+    `name`                      varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci   NOT NULL COMMENT '优惠券名称',
+    `coupon_type_id`            bigint UNSIGNED                                                         DEFAULT 0 COMMENT '优惠券类型id',
+    `coupon_code`               varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci  NOT NULL COMMENT '优惠券编码',
+    `member_id`                 bigint UNSIGNED                                                NOT NULL DEFAULT 0 COMMENT '领用人',
+    `use_order_id`              bigint UNSIGNED                                                NOT NULL DEFAULT 0 COMMENT '优惠券使用订单id',
+    `goods_type`                tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用',
+    `goods_ids`                 varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '适用商品id',
+    `at_least`                  decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '最小金额',
+    `money`                     decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '面额',
+    `discount`                  decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
+    `discount_limit`            decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
+    `whether_forbid_preference` tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
+    `whether_expire_notice`     tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
+    `expire_notice_fixed_term`  int(11) UNSIGNED                                               NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
+    `whether_noticed`           tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '是否已提醒',
+    `state`                     tinyint(4) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '优惠券状态 1已领用(未使用) 2已使用 3已过期',
+    `get_type`                  tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取',
+    `fetch_time`                datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '领取时间',
+    `use_time`                  datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '使用时间',
+    `start_time`                datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '可使用的开始时间',
+    `end_time`                  datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '有效期结束时间',
+    `creator`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci   NULL     DEFAULT '' COMMENT '创建者',
+    `create_time`               datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci   NULL     DEFAULT '' COMMENT '更新者',
+    `update_time`               datetime                                                       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`                   bit(1)                                                         NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`                 bigint                                                         NOT NULL DEFAULT 0 COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB
+  AUTO_INCREMENT = 119
+  CHARACTER SET = utf8mb4
+  COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券';
+
+DROP TABLE IF EXISTS `coupon_templete`;
+CREATE TABLE `coupon_templete`
+(
+    `id`                        bigint                                                       NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `type`                      varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
+    `name`                      varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券名称',
+    `coupon_name_remark`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '名称备注',
+    `image`                     varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '优惠券图片',
+    `count`                     int(11)                                                      NOT NULL DEFAULT 0 COMMENT '发放数量',
+    `lead_count`                int(11)                                                      NOT NULL DEFAULT 0 COMMENT '已领取数量',
+    `used_count`                int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '已使用数量',
+    `goods_type`                tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 1 COMMENT '适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用',
+    `product_ids`               varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '适用商品id',
+    `has_use_limit`             tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '使用门槛0-无门槛 1-有门槛',
+    `at_least`                  decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '满多少元使用 0代表无限制',
+    `money`                     decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '发放面额 当type为reward时需要添加',
+    `discount`                  decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
+    `discount_limit`            decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
+    `min_money`                 decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '最低金额 当type为radom时需要添加',
+    `max_money`                 decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '最大金额 当type为radom时需要添加',
+    `validity_type`             tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期',
+    `start_use_time`            datetime COMMENT '使用开始日期 过期类型1时必填',
+    `end_use_time`              datetime COMMENT '使用结束日期 过期类型1时必填',
+    `fixed_term`                int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效',
+    `whether_limitless`         tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否无限制0-否 1是',
+    `max_fetch`                 int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '每人最大领取个数',
+    `whether_expire_notice`     tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
+    `expire_notice_fixed_term`  int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
+    `whether_forbid_preference` tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
+    `whether_show`              int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '是否显示',
+    `discount_order_money`      decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '订单的优惠总金额',
+    `order_money`               decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '用券总成交额',
+    `whether_forbidden`         tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否禁止发放0-否 1-是',
+    `order_goods_num`           int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '使用优惠券购买的商品数量',
+    `status`                    tinyint(11)                                                  NOT NULL DEFAULT 0 COMMENT '状态(1进行中2已结束-1已关闭)',
+    `end_time`                  datetime COMMENT '有效日期结束时间',
+    `creator`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL     DEFAULT '' COMMENT '创建者',
+    `create_time`               datetime                                                     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`                   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL     DEFAULT '' COMMENT '更新者',
+    `update_time`               datetime                                                     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`                   bit(1)                                                       NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`                 bigint                                                       NOT NULL DEFAULT 0 COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB
+  AUTO_INCREMENT = 119
+  CHARACTER SET = utf8mb4
+  COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券模板';

+ 315 - 0
sql/optional/mall/mall.sql

@@ -0,0 +1,315 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server         : 127.0.0.1 MySQL
+ Source Server Type    : MySQL
+ Source Server Version : 80026
+ Source Host           : localhost:3306
+ Source Schema         : ruoyi-vue-pro
+
+ Target Server Type    : MySQL
+ Target Server Version : 80026
+ File Encoding         : 65001
+
+ Date: 01/08/2022 23:01:36
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for market_activity
+-- ----------------------------
+DROP TABLE IF EXISTS `market_activity`;
+CREATE TABLE `market_activity`  (
+                                    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '活动编号',
+                                    `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '活动标题',
+                                    `activity_type` tinyint NOT NULL COMMENT '活动类型',
+                                    `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
+                                    `start_time` datetime NOT NULL COMMENT '开始时间',
+                                    `end_time` datetime NOT NULL COMMENT '结束时间',
+                                    `invalid_time` datetime NULL DEFAULT NULL COMMENT '失效时间',
+                                    `delete_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
+                                    `time_limited_discount` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
+                                    `full_privilege` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
+                                    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                    `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '促销活动';
+
+-- ----------------------------
+-- Records of market_activity
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for market_banner
+-- ----------------------------
+DROP TABLE IF EXISTS `market_banner`;
+CREATE TABLE `market_banner`  (
+                                  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Banner编号',
+                                  `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Banner标题',
+                                  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片URL',
+                                  `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
+                                  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '跳转地址',
+                                  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                  `sort` tinyint NULL DEFAULT NULL COMMENT '排序',
+                                  `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '描述',
+                                  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Banner管理';
+
+-- ----------------------------
+-- Records of market_banner
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for member_address
+-- ----------------------------
+DROP TABLE IF EXISTS `member_address`;
+CREATE TABLE `member_address`  (
+                                   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '收件地址编号',
+                                   `user_id` bigint NOT NULL COMMENT '用户编号',
+                                   `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收件人名称',
+                                   `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号',
+                                   `area_id` bigint NOT NULL COMMENT '地区编码',
+                                   `post_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮编',
+                                   `detail_address` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收件详细地址',
+                                   `defaulted` bit(1) NOT NULL COMMENT '是否默认',
+                                   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                   `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                   PRIMARY KEY (`id`) USING BTREE,
+                                   INDEX `idx_userId`(`user_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户收件地址';
+
+-- ----------------------------
+-- Records of member_address
+-- ----------------------------
+BEGIN;
+INSERT INTO `member_address` (`id`, `user_id`, `name`, `mobile`, `area_id`, `post_code`, `detail_address`, `defaulted`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (21, 1, 'yunai', '15601691300', 610632, '200000', '芋道源码 233 号 666 室', b'1', '1', '2022-08-01 22:46:35', '1', '2022-08-01 22:46:35', b'0', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for product_brand
+-- ----------------------------
+DROP TABLE IF EXISTS `product_brand`;
+CREATE TABLE `product_brand`  (
+                                  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
+                                  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌名称',
+                                  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌图片',
+                                  `sort` int NULL DEFAULT 0 COMMENT '品牌排序',
+                                  `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '品牌描述',
+                                  `status` tinyint NOT NULL COMMENT '状态',
+                                  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品品牌';
+
+-- ----------------------------
+-- Records of product_brand
+-- ----------------------------
+BEGIN;
+INSERT INTO `product_brand` (`id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '苹果', 'http://test.yudao.iocoder.cn/e3726713fa56db5717c78c011762fcc7a251db12735c3581470638b8e1fa17e2.jpeg', 0, '是上市', 0, '1', '2022-07-30 22:12:18', '1', '2022-07-30 22:13:55', b'0', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for product_category
+-- ----------------------------
+DROP TABLE IF EXISTS `product_category`;
+CREATE TABLE `product_category`  (
+                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号',
+                                     `parent_id` bigint NOT NULL COMMENT '父分类编号',
+                                     `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
+                                     `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类图片',
+                                     `sort` int NULL DEFAULT 0 COMMENT '分类排序',
+                                     `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '分类描述',
+                                     `status` tinyint NOT NULL COMMENT '开启状态',
+                                     `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                     `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                     `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                     `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                     `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                     `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                     PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品分类';
+
+-- ----------------------------
+-- Records of product_category
+-- ----------------------------
+BEGIN;
+INSERT INTO `product_category` (`id`, `parent_id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 0, '电脑办公', 'http://test.yudao.iocoder.cn/122d548e1b3cd5dec72fe8075c6977a70f9cc13541a684ab3685f1b5df42f6bd.jpeg', 1, '1234', 0, '1', '2022-07-30 16:36:35', '1', '2022-07-30 20:27:16', b'0', 1), (2, 1, '笔记本', 'http://test.yudao.iocoder.cn/72713ac7b947600a019a18786ed0e6562e8692e253dbd35110a0a85c2469bbec.jpg', 1, '<p>测试一下</p>', 0, '1', '2022-07-30 16:38:09', '1', '2022-07-30 16:38:09', b'0', 1), (3, 1, '游戏本', 'http://test.yudao.iocoder.cn/287c50dd9f5f575f57329a0c57b2095be6d1aeba83867b905fe549f54a296feb.jpg', 2, '<p>测试一下</p>', 0, '1', '2022-07-30 16:39:09', '1', '2022-07-30 20:26:59', b'0', 1), (4, 0, '手机', 'http://test.yudao.iocoder.cn/e1b63900c78dbb661b3e383960cee5cfea7e1dd2fb22cff2e317ff025faaf8b2.jpeg', 2, '<p>123</p>', 0, '1', '2022-07-30 16:40:00', '1', '2022-07-30 16:40:09', b'0', 1), (5, 4, '5G手机', 'http://test.yudao.iocoder.cn/3af6557ac7def6423f046f5b2e920b644793420b466959aaa996a2e19068bbde.jpeg', 1, '<p><br></p>', 0, '1', '2022-07-30 16:43:00', '1', '2022-07-30 16:43:00', b'0', 1), (6, 4, '游戏手机', 'http://test.yudao.iocoder.cn/964fe9ccd1710d64ede261dc36d231918a017641986c15293c367f9f66d94d05.jpeg', 2, NULL, 0, '1', '2022-07-30 16:43:44', '1', '2022-07-30 16:43:44', b'0', 1), (7, 5, '厉害的 5G 手机', 'http://test.yudao.iocoder.cn/b287122f277838e8de368769b96217918605743bc45f3a29bda3cc7359dc66e1.png', 0, '123', 0, '1', '2022-07-30 20:38:09', '1', '2022-07-30 20:38:09', b'0', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for product_property
+-- ----------------------------
+DROP TABLE IF EXISTS `product_property`;
+CREATE TABLE `product_property`  (
+                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                     `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格名称',
+                                     `status` tinyint NULL DEFAULT NULL COMMENT '状态: 0 开启 ,1 禁用',
+                                     `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                     `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                     `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
+                                     `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
+                                     `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                     `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                     PRIMARY KEY (`id`) USING BTREE,
+                                     INDEX `idx_name`(`name`(32) ASC) USING BTREE COMMENT '规格名称索引'
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格名称';
+
+-- ----------------------------
+-- Records of product_property
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for product_property_value
+-- ----------------------------
+DROP TABLE IF EXISTS `product_property_value`;
+CREATE TABLE `product_property_value`  (
+                                           `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                           `property_id` bigint NULL DEFAULT NULL COMMENT '规格键id',
+                                           `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格值名字',
+                                           `status` tinyint NULL DEFAULT NULL COMMENT '状态: 1 开启 ,2 禁用',
+                                           `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                           `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                           `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
+                                           `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
+                                           `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                           `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                           PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格值';
+
+-- ----------------------------
+-- Records of product_property_value
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for product_sku
+-- ----------------------------
+DROP TABLE IF EXISTS `product_sku`;
+CREATE TABLE `product_sku` (
+                               `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                               `spu_id` bigint NOT NULL COMMENT 'spu编号',
+                               `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
+                               `name` varchar(128)  DEFAULT NULL COMMENT '商品 SKU 名字',
+                               `properties` varchar(128)  DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
+                               `price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',
+                               `market_price` int DEFAULT NULL COMMENT '市场价',
+                               `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
+                               `pic_url` varchar(128)  NOT NULL COMMENT '图片地址',
+                               `stock` int DEFAULT NULL COMMENT '库存',
+                               `warn_stock` int DEFAULT NULL COMMENT '预警库存',
+                               `volume` double DEFAULT NULL COMMENT '商品体积',
+                               `weight` double DEFAULT NULL COMMENT '商品重量',
+                               `bar_code` varchar(64)  DEFAULT NULL COMMENT '条形码',
+                               `status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',
+                               `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                               `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                               `creator` varchar(64) DEFAULT NULL COMMENT '创建人',
+                               `updater` double(64,0) DEFAULT NULL COMMENT '更新人',
+`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='商品sku';
+
+-- ----------------------------
+-- Records of product_sku
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for product_spu
+-- ----------------------------
+DROP TABLE IF EXISTS `product_spu`;
+CREATE TABLE `product_spu` (
+                               `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                               `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
+                               `brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',
+                               `category_id` bigint NOT NULL COMMENT '分类id',
+                               `spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
+                               `code` varchar(128)  DEFAULT NULL COMMENT '商品编码',
+                               `name` varchar(128)  NOT NULL COMMENT '商品名称',
+                               `sell_point` varchar(128)  DEFAULT NULL COMMENT '卖点',
+                               `description` text  COMMENT '描述',
+                               `pic_urls` varchar(1024)  DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张',
+                               `video_url` varchar(128)  DEFAULT NULL COMMENT '商品视频',
+                               `market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',
+                               `min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',
+                               `max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',
+                               `total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
+                               `show_stock` int DEFAULT '0' COMMENT '是否展示库存',
+                               `sales_count` int DEFAULT '0' COMMENT '商品销量',
+                               `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
+                               `click_count` int DEFAULT '0' COMMENT '商品点击量',
+                               `status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',
+                               `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
+                               `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                               `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                               `creator` varchar(64)  DEFAULT NULL COMMENT '创建人',
+                               `updater` varchar(64)  DEFAULT NULL COMMENT '更新人',
+                               `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                               PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='商品spu';
+
+-- ----------------------------
+-- Records of product_spu
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2000, '商品中心', '', 1, 60, 0, '/product', 'merchant', NULL, 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:26:19', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2002, '商品分类', '', 2, 2, 2000, 'category', 'dict', 'mall/product/category/index', 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:23:37', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2003, '分类查询', 'product:category:query', 3, 1, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2004, '分类创建', 'product:category:create', 3, 2, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2005, '分类更新', 'product:category:update', 3, 3, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2006, '分类删除', 'product:category:delete', 3, 4, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2007, '分类导出', 'product:category:export', 3, 5, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-30 13:52:13', b'1');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2008, '商品品牌', '', 2, 1, 2000, 'brand', 'dashboard', 'mall/product/brand/index', 0, b'1', b'1', '', '2022-07-30 13:52:44', '1', '2022-07-30 22:23:43', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2009, '品牌查询', 'product:brand:query', 3, 1, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2010, '品牌创建', 'product:brand:create', 3, 2, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2011, '品牌更新', 'product:brand:update', 3, 3, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2012, '品牌删除', 'product:brand:delete', 3, 4, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2013, '品牌导出', 'product:brand:export', 3, 5, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 14:15:00', b'1');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2014, '商品管理', '', 2, 0, 2000, 'spu', 'link', 'mall/product/spu/index', 0, b'1', b'1', '', '2022-07-30 14:22:58', '1', '2022-07-30 22:26:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2015, '商品查询', 'product:spu:query', 3, 1, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2016, '商品创建', 'product:spu:create', 3, 2, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2017, '商品更新', 'product:spu:update', 3, 3, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2018, '商品删除', 'product:spu:delete', 3, 4, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2019, '规格管理', '', 2, 3, 2000, 'property', '', 'mall/product/property/index', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2020, '规格查询', 'product:property:query', 3, 1, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2021, '规格创建', 'product:property:create', 3, 2, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2022, '规格更新', 'product:property:update', 3, 3, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2023, '规格删除', 'product:property:delete', 3, 4, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2024, '规格导出', 'product:property:export', 3, 5, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2025, 'Banner管理', '', 2, 1, 2000, 'banner', '', 'mall/market/banner/index', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2026, 'Banner查询', 'market:banner:query', 3, 1, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2027, 'Banner创建', 'market:banner:create', 3, 2, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2028, 'Banner更新', 'market:banner:update', 3, 3, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2029, 'Banner删除', 'market:banner:delete', 3, 4, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+

+ 78 - 0
sql/optional/mall/order.sql

@@ -0,0 +1,78 @@
+/**todo cancelType 设置默认值 0?*/
+DROP TABLE IF EXISTS `trade_order`;
+CREATE TABLE `trade_order`
+(
+    `id`                      bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `sn`                      varchar(32)     NOT NULL COMMENT '订单流水号',
+    `type`                    int             NOT NULL DEFAULT '0' COMMENT '订单类型:[0:普通订单 1:秒杀订单 2:拼团订单 3:砍价订单]',
+    `terminal`                int             NOT NULL COMMENT '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]',
+    `user_id`                 bigint unsigned NOT NULL COMMENT '用户编号',
+    `user_ip`                 varchar(30)     NOT NULL DEFAULT '' COMMENT '用户 IP',
+    `user_remark`             varchar(200)             DEFAULT NULL COMMENT '用户备注',
+    `status`                  int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:待付款 1:待发货 2:待收货 3:已完成 4:已关闭]',
+    `product_count`           int             NOT NULL COMMENT '购买的商品数量',
+    `cancel_type`             int             DEFAULT NULL COMMENT '取消类型:[10:超时未支付 20:退款关闭 30:买家取消 40:已通过货到付款交易]',
+    `remark`                  varchar(200)             DEFAULT NULL COMMENT '商家备注',
+    `payed`                   bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否已支付:[0:未支付 1:已经支付过]',
+    `pay_time`                datetime                 DEFAULT NULL COMMENT '订单支付时间',
+    `finish_time`             datetime                 DEFAULT NULL COMMENT '订单完成时间',
+    `cancel_time`             datetime                 DEFAULT NULL COMMENT '订单取消时间',
+    `sku_original_price`      int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
+    `sku_promotion_price`     int             NOT NULL DEFAULT '0' COMMENT '商品优惠(总),单位:分',
+    `order_promotion_price`   int             NOT NULL DEFAULT '0' COMMENT '订单优惠(总),单位:分',
+    `delivery_price`          int             NOT NULL DEFAULT '0' COMMENT '运费金额,单位:分',
+    `pay_price`               int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
+    `pay_order_id`            int             DEFAULT NULL COMMENT '支付订单编号',
+    `pay_channel`             int             DEFAULT NULL COMMENT '支付成功的支付渠道',
+    `delivery_type`           int             DEFAULT NULL DEFAULT '1' COMMENT '配送方式:[1:快递发货 2:自提]',
+    `actual_delivery_type`    int             DEFAULT NULL DEFAULT '1' COMMENT '实际的配送方式:[1:快递发货 2:自提]',
+    `delivery_template_id`     int                      DEFAULT NULL COMMENT '配置模板的编号',
+    `express_no`              int                      DEFAULT NULL COMMENT '物流公司单号',
+    `delivery_status`         bit(1)          NOT NULL DEFAULT b'0' COMMENT '发货状态[0:未发货 1:已发货]',
+    `delivery_time`           datetime                 DEFAULT NULL COMMENT '发货时间',
+    `receive_time`            datetime                 DEFAULT NULL COMMENT '收货时间',
+    `receiver_name`           varchar(20)     NOT NULL COMMENT '收件人名称',
+    `receiver_mobile`         varchar(20)     NOT NULL COMMENT '收件人手机',
+    `receiver_area_id`        int             NOT NULL COMMENT '收件人地区编号',
+    `receiver_post_code`      int                      DEFAULT NULL COMMENT '收件人邮编',
+    `receiver_detail_address` varchar(255)    NOT NULL COMMENT '收件人详细地址',
+    `refund_status`           int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:未退款 1:部分退款 2:全部退款]',
+    `refund_price`            int             NOT NULL DEFAULT '0' COMMENT '退款金额,单位:分',
+    `coupon_id`               bigint unsigned NOT NULL COMMENT '优惠劵编号',
+    `creator`                 varchar(64)              DEFAULT '' COMMENT '创建者',
+    `create_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`                 varchar(64)              DEFAULT '' COMMENT '更新者',
+    `update_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`                 bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='交易订单表';
+
+
+DROP TABLE IF EXISTS `trade_order_item`;
+CREATE TABLE `trade_order_item`
+(
+    `id`                    bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `user_id`               bigint unsigned NOT NULL COMMENT '用户编号',
+    `order_Id`              bigint unsigned NOT NULL COMMENT '订单编号',
+    `spu_id`                bigint unsigned NOT NULL COMMENT '商品 SPU 编号',
+    `sku_id`                bigint unsigned NOT NULL COMMENT '商品 SKU 编号',
+    `properties`            json                     DEFAULT NULL COMMENT '规格值数组,JSON 格式',
+    `name`                  varchar(128)    NOT NULL DEFAULT '' COMMENT '商品名称',
+    `pic_url`               varchar(200)             DEFAULT NULL COMMENT '商品图片',
+    `count`                 int             NOT NULL COMMENT '购买数量',
+    `commented`             bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否评论:[0:未评论 1:已评论]',
+    `original_price`        int             NOT NULL DEFAULT '0' COMMENT '商品原价(单),单位:分',
+    `total_original_price`  int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
+    `total_promotion_price` int             NOT NULL DEFAULT '0' COMMENT '商品级优惠(总),单位:分',
+    `present_price`         int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(单),单位:分。',
+    `total_present_price`   int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(总),单位:分。',
+    `total_pay_price`       int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
+    `refund_status`         int             NOT NULL DEFAULT '0' COMMENT '退款状态:[0:未申请退款 1:申请退款 2:等待退款 3:退款成功]',
+    `refund_total`          int             NOT NULL DEFAULT '0' COMMENT '退款总金额,单位:分',
+    `creator`               varchar(64)              DEFAULT '' COMMENT '创建者',
+    `create_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`               varchar(64)              DEFAULT '' COMMENT '更新者',
+    `update_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`               bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='交易订单明细表';

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 357 - 0
sql/optional/visualization/jimureport.mysql5.7.create.sql


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 211 - 188
sql/oracle/ruoyi-vue-pro.sql


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 57 - 79
sql/postgresql/ruoyi-vue-pro.sql


+ 143 - 106
sql/sqlserver/ruoyi-vue-pro.sql

@@ -12,7 +12,7 @@
  Target Server Version : 15004198
  File Encoding         : 65001
 
- Date: 26/05/2022 01:01:02
+ Date: 15/06/2022 08:15:45
 */
 
 
@@ -2634,13 +2634,14 @@ CREATE TABLE [dbo].[infra_file] (
   [config_id] bigint  NULL,
   [path] nvarchar(512) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
   [url] nvarchar(1024) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
-  [type] nvarchar(63) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
+  [type] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
   [size] int  NOT NULL,
   [creator] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
   [create_time] datetime2(7)  NOT NULL,
   [updater] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
   [update_time] datetime2(7)  NOT NULL,
-  [deleted] bit DEFAULT 0 NOT NULL
+  [deleted] bit DEFAULT 0 NOT NULL,
+  [name] nvarchar(512) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL
 )
 GO
 
@@ -2676,7 +2677,7 @@ EXEC sp_addextendedproperty
 GO
 
 EXEC sp_addextendedproperty
-'MS_Description', N'文件类型',
+'MS_Description', N'文件 MIME 类型',
 'SCHEMA', N'dbo',
 'TABLE', N'infra_file',
 'COLUMN', N'type'
@@ -2724,6 +2725,13 @@ EXEC sp_addextendedproperty
 'COLUMN', N'deleted'
 GO
 
+EXEC sp_addextendedproperty
+'MS_Description', N'文件路径',
+'SCHEMA', N'dbo',
+'TABLE', N'infra_file',
+'COLUMN', N'name'
+GO
+
 EXEC sp_addextendedproperty
 'MS_Description', N'文件表',
 'SCHEMA', N'dbo',
@@ -5537,11 +5545,7 @@ GO
 INSERT INTO [dbo].[system_dict_data] ([id], [sort], [label], [value], [dict_type], [status], [color_type], [css_class], [remark], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'63', N'1', N'已处理', N'1', N'infra_api_error_log_process_status', N'0', N'success', N'', NULL, N'', N'2021-02-26 07:07:26.0000000', N'1', N'2022-02-16 20:14:08.0000000', N'0')
 GO
 
-INSERT INTO [dbo].[system_dict_data] ([id], [sort], [label], [value], [dict_type], [status], [color_type], [css_class], [remark], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'64', N'2', N'已忽略', N'2', N'infra_api_error_log_process_status', N'0', N'danger', N'', NULL, N'', N'2021-02-26 07:07:34.0000000', N'1', N'2022-02-16 20:14:14.0000000', N'0')
-GO
 
-INSERT INTO [dbo].[system_dict_data] ([id], [sort], [label], [value], [dict_type], [status], [color_type], [css_class], [remark], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'65', N'1', N'云片', N'YUN_PIAN', N'system_sms_channel_code', N'0', N'success', N'', NULL, N'1', N'2021-04-05 01:05:14.0000000', N'1', N'2022-02-16 10:09:55.0000000', N'0')
-GO
 
 INSERT INTO [dbo].[system_dict_data] ([id], [sort], [label], [value], [dict_type], [status], [color_type], [css_class], [remark], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'66', N'2', N'阿里云', N'ALIYUN', N'system_sms_channel_code', N'0', N'primary', N'', NULL, N'1', N'2021-04-05 01:05:26.0000000', N'1', N'2022-02-16 10:09:52.0000000', N'0')
 GO
@@ -5809,18 +5813,51 @@ IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[sy
 	DROP TABLE [dbo].[system_dict_type]
 GO
 
-CREATE TABLE [dbo].[system_dict_type] (
-  [id] bigint  IDENTITY(1,1) NOT NULL,
-  [name] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
-  [type] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
-  [status] tinyint  NOT NULL,
-  [remark] nvarchar(500) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
-  [creator] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
-  [create_time] datetime2(7)  NOT NULL,
-  [updater] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
-  [update_time] datetime2(7)  NOT NULL,
-  [deleted] bit DEFAULT 0 NOT NULL
-)
+CREATE TABLE [dbo].[system_dict_type]
+(
+    [
+    id]
+    bigint
+    IDENTITY
+(
+    1,
+    1
+) NOT NULL,
+    [name] nvarchar
+(
+    100
+) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
+    [type] nvarchar
+(
+    100
+) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
+    [status] tinyint NOT NULL,
+    [remark] nvarchar
+(
+    500
+) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
+    [creator] nvarchar
+(
+    64
+) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
+    [create_time] datetime2
+(
+    7
+) NOT NULL,
+    [updater] nvarchar
+(
+    64
+) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
+    [update_time] datetime2
+(
+    7
+) NOT NULL,
+    [deleted_time] datetime2
+(
+    7
+),
+    [deleted] bit DEFAULT 0 NOT NULL
+    )
 GO
 
 ALTER TABLE [dbo].[system_dict_type] SET (LOCK_ESCALATION = TABLE)
@@ -5878,26 +5915,29 @@ GO
 EXEC sp_addextendedproperty
 'MS_Description', N'更新者',
 'SCHEMA', N'dbo',
-'TABLE', N'system_dict_type',
-'COLUMN', N'updater'
-GO
-
-EXEC sp_addextendedproperty
-'MS_Description', N'更新时间',
-'SCHEMA', N'dbo',
-'TABLE', N'system_dict_type',
-'COLUMN', N'update_time'
-GO
-
-EXEC sp_addextendedproperty
-'MS_Description', N'是否删除',
-'SCHEMA', N'dbo',
-'TABLE', N'system_dict_type',
-'COLUMN', N'deleted'
-GO
-
-EXEC sp_addextendedproperty
-'MS_Description', N'字典类型表',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'updater'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'更新时间',
+    'SCHEMA', N'dbo',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'update_time'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'删除时间',
+    'SCHEMA', N'dbo',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'deleted_time'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'是否删除',
+    'SCHEMA', N'dbo',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'deleted'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'字典类型表',
 'SCHEMA', N'dbo',
 'TABLE', N'system_dict_type'
 GO
@@ -9490,7 +9530,7 @@ GO
 
 CREATE TABLE [dbo].[system_sms_channel] (
   [id] bigint  IDENTITY(1,1) NOT NULL,
-  [signature] nvarchar(10) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
+  [signature] nvarchar(12) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
   [code] nvarchar(63) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
   [status] tinyint  NOT NULL,
   [remark] nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
@@ -9615,9 +9655,6 @@ GO
 SET IDENTITY_INSERT [dbo].[system_sms_channel] ON
 GO
 
-INSERT INTO [dbo].[system_sms_channel] ([id], [signature], [code], [status], [remark], [api_key], [api_secret], [callback_url], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'1', N'芋道', N'YUN_PIAN', N'0', N'呵呵呵哒', N'1555a14277cb8a608cf45a9e6a80d510', NULL, N'http://vdwapu.natappfree.cc/admin-api/system/sms/callback/yunpian', N'', N'2021-03-31 06:12:20.0000000', N'1', N'2022-02-23 16:48:44.0000000', N'0')
-GO
-
 INSERT INTO [dbo].[system_sms_channel] ([id], [signature], [code], [status], [remark], [api_key], [api_secret], [callback_url], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'2', N'Ballcat', N'ALIYUN', N'0', N'啦啦啦', N'LTAI5tCnKso2uG3kJ5gRav88', N'fGJ5SNXL7P1NHNRmJ7DJaMJGPyE55C', NULL, N'', N'2021-03-31 11:53:10.0000000', N'1', N'2021-04-14 00:08:37.0000000', N'0')
 GO
 
@@ -11340,7 +11377,7 @@ GO
 -- Primary Key structure for table QRTZ_CALENDARS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_CALENDARS] ADD CONSTRAINT [PK_QRTZ_CALENDARS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [CALENDAR_NAME])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11361,7 +11398,7 @@ GO
 -- Primary Key structure for table QRTZ_CRON_TRIGGERS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_CRON_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11370,7 +11407,7 @@ GO
 -- Primary Key structure for table QRTZ_FIRED_TRIGGERS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_FIRED_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_FIRED_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [ENTRY_ID])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11379,7 +11416,7 @@ GO
 -- Primary Key structure for table QRTZ_JOB_DETAILS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_JOB_DETAILS] ADD CONSTRAINT [PK_QRTZ_JOB_DETAILS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [JOB_NAME], [JOB_GROUP])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11388,7 +11425,7 @@ GO
 -- Primary Key structure for table QRTZ_LOCKS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_LOCKS] ADD CONSTRAINT [PK_QRTZ_LOCKS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [LOCK_NAME])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11397,7 +11434,7 @@ GO
 -- Primary Key structure for table QRTZ_PAUSED_TRIGGER_GRPS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] ADD CONSTRAINT [PK_QRTZ_PAUSED_TRIGGER_GRPS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_GROUP])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11406,7 +11443,7 @@ GO
 -- Primary Key structure for table QRTZ_SCHEDULER_STATE
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_SCHEDULER_STATE] ADD CONSTRAINT [PK_QRTZ_SCHEDULER_STATE] PRIMARY KEY CLUSTERED ([SCHED_NAME], [INSTANCE_NAME])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11427,7 +11464,7 @@ GO
 -- Primary Key structure for table QRTZ_SIMPLE_TRIGGERS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_SIMPLE_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11448,7 +11485,7 @@ GO
 -- Primary Key structure for table QRTZ_SIMPROP_TRIGGERS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_SIMPROP_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11469,7 +11506,7 @@ GO
 -- Primary Key structure for table QRTZ_TRIGGERS
 -- ----------------------------
 ALTER TABLE [dbo].[QRTZ_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11485,7 +11522,7 @@ GO
 -- Primary Key structure for table bpm_form
 -- ----------------------------
 ALTER TABLE [dbo].[bpm_form] ADD CONSTRAINT [PK__bpm_form__3213E83F86C2B27F] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11501,7 +11538,7 @@ GO
 -- Primary Key structure for table bpm_oa_leave
 -- ----------------------------
 ALTER TABLE [dbo].[bpm_oa_leave] ADD CONSTRAINT [PK__bpm_oa_l__3213E83F3569F596] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11517,7 +11554,7 @@ GO
 -- Primary Key structure for table bpm_process_definition_ext
 -- ----------------------------
 ALTER TABLE [dbo].[bpm_process_definition_ext] ADD CONSTRAINT [PK__bpm_proc__3213E83F0A8AB015] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11533,7 +11570,7 @@ GO
 -- Primary Key structure for table bpm_process_instance_ext
 -- ----------------------------
 ALTER TABLE [dbo].[bpm_process_instance_ext] ADD CONSTRAINT [PK__bpm_proc__3213E83FFD88328F] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11549,7 +11586,7 @@ GO
 -- Primary Key structure for table bpm_task_assign_rule
 -- ----------------------------
 ALTER TABLE [dbo].[bpm_task_assign_rule] ADD CONSTRAINT [PK__bpm_task__3213E83F474371C5] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11565,7 +11602,7 @@ GO
 -- Primary Key structure for table bpm_task_ext
 -- ----------------------------
 ALTER TABLE [dbo].[bpm_task_ext] ADD CONSTRAINT [PK__bpm_task__3213E83FD8AFE1F9] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11581,7 +11618,7 @@ GO
 -- Primary Key structure for table bpm_user_group
 -- ----------------------------
 ALTER TABLE [dbo].[bpm_user_group] ADD CONSTRAINT [PK__bpm_user__3213E83F25E4725B] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11597,7 +11634,7 @@ GO
 -- Primary Key structure for table infra_api_access_log
 -- ----------------------------
 ALTER TABLE [dbo].[infra_api_access_log] ADD CONSTRAINT [PK__infra_ap__3213E83F04F27A05] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11613,7 +11650,7 @@ GO
 -- Primary Key structure for table infra_api_error_log
 -- ----------------------------
 ALTER TABLE [dbo].[infra_api_error_log] ADD CONSTRAINT [PK__infra_ap__3213E83FCA2446D4] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11629,7 +11666,7 @@ GO
 -- Primary Key structure for table infra_codegen_column
 -- ----------------------------
 ALTER TABLE [dbo].[infra_codegen_column] ADD CONSTRAINT [PK__infra_co__3213E83FA9EC5005] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11645,7 +11682,7 @@ GO
 -- Primary Key structure for table infra_codegen_table
 -- ----------------------------
 ALTER TABLE [dbo].[infra_codegen_table] ADD CONSTRAINT [PK__infra_co__3213E83F555031D0] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11654,7 +11691,7 @@ GO
 -- Primary Key structure for table infra_config
 -- ----------------------------
 ALTER TABLE [dbo].[infra_config] ADD CONSTRAINT [PK__infra_co__3213E83FF4C71E85] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11670,7 +11707,7 @@ GO
 -- Primary Key structure for table infra_data_source_config
 -- ----------------------------
 ALTER TABLE [dbo].[infra_data_source_config] ADD CONSTRAINT [PK__infra_da__3213E83F02D21AEB] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11693,7 +11730,7 @@ GO
 -- Primary Key structure for table infra_file_config
 -- ----------------------------
 ALTER TABLE [dbo].[infra_file_config] ADD CONSTRAINT [PK__infra_fi__3213E83F8A7903EA] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11709,7 +11746,7 @@ GO
 -- Primary Key structure for table infra_file_content
 -- ----------------------------
 ALTER TABLE [dbo].[infra_file_content] ADD CONSTRAINT [PK__infra_fi__3213E83F033E6045] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11725,7 +11762,7 @@ GO
 -- Primary Key structure for table infra_job
 -- ----------------------------
 ALTER TABLE [dbo].[infra_job] ADD CONSTRAINT [PK__infra_jo__3213E83F3C7DE10C] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11741,7 +11778,7 @@ GO
 -- Primary Key structure for table infra_job_log
 -- ----------------------------
 ALTER TABLE [dbo].[infra_job_log] ADD CONSTRAINT [PK__infra_jo__3213E83F4CA8F353] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11764,7 +11801,7 @@ GO
 -- Primary Key structure for table member_user
 -- ----------------------------
 ALTER TABLE [dbo].[member_user] ADD CONSTRAINT [PK__member_u__3213E83F0A9AEC0B] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11780,7 +11817,7 @@ GO
 -- Primary Key structure for table pay_app
 -- ----------------------------
 ALTER TABLE [dbo].[pay_app] ADD CONSTRAINT [PK__pay_app__3213E83FB26E0A6B] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11796,7 +11833,7 @@ GO
 -- Primary Key structure for table pay_channel
 -- ----------------------------
 ALTER TABLE [dbo].[pay_channel] ADD CONSTRAINT [PK__pay_chan__3213E83F2556A7FC] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11812,7 +11849,7 @@ GO
 -- Primary Key structure for table pay_merchant
 -- ----------------------------
 ALTER TABLE [dbo].[pay_merchant] ADD CONSTRAINT [PK__pay_merc__3213E83F010D02B8] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11828,7 +11865,7 @@ GO
 -- Primary Key structure for table pay_notify_log
 -- ----------------------------
 ALTER TABLE [dbo].[pay_notify_log] ADD CONSTRAINT [PK__pay_noti__3213E83F5F4B3447] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11844,7 +11881,7 @@ GO
 -- Primary Key structure for table pay_notify_task
 -- ----------------------------
 ALTER TABLE [dbo].[pay_notify_task] ADD CONSTRAINT [PK__pay_noti__3213E83FB9215103] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11860,7 +11897,7 @@ GO
 -- Primary Key structure for table pay_order
 -- ----------------------------
 ALTER TABLE [dbo].[pay_order] ADD CONSTRAINT [PK__pay_orde__3213E83F34C95271] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11876,7 +11913,7 @@ GO
 -- Primary Key structure for table pay_order_extension
 -- ----------------------------
 ALTER TABLE [dbo].[pay_order_extension] ADD CONSTRAINT [PK__pay_orde__3213E83F5ACB776F] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11892,7 +11929,7 @@ GO
 -- Primary Key structure for table pay_refund
 -- ----------------------------
 ALTER TABLE [dbo].[pay_refund] ADD CONSTRAINT [PK__pay_refu__3213E83FBE1B54AC] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11908,7 +11945,7 @@ GO
 -- Primary Key structure for table system_dept
 -- ----------------------------
 ALTER TABLE [dbo].[system_dept] ADD CONSTRAINT [PK__system_d__3213E83FFA72847C] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11924,7 +11961,7 @@ GO
 -- Primary Key structure for table system_dict_data
 -- ----------------------------
 ALTER TABLE [dbo].[system_dict_data] ADD CONSTRAINT [PK__system_d__3213E83F20407597] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11940,7 +11977,7 @@ GO
 -- Primary Key structure for table system_dict_type
 -- ----------------------------
 ALTER TABLE [dbo].[system_dict_type] ADD CONSTRAINT [PK__system_d__3213E83F7C36B1FD] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11956,7 +11993,7 @@ GO
 -- Primary Key structure for table system_error_code
 -- ----------------------------
 ALTER TABLE [dbo].[system_error_code] ADD CONSTRAINT [PK__system_e__3213E83F68B8DFD0] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11972,7 +12009,7 @@ GO
 -- Primary Key structure for table system_login_log
 -- ----------------------------
 ALTER TABLE [dbo].[system_login_log] ADD CONSTRAINT [PK__system_l__3213E83F717953E9] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -11988,7 +12025,7 @@ GO
 -- Primary Key structure for table system_menu
 -- ----------------------------
 ALTER TABLE [dbo].[system_menu] ADD CONSTRAINT [PK__system_m__3213E83F14175801] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12004,7 +12041,7 @@ GO
 -- Primary Key structure for table system_notice
 -- ----------------------------
 ALTER TABLE [dbo].[system_notice] ADD CONSTRAINT [PK__system_n__3213E83FA158BA8D] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12027,7 +12064,7 @@ GO
 -- Primary Key structure for table system_oauth2_approve
 -- ----------------------------
 ALTER TABLE [dbo].[system_oauth2_approve] ADD CONSTRAINT [PK__system_o__3213E83F7CC08ED6] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12050,7 +12087,7 @@ GO
 -- Primary Key structure for table system_oauth2_code
 -- ----------------------------
 ALTER TABLE [dbo].[system_oauth2_code] ADD CONSTRAINT [PK__system_o__3213E83F38C13543] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12066,7 +12103,7 @@ GO
 -- Primary Key structure for table system_oauth2_refresh_token
 -- ----------------------------
 ALTER TABLE [dbo].[system_oauth2_refresh_token] ADD CONSTRAINT [PK__system_o__3213E83FCFB541CC] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12082,7 +12119,7 @@ GO
 -- Primary Key structure for table system_operate_log
 -- ----------------------------
 ALTER TABLE [dbo].[system_operate_log] ADD CONSTRAINT [PK__system_o__3213E83F85EC81FD] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12098,7 +12135,7 @@ GO
 -- Primary Key structure for table system_post
 -- ----------------------------
 ALTER TABLE [dbo].[system_post] ADD CONSTRAINT [PK__system_p__3213E83FBC098F34] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12114,7 +12151,7 @@ GO
 -- Primary Key structure for table system_role
 -- ----------------------------
 ALTER TABLE [dbo].[system_role] ADD CONSTRAINT [PK__system_r__3213E83F209B43F2] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12130,7 +12167,7 @@ GO
 -- Primary Key structure for table system_role_menu
 -- ----------------------------
 ALTER TABLE [dbo].[system_role_menu] ADD CONSTRAINT [PK__system_r__3213E83F6F1E4A9B] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12146,7 +12183,7 @@ GO
 -- Primary Key structure for table system_sensitive_word
 -- ----------------------------
 ALTER TABLE [dbo].[system_sensitive_word] ADD CONSTRAINT [PK__system_s__3213E83FFFD8E555] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12162,7 +12199,7 @@ GO
 -- Primary Key structure for table system_sms_channel
 -- ----------------------------
 ALTER TABLE [dbo].[system_sms_channel] ADD CONSTRAINT [PK__system_s__3213E83FA96B966E] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12178,7 +12215,7 @@ GO
 -- Primary Key structure for table system_sms_code
 -- ----------------------------
 ALTER TABLE [dbo].[system_sms_code] ADD CONSTRAINT [PK__system_s__3213E83F825CBCB9] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12194,7 +12231,7 @@ GO
 -- Primary Key structure for table system_sms_log
 -- ----------------------------
 ALTER TABLE [dbo].[system_sms_log] ADD CONSTRAINT [PK__system_s__3213E83F5F1968A9] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12210,7 +12247,7 @@ GO
 -- Primary Key structure for table system_sms_template
 -- ----------------------------
 ALTER TABLE [dbo].[system_sms_template] ADD CONSTRAINT [PK__system_s__3213E83F5C91CA37] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12226,7 +12263,7 @@ GO
 -- Primary Key structure for table system_social_user
 -- ----------------------------
 ALTER TABLE [dbo].[system_social_user] ADD CONSTRAINT [PK__system_s__3213E83F6EF3863C] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12242,7 +12279,7 @@ GO
 -- Primary Key structure for table system_social_user_bind
 -- ----------------------------
 ALTER TABLE [dbo].[system_social_user_bind] ADD CONSTRAINT [PK__system_s__3213E83F21F44049] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12258,7 +12295,7 @@ GO
 -- Primary Key structure for table system_tenant
 -- ----------------------------
 ALTER TABLE [dbo].[system_tenant] ADD CONSTRAINT [PK__system_t__3213E83FAF444092] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12274,7 +12311,7 @@ GO
 -- Primary Key structure for table system_tenant_package
 -- ----------------------------
 ALTER TABLE [dbo].[system_tenant_package] ADD CONSTRAINT [PK__system_t__3213E83FA2213DB5] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12290,7 +12327,7 @@ GO
 -- Primary Key structure for table system_user_post
 -- ----------------------------
 ALTER TABLE [dbo].[system_user_post] ADD CONSTRAINT [PK__system_u__3213E83F56DD4107] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12306,7 +12343,7 @@ GO
 -- Primary Key structure for table system_user_role
 -- ----------------------------
 ALTER TABLE [dbo].[system_user_role] ADD CONSTRAINT [PK__system_u__3213E83F3593F652] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 
@@ -12322,7 +12359,7 @@ GO
 -- Primary Key structure for table system_users
 -- ----------------------------
 ALTER TABLE [dbo].[system_users] ADD CONSTRAINT [PK__system_u__3213E83F7CF2516E] PRIMARY KEY CLUSTERED ([id])
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
+WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
 ON [PRIMARY]
 GO
 

+ 121 - 93
yudao-dependencies/pom.xml

@@ -14,56 +14,59 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.6.2-snapshot</revision>
+        <revision>1.6.5-snapshot</revision>
         <!-- 统一依赖管理 -->
-        <spring.boot.version>2.5.12</spring.boot.version>
+        <spring.boot.version>2.7.6</spring.boot.version>
         <!-- Web 相关 -->
-        <knife4j.version>3.0.2</knife4j.version>
-        <swagger-annotations.version>1.5.22</swagger-annotations.version>
+        <knife4j.version>3.0.3</knife4j.version>
+        <swagger-annotations.version>1.6.8</swagger-annotations.version>
         <servlet.versoin>2.5</servlet.versoin>
         <!-- DB 相关 -->
-        <druid.version>1.2.8</druid.version>
-        <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
+        <druid.version>1.2.15</druid.version>
+        <mybatis-plus.version>3.5.2</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
-        <dynamic-datasource.version>3.5.0</dynamic-datasource.version>
-        <redisson.version>3.17.0</redisson.version>
-        <!-- Config 配置中心相关 -->
-        <apollo.version>1.9.2</apollo.version>
-        <!-- Job 定时任务相关 -->
+        <dynamic-datasource.version>3.6.0</dynamic-datasource.version>
+        <redisson.version>3.18.0</redisson.version>
         <!-- 服务保障相关 -->
-        <lock4j.version>2.2.0</lock4j.version>
-        <resilience4j.version>1.7.0</resilience4j.version>
+        <lock4j.version>2.2.3</lock4j.version>
+        <resilience4j.version>1.7.1</resilience4j.version>
         <!-- 监控相关 -->
-        <skywalking.version>8.7.0</skywalking.version>
-        <spring-boot-admin.version>2.6.2</spring-boot-admin.version>
-        <opentracing.version>0.31.0</opentracing.version>
+        <skywalking.version>8.12.0</skywalking.version>
+        <spring-boot-admin.version>2.7.9</spring-boot-admin.version>
+        <opentracing.version>0.33.0</opentracing.version>
         <!-- Test 测试相关 -->
-        <podam.version>7.2.6.RELEASE</podam.version>
-        <jedis-mock.version>0.1.16</jedis-mock.version>
-        <mockito-inline.version>3.9.0</mockito-inline.version>
+        <podam.version>7.2.11.RELEASE</podam.version>
+        <jedis-mock.version>1.0.5</jedis-mock.version>
+        <mockito-inline.version>4.8.0</mockito-inline.version>
         <!-- Bpm 工作流相关 -->
-        <activiti.version>7.1.0.M6</activiti.version>
-        <flowable.version>6.7.0</flowable.version>
+        <flowable.version>6.7.2</flowable.version>
         <!-- 工具类相关 -->
-        <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version>
-        <lombok.version>1.18.20</lombok.version>
-        <mapstruct.version>1.4.1.Final</mapstruct.version>
-        <hutool.version>5.6.1</hutool.version>
-        <easyexcel.verion>2.2.7</easyexcel.verion>
-        <velocity.version>2.2</velocity.version>
+        <lombok.version>1.18.24</lombok.version>
+        <mapstruct.version>1.5.3.Final</mapstruct.version>
+        <hutool.version>5.8.10</hutool.version>
+        <easyexcel.verion>3.1.3</easyexcel.verion>
+        <velocity.version>2.3</velocity.version>
         <screw.version>1.0.5</screw.version>
-        <guava.version>30.1.1-jre</guava.version>
+        <fastjson.version>1.2.83</fastjson.version>
+        <guava.version>31.1-jre</guava.version>
         <guice.version>5.1.0</guice.version>
-        <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
+        <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
         <commons-net.version>3.8.0</commons-net.version>
         <jsch.version>0.1.55</jsch.version>
+        <tika-core.version>2.6.0</tika-core.version>
+        <aj-captcha.version>1.3.0</aj-captcha.version>
+        <netty-all.version>4.1.85.Final</netty-all.version>
+        <ip2region.version>2.6.6</ip2region.version>
         <!-- 三方云服务相关 -->
-        <minio.version>8.2.2</minio.version>
-        <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version>
-        <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
-        <tencentcloud-sdk-java.version>3.1.471</tencentcloud-sdk-java.version>
-        <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
+        <okio.version>3.0.0</okio.version>
+        <okhttp3.version>4.10.0</okhttp3.version>
+        <minio.version>8.4.6</minio.version>
+        <aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
+        <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
+        <tencentcloud-sdk-java.version>3.1.637</tencentcloud-sdk-java.version>
         <justauth.version>1.4.0</justauth.version>
+        <jimureport.version>1.5.6</jimureport.version>
+        <xercesImpl.version>2.12.2</xercesImpl.version>
         <wx-java-mp.version>4.3.0</wx-java-mp.version>
     </properties>
 
@@ -79,6 +82,11 @@
             </dependency>
 
             <!-- 业务组件 -->
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-banner</artifactId>
+                <version>${revision}</version>
+            </dependency>
             <dependency>
                 <groupId>cn.iocoder.boot</groupId>
                 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
@@ -119,6 +127,21 @@
                 <artifactId>yudao-spring-boot-starter-biz-social</artifactId>
                 <version>${revision}</version>
             </dependency>
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-captcha</artifactId>
+                <version>${revision}</version>
+            </dependency>
 
             <!-- Spring 核心 -->
             <dependency>
@@ -207,17 +230,6 @@
             </dependency>
 
             <!-- Config 配置中心相关 -->
-            <dependency>
-                <groupId>cn.iocoder.boot</groupId>
-                <artifactId>yudao-spring-boot-starter-config</artifactId>
-                <version>${revision}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>com.ctrip.framework.apollo</groupId>
-                <artifactId>apollo-client</artifactId> <!-- 引入 Apollo Client 库,实现内嵌的配置中心 -->
-                <version>${apollo.version}</version>
-            </dependency>
 
             <!-- Job 定时任务相关 -->
             <dependency>
@@ -365,41 +377,6 @@
             </dependency>
 
             <!-- 工作流相关 -->
-            <dependency>
-                <groupId>org.activiti</groupId>
-                <artifactId>activiti-spring-boot-starter</artifactId>
-                <version>${activiti.version}</version>
-                <exclusions>
-                    <exclusion>
-                        <groupId>de.odysseus.juel</groupId>
-                        <artifactId>juel-api</artifactId>
-                    </exclusion>
-                    <exclusion>
-                        <groupId>de.odysseus.juel</groupId>
-                        <artifactId>juel-spi</artifactId>
-                    </exclusion>
-                    <exclusion>
-                        <groupId>org.mybatis</groupId>
-                        <artifactId>mybatis</artifactId>
-                    </exclusion>
-                    <exclusion>
-                        <artifactId>el-api</artifactId>
-                        <groupId>javax.el</groupId>
-                    </exclusion>
-                </exclusions>
-            </dependency>
-            <dependency>
-                <groupId>org.activiti</groupId>
-                <artifactId>activiti-image-generator</artifactId>
-                <version>${activiti.version}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>cn.iocoder.boot</groupId>
-                <artifactId>yudao-spring-boot-starter-activiti</artifactId>
-                <version>${revision}</version>
-            </dependency>
-            <!-- 工作流相关 flowable -->
             <dependency>
                 <groupId>cn.iocoder.boot</groupId>
                 <artifactId>yudao-spring-boot-starter-flowable</artifactId>
@@ -407,7 +384,7 @@
             </dependency>
             <dependency>
                 <groupId>org.flowable</groupId>
-                <artifactId>flowable-spring-boot-starter-basic</artifactId>
+                <artifactId>flowable-spring-boot-starter-process</artifactId>
                 <version>${flowable.version}</version>
             </dependency>
             <dependency>
@@ -424,12 +401,6 @@
                 <version>${revision}</version>
             </dependency>
 
-            <dependency>
-                <groupId>com.github.ulisesbocchio</groupId>
-                <artifactId>jasypt-spring-boot-starter</artifactId> <!-- 加解密 -->
-                <version>${jasypt-spring-boot-starter.version}</version>
-            </dependency>
-
             <dependency>
                 <groupId>cn.iocoder.boot</groupId>
                 <artifactId>yudao-spring-boot-starter-excel</artifactId>
@@ -470,12 +441,30 @@
                 <version>${easyexcel.verion}</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.apache.tika</groupId>
+                <artifactId>tika-core</artifactId> <!-- 文件类型的识别 -->
+                <version>${tika-core.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.anji-plus</groupId>
+                <artifactId>spring-boot-starter-captcha</artifactId>
+                <version>${aj-captcha.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.apache.velocity</groupId>
                 <artifactId>velocity-engine-core</artifactId>
                 <version>${velocity.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>cn.smallbun.screw</groupId>
                 <artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->
@@ -484,6 +473,10 @@
                     <exclusion>
                         <groupId>org.freemarker</groupId>
                         <artifactId>freemarker</artifactId> <!-- 移除 Freemarker 依赖,采用 Velocity 作为模板引擎 -->
+                    </exclusion>
+				    <exclusion>
+                        <groupId>com.alibaba</groupId>
+                        <artifactId>fastjson</artifactId> <!-- 最新版screw-core1.0.5依赖fastjson1.2.73存在漏洞,移除。 -->
                     </exclusion>
                 </exclusions>
             </dependency>
@@ -511,13 +504,36 @@
                 <artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->
                 <version>${commons-net.version}</version>
             </dependency>
+
             <dependency>
                 <groupId>com.jcraft</groupId>
                 <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
                 <version>${jsch.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-all</artifactId>
+                <version>${netty-all.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.lionsoul</groupId>
+                <artifactId>ip2region</artifactId>
+                <version>${ip2region.version}</version>
+            </dependency>
+
             <!-- 三方云服务相关 -->
+            <dependency>
+                <groupId>com.squareup.okio</groupId>
+                <artifactId>okio</artifactId>
+                <version>${okio.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp</artifactId>
+                <version>${okhttp3.version}</version>
+            </dependency>
             <dependency>
                 <groupId>cn.iocoder.boot</groupId>
                 <artifactId>yudao-spring-boot-starter-file</artifactId>
@@ -530,11 +546,6 @@
             </dependency>
 
             <!-- SMS SDK begin -->
-            <dependency>
-                <groupId>com.yunpian.sdk</groupId>
-                <artifactId>yunpian-java-sdk</artifactId>
-                <version>${yunpian-java-sdk.version}</version>
-            </dependency>
             <dependency>
                 <groupId>com.aliyun</groupId>
                 <artifactId>aliyun-java-sdk-core</artifactId>
@@ -557,7 +568,7 @@
             </dependency>
             <dependency>
                 <groupId>com.tencentcloudapi</groupId>
-                <artifactId>tencentcloud-sdk-java</artifactId>
+                <artifactId>tencentcloud-sdk-java-sms</artifactId>
                 <version>${tencentcloud-sdk-java.version}</version>
             </dependency>
             <!-- SMS SDK end -->
@@ -574,6 +585,23 @@
                 <version>${wx-java-mp.version}</version>
             </dependency>
 
+            <!-- 积木报表-->
+            <dependency>
+                <groupId>org.jeecgframework.jimureport</groupId>
+                <artifactId>jimureport-spring-boot-starter</artifactId>
+                <version>${jimureport.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>com.alibaba</groupId>
+                        <artifactId>druid</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>xerces</groupId>
+                <artifactId>xercesImpl</artifactId>
+                <version>${xercesImpl.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 22 - 0
yudao-example/pom.xml

@@ -0,0 +1,22 @@
+<?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>
+
+    <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
+
+    <groupId>cn.iocoder.boot</groupId>
+    <artifactId>yudao-example</artifactId>
+    <version>1.0.0-snapshot</version>
+    <packaging>pom</packaging>
+    <modules>
+        <module>yudao-sso-demo-by-code</module>
+        <module>yudao-sso-demo-by-password</module>
+    </modules>
+
+    <name>${project.artifactId}</name>
+    <description>提供各种示例,例如说:SSO 单点登录</description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+</project>

+ 65 - 0
yudao-example/yudao-sso-demo-by-code/pom.xml

@@ -0,0 +1,65 @@
+<?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>
+
+    <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
+
+    <groupId>cn.iocoder.boot</groupId>
+    <artifactId>yudao-sso-demo-by-code</artifactId>
+    <version>1.0.0-snapshot</version>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>基于授权码模式,如何实现 SSO 单点登录?</description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+    <properties>
+        <!-- Maven 相关 -->
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <!-- 统一依赖管理 -->
+        <spring.boot.version>2.7.6</spring.boot.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- 统一依赖管理 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.10</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 13 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.ssodemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SSODemoApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SSODemoApplication.class, args);
+    }
+
+}

+ 157 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java

@@ -0,0 +1,157 @@
+package cn.iocoder.yudao.ssodemo.client;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.springframework.util.Base64Utils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * OAuth 2.0 客户端
+ *
+ * 对应调用 OAuth2OpenController 接口
+ */
+@Component
+public class OAuth2Client {
+
+    private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
+
+    /**
+     * 租户编号
+     *
+     * 默认使用 1;如果使用别的租户,可以调整
+     */
+    public static final Long TENANT_ID = 1L;
+
+    private static final String CLIENT_ID = "yudao-sso-demo-by-code";
+    private static final String CLIENT_SECRET = "test";
+
+
+//    @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    /**
+     * 使用 code 授权码,获得访问令牌
+     *
+     * @param code        授权码
+     * @param redirectUri 重定向 URI
+     * @return 访问令牌
+     */
+    public CommonResult<OAuth2AccessTokenRespDTO> postAccessToken(String code, String redirectUri) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID.toString());
+        addClientHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("grant_type", "authorization_code");
+        body.add("code", code);
+        body.add("redirect_uri", redirectUri);
+//        body.add("state", ""); // 选填;填了会校验
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/token",
+                HttpMethod.POST,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    /**
+     * 校验访问令牌,并返回它的基本信息
+     *
+     * @param token 访问令牌
+     * @return 访问令牌的基本信息
+     */
+    public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID.toString());
+        addClientHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("token", token);
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/check-token",
+                HttpMethod.POST,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    /**
+     * 使用刷新令牌,获得(刷新)访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 访问令牌
+     */
+    public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID.toString());
+        addClientHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("grant_type", "refresh_token");
+        body.add("refresh_token", refreshToken);
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/token",
+                HttpMethod.POST,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    /**
+     * 删除访问令牌
+     *
+     * @param token 访问令牌
+     * @return 成功
+     */
+    public CommonResult<Boolean> revokeToken(String token) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID.toString());
+        addClientHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("token", token);
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
+                BASE_URL + "/token",
+                HttpMethod.DELETE,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    private static void addClientHeader(HttpHeaders headers) {
+        // client 拼接,需要 BASE64 编码
+        String client = CLIENT_ID + ":" + CLIENT_SECRET;
+        client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
+        headers.add("Authorization", "Basic " + client);
+    }
+
+}

+ 73 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.ssodemo.client;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
+import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * 用户 User 信息的客户端
+ *
+ * 对应调用 OAuth2UserController 接口
+ */
+@Component
+public class UserClient {
+
+    private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
+
+    //    @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    public CommonResult<UserInfoRespDTO> getUser() {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
+        addTokenHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/get",
+                HttpMethod.GET,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
+        addTokenHeader(headers);
+        // 1.2 构建请求参数
+        // 使用 updateReqDTO 即可
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
+                BASE_URL + "/update",
+                HttpMethod.PUT,
+                new HttpEntity<>(updateReqDTO, headers),
+                new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+
+    private static void addTokenHeader(HttpHeaders headers) {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        Assert.notNull(loginUser, "登录用户不能为空");
+        headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
+    }
+}

+ 28 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.ssodemo.client.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 通用返回
+ *
+ * @param <T> 数据泛型
+ */
+@Data
+public class CommonResult<T> implements Serializable {
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+    /**
+     * 返回数据
+     */
+    private T data;
+    /**
+     * 错误提示,用户可阅读
+     */
+    private String msg;
+
+}

+ 45 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 访问令牌 Response DTO
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OAuth2AccessTokenRespDTO {
+
+    /**
+     * 访问令牌
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+
+    /**
+     * 刷新令牌
+     */
+    @JsonProperty("refresh_token")
+    private String refreshToken;
+
+    /**
+     * 令牌类型
+     */
+    @JsonProperty("token_type")
+    private String tokenType;
+
+    /**
+     * 过期时间;单位:秒
+     */
+    @JsonProperty("expires_in")
+    private Long expiresIn;
+
+    /**
+     * 授权范围;如果多个授权范围,使用空格分隔
+     */
+    private String scope;
+
+}

+ 59 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java

@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 校验令牌 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OAuth2CheckTokenRespDTO {
+
+    /**
+     * 用户编号
+     */
+    @JsonProperty("user_id")
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    @JsonProperty("user_type")
+    private Integer userType;
+    /**
+     * 租户编号
+     */
+    @JsonProperty("tenant_id")
+    private Long tenantId;
+
+    /**
+     * 客户端编号
+     */
+    @JsonProperty("client_id")
+    private String clientId;
+    /**
+     * 授权范围
+     */
+    private List<String> scopes;
+
+    /**
+     * 访问令牌
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+
+    /**
+     * 过期时间
+     *
+     * 时间戳 / 1000,即单位:秒
+     */
+    private Long exp;
+
+}

+ 97 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java

@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.ssodemo.client.dto.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 获得用户基本信息 Response dto
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserInfoRespDTO {
+
+    /**
+     * 用户编号
+     */
+    private Long id;
+
+    /**
+     * 用户账号
+     */
+    private String username;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+    /**
+     * 手机号码
+     */
+    private String mobile;
+
+    /**
+     * 用户性别
+     */
+    private Integer sex;
+
+    /**
+     * 用户头像
+     */
+    private String avatar;
+
+    /**
+     * 所在部门
+     */
+    private Dept dept;
+
+    /**
+     * 所属岗位数组
+     */
+    private List<Post> posts;
+
+    /**
+     * 部门
+     */
+    @Data
+    public static class Dept {
+
+        /**
+         * 部门编号
+         */
+        private Long id;
+
+        /**
+         * 部门名称
+         */
+        private String name;
+
+    }
+
+    /**
+     * 岗位
+     */
+    @Data
+    public static class Post {
+
+        /**
+         * 岗位编号
+         */
+        private Long id;
+
+        /**
+         * 岗位名称
+         */
+        private String name;
+
+    }
+
+}

+ 35 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.ssodemo.client.dto.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 更新用户基本信息 Request DTO
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserUpdateReqDTO {
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String mobile;
+
+    /**
+     * 用户性别
+     */
+    private Integer sex;
+
+}

+ 63 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.ssodemo.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+    @Resource
+    private OAuth2Client oauth2Client;
+
+    /**
+     * 使用 code 访问令牌,获得访问令牌
+     *
+     * @param code 授权码
+     * @param redirectUri 重定向 URI
+     * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
+     */
+    @PostMapping("/login-by-code")
+    public CommonResult<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code,
+                                                              @RequestParam("redirectUri") String redirectUri) {
+        return oauth2Client.postAccessToken(code, redirectUri);
+    }
+
+    /**
+     * 使用刷新令牌,获得(刷新)访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
+     */
+    @PostMapping("/refresh-token")
+    public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
+        return oauth2Client.refreshToken(refreshToken);
+    }
+
+    /**
+     * 退出登录
+     *
+     * @param request 请求
+     * @return 成功
+     */
+    @PostMapping("/logout")
+    public CommonResult<Boolean> logout(HttpServletRequest request) {
+        String token = SecurityUtils.obtainAuthorization(request, "Authorization");
+        if (StrUtil.isNotBlank(token)) {
+            return oauth2Client.revokeToken(token);
+        }
+        // 返回成功
+        return new CommonResult<>();
+    }
+
+}

+ 40 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.ssodemo.controller;
+
+import cn.iocoder.yudao.ssodemo.client.UserClient;
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@RestController
+@RequestMapping("/user")
+public class UserController {
+
+    @Resource
+    private UserClient userClient;
+
+    /**
+     * 获得当前登录用户的基本信息
+     *
+     * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
+     */
+    @GetMapping("/get")
+    public CommonResult<UserInfoRespDTO> getUser() {
+        return userClient.getUser();
+    }
+
+    /**
+     * 更新当前登录用户的昵称
+     *
+     * @param nickname 昵称
+     * @return 成功
+     */
+    @PutMapping("/update")
+    public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
+        UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
+        return userClient.updateUser(updateReqDTO);
+    }
+
+}

+ 52 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.ssodemo.framework.config;
+
+import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
+import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import javax.annotation.Resource;
+
+@Configuration(proxyBeanMethods = false)
+@EnableWebSecurity
+public class SecurityConfiguration{
+
+    @Resource
+    private TokenAuthenticationFilter tokenAuthenticationFilter;
+
+    @Resource
+    private AccessDeniedHandlerImpl accessDeniedHandler;
+    @Resource
+    private AuthenticationEntryPoint authenticationEntryPoint;
+
+    @Bean
+    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
+        // 设置 URL 安全权限
+        httpSecurity.csrf().disable() // 禁用 CSRF 保护
+                .authorizeRequests()
+                // 1. 静态资源,可匿名访问
+                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
+                // 2. 登录相关的接口,可匿名访问
+                .antMatchers("/auth/login-by-code").permitAll()
+                .antMatchers("/auth/refresh-token").permitAll()
+                .antMatchers("/auth/logout").permitAll()
+                // last. 兜底规则,必须认证
+                .and().authorizeRequests()
+                .anyRequest().authenticated();
+
+        // 设置处理器
+        httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
+                .authenticationEntryPoint(authenticationEntryPoint);
+
+        // 添加 Token Filter
+        httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+        return httpSecurity.build();
+    }
+
+}

+ 37 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.ssodemo.framework.core;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 登录用户信息
+ *
+ * @author 芋道源码
+ */
+@Data
+public class LoginUser {
+
+    /**
+     * 用户编号
+     */
+    private Long id;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 租户编号
+     */
+    private Long tenantId;
+    /**
+     * 授权范围
+     */
+    private List<String> scopes;
+
+    /**
+     * 访问令牌
+     */
+    private String accessToken;
+
+}

+ 66 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java

@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.ssodemo.framework.core.filter;
+
+import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
+import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.annotation.Resource;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Token 过滤器,验证 token 的有效性
+ * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TokenAuthenticationFilter extends OncePerRequestFilter {
+
+    @Resource
+    private OAuth2Client oauth2Client;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+                                    FilterChain filterChain) throws ServletException, IOException {
+        // 1. 获得访问令牌
+        String token = SecurityUtils.obtainAuthorization(request, "Authorization");
+        if (StringUtils.hasText(token)) {
+            // 2. 基于 token 构建登录用户
+            LoginUser loginUser = buildLoginUserByToken(token);
+            // 3. 设置当前用户
+            if (loginUser != null) {
+                SecurityUtils.setLoginUser(loginUser, request);
+            }
+        }
+
+        // 继续过滤链
+        filterChain.doFilter(request, response);
+    }
+
+    private LoginUser buildLoginUserByToken(String token) {
+        try {
+            CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
+            OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
+            if (accessToken == null) {
+                return null;
+            }
+            // 构建登录用户
+            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
+                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
+                    .setAccessToken(accessToken.getAccessToken());
+        } catch (Exception exception) {
+            // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
+            return null;
+        }
+    }
+
+}

+ 44 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.ssodemo.framework.core.handler;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
+ *
+ * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
+ *
+ * @author 芋道源码
+ */
+@Component
+@SuppressWarnings("JavadocReference")
+@Slf4j
+public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
+
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
+            throws IOException, ServletException {
+        // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
+        log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
+                SecurityUtils.getLoginUserId(), e);
+        // 返回 403
+        CommonResult<Object> result = new CommonResult<>();
+        result.setCode(HttpStatus.FORBIDDEN.value());
+        result.setMsg("没有该操作权限");
+        ServletUtils.writeJSON(response, result);
+    }
+
+}

+ 36 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.ssodemo.framework.core.handler;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
+ *
+ * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
+ */
+@Component
+@Slf4j
+@SuppressWarnings("JavadocReference") // 忽略文档引用报错
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
+        log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
+        // 返回 401
+        CommonResult<Object> result = new CommonResult<>();
+        result.setCode(HttpStatus.UNAUTHORIZED.value());
+        result.setMsg("账号未登录");
+        ServletUtils.writeJSON(response, result);
+    }
+
+}

+ 103 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java

@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.ssodemo.framework.core.util;
+
+import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
+import org.springframework.lang.Nullable;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+
+/**
+ * 安全服务工具类
+ *
+ * @author 芋道源码
+ */
+public class SecurityUtils {
+
+    public static final String AUTHORIZATION_BEARER = "Bearer";
+
+    private SecurityUtils() {}
+
+    /**
+     * 从请求中,获得认证 Token
+     *
+     * @param request 请求
+     * @param header 认证 Token 对应的 Header 名字
+     * @return 认证 Token
+     */
+    public static String obtainAuthorization(HttpServletRequest request, String header) {
+        String authorization = request.getHeader(header);
+        if (!StringUtils.hasText(authorization)) {
+            return null;
+        }
+        int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
+        if (index == -1) { // 未找到
+            return null;
+        }
+        return authorization.substring(index + 7).trim();
+    }
+
+    /**
+     * 获得当前认证信息
+     *
+     * @return 认证信息
+     */
+    public static Authentication getAuthentication() {
+        SecurityContext context = SecurityContextHolder.getContext();
+        if (context == null) {
+            return null;
+        }
+        return context.getAuthentication();
+    }
+
+    /**
+     * 获取当前用户
+     *
+     * @return 当前用户
+     */
+    @Nullable
+    public static LoginUser getLoginUser() {
+        Authentication authentication = getAuthentication();
+        if (authentication == null) {
+            return null;
+        }
+        return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
+    }
+
+    /**
+     * 获得当前用户的编号,从上下文中
+     *
+     * @return 用户编号
+     */
+    @Nullable
+    public static Long getLoginUserId() {
+        LoginUser loginUser = getLoginUser();
+        return loginUser != null ? loginUser.getId() : null;
+    }
+
+    /**
+     * 设置当前用户
+     *
+     * @param loginUser 登录用户
+     * @param request 请求
+     */
+    public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
+        // 创建 Authentication,并设置到上下文
+        Authentication authentication = buildAuthentication(loginUser, request);
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+    }
+
+    private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
+        // 创建 UsernamePasswordAuthenticationToken 对象
+        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
+                loginUser, null, Collections.emptyList());
+        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+        return authenticationToken;
+    }
+
+}

+ 28 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.ssodemo.framework.core.util;
+
+import cn.hutool.extra.servlet.ServletUtil;
+import cn.hutool.json.JSONUtil;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 客户端工具类
+ *
+ * @author 芋道源码
+ */
+public class ServletUtils {
+
+    /**
+     * 返回 JSON 字符串
+     *
+     * @param response 响应
+     * @param object 对象,会序列化成 JSON 字符串
+     */
+    @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
+    public static void writeJSON(HttpServletResponse response, Object object) {
+        String content = JSONUtil.toJsonStr(object);
+        ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
+    }
+
+}

+ 2 - 0
yudao-example/yudao-sso-demo-by-code/src/main/resources/application.yaml

@@ -0,0 +1,2 @@
+server:
+  port: 18080

+ 61 - 0
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html

@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>SSO 授权后的回调页</title>
+	<!-- jQuery:操作 dom、发起请求等 -->
+	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
+	<!-- 工具类 -->
+	<script type="application/javascript">
+    (function ($) {
+      /**
+       * 获得 URL 的指定参数的值
+       *
+       * @param name 参数名
+       * @returns 参数值
+       */
+      $.getUrlParam = function (name) {
+        const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+        const r = window.location.search.substr(1).match(reg);
+        if (r != null) return unescape(r[2]); return null;
+      }
+    })(jQuery);
+	</script>
+
+	<script type="application/javascript">
+    $(function () {
+      // 获得 code 授权码
+      const code = $.getUrlParam('code');
+      if (!code) {
+        alert('获取不到 code 参数,请排查!')
+        return;
+      }
+
+      // 提交
+      const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri
+      $.ajax({
+        url:  "http://127.0.0.1:18080/auth/login-by-code?code=" + code
+          + '&redirectUri=' + redirectUri,
+        method: 'POST',
+        success: function( result ) {
+          if (result.code !== 0) {
+            alert('获得访问令牌失败,原因:' + result.msg)
+            return;
+          }
+          alert('获得访问令牌成功!点击确认,跳转回首页')
+
+          // 设置到 localStorage 中
+          localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
+          localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
+
+          // 跳转回首页
+          window.location.href = '/index.html';
+        }
+      })
+    })
+	</script>
+</head>
+<body>
+正在使用 code 授权码,进行 accessToken 访问令牌的获取
+</body>
+</html>

+ 159 - 0
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html

@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>首页</title>
+	<!-- jQuery:操作 dom、发起请求等 -->
+	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
+
+	<script type="application/javascript">
+
+    /**
+     * 跳转单点登录
+     */
+    function ssoLogin() {
+      const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId
+      const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址
+      const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token
+      window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId
+        + '&redirect_uri=' + redirectUri
+        + '&response_type=' + responseType;
+    }
+
+    /**
+     * 修改昵称
+     */
+    function updateNickname() {
+      const nickname = prompt("请输入新的昵称", "");
+      if (!nickname) {
+        return;
+      }
+      // 更新用户的昵称
+      const accessToken = localStorage.getItem('ACCESS-TOKEN');
+      $.ajax({
+        url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
+        method: 'PUT',
+        headers: {
+          'Authorization': 'Bearer ' + accessToken
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('更新昵称失败,原因:' + result.msg)
+            return;
+          }
+          alert('更新昵称成功!');
+          $('#nicknameSpan').html(nickname);
+        }
+      });
+    }
+
+    /**
+     * 刷新令牌
+     */
+    function refreshToken() {
+      const refreshToken = localStorage.getItem('REFRESH-TOKEN');
+      if (!refreshToken) {
+        alert("获取不到刷新令牌");
+        return;
+      }
+      $.ajax({
+        url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken,
+        method: 'POST',
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('刷新访问令牌失败,原因:' + result.msg)
+            return;
+          }
+          alert('更新访问令牌成功!');
+          $('#accessTokenSpan').html(result.data.access_token);
+
+          // 设置到 localStorage 中
+          localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
+          localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
+        }
+      });
+    }
+
+    /**
+     * 登出,删除访问令牌
+     */
+    function logout() {
+      const accessToken = localStorage.getItem('ACCESS-TOKEN');
+      if (!accessToken) {
+        location.reload();
+        return;
+      }
+      $.ajax({
+        url: "http://127.0.0.1:18080/auth/logout",
+        method: 'POST',
+        headers: {
+          'Authorization': 'Bearer ' + accessToken
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('退出登录失败,原因:' + result.msg)
+            return;
+          }
+          alert('退出登录成功!');
+          // 删除 localStorage 中
+          localStorage.removeItem('ACCESS-TOKEN');
+          localStorage.removeItem('REFRESH-TOKEN');
+
+          location.reload();
+        }
+      });
+    }
+
+    $(function () {
+      const accessToken = localStorage.getItem('ACCESS-TOKEN');
+      // 情况一:未登录
+      if (!accessToken) {
+        $('#noLoginDiv').css("display", "block");
+        return;
+      }
+
+      // 情况二:已登录
+      $('#yesLoginDiv').css("display", "block");
+      $('#accessTokenSpan').html(accessToken);
+      // 获得登录用户的信息
+      $.ajax({
+        url: "http://127.0.0.1:18080/user/get",
+        method: 'GET',
+        headers: {
+          'Authorization': 'Bearer ' + accessToken
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('获得个人信息失败,原因:' + result.msg)
+            return;
+          }
+          $('#nicknameSpan').html(result.data.nickname);
+        }
+      });
+    })
+	</script>
+</head>
+<body>
+<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
+<div id="noLoginDiv" style="display: none">
+	您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录
+</div>
+
+<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
+<div id="yesLoginDiv" style="display: none">
+	您已登录!<button onclick="logout()">退出登录</button> <br />
+	昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
+	访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br />
+</div>
+</body>
+<style>
+    body { /** 页面居中 */
+        border-radius: 20px;
+        height: 350px;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%,-50%);
+    }
+</style>
+</html>

+ 65 - 0
yudao-example/yudao-sso-demo-by-password/pom.xml

@@ -0,0 +1,65 @@
+<?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>
+
+    <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
+
+    <groupId>cn.iocoder.boot</groupId>
+    <artifactId>yudao-sso-demo-by-password</artifactId>
+    <version>1.0.0-snapshot</version>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>基于密码模式,如何实现 SSO 单点登录?</description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+    <properties>
+        <!-- Maven 相关 -->
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <!-- 统一依赖管理 -->
+        <spring.boot.version>2.7.6</spring.boot.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- 统一依赖管理 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.10</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 13 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.ssodemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SSODemoApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SSODemoApplication.class, args);
+    }
+
+}

+ 127 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java

@@ -0,0 +1,127 @@
+package cn.iocoder.yudao.ssodemo.client;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.springframework.util.Base64Utils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * OAuth 2.0 客户端
+ *
+ * 对应调用 OAuth2OpenController 接口
+ */
+@Component
+public class OAuth2Client {
+
+    private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
+
+    /**
+     * 租户编号
+     *
+     * 默认使用 1;如果使用别的租户,可以调整
+     */
+    public static final Long TENANT_ID = 1L;
+
+    private static final String CLIENT_ID = "yudao-sso-demo-by-password";
+    private static final String CLIENT_SECRET = "test";
+
+
+//    @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    /**
+     * 校验访问令牌,并返回它的基本信息
+     *
+     * @param token 访问令牌
+     * @return 访问令牌的基本信息
+     */
+    public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID.toString());
+        addClientHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("token", token);
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/check-token",
+                HttpMethod.POST,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    /**
+     * 使用刷新令牌,获得(刷新)访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 访问令牌
+     */
+    public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID.toString());
+        addClientHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("grant_type", "refresh_token");
+        body.add("refresh_token", refreshToken);
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/token",
+                HttpMethod.POST,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    /**
+     * 删除访问令牌
+     *
+     * @param token 访问令牌
+     * @return 成功
+     */
+    public CommonResult<Boolean> revokeToken(String token) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", TENANT_ID.toString());
+        addClientHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("token", token);
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
+                BASE_URL + "/token",
+                HttpMethod.DELETE,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    private static void addClientHeader(HttpHeaders headers) {
+        // client 拼接,需要 BASE64 编码
+        String client = CLIENT_ID + ":" + CLIENT_SECRET;
+        client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
+        headers.add("Authorization", "Basic " + client);
+    }
+
+}

+ 73 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.ssodemo.client;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
+import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * 用户 User 信息的客户端
+ *
+ * 对应调用 OAuth2UserController 接口
+ */
+@Component
+public class UserClient {
+
+    private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
+
+    //    @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    public CommonResult<UserInfoRespDTO> getUser() {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
+        addTokenHeader(headers);
+        // 1.2 构建请求参数
+        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/get",
+                HttpMethod.GET,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+    public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
+        addTokenHeader(headers);
+        // 1.2 构建请求参数
+        // 使用 updateReqDTO 即可
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
+                BASE_URL + "/update",
+                HttpMethod.PUT,
+                new HttpEntity<>(updateReqDTO, headers),
+                new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+
+    private static void addTokenHeader(HttpHeaders headers) {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        Assert.notNull(loginUser, "登录用户不能为空");
+        headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
+    }
+}

+ 28 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.ssodemo.client.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 通用返回
+ *
+ * @param <T> 数据泛型
+ */
+@Data
+public class CommonResult<T> implements Serializable {
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+    /**
+     * 返回数据
+     */
+    private T data;
+    /**
+     * 错误提示,用户可阅读
+     */
+    private String msg;
+
+}

+ 45 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 访问令牌 Response DTO
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OAuth2AccessTokenRespDTO {
+
+    /**
+     * 访问令牌
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+
+    /**
+     * 刷新令牌
+     */
+    @JsonProperty("refresh_token")
+    private String refreshToken;
+
+    /**
+     * 令牌类型
+     */
+    @JsonProperty("token_type")
+    private String tokenType;
+
+    /**
+     * 过期时间;单位:秒
+     */
+    @JsonProperty("expires_in")
+    private Long expiresIn;
+
+    /**
+     * 授权范围;如果多个授权范围,使用空格分隔
+     */
+    private String scope;
+
+}

+ 59 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java

@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 校验令牌 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OAuth2CheckTokenRespDTO {
+
+    /**
+     * 用户编号
+     */
+    @JsonProperty("user_id")
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    @JsonProperty("user_type")
+    private Integer userType;
+    /**
+     * 租户编号
+     */
+    @JsonProperty("tenant_id")
+    private Long tenantId;
+
+    /**
+     * 客户端编号
+     */
+    @JsonProperty("client_id")
+    private String clientId;
+    /**
+     * 授权范围
+     */
+    private List<String> scopes;
+
+    /**
+     * 访问令牌
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+
+    /**
+     * 过期时间
+     *
+     * 时间戳 / 1000,即单位:秒
+     */
+    private Long exp;
+
+}

+ 97 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java

@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.ssodemo.client.dto.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 获得用户基本信息 Response dto
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserInfoRespDTO {
+
+    /**
+     * 用户编号
+     */
+    private Long id;
+
+    /**
+     * 用户账号
+     */
+    private String username;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+    /**
+     * 手机号码
+     */
+    private String mobile;
+
+    /**
+     * 用户性别
+     */
+    private Integer sex;
+
+    /**
+     * 用户头像
+     */
+    private String avatar;
+
+    /**
+     * 所在部门
+     */
+    private Dept dept;
+
+    /**
+     * 所属岗位数组
+     */
+    private List<Post> posts;
+
+    /**
+     * 部门
+     */
+    @Data
+    public static class Dept {
+
+        /**
+         * 部门编号
+         */
+        private Long id;
+
+        /**
+         * 部门名称
+         */
+        private String name;
+
+    }
+
+    /**
+     * 岗位
+     */
+    @Data
+    public static class Post {
+
+        /**
+         * 岗位编号
+         */
+        private Long id;
+
+        /**
+         * 岗位名称
+         */
+        private String name;
+
+    }
+
+}

+ 35 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.ssodemo.client.dto.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 更新用户基本信息 Request DTO
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserUpdateReqDTO {
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String mobile;
+
+    /**
+     * 用户性别
+     */
+    private Integer sex;
+
+}

+ 50 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.ssodemo.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+    @Resource
+    private OAuth2Client oauth2Client;
+
+    /**
+     * 使用刷新令牌,获得(刷新)访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
+     */
+    @PostMapping("/refresh-token")
+    public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
+        return oauth2Client.refreshToken(refreshToken);
+    }
+
+    /**
+     * 退出登录
+     *
+     * @param request 请求
+     * @return 成功
+     */
+    @PostMapping("/logout")
+    public CommonResult<Boolean> logout(HttpServletRequest request) {
+        String token = SecurityUtils.obtainAuthorization(request, "Authorization");
+        if (StrUtil.isNotBlank(token)) {
+            return oauth2Client.revokeToken(token);
+        }
+        // 返回成功
+        return new CommonResult<>();
+    }
+
+}

+ 40 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.ssodemo.controller;
+
+import cn.iocoder.yudao.ssodemo.client.UserClient;
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
+import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@RestController
+@RequestMapping("/user")
+public class UserController {
+
+    @Resource
+    private UserClient userClient;
+
+    /**
+     * 获得当前登录用户的基本信息
+     *
+     * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
+     */
+    @GetMapping("/get")
+    public CommonResult<UserInfoRespDTO> getUser() {
+        return userClient.getUser();
+    }
+
+    /**
+     * 更新当前登录用户的昵称
+     *
+     * @param nickname 昵称
+     * @return 成功
+     */
+    @PutMapping("/update")
+    public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
+        UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
+        return userClient.updateUser(updateReqDTO);
+    }
+
+}

+ 52 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.ssodemo.framework.config;
+
+import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
+import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import javax.annotation.Resource;
+
+@Configuration(proxyBeanMethods = false)
+@EnableWebSecurity
+public class SecurityConfiguration {
+
+    @Resource
+    private TokenAuthenticationFilter tokenAuthenticationFilter;
+
+    @Resource
+    private AccessDeniedHandlerImpl accessDeniedHandler;
+    @Resource
+    private AuthenticationEntryPoint authenticationEntryPoint;
+
+    @Bean
+    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
+        // 设置 URL 安全权限
+        httpSecurity.csrf().disable() // 禁用 CSRF 保护
+                .authorizeRequests()
+                // 1. 静态资源,可匿名访问
+                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
+                // 2. 登录相关的接口,可匿名访问
+                .antMatchers("/auth/login-by-code").permitAll()
+                .antMatchers("/auth/refresh-token").permitAll()
+                .antMatchers("/auth/logout").permitAll()
+                // last. 兜底规则,必须认证
+                .and().authorizeRequests()
+                .anyRequest().authenticated();
+
+        // 设置处理器
+        httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
+                .authenticationEntryPoint(authenticationEntryPoint);
+
+        // 添加 Token Filter
+        httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+        return httpSecurity.build();
+    }
+
+}

+ 37 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.ssodemo.framework.core;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 登录用户信息
+ *
+ * @author 芋道源码
+ */
+@Data
+public class LoginUser {
+
+    /**
+     * 用户编号
+     */
+    private Long id;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 租户编号
+     */
+    private Long tenantId;
+    /**
+     * 授权范围
+     */
+    private List<String> scopes;
+
+    /**
+     * 访问令牌
+     */
+    private String accessToken;
+
+}

+ 66 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java

@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.ssodemo.framework.core.filter;
+
+import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
+import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.annotation.Resource;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Token 过滤器,验证 token 的有效性
+ * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TokenAuthenticationFilter extends OncePerRequestFilter {
+
+    @Resource
+    private OAuth2Client oauth2Client;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+                                    FilterChain filterChain) throws ServletException, IOException {
+        // 1. 获得访问令牌
+        String token = SecurityUtils.obtainAuthorization(request, "Authorization");
+        if (StringUtils.hasText(token)) {
+            // 2. 基于 token 构建登录用户
+            LoginUser loginUser = buildLoginUserByToken(token);
+            // 3. 设置当前用户
+            if (loginUser != null) {
+                SecurityUtils.setLoginUser(loginUser, request);
+            }
+        }
+
+        // 继续过滤链
+        filterChain.doFilter(request, response);
+    }
+
+    private LoginUser buildLoginUserByToken(String token) {
+        try {
+            CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
+            OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
+            if (accessToken == null) {
+                return null;
+            }
+            // 构建登录用户
+            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
+                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
+                    .setAccessToken(accessToken.getAccessToken());
+        } catch (Exception exception) {
+            // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
+            return null;
+        }
+    }
+
+}

+ 44 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.ssodemo.framework.core.handler;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
+ *
+ * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
+ *
+ * @author 芋道源码
+ */
+@Component
+@SuppressWarnings("JavadocReference")
+@Slf4j
+public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
+
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
+            throws IOException, ServletException {
+        // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
+        log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
+                SecurityUtils.getLoginUserId(), e);
+        // 返回 403
+        CommonResult<Object> result = new CommonResult<>();
+        result.setCode(HttpStatus.FORBIDDEN.value());
+        result.setMsg("没有该操作权限");
+        ServletUtils.writeJSON(response, result);
+    }
+
+}

+ 36 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.ssodemo.framework.core.handler;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
+ *
+ * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
+ */
+@Component
+@Slf4j
+@SuppressWarnings("JavadocReference") // 忽略文档引用报错
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
+        log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
+        // 返回 401
+        CommonResult<Object> result = new CommonResult<>();
+        result.setCode(HttpStatus.UNAUTHORIZED.value());
+        result.setMsg("账号未登录");
+        ServletUtils.writeJSON(response, result);
+    }
+
+}

+ 103 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java

@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.ssodemo.framework.core.util;
+
+import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
+import org.springframework.lang.Nullable;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+
+/**
+ * 安全服务工具类
+ *
+ * @author 芋道源码
+ */
+public class SecurityUtils {
+
+    public static final String AUTHORIZATION_BEARER = "Bearer";
+
+    private SecurityUtils() {}
+
+    /**
+     * 从请求中,获得认证 Token
+     *
+     * @param request 请求
+     * @param header 认证 Token 对应的 Header 名字
+     * @return 认证 Token
+     */
+    public static String obtainAuthorization(HttpServletRequest request, String header) {
+        String authorization = request.getHeader(header);
+        if (!StringUtils.hasText(authorization)) {
+            return null;
+        }
+        int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
+        if (index == -1) { // 未找到
+            return null;
+        }
+        return authorization.substring(index + 7).trim();
+    }
+
+    /**
+     * 获得当前认证信息
+     *
+     * @return 认证信息
+     */
+    public static Authentication getAuthentication() {
+        SecurityContext context = SecurityContextHolder.getContext();
+        if (context == null) {
+            return null;
+        }
+        return context.getAuthentication();
+    }
+
+    /**
+     * 获取当前用户
+     *
+     * @return 当前用户
+     */
+    @Nullable
+    public static LoginUser getLoginUser() {
+        Authentication authentication = getAuthentication();
+        if (authentication == null) {
+            return null;
+        }
+        return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
+    }
+
+    /**
+     * 获得当前用户的编号,从上下文中
+     *
+     * @return 用户编号
+     */
+    @Nullable
+    public static Long getLoginUserId() {
+        LoginUser loginUser = getLoginUser();
+        return loginUser != null ? loginUser.getId() : null;
+    }
+
+    /**
+     * 设置当前用户
+     *
+     * @param loginUser 登录用户
+     * @param request 请求
+     */
+    public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
+        // 创建 Authentication,并设置到上下文
+        Authentication authentication = buildAuthentication(loginUser, request);
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+    }
+
+    private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
+        // 创建 UsernamePasswordAuthenticationToken 对象
+        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
+                loginUser, null, Collections.emptyList());
+        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+        return authenticationToken;
+    }
+
+}

+ 28 - 0
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.ssodemo.framework.core.util;
+
+import cn.hutool.extra.servlet.ServletUtil;
+import cn.hutool.json.JSONUtil;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 客户端工具类
+ *
+ * @author 芋道源码
+ */
+public class ServletUtils {
+
+    /**
+     * 返回 JSON 字符串
+     *
+     * @param response 响应
+     * @param object 对象,会序列化成 JSON 字符串
+     */
+    @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
+    public static void writeJSON(HttpServletResponse response, Object object) {
+        String content = JSONUtil.toJsonStr(object);
+        ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
+    }
+
+}

+ 2 - 0
yudao-example/yudao-sso-demo-by-password/src/main/resources/application.yaml

@@ -0,0 +1,2 @@
+server:
+  port: 18080

+ 154 - 0
yudao-example/yudao-sso-demo-by-password/src/main/resources/static/index.html

@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>首页</title>
+	<!-- jQuery:操作 dom、发起请求等 -->
+	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
+
+	<script type="application/javascript">
+
+    /**
+     * 跳转单点登录
+     */
+    function passwordLogin() {
+      window.location.href = '/login.html'
+    }
+
+    /**
+     * 修改昵称
+     */
+    function updateNickname() {
+      const nickname = prompt("请输入新的昵称", "");
+      if (!nickname) {
+        return;
+      }
+      // 更新用户的昵称
+      const accessToken = localStorage.getItem('ACCESS-TOKEN');
+      $.ajax({
+        url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
+        method: 'PUT',
+        headers: {
+          'Authorization': 'Bearer ' + accessToken
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('更新昵称失败,原因:' + result.msg)
+            return;
+          }
+          alert('更新昵称成功!');
+          $('#nicknameSpan').html(nickname);
+        }
+      });
+    }
+
+    /**
+     * 刷新令牌
+     */
+    function refreshToken() {
+      const refreshToken = localStorage.getItem('REFRESH-TOKEN');
+      if (!refreshToken) {
+        alert("获取不到刷新令牌");
+        return;
+      }
+      $.ajax({
+        url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken,
+        method: 'POST',
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('刷新访问令牌失败,原因:' + result.msg)
+            return;
+          }
+          alert('更新访问令牌成功!');
+          $('#accessTokenSpan').html(result.data.access_token);
+
+          // 设置到 localStorage 中
+          localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
+          localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
+        }
+      });
+    }
+
+    /**
+     * 登出,删除访问令牌
+     */
+    function logout() {
+      const accessToken = localStorage.getItem('ACCESS-TOKEN');
+      if (!accessToken) {
+        location.reload();
+        return;
+      }
+      $.ajax({
+        url: "http://127.0.0.1:18080/auth/logout",
+        method: 'POST',
+        headers: {
+          'Authorization': 'Bearer ' + accessToken
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('退出登录失败,原因:' + result.msg)
+            return;
+          }
+          alert('退出登录成功!');
+          // 删除 localStorage 中
+          localStorage.removeItem('ACCESS-TOKEN');
+          localStorage.removeItem('REFRESH-TOKEN');
+
+          location.reload();
+        }
+      });
+    }
+
+    $(function () {
+      const accessToken = localStorage.getItem('ACCESS-TOKEN');
+      // 情况一:未登录
+      if (!accessToken) {
+        $('#noLoginDiv').css("display", "block");
+        return;
+      }
+
+      // 情况二:已登录
+      $('#yesLoginDiv').css("display", "block");
+      $('#accessTokenSpan').html(accessToken);
+      // 获得登录用户的信息
+      $.ajax({
+        url: "http://127.0.0.1:18080/user/get",
+        method: 'GET',
+        headers: {
+          'Authorization': 'Bearer ' + accessToken
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('获得个人信息失败,原因:' + result.msg)
+            return;
+          }
+          $('#nicknameSpan').html(result.data.nickname);
+        }
+      });
+    })
+	</script>
+</head>
+<body>
+<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
+<div id="noLoginDiv" style="display: none">
+	您未登录,点击 <a href="#" onclick="passwordLogin()">跳转 </a> 账号密码登录
+</div>
+
+<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
+<div id="yesLoginDiv" style="display: none">
+	您已登录!<button onclick="logout()">退出登录</button> <br />
+	昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
+	访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br />
+</div>
+</body>
+<style>
+    body { /** 页面居中 */
+        border-radius: 20px;
+        height: 350px;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%,-50%);
+    }
+</style>
+</html>

+ 74 - 0
yudao-example/yudao-sso-demo-by-password/src/main/resources/static/login.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>登录</title>
+	<!-- jQuery:操作 dom、发起请求等 -->
+	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
+
+	<script type="application/javascript">
+
+    /**
+     * 账号密码登录
+     */
+    function login() {
+      const clientId = 'yudao-sso-demo-by-password'; // 可以改写成,你的 clientId
+      const clientSecret = 'test'; // 可以改写成,你的 clientSecret
+      const grantType = 'password'; // 密码模式
+
+      // 账号 + 密码
+      const username = $('#username').val();
+      const password = $('#password').val();
+      if (username.length === 0 || password.length === 0) {
+        alert('账号或密码未输入');
+        return;
+      }
+
+      // 发起请求
+      $.ajax({
+        url: "http://127.0.0.1:48080/admin-api/system/oauth2/token?"
+          // 客户端
+          + "client_id=" + clientId
+          + "&client_secret=" + clientSecret
+          // 密码模式的参数
+          + "&grant_type=" + grantType
+          + "&username=" + username
+          + "&password=" + password
+          + '&scope=user.read user.write',
+        method: 'POST',
+        headers: {
+          'tenant-id': '1', // 多租户编号,写死
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('登录失败,原因:' + result.msg)
+            return;
+          }
+          // 设置到 localStorage 中
+          localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
+          localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
+
+          // 提示登录成功
+          alert('登录成功!点击确认,跳转回首页');
+          window.location.href = '/index.html';
+        }
+      });
+    }
+	</script>
+</head>
+<body>
+账号:<input id="username" value="admin" /> <br />
+密码:<input id="password" value="admin123" > <br />
+<button style="float: right; margin-top: 5px;" onclick="login()">登录</button>
+</body>
+<style>
+    body { /** 页面居中 */
+        border-radius: 20px;
+        height: 350px;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%,-50%);
+    }
+</style>
+</html>

+ 6 - 3
yudao-framework/pom.xml

@@ -11,6 +11,7 @@
     <packaging>pom</packaging>
     <modules>
         <module>yudao-common</module>
+        <module>yudao-spring-boot-starter-banner</module>
         <module>yudao-spring-boot-starter-mybatis</module>
         <module>yudao-spring-boot-starter-redis</module>
         <module>yudao-spring-boot-starter-web</module>
@@ -19,24 +20,26 @@
         <module>yudao-spring-boot-starter-file</module>
         <module>yudao-spring-boot-starter-monitor</module>
         <module>yudao-spring-boot-starter-protection</module>
-        <module>yudao-spring-boot-starter-config</module>
         <module>yudao-spring-boot-starter-job</module>
         <module>yudao-spring-boot-starter-mq</module>
 
         <module>yudao-spring-boot-starter-excel</module>
         <module>yudao-spring-boot-starter-test</module>
-        <module>yudao-spring-boot-starter-extension</module>
 
         <module>yudao-spring-boot-starter-biz-operatelog</module>
         <module>yudao-spring-boot-starter-biz-dict</module>
         <module>yudao-spring-boot-starter-biz-sms</module>
-        <module>yudao-spring-boot-starter-activiti</module>
+
         <module>yudao-spring-boot-starter-biz-pay</module>
         <module>yudao-spring-boot-starter-biz-weixin</module>
         <module>yudao-spring-boot-starter-biz-social</module>
         <module>yudao-spring-boot-starter-biz-tenant</module>
         <module>yudao-spring-boot-starter-biz-data-permission</module>
+        <module>yudao-spring-boot-starter-biz-error-code</module>
+        <module>yudao-spring-boot-starter-biz-ip</module>
+
         <module>yudao-spring-boot-starter-flowable</module>
+        <module>yudao-spring-boot-starter-captcha</module>
     </modules>
 
     <artifactId>yudao-framework</artifactId>

+ 5 - 0
yudao-framework/yudao-common/pom.xml

@@ -105,6 +105,11 @@
             <artifactId>jackson-core</artifactId>
             <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>

+ 11 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/CommonStatusEnum.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.framework.common.enums;
 
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+import java.util.Arrays;
+
 /**
  * 通用状态枚举
  *
@@ -10,11 +13,13 @@ import lombok.Getter;
  */
 @Getter
 @AllArgsConstructor
-public enum CommonStatusEnum {
+public enum CommonStatusEnum implements IntArrayValuable {
 
     ENABLE(0, "开启"),
     DISABLE(1, "关闭");
 
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();
+
     /**
      * 状态值
      */
@@ -24,4 +29,9 @@ public enum CommonStatusEnum {
      */
     private final String name;
 
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
 }

+ 2 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DocumentEnum.java

@@ -12,7 +12,8 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum DocumentEnum {
 
-    REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档");
+    REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"),
+    TENANT("https://doc.iocoder.cn", "SaaS 多租户文档");
 
     private final String url;
     private final String memo;

+ 40 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.common.enums;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 终端的枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum TerminalEnum implements IntArrayValuable {
+
+    WECHAT_MINI_PROGRAM(10, "微信小程序"),
+    WECHAT_WAP(11, "微信公众号"),
+    H5(20, "H5 网页"),
+    IOS(31, "苹果 App"),
+    ANDROID(32, "安卓 App"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
+
+    /**
+     * 终端
+     */
+    private final Integer terminal;
+    /**
+     * 终端名
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+}

+ 1 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.common.enums;
 /**
  * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
  *
- *  考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
+ *  考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
  *
  * @author 芋道源码
  */

+ 60 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/ServerException.java

@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.framework.common.exception;
+
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 服务器异常 Exception
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public final class ServerException extends RuntimeException {
+
+    /**
+     * 全局错误码
+     *
+     * @see GlobalErrorCodeConstants
+     */
+    private Integer code;
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public ServerException() {
+    }
+
+    public ServerException(ErrorCode errorCode) {
+        this.code = errorCode.getCode();
+        this.message = errorCode.getMsg();
+    }
+
+    public ServerException(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public ServerException setCode(Integer code) {
+        this.code = code;
+        return this;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public ServerException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+}

+ 9 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java

@@ -29,6 +29,7 @@ public interface GlobalErrorCodeConstants {
     // ========== 服务端错误段 ==========
 
     ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
+    ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
 
     // ========== 自定义错误段 ==========
     ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
@@ -36,9 +37,15 @@ public interface GlobalErrorCodeConstants {
 
     ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
 
-   static boolean isMatch(Integer code) {
+    /**
+     * 是否为服务端错误,参考 HTTP 5XX 错误码段
+     *
+     * @param code 错误码
+     * @return 是否
+     */
+   static boolean isServerErrorCode(Integer code) {
        return code != null
-               && code >= SUCCESS.getCode() && code <= UNKNOWN.getCode();
+               && code >= INTERNAL_SERVER_ERROR.getCode() && code <= INTERNAL_SERVER_ERROR.getCode() + 99;
    }
 
 }

+ 5 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/ServiceErrorCodeRange.java

@@ -29,6 +29,10 @@ package cn.iocoder.yudao.framework.common.exception.enums;
  */
 public class ServiceErrorCodeRange {
 
-    // 模块 system 错误码区间 [1-000-001-000 ~ 1-000-002-000]
+    // 模块 infra 错误码区间 [1-001-000-000 ~ 1-002-000-000)
+    // 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000)
+    // 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
+    // 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
+    // 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
 
 }

+ 15 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.framework.common.pojo;
 
 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServerException;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -91,10 +92,24 @@ public class CommonResult<T> implements Serializable {
         if (isSuccess()) {
             return;
         }
+        // 服务端异常
+        if (GlobalErrorCodeConstants.isServerErrorCode(code)) {
+            throw new ServerException(code, msg);
+        }
         // 业务异常
         throw new ServiceException(code, msg);
     }
 
+    /**
+     * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
+     * 如果没有,则返回 {@link #data} 数据
+     */
+    @JsonIgnore // 避免 jackson 序列化
+    public T getCheckedData() {
+        checkError();
+        return data;
+    }
+
     public static <T> CommonResult<T> error(ServiceException serviceException) {
         return error(serviceException.getCode(), serviceException.getMessage());
     }

+ 2 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageParam.java

@@ -23,8 +23,8 @@ public class PageParam implements Serializable {
 
     @ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
     @NotNull(message = "每页条数不能为空")
-    @Min(value = 1, message = "页码最小值为 1")
-    @Max(value = 100, message = "页码最大值为 100")
+    @Min(value = 1, message = "每页条数最小值为 1")
+    @Max(value = 100, message = "每页条数最大值为 100")
     private Integer pageSize = PAGE_SIZE;
 
 }

+ 25 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.common.util.cache;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import java.time.Duration;
+import java.util.concurrent.Executors;
+
+/**
+ * Cache 工具类
+ *
+ * @author 芋道源码
+ */
+public class CacheUtils {
+
+    public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
+        return CacheBuilder.newBuilder()
+                // 只阻塞当前数据加载线程,其他线程返回旧值
+                .refreshAfterWrite(duration)
+                // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
+                .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置
+    }
+
+}

+ 9 - 7
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java

@@ -1,15 +1,10 @@
 package cn.iocoder.yudao.framework.common.util.collection;
 
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.collection.IterUtil;
 import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.TypeUtil;
-import org.springframework.cglib.core.TypeUtils;
 
-import java.lang.reflect.Array;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
 import java.util.Collection;
-import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -50,7 +45,14 @@ public class ArrayUtils {
         if (CollectionUtil.isEmpty(from)) {
             return (T[]) (new Object[0]);
         }
-        return ArrayUtil.toArray(from, (Class<T>) CollectionUtil.getElementType(from.iterator()));
+        return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));
+    }
+
+    public static <T> T get(T[] array, int index) {
+        if (null == array || index >= array.length) {
+            return null;
+        }
+        return array[index];
     }
 
 }

+ 17 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -173,6 +173,23 @@ public class CollectionUtils {
         return valueFunc.apply(t);
     }
 
+    public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
+        if (CollUtil.isEmpty(from)) {
+            return null;
+        }
+        assert from.size() > 0; // 断言,避免告警
+        T t = from.stream().min(Comparator.comparing(valueFunc)).get();
+        return valueFunc.apply(t);
+    }
+
+    public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc, BinaryOperator<V> accumulator) {
+        if (CollUtil.isEmpty(from)) {
+            return null;
+        }
+        assert from.size() > 0; // 断言,避免告警
+        return from.stream().map(valueFunc).reduce(accumulator).get();
+    }
+
     public static <T> void addIfNotNull(Collection<T> coll, T item) {
         if (item == null) {
             return;

+ 1 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/SetUtils.java

@@ -11,6 +11,7 @@ import java.util.Set;
  */
 public class SetUtils {
 
+    @SafeVarargs
     public static <T> Set<T> asSet(T... objs) {
         return new HashSet<>(Arrays.asList(objs));
     }

+ 66 - 16
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.framework.common.util.date;
 
-import java.time.Duration;
+import cn.hutool.core.date.LocalDateTimeUtil;
+
+import java.time.*;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -23,6 +25,37 @@ public class DateUtils {
 
     public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
 
+    public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";
+
+    /**
+     * 将 LocalDateTime 转换成 Date
+     *
+     * @param date LocalDateTime
+     * @return LocalDateTime
+     */
+    public static Date of(LocalDateTime date) {
+        // 将此日期时间与时区相结合以创建 ZonedDateTime
+        ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
+        // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
+        Instant instant = zonedDateTime.toInstant();
+        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
+        return Date.from(instant);
+    }
+
+    /**
+     * 将 Date 转换成 LocalDateTime
+     *
+     * @param date Date
+     * @return LocalDateTime
+     */
+    public static LocalDateTime of(Date date) {
+        // 转为时间戳
+        Instant instant = date.toInstant();
+        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
+        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+    }
+
+    @Deprecated
     public static Date addTime(Duration duration) {
         return new Date(System.currentTimeMillis() + duration.toMillis());
     }
@@ -31,6 +64,11 @@ public class DateUtils {
         return System.currentTimeMillis() > time.getTime();
     }
 
+    public static boolean isExpired(LocalDateTime time) {
+        LocalDateTime now = LocalDateTime.now();
+        return now.isAfter(time);
+    }
+
     public static long diff(Date endTime, Date startTime) {
         return endTime.getTime() - startTime.getTime();
     }
@@ -38,9 +76,9 @@ public class DateUtils {
     /**
      * 创建指定时间
      *
-     * @param year        
-     * @param mouth       
-     * @param day         
+     * @param year  年
+     * @param mouth 月
+     * @param day   日
      * @return 指定时间
      */
     public static Date buildTime(int year, int mouth, int day) {
@@ -50,12 +88,12 @@ public class DateUtils {
     /**
      * 创建指定时间
      *
-     * @param year        
-     * @param mouth       
-     * @param day         
-     * @param hour        小时
-     * @param minute      分钟
-     * @param second      
+     * @param year   年
+     * @param mouth  月
+     * @param day    日
+     * @param hour   小时
+     * @param minute 分钟
+     * @param second 秒
      * @return 指定时间
      */
     public static Date buildTime(int year, int mouth, int day,
@@ -81,12 +119,14 @@ public class DateUtils {
         return a.compareTo(b) > 0 ? a : b;
     }
 
-    public static boolean beforeNow(Date date) {
-        return date.getTime() < System.currentTimeMillis();
-    }
-
-    public static boolean afterNow(Date date) {
-        return date.getTime() >= System.currentTimeMillis();
+    public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
+        if (a == null) {
+            return b;
+        }
+        if (b == null) {
+            return a;
+        }
+        return a.isAfter(b) ? a : b;
     }
 
     /**
@@ -120,4 +160,14 @@ public class DateUtils {
         return c.getTime();
     }
 
+    /**
+     * 是否今天
+     *
+     * @param date 日期
+     * @return 是否
+     */
+    public static boolean isToday(LocalDateTime date) {
+        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
+    }
+
 }

+ 58 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.framework.common.util.date;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+/**
+ * 时间工具类,用于 {@link java.time.LocalDateTime}
+ *
+ * @author 芋道源码
+ */
+public class LocalDateTimeUtils {
+
+    /**
+     * 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
+     */
+    public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
+
+    public static LocalDateTime addTime(Duration duration) {
+        return LocalDateTime.now().plus(duration);
+    }
+
+    public static boolean beforeNow(LocalDateTime date) {
+        return date.isBefore(LocalDateTime.now());
+    }
+
+    public static boolean afterNow(LocalDateTime date) {
+        return date.isAfter(LocalDateTime.now());
+    }
+
+    /**
+     * 创建指定时间
+     *
+     * @param year  年
+     * @param mouth 月
+     * @param day   日
+     * @return 指定时间
+     */
+    public static LocalDateTime buildTime(int year, int mouth, int day) {
+        return LocalDateTime.of(year, mouth, day, 0, 0, 0);
+    }
+
+    /**
+     * 判断当前时间是否在该时间范围内
+     *
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @return 是否
+     */
+    public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) {
+        if (startTime == null || endTime == null) {
+            return false;
+        }
+        return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);
+    }
+
+}

+ 23 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java

@@ -1,9 +1,14 @@
 package cn.iocoder.yudao.framework.common.util.io;
 
+import cn.hutool.core.io.FileTypeUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.file.FileNameUtil;
 import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
 import lombok.SneakyThrows;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 
 /**
@@ -58,4 +63,22 @@ public class FileUtils {
         return file;
     }
 
+    /**
+     * 生成文件路径
+     *
+     * @param content      文件内容
+     * @param originalName 原始文件名
+     * @return path,唯一不可重复
+     */
+    public static String generatePath(byte[] content, String originalName) {
+        String sha256Hex = DigestUtil.sha256Hex(content);
+        // 情况一:如果存在 name,则优先使用 name 的后缀
+        if (StrUtil.isNotBlank(originalName)) {
+            String extName = FileNameUtil.extName(originalName);
+            return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
+        }
+        // 情况二:基于 content 计算
+        return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
+    }
+
 }

+ 7 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java

@@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import lombok.SneakyThrows;
 import lombok.experimental.UtilityClass;
 import lombok.extern.slf4j.Slf4j;
@@ -28,6 +29,7 @@ public class JsonUtils {
 
     static {
         objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+        objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
     }
 
     /**
@@ -51,6 +53,10 @@ public class JsonUtils {
         return objectMapper.writeValueAsBytes(object);
     }
 
+    @SneakyThrows
+    public static String toJsonPrettyString(Object object) {
+        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
+    }
 
     public static <T> T parseObject(String text, Class<T> clazz) {
         if (StrUtil.isEmpty(text)) {
@@ -132,7 +138,7 @@ public class JsonUtils {
     }
 
     public static boolean isJson(String text) {
-        return JSONUtil.isJson(text);
+        return JSONUtil.isTypeJSON(text);
     }
 
 }

+ 2 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/ObjectUtils.java

@@ -1,12 +1,10 @@
 package cn.iocoder.yudao.framework.common.util.object;
 
-import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ReflectUtil;
 
 import java.lang.reflect.Field;
 import java.util.Arrays;
-import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -47,6 +45,7 @@ public class ObjectUtils {
         return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
     }
 
+    @SafeVarargs
     public static <T> T defaultIfNull(T... array) {
         for (T item : array) {
             if (item != null) {
@@ -56,6 +55,7 @@ public class ObjectUtils {
         return null;
     }
 
+    @SafeVarargs
     public static <T> boolean equalsAny(T obj, T... array) {
         return Arrays.asList(array).contains(obj);
     }

+ 8 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java

@@ -3,7 +3,10 @@ package cn.iocoder.yudao.framework.common.util.string;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 字符串工具类
@@ -37,4 +40,9 @@ public class StrUtils {
         return false;
     }
 
+    public static List<Long> splitToLong(String value,  CharSequence separator) {
+        long[] longs = StrUtil.splitToLong(value, separator);
+        return Arrays.stream(longs).boxed().collect(Collectors.toList());
+    }
+
 }

+ 4 - 5
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java

@@ -17,16 +17,15 @@ import java.util.regex.Pattern;
  */
 public class ValidationUtils {
 
+    private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[189]))\\d{8}$");
+
     private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
 
     private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*");
 
     public static boolean isMobile(String mobile) {
-        if (StrUtil.length(mobile) != 11) {
-            return false;
-        }
-        // TODO 芋艿,后面完善手机校验
-        return true;
+        return StringUtils.hasText(mobile)
+                && PATTERN_MOBILE.matcher(mobile).matches();
     }
 
     public static boolean isURL(String url) {

+ 0 - 48
yudao-framework/yudao-spring-boot-starter-activiti/pom.xml

@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <groupId>cn.iocoder.boot</groupId>
-        <artifactId>yudao-framework</artifactId>
-        <version>${revision}</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>yudao-spring-boot-starter-activiti</artifactId>
-    <packaging>jar</packaging>
-
-    <name>${project.artifactId}</name>
-    <description>Activiti 拓展</description>
-    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
-
-    <dependencies>
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-common</artifactId>
-        </dependency>
-
-        <!-- Web 相关 -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-security</artifactId>
-        </dependency>
-
-        <!-- DB 相关 -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
-        </dependency>
-
-        <!-- 工作流相关 -->
-        <dependency>
-            <groupId>org.activiti</groupId>
-            <artifactId>activiti-spring-boot-starter</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.activiti</groupId>
-            <artifactId>activiti-image-generator</artifactId>
-        </dependency>
-
-    </dependencies>
-
-</project>

+ 0 - 45
yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.framework.activiti.config;
-
-import cn.iocoder.yudao.framework.activiti.core.web.ActivitiWebFilter;
-import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
-import org.activiti.image.ProcessDiagramGenerator;
-import org.activiti.image.impl.DefaultProcessDiagramGenerator;
-import org.activiti.spring.SpringProcessEngineConfiguration;
-import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
-import org.apache.ibatis.session.SqlSessionFactory;
-import org.apache.ibatis.transaction.TransactionFactory;
-import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.transaction.PlatformTransactionManager;
-
-@Configuration
-public class YudaoActivitiConfiguration {
-
-    /**
-     * Activiti 流程图的生成器。目前管理后台的流程图 svg,通过它绘制生成。
-     */
-    @Bean
-    public ProcessDiagramGenerator processDiagramGenerator() {
-        return new DefaultProcessDiagramGenerator();
-    }
-
-    @Bean
-    public FilterRegistrationBean<ActivitiWebFilter> activitiWebFilter() {
-        FilterRegistrationBean<ActivitiWebFilter> registrationBean = new FilterRegistrationBean<>();
-        registrationBean.setFilter(new ActivitiWebFilter());
-        registrationBean.setOrder(WebFilterOrderEnum.ACTIVITI_FILTER);
-        return registrationBean;
-    }
-
-    /**
-     * ProcessEngineConfigurationConfigurer 实现类,设置事务管理器,保证 ACT_ 表和自己的表的事务一致性
-     */
-    @Bean
-    public ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer(
-            PlatformTransactionManager platformTransactionManager) {
-        return processEngineConfiguration -> processEngineConfiguration.setTransactionManager(platformTransactionManager);
-    }
-
-}

+ 0 - 109
yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/core/util/ActivitiUtils.java

@@ -1,109 +0,0 @@
-package cn.iocoder.yudao.framework.activiti.core.util;
-
-import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.ReflectUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
-import com.alibaba.ttl.TransmittableThreadLocal;
-import org.activiti.bpmn.converter.BpmnXMLConverter;
-import org.activiti.bpmn.model.BpmnModel;
-import org.activiti.bpmn.model.FlowElement;
-import org.activiti.bpmn.model.Process;
-import org.activiti.engine.impl.identity.Authentication;
-import org.activiti.engine.impl.util.io.BytesStreamSource;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Consumer;
-
-/**
- * Activiti 工具类
- *
- * @author 芋道源码
- */
-public class ActivitiUtils {
-
-    static {
-        setAuthenticationThreadLocal();
-    }
-
-    // ========== Authentication 相关 ==========
-
-    /**
-     * 反射修改 Authentication 的 authenticatedUserIdThreadLocal 静态变量,使用 TTL 线程变量
-     * 目的:保证 @Async 等异步执行时,变量丢失的问题
-     */
-    private static void setAuthenticationThreadLocal() {
-        ReflectUtil.setFieldValue(Authentication.class, "authenticatedUserIdThreadLocal",
-                new TransmittableThreadLocal<String>());
-    }
-
-    public static void setAuthenticatedUserId(Long userId) {
-        Authentication.setAuthenticatedUserId(String.valueOf(userId));
-    }
-
-    public static void clearAuthenticatedUserId() {
-        Authentication.setAuthenticatedUserId(null);
-    }
-
-    public static boolean equals(String userIdStr, Long userId) {
-        return Objects.equals(userId, NumberUtils.parseLong(userIdStr));
-    }
-
-    // ========== BPMN XML 相关 ==========
-
-    /**
-     * 构建对应的 BPMN Model
-     *
-     * @param bpmnBytes 原始的 BPMN XML 字节数组
-     * @return BPMN Model
-     */
-    public static BpmnModel buildBpmnModel(byte[] bpmnBytes) {
-        // 转换成 BpmnModel 对象
-        BpmnXMLConverter converter = new BpmnXMLConverter();
-        return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
-    }
-
-    /**
-     * 获得 BPMN 流程中,指定的元素们
-     *
-     * @param model
-     * @param clazz 指定元素。例如说,{@link org.activiti.bpmn.model.UserTask}、{@link org.activiti.bpmn.model.Gateway} 等等
-     * @return 元素们
-     */
-    public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
-        List<T> result = new ArrayList<>();
-        model.getProcesses().forEach(process -> {
-            process.getFlowElements().forEach(flowElement -> {
-                if (flowElement.getClass().isAssignableFrom(clazz)) {
-                    result.add((T) flowElement);
-                }
-            });
-        });
-        return result;
-    }
-
-    public static String getBpmnXml(BpmnModel model) {
-        if (model == null) {
-            return null;
-        }
-        return StrUtil.utf8Str(getBpmnBytes(model));
-    }
-
-    public static byte[] getBpmnBytes(BpmnModel model) {
-        if (model == null) {
-            return new byte[0];
-        }
-        BpmnXMLConverter converter = new BpmnXMLConverter();
-        return converter.convertToXML(model);
-    }
-
-    public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
-        // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
-        return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
-    }
-
-}

+ 0 - 37
yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/core/web/ActivitiWebFilter.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.framework.activiti.core.web;
-
-import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
-import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
-import org.springframework.web.filter.OncePerRequestFilter;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-/**
- * Activiti Web 过滤器,将 userId 设置到 {@link org.activiti.engine.impl.identity.Authentication} 中
- *
- * @author 芋道源码
- */
-public class ActivitiWebFilter extends OncePerRequestFilter {
-
-    @Override
-    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
-            throws ServletException, IOException {
-        try {
-            // 设置工作流的用户
-            Long userId = SecurityFrameworkUtils.getLoginUserId();
-            if (userId != null) {
-                ActivitiUtils.setAuthenticatedUserId(userId);
-            }
-            // 过滤
-            chain.doFilter(request, response);
-        } finally {
-            // 清理
-            ActivitiUtils.clearAuthenticatedUserId();
-        }
-    }
-
-}

+ 0 - 1
yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/package-info.java

@@ -1 +0,0 @@
-package cn.iocoder.yudao.framework.activiti;

+ 0 - 2
yudao-framework/yudao-spring-boot-starter-activiti/src/main/resources/META-INF/spring.factories

@@ -1,2 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  cn.iocoder.yudao.framework.activiti.config.YudaoActivitiConfiguration

+ 30 - 0
yudao-framework/yudao-spring-boot-starter-banner/pom.xml

@@ -0,0 +1,30 @@
+<?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>yudao-framework</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-spring-boot-starter-banner</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>Banner 用于在 console 控制台,打印开发文档、接口文档等</description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 20 - 0
yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/config/YudaoBannerAutoConfiguration.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.framework.banner.config;
+
+import cn.iocoder.yudao.framework.banner.core.BannerApplicationRunner;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Banner 的自动配置类
+ *
+ * @author 芋道源码
+ */
+@AutoConfiguration
+public class YudaoBannerAutoConfiguration {
+
+    @Bean
+    public BannerApplicationRunner bannerApplicationRunner() {
+        return new BannerApplicationRunner();
+    }
+
+}

+ 50 - 0
yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.framework.banner.core;
+
+import cn.hutool.core.thread.ThreadUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.util.ClassUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 项目启动成功后,提供文档相关的地址
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class BannerApplicationRunner implements ApplicationRunner {
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        ThreadUtil.execute(() -> {
+            ThreadUtil.sleep(1, TimeUnit.SECONDS); // 延迟 1 秒,保证输出到结尾
+            log.info("\n----------------------------------------------------------\n\t" +
+                            "项目启动成功!\n\t" +
+                            "接口文档: \t{} \n\t" +
+                            "开发文档: \t{} \n\t" +
+                            "视频教程: \t{} \n\t" +
+                            "源码解析: \t{} \n" +
+                            "----------------------------------------------------------",
+                    "https://doc.iocoder.cn/api-doc/",
+                    "https://doc.iocoder.cn",
+                    "https://t.zsxq.com/02Yf6M7Qn",
+                    "https://t.zsxq.com/02B6ujIee");
+
+            // 数据报表
+            if (isNotPresent("cn.iocoder.yudao.module.visualization.framework.security.config.SecurityConfiguration")) {
+                System.out.println("[报表模块 yudao-module-visualization - 已禁用][参考 https://doc.iocoder.cn/report/ 开启]");
+            }
+            // 工作流
+            if (isNotPresent("cn.iocoder.yudao.framework.flowable.config.YudaoFlowableConfiguration")) {
+                System.out.println("[工作流模块 yudao-module-bpm - 已禁用][参考 https://doc.iocoder.cn/bpm/ 开启]");
+            }
+        });
+    }
+
+    private static boolean isNotPresent(String className) {
+        return !ClassUtils.isPresent(className, ClassUtils.getDefaultClassLoader());
+    }
+
+}

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * Banner 用于在 console 控制台,打印开发文档、接口文档等
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.framework.banner;

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-banner/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration

+ 0 - 0
yudao-server/src/main/resources/banner.txt → yudao-framework/yudao-spring-boot-starter-banner/src/main/resources/banner.txt


+ 4 - 5
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDataPermissionAutoConfiguration.java

@@ -7,17 +7,17 @@ import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFac
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
 
 import java.util.List;
 
 /**
- * 数据全新啊的自动配置类
+ * 数据权限的自动配置类
  *
  * @author 芋道源码
  */
-@Configuration
+@AutoConfiguration
 public class YudaoDataPermissionAutoConfiguration {
 
     @Bean
@@ -27,9 +27,8 @@ public class YudaoDataPermissionAutoConfiguration {
 
     @Bean
     public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,
-                                                                               List<DataPermissionRule> rules) {
+                                                                               DataPermissionRuleFactory ruleFactory) {
         // 创建 DataPermissionDatabaseInterceptor 拦截器
-        DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules);
         DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory);
         // 添加到 interceptor 中
         // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDeptDataPermissionAutoConfiguration.java

@@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissi
 import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
 
 import java.util.List;
 
@@ -16,7 +16,7 @@ import java.util.List;
  *
  * @author 芋道源码
  */
-@Configuration
+@AutoConfiguration
 @ConditionalOnClass(LoginUser.class)
 @ConditionalOnBean(value = {PermissionApi.class, DeptDataPermissionRuleCustomizer.class})
 public class YudaoDeptDataPermissionAutoConfiguration {

+ 211 - 80
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java

@@ -18,7 +18,6 @@ import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
 import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
-import net.sf.jsqlparser.expression.operators.relational.ItemsList;
 import net.sf.jsqlparser.schema.Table;
 import net.sf.jsqlparser.statement.delete.Delete;
 import net.sf.jsqlparser.statement.select.*;
@@ -37,7 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * 数据权限拦截器,通过 {@link DataPermissionRule} 数据权限规则,重写 SQL 的方式来实现
- * 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, Table)} 方法
+ * 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, List)} 方法
  *
  * 整体的代码实现上,参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。
  * 所以每次 MyBatis Plus 升级时,需要 Review 下其具体的实现是否有变更!
@@ -53,8 +52,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
     private final MappedStatementCache mappedStatementCache = new MappedStatementCache();
 
     @Override // SELECT 场景
-    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
-                            RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
+    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
         // 获得 Mapper 对应的数据权限的规则
         List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());
         if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过
@@ -68,12 +66,14 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
             // 处理 SQL
             mpBs.sql(parserSingle(mpBs.sql(), null));
         } finally {
+            // 添加是否需要重写的缓存
             addMappedStatementCache(ms);
+            // 清空上下文
             ContextHolder.clear();
         }
     }
 
-    @Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景
+    @Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景(因为 INSERT 不需要数据权限)
     public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
         PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
         MappedStatement ms = mpSh.mappedStatement();
@@ -92,7 +92,9 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
                 // 处理 SQL
                 mpBs.sql(parserMulti(mpBs.sql(), null));
             } finally {
+                // 添加是否需要重写的缓存
                 addMappedStatementCache(ms);
+                // 清空上下文
                 ContextHolder.clear();
             }
         }
@@ -107,24 +109,6 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
         }
     }
 
-    protected void processSelectBody(SelectBody selectBody) {
-        if (selectBody == null) {
-            return;
-        }
-        if (selectBody instanceof PlainSelect) {
-            processPlainSelect((PlainSelect) selectBody);
-        } else if (selectBody instanceof WithItem) {
-            WithItem withItem = (WithItem) selectBody;
-            processSelectBody(withItem.getSubSelect().getSelectBody());
-        } else {
-            SetOperationList operationList = (SetOperationList) selectBody;
-            List<SelectBody> selectBodys = operationList.getSelects();
-            if (CollectionUtils.isNotEmpty(selectBodys)) {
-                selectBodys.forEach(this::processSelectBody);
-            }
-        }
-    }
-
     /**
      * update 语句处理
      */
@@ -142,28 +126,77 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
         delete.setWhere(this.builderExpression(delete.getWhere(), delete.getTable()));
     }
 
+    // ========== 和 TenantLineInnerInterceptor 一致的逻辑 ==========
+
+    protected void processSelectBody(SelectBody selectBody) {
+        if (selectBody == null) {
+            return;
+        }
+        if (selectBody instanceof PlainSelect) {
+            processPlainSelect((PlainSelect) selectBody);
+        } else if (selectBody instanceof WithItem) {
+            WithItem withItem = (WithItem) selectBody;
+            processSelectBody(withItem.getSubSelect().getSelectBody());
+        } else {
+            SetOperationList operationList = (SetOperationList) selectBody;
+            List<SelectBody> selectBodyList = operationList.getSelects();
+            if (CollectionUtils.isNotEmpty(selectBodyList)) {
+                selectBodyList.forEach(this::processSelectBody);
+            }
+        }
+    }
+
     /**
      * 处理 PlainSelect
      */
     protected void processPlainSelect(PlainSelect plainSelect) {
-        FromItem fromItem = plainSelect.getFromItem();
-        Expression where = plainSelect.getWhere();
-        processWhereSubSelect(where);
-        if (fromItem instanceof Table) {
-            Table fromTable = (Table) fromItem;
-            plainSelect.setWhere(builderExpression(where, fromTable));
-        } else {
-            processFromItem(fromItem);
-        }
         //#3087 github
         List<SelectItem> selectItems = plainSelect.getSelectItems();
         if (CollectionUtils.isNotEmpty(selectItems)) {
             selectItems.forEach(this::processSelectItem);
         }
+
+        // 处理 where 中的子查询
+        Expression where = plainSelect.getWhere();
+        processWhereSubSelect(where);
+
+        // 处理 fromItem
+        FromItem fromItem = plainSelect.getFromItem();
+        List<Table> list = processFromItem(fromItem);
+        List<Table> mainTables = new ArrayList<>(list);
+
+        // 处理 join
         List<Join> joins = plainSelect.getJoins();
         if (CollectionUtils.isNotEmpty(joins)) {
-            processJoins(joins);
+            mainTables = processJoins(mainTables, joins);
+        }
+
+        // 当有 mainTable 时,进行 where 条件追加
+        if (CollectionUtils.isNotEmpty(mainTables)) {
+            plainSelect.setWhere(builderExpression(where, mainTables));
+        }
+    }
+
+    private List<Table> processFromItem(FromItem fromItem) {
+        // 处理括号括起来的表达式
+        while (fromItem instanceof ParenthesisFromItem) {
+            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
+        }
+
+        List<Table> mainTables = new ArrayList<>();
+        // 无 join 时的处理逻辑
+        if (fromItem instanceof Table) {
+            Table fromTable = (Table) fromItem;
+            mainTables.add(fromTable);
+        } else if (fromItem instanceof SubJoin) {
+            // SubJoin 类型则还需要添加上 where 条件
+            List<Table> tables = processSubJoin((SubJoin) fromItem);
+            mainTables.addAll(tables);
+        } else {
+            // 处理下 fromItem
+            processOtherFromItem(fromItem);
         }
+        return mainTables;
     }
 
     /**
@@ -191,7 +224,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
             return;
         }
         if (where instanceof FromItem) {
-            processFromItem((FromItem) where);
+            processOtherFromItem((FromItem) where);
             return;
         }
         if (where.toString().indexOf("SELECT") > 0) {
@@ -204,9 +237,9 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
             } else if (where instanceof InExpression) {
                 // in
                 InExpression expression = (InExpression) where;
-                ItemsList itemsList = expression.getRightItemsList();
-                if (itemsList instanceof SubSelect) {
-                    processSelectBody(((SubSelect) itemsList).getSelectBody());
+                Expression inExpression = expression.getRightExpression();
+                if (inExpression instanceof SubSelect) {
+                    processSelectBody(((SubSelect) inExpression).getSelectBody());
                 }
             } else if (where instanceof ExistsExpression) {
                 // exists
@@ -239,7 +272,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
      * <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
      * <p> fixed gitee pulls/141</p>
      *
-     * @param function 函数
+     * @param function
      */
     protected void processFunction(Function function) {
         ExpressionList parameters = function.getParameters();
@@ -257,22 +290,19 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
     /**
      * 处理子查询等
      */
-    protected void processFromItem(FromItem fromItem) {
-        if (fromItem instanceof SubJoin) {
-            SubJoin subJoin = (SubJoin) fromItem;
-            if (subJoin.getJoinList() != null) {
-                processJoins(subJoin.getJoinList());
-            }
-            if (subJoin.getLeft() != null) {
-                processFromItem(subJoin.getLeft());
-            }
-        } else if (fromItem instanceof SubSelect) {
+    protected void processOtherFromItem(FromItem fromItem) {
+        // 去除括号
+        while (fromItem instanceof ParenthesisFromItem) {
+            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
+        }
+
+        if (fromItem instanceof SubSelect) {
             SubSelect subSelect = (SubSelect) fromItem;
             if (subSelect.getSelectBody() != null) {
                 processSelectBody(subSelect.getSelectBody());
             }
         } else if (fromItem instanceof ValuesList) {
-            logger.debug("Perform a subquery, if you do not give us feedback");
+            logger.debug("Perform a subQuery, if you do not give us feedback");
         } else if (fromItem instanceof LateralSubSelect) {
             LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
             if (lateralSubSelect.getSubSelect() != null) {
@@ -284,75 +314,176 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
         }
     }
 
+    /**
+     * 处理 sub join
+     *
+     * @param subJoin subJoin
+     * @return Table subJoin 中的主表
+     */
+    private List<Table> processSubJoin(SubJoin subJoin) {
+        List<Table> mainTables = new ArrayList<>();
+        if (subJoin.getJoinList() != null) {
+            List<Table> list = processFromItem(subJoin.getLeft());
+            mainTables.addAll(list);
+            mainTables = processJoins(mainTables, subJoin.getJoinList());
+        }
+        return mainTables;
+    }
+
     /**
      * 处理 joins
      *
-     * @param joins join 集合
+     * @param mainTables 可以为 null
+     * @param joins      join 集合
+     * @return List<Table> 右连接查询的 Table 列表
      */
-    private void processJoins(List<Join> joins) {
+    private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {
+        // join 表达式中最终的主表
+        Table mainTable = null;
+        // 当前 join 的左表
+        Table leftTable = null;
+
+        if (mainTables == null) {
+            mainTables = new ArrayList<>();
+        } else if (mainTables.size() == 1) {
+            mainTable = mainTables.get(0);
+            leftTable = mainTable;
+        }
+
         //对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
-        Deque<Table> tables = new LinkedList<>();
+        Deque<List<Table>> onTableDeque = new LinkedList<>();
         for (Join join : joins) {
             // 处理 on 表达式
-            FromItem fromItem = join.getRightItem();
-            if (fromItem instanceof Table) {
-                Table fromTable = (Table) fromItem;
+            FromItem joinItem = join.getRightItem();
+
+            // 获取当前 join 的表,subJoint 可以看作是一张表
+            List<Table> joinTables = null;
+            if (joinItem instanceof Table) {
+                joinTables = new ArrayList<>();
+                joinTables.add((Table) joinItem);
+            } else if (joinItem instanceof SubJoin) {
+                joinTables = processSubJoin((SubJoin) joinItem);
+            }
+
+            if (joinTables != null) {
+
+                // 如果是隐式内连接
+                if (join.isSimple()) {
+                    mainTables.addAll(joinTables);
+                    continue;
+                }
+
+                // 当前表是否忽略
+                Table joinTable = joinTables.get(0);
+
+                List<Table> onTables = null;
+                // 如果不要忽略,且是右连接,则记录下当前表
+                if (join.isRight()) {
+                    mainTable = joinTable;
+                    if (leftTable != null) {
+                        onTables = Collections.singletonList(leftTable);
+                    }
+                } else if (join.isLeft()) {
+                    onTables = Collections.singletonList(joinTable);
+                } else if (join.isInner()) {
+                    if (mainTable == null) {
+                        onTables = Collections.singletonList(joinTable);
+                    } else {
+                        onTables = Arrays.asList(mainTable, joinTable);
+                    }
+                    mainTable = null;
+                }
+
+                mainTables = new ArrayList<>();
+                if (mainTable != null) {
+                    mainTables.add(mainTable);
+                }
+
                 // 获取 join 尾缀的 on 表达式列表
                 Collection<Expression> originOnExpressions = join.getOnExpressions();
                 // 正常 join on 表达式只有一个,立刻处理
-                if (originOnExpressions.size() == 1) {
-                    processJoin(join);
+                if (originOnExpressions.size() == 1 && onTables != null) {
+                    List<Expression> onExpressions = new LinkedList<>();
+                    onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));
+                    join.setOnExpressions(onExpressions);
+                    leftTable = joinTable;
                     continue;
                 }
-                tables.push(fromTable);
+                // 表名压栈,忽略的表压入 null,以便后续不处理
+                onTableDeque.push(onTables);
                 // 尾缀多个 on 表达式的时候统一处理
                 if (originOnExpressions.size() > 1) {
                     Collection<Expression> onExpressions = new LinkedList<>();
                     for (Expression originOnExpression : originOnExpressions) {
-                        Table currentTable = tables.poll();
-                        onExpressions.add(builderExpression(originOnExpression, currentTable));
+                        List<Table> currentTableList = onTableDeque.poll();
+                        if (CollectionUtils.isEmpty(currentTableList)) {
+                            onExpressions.add(originOnExpression);
+                        } else {
+                            onExpressions.add(builderExpression(originOnExpression, currentTableList));
+                        }
                     }
                     join.setOnExpressions(onExpressions);
                 }
+                leftTable = joinTable;
             } else {
-                // 处理右边连接的子表达式
-                processFromItem(fromItem);
+                processOtherFromItem(joinItem);
+                leftTable = null;
             }
         }
+
+        return mainTables;
     }
 
+    // ========== 和 TenantLineInnerInterceptor 存在差异的逻辑:关键,实现权限条件的拼接 ==========
+
     /**
-     * 处理联接语句
+     * 处理条件
+     *
+     * @param currentExpression 当前 where 条件
+     * @param table             单个表
      */
-    protected void processJoin(Join join) {
-        if (join.getRightItem() instanceof Table) {
-            Table fromTable = (Table) join.getRightItem();
-            Expression originOnExpression = CollUtil.getFirst(join.getOnExpressions());
-            originOnExpression = builderExpression(originOnExpression, fromTable);
-            join.setOnExpressions(CollUtil.newArrayList(originOnExpression));
-        }
+    protected Expression builderExpression(Expression currentExpression, Table table) {
+        return this.builderExpression(currentExpression, Collections.singletonList(table));
     }
 
     /**
      * 处理条件
+     *
+     * @param currentExpression 当前 where 条件
+     * @param tables 多个表
      */
-    protected Expression builderExpression(Expression currentExpression, Table table) {
-        // 获得 Table 对应的数据权限条件
-        Expression equalsTo = buildDataPermissionExpression(table);
-        if (equalsTo == null) { // 如果没条件,则返回 currentExpression 默认
+    protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
+        // 没有表需要处理直接返回
+        if (CollectionUtils.isEmpty(tables)) {
             return currentExpression;
         }
 
-        // 表达式为空,则直接返回 equalsTo
+        // 第一步,获得 Table 对应的数据权限条件
+        Expression dataPermissionExpression = null;
+        for (Table table : tables) {
+            // 构建每个表的权限 Expression 条件
+            Expression expression = buildDataPermissionExpression(table);
+            if (expression == null) {
+                continue;
+            }
+            // 合并到 dataPermissionExpression 中
+            dataPermissionExpression = dataPermissionExpression == null ? expression
+                    : new AndExpression(dataPermissionExpression, expression);
+        }
+
+        // 第二步,合并多个 Expression 条件
+        if (dataPermissionExpression == null) {
+            return currentExpression;
+        }
         if (currentExpression == null) {
-            return equalsTo;
+            return dataPermissionExpression;
         }
-        // 如果表达式为 Or,则需要 (currentExpression) AND equalsTo
+        // 如果表达式为 Or,则需要 (currentExpression) AND dataPermissionExpression
         if (currentExpression instanceof OrExpression) {
-            return new AndExpression(new Parenthesis(currentExpression), equalsTo);
+            return new AndExpression(new Parenthesis(currentExpression), dataPermissionExpression);
         }
-        // 如果表达式为 And,则直接返回 currentExpression AND equalsTo
-        return new AndExpression(currentExpression, equalsTo);
+        // ② 如果表达式为 And,则直接返回 where AND dataPermissionExpression
+        return new AndExpression(currentExpression, dataPermissionExpression);
     }
 
     /**

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
+import cn.iocoder.yudao.framework.expression.OrExpressionX;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
@@ -20,7 +21,6 @@ import net.sf.jsqlparser.expression.Alias;
 import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.LongValue;
 import net.sf.jsqlparser.expression.NullValue;
-import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
 import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
@@ -143,8 +143,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
         if (userExpression == null) {
             return deptExpression;
         }
-        // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE dept_id IN ? OR user_id = ?
-        return new OrExpression(deptExpression, userExpression);
+        // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
+        return new OrExpressionX(deptExpression, userExpression);
     }
 
     private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {

+ 0 - 3
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring.factories

@@ -1,3 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionAutoConfiguration,\
-  cn.iocoder.yudao.framework.datapermission.config.YudaoDeptDataPermissionAutoConfiguration

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,2 @@
+cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionAutoConfiguration
+cn.iocoder.yudao.framework.datapermission.config.YudaoDeptDataPermissionAutoConfiguration

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java

@@ -62,7 +62,7 @@ public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
             // 调用
             interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
             // 断言
-            pluginUtilsMock.verify(never(), () -> PluginUtils.mpBoundSql(boundSql));
+            pluginUtilsMock.verify(() -> PluginUtils.mpBoundSql(boundSql), never());
         }
     }
 
@@ -87,7 +87,7 @@ public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
             interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
             // 断言
             verify(mpBs, times(1)).sql(
-                    eq("SELECT * FROM t_user WHERE id = 1 AND dept_id = 100"));
+                    eq("SELECT * FROM t_user WHERE id = 1 AND t_user.dept_id = 100"));
             // 断言缓存
             assertTrue(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
         }

+ 205 - 42
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java

@@ -46,7 +46,7 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
 
             @Override
             public Set<String> getTableNames() {
-                return asSet("entity", "entity1", "entity2", "t1", "t2", // 支持 MyBatis Plus 的单元测试
+                return asSet("entity", "entity1", "entity2", "entity3", "t1", "t2", "sys_dict_item", // 支持 MyBatis Plus 的单元测试
                         "t_user", "t_role"); // 满足自己的单元测试
             }
 
@@ -84,30 +84,30 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
     @Test
     void delete() {
         assertSql("delete from entity where id = ?",
-                "DELETE FROM entity WHERE id = ? AND tenant_id = 1");
+                "DELETE FROM entity WHERE id = ? AND entity.tenant_id = 1");
     }
 
     @Test
     void update() {
         assertSql("update entity set name = ? where id = ?",
-                "UPDATE entity SET name = ? WHERE id = ? AND tenant_id = 1");
+                "UPDATE entity SET name = ? WHERE id = ? AND entity.tenant_id = 1");
     }
 
     @Test
     void selectSingle() {
         // 单表
         assertSql("select * from entity where id = ?",
-                "SELECT * FROM entity WHERE id = ? AND tenant_id = 1");
+                "SELECT * FROM entity WHERE id = ? AND entity.tenant_id = 1");
 
         assertSql("select * from entity where id = ? or name = ?",
-                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND tenant_id = 1");
+                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
 
         assertSql("SELECT * FROM entity WHERE (id = ? OR name = ?)",
-                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND tenant_id = 1");
+                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
 
         /* not */
         assertSql("SELECT * FROM entity WHERE not (id = ? OR name = ?)",
-                "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND tenant_id = 1");
+                "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND entity.tenant_id = 1");
     }
 
     @Test
@@ -167,10 +167,12 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
         assertSql("SELECT * FROM entity e WHERE e.id >= (select e1.id from entity1 e1 where e1.id = ?)",
                 "SELECT * FROM entity e WHERE e.id >= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
 
+
         /* <= */
         assertSql("SELECT * FROM entity e WHERE e.id <= (select e1.id from entity1 e1 where e1.id = ?)",
                 "SELECT * FROM entity e WHERE e.id <= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
 
+
         /* <> */
         assertSql("SELECT * FROM entity e WHERE e.id <> (select e1.id from entity1 e1 where e1.id = ?)",
                 "SELECT * FROM entity e WHERE e.id <> (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
@@ -204,6 +206,14 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                 "SELECT * FROM entity e " +
                         "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
                         "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "left join entity1 e1 on e1.id = e.id " +
+                        "left join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
+                        "WHERE e.tenant_id = 1");
     }
 
     @Test
@@ -212,17 +222,125 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
         assertSql("SELECT * FROM entity e " +
                         "right join entity1 e1 on e1.id = e.id",
                 "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE e.tenant_id = 1");
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "WHERE e1.tenant_id = 1");
+
+        assertSql("SELECT * FROM with_as_1 e " +
+                        "right join entity1 e1 on e1.id = e.id",
+                "SELECT * FROM with_as_1 e " +
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id " +
+                        "WHERE e1.tenant_id = 1");
 
         assertSql("SELECT * FROM entity e " +
                         "right join entity1 e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "right join entity1 e1 on e1.id = e.id " +
+                        "right join entity2 e2 on e1.id = e2.id ",
+                "SELECT * FROM entity e " +
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
+                        "WHERE e2.tenant_id = 1");
+    }
+
+    @Test
+    void selectMixJoin() {
+        assertSql("SELECT * FROM entity e " +
+                        "right join entity1 e1 on e1.id = e.id " +
+                        "left join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
+                        "WHERE e1.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "left join entity1 e1 on e1.id = e.id " +
+                        "right join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+                        "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
+                        "WHERE e2.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "left join entity1 e1 on e1.id = e.id " +
+                        "inner join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+                        "INNER JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 AND e2.tenant_id = 1");
     }
 
+
+    @Test
+    void selectJoinSubSelect() {
+        assertSql("select * from (select * from entity) e1 " +
+                        "left join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM (SELECT * FROM entity WHERE entity.tenant_id = 1) e1 " +
+                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1");
+
+        assertSql("select * from entity1 e1 " +
+                        "left join (select * from entity2) e2 " +
+                        "on e1.id = e2.id",
+                "SELECT * FROM entity1 e1 " +
+                        "LEFT JOIN (SELECT * FROM entity2 WHERE entity2.tenant_id = 1) e2 " +
+                        "ON e1.id = e2.id " +
+                        "WHERE e1.tenant_id = 1");
+    }
+
+    @Test
+    void selectSubJoin() {
+
+        assertSql("select * FROM " +
+                        "(entity1 e1 right JOIN entity2 e2 ON e1.id = e2.id)",
+                "SELECT * FROM " +
+                        "(entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
+                        "WHERE e2.tenant_id = 1");
+
+        assertSql("select * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id)",
+                "SELECT * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "WHERE e1.tenant_id = 1");
+
+
+        assertSql("select * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id) " +
+                        "right join entity3 e3 on e1.id = e3.id",
+                "SELECT * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "RIGHT JOIN entity3 e3 ON e1.id = e3.id AND e1.tenant_id = 1 " +
+                        "WHERE e3.tenant_id = 1");
+
+
+        assertSql("select * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 right join entity2 e2 ON e1.id = e2.id) " +
+                        "on e.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
+                        "ON e.id = e2.id AND e2.tenant_id = 1 " +
+                        "WHERE e.tenant_id = 1");
+
+        assertSql("select * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
+                        "on e.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "ON e.id = e2.id AND e1.tenant_id = 1 " +
+                        "WHERE e.tenant_id = 1");
+
+        assertSql("select * FROM entity e " +
+                        "RIGHT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
+                        "on e.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "RIGHT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "ON e.id = e2.id AND e.tenant_id = 1 " +
+                        "WHERE e1.tenant_id = 1");
+    }
+
+
     @Test
     void selectLeftJoinMultipleTrailingOn() {
         // 多个 on 尾缀的
@@ -256,51 +374,97 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                         "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM entity e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
+                        "WHERE e.id = ? OR e.name = ?");
 
         assertSql("SELECT * FROM entity e " +
                         "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE (e.id = ? OR e.name = ?)",
                 "SELECT * FROM entity e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
+                        "WHERE (e.id = ? OR e.name = ?)");
+
+        // 隐式内连接
+        assertSql("SELECT * FROM entity,entity1 " +
+                        "WHERE entity.id = entity1.id",
+                "SELECT * FROM entity, entity1 " +
+                        "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
+
+        // 隐式内连接
+        assertSql("SELECT * FROM entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id",
+                "SELECT * FROM entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id AND a.tenant_id = 1");
+
+        assertSql("SELECT * FROM with_as_entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id",
+                "SELECT * FROM with_as_entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id");
+
+        // SubJoin with 隐式内连接
+        assertSql("SELECT * FROM (entity,entity1) " +
+                        "WHERE entity.id = entity1.id",
+                "SELECT * FROM (entity, entity1) " +
+                        "WHERE entity.id = entity1.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
+
+        assertSql("SELECT * FROM ((entity,entity1),entity2) " +
+                        "WHERE entity.id = entity1.id and entity.id = entity2.id",
+                "SELECT * FROM ((entity, entity1), entity2) " +
+                        "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
+
+        assertSql("SELECT * FROM (entity,(entity1,entity2)) " +
+                        "WHERE entity.id = entity1.id and entity.id = entity2.id",
+                "SELECT * FROM (entity, (entity1, entity2)) " +
+                        "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
+
+        // 沙雕的括号写法
+        assertSql("SELECT * FROM (((entity,entity1))) " +
+                        "WHERE entity.id = entity1.id",
+                "SELECT * FROM (((entity, entity1))) " +
+                        "WHERE entity.id = entity1.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
 
-        // 垃圾 inner join todo
-//        assertSql("SELECT * FROM entity,entity1 " +
-//                "WHERE entity.id = entity1.id",
-//            "SELECT * FROM entity e " +
-//                "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-//                "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
     }
 
+
     @Test
     void selectWithAs() {
         assertSql("with with_as_A as (select * from entity) select * from with_as_A",
-                "WITH with_as_A AS (SELECT * FROM entity WHERE tenant_id = 1) SELECT * FROM with_as_A");
+                "WITH with_as_A AS (SELECT * FROM entity WHERE entity.tenant_id = 1) SELECT * FROM with_as_A");
+    }
+
+
+    @Test
+    void selectIgnoreTable() {
+        assertSql(" SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)",
+                "SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)");
     }
 
     private void assertSql(String sql, String targetSql) {
         assertEquals(targetSql, interceptor.parserSingle(sql, null));
     }
 
+
     // ========== 额外的测试 ==========
 
     @Test
     public void testSelectSingle() {
         // 单表
         assertSql("select * from t_user where id = ?",
-                "SELECT * FROM t_user WHERE id = ? AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE id = ? AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
 
         assertSql("select * from t_user where id = ? or name = ?",
-                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
 
         assertSql("SELECT * FROM t_user WHERE (id = ? OR name = ?)",
-                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
 
         /* not */
         assertSql("SELECT * FROM t_user WHERE not (id = ? OR name = ?)",
-                "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
     }
 
     @Test
@@ -329,16 +493,16 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                         "right join t_role e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM t_user e " +
-                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
+                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
 
         // 条件 e.id = ? OR e.name = ? 带括号
         assertSql("SELECT * FROM t_user e " +
                         "right join t_role e1 on e1.id = e.id " +
                         "WHERE (e.id = ? OR e.name = ?)",
                 "SELECT * FROM t_user e " +
-                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
+                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
     }
 
     @Test
@@ -348,23 +512,22 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                         "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM t_user e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
+                        "WHERE e.id = ? OR e.name = ?");
 
         // 条件 e.id = ? OR e.name = ? 带括号
         assertSql("SELECT * FROM t_user e " +
-                        "inner join t_role e1 on e1.id = e.id " +
+                        "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE (e.id = ? OR e.name = ?)",
                 "SELECT * FROM t_user e " +
-                        "INNER JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
-
-        // 垃圾 inner join todo
-//        assertSql("SELECT * FROM entity,entity1 " +
-//                "WHERE entity.id = entity1.id",
-//            "SELECT * FROM entity e " +
-//                "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-//                "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
+                        "WHERE (e.id = ? OR e.name = ?)");
+
+        // 没有 On 的 inner join
+        assertSql("SELECT * FROM entity,entity1 " +
+                "WHERE entity.id = entity1.id",
+            "SELECT * FROM entity, entity1 " +
+                    "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
     }
 
 }

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java

@@ -227,7 +227,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             // 调用
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
-            assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString());
+            assertEquals("(u.dept_id IN (10, 20) OR u.id = 1)", expression.toString());
             assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
     }

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels