Sfoglia il codice sorgente

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

 Conflicts:
	yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
	yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/UserController.java
	yudao-ui-admin/src/components/ImageUpload/index.vue
YunaiV 3 anni fa
parent
commit
bb26bc4012
100 ha cambiato i file con 3651 aggiunte e 1559 eliminazioni
  1. 25 0
      .gitee/ISSUE_TEMPLATE.zh-CN.md
  2. 34 0
      .github/ISSUE_TEMPLATE/question.md
  3. 1 1
      README.md
  4. 237 59
      sql/mysql/ruoyi-vue-pro.sql
  5. 630 296
      sql/oracle/ruoyi-vue-pro.sql
  6. 56 36
      sql/postgresql/ruoyi-vue-pro.sql
  7. 889 60
      sql/sqlserver/ruoyi-vue-pro.sql
  8. 0 12
      yudao-dependencies/pom.xml
  9. 14 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  10. 98 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
  11. 6 3
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
  12. 17 14
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java
  13. 7 0
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/pom.xml
  14. 6 6
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDeptDataPermissionAutoConfiguration.java
  15. 0 22
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java
  16. 27 11
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
  17. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleCustomizer.java
  18. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/package-info.java
  19. 32 18
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java
  20. 4 4
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java
  21. 10 5
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java
  22. 8 0
      yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
  23. 70 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java
  24. 1 1
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringListTypeHandler.java
  25. 3 0
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/config/YudaoLock4jConfiguration.java
  26. 4 11
      yudao-framework/yudao-spring-boot-starter-security/pom.xml
  27. 0 13
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
  28. 10 25
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
  29. 3 29
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
  30. 6 74
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
  31. 0 149
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java
  32. 0 43
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java
  33. 39 10
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
  34. 0 40
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java
  35. 0 45
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java
  36. 17 2
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkService.java
  37. 57 0
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkServiceImpl.java
  38. 5 14
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
  39. 9 6
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
  40. 8 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
  41. 44 2
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
  42. 5 18
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
  43. 4 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/db/DataSourceConfigDO.java
  44. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java
  45. 1 12
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImpl.java
  46. 9 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
  47. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do.vm
  48. 1 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/sql/sql.vm
  49. 2 2
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java
  50. 31 7
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java
  51. 3 3
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/create_tables.sql
  52. 0 1
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  53. 8 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
  54. 34 13
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
  55. 14 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
  56. 3 12
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
  57. 6 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java
  58. 28 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java
  59. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java
  60. 23 18
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
  61. 73 138
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
  62. 9 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java
  63. 16 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  64. 4 5
      yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java
  65. 0 7
      yudao-module-system/yudao-module-system-api/pom.xml
  66. 49 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java
  67. 0 56
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java
  68. 33 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java
  69. 40 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java
  70. 39 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java
  71. 28 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApi.java
  72. 1 1
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/permission/dto/DeptDataPermissionRespDTO.java
  73. 20 2
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  74. 12 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientConstants.java
  75. 29 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2GrantTypeEnum.java
  76. 0 2
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java
  77. 48 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java
  78. 0 47
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java
  79. 16 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java
  80. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
  81. 43 24
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  82. 0 79
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java
  83. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java
  84. 31 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java
  85. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthMenuRespVO.java
  86. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java
  87. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSmsLoginReqVO.java
  88. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSmsSendReqVO.java
  89. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSocialBindLoginReqVO.java
  90. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSocialQuickLoginReqVO.java
  91. 0 20
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java
  92. 0 38
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java
  93. 0 20
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java
  94. 23 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2ClientController.http
  95. 74 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2ClientController.java
  96. 54 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http
  97. 298 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java
  98. 50 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2TokenController.java
  99. 14 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2UserController.http
  100. 80 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2UserController.java

+ 25 - 0
.gitee/ISSUE_TEMPLATE.zh-CN.md

@@ -0,0 +1,25 @@
+碰到问题,请在 <https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues> 搜索是否存在相似的 issue。
+
+不按照模板提交的 issue,会被系统自动删除。
+
+### 基本信息
+
+- ruoyi-vue-pro 版本:
+- 操作系统:
+- 数据库:
+
+### 你猜测可能的原因
+
+(必填)我花费了 2-4 小时自查,发现可能的原因是:xxxxxx
+
+### 复现步骤
+
+第一步,
+
+第二步,
+
+第三步,
+
+### 报错信息
+
+带上必要的截图

+ 34 - 0
.github/ISSUE_TEMPLATE/question.md

@@ -0,0 +1,34 @@
+---
+name: 问题反馈
+about: 请详细描述,以便更高快的获得到解决
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+碰到问题,请在 <https://github.com/YunaiV/ruoyi-vue-pro/issues> 搜索是否存在相似的 issue。
+
+不按照模板提交的 issue,会被系统自动删除。
+
+### 基本信息
+
+- ruoyi-vue-pro 版本:
+- 操作系统:
+- 数据库:
+
+### 你猜测可能的原因
+
+(必填)我花费了 2-4 小时自查,发现可能的原因是:xxxxxx
+
+### 复现步骤
+
+第一步,
+
+第二步,
+
+第三步,
+
+### 报错信息
+
+带上必要的截图

+ 1 - 1
README.md

@@ -31,7 +31,7 @@
 | 项目名                | 说明                     | 传说门                                                                                                                                 |
 |--------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
 | `ruoyi-vue-pro`    | Spring Boot 多模块        | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/ruoyi-vue-pro)     |
-| `ruoyi-vue-cloud`  | Spring Cloud 微服务       | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-cloud)** &nbsp;&nbsp;&nbsp; [Github](https://github.com/YunaiV/onemall)         |
+| `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) |
 
 ## 🐶 在线体验

+ 237 - 59
sql/mysql/ruoyi-vue-pro.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80026
  File Encoding         : 65001
 
- Date: 02/05/2022 16:13:31
+ Date: 25/05/2022 23:28:25
 */
 
 SET NAMES utf8mb4;
@@ -73,7 +73,6 @@ CREATE TABLE `QRTZ_CRON_TRIGGERS`  (
 -- ----------------------------
 BEGIN;
 INSERT INTO `QRTZ_CRON_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `CRON_EXPRESSION`, `TIME_ZONE_ID`) VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', '* * * * * ?', 'Asia/Shanghai');
-INSERT INTO `QRTZ_CRON_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `CRON_EXPRESSION`, `TIME_ZONE_ID`) VALUES ('schedulerName', 'userSessionTimeoutJob', 'DEFAULT', '0 * * * * ? *', 'Asia/Shanghai');
 COMMIT;
 
 -- ----------------------------
@@ -134,7 +133,6 @@ CREATE TABLE `QRTZ_JOB_DETAILS`  (
 -- ----------------------------
 BEGIN;
 INSERT INTO `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `JOB_CLASS_NAME`, `IS_DURABLE`, `IS_NONCONCURRENT`, `IS_UPDATE_DATA`, `REQUESTS_RECOVERY`, `JOB_DATA`) VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000057400104A4F425F48414E444C45525F4E414D4574000C7061794E6F746966794A6F627800);
-INSERT INTO `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `JOB_CLASS_NAME`, `IS_DURABLE`, `IS_NONCONCURRENT`, `IS_UPDATE_DATA`, `REQUESTS_RECOVERY`, `JOB_DATA`) VALUES ('schedulerName', 'userSessionTimeoutJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000000000000000D7400104A4F425F48414E444C45525F4E414D457400157573657253657373696F6E54696D656F75744A6F627800);
 COMMIT;
 
 -- ----------------------------
@@ -282,7 +280,6 @@ CREATE TABLE `QRTZ_TRIGGERS`  (
 -- ----------------------------
 BEGIN;
 INSERT INTO `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `NEXT_FIRE_TIME`, `PREV_FIRE_TIME`, `PRIORITY`, `TRIGGER_STATE`, `TRIGGER_TYPE`, `START_TIME`, `END_TIME`, `CALENDAR_NAME`, `MISFIRE_INSTR`, `JOB_DATA`) VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', 'payNotifyJob', 'DEFAULT', NULL, 1635572540000, 1635572539000, 5, 'WAITING', 'CRON', 1635294882000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800);
-INSERT INTO `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `NEXT_FIRE_TIME`, `PREV_FIRE_TIME`, `PRIORITY`, `TRIGGER_STATE`, `TRIGGER_TYPE`, `START_TIME`, `END_TIME`, `CALENDAR_NAME`, `MISFIRE_INSTR`, `JOB_DATA`) VALUES ('schedulerName', 'userSessionTimeoutJob', 'DEFAULT', 'userSessionTimeoutJob', 'DEFAULT', NULL, 1643993400000, -1, 5, 'WAITING', 'CRON', 1643993386000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000007D074000F4A4F425F52455452595F434F554E547371007E0009000000037800);
 COMMIT;
 
 -- ----------------------------
@@ -303,7 +300,7 @@ CREATE TABLE `bpm_form`  (
   `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 = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义';
 
 -- ----------------------------
 -- Records of bpm_form
@@ -362,7 +359,7 @@ CREATE TABLE `bpm_process_definition_ext`  (
   `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 = 96 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 104 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
 
 -- ----------------------------
 -- Records of bpm_process_definition_ext
@@ -392,7 +389,7 @@ CREATE TABLE `bpm_process_instance_ext`  (
   `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 = 200 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
+) ENGINE = InnoDB AUTO_INCREMENT = 204 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
 
 -- ----------------------------
 -- Records of bpm_process_instance_ext
@@ -418,7 +415,7 @@ CREATE TABLE `bpm_task_assign_rule`  (
   `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 = 187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
+) ENGINE = InnoDB AUTO_INCREMENT = 201 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
 
 -- ----------------------------
 -- Records of bpm_task_assign_rule
@@ -447,7 +444,7 @@ CREATE TABLE `bpm_task_ext`  (
   `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 = 213 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
+) ENGINE = InnoDB AUTO_INCREMENT = 217 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
 
 -- ----------------------------
 -- Records of bpm_task_ext
@@ -507,7 +504,7 @@ CREATE TABLE `infra_api_access_log`  (
   `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 = 26800 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 33232 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
 
 -- ----------------------------
 -- Records of infra_api_access_log
@@ -549,7 +546,7 @@ CREATE TABLE `infra_api_error_log`  (
   `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 = 409 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 454 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
 
 -- ----------------------------
 -- Records of infra_api_error_log
@@ -587,7 +584,7 @@ CREATE TABLE `infra_codegen_column`  (
   `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 = 1094 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 1114 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
 
 -- ----------------------------
 -- Records of infra_codegen_column
@@ -619,7 +616,7 @@ CREATE TABLE `infra_codegen_table`  (
   `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 = 97 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 98 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
 
 -- ----------------------------
 -- Records of infra_codegen_table
@@ -676,7 +673,7 @@ CREATE TABLE `infra_data_source_config`  (
   `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 = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '数据源配置表';
+) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '数据源配置表';
 
 -- ----------------------------
 -- Records of infra_data_source_config
@@ -701,7 +698,7 @@ CREATE TABLE `infra_file`  (
   `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 = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 83 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file
@@ -787,7 +784,6 @@ CREATE TABLE `infra_job`  (
 -- ----------------------------
 BEGIN;
 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5, '支付通知 Job', 2, 'payNotifyJob', NULL, '* * * * * ?', 0, 0, 0, '1', '2021-10-27 08:34:42', '1', '2022-04-03 20:35:25', b'0');
-INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (15, '用户 Session 超时 Job', 1, 'userSessionTimeoutJob', NULL, '0 * * * * ?', 0, 0, 60000, '1', '2022-04-03 22:18:14', '1', '2022-04-03 22:18:14', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -1157,11 +1153,11 @@ CREATE TABLE `system_dept`  (
 -- ----------------------------
 BEGIN;
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, '芋道源码', 0, 0, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '103', '2022-01-14 01:04:05', b'0', 1);
-INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (101, '深圳总公司', 100, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-02-22 19:47:48', b'0', 1);
+INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (101, '深圳总公司', 100, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-05-16 20:25:23', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (102, '长沙分公司', 100, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:40', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, '研发部门', 101, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '103', '2022-01-14 01:04:14', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, '市场部门', 101, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:38', b'0', 1);
-INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (105, '测试部门', 101, 3, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:37', b'0', 1);
+INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (105, '测试部门', 101, 3, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-05-16 20:25:15', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (106, '财务部门', 101, 4, 103, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '103', '2022-01-15 21:32:22', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, '运维部门', 101, 5, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:33', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, '市场部门', 102, 1, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-02-16 08:35:45', b'0', 1);
@@ -1190,7 +1186,7 @@ CREATE TABLE `system_dict_data`  (
   `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 = 1155 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1162 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
 
 -- ----------------------------
 -- Records of system_dict_data
@@ -1264,7 +1260,6 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (81, 101, '社交登录', '101', 'system_login_type', 0, 'info', '', '社交登录', '1', '2021-10-06 00:52:17', '1', '2022-02-16 13:11:40', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (82, 102, 'Mock 登录', '102', 'system_login_type', 0, 'danger', '', 'Mock 登录', '1', '2021-10-06 00:52:32', '1', '2022-02-16 13:11:44', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (83, 200, '主动登出', '200', 'system_login_type', 0, 'primary', '', '主动登出', '1', '2021-10-06 00:52:58', '1', '2022-02-16 13:11:49', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (84, 201, '超时登出', '201', 'system_login_type', 0, 'info', '', '超时登出', '1', '2021-10-06 00:53:17', '1', '2022-02-16 13:11:53', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (85, 202, '强制登出', '202', 'system_login_type', 0, 'danger', '', '强制退出', '1', '2021-10-06 00:53:41', '1', '2022-02-16 13:11:57', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', '2021-09-21 22:35:28', '1', '2022-02-16 10:00:41', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', '2021-09-21 22:36:11', '1', '2022-02-16 10:00:49', b'0');
@@ -1326,6 +1321,12 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1152, 11, 'FTP 服务器', '11', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:06', '1', '2022-03-15 00:26:10', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1153, 12, 'SFTP 服务器', '12', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:22', '1', '2022-03-15 00:26:22', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1154, 20, 'S3 对象存储', '20', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:31', '1', '2022-03-15 00:26:45', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1155, 103, '短信登录', '103', 'system_login_type', 0, 'default', '', NULL, '1', '2022-05-09 23:57:58', '1', '2022-05-09 23:58:09', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1156, 1, 'password', 'password', 'system_oauth2_grant_type', 0, 'default', '', '密码模式', '1', '2022-05-12 00:22:05', '1', '2022-05-11 16:26:01', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1157, 2, 'authorization_code', 'authorization_code', 'system_oauth2_grant_type', 0, 'primary', '', '授权码模式', '1', '2022-05-12 00:22:59', '1', '2022-05-11 16:26:02', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1158, 3, 'implicit', 'implicit', 'system_oauth2_grant_type', 0, 'success', '', '简化模式', '1', '2022-05-12 00:23:40', '1', '2022-05-11 16:26:05', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1159, 4, 'client_credentials', 'client_credentials', 'system_oauth2_grant_type', 0, 'default', '', '客户端模式', '1', '2022-05-12 00:23:51', '1', '2022-05-11 16:26:08', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1160, 5, 'refresh_token', 'refresh_token', 'system_oauth2_grant_type', 0, 'info', '', '刷新模式', '1', '2022-05-12 00:24:02', '1', '2022-05-11 16:26:11', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -1345,13 +1346,13 @@ CREATE TABLE `system_dict_type`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE,
   UNIQUE INDEX `dict_type`(`type` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 147 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
+) ENGINE = InnoDB AUTO_INCREMENT = 149 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
 
 -- ----------------------------
 -- Records of system_dict_type
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '用户性别', 'system_user_sex', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:30:31', b'0');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '用户性别', 'system_user_sex', 0, NULL, 'admin', '2021-01-05 17:03:48', '1', '2022-05-16 20:29:32', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (6, '参数类型', 'infra_config_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:36:54', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (7, '通知类型', 'system_notice_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:35:26', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (9, '操作类型', 'system_operate_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 09:32:21', b'0');
@@ -1359,7 +1360,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (11, 'Boolean 是否类型', 'infra_boolean_string', 0, 'boolean 转是否', '', '2021-01-19 03:20:08', '', '2022-02-01 16:37:10', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (104, '登陆结果', 'system_login_result', 0, '登陆结果', '', '2021-01-18 06:17:11', '', '2022-02-01 16:36:00', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (105, 'Redis 超时类型', 'infra_redis_timeout_type', 0, 'RedisKeyDefine.TimeoutTypeEnum', '', '2021-01-26 00:52:50', '', '2022-02-01 16:50:29', b'0');
-INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (106, '代码生成模板类型', 'infra_codegen_template_type', 0, NULL, '', '2021-02-05 07:08:06', '', '2022-03-10 16:33:42', b'0');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (106, '代码生成模板类型', 'infra_codegen_template_type', 0, NULL, '', '2021-02-05 07:08:06', '1', '2022-05-16 20:26:50', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (107, '定时任务状态', 'infra_job_status', 0, NULL, '', '2021-02-07 07:44:16', '', '2022-02-01 16:51:11', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (108, '定时任务日志状态', 'infra_job_log_status', 0, NULL, '', '2021-02-08 10:03:51', '', '2022-02-01 16:50:43', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (109, '用户类型', 'user_type', 0, NULL, '', '2021-02-26 00:15:51', '', '2021-02-26 00:15:51', b'0');
@@ -1390,6 +1391,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (144, '代码生成的场景枚举', 'infra_codegen_scene', 0, '代码生成的场景枚举', '1', '2022-02-02 13:14:45', '1', '2022-03-10 16:33:46', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (145, '角色类型', 'system_role_type', 0, '角色类型', '1', '2022-02-16 13:01:46', '1', '2022-02-16 13:01:46', b'0');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (146, '文件存储器', 'infra_file_storage', 0, '文件存储器', '1', '2022-03-15 00:24:38', '1', '2022-03-15 00:24:38', b'0');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (147, 'OAuth 2.0 授权类型', 'system_oauth2_grant_type', 0, 'OAuth 2.0 授权类型(模式)', '1', '2022-05-12 00:20:52', '1', '2022-05-11 16:25:49', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -1409,7 +1411,7 @@ CREATE TABLE `system_error_code`  (
   `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 = 5453 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5829 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
 
 -- ----------------------------
 -- Records of system_error_code
@@ -1438,7 +1440,7 @@ CREATE TABLE `system_login_log`  (
   `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 = 1247 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 1416 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
 
 -- ----------------------------
 -- Records of system_login_log
@@ -1469,7 +1471,7 @@ CREATE TABLE `system_menu`  (
   `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 = 1261 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1268 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
 
 -- ----------------------------
 -- Records of system_menu
@@ -1487,7 +1489,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (106, '配置管理', '', 2, 6, 2, 'config', 'edit', 'infra/config/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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (107, '通知公告', '', 2, 8, 1, 'notice', 'message', 'system/notice/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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (108, '审计日志', '', 1, 9, 1, 'log', 'log', '', 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (109, '在线用户', 'system:user-session:list', 2, 10, 1, 'user-session', 'online', 'system/session/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (110, '定时任务', '', 2, 12, 2, 'job', 'job', 'infra/job/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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (111, 'MySQL 监控', '', 2, 9, 2, 'druid', 'druid', 'infra/druid/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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (112, 'Java 监控', '', 2, 11, 2, 'admin-server', 'server', 'infra/server/index', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
@@ -1540,8 +1542,8 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1046, '在线查询', 'system:user-session:list', 3, 1, 109, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1048, '单条强退', 'system:user-session:delete', 3, 3, 109, '', '', '', 0, b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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');
@@ -1699,6 +1701,12 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1261, 'OAuth 2.0', '', 1, 10, 1, 'oauth2', 'people', NULL, 0, b'1', b'1', '1', '2022-05-09 23:38:17', '1', '2022-05-11 23:51:46', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) 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');
 COMMIT;
 
 -- ----------------------------
@@ -1724,11 +1732,159 @@ CREATE TABLE `system_notice`  (
 -- Records of system_notice
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '温馨提醒:2018-07-01 若依新版本发布啦', '<p>新版本内容133</p>', 2, 0, 'admin', '2021-01-05 17:03:48', '1', '2022-02-15 19:47:20', b'0', 1);
-INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', '维护内容', 1, 0, 'admin', '2021-01-05 17:03:48', '', '2021-12-15 05:02:22', b'0', 1);
+INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '芋道的公众', '<p>新版本内容133</p>', 1, 0, 'admin', '2021-01-05 17:03:48', '1', '2022-05-04 21:00:20', b'0', 1);
+INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', '<p><img src=\"http://test.yudao.iocoder.cn/b7cb3cf49b4b3258bf7309a09dd2f4e5.jpg\">维护内容</p>', 2, 1, 'admin', '2021-01-05 17:03:48', '1', '2022-05-11 12:34:24', b'0', 1);
 INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, '我是测试标题', '<p>哈哈哈哈123</p>', 1, 0, '110', '2022-02-22 01:01:25', '110', '2022-02-22 01:01:46', b'0', 121);
 COMMIT;
 
+-- ----------------------------
+-- Table structure for system_oauth2_access_token
+-- ----------------------------
+DROP TABLE IF EXISTS `system_oauth2_access_token`;
+CREATE TABLE `system_oauth2_access_token`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `user_id` bigint NOT NULL COMMENT '用户编号',
+  `user_type` tinyint NOT NULL COMMENT '用户类型',
+  `access_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '访问令牌',
+  `refresh_token` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '刷新令牌',
+  `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
+  `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '授权范围',
+  `expires_time` datetime 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 = 172 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
+
+-- ----------------------------
+-- Records of system_oauth2_access_token
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for system_oauth2_approve
+-- ----------------------------
+DROP TABLE IF EXISTS `system_oauth2_approve`;
+CREATE TABLE `system_oauth2_approve`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `user_id` bigint NOT NULL COMMENT '用户编号',
+  `user_type` tinyint NOT NULL COMMENT '用户类型',
+  `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
+  `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '授权范围',
+  `approved` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否接受',
+  `expires_time` datetime 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 = 80 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 批准表';
+
+-- ----------------------------
+-- Records of system_oauth2_approve
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for system_oauth2_client
+-- ----------------------------
+DROP TABLE IF EXISTS `system_oauth2_client`;
+CREATE TABLE `system_oauth2_client`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
+  `secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端密钥',
+  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名',
+  `logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用图标',
+  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '应用描述',
+  `status` tinyint NOT NULL COMMENT '状态',
+  `access_token_validity_seconds` int NOT NULL COMMENT '访问令牌的有效期',
+  `refresh_token_validity_seconds` int NOT NULL COMMENT '刷新令牌的有效期',
+  `redirect_uris` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '可重定向的 URI 地址',
+  `authorized_grant_types` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '授权类型',
+  `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '授权范围',
+  `auto_approve_scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '自动通过的授权范围',
+  `authorities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '权限',
+  `resource_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '资源',
+  `additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 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 '是否删除',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 客户端表';
+
+-- ----------------------------
+-- Records of system_oauth2_client
+-- ----------------------------
+BEGIN;
+INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'default', 'admin123', '芋道源码', 'http://test.yudao.iocoder.cn/a5e2e244368878a366b516805a4aabf1.png', '我是描述', 0, 999999999, 8640, '[\"https://www.iocoder.cn\",\"https://doc.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2022-05-23 13:33:11', b'0');
+INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/277a899d573723f1fcdfb57340f00379.png', NULL, 0, 1800, 43200, '[\"https://www.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\"]', '[\"user_info\",\"projects\"]', '[\"user_info\"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2022-05-14 15:11:31', b'0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for system_oauth2_code
+-- ----------------------------
+DROP TABLE IF EXISTS `system_oauth2_code`;
+CREATE TABLE `system_oauth2_code`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `user_id` bigint NOT NULL COMMENT '用户编号',
+  `user_type` tinyint NOT NULL COMMENT '用户类型',
+  `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '授权码',
+  `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
+  `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '授权范围',
+  `expires_time` datetime NOT NULL COMMENT '过期时间',
+  `redirect_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '可重定向的 URI 地址',
+  `state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' 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 = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 授权码表';
+
+-- ----------------------------
+-- Records of system_oauth2_code
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for system_oauth2_refresh_token
+-- ----------------------------
+DROP TABLE IF EXISTS `system_oauth2_refresh_token`;
+CREATE TABLE `system_oauth2_refresh_token`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `user_id` bigint NOT NULL COMMENT '用户编号',
+  `refresh_token` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '刷新令牌',
+  `user_type` tinyint NOT NULL COMMENT '用户类型',
+  `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
+  `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '授权范围',
+  `expires_time` datetime 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 = 106 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '刷新令牌';
+
+-- ----------------------------
+-- Records of system_oauth2_refresh_token
+-- ----------------------------
+BEGIN;
+COMMIT;
+
 -- ----------------------------
 -- Table structure for system_operate_log
 -- ----------------------------
@@ -1761,7 +1917,7 @@ CREATE TABLE `system_operate_log`  (
   `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 = 1943 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 2214 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
 
 -- ----------------------------
 -- Records of system_operate_log
@@ -1795,7 +1951,7 @@ CREATE TABLE `system_post`  (
 BEGIN;
 INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'ceo', '董事长', 1, 0, '', 'admin', '2021-01-06 17:03:48', '1', '2022-04-19 16:53:39', b'0', 1);
 INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 'se', '项目经理', 2, 0, '', 'admin', '2021-01-05 17:03:48', '1', '2021-12-12 10:47:47', b'0', 1);
-INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 'user', '普通员工', 4, 0, '111', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 00:59:35', b'0', 1);
+INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 'user', '普通员工', 4, 0, '111', 'admin', '2021-01-05 17:03:48', '1', '2022-05-04 22:46:35', b'0', 1);
 COMMIT;
 
 -- ----------------------------
@@ -1819,7 +1975,7 @@ CREATE TABLE `system_role`  (
   `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 = 112 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色信息表';
+) ENGINE = InnoDB AUTO_INCREMENT = 114 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色信息表';
 
 -- ----------------------------
 -- Records of system_role
@@ -1831,6 +1987,7 @@ INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_sco
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121);
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (110, '测试角色', 'test', 0, 1, '[]', 0, 2, '嘿嘿', '110', '2022-02-23 00:14:34', '110', '2022-02-23 13:14:58', b'0', 121);
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
+INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
 COMMIT;
 
 -- ----------------------------
@@ -1848,7 +2005,7 @@ CREATE TABLE `system_role_menu`  (
   `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 = 1695 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1729 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表';
 
 -- ----------------------------
 -- Records of system_role_menu
@@ -2045,6 +2202,23 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1692, 101, 114, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1693, 101, 115, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1694, 101, 116, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1712, 113, 1024, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1713, 113, 1025, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1714, 113, 1, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1715, 113, 102, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1716, 113, 103, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1717, 113, 104, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1718, 113, 1013, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1719, 113, 1014, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1720, 113, 1015, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1721, 113, 1016, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1722, 113, 1017, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1723, 113, 1018, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1724, 113, 1019, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1725, 113, 1020, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1726, 113, 1021, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1727, 113, 1022, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1728, 113, 1023, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
 COMMIT;
 
 -- ----------------------------
@@ -2101,7 +2275,7 @@ BEGIN;
 INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '芋道', 'YUN_PIAN', 0, '呵呵呵哒', '1555a14277cb8a608cf45a9e6a80d510', NULL, 'http://vdwapu.natappfree.cc/admin-api/system/sms/callback/yunpian', '', '2021-03-31 06:12:20', '1', '2022-02-23 16:48:44', b'0');
 INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'Ballcat', 'ALIYUN', 0, '啦啦啦', 'LTAI5tCnKso2uG3kJ5gRav88', 'fGJ5SNXL7P1NHNRmJ7DJaMJGPyE55C', NULL, '', '2021-03-31 11:53:10', '1', '2021-04-14 00:08:37', b'0');
 INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '测试渠道', 'DEBUG_DING_TALK', 0, '123', '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2021-04-13 00:23:14', '1', '2022-03-27 20:29:49', b'0');
-INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (6, '测试演示', 'DEBUG_DING_TALK', 0, NULL, '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2022-04-10 23:07:59', '1', '2022-04-10 23:07:59', b'0');
+INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (6, '测试演示', 'DEBUG_DING_TALK', 0, NULL, '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2022-04-10 23:07:59', '1', '2022-05-16 20:34:49', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -2126,7 +2300,7 @@ CREATE TABLE `system_sms_code`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
-) ENGINE = InnoDB AUTO_INCREMENT = 467 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
+) ENGINE = InnoDB AUTO_INCREMENT = 468 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
 
 -- ----------------------------
 -- Records of system_sms_code
@@ -2169,7 +2343,7 @@ CREATE TABLE `system_sms_log`  (
   `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 = 138 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 144 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
 
 -- ----------------------------
 -- Records of system_sms_log
@@ -2199,7 +2373,7 @@ CREATE TABLE `system_sms_template`  (
   `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 = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
+) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
 
 -- ----------------------------
 -- Records of system_sms_template
@@ -2239,7 +2413,7 @@ CREATE TABLE `system_social_user`  (
   `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 = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
+) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
 
 -- ----------------------------
 -- Records of system_social_user
@@ -2264,7 +2438,7 @@ CREATE TABLE `system_social_user_bind`  (
   `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 = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
+) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
 
 -- ----------------------------
 -- Records of system_social_user_bind
@@ -2293,14 +2467,14 @@ CREATE TABLE `system_tenant`  (
   `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 = 123 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户表';
+) ENGINE = InnoDB AUTO_INCREMENT = 125 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户表';
 
 -- ----------------------------
 -- Records of system_tenant
 -- ----------------------------
 BEGIN;
 INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'https://www.iocoder.cn', 0, '2099-02-19 17:14:16', 9999, '1', '2021-01-05 17:03:47', '1', '2022-02-23 12:15:11', b'0');
-INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'http://www.iocoder.cn', 111, '2024-03-11 00:00:00', 20, '1', '2022-02-22 00:56:14', '1', '2022-03-19 18:37:20', b'0');
+INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'http://www.iocoder.cn', 111, '2024-03-11 00:00:00', 20, '1', '2022-02-22 00:56:14', '1', '2022-05-17 10:03:59', b'0');
 INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'https://www.iocoder.cn', 111, '2022-04-30 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0');
 COMMIT;
 
@@ -2344,7 +2518,7 @@ CREATE TABLE `system_user_post`  (
   `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 = 115 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户岗位表';
+) ENGINE = InnoDB AUTO_INCREMENT = 116 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户岗位表';
 
 -- ----------------------------
 -- Records of system_user_post
@@ -2353,6 +2527,7 @@ BEGIN;
 INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (112, 1, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1);
 INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 100, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1);
 INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 114, 3, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1);
+INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 104, 1, '1', '2022-05-16 19:36:28', '1', '2022-05-16 19:36:28', b'0', 1);
 COMMIT;
 
 -- ----------------------------
@@ -2364,24 +2539,24 @@ CREATE TABLE `system_user_role`  (
   `user_id` bigint NOT NULL COMMENT '用户ID',
   `role_id` bigint NOT NULL COMMENT '角色ID',
   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
+  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
-  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
+  `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
+) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
 
 -- ----------------------------
 -- Records of system_user_role
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 1, '', NULL, '', NULL, b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, 2, '', NULL, '', NULL, b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 100, 101, '', NULL, '', NULL, b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 100, 1, '', NULL, '', NULL, b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 100, 2, '', NULL, '', NULL, b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 104, 101, '', NULL, '', NULL, b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:17', b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:13', b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 100, 101, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:13', b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 100, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:12', b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 100, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:11', b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 104, 101, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:11', b'0', 1);
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 103, 1, '1', '2022-01-11 13:19:45', '1', '2022-01-11 13:19:45', b'0', 1);
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 107, 106, '1', '2022-02-20 22:59:33', '1', '2022-02-20 22:59:33', b'0', 118);
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 108, 107, '1', '2022-02-20 23:00:50', '1', '2022-02-20 23:00:50', b'0', 119);
@@ -2390,6 +2565,8 @@ INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_t
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (15, 111, 110, '110', '2022-02-23 13:14:38', '110', '2022-02-23 13:14:38', b'0', 121);
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (16, 113, 111, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (17, 114, 101, '1', '2022-03-19 21:51:13', '1', '2022-03-19 21:51:13', b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', b'0', 1);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (19, 116, 113, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
 COMMIT;
 
 -- ----------------------------
@@ -2412,7 +2589,7 @@ CREATE TABLE `system_user_session`  (
   `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 = '用户在线 Session';
+) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户在线 Session';
 
 -- ----------------------------
 -- Records of system_user_session
@@ -2447,16 +2624,16 @@ CREATE TABLE `system_users`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   UNIQUE INDEX `idx_username`(`username` ASC, `update_time` ASC, `tenant_id` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 116 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表';
+) ENGINE = InnoDB AUTO_INCREMENT = 117 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表';
 
 -- ----------------------------
 -- Records of system_users
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/48934f2f-92d4-4250-b917-d10d2b262c6a', 0, '127.0.0.1', '2022-05-02 12:32:50', 'admin', '2021-01-05 17:03:47', NULL, '2022-05-02 12:32:50', b'0', 1);
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '', NULL, '', '2021-01-07 09:07:17', '104', '2021-12-16 09:26:10', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/48934f2f-92d4-4250-b917-d10d2b262c6a', 0, '127.0.0.1', '2022-05-23 20:27:29', 'admin', '2021-01-05 17:03:47', NULL, '2022-05-23 20:27:29', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-05-22 19:35:33', '', '2021-01-07 09:07:17', NULL, '2022-05-22 19:35:33', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$wWoPT7sqriM2O1YXRL.je.GiL538OR6ZTN8aQZr9JAGdnpCH2tpYe', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '127.0.0.1', '2022-01-18 00:33:40', '', '2021-01-13 23:50:35', NULL, '2022-01-18 00:33:40', b'0', 1);
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$e5RpuDCC0GYSt0Hvd2.CjujIXwgGct4SnXi6dVGxdgFsnqgEryk5a', '测试号', NULL, 107, '[]', '111@qq.com', '15601691200', 1, '', 0, '127.0.0.1', '2022-03-19 21:46:19', '', '2021-01-21 02:13:53', NULL, '2022-03-19 21:46:19', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$e5RpuDCC0GYSt0Hvd2.CjujIXwgGct4SnXi6dVGxdgFsnqgEryk5a', '测试号', NULL, 107, '[1]', '111@qq.com', '15601691200', 1, '', 0, '127.0.0.1', '2022-03-19 21:46:19', '', '2021-01-21 02:13:53', '1', '2022-05-16 19:36:28', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2022-02-27 08:26:51', b'0', 118);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120);
@@ -2465,7 +2642,8 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`,
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (112, 'newobject', '$2a$10$jh5MsR.ud/gKe3mVeUp5t.nEXGDSmHyv5OYjWQwHO8wlGmMSI9Twy', '新对象', NULL, NULL, '[]', '', '', 0, '', 0, '', NULL, '1', '2022-02-23 19:08:03', '1', '2022-02-23 19:08:03', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', NULL, '2022-03-19 18:38:51', b'0', 122);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[3]', '', '', 0, '', 0, '127.0.0.1', '2022-03-19 22:15:43', '1', '2022-03-19 21:50:58', NULL, '2022-03-19 22:15:43', b'0', 1);
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 'aotemane', '$2a$10$/WCwGHu1eq0wOVDd/u8HweJ0gJCHyLS6T7ndCqI8UXZAQom1etk2e', '1', '11', 100, '[]', '', '', 0, '', 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2022-04-30 02:55:43', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 'aotemane', '$2a$10$/WCwGHu1eq0wOVDd/u8HweJ0gJCHyLS6T7ndCqI8UXZAQom1etk2e', '1', '11', 101, '[]', '', '', 1, '', 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2022-05-22 20:18:45', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (116, '15601691302', '$2a$10$L5C4S0U6adBWMvFv1Wwl4.DI/NwYS3WIfLj5Q.Naqr5II8CmqsDZ6', '小豆', NULL, NULL, NULL, '', '', 0, '', 0, '', NULL, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
 COMMIT;
 
 SET FOREIGN_KEY_CHECKS = 1;

File diff suppressed because it is too large
+ 630 - 296
sql/oracle/ruoyi-vue-pro.sql


File diff suppressed because it is too large
+ 56 - 36
sql/postgresql/ruoyi-vue-pro.sql


File diff suppressed because it is too large
+ 889 - 60
sql/sqlserver/ruoyi-vue-pro.sql


+ 0 - 12
yudao-dependencies/pom.xml

@@ -22,7 +22,6 @@
         <swagger-annotations.version>1.5.22</swagger-annotations.version>
         <servlet.versoin>2.5</servlet.versoin>
         <!-- DB 相关 -->
-        <mysql.version>5.1.46</mysql.version>
         <druid.version>1.2.8</druid.version>
         <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
@@ -76,12 +75,6 @@
                 <version>${spring.boot.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
-                <exclusions>
-                    <exclusion>
-                        <groupId>mysql</groupId>
-                        <artifactId>mysql-connector-java</artifactId>
-                    </exclusion>
-                </exclusions>
             </dependency>
 
             <!-- 业务组件 -->
@@ -179,11 +172,6 @@
                 <version>${revision}</version>
             </dependency>
 
-            <dependency>
-                <groupId>mysql</groupId>
-                <artifactId>mysql-connector-java</artifactId>
-                <version>${mysql.version}</version>
-            </dependency>
             <dependency>
                 <groupId>com.alibaba</groupId>
                 <artifactId>druid-spring-boot-starter</artifactId>

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

@@ -54,6 +54,13 @@ public class CollectionUtils {
         return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
     }
 
+    public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
+        if (CollUtil.isEmpty(from)) {
+            return new ArrayList<>();
+        }
+        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
+    }
+
     public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
             return new HashSet<>();
@@ -61,6 +68,13 @@ public class CollectionUtils {
         return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
     }
 
+    public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
+        if (CollUtil.isEmpty(from)) {
+            return new HashSet<>();
+        }
+        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
+    }
+
     public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
         if (CollUtil.isEmpty(from)) {
             return new HashMap<>();

+ 98 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java

@@ -1,10 +1,18 @@
 package cn.iocoder.yudao.framework.common.util.http;
 
+import cn.hutool.core.codec.Base64;
 import cn.hutool.core.map.TableMap;
 import cn.hutool.core.net.url.UrlBuilder;
 import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.util.StringUtils;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
 
+import javax.servlet.http.HttpServletRequest;
+import java.net.URI;
 import java.nio.charset.Charset;
+import java.util.Map;
 
 /**
  * HTTP 工具类
@@ -25,4 +33,94 @@ public class HttpUtils {
         return builder.build();
     }
 
+    private String append(String base, Map<String, ?> query, boolean fragment) {
+        return append(base, query, null, fragment);
+    }
+
+    /**
+     * 拼接 URL
+     *
+     * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法
+     *
+     * @param base 基础 URL
+     * @param query 查询参数
+     * @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射
+     * @param fragment URL 的 fragment,即拼接到 # 中
+     * @return 拼接后的 URL
+     */
+    public static String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
+        UriComponentsBuilder template = UriComponentsBuilder.newInstance();
+        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
+        URI redirectUri;
+        try {
+            // assume it's encoded to start with (if it came in over the wire)
+            redirectUri = builder.build(true).toUri();
+        } catch (Exception e) {
+            // ... but allow client registrations to contain hard-coded non-encoded values
+            redirectUri = builder.build().toUri();
+            builder = UriComponentsBuilder.fromUri(redirectUri);
+        }
+        template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
+                .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
+
+        if (fragment) {
+            StringBuilder values = new StringBuilder();
+            if (redirectUri.getFragment() != null) {
+                String append = redirectUri.getFragment();
+                values.append(append);
+            }
+            for (String key : query.keySet()) {
+                if (values.length() > 0) {
+                    values.append("&");
+                }
+                String name = key;
+                if (keys != null && keys.containsKey(key)) {
+                    name = keys.get(key);
+                }
+                values.append(name).append("={").append(key).append("}");
+            }
+            if (values.length() > 0) {
+                template.fragment(values.toString());
+            }
+            UriComponents encoded = template.build().expand(query).encode();
+            builder.fragment(encoded.getFragment());
+        } else {
+            for (String key : query.keySet()) {
+                String name = key;
+                if (keys != null && keys.containsKey(key)) {
+                    name = keys.get(key);
+                }
+                template.queryParam(name, "{" + key + "}");
+            }
+            template.fragment(redirectUri.getFragment());
+            UriComponents encoded = template.build().expand(query).encode();
+            builder.query(encoded.getQuery());
+        }
+        return builder.build().toUriString();
+    }
+
+    public static String[] obtainBasicAuthorization(HttpServletRequest request) {
+        String clientId;
+        String clientSecret;
+        // 先从 Header 中获取
+        String authorization = request.getHeader("Authorization");
+        authorization = StrUtil.subAfter(authorization, "Basic ", true);
+        if (StringUtils.hasText(authorization)) {
+            authorization = Base64.decodeStr(authorization);
+            clientId = StrUtil.subBefore(authorization, ":", false);
+            clientSecret = StrUtil.subAfter(authorization, ":", false);
+        // 再从 Param 中获取
+        } else {
+            clientId = request.getParameter("client_id");
+            clientSecret = request.getParameter("client_secret");
+        }
+
+        // 如果两者非空,则返回
+        if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) {
+            return new String[]{clientId, clientSecret};
+        }
+        return null;
+    }
+
+
 }

+ 6 - 3
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java

@@ -113,8 +113,7 @@ public class JsonUtils {
         }
     }
 
-    // TODO @Li:和上面的风格保持一致哈。parseTree
-    public static JsonNode readTree(String text) {
+    public static JsonNode parseTree(String text) {
         try {
             return objectMapper.readTree(text);
         } catch (IOException e) {
@@ -123,7 +122,7 @@ public class JsonUtils {
         }
     }
 
-    public static JsonNode readTree(byte[] text) {
+    public static JsonNode parseTree(byte[] text) {
         try {
             return objectMapper.readTree(text);
         } catch (IOException e) {
@@ -132,4 +131,8 @@ public class JsonUtils {
         }
     }
 
+    public static boolean isJson(String text) {
+        return JSONUtil.isJson(text);
+    }
+
 }

+ 17 - 14
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.framework.common.util.string;
 
-import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 
-import java.util.Map;
+import java.util.Collection;
 
 /**
  * 字符串工具类
@@ -17,21 +17,24 @@ public class StrUtils {
     }
 
     /**
-     * 指定字符串的
-     * @param str
-     * @param replaceMap
-     * @return
+     * 给定字符串是否以任何一个字符串开始
+     * 给定字符串和数组为空都返回 false
+     *
+     * @param str      给定字符串
+     * @param prefixes 需要检测的开始字符串
+     * @since 3.0.6
      */
-    public static String replace(String str, Map<String, String> replaceMap) {
-        assert StrUtil.isNotBlank(str);
-        if (ObjectUtil.isEmpty(replaceMap)) {
-            return str;
+    public static boolean startWithAny(String str, Collection<String> prefixes) {
+        if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
+            return false;
         }
-        String result = null;
-        for (String key : replaceMap.keySet()) {
-            result = str.replace(key, replaceMap.get(key));
+
+        for (CharSequence suffix : prefixes) {
+            if (StrUtil.startWith(str, suffix, false)) {
+                return true;
+            }
         }
-        return result;
+        return false;
     }
 
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-biz-data-permission/pom.xml

@@ -34,6 +34,13 @@
             <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
         </dependency>
 
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行数据权限的获取 -->
+            <version>${revision}</version>
+        </dependency>
+
         <!-- Test 测试相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

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

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.framework.datapermission.config;
 
-import cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRule;
-import cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRuleCustomizer;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
+import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
+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.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.context.annotation.Bean;
@@ -18,14 +18,14 @@ import java.util.List;
  */
 @Configuration
 @ConditionalOnClass(LoginUser.class)
-@ConditionalOnBean(value = {DeptDataPermissionFrameworkService.class, DeptDataPermissionRuleCustomizer.class})
+@ConditionalOnBean(value = {PermissionApi.class, DeptDataPermissionRuleCustomizer.class})
 public class YudaoDeptDataPermissionAutoConfiguration {
 
     @Bean
-    public DeptDataPermissionRule deptDataPermissionRule(DeptDataPermissionFrameworkService service,
+    public DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi,
                                                          List<DeptDataPermissionRuleCustomizer> customizers) {
         // 创建 DeptDataPermissionRule 对象
-        DeptDataPermissionRule rule = new DeptDataPermissionRule(service);
+        DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);
         // 补全表配置
         customizers.forEach(customizer -> customizer.customize(rule));
         return rule;

+ 0 - 22
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java

@@ -1,22 +0,0 @@
-package cn.iocoder.yudao.framework.datapermission.core.dept.service;
-
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-
-/**
- * 基于部门的数据权限 Framework Service 接口
- * 目前的实现类是 SysPermissionServiceImpl 类
- *
- * @author 芋道源码
- */
-public interface DeptDataPermissionFrameworkService {
-
-    /**
-     * 获得登陆用户的部门数据权限
-     *
-     * @param loginUser 登陆用户
-     * @return 部门数据权限
-     */
-    DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser);
-
-}

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

@@ -1,9 +1,9 @@
-package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
+package cn.iocoder.yudao.framework.datapermission.core.rule.dept;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
+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;
@@ -11,9 +11,10 @@ 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;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import lombok.AllArgsConstructor;
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import net.sf.jsqlparser.expression.Alias;
 import net.sf.jsqlparser.expression.Expression;
@@ -50,12 +51,17 @@ import java.util.Set;
 @Slf4j
 public class DeptDataPermissionRule implements DataPermissionRule {
 
+    /**
+     * LoginUser 的 Context 缓存 Key
+     */
+    protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();
+
     private static final String DEPT_COLUMN_NAME = "dept_id";
     private static final String USER_COLUMN_NAME = "user_id";
 
     static final Expression EXPRESSION_NULL = new NullValue();
 
-    private final DeptDataPermissionFrameworkService deptDataPermissionService;
+    private final PermissionApi permissionApi;
 
     /**
      * 基于部门的表字段配置
@@ -90,13 +96,23 @@ public class DeptDataPermissionRule implements DataPermissionRule {
         if (loginUser == null) {
             return null;
         }
+        // 只有管理员类型的用户,才进行数据权限的处理
+        if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
+            return null;
+        }
 
         // 获得数据权限
-        DeptDataPermissionRespDTO deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser);
+        DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
+        // 从上下文中拿不到,则调用逻辑进行获取
         if (deptDataPermission == null) {
-            log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
-            throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
-                    loginUser.getId(), tableName, tableAlias.getName()));
+            deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId());
+            if (deptDataPermission == null) {
+                log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
+                throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
+                        loginUser.getId(), tableName, tableAlias.getName()));
+            }
+            // 添加到上下文中,避免重复计算
+            loginUser.setContext(CONTEXT_KEY, deptDataPermission);
         }
 
         // 情况一,如果是 ALL 可查看全部,则无需拼接条件
@@ -111,8 +127,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
         }
 
         // 情况三,拼接 Dept 和 User 的条件,最后组合
-        Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
-        Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
+        Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
+        Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
         if (deptExpression == null && userExpression == null) {
             // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
             log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",

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

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
+package cn.iocoder.yudao.framework.datapermission.core.rule.dept;
 
 /**
  * {@link DeptDataPermissionRule} 的自定义配置接口

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/package-info.java → yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/package-info.java

@@ -3,4 +3,4 @@
  *
  * @author 芋道源码
  */
-package cn.iocoder.yudao.framework.datapermission.core.dept;
+package cn.iocoder.yudao.framework.datapermission.core.rule.dept;

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

@@ -1,10 +1,11 @@
-package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
+package cn.iocoder.yudao.framework.datapermission.core.rule.dept;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
@@ -18,7 +19,7 @@ import org.mockito.MockedStatic;
 
 import java.util.Map;
 
-import static cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRule.EXPRESSION_NULL;
+import static cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule.EXPRESSION_NULL;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
 import static org.junit.jupiter.api.Assertions.*;
@@ -37,7 +38,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
     private DeptDataPermissionRule rule;
 
     @Mock
-    private DeptDataPermissionFrameworkService deptDataPermissionFrameworkService;
+    private PermissionApi permissionApi;
 
     @BeforeEach
     @SuppressWarnings("unchecked")
@@ -69,7 +70,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             // mock 方法
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
 
             // 调用
@@ -88,16 +90,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
 
             // 调用
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             assertNull(expression);
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
     }
 
@@ -109,16 +113,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO();
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
 
             // 调用
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             assertEquals("null = null", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
     }
 
@@ -130,17 +136,19 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
 
             // 调用
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             assertSame(EXPRESSION_NULL, expression);
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
     }
 
@@ -152,12 +160,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setSelf(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
             // 添加 user 字段配置
             rule.addUserColumn("t_user", "id");
 
@@ -165,6 +174,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             assertEquals("u.id = 1", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
     }
 
@@ -176,12 +186,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L));
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
             // 添加 dept 字段配置
             rule.addDeptColumn("t_user", "dept_id");
 
@@ -189,6 +200,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             assertEquals("u.dept_id IN (10, 20)", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
     }
 
@@ -200,12 +212,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
             // 添加 user 字段配置
             rule.addUserColumn("t_user", "id");
             // 添加 dept 字段配置
@@ -215,6 +228,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
     }
 

+ 4 - 4
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.tenant.core.web;
 
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 import javax.servlet.FilterChain;
@@ -24,9 +24,9 @@ public class TenantContextWebFilter extends OncePerRequestFilter {
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {
         // 设置
-        String tenantId = request.getHeader(HEADER_TENANT_ID);
-        if (StrUtil.isNotEmpty(tenantId)) {
-            TenantContextHolder.setTenantId(Long.valueOf(tenantId));
+        Long tenantId = WebFrameworkUtils.getTenantId(request);
+        if (tenantId != null) {
+            TenantContextHolder.setTenantId(tenantId);
         }
         try {
             chain.doFilter(request, response);

+ 10 - 5
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java

@@ -27,9 +27,11 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
 
     @Override
     protected void doInit() {
-        // 补全风格。例如说 Linux 是 /,Windows 是 \
-        if (!config.getBasePath().endsWith(File.separator)) {
-            config.setBasePath(config.getBasePath() + File.separator);
+        // 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况
+        config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));
+        // ftp的路径是 / 结尾
+        if (!config.getBasePath().endsWith(StrUtil.SLASH)) {
+            config.setBasePath(config.getBasePath() + StrUtil.SLASH);
         }
         // 初始化 Ftp 对象
         this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
@@ -42,6 +44,7 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
         String filePath = getFilePath(path);
         String fileName = FileUtil.getName(filePath);
         String dir = StrUtil.removeSuffix(filePath, fileName);
+        ftp.reconnectIfTimeout();
         boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
         if (!success) {
             throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
@@ -53,6 +56,7 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
     @Override
     public void delete(String path) {
         String filePath = getFilePath(path);
+        ftp.reconnectIfTimeout();
         ftp.delFile(filePath);
     }
 
@@ -60,8 +64,9 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
     public byte[] getContent(String path) {
         String filePath = getFilePath(path);
         String fileName = FileUtil.getName(filePath);
-        String dir = StrUtil.removeSuffix(path, fileName);
+        String dir = StrUtil.removeSuffix(filePath, fileName);
         ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ftp.reconnectIfTimeout();
         ftp.download(dir, fileName, out);
         return out.toByteArray();
     }
@@ -70,4 +75,4 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
         return config.getBasePath() + path;
     }
 
-}
+}

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml

@@ -58,6 +58,14 @@
             <groupId>com.baomidou</groupId>
             <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
         </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.github.ulisesbocchio</groupId>
+            <artifactId>jasypt-spring-boot-starter</artifactId> <!-- 加解密 -->
+            <optional>true</optional>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 70 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.framework.mybatis.core.type;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.extra.spring.SpringUtil;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.jasypt.encryption.StringEncryptor;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * 字段字段的 TypeHandler 实现类,基于 {@link StringEncryptor} 实现
+ * 可通过 jasypt.encryptor.password 配置项,设置密钥
+ *
+ * @author 芋道源码
+ */
+public class EncryptTypeHandler extends BaseTypeHandler<String> {
+
+    private static StringEncryptor encryptor;
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
+        ps.setString(i, getEncryptor().encrypt(parameter));
+    }
+
+    @Override
+    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return decrypt(value);
+    }
+
+    @Override
+    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return decrypt(value);
+    }
+
+    @Override
+    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return decrypt(value);
+    }
+
+    private static String decrypt(String value) {
+        if (value == null) {
+            return null;
+        }
+        return getEncryptor().decrypt(value);
+    }
+
+    public static String encrypt(String rawValue) {
+        if (rawValue == null) {
+            return null;
+        }
+        return getEncryptor().encrypt(rawValue);
+    }
+
+    private static StringEncryptor getEncryptor() {
+        if (encryptor != null) {
+            return encryptor;
+        }
+        encryptor = SpringUtil.getBean(StringEncryptor.class);
+        Assert.notNull(encryptor, "StringEncryptor 不能为空");
+        return encryptor;
+    }
+
+}

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java → yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringListTypeHandler.java

@@ -21,7 +21,7 @@ import java.util.List;
  */
 @MappedJdbcTypes(JdbcType.VARCHAR)
 @MappedTypes(List.class)
-public class StringLiSTTypeHandler implements TypeHandler<List<String>> {
+public class StringListTypeHandler implements TypeHandler<List<String>> {
 
     private static final String COMMA = ",";
 

+ 3 - 0
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/config/YudaoLock4jConfiguration.java

@@ -1,12 +1,15 @@
 package cn.iocoder.yudao.framework.lock4j.config;
 
 import cn.hutool.core.util.ClassUtil;
+import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
 import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
 import cn.iocoder.yudao.framework.lock4j.core.Lock4jRedisKeyConstants;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
+@AutoConfigureBefore(LockAutoConfiguration.class)
 public class YudaoLock4jConfiguration {
 
     static {

+ 4 - 11
yudao-framework/yudao-spring-boot-starter-security/pom.xml

@@ -44,18 +44,11 @@
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
 
-        <!-- TODO 芋艿: -->
+        <!-- 业务组件 -->
         <dependency>
-            <groupId>org.activiti</groupId>
-            <artifactId>activiti-engine</artifactId>
-            <version>7.1.0.M6</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>*</groupId>
-                    <artifactId>*</artifactId>
-                </exclusion>
-            </exclusions>
-            <optional>true</optional>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
+            <version>${revision}</version>
         </dependency>
     </dependencies>
 

+ 0 - 13
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java

@@ -6,7 +6,6 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
-import java.time.Duration;
 
 @ConfigurationProperties(prefix = "yudao.security")
 @Validated
@@ -18,18 +17,6 @@ public class SecurityProperties {
      */
     @NotEmpty(message = "Token Header 不能为空")
     private String tokenHeader;
-    /**
-     * Token 过期时间
-     */
-    @NotNull(message = "Token 过期时间不能为空")
-    private Duration tokenTimeout;
-    /**
-     * Session 过期时间
-     *
-     * 当 User 用户超过当前时间未操作,则 Session 会过期
-     */
-    @NotNull(message = "Session 过期时间不能为空")
-    private Duration sessionTimeout;
 
     /**
      * mock 模式的开关

+ 10 - 25
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java

@@ -1,15 +1,15 @@
 package cn.iocoder.yudao.framework.security.config;
 
 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
-import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
-import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
-import cn.iocoder.yudao.framework.web.config.WebProperties;
+import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
+import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkServiceImpl;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
@@ -19,10 +19,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.AccessDeniedHandler;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
 import javax.annotation.Resource;
-import java.util.List;
 
 /**
  * Spring Security 自动配置类,主要用于相关组件的配置
@@ -63,14 +61,6 @@ public class YudaoSecurityAutoConfiguration {
         return new AccessDeniedHandlerImpl();
     }
 
-    /**
-     * 退出处理类 Bean
-     */
-    @Bean
-    public LogoutSuccessHandler logoutSuccessHandler(MultiUserDetailsAuthenticationProvider authenticationProvider) {
-        return new LogoutSuccessHandlerImpl(securityProperties, authenticationProvider);
-    }
-
     /**
      * Spring Security 加密器
      * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器
@@ -86,19 +76,14 @@ public class YudaoSecurityAutoConfiguration {
      * Token 认证过滤器 Bean
      */
     @Bean
-    public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
-                                                               GlobalExceptionHandler globalExceptionHandler) {
-        return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler);
+    public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,
+                                                               OAuth2TokenApi oauth2TokenApi) {
+        return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
     }
 
-    /**
-     * 身份验证的 Provider Bean,通过它实现账号 + 密码的认证
-     */
-    @Bean
-    public MultiUserDetailsAuthenticationProvider authenticationProvider(
-            List<SecurityAuthFrameworkService> securityFrameworkServices,
-            WebProperties webProperties, PasswordEncoder passwordEncoder) {
-        return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder);
+    @Bean("ss") // 使用 Spring Security 的缩写,方便食用
+    public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) {
+        return new SecurityFrameworkServiceImpl(permissionApi);
     }
 
     /**

+ 3 - 29
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java

@@ -1,7 +1,5 @@
 package cn.iocoder.yudao.framework.security.config;
 
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -9,7 +7,6 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -17,7 +14,6 @@ import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.AccessDeniedHandler;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
 import javax.annotation.Resource;
 import java.util.List;
@@ -34,8 +30,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
     @Resource
     private WebProperties webProperties;
 
-    @Resource
-    private MultiUserDetailsAuthenticationProvider authenticationProvider;
     /**
      * 认证失败处理类 Bean
      */
@@ -46,11 +40,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
      */
     @Resource
     private AccessDeniedHandler accessDeniedHandler;
-    /**
-     * 退出处理类 Bean
-     */
-    @Resource
-    private LogoutSuccessHandler logoutSuccessHandler;
     /**
      * Token 认证过滤器 Bean
      */
@@ -76,14 +65,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
         return super.authenticationManagerBean();
     }
 
-    /**
-     * 身份认证接口
-     */
-    @Override
-    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
-        auth.authenticationProvider(authenticationProvider);
-    }
-
     /**
      * 配置 URL 的安全配置
      *
@@ -114,11 +95,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
                 .headers().frameOptions().disable().and()
                 // 一堆自定义的 Spring Security 处理器
                 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
-                    .accessDeniedHandler(accessDeniedHandler).and()
-                // 登出地址的配置
-                .logout().logoutSuccessHandler(logoutSuccessHandler).logoutRequestMatcher(request -> // 匹配多种用户类型的登出
-                        StrUtil.equalsAny(request.getRequestURI(), buildAdminApi("/system/logout"),
-                                                                   buildAppApi("/member/logout")));
+                    .accessDeniedHandler(accessDeniedHandler);
+                // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
 
         // 设置每个请求的权限
         httpSecurity
@@ -140,11 +118,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
         // 添加 JWT Filter
         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
     }
-
-    private String buildAdminApi(String url) {
-        return webProperties.getAdminApi().getPrefix() + url;
-    }
-
+    
     private String buildAppApi(String url) {
         return webProperties.getAppApi().getPrefix() + url;
     }

+ 6 - 74
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java

@@ -1,14 +1,13 @@
 package cn.iocoder.yudao.framework.security.core;
 
 import cn.hutool.core.map.MapUtil;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
 
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 登录用户信息
@@ -16,7 +15,7 @@ import java.util.*;
  * @author 芋道源码
  */
 @Data
-public class LoginUser implements UserDetails {
+public class LoginUser {
 
     /**
      * 用户编号
@@ -28,38 +27,14 @@ public class LoginUser implements UserDetails {
      * 关联 {@link UserTypeEnum}
      */
     private Integer userType;
-    /**
-     * 最后更新时间
-     */
-    private Date updateTime;
-
-    /**
-     * 用户名
-     */
-    private String username;
-    /**
-     * 密码
-     */
-    private String password;
-    /**
-     * 状态
-     */
-    private Integer status;
     /**
      * 租户编号
      */
     private Long tenantId;
-
-    // ========== UserTypeEnum.ADMIN 独有字段 ==========
-    // TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先;
     /**
-     * 角色编号数组
+     * 授权范围
      */
-    private Set<Long> roleIds;
-    /**
-     * 部门编号
-     */
-    private Long deptId;
+    private List<String> scopes;
 
     // ========== 上下文 ==========
     /**
@@ -70,49 +45,6 @@ public class LoginUser implements UserDetails {
     @JsonIgnore
     private Map<String, Object> context;
 
-    @Override
-    @JsonIgnore// 避免序列化
-    public String getPassword() {
-        return password;
-    }
-
-    @Override
-    public String getUsername() {
-        return username;
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isEnabled() {
-        return CommonStatusEnum.ENABLE.getStatus().equals(status);
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public Collection<? extends GrantedAuthority> getAuthorities() {
-        return new HashSet<>();
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isAccountNonExpired() {
-        return true; // 返回 true,不依赖 Spring Security 判断
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isAccountNonLocked() {
-        return true; // 返回 true,不依赖 Spring Security 判断
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isCredentialsNonExpired() {
-        return true;  // 返回 true,不依赖 Spring Security 判断
-    }
-
-    // ========== 上下文 ==========
-
     public void setContext(String key, Object value) {
         if (context == null) {
             context = new HashMap<>();

+ 0 - 149
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java

@@ -1,149 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.authentication;
-
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
-import cn.iocoder.yudao.framework.web.config.WebProperties;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.crypto.password.PasswordEncoder;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 支持多用户类型的 AuthenticationProvider 实现类
- *
- * 为什么不用 {@link org.springframework.security.authentication.ProviderManager} 呢?
- * 原因是,需要每个用户类型实现对应的 {@link AuthenticationProvider} + authentication,略显麻烦。实际,也是可以实现的。
- *
- * 另外,额外支持 verifyTokenAndRefresh 校验令牌、logout 登出、mockLogin 模拟登陆等操作。
- * 实际上,它就是 {@link SecurityAuthFrameworkService} 定义的三个接口。
- * 因为需要支持多种类型,所以需要根据请求的 URL,判断出对应的用户类型,从而使用对应的 SecurityAuthFrameworkService 是吸纳
- *
- * @see cn.iocoder.yudao.framework.common.enums.UserTypeEnum
- * @author 芋道源码
- */
-public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
-
-    private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
-
-    private final WebProperties properties;
-
-    private final PasswordEncoder passwordEncoder;
-
-    public MultiUserDetailsAuthenticationProvider(List<SecurityAuthFrameworkService> serviceList,
-                                                  WebProperties properties, PasswordEncoder passwordEncoder) {
-        serviceList.forEach(service -> services.put(service.getUserType(), service));
-        this.properties = properties;
-        this.passwordEncoder = passwordEncoder;
-    }
-
-    // ========== AuthenticationProvider 相关 ==========
-
-    @Override
-    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
-            throws AuthenticationException {
-        // 执行用户的加载
-        return selectService(authentication).loadUserByUsername(username);
-    }
-
-    private SecurityAuthFrameworkService selectService(UsernamePasswordAuthenticationToken authentication) {
-        // 第一步,获得用户类型
-        UserTypeEnum userType = getUserType(authentication);
-        // 第二步,获得 SecurityAuthFrameworkService
-        SecurityAuthFrameworkService service = services.get(userType);
-        Assert.notNull(service, "用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", userType);
-        return service;
-    }
-
-    private UserTypeEnum getUserType(UsernamePasswordAuthenticationToken authentication) {
-        Assert.isInstanceOf(MultiUsernamePasswordAuthenticationToken.class, authentication);
-        MultiUsernamePasswordAuthenticationToken multiAuthentication = (MultiUsernamePasswordAuthenticationToken) authentication;
-        UserTypeEnum userType = multiAuthentication.getUserType();
-        Assert.notNull(userType, "用户类型不能为空");
-        return userType;
-    }
-
-    @Override // copy 自 DaoAuthenticationProvider 的 additionalAuthenticationChecks 方法
-    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
-            throws AuthenticationException {
-        // 校验 credentials
-        if (authentication.getCredentials() == null) {
-            this.logger.debug("Failed to authenticate since no credentials provided");
-            throw new BadCredentialsException(this.messages.getMessage(
-                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
-        }
-        // 校验 password
-        String presentedPassword = authentication.getCredentials().toString();
-        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
-            this.logger.debug("Failed to authenticate since password does not match stored value");
-            throw new BadCredentialsException(this.messages.getMessage(
-                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
-        }
-    }
-
-    // ========== SecurityAuthFrameworkService 相关 ==========
-
-    /**
-     * 校验 token 的有效性,并获取用户信息
-     * 通过后,刷新 token 的过期时间
-     *
-     * @param request 请求
-     * @param token token
-     * @return 用户信息
-     */
-    public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
-        return selectService(request).verifyTokenAndRefresh(token);
-    }
-
-    /**
-     * 模拟指定用户编号的 LoginUser
-     *
-     * @param request 请求
-     * @param userId 用户编号
-     * @return 登录用户
-     */
-    public LoginUser mockLogin(HttpServletRequest request, Long userId) {
-        return selectService(request).mockLogin(userId);
-    }
-
-    /**
-     * 基于 token 退出登录
-     *
-     * @param request 请求
-     * @param token token
-     */
-    public void logout(HttpServletRequest request, String token) {
-        selectService(request).logout(token);
-    }
-
-    private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
-        // 第一步,获得用户类型
-        UserTypeEnum userType = getUserType(request);
-        // 第二步,获得 SecurityAuthFrameworkService
-        SecurityAuthFrameworkService service = services.get(userType);
-        Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类",
-                request.getRequestURI(), userType);
-        return service;
-    }
-
-    private UserTypeEnum getUserType(HttpServletRequest request) {
-        if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
-            return UserTypeEnum.ADMIN;
-        }
-        if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
-            return UserTypeEnum.MEMBER;
-        }
-        throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI()));
-    }
-
-}

+ 0 - 43
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java

@@ -1,43 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.authentication;
-
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import lombok.Getter;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.GrantedAuthority;
-
-import java.util.Collection;
-
-/**
- * 支持多用户的 UsernamePasswordAuthenticationToken 实现类
- *
- * @author 芋道源码
- */
-@Getter
-public class MultiUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
-
-    /**
-     * 用户类型
-     */
-    private UserTypeEnum userType;
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
-        super(principal, credentials);
-    }
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
-                                                    Collection<? extends GrantedAuthority> authorities) {
-        super(principal, credentials, authorities);
-    }
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, UserTypeEnum userType) {
-        super(principal, credentials);
-        this.userType = userType;
-    }
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
-                                                    Collection<? extends GrantedAuthority> authorities, UserTypeEnum userType) {
-        super(principal, credentials, authorities);
-        this.userType = userType;
-    }
-
-}

+ 39 - 10
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java

@@ -1,14 +1,19 @@
 package cn.iocoder.yudao.framework.security.core.filter;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
 import lombok.RequiredArgsConstructor;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 import javax.servlet.FilterChain;
@@ -28,24 +33,26 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
 
     private final SecurityProperties securityProperties;
 
-    private final MultiUserDetailsAuthenticationProvider authenticationProvider;
-
     private final GlobalExceptionHandler globalExceptionHandler;
 
+    private final OAuth2TokenApi oauth2TokenApi;
+
     @Override
     @SuppressWarnings("NullableProblems")
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {
         String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
         if (StrUtil.isNotEmpty(token)) {
+            Integer userType = WebFrameworkUtils.getLoginUserType(request);
             try {
-                // 验证 token 有效性
-                LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
-                // 模拟 Login 功能,方便日常开发调试
+                // 1.1 基于 token 构建登录用户
+                LoginUser loginUser = buildLoginUserByToken(token, userType);
+                // 1.2 模拟 Login 功能,方便日常开发调试
                 if (loginUser == null) {
-                    loginUser = mockLoginUser(request, token);
+                    loginUser = mockLoginUser(request, token, userType);
                 }
-                // 设置当前用户
+
+                // 2. 设置当前用户
                 if (loginUser != null) {
                     SecurityFrameworkUtils.setLoginUser(loginUser, request);
                 }
@@ -60,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
         chain.doFilter(request, response);
     }
 
+    private LoginUser buildLoginUserByToken(String token, Integer userType) {
+        try {
+            OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
+            if (accessToken == null) {
+                return null;
+            }
+            // 用户类型不匹配,无权限
+            if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
+                throw new AccessDeniedException("错误的用户类型");
+            }
+            // 构建登录用户
+            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
+                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
+        } catch (ServiceException serviceException) {
+            // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
+            return null;
+        }
+    }
+
     /**
      * 模拟登录用户,方便日常开发调试
      *
@@ -67,9 +93,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
      *
      * @param request 请求
      * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
+     * @param userType 用户类型
      * @return 模拟的 LoginUser
      */
-    private LoginUser mockLoginUser(HttpServletRequest request, String token) {
+    private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
         if (!securityProperties.getMockEnable()) {
             return null;
         }
@@ -77,8 +104,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
         if (!token.startsWith(securityProperties.getMockSecret())) {
             return null;
         }
+        // 构建模拟用户
         Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
-        return authenticationProvider.mockLogin(request, userId);
+        return new LoginUser().setId(userId).setUserType(userType)
+                .setTenantId(WebFrameworkUtils.getTenantId(request));
     }
 
 }

+ 0 - 40
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java

@@ -1,40 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.handler;
-
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import cn.iocoder.yudao.framework.security.config.SecurityProperties;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
-import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-/**
- * 自定义退出处理器
- *
- * @author ruoyi
- */
-@AllArgsConstructor
-public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
-
-    private final SecurityProperties securityProperties;
-
-    private final MultiUserDetailsAuthenticationProvider authenticationProvider;
-
-    @Override
-    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
-        // 执行退出
-        String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
-        if (StrUtil.isNotBlank(token)) {
-            authenticationProvider.logout(request, token);
-        }
-        // 返回成功
-        ServletUtils.writeJSON(response, CommonResult.success(null));
-    }
-
-}

+ 0 - 45
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.service;
-
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import org.springframework.security.core.userdetails.UserDetailsService;
-
-/**
- * Security 框架 Auth Service 接口,定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法
- *
- * @author 芋道源码
- */
-public interface SecurityAuthFrameworkService extends UserDetailsService {
-
-    /**
-     * 校验 token 的有效性,并获取用户信息
-     * 通过后,刷新 token 的过期时间
-     *
-     * @param token token
-     * @return 用户信息
-     */
-    LoginUser verifyTokenAndRefresh(String token);
-
-    /**
-     * 模拟指定用户编号的 LoginUser
-     *
-     * @param userId 用户编号
-     * @return 登录用户
-     */
-    LoginUser mockLogin(Long userId);
-
-    /**
-     * 基于 token 退出登录
-     *
-     * @param token token
-     */
-    void logout(String token);
-
-    /**
-     * 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。
-     *
-     * @return 用户类型
-     */
-    UserTypeEnum getUserType();
-
-}

+ 17 - 2
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityPermissionFrameworkService.java → yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkService.java

@@ -1,11 +1,11 @@
 package cn.iocoder.yudao.framework.security.core.service;
 
 /**
- * Security 框架 Permission Service 接口,定义 security 组件需要的功能
+ * Security 框架 Service 接口,定义权限相关的校验操作
  *
  * @author 芋道源码
  */
-public interface SecurityPermissionFrameworkService {
+public interface SecurityFrameworkService {
 
     /**
      * 判断是否有权限
@@ -41,4 +41,19 @@ public interface SecurityPermissionFrameworkService {
      */
     boolean hasAnyRoles(String... roles);
 
+    /**
+     * 判断是否有授权
+     *
+     * @param scope 授权
+     * @return 是否
+     */
+    boolean hasScope(String scope);
+
+    /**
+     * 判断是否有授权范围,任一一个即可
+     *
+     * @param scope 授权范围数组
+     * @return 是否
+     */
+    boolean hasAnyScopes(String... scope);
 }

+ 57 - 0
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkServiceImpl.java

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.framework.security.core.service;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import lombok.AllArgsConstructor;
+
+import java.util.Arrays;
+
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * 默认的 {@link SecurityFrameworkService} 实现类
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
+
+    private final PermissionApi permissionApi;
+
+    @Override
+    public boolean hasPermission(String permission) {
+        return hasAnyPermissions(permission);
+    }
+
+    @Override
+    public boolean hasAnyPermissions(String... permissions) {
+        return permissionApi.hasAnyPermissions(getLoginUserId(), permissions);
+    }
+
+    @Override
+    public boolean hasRole(String role) {
+        return hasAnyRoles(role);
+    }
+
+    @Override
+    public boolean hasAnyRoles(String... roles) {
+        return permissionApi.hasAnyRoles(getLoginUserId(), roles);
+    }
+
+    @Override
+    public boolean hasScope(String scope) {
+        return hasAnyScopes(scope);
+    }
+
+    @Override
+    public boolean hasAnyScopes(String... scope) {
+        LoginUser user = SecurityFrameworkUtils.getLoginUser();
+        if (user == null) {
+            return false;
+        }
+        return CollUtil.containsAny(user.getScopes(), Arrays.asList(scope));
+    }
+
+}

+ 5 - 14
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java

@@ -11,7 +11,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
 import org.springframework.util.StringUtils;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.Set;
+import java.util.Collections;
 
 /**
  * 安全服务工具类
@@ -20,6 +20,8 @@ import java.util.Set;
  */
 public class SecurityFrameworkUtils {
 
+    public static final String AUTHORIZATION_BEARER = "Bearer";
+
     private SecurityFrameworkUtils() {}
 
     /**
@@ -34,7 +36,7 @@ public class SecurityFrameworkUtils {
         if (!StringUtils.hasText(authorization)) {
             return null;
         }
-        int index = authorization.indexOf("Bearer ");
+        int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
         if (index == -1) { // 未找到
             return null;
         }
@@ -79,17 +81,6 @@ public class SecurityFrameworkUtils {
         return loginUser != null ? loginUser.getId() : null;
     }
 
-    /**
-     * 获得当前用户的角色编号数组
-     *
-     * @return 角色编号数组
-     */
-    @Nullable
-    public static Set<Long> getLoginUserRoleIds() {
-        LoginUser loginUser = getLoginUser();
-        return loginUser != null ? loginUser.getRoleIds() : null;
-    }
-
     /**
      * 设置当前用户
      *
@@ -110,7 +101,7 @@ public class SecurityFrameworkUtils {
     private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
         // 创建 UsernamePasswordAuthenticationToken 对象
         UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
-                loginUser, null, loginUser.getAuthorities());
+                loginUser, null, Collections.emptyList());
         authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
         return authenticationToken;
     }

+ 9 - 6
yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java

@@ -2,13 +2,16 @@ package cn.iocoder.yudao.framework.test.core.util;
 
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import uk.co.jemos.podam.api.PodamFactory;
 import uk.co.jemos.podam.api.PodamFactoryImpl;
 
 import java.lang.reflect.Type;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -22,7 +25,6 @@ public class RandomUtils {
 
     private static final int RANDOM_STRING_LENGTH = 10;
 
-    private static final Set<String> TINYINT_FIELDS = SetUtils.asSet("type", "category");
     private static final int TINYINT_MAX = 127;
 
     private static final int RANDOM_DATE_MAX = 30;
@@ -41,9 +43,10 @@ public class RandomUtils {
             if (attributeMetadata.getAttributeName().equals("status")) {
                 return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
             }
-            // 针对部分字段,使用 tinyint 范围
-            if (TINYINT_FIELDS.contains(attributeMetadata.getAttributeName())) {
-                return RandomUtil.randomInt(1, TINYINT_MAX + 1);
+            // 如果是 type、status 结尾的字段,返回 tinyint 范围
+            if (StrUtil.endWithAnyIgnoreCase(attributeMetadata.getAttributeName(),
+                    "type", "status", "category", "scope")) {
+                return RandomUtil.randomInt(0, TINYINT_MAX + 1);
             }
             return RandomUtil.randomInt();
         });

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
 import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -65,6 +66,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
         return new GlobalResponseBodyHandler();
     }
 
+    @Bean
+    @SuppressWarnings("InstantiationOfUtilityClass")
+    public WebFrameworkUtils webFrameworkUtils(WebProperties webProperties) {
+        // 由于 WebFrameworkUtils 需要使用到 webProperties 属性,所以注册为一个 Bean
+        return new WebFrameworkUtils(webProperties);
+    }
+
     // ========== Filter 相关 ==========
 
     /**

+ 44 - 2
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.framework.web.core.util;
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.web.config.WebProperties;
 import org.springframework.web.context.request.RequestAttributes;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
@@ -21,16 +23,43 @@ public class WebFrameworkUtils {
 
     private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
 
+    private static final String HEADER_TENANT_ID = "tenant-id";
+
+    private static WebProperties properties;
+
+    public WebFrameworkUtils(WebProperties webProperties) {
+        WebFrameworkUtils.properties = webProperties;
+    }
+
+    /**
+     * 获得租户编号,从 header 中
+     * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供
+     *
+     * @param request 请求
+     * @return 租户编号
+     */
+    public static Long getTenantId(HttpServletRequest request) {
+        String tenantId = request.getHeader(HEADER_TENANT_ID);
+        return StrUtil.isNotEmpty(tenantId) ? Long.valueOf(tenantId) : null;
+    }
+
     public static void setLoginUserId(ServletRequest request, Long userId) {
         request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
     }
 
+    /**
+     * 设置用户类型
+     *
+     * @param request 请求
+     * @param userType 用户类型
+     */
     public static void setLoginUserType(ServletRequest request, Integer userType) {
         request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);
     }
 
     /**
      * 获得当前用户的编号,从请求中
+     * 注意:该方法仅限于 framework 框架使用!!!
      *
      * @param request 请求
      * @return 用户编号
@@ -43,7 +72,8 @@ public class WebFrameworkUtils {
     }
 
     /**
-     * 获得当前用户的类型,从请求中
+     * 获得当前用户的类型
+     * 注意:该方法仅限于 web 相关的 framework 组件使用!!!
      *
      * @param request 请求
      * @return 用户编号
@@ -52,7 +82,19 @@ public class WebFrameworkUtils {
         if (request == null) {
             return null;
         }
-        return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
+        // 1. 优先,从 Attribute 中获取
+        Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
+        if (userType != null) {
+            return userType;
+        }
+        // 2. 其次,基于 URL 前缀的约定
+        if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
+            return UserTypeEnum.ADMIN.getValue();
+        }
+        if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
+            return UserTypeEnum.MEMBER.getValue();
+        }
+        return null;
     }
 
     public static Integer getLoginUserType() {

+ 5 - 18
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java

@@ -1,15 +1,12 @@
 package cn.iocoder.yudao.module.infra.controller.admin.file;
 
-import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
-import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.UploadRespVO;
 import cn.iocoder.yudao.module.infra.convert.file.FileConvert;
 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
 import cn.iocoder.yudao.module.infra.service.file.FileService;
@@ -46,20 +43,10 @@ public class FileController {
             @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class),
             @ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class)
     })
-    public CommonResult<UploadRespVO> uploadFile(@RequestParam("file") MultipartFile file,
-                                                 @RequestParam(value = "path", required = false) String path)
-            throws Exception {
-        // 如果路径没传, 系统生成随机路径
-        if (StrUtil.isBlank(path)) {
-            // TODO 生成带日期的路径, 目前 #getFileContent 不支持
-            path = IdUtil.fastSimpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());
-        }
-        String fileUrl = fileService.createFile(path, IoUtil.readBytes(file.getInputStream()));
-        // 返回结果
-        UploadRespVO uploadRespVO = new UploadRespVO();
-        uploadRespVO.setFileName(file.getOriginalFilename());
-        uploadRespVO.setFileUrl(fileUrl);
-        return success(uploadRespVO);
+    @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要
+    public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file,
+                                           @RequestParam(value = "path", required = false) String path) throws Exception {
+        return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream())));
     }
 
     @DeleteMapping("/delete")

+ 4 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/db/DataSourceConfigDO.java

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.infra.dal.dataobject.db;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
 import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
@@ -10,7 +12,7 @@ import lombok.Data;
  *
  * @author 芋道源码
  */
-@TableName("infra_data_source_config")
+@TableName(value = "infra_data_source_config", autoResultMap = true)
 @KeySequence("infra_data_source_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 public class DataSourceConfigDO extends BaseDO {
@@ -40,6 +42,7 @@ public class DataSourceConfigDO extends BaseDO {
     /**
      * 密码
      */
+    @TableField(typeHandler = EncryptTypeHandler.class)
     private String password;
 
 }

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java

@@ -206,7 +206,7 @@ public class CodegenServiceImpl implements CodegenService {
     public Map<String, String> generationCodes(Long tableId) {
         // 校验是否已经存在
         CodegenTableDO table = codegenTableMapper.selectById(tableId);
-        if (codegenTableMapper.selectById(tableId) == null) {
+        if (table == null) {
             throw exception(CODEGEN_TABLE_NOT_EXISTS);
         }
         List<CodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(tableId);

+ 1 - 12
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImpl.java

@@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
-import org.jasypt.encryption.StringEncryptor;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -32,9 +31,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
     @Resource
     private DataSourceConfigMapper dataSourceConfigMapper;
 
-    @Resource
-    private StringEncryptor stringEncryptor;
-
     @Resource
     private DynamicDataSourceProperties dynamicDataSourceProperties;
 
@@ -44,7 +40,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
         checkConnectionOK(dataSourceConfig);
 
         // 插入
-        dataSourceConfig.setPassword(stringEncryptor.encrypt(createReqVO.getPassword()));
         dataSourceConfigMapper.insert(dataSourceConfig);
         // 返回
         return dataSourceConfig.getId();
@@ -58,7 +53,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
         checkConnectionOK(updateObj);
 
         // 更新
-        updateObj.setPassword(stringEncryptor.encrypt(updateObj.getPassword()));
         dataSourceConfigMapper.updateById(updateObj);
     }
 
@@ -83,12 +77,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
             return buildMasterDataSourceConfig();
         }
         // 从 DB 中读取
-        DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(id);
-        try {
-            dataSourceConfig.setPassword(stringEncryptor.decrypt(dataSourceConfig.getPassword()));
-        } catch (Exception ignore) { // 解码失败,则不解码
-        }
-        return dataSourceConfig;
+        return dataSourceConfigMapper.selectById(id);
     }
 
     @Override

+ 9 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.infra.service.file;
 
 import cn.hutool.core.io.FileTypeUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.file.core.client.FileClient;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
@@ -36,6 +38,12 @@ public class FileServiceImpl implements FileService {
 
     @Override
     public String createFile(String path, byte[] content) throws Exception {
+        // 计算默认的 path 名
+        String type = FileTypeUtil.getType(new ByteArrayInputStream(content), path);
+        if (StrUtil.isEmpty(path)) {
+            path = DigestUtil.md5Hex(content) + '.' + type;
+        }
+
         // 上传到文件存储器
         FileClient client = fileConfigService.getMasterFileClient();
         Assert.notNull(client, "客户端(master) 不能为空");
@@ -46,7 +54,7 @@ public class FileServiceImpl implements FileService {
         file.setConfigId(client.getId());
         file.setPath(path);
         file.setUrl(url);
-        file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content)));
+        file.setType(type);
         file.setSize(content.length);
         fileMapper.insert(file);
         return url;

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do.vm

@@ -30,7 +30,7 @@ public class ${table.className}DO extends BaseDO {
     #end
      */
     #if (${column.primaryKey})##处理主键
-    @TableId#if (${column.javaType} == 'String')type = IdType.INPUT)#end
+    @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end
     #end
     private ${column.javaType} ${column.javaField};
 #end

+ 1 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/sql/sql.vm

@@ -9,6 +9,7 @@ VALUES (
 );
 
 -- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
 SELECT @parentId := LAST_INSERT_ID();
 
 -- 按钮 SQL

+ 2 - 2
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java

@@ -27,8 +27,8 @@ public class DefaultDatabaseQueryTest {
             if (StrUtil.startWithAny(tableInfo.getName().toLowerCase(), "act_", "flw_", "qrtz_")) {
                 continue;
             }
-//            System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 0;", tableInfo.getName()));
-            System.out.println(String.format("DELETE FROM %s WHERE deleted = '1';", tableInfo.getName()));
+            System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 1;", tableInfo.getName()));
+//            System.out.println(String.format("DELETE FROM %s WHERE deleted = '1';", tableInfo.getName()));
         }
         System.out.println(tableInfos.size());
         System.out.println(System.currentTimeMillis() - time);

+ 31 - 7
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.infra.service.db;
 
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigCreateReqVO;
@@ -8,8 +10,10 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
 import org.jasypt.encryption.StringEncryptor;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
+import org.mockito.stubbing.Answer;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
@@ -21,7 +25,10 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
 
 /**
  * {@link DataSourceConfigServiceImpl} 的单元测试类
@@ -43,13 +50,20 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
     @MockBean
     private DynamicDataSourceProperties dynamicDataSourceProperties;
 
+    @BeforeEach
+    public void setUp() {
+        // mock 一个空实现的 StringEncryptor,避免 EncryptTypeHandler 报错
+        ReflectUtil.setFieldValue(EncryptTypeHandler.class, "encryptor", stringEncryptor);
+        when(stringEncryptor.encrypt(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
+        when(stringEncryptor.decrypt(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
+    }
+
     @Test
     public void testCreateDataSourceConfig_success() {
         try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) {
             // 准备参数
             DataSourceConfigCreateReqVO reqVO = randomPojo(DataSourceConfigCreateReqVO.class);
             // mock 方法
-            when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
             databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
                     eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
 
@@ -59,8 +73,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
             assertNotNull(dataSourceConfigId);
             // 校验记录的属性是否正确
             DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(dataSourceConfigId);
-            assertPojoEquals(reqVO, dataSourceConfig, "password");
-            assertEquals("123456", dataSourceConfig.getPassword());
+            assertPojoEquals(reqVO, dataSourceConfig);
         }
     }
 
@@ -75,7 +88,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
                 o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID
             });
             // mock 方法
-            when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
+//            when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
             databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
                     eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
 
@@ -83,8 +96,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
             dataSourceConfigService.updateDataSourceConfig(reqVO);
             // 校验是否更新正确
             DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(reqVO.getId()); // 获取最新的
-            assertPojoEquals(reqVO, dataSourceConfig, "password");
-            assertEquals("123456", dataSourceConfig.getPassword());
+            assertPojoEquals(reqVO, dataSourceConfig);
         }
     }
 
@@ -120,4 +132,16 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
         assertServiceException(() -> dataSourceConfigService.deleteDataSourceConfig(id), DATA_SOURCE_CONFIG_NOT_EXISTS);
     }
 
+    @Test // 测试使用 password 查询,可以查询到数据
+    public void testSelectPassword() {
+        // mock 数据
+        DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
+        dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
+
+        // 调用
+        DataSourceConfigDO result = dataSourceConfigMapper.selectOne(DataSourceConfigDO::getPassword,
+                EncryptTypeHandler.encrypt(dbDataSourceConfig.getPassword()));
+        System.out.println(result);
+    }
+
 }

+ 3 - 3
yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/create_tables.sql

@@ -1,12 +1,12 @@
 
 CREATE TABLE IF NOT EXISTS "infra_config" (
     "id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
-    "group" varchar(50) NOT NULL,
+    "category" varchar(50) NOT NULL,
     "type" tinyint NOT NULL,
     "name" varchar(100) NOT NULL DEFAULT '',
-    "key" varchar(100) NOT NULL DEFAULT '',
+    "config_key" varchar(100) NOT NULL DEFAULT '',
     "value" varchar(500) NOT NULL DEFAULT '',
-    "sensitive" bit NOT NULL,
+    "visible" bit NOT NULL,
     "remark" varchar(500) DEFAULT NULL,
     "creator" varchar(64) DEFAULT '',
     "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

+ 0 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java

@@ -17,7 +17,6 @@ public interface ErrorCodeConstants {
     // ========== AUTH 模块 1004003000 ==========
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
-    ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1004003002, "登录失败"); // 登录失败的兜底,未知原因
     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
 

+ 8 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http

@@ -1,5 +1,5 @@
 ### 请求 /login 接口 => 成功
-POST {{appApi}}/member/login
+POST {{appApi}}/member/auth/login
 Content-Type: application/json
 tenant-id: {{appTenentId}}
 
@@ -19,7 +19,7 @@ tenant-id: {{appTenentId}}
 }
 
 ### 请求 /sms-login 接口 => 成功
-POST {{appApi}}/member/sms-login
+POST {{appApi}}/member/auth/sms-login
 Content-Type: application/json
 tenant-id: {{appTenentId}}
 
@@ -29,7 +29,12 @@ tenant-id: {{appTenentId}}
 }
 
 ### 请求 /logout 接口 => 成功
-POST {{appApi}}/member/logout
+POST {{appApi}}/member/auth/logout
 Content-Type: application/json
 Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
 tenant-id: {{appTenentId}}
+
+### 请求 /auth/refresh-token 接口 => 成功
+POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
+Content-Type: application/json
+tenant-id: {{appTenentId}}

+ 34 - 13
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java

@@ -1,7 +1,11 @@
 package cn.iocoder.yudao.module.member.controller.app.auth;
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
 import io.swagger.annotations.Api;
@@ -13,11 +17,10 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Api(tags = "用户 APP - 认证")
@@ -30,19 +33,39 @@ public class AppAuthController {
     @Resource
     private MemberAuthService authService;
 
+    @Resource
+    private SecurityProperties securityProperties;
+
     @PostMapping("/login")
     @ApiOperation("使用手机 + 密码登录")
     public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
-        String token = authService.login(reqVO, getClientIP(), getUserAgent());
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+        return success(authService.login(reqVO));
+    }
+
+    @PostMapping("/logout")
+    @ApiOperation("登出系统")
+    public CommonResult<Boolean> logout(HttpServletRequest request) {
+        String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
+        if (StrUtil.isNotBlank(token)) {
+            authService.logout(token);
+        }
+        return success(true);
+    }
+
+    @PostMapping("/refresh-token")
+    @ApiOperation("刷新令牌")
+    @ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<AppAuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
+        return success(authService.refreshToken(refreshToken));
     }
 
+    // ========== 短信登录相关 ==========
+
     @PostMapping("/sms-login")
     @ApiOperation("使用手机 + 验证码登录")
     public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
-        String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+        return success(authService.smsLogin(reqVO));
     }
 
     @PostMapping("/send-sms-code")
@@ -83,16 +106,14 @@ public class AppAuthController {
 
     @PostMapping("/social-quick-login")
     @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
-    public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
-        String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+    public CommonResult<AppAuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
+        return success(authService.socialQuickLogin(reqVO));
     }
 
     @PostMapping("/social-bind-login")
     @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
-    public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
-        String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+    public CommonResult<AppAuthLoginRespVO> socialBindLogin(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
+        return success(authService.socialBindLogin(reqVO));
     }
 
 }

+ 14 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java

@@ -7,14 +7,25 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-@ApiModel("用户 APP - 手机密码登录 Response VO")
+import java.util.Date;
+
+@ApiModel("用户 APP - 登录 Response VO")
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
 public class AppAuthLoginRespVO {
 
-    @ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
-    private String token;
+    @ApiModelProperty(value = "用户编号", required = true, example = "1024")
+    private Long userId;
+
+    @ApiModelProperty(value = "访问令牌", required = true, example = "happy")
+    private String accessToken;
+
+    @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
+    private String refreshToken;
+
+    @ApiModelProperty(value = "过期时间", required = true)
+    private Date expiresTime;
 
 }

+ 3 - 12
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java

@@ -1,17 +1,14 @@
 package cn.iocoder.yudao.module.member.convert.auth;
 
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 
 @Mapper
@@ -19,14 +16,6 @@ public interface AuthConvert {
 
     AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
 
-    @Mapping(source = "mobile", target = "username")
-    LoginUser convert0(MemberUserDO bean);
-
-    default LoginUser convert(MemberUserDO bean) {
-        // 目的,为了设置 UserTypeEnum.MEMBER.getValue()
-        return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
-    }
-
     SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
     SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
     SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
@@ -35,4 +24,6 @@ public interface AuthConvert {
     SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
     SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
 
+    AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);
+
 }

+ 6 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 属于 system 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.member.framework;

+ 28 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.framework.security.config;
+
+import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+
+/**
+ * Member 模块的 Security 配置
+ */
+@Configuration("memberSecurityConfiguration")
+public class SecurityConfiguration {
+
+    @Bean("memberAuthorizeRequestsCustomizer")
+    public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
+        return new AuthorizeRequestsCustomizer() {
+
+            @Override
+            public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
+                // 登录的接口
+                registry.antMatchers(buildAdminApi("/member/auth/logout")).permitAll();
+            }
+
+        };
+    }
+
+}

+ 4 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.member.framework.security.core;

+ 23 - 18
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.member.service.auth;
 
-import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 
 import javax.validation.Valid;
@@ -12,47 +11,46 @@ import javax.validation.Valid;
  *
  * @author 芋道源码
  */
-public interface MemberAuthService extends SecurityAuthFrameworkService {
+public interface MemberAuthService {
 
     /**
      * 手机 + 密码登录
      *
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
-    String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO login(@Valid AppAuthLoginReqVO reqVO);
+
+    /**
+     * 基于 token 退出登录
+     *
+     * @param token token
+     */
+    void logout(String token);
 
     /**
      * 手机 + 验证码登陆
      *
      * @param reqVO 登陆信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
-    String smsLogin(@Valid AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO);
 
     /**
      * 社交登录,使用 code 授权码
      *
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
-    String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO);
 
     /**
      * 社交登录,使用 手机号 + 手机验证码
      *
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
-    String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO);
 
     /**
      * 获得社交认证 URL
@@ -84,4 +82,11 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
      */
     void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO);
 
+    /**
+     * 刷新访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 登录结果
+     */
+    AppAuthLoginRespVO refreshToken(String refreshToken);
 }

+ 73 - 138
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java

@@ -1,35 +1,29 @@
 package cn.iocoder.yudao.module.member.service.auth;
 
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
-import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
+import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.DisabledException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -50,10 +44,6 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
 @Slf4j
 public class MemberAuthServiceImpl implements MemberAuthService {
 
-    @Resource
-    @Lazy // 延迟加载,因为存在相互依赖的问题
-    private AuthenticationManager authenticationManager;
-
     @Resource
     private MemberUserService userService;
     @Resource
@@ -61,9 +51,9 @@ public class MemberAuthServiceImpl implements MemberAuthService {
     @Resource
     private LoginLogApi loginLogApi;
     @Resource
-    private UserSessionApi userSessionApi;
-    @Resource
     private SocialUserApi socialUserApi;
+    @Resource
+    private OAuth2TokenApi oauth2TokenApi;
 
     @Resource
     private PasswordEncoder passwordEncoder;
@@ -71,44 +61,31 @@ public class MemberAuthServiceImpl implements MemberAuthService {
     private MemberUserMapper userMapper;
 
     @Override
-    public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
-        // 获取 username 对应的 SysUserDO
-        MemberUserDO user = userService.getUserByMobile(mobile);
-        if (user == null) {
-            throw new UsernameNotFoundException(mobile);
-        }
-        // 创建 LoginUser 对象
-        return AuthConvert.INSTANCE.convert(user);
-    }
-
-    @Override
-    public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {
         // 使用手机 + 密码,进行登录。
-        LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
+        MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
 
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
     }
 
     @Override
     @Transactional
-    public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO) {
         // 校验验证码
+        String userIp = getClientIP();
         smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
 
         // 获得获得注册用户
         MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
         Assert.notNull(user, "获取用户失败,结果为空");
 
-        // 执行登陆
-        LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
-
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);
     }
 
     @Override
-    public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO) {
         // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
         Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
                 reqVO.getCode(), reqVO.getState());
@@ -122,31 +99,30 @@ public class MemberAuthServiceImpl implements MemberAuthService {
             throw exception(USER_NOT_EXISTS);
         }
 
-        // 创建 LoginUser 对象
-        LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
-
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
     }
 
     @Override
-    public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO socialBindLogin(AppAuthSocialBindLoginReqVO reqVO) {
         // 使用手机号、手机验证码登录
         AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
                 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
-        String token = this.smsLogin(loginReqVO, userIp, userAgent);
-        LoginUser loginUser = userSessionApi.getLoginUser(token);
+        AppAuthLoginRespVO token = smsLogin(loginReqVO);
 
         // 绑定社交用户
-        socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
+        socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(token.getUserId(), getUserType().getValue(), reqVO));
         return token;
     }
 
-    private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
+    private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
         // 插入登陆日志
-        createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return userSessionApi.createUserSession(loginUser, userIp, userAgent);
+        createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
+        // 创建 Token 令牌
+        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
+                .setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
+        // 构建返回结果
+        return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
     }
 
     @Override
@@ -154,40 +130,32 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         return socialUserApi.getAuthorizeUrl(type, redirectUri);
     }
 
-    private LoginUser login0(String username, String password) {
-        final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME;
-        // 用户验证
-        Authentication authentication;
-        try {
-            // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
-            // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
-            authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
-                    username, password, getUserType()));
-        } catch (BadCredentialsException badCredentialsException) {
-            this.createLoginLog(username, logType, LoginResultEnum.BAD_CREDENTIALS);
+    private MemberUserDO login0(String mobile, String password) {
+        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;
+        // 校验账号是否存在
+        MemberUserDO user = userService.getUserByMobile(mobile);
+        if (user == null) {
+            createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
-        } catch (DisabledException disabledException) {
-            this.createLoginLog(username, logType, LoginResultEnum.USER_DISABLED);
+        }
+        if (!userService.isPasswordMatch(password, user.getPassword())) {
+            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
+            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
+        }
+        // 校验是否禁用
+        if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);
             throw exception(AUTH_LOGIN_USER_DISABLED);
-        } catch (AuthenticationException authenticationException) {
-            log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
-            this.createLoginLog(username, logType, LoginResultEnum.UNKNOWN_ERROR);
-            throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
         }
-        Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
-        return (LoginUser) authentication.getPrincipal();
+        return user;
     }
 
-    private void createLoginLog(String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
-        // 获得用户
-        MemberUserDO user = userService.getUserByMobile(mobile);
+    private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
         // 插入登录日志
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         reqDTO.setLogType(logType.getType());
         reqDTO.setTraceId(TracerUtils.getTraceId());
-        if (user != null) {
-            reqDTO.setUserId(user.getId());
-        }
+        reqDTO.setUserId(userId);
         reqDTO.setUserType(getUserType().getValue());
         reqDTO.setUsername(mobile);
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
@@ -195,72 +163,20 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         reqDTO.setResult(loginResult.getResult());
         loginLogApi.createLoginLog(reqDTO);
         // 更新最后登录时间
-        if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
-            userService.updateUserLogin(user.getId(), getClientIP());
+        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
+            userService.updateUserLogin(userId, getClientIP());
         }
     }
 
-    @Override
-    public LoginUser verifyTokenAndRefresh(String token) {
-        // 获得 LoginUser
-        LoginUser loginUser = userSessionApi.getLoginUser(token);
-        if (loginUser == null) {
-            return null;
-        }
-        // 刷新 LoginUser 缓存
-        this.refreshLoginUserCache(token, loginUser);
-        return loginUser;
-    }
-
-    private void refreshLoginUserCache(String token, LoginUser loginUser) {
-        // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
-        if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
-                userSessionApi.getSessionTimeoutMillis() / 3) {
-            return;
-        }
-
-        // 重新加载 UserDO 信息
-        MemberUserDO user = userService.getUser(loginUser.getId());
-        if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
-            // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
-            throw exception(AUTH_TOKEN_EXPIRED);
-        }
-
-        // 刷新 LoginUser 缓存
-        userSessionApi.refreshUserSession(token, loginUser);
-    }
-
-    @Override
-    public LoginUser mockLogin(Long userId) {
-        // 获取用户编号对应的 UserDO
-        MemberUserDO user = userService.getUser(userId);
-        if (user == null) {
-            throw new UsernameNotFoundException(String.valueOf(userId));
-        }
-
-        // 执行登陆
-        this.createLoginLog(user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
-
-        // 创建 LoginUser 对象
-        return AuthConvert.INSTANCE.convert(user);
-    }
-
     @Override
     public void logout(String token) {
-        // 查询用户信息
-        LoginUser loginUser = userSessionApi.getLoginUser(token);
-        if (loginUser == null) {
+        // 删除访问令牌
+        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token);
+        if (accessTokenRespDTO == null) {
             return;
         }
-        // 删除 session
-        userSessionApi.deleteUserSession(token);
-        // 记录登出日志
-        this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
-    }
-
-    @Override
-    public UserTypeEnum getUserType() {
-        return UserTypeEnum.MEMBER;
+        // 删除成功,则记录登出日志
+        createLogoutLog(accessTokenRespDTO.getUserId());
     }
 
     @Override
@@ -269,6 +185,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
 
         // 更新用户密码
+        // TODO 芋艿:需要重构到用户模块
         userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
                 .password(passwordEncoder.encode(reqVO.getPassword())).build());
     }
@@ -293,6 +210,12 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
     }
 
+    @Override
+    public AppAuthLoginRespVO refreshToken(String refreshToken) {
+        OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
+        return AuthConvert.INSTANCE.convert(accessTokenDO);
+    }
+
     /**
      * 校验旧密码
      *
@@ -321,17 +244,29 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         return user;
     }
 
-    private void createLogoutLog(Long userId, String username) {
+    private void createLogoutLog(Long userId) {
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setUserId(userId);
         reqDTO.setUserType(getUserType().getValue());
-        reqDTO.setUsername(username);
+        reqDTO.setUsername(getMobile(userId));
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserIp(getClientIP());
         reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
         loginLogApi.createLoginLog(reqDTO);
     }
 
+    private String getMobile(Long userId) {
+        if (userId == null) {
+            return null;
+        }
+        MemberUserDO user = userService.getUser(userId);
+        return user != null ? user.getMobile() : null;
+    }
+
+    private UserTypeEnum getUserType() {
+        return UserTypeEnum.MEMBER;
+    }
+
 }

+ 9 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java

@@ -69,4 +69,13 @@ public interface MemberUserService {
      */
     void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
 
+    /**
+     * 判断密码是否匹配
+     *
+     * @param rawPassword 未加密的密码
+     * @param encodedPassword 加密后的密码
+     * @return 是否匹配
+     */
+    boolean isPasswordMatch(String rawPassword, String encodedPassword);
+
 }

+ 16 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -69,7 +69,7 @@ public class MemberUserServiceImpl implements MemberUserService {
         MemberUserDO user = new MemberUserDO();
         user.setMobile(mobile);
         user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
-        user.setPassword(passwordEncoder.encode(password)); // 加密密码
+        user.setPassword(encodePassword(password)); // 加密密码
         user.setRegisterIp(registerIp);
         memberUserMapper.insert(user);
         return user;
@@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService {
         memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
     }
 
+    @Override
+    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 对密码进行加密
+     *
+     * @param password 密码
+     * @return 加密后的密码
+     */
+    private String encodePassword(String password) {
+        return passwordEncoder.encode(password);
+    }
+
     @VisibleForTesting
     public MemberUserDO checkUserExists(Long id) {
         if (id == null) {

+ 4 - 5
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java

@@ -9,14 +9,13 @@ import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthUpdatePasswo
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
-import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
-import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.crypto.password.PasswordEncoder;
 
 import javax.annotation.Resource;
@@ -38,8 +37,8 @@ import static org.mockito.Mockito.when;
 @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
 public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
 
-    @MockBean
-    private AuthenticationManager authenticationManager;
+    // TODO @芋艿:登录相关的单测,待补全
+
     @MockBean
     private MemberUserService userService;
     @MockBean
@@ -47,7 +46,7 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
     @MockBean
     private LoginLogApi loginLogApi;
     @MockBean
-    private UserSessionApi userSessionApi;
+    private OAuth2TokenApi oauth2TokenApi;
     @MockBean
     private SocialUserApi socialUserApi;
     @MockBean

+ 0 - 7
yudao-module-system/yudao-module-system-api/pom.xml

@@ -29,13 +29,6 @@
             <optional>true</optional>
         </dependency>
 
-        <!-- 用户信息 -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-security</artifactId>
-            <optional>true</optional>
-        </dependency>
-
     </dependencies>
 
 </project>

+ 49 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.system.api.auth;
+
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
+
+import javax.validation.Valid;
+
+/**
+ * OAuth2.0 Token API 接口
+ *
+ * @author 芋道源码
+ */
+public interface OAuth2TokenApi {
+
+    /**
+     * 创建访问令牌
+     *
+     * @param reqDTO 访问令牌的创建信息
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO);
+
+    /**
+     * 校验访问令牌
+     *
+     * @param accessToken 访问令牌
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken);
+
+    /**
+     * 移除访问令牌
+     *
+     * @param accessToken 访问令牌
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenRespDTO removeAccessToken(String accessToken);
+
+    /**
+     * 刷新访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @param clientId 客户端编号
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId);
+
+}

+ 0 - 56
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java

@@ -1,56 +0,0 @@
-package cn.iocoder.yudao.module.system.api.auth;
-
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-/**
- * 在线用户 Session API 接口
- *
- * @author 芋道源码
- */
-public interface UserSessionApi {
-
-    /**
-     * 创建在线用户 Session
-     *
-     * @param loginUser 登录用户
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return Token 令牌
-     */
-    String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent);
-
-    /**
-     * 刷新在线用户 Session 的更新时间
-     *
-     * @param token Token 令牌
-     * @param loginUser 登录用户
-     */
-    void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token,
-                            @NotNull(message = "登录用户不能为空") LoginUser loginUser);
-
-    /**
-     * 删除在线用户 Session
-     *
-     * @param token Token 令牌
-     */
-    void deleteUserSession(String token);
-
-    /**
-     * 获得 Token 令牌对应的在线用户
-     *
-     * @param token Token 令牌
-     * @return 在线用户
-     */
-    LoginUser getLoginUser(String token);
-
-    /**
-     * 获得 Session 超时时间,单位:毫秒
-     *
-     * @return 超时时间
-     */
-    Long getSessionTimeoutMillis();
-
-}

+ 33 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.system.api.auth.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * OAuth2.0 访问令牌的校验 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class OAuth2AccessTokenCheckRespDTO implements Serializable {
+
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 租户编号
+     */
+    private Long tenantId;
+    /**
+     * 授权范围的数组
+     */
+    private List<String> scopes;
+
+}

+ 40 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.system.api.auth.dto;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * OAuth2.0 访问令牌创建 Request DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class OAuth2AccessTokenCreateReqDTO implements Serializable {
+
+    /**
+     * 用户编号
+     */
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    @NotNull(message = "用户类型不能为空")
+    @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
+    private Integer userType;
+    /**
+     * 客户端编号
+     */
+    @NotNull(message = "客户端编号不能为空")
+    private String clientId;
+    /**
+     * 授权范围
+     */
+    private List<String> scopes;
+
+}

+ 39 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.system.api.auth.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * OAuth2.0 访问令牌的信息 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@Accessors(chain = true)
+public class OAuth2AccessTokenRespDTO implements Serializable {
+
+    /**
+     * 访问令牌
+     */
+    private String accessToken;
+    /**
+     * 刷新令牌
+     */
+    private String refreshToken;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 过期时间
+     */
+    private Date expiresTime;
+
+}

+ 28 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApi.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.system.api.permission;
 
+import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
+
 import java.util.Collection;
 import java.util.Set;
 
@@ -18,4 +20,30 @@ public interface PermissionApi {
      */
     Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
 
+    /**
+     * 判断是否有权限,任一一个即可
+     *
+     * @param userId 用户编号
+     * @param permissions 权限
+     * @return 是否
+     */
+    boolean hasAnyPermissions(Long userId, String... permissions);
+
+    /**
+     * 判断是否有角色,任一一个即可
+     *
+     * @param userId 用户编号
+     * @param roles 角色数组
+     * @return 是否
+     */
+    boolean hasAnyRoles(Long userId, String... roles);
+
+    /**
+     * 获得登陆用户的部门数据权限
+     *
+     * @param userId 用户编号
+     * @return 部门数据权限
+     */
+    DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
+
 }

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/dto/DeptDataPermissionRespDTO.java → yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/permission/dto/DeptDataPermissionRespDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.datapermission.core.dept.service.dto;
+package cn.iocoder.yudao.module.system.api.permission.dto;
 
 import lombok.Data;
 

+ 20 - 2
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -12,7 +12,6 @@ public interface ErrorCodeConstants {
     // ========== AUTH 模块 1002000000 ==========
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
-    ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登录失败的兜底,未知原因
     ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
     ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
@@ -120,8 +119,27 @@ public interface ErrorCodeConstants {
     ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定");
     ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户");
 
-    // ========== 系统感词 1002019000 =========
+    // ========== 系统感词 1002019000 =========
     ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在");
     ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在");
 
+    // ========== OAuth2 客户端 1002020000 =========
+    ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在");
+    ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1002020001, "OAuth2 客户端编号已存在");
+    ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1002020002, "OAuth2 客户端已禁用");
+    ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1002020003, "不支持该授权类型");
+    ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1002020004, "授权范围过大");
+    ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1002020005, "无效 redirect_uri: {}");
+    ErrorCode OAUTH2_CLIENT_CLIENT_SECRET_ERROR = new ErrorCode(1002020006, "无效 client_secret: {}");
+
+    // ========== OAuth2 授权 1002021000 =========
+    ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1002021000, "client_id 不匹配");
+    ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1002021001, "redirect_uri 不匹配");
+    ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1002021002, "state 不匹配");
+    ErrorCode OAUTH2_GRANT_CODE_NOT_EXISTS = new ErrorCode(1002021003, "code 不存在");
+
+    // ========== OAuth2 授权 1002022000 =========
+    ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1002022000, "code 不存在");
+    ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1002022000, "code 已过期");
+
 }

+ 12 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientConstants.java

@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.system.enums.auth;
+
+/**
+ * OAuth2.0 客户端的通用枚举
+ *
+ * @author 芋道源码
+ */
+public interface OAuth2ClientConstants {
+
+    String CLIENT_ID_DEFAULT = "default";
+
+}

+ 29 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2GrantTypeEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.system.enums.auth;
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * OAuth2 授权类型(模式)的枚举
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum OAuth2GrantTypeEnum {
+
+    PASSWORD("password"), // 密码模式
+    AUTHORIZATION_CODE("authorization_code"), // 授权码模式
+    IMPLICIT("implicit"), // 简化模式
+    CLIENT_CREDENTIALS("client_credentials"), // 客户端模式
+    REFRESH_TOKEN("refresh_token"), // 刷新模式
+    ;
+
+    private final String grantType;
+
+    public static OAuth2GrantTypeEnum getByGranType(String grantType) {
+        return ArrayUtil.firstMatch(o -> o.getGrantType().equals(grantType), values());
+    }
+
+}

+ 0 - 2
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java

@@ -12,12 +12,10 @@ public enum LoginLogTypeEnum {
 
     LOGIN_USERNAME(100), // 使用账号登录
     LOGIN_SOCIAL(101), // 使用社交登录
-    LOGIN_MOCK(102), // 使用 Mock 登录
     LOGIN_MOBILE(103), // 使用手机登陆
     LOGIN_SMS(104), // 使用短信登陆
 
     LOGOUT_SELF(200),  // 自己主动登出
-    LOGOUT_TIMEOUT(201), // 超时登出
     LOGOUT_DELETE(202), // 强制退出
     ;
 

+ 48 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.system.api.auth;
+
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * OAuth2.0 Token API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+public class OAuth2TokenApiImpl implements OAuth2TokenApi {
+
+    @Resource
+    private OAuth2TokenService oauth2TokenService;
+
+    @Override
+    public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) {
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(
+                reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId(), reqDTO.getScopes());
+        return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
+    }
+
+    @Override
+    public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) {
+        return OAuth2TokenConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken));
+    }
+
+    @Override
+    public OAuth2AccessTokenRespDTO removeAccessToken(String accessToken) {
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(accessToken);
+        return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
+    }
+
+    @Override
+    public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId) {
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
+        return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
+    }
+
+}

+ 0 - 47
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.system.api.auth;
-
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-
-/**
- * 在线用户 Session API 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class UserSessionApiImpl implements UserSessionApi {
-
-    @Resource
-    private UserSessionService userSessionService;
-
-    @Override
-    public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
-        return userSessionService.createUserSession(loginUser, userIp, userAgent);
-    }
-
-    @Override
-    public void refreshUserSession(String token, LoginUser loginUser) {
-        userSessionService.refreshUserSession(token, loginUser);
-    }
-
-    @Override
-    public void deleteUserSession(String token) {
-        userSessionService.deleteUserSession(token);
-    }
-
-    @Override
-    public LoginUser getLoginUser(String token) {
-        return userSessionService.getLoginUser(token);
-    }
-
-    @Override
-    public Long getSessionTimeoutMillis() {
-        return userSessionService.getSessionTimeoutMillis();
-    }
-
-}

+ 16 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.system.api.permission;
 
+import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import org.springframework.stereotype.Service;
 
@@ -23,4 +24,19 @@ public class PermissionApiImpl implements PermissionApi {
         return permissionService.getUserRoleIdListByRoleIds(roleIds);
     }
 
+    @Override
+    public boolean hasAnyPermissions(Long userId, String... permissions) {
+        return permissionService.hasAnyPermissions(userId, permissions);
+    }
+
+    @Override
+    public boolean hasAnyRoles(Long userId, String... roles) {
+        return permissionService.hasAnyRoles(userId, roles);
+    }
+
+    @Override
+    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
+        return permissionService.getDeptDataPermission(userId);
+    }
+
 }

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http

@@ -1,5 +1,5 @@
 ### 请求 /login 接口 => 成功
-POST {{baseUrl}}/system/login
+POST {{baseUrl}}/system/auth/login
 Content-Type: application/json
 tenant-id: {{adminTenentId}}
 
@@ -11,7 +11,7 @@ tenant-id: {{adminTenentId}}
 }
 
 ### 请求 /login 接口 => 成功(无验证码)
-POST {{baseUrl}}/system/login
+POST {{baseUrl}}/system/auth/login
 Content-Type: application/json
 tenant-id: {{adminTenentId}}
 
@@ -21,7 +21,7 @@ tenant-id: {{adminTenentId}}
 }
 
 ### 请求 /get-permission-info 接口 => 成功
-GET {{baseUrl}}/system/get-permission-info
+GET {{baseUrl}}/system/auth/get-permission-info
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
 

+ 43 - 24
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java

@@ -1,14 +1,17 @@
 package cn.iocoder.yudao.module.system.controller.admin.auth;
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
+import cn.iocoder.yudao.framework.security.config.SecurityProperties;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
@@ -24,18 +27,19 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import java.util.List;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
-import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserRoleIds;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
+import static java.util.Collections.singleton;
 
 @Api(tags = "管理后台 - 认证")
 @RestController
-@RequestMapping("/system/auth") // 暂时不跟 /auth 结尾
+@RequestMapping("/system/auth")
 @Validated
 @Slf4j
 public class AuthController {
@@ -51,13 +55,33 @@ public class AuthController {
     @Resource
     private SocialUserService socialUserService;
 
+    @Resource
+    private SecurityProperties securityProperties;
+
     @PostMapping("/login")
     @ApiOperation("使用账号密码登录")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
-        String token = authService.login(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.login(reqVO));
+    }
+
+    @PostMapping("/logout")
+    @ApiOperation("登出系统")
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<Boolean> logout(HttpServletRequest request) {
+        String token = obtainAuthorization(request, securityProperties.getTokenHeader());
+        if (StrUtil.isNotBlank(token)) {
+            authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
+        }
+        return success(true);
+    }
+
+    @PostMapping("/refresh-token")
+    @ApiOperation("刷新令牌")
+    @ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<AuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
+        return success(authService.refreshToken(refreshToken));
     }
 
     @GetMapping("/get-permission-info")
@@ -69,12 +93,12 @@ public class AuthController {
             return null;
         }
         // 获得角色列表
-        List<RoleDO> roleList = roleService.getRolesFromCache(getLoginUserRoleIds());
+        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
+        List<RoleDO> roleList = roleService.getRolesFromCache(roleIds);
         // 获得菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
-                getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
+        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
-                SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus()));
+                singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
         // 拼接结果返回
         return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
     }
@@ -82,11 +106,12 @@ public class AuthController {
     @GetMapping("/list-menus")
     @ApiOperation("获得登录用户的菜单列表")
     public CommonResult<List<AuthMenuRespVO>> getMenus() {
+        // 获得角色列表
+        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
         // 获得用户拥有的菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
-                getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
+        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
-                SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
+                singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
         // 转换成 Tree 结构返回
         return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
     }
@@ -97,9 +122,7 @@ public class AuthController {
     @ApiOperation("使用短信验证码登录")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
-        String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.smsLogin(reqVO));
     }
 
     @PostMapping("/send-sms-code")
@@ -127,18 +150,14 @@ public class AuthController {
     @ApiOperation("社交快捷登录,使用 code 授权码")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) {
-        String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.socialQuickLogin(reqVO));
     }
 
     @PostMapping("/social-bind-login")
     @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) {
-        String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.socialBindLogin(reqVO));
     }
 
 }

+ 0 - 79
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java

@@ -1,79 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth;
-
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
-import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.module.system.service.dept.DeptService;
-import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiImplicitParam;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-
-@Api(tags = "管理后台 - 用户 Session")
-@RestController
-@RequestMapping("/system/user-session")
-public class UserSessionController {
-
-    @Resource
-    private UserSessionService userSessionService;
-    @Resource
-    private AdminUserService userService;
-
-    @Resource
-    private DeptService deptService;
-
-    @GetMapping("/page")
-    @ApiOperation("获得 Session 分页列表")
-    @PreAuthorize("@ss.hasPermission('system:user-session:page')")
-    public CommonResult<PageResult<UserSessionPageItemRespVO>> getUserSessionPage(@Validated UserSessionPageReqVO reqVO) {
-        // 获得 Session 分页
-        PageResult<UserSessionDO> pageResult = userSessionService.getUserSessionPage(reqVO);
-
-        // 获得拼接需要的数据
-        Map<Long, AdminUserDO> userMap = userService.getUserMap(
-                convertList(pageResult.getList(), UserSessionDO::getUserId));
-        Map<Long, DeptDO> deptMap = deptService.getDeptMap(
-                convertList(userMap.values(), AdminUserDO::getDeptId));
-        // 拼接结果返回
-        List<UserSessionPageItemRespVO> sessionList = new ArrayList<>(pageResult.getList().size());
-        pageResult.getList().forEach(session -> {
-            UserSessionPageItemRespVO respVO = UserSessionConvert.INSTANCE.convert(session);
-            sessionList.add(respVO);
-            // 设置用户账号
-            MapUtils.findAndThen(userMap, session.getUserId(), user -> {
-                respVO.setUsername(user.getUsername());
-                // 设置用户部门
-                MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> respVO.setDeptName(dept.getName()));
-            });
-        });
-        return success(new PageResult<>(sessionList, pageResult.getTotal()));
-    }
-
-    @DeleteMapping("/delete")
-    @ApiOperation("删除 Session")
-    @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = Long.class, example = "1024")
-    @PreAuthorize("@ss.hasPermission('system:user-session:delete')")
-    public CommonResult<Boolean> deleteUserSession(@RequestParam("id") Long id) {
-        userSessionService.deleteUserSession(id);
-        return success(true);
-    }
-
-}

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginReqVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 31 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 登录 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AuthLoginRespVO {
+
+    @ApiModelProperty(value = "用户编号", required = true, example = "1024")
+    private Long userId;
+
+    @ApiModelProperty(value = "访问令牌", required = true, example = "happy")
+    private String accessToken;
+
+    @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
+    private String refreshToken;
+
+    @ApiModelProperty(value = "过期时间", required = true)
+    private Date expiresTime;
+
+}

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthMenuRespVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthMenuRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthPermissionInfoRespVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSmsLoginReqVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSmsLoginReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSmsSendReqVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSmsSendReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
 
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.framework.common.validation.Mobile;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindLoginReqVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSocialBindLoginReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
 
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialQuickLoginReqVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSocialQuickLoginReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
 
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
 import cn.iocoder.yudao.framework.common.validation.InEnum;

+ 0 - 20
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java

@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@ApiModel("管理后台 - 账号密码登录 Response VO")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-public class AuthLoginRespVO {
-
-    @ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
-    private String token;
-
-}

+ 0 - 38
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java

@@ -1,38 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-import java.util.Date;
-
-@ApiModel(value = "管理后台 - 用户在线 Session Response VO", description = "相比用户基本信息来说,会多部门、用户账号等信息")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@EqualsAndHashCode(callSuper = true)
-public class UserSessionPageItemRespVO extends PageParam {
-
-    @ApiModelProperty(value = "Session 编号", required = true, example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7")
-    private String id;
-
-    @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1")
-    private String userIp;
-
-    @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0")
-    private String userAgent;
-
-    @ApiModelProperty(value = "登录时间", required = true)
-    private Date createTime;
-
-    @ApiModelProperty(value = "用户账号", required = true, example = "yudao")
-    private String username;
-
-    @ApiModelProperty(value = "部门名称", example = "研发部")
-    private String deptName;
-
-}

+ 0 - 20
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java

@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-@ApiModel("管理后台 - 在线用户 Session 分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class UserSessionPageReqVO extends PageParam {
-
-    @ApiModelProperty(value = "用户 IP", example = "127.0.0.1", notes = "模糊匹配")
-    private String userIp;
-
-    @ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配")
-    private String username;
-
-}

+ 23 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2ClientController.http

@@ -0,0 +1,23 @@
+### 请求 /login 接口 => 成功
+POST {{baseUrl}}/system/oauth2-client/create
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+{
+  "id": "1",
+  "secret": "admin123",
+  "name": "芋道源码",
+  "logo": "https://www.iocoder.cn/images/favicon.ico",
+  "description": "我是描述",
+  "status": 0,
+  "accessTokenValiditySeconds": 180,
+  "refreshTokenValiditySeconds": 8640,
+  "redirectUris": ["https://www.iocoder.cn"],
+  "autoApprove": true,
+  "authorizedGrantTypes": ["password"],
+  "scopes": ["user_info"],
+  "authorities": ["system:user:query"],
+  "resource_ids": ["1024"],
+  "additionalInformation": "{}"
+}

+ 74 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2ClientController.java

@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.system.controller.admin.oauth2;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO;
+import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - OAuth2 客户端")
+@RestController
+@RequestMapping("/system/oauth2-client")
+@Validated
+public class OAuth2ClientController {
+
+    @Resource
+    private OAuth2ClientService oAuth2ClientService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建 OAuth2 客户端")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:create')")
+    public CommonResult<Long> createOAuth2Client(@Valid @RequestBody OAuth2ClientCreateReqVO createReqVO) {
+        return success(oAuth2ClientService.createOAuth2Client(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新 OAuth2 客户端")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:update')")
+    public CommonResult<Boolean> updateOAuth2Client(@Valid @RequestBody OAuth2ClientUpdateReqVO updateReqVO) {
+        oAuth2ClientService.updateOAuth2Client(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除 OAuth2 客户端")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:delete')")
+    public CommonResult<Boolean> deleteOAuth2Client(@RequestParam("id") Long id) {
+        oAuth2ClientService.deleteOAuth2Client(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得 OAuth2 客户端")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')")
+    public CommonResult<OAuth2ClientRespVO> getOAuth2Client(@RequestParam("id") Long id) {
+        OAuth2ClientDO oAuth2Client = oAuth2ClientService.getOAuth2Client(id);
+        return success(OAuth2ClientConvert.INSTANCE.convert(oAuth2Client));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得OAuth2 客户端分页")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')")
+    public CommonResult<PageResult<OAuth2ClientRespVO>> getOAuth2ClientPage(@Valid OAuth2ClientPageReqVO pageVO) {
+        PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(pageVO);
+        return success(OAuth2ClientConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 54 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http

@@ -0,0 +1,54 @@
+### 请求 /system/oauth2/authorize 接口 => 成功
+GET {{baseUrl}}/system/oauth2/authorize?clientId=default
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+### 请求 /system/oauth2/authorize + token 接口 => 成功
+POST {{baseUrl}}/system/oauth2/authorize
+Content-Type: application/x-www-form-urlencoded
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true
+
+### 请求 /system/oauth2/authorize + code 接口 => 成功
+POST {{baseUrl}}/system/oauth2/authorize
+Content-Type: application/x-www-form-urlencoded
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=false
+
+### 请求 /system/oauth2/token + code 接口 => 成功
+POST {{baseUrl}}/system/oauth2/token
+Content-Type: application/x-www-form-urlencoded
+Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
+tenant-id: {{adminTenentId}}
+
+grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07a174588a97157eabef2f93a
+
+### 请求 /system/oauth2/token + password 接口 => 成功
+POST {{baseUrl}}/system/oauth2/token
+Content-Type: application/x-www-form-urlencoded
+Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
+tenant-id: {{adminTenentId}}
+
+grant_type=password&username=admin&password=admin123&scope=user.read
+
+### 请求 /system/oauth2/token + refresh_token 接口 => 成功
+POST {{baseUrl}}/system/oauth2/token
+Content-Type: application/x-www-form-urlencoded
+Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
+tenant-id: {{adminTenentId}}
+
+grant_type=refresh_token&refresh_token=00895465d6994f72a9d926ceeed0f588
+
+### 请求 /system/oauth2/token + DELETE 接口 => 成功
+DELETE {{baseUrl}}/system/oauth2/token?token=ca8a188f464441d6949c51493a2b7596
+Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
+tenant-id: {{adminTenentId}}
+
+### 请求 /system/oauth2/check-token 接口 => 成功
+POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106
+Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
+tenant-id: {{adminTenentId}}

+ 298 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java

@@ -0,0 +1,298 @@
+package cn.iocoder.yudao.module.system.controller.admin.oauth2;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
+import cn.iocoder.yudao.module.system.convert.oauth2.OAuth2OpenConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
+import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ApproveService;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
+import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * 提供给外部应用调用为主
+ *
+ * 一般来说,管理后台的 /system-api/* 是不直接提供给外部应用使用,主要是外部应用能够访问的数据与接口是有限的,而管理后台的 RBAC 无法很好的控制。
+ * 参考大量的开放平台,都是独立的一套 OpenAPI,对应到【本系统】就是在 Controller 下新建 open 包,实现 /open-api/* 接口,然后通过 scope 进行控制。
+ * 另外,一个公司如果有多个管理后台,它们 client_id 产生的 access token 相互之间是无法互通的,即无法访问它们系统的 API 接口,直到两个 client_id 产生信任授权。
+ *
+ * 考虑到【本系统】暂时不想做的过于复杂,默认只有获取到 access token 之后,可以访问【本系统】管理后台的 /system-api/* 所有接口,除非手动添加 scope 控制。
+ * scope 的使用示例,可见 {@link OAuth2UserController} 类
+ *
+ * @author 芋道源码
+ */
+@Api(tags = "管理后台 - OAuth2.0 授权")
+@RestController
+@RequestMapping("/system/oauth2")
+@Validated
+@Slf4j
+public class OAuth2OpenController {
+
+    @Resource
+    private OAuth2GrantService oauth2GrantService;
+    @Resource
+    private OAuth2ClientService oauth2ClientService;
+    @Resource
+    private OAuth2ApproveService oauth2ApproveService;
+    @Resource
+    private OAuth2TokenService oauth2TokenService;
+
+    /**
+     * 对应 Spring Security OAuth 的 TokenEndpoint 类的 postAccessToken 方法
+     *
+     * 授权码 authorization_code 模式时:code + redirectUri + state 参数
+     * 密码 password 模式时:username + password + scope 参数
+     * 刷新 refresh_token 模式时:refreshToken 参数
+     * 客户端 client_credentials 模式:scope 参数
+     * 简化 implicit 模式时:不支持
+     *
+     * 注意,默认需要传递 client_id + client_secret 参数
+     */
+    @PostMapping("/token")
+    @ApiOperation(value = "获得访问令牌", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "grant_type", required = true, value = "授权类型", example = "code", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "code", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "redirect_uri", value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "state", value = "状态", example = "1", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "username", example = "tudou", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "password", example = "cai", dataTypeClass = String.class), // 多个使用空格分隔
+            @ApiImplicitParam(name = "scope", example = "user_info", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "refresh_token", example = "123424233", dataTypeClass = String.class),
+    })
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<OAuth2OpenAccessTokenRespVO> postAccessToken(HttpServletRequest request,
+                                                                     @RequestParam("grant_type") String grantType,
+                                                                     @RequestParam(value = "code", required = false) String code, // 授权码模式
+                                                                     @RequestParam(value = "redirect_uri", required = false) String redirectUri, // 授权码模式
+                                                                     @RequestParam(value = "state", required = false) String state, // 授权码模式
+                                                                     @RequestParam(value = "username", required = false) String username, // 密码模式
+                                                                     @RequestParam(value = "password", required = false) String password, // 密码模式
+                                                                     @RequestParam(value = "scope", required = false) String scope, // 密码模式
+                                                                     @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式
+        List<String> scopes = OAuth2Utils.buildScopes(scope);
+        // 授权类型
+        OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType);
+        if (grantTypeEnum == null) {
+            throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType));
+        }
+        if (grantTypeEnum == OAuth2GrantTypeEnum.IMPLICIT) {
+            throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式");
+        }
+
+        // 校验客户端
+        String[] clientIdAndSecret = obtainBasicAuthorization(request);
+        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],
+                grantType, scopes, redirectUri);
+
+        // 根据授权模式,获取访问令牌
+        OAuth2AccessTokenDO accessTokenDO;
+        switch (grantTypeEnum) {
+            case AUTHORIZATION_CODE:
+                accessTokenDO = oauth2GrantService.grantAuthorizationCodeForAccessToken(client.getClientId(), code, redirectUri, state);
+                break;
+            case PASSWORD:
+                accessTokenDO = oauth2GrantService.grantPassword(username, password, client.getClientId(), scopes);
+                break;
+            case CLIENT_CREDENTIALS:
+                accessTokenDO = oauth2GrantService.grantClientCredentials(client.getClientId(), scopes);
+                break;
+            case REFRESH_TOKEN:
+                accessTokenDO = oauth2GrantService.grantRefreshToken(refreshToken, client.getClientId());
+                break;
+            default:
+                throw new IllegalArgumentException("未知授权类型:" + grantType);
+        }
+        Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查
+        return success(OAuth2OpenConvert.INSTANCE.convert(accessTokenDO));
+    }
+
+    @DeleteMapping("/token")
+    @ApiOperation(value = "删除访问令牌")
+    @ApiImplicitParam(name = "token", required = true, value = "访问令牌", example = "biu", dataTypeClass = String.class)
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<Boolean> revokeToken(HttpServletRequest request,
+                                             @RequestParam("token") String token) {
+        // 校验客户端
+        String[] clientIdAndSecret = obtainBasicAuthorization(request);
+        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],
+                null, null, null);
+
+        // 删除访问令牌
+        return success(oauth2GrantService.revokeToken(client.getClientId(), token));
+    }
+
+    /**
+     * 对应 Spring Security OAuth 的 CheckTokenEndpoint 类的 checkToken 方法
+     */
+    @PostMapping("/check-token")
+    @ApiOperation(value = "校验访问令牌")
+    @ApiImplicitParam(name = "token", required = true, value = "访问令牌", example = "biu", dataTypeClass = String.class)
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<OAuth2OpenCheckTokenRespVO> checkToken(HttpServletRequest request,
+                                                               @RequestParam("token") String token) {
+        // 校验客户端
+        String[] clientIdAndSecret = obtainBasicAuthorization(request);
+        oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],
+                null, null, null);
+
+        // 校验令牌
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(token);
+        Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查
+        return success(OAuth2OpenConvert.INSTANCE.convert2(accessTokenDO));
+    }
+
+    /**
+     * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 authorize 方法
+     */
+    @GetMapping("/authorize")
+    @ApiOperation(value = "获得授权信息", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用")
+    @ApiImplicitParam(name = "clientId", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class)
+    public CommonResult<OAuth2OpenAuthorizeInfoRespVO> authorize(@RequestParam("clientId") String clientId) {
+        // 0. 校验用户已经登录。通过 Spring Security 实现
+
+        // 1. 获得 Client 客户端的信息
+        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId);
+        // 2. 获得用户已经授权的信息
+        List<OAuth2ApproveDO> approves = oauth2ApproveService.getApproveList(getLoginUserId(), getUserType(), clientId);
+        // 拼接返回
+        return success(OAuth2OpenConvert.INSTANCE.convert(client, approves));
+    }
+
+    /**
+     * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 approveOrDeny 方法
+     *
+     * 场景一:【自动授权 autoApprove = true】
+     *      刚进入 sso.vue 界面,调用该接口,用户历史已经给该应用做过对应的授权,或者 OAuth2Client 支持该 scope 的自动授权
+     * 场景二:【手动授权 autoApprove = false】
+     *      在 sso.vue 界面,用户选择好 scope 授权范围,调用该接口,进行授权。此时,approved 为 true 或者 false
+     *
+     * 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理
+     */
+    @PostMapping("/authorize")
+    @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【提交】调用")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "scope", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), // 使用 Map<String, Boolean> 格式,Spring MVC 暂时不支持这么接收参数
+            @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "auto_approve", required = true, value = "用户是否接受", example = "true", dataTypeClass = Boolean.class),
+            @ApiImplicitParam(name = "state", example = "1", dataTypeClass = String.class)
+    })
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<String> approveOrDeny(@RequestParam("response_type") String responseType,
+                                              @RequestParam("client_id") String clientId,
+                                              @RequestParam(value = "scope", required = false) String scope,
+                                              @RequestParam("redirect_uri") String redirectUri,
+                                              @RequestParam(value = "auto_approve") Boolean autoApprove,
+                                              @RequestParam(value = "state", required = false) String state) {
+        @SuppressWarnings("unchecked")
+        Map<String, Boolean> scopes = JsonUtils.parseObject(scope, Map.class);
+        scopes = ObjectUtil.defaultIfNull(scopes, Collections.emptyMap());
+        // 0. 校验用户已经登录。通过 Spring Security 实现
+
+        // 1.1 校验 responseType 是否满足 code 或者 token 值
+        OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType);
+        // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内
+        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, null,
+                grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri);
+
+        // 2.1 假设 approved 为 null,说明是场景一
+        if (Boolean.TRUE.equals(autoApprove)) {
+            // 如果无法自动授权通过,则返回空 url,前端不进行跳转
+            if (!oauth2ApproveService.checkForPreApproval(getLoginUserId(), getUserType(), clientId, scopes.keySet())) {
+                return success(null);
+            }
+        } else { // 2.2 假设 approved 非 null,说明是场景二
+            // 如果计算后不通过,则跳转一个错误链接
+            if (!oauth2ApproveService.updateAfterApproval(getLoginUserId(), getUserType(), clientId, scopes)) {
+                return success(OAuth2Utils.buildUnsuccessfulRedirect(redirectUri, responseType, state,
+                        "access_denied", "User denied access"));
+            }
+        }
+
+        // 3.1 如果是 code 授权码模式,则发放 code 授权码,并重定向
+        List<String> approveScopes = convertList(scopes.entrySet(), Map.Entry::getKey, Map.Entry::getValue);
+        if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) {
+            return success(getAuthorizationCodeRedirect(getLoginUserId(), client, approveScopes, redirectUri, state));
+        }
+        // 3.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向
+        return success(getImplicitGrantRedirect(getLoginUserId(), client, approveScopes, redirectUri, state));
+    }
+
+    private static OAuth2GrantTypeEnum getGrantTypeEnum(String responseType) {
+        if (StrUtil.equals(responseType, "code")) {
+            return OAuth2GrantTypeEnum.AUTHORIZATION_CODE;
+        }
+        if (StrUtil.equalsAny(responseType, "token")) {
+            return OAuth2GrantTypeEnum.IMPLICIT;
+        }
+        throw exception0(BAD_REQUEST.getCode(), "response_type 参数值只允许 code 和 token");
+    }
+
+    private String getImplicitGrantRedirect(Long userId, OAuth2ClientDO client,
+                                            List<String> scopes, String redirectUri, String state) {
+        // 1. 创建 access token 访问令牌
+        OAuth2AccessTokenDO accessTokenDO = oauth2GrantService.grantImplicit(userId, getUserType(), client.getClientId(), scopes);
+        Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查
+        // 2. 拼接重定向的 URL
+        // noinspection unchecked
+        return OAuth2Utils.buildImplicitRedirectUri(redirectUri, accessTokenDO.getAccessToken(), state, accessTokenDO.getExpiresTime(),
+                scopes, JsonUtils.parseObject(client.getAdditionalInformation(), Map.class));
+    }
+
+    private String getAuthorizationCodeRedirect(Long userId, OAuth2ClientDO client,
+                                                List<String> scopes, String redirectUri, String state) {
+        // 1. 创建 code 授权码
+        String authorizationCode = oauth2GrantService.grantAuthorizationCodeForCode(userId, getUserType(), client.getClientId(), scopes,
+                redirectUri, state);
+        // 2. 拼接重定向的 URL
+        return OAuth2Utils.buildAuthorizationCodeRedirectUri(redirectUri, authorizationCode, state);
+    }
+
+    private Integer getUserType() {
+        return UserTypeEnum.ADMIN.getValue();
+    }
+
+    private String[] obtainBasicAuthorization(HttpServletRequest request) {
+        String[] clientIdAndSecret = HttpUtils.obtainBasicAuthorization(request);
+        if (ArrayUtil.isEmpty(clientIdAndSecret) || clientIdAndSecret.length != 2) {
+            throw exception0(BAD_REQUEST.getCode(), "client_id 或 client_secret 未正确传递");
+        }
+        return clientIdAndSecret;
+    }
+
+}

+ 50 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2TokenController.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.system.controller.admin.oauth2;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenRespVO;
+import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
+import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
+import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - OAuth2.0 令牌")
+@RestController
+@RequestMapping("/system/oauth2-token")
+public class OAuth2TokenController {
+
+    @Resource
+    private OAuth2TokenService oauth2TokenService;
+    @Resource
+    private AdminAuthService authService;
+
+    @GetMapping("/page")
+    @ApiOperation(value = "获得访问令牌分页", notes = "只返回有效期内的")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-token:page')")
+    public CommonResult<PageResult<OAuth2AccessTokenRespVO>> getAccessTokenPage(@Valid OAuth2AccessTokenPageReqVO reqVO) {
+        PageResult<OAuth2AccessTokenDO> pageResult = oauth2TokenService.getAccessTokenPage(reqVO);
+        return success(OAuth2TokenConvert.INSTANCE.convert(pageResult));
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除访问令牌")
+    @ApiImplicitParam(name = "accessToken", value = "访问令牌", required = true, dataTypeClass = String.class, example = "tudou")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-token:delete')")
+    public CommonResult<Boolean> deleteAccessToken(@RequestParam("accessToken") String accessToken) {
+        authService.logout(accessToken, LoginLogTypeEnum.LOGOUT_DELETE.getType());
+        return success(true);
+    }
+
+}

+ 14 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2UserController.http

@@ -0,0 +1,14 @@
+### 请求 /system/oauth2/user/get 接口 => 成功
+GET {{baseUrl}}/system/oauth2/user/get
+Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d
+tenant-id: {{adminTenentId}}
+
+### 请求 /system/oauth2/user/update 接口 => 成功
+PUT {{baseUrl}}/system/oauth2/user/update
+Content-Type: application/json
+Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d
+tenant-id: {{adminTenentId}}
+
+{
+  "nickname": "芋道源码"
+}

+ 80 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2UserController.java

@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.system.controller.admin.oauth2;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.user.OAuth2UserInfoRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.user.OAuth2UserUpdateReqVO;
+import cn.iocoder.yudao.module.system.convert.oauth2.OAuth2UserConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.service.dept.DeptService;
+import cn.iocoder.yudao.module.system.service.dept.PostService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * 提供给外部应用调用为主
+ *
+ * 1. 在 getUserInfo 方法上,添加 @PreAuthorize("@ss.hasScope('user.read')") 注解,声明需要满足 scope = user.read
+ * 2. 在 updateUserInfo 方法上,添加 @PreAuthorize("@ss.hasScope('user.write')") 注解,声明需要满足 scope = user.write
+ *
+ * @author 芋道源码
+ */
+@Api(tags = "管理后台 - OAuth2.0 用户")
+@RestController
+@RequestMapping("/system/oauth2/user")
+@Validated
+@Slf4j
+public class OAuth2UserController {
+
+    @Resource
+    private AdminUserService userService;
+    @Resource
+    private DeptService deptService;
+    @Resource
+    private PostService postService;
+
+    @GetMapping("/get")
+    @ApiOperation("获得用户基本信息")
+    @PreAuthorize("@ss.hasScope('user.read')") //
+    public CommonResult<OAuth2UserInfoRespVO> getUserInfo() {
+        // 获得用户基本信息
+        AdminUserDO user = userService.getUser(getLoginUserId());
+        OAuth2UserInfoRespVO resp = OAuth2UserConvert.INSTANCE.convert(user);
+        // 获得部门信息
+        if (user.getDeptId() != null) {
+            DeptDO dept = deptService.getDept(user.getDeptId());
+            resp.setDept(OAuth2UserConvert.INSTANCE.convert(dept));
+        }
+        // 获得岗位信息
+        if (CollUtil.isNotEmpty(user.getPostIds())) {
+            List<PostDO> posts = postService.getPosts(user.getPostIds());
+            resp.setPosts(OAuth2UserConvert.INSTANCE.convertList(posts));
+        }
+        return success(resp);
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新用户基本信息")
+    @PreAuthorize("@ss.hasScope('user.write')")
+    public CommonResult<Boolean> updateUserInfo(@Valid @RequestBody OAuth2UserUpdateReqVO reqVO) {
+        // 这里将 UserProfileUpdateReqVO =》UserProfileUpdateReqVO 对象,实现接口的复用。
+        // 主要是,AdminUserService 没有自己的 BO 对象,所以复用只能这么做
+        userService.updateUserProfile(getLoginUserId(), OAuth2UserConvert.INSTANCE.convert(reqVO));
+        return success(true);
+    }
+
+}

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