Selaa lähdekoodia

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

 Conflicts:
	README.md
	yudao-framework/pom.xml
	yudao-framework/yudao-spring-boot-starter-biz-error-code/pom.xml
	yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java
YunaiV 2 vuotta sitten
vanhempi
commit
9270924597
100 muutettua tiedostoa jossa 6380 lisäystä ja 1083 poistoa
  1. 9 10
      .gitee/ISSUE_TEMPLATE.zh-CN.md
  2. 9 9
      .github/ISSUE_TEMPLATE/question.md
  3. 39 40
      README.md
  4. 9 0
      http-client.env.json
  5. 1 0
      pom.xml
  6. 287 0
      sql/mall.sql
  7. 130 47
      sql/mysql/ruoyi-vue-pro.sql
  8. 285 145
      sql/oracle/ruoyi-vue-pro.sql
  9. 305 330
      sql/postgresql/ruoyi-vue-pro.sql
  10. 3544 1
      sql/sqlserver/ruoyi-vue-pro.sql
  11. 29 8
      yudao-dependencies/pom.xml
  12. 5 0
      yudao-framework/pom.xml
  13. 12 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/CommonStatusEnum.java
  14. 1 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java
  15. 60 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/ServerException.java
  16. 8 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java
  17. 5 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/ServiceErrorCodeRange.java
  18. 15 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java
  19. 2 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageParam.java
  20. 25 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
  21. 30 0
      yudao-framework/yudao-spring-boot-starter-banner/pom.xml
  22. 20 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/config/YudaoBannerAutoConfiguration.java
  23. 4 4
      yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
  24. 6 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/package-info.java
  25. 2 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/resources/META-INF/spring.factories
  26. 0 0
      yudao-framework/yudao-spring-boot-starter-banner/src/main/resources/banner.txt
  27. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDataPermissionAutoConfiguration.java
  28. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java
  29. 13 0
      yudao-framework/yudao-spring-boot-starter-biz-dict/pom.xml
  30. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/config/YudaoDictAutoConfiguration.java
  31. 4 0
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/package-info.java
  32. 0 35
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/service/DictDataFrameworkService.java
  33. 51 9
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java
  34. 49 0
      yudao-framework/yudao-spring-boot-starter-biz-error-code/pom.xml
  35. 5 1
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/config/ErrorCodeProperties.java
  36. 15 13
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/config/YudaoErrorCodeConfiguration.java
  37. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/generator/ErrorCodeAutoGenerator.java
  38. 6 6
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/generator/ErrorCodeAutoGeneratorImpl.java
  39. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoader.java
  40. 6 6
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java
  41. 10 0
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/package-info.java
  42. 2 0
      yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/resources/META-INF/spring.factories
  43. 7 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml
  44. 8 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogAutoConfiguration.java
  45. 70 60
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java
  46. 0 87
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/dto/OperateLogCreateReqDTO.java
  47. 110 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLog.java
  48. 8 8
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLogFrameworkService.java
  49. 28 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLogFrameworkServiceImpl.java
  50. 2 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
  51. 13 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
  52. 203 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java
  53. 58 13
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java
  54. 0 3
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java
  55. 47 8
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
  56. 0 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java
  57. 2 1
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java
  58. 5 7
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java
  59. 8 8
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java
  60. 0 23
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java
  61. 97 100
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java
  62. 6 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml
  63. 7 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
  64. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/TenantRedisMessageInterceptor.java
  65. 73 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkServiceImpl.java
  66. 0 2
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java
  67. 6 2
      yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
  68. 7 8
      yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
  69. 6 0
      yudao-framework/yudao-spring-boot-starter-monitor/pom.xml
  70. 27 0
      yudao-framework/yudao-spring-boot-starter-monitor/src/main/java/cn/iocoder/yudao/framework/tracer/config/YudaoMetricsAutoConfiguration.java
  71. 2 1
      yudao-framework/yudao-spring-boot-starter-monitor/src/main/resources/META-INF/spring.factories
  72. 6 0
      yudao-framework/yudao-spring-boot-starter-security/pom.xml
  73. 10 3
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
  74. 2 2
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
  75. 61 2
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
  76. 2 2
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
  77. 9 6
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
  78. 7 0
      yudao-framework/yudao-spring-boot-starter-web/pom.xml
  79. 18 1
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/config/YudaoApiLogAutoConfiguration.java
  80. 4 4
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/filter/ApiAccessLogFilter.java
  81. 85 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLog.java
  82. 2 6
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java
  83. 28 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java
  84. 107 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLog.java
  85. 2 6
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java
  86. 28 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java
  87. 14 12
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
  88. 43 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/SpringFoxHandlerProviderBeanPostProcessor.java
  89. 6 2
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/WebProperties.java
  90. 7 7
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
  91. 1 1
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
  92. 7 0
      yudao-module-infra/yudao-module-infra-api/pom.xml
  93. 16 6
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java
  94. 21 0
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiAccessLogApi.java
  95. 21 0
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiErrorLogApi.java
  96. 2 2
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/dto/ApiAccessLogCreateReqDTO.java
  97. 3 4
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/dto/ApiErrorLogCreateReqDTO.java
  98. 2 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java
  99. 27 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiAccessLogApiImpl.java
  100. 27 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiErrorLogApiImpl.java

+ 9 - 10
.gitee/ISSUE_TEMPLATE.zh-CN.md

@@ -1,9 +1,17 @@
+碰到问题,请在 <https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues> 搜索是否存在相似的 issue。
+
+不按照模板提交的 issue,会被系统自动删除。
+
 ### 基本信息
 
 - ruoyi-vue-pro 版本:
 - 操作系统:
 - 数据库:
 
+### 你猜测可能的原因
+
+(必填)我花费了 2-4 小时自查,发现可能的原因是:xxxxxx
+
 ### 复现步骤
 
 第一步,
@@ -14,13 +22,4 @@
 
 ### 报错信息
 
-[截图]
-
-[截图]
-
----
-
-建议:项目不是很难,碰到问题时,建议先花 2-4 小时的时间进行自查。
-
-Issue 中如果带上自己的分析,可以被更快更高效的解决。
-
+带上必要的截图

+ 9 - 9
.github/ISSUE_TEMPLATE/question.md

@@ -7,12 +7,20 @@ assignees: ''
 
 ---
 
+碰到问题,请在 <https://github.com/YunaiV/ruoyi-vue-pro/issues> 搜索是否存在相似的 issue。
+
+不按照模板提交的 issue,会被系统自动删除。
+
 ### 基本信息
 
 - ruoyi-vue-pro 版本:
 - 操作系统:
 - 数据库:
 
+### 你猜测可能的原因
+
+(必填)我花费了 2-4 小时自查,发现可能的原因是:xxxxxx
+
 ### 复现步骤
 
 第一步,
@@ -23,12 +31,4 @@ assignees: ''
 
 ### 报错信息
 
-[截图]
-
-[截图]
-
----
-
-建议:项目不是很难,碰到问题时,建议先花 2-4 小时的时间进行自查。
-
-Issue 中如果带上自己的分析,可以被更快更高效的解决。
+带上必要的截图

+ 39 - 40
README.md

@@ -157,27 +157,27 @@ ps:核心功能已经实现,正在对接微信小程序中...
 
 | 框架                                                                                          | 说明               | 版本       | 学习指南                                                           |
 |---------------------------------------------------------------------------------------------|------------------|----------|----------------------------------------------------------------|
-| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架           | 2.5.12   | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
+| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架           | 2.6.8   | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
 | [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器           | 5.7      |                                                                |
 | [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件    | 1.2.8    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
-| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包    | 3.5.1    | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
+| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包    | 3.5.2    | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
 | [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源            | 3.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | [Redis](https://redis.io/)                                                                  | key-value 数据库    | 5.0      |                                                                |
-| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端        | 3.16.8   | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
-| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架           | 5.3.16   | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao)               |
-| [Spring Security](https://github.com/spring-projects/spring-security)                       | Spring 安全框架      | 5.5.5    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
-| [Hibernate Validator](https://github.com/hibernate/hibernate-validator)                     | 参数校验组件           | 6.2.2    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao)      |
+| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端        | 3.17.3   | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
+| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架           | 5.3.20   | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao)               |
+| [Spring Security](https://github.com/spring-projects/spring-security)                       | Spring 安全框架      | 5.6.5    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
+| [Hibernate Validator](https://github.com/hibernate/hibernate-validator)                     | 参数校验组件           | 6.2.3    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao)      |
 | [Flowable](https://github.com/flowable/flowable-engine)                                            | 工作流引擎            | 6.7.0 | [文档](https://doc.iocoder.cn/bpm/)                                                     |
 | [Quartz](https://github.com/quartz-scheduler)                                               | 任务调度组件           | 2.3.2    | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao)             |
-| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现 | 3.0.2    | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
-| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件           | 1.7.0    | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
+| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现 | 3.0.3    | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
+| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件           | 1.7.1    | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
 | [SkyWalking](https://skywalking.apache.org/)                                                | 分布式应用追踪系统        | 8.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao)      |
-| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台 | 2.4.2    | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
-| [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库         | 2.12.6   |                                                                |
+| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台 | 2.6.7    | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
+| [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库         | 2.13.3   |                                                                |
 | [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换     | 1.4.1    | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
 | [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码    | 1.16.14  | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
-| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架      | 5.7.2    | -                                                              |
-| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架     | 3.9.0    | -                                                              |
+| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架      | 5.8.2    | -                                                              |
+| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架     | 4.0.0    | -                                                              |
 
 ### 前端
 
@@ -192,42 +192,41 @@ ps:核心功能已经实现,正在对接微信小程序中...
 
 | 模块       | biu                                                                | biu                                                              | biu                                                              |
 |----------|--------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|
-| 登录 & 首页  | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg)       | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg)     | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg) |
-| 用户       | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg)   | ![在线用户](https://static.iocoder.cn/images/ruoyi-vue-pro/在线用户.jpg) | -                                                                |
-| 租户 & 套餐  | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg)   | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | -                                                                |
-| 部门 & 岗位  | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg)   | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg) | -                                                                |
-| 菜单 & 角色  | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg)   | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg) | -                                                                |
-| 审计日志     | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg)   | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg) | -                                                                |
-| 短信       | ![短信渠道](https://static.iocoder.cn/images/ruoyi-vue-pro/短信渠道.jpg)   | ![短信模板](https://static.iocoder.cn/images/ruoyi-vue-pro/短信模板.jpg) | ![短信日志](https://static.iocoder.cn/images/ruoyi-vue-pro/短信日志.jpg) |
-| 字典       | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg)   | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg) | -                                                                |
-| 错误码 & 通知 | ![错误码管理](https://static.iocoder.cn/images/ruoyi-vue-pro/错误码管理.jpg) | ![通知公告](https://static.iocoder.cn/images/ruoyi-vue-pro/通知公告.jpg) | -                                                                |
+| 登录 & 首页  | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg?imageView2/2/format/webp/w/1280)       | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg?imageView2/2/format/webp/w/1280)     | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg?imageView2/2/format/webp/w/1280) |
+| 用户       | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg?imageView2/2/format/webp/w/1280)   | ![在线用户](https://static.iocoder.cn/images/ruoyi-vue-pro/在线用户.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 租户 & 套餐  | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg?imageView2/2/format/webp/w/1280)   | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | -                                                                |
+| 部门 & 岗位  | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg?imageView2/2/format/webp/w/1280)   | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 菜单 & 角色  | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg?imageView2/2/format/webp/w/1280)   | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 审计日志     | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg?imageView2/2/format/webp/w/1280)   | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 短信       | ![短信渠道](https://static.iocoder.cn/images/ruoyi-vue-pro/短信渠道.jpg?imageView2/2/format/webp/w/1280)   | ![短信模板](https://static.iocoder.cn/images/ruoyi-vue-pro/短信模板.jpg?imageView2/2/format/webp/w/1280) | ![短信日志](https://static.iocoder.cn/images/ruoyi-vue-pro/短信日志.jpg?imageView2/2/format/webp/w/1280) |
+| 字典       | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg?imageView2/2/format/webp/w/1280)   | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 错误码 & 通知 | ![错误码管理](https://static.iocoder.cn/images/ruoyi-vue-pro/错误码管理.jpg?imageView2/2/format/webp/w/1280) | ![通知公告](https://static.iocoder.cn/images/ruoyi-vue-pro/通知公告.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
 
 ### 工作流程
 
 | 模块      | biu                                                                    | biu                                                                    | biu                                                                    |
 |---------|------------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
-| 流程模型    | ![流程模型-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-列表.jpg) | ![流程模型-设计](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-设计.jpg) | ![流程模型-定义](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-定义.jpg) |
-| 表单 & 分组 | ![流程表单](https://static.iocoder.cn/images/ruoyi-vue-pro/流程表单.jpg)       | ![用户分组](https://static.iocoder.cn/images/ruoyi-vue-pro/用户分组.jpg)       | -                                                                      |
-| 我的流程    | ![我的流程-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-列表.jpg) | ![我的流程-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-发起.jpg) | ![我的流程-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-详情.jpg) |
-| 待办 & 已办 | ![任务列表-审批](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-审批.jpg) | ![任务列表-待办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-待办.jpg) | ![任务列表-已办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-已办.jpg) |
-| OA 请假   | ![OA请假-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-列表.jpg) | ![OA请假-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-发起.jpg) | ![OA请假-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-详情.jpg) |
-
-### 支付系统
-
-| 模块      | biu                                                              | biu                                                                    | biu                                                                    |
-|---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
-| 商家 & 应用 | ![商户信息](https://static.iocoder.cn/images/ruoyi-vue-pro/商户信息.jpg) | ![应用信息-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-列表.jpg) | ![应用信息-编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-编辑.jpg) |
-| 支付 & 退款 | ![支付订单](https://static.iocoder.cn/images/ruoyi-vue-pro/支付订单.jpg) | ![退款订单](https://static.iocoder.cn/images/ruoyi-vue-pro/退款订单.jpg)       | ---                                                                    |
+| 流程模型    | ![流程模型-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-列表.jpg?imageView2/2/format/webp/w/1280) | ![流程模型-设计](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-设计.jpg?imageView2/2/format/webp/w/1280) | ![流程模型-定义](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-定义.jpg?imageView2/2/format/webp/w/1280) |
+| 表单 & 分组 | ![流程表单](https://static.iocoder.cn/images/ruoyi-vue-pro/流程表单.jpg?imageView2/2/format/webp/w/1280)       | ![用户分组](https://static.iocoder.cn/images/ruoyi-vue-pro/用户分组.jpg?imageView2/2/format/webp/w/1280)       | -                                                                      |
+| 我的流程    | ![我的流程-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-列表.jpg?imageView2/2/format/webp/w/1280) | ![我的流程-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-发起.jpg?imageView2/2/format/webp/w/1280) | ![我的流程-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-详情.jpg?imageView2/2/format/webp/w/1280) |
+| 待办 & 已办 | ![任务列表-审批](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-审批.jpg?imageView2/2/format/webp/w/1280) | ![任务列表-待办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-待办.jpg?imageView2/2/format/webp/w/1280) | ![任务列表-已办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-已办.jpg?imageView2/2/format/webp/w/1280) |
+| OA 请假   | ![OA请假-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-列表.jpg?imageView2/2/format/webp/w/1280) | ![OA请假-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-发起.jpg?imageView2/2/format/webp/w/1280) | ![OA请假-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-详情.jpg?imageView2/2/format/webp/w/1280) |
 
 ### 基础设施
 
 | 模块            | biu                                                                  | biu                                                                | biu                                                              |
 |---------------|----------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------|
-| 代码生成          | ![代码生成](https://static.iocoder.cn/images/ruoyi-vue-pro/代码生成.jpg)     | ![生成效果](https://static.iocoder.cn/images/ruoyi-vue-pro/生成效果.jpg)   | -                                                                |
-| 文档            | ![系统接口](https://static.iocoder.cn/images/ruoyi-vue-pro/系统接口.jpg)     | ![数据库文档](https://static.iocoder.cn/images/ruoyi-vue-pro/数据库文档.jpg) | -                                                                |
-| 文件 & 配置       | ![文件配置](https://static.iocoder.cn/images/ruoyi-vue-pro/文件配置.jpg) | ![文件管理](https://static.iocoder.cn/images/ruoyi-vue-pro/文件管理2.jpg)     | ![配置管理](https://static.iocoder.cn/images/ruoyi-vue-pro/配置管理.jpg)   |
-| 定时任务          | ![定时任务](https://static.iocoder.cn/images/ruoyi-vue-pro/定时任务.jpg)     | ![任务日志](https://static.iocoder.cn/images/ruoyi-vue-pro/任务日志.jpg)   | -                                                                |
-| API 日志        | ![访问日志](https://static.iocoder.cn/images/ruoyi-vue-pro/访问日志.jpg)     | ![错误日志](https://static.iocoder.cn/images/ruoyi-vue-pro/错误日志.jpg)   | -                                                                |
-| MySQL & Redis | ![MySQL](https://static.iocoder.cn/images/ruoyi-vue-pro/MySQL.jpg)   | ![Redis](https://static.iocoder.cn/images/ruoyi-vue-pro/Redis.jpg) | -                                                                |
-| 监控平台          | ![Java监控](https://static.iocoder.cn/images/ruoyi-vue-pro/Java监控.jpg) | ![链路追踪](https://static.iocoder.cn/images/ruoyi-vue-pro/链路追踪.jpg)   | ![日志中心](https://static.iocoder.cn/images/ruoyi-vue-pro/日志中心.jpg) |
+| 代码生成          | ![代码生成](https://static.iocoder.cn/images/ruoyi-vue-pro/代码生成.jpg?imageView2/2/format/webp/w/1280)     | ![生成效果](https://static.iocoder.cn/images/ruoyi-vue-pro/生成效果.jpg?imageView2/2/format/webp/w/1280)   | -                                                                |
+| 文档            | ![系统接口](https://static.iocoder.cn/images/ruoyi-vue-pro/系统接口.jpg?imageView2/2/format/webp/w/1280)     | ![数据库文档](https://static.iocoder.cn/images/ruoyi-vue-pro/数据库文档.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 文件 & 配置       | ![文件配置](https://static.iocoder.cn/images/ruoyi-vue-pro/文件配置.jpg?imageView2/2/format/webp/w/1280) | ![文件管理](https://static.iocoder.cn/images/ruoyi-vue-pro/文件管理2.jpg?imageView2/2/format/webp/w/1280)     | ![配置管理](https://static.iocoder.cn/images/ruoyi-vue-pro/配置管理.jpg?imageView2/2/format/webp/w/1280)   |
+| 定时任务          | ![定时任务](https://static.iocoder.cn/images/ruoyi-vue-pro/定时任务.jpg?imageView2/2/format/webp/w/1280)     | ![任务日志](https://static.iocoder.cn/images/ruoyi-vue-pro/任务日志.jpg?imageView2/2/format/webp/w/1280)   | -                                                                |
+| API 日志        | ![访问日志](https://static.iocoder.cn/images/ruoyi-vue-pro/访问日志.jpg?imageView2/2/format/webp/w/1280)     | ![错误日志](https://static.iocoder.cn/images/ruoyi-vue-pro/错误日志.jpg?imageView2/2/format/webp/w/1280)   | -                                                                |
+| MySQL & Redis | ![MySQL](https://static.iocoder.cn/images/ruoyi-vue-pro/MySQL.jpg?imageView2/2/format/webp/w/1280)   | ![Redis](https://static.iocoder.cn/images/ruoyi-vue-pro/Redis.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
+| 监控平台          | ![Java监控](https://static.iocoder.cn/images/ruoyi-vue-pro/Java监控.jpg?imageView2/2/format/webp/w/1280) | ![链路追踪](https://static.iocoder.cn/images/ruoyi-vue-pro/链路追踪.jpg?imageView2/2/format/webp/w/1280)   | ![日志中心](https://static.iocoder.cn/images/ruoyi-vue-pro/日志中心.jpg?imageView2/2/format/webp/w/1280) |
 
+### 支付系统
+
+| 模块      | biu                                                              | biu                                                                    | biu                                                                    |
+|---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
+| 商家 & 应用 | ![商户信息](https://static.iocoder.cn/images/ruoyi-vue-pro/商户信息.jpg?imageView2/2/format/webp/w/1280) | ![应用信息-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-列表.jpg?imageView2/2/format/webp/w/1280) | ![应用信息-编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-编辑.jpg?imageView2/2/format/webp/w/1280) |
+| 支付 & 退款 | ![支付订单](https://static.iocoder.cn/images/ruoyi-vue-pro/支付订单.jpg?imageView2/2/format/webp/w/1280) | ![退款订单](https://static.iocoder.cn/images/ruoyi-vue-pro/退款订单.jpg?imageView2/2/format/webp/w/1280)       | ---                                                                    |

+ 9 - 0
http-client.env.json

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

+ 1 - 0
pom.xml

@@ -18,6 +18,7 @@
         <module>yudao-module-system</module>
         <module>yudao-module-infra</module>
         <module>yudao-module-pay</module>
+        <module>yudao-module-mall</module>
     </modules>
 
     <name>${project.artifactId}</name>

+ 287 - 0
sql/mall.sql

@@ -0,0 +1,287 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server         : 127.0.0.1
+ Source Server Type    : MySQL
+ Source Server Version : 80026
+ Source Host           : localhost:3306
+ Source Schema         : ruoyi-vue-pro
+
+ Target Server Type    : MySQL
+ Target Server Version : 80026
+ File Encoding         : 65001
+
+ Date: 05/02/2022 00:50:30
+*/
+SET
+FOREIGN_KEY_CHECKS = 0;
+SET NAMES utf8mb4;
+
+-- ----------------------------
+-- Table structure for product_category
+-- ----------------------------
+DROP TABLE IF EXISTS `product_category`;
+CREATE TABLE `product_category`
+(
+    `id`          bigint       NOT NULL AUTO_INCREMENT COMMENT '分类编号',
+    `parent_id`   bigint       NOT NULL COMMENT '父分类编号',
+    `name`        varchar(255) NOT NULL COMMENT '分类名称',
+    `icon`        varchar(100) NOT NULL DEFAULT '#' COMMENT '分类图标',
+    `banner_url`  varchar(255) NOT NULL COMMENT '分类图片',
+    `sort`        int                   DEFAULT '0' COMMENT '分类排序',
+    `description` varchar(1024)         DEFAULT NULL COMMENT '分类描述',
+    `status`      tinyint      NOT NULL COMMENT '开启状态',
+    `creator`     varchar(64)           DEFAULT '' COMMENT '创建者',
+    `create_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`     varchar(64)           DEFAULT '' COMMENT '更新者',
+    `update_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`     bit(1)       NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`   bigint       NOT NULL DEFAULT '0' COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='商品分类';
+
+-- ----------------------------
+-- Table structure for product_brand
+-- ----------------------------
+DROP TABLE IF EXISTS `product_brand`;
+CREATE TABLE `product_brand`
+(
+    `id`          bigint       NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
+    `category_id` bigint       NOT NULL COMMENT '分类编号',
+    `name`        varchar(255) NOT NULL COMMENT '品牌名称',
+    `banner_url`  varchar(255) NOT NULL COMMENT '品牌图片',
+    `sort`        int                   DEFAULT '0' COMMENT '品牌排序',
+    `description` varchar(1024)         DEFAULT NULL COMMENT '品牌描述',
+    `status`      tinyint      NOT NULL COMMENT '状态',
+    `creator`     varchar(64)           DEFAULT '' COMMENT '创建者',
+    `create_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`     varchar(64)           DEFAULT '' COMMENT '更新者',
+    `update_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`     bit(1)       NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`   bigint       NOT NULL DEFAULT '0' COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='品牌';
+
+-- TODO 父级菜单的 id 处理: 2000 、 2001
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES (2000, '商城', '', 1, 1, 0, '/mall', 'merchant', NULL, 0);
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES (2001, '商品', '', 1, 1, 2000, 'product', 'dict', NULL, 0);
+-- 商品分类 菜单 SQL
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('分类管理', '', 2, 0, 2001, 'category', '', 'mall/product/category/index', 0);
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('分类查询', 'product:category:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('分类创建', 'product:category:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('分类更新', 'product:category:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('分类删除', 'product:category:delete', 3, 4, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('分类导出', 'product:category:export', 3, 5, @parentId, '', '', '', 0);
+-- 品牌管理 菜单 SQL
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('品牌管理', '', 2, 1, 2001, 'brand', '', 'mall/product/brand/index', 0);
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('品牌查询', 'product:brand:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('品牌创建', 'product:brand:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('品牌更新', 'product:brand:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('品牌删除', 'product:brand:delete', 3, 4, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('品牌导出', 'product:brand:export', 3, 5, @parentId, '', '', '', 0);
+
+
+-- ----------------------------
+-- Table structure for market_activity
+-- ----------------------------
+DROP TABLE IF EXISTS `market_activity`;
+CREATE TABLE `market_activity`
+(
+    `id`                    bigint      NOT NULL AUTO_INCREMENT COMMENT '活动编号',
+    `title`                 varchar(50) NOT NULL DEFAULT '' COMMENT '活动标题',
+    `activity_type`         tinyint(4) NOT NULL COMMENT '活动类型',
+    `status`                tinyint(4) NOT NULL DEFAULT '-1' COMMENT '活动状态',
+    `start_time`            datetime    NOT NULL COMMENT '开始时间',
+    `end_time`              datetime    NOT NULL COMMENT '结束时间',
+    `invalid_time`          datetime             DEFAULT NULL COMMENT '失效时间',
+    `delete_time`           datetime             DEFAULT NULL COMMENT '删除时间',
+    `time_limited_discount` varchar(2000)        DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
+    `full_privilege`        varchar(2000)        DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
+    `creator`               varchar(64)          DEFAULT '' COMMENT '创建者',
+    `create_time`           datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`               varchar(64)          DEFAULT '' COMMENT '更新者',
+    `update_time`           datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`               bit(1)      NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`             bigint      NOT NULL DEFAULT '0' COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='促销活动';
+
+
+-- 规格菜单 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('规格管理', '', 2, 3, 2001, 'property', '', 'mall/product/property/index', 0);
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('规格查询', 'product:property:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('规格创建', 'product:property:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('规格更新', 'product:property:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('规格删除', 'product:property:delete', 3, 4, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('规格导出', 'product:property:export', 3, 5, @parentId, '', '', '', 0);
+
+
+-- 商品菜单 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('商品管理', '', 2, 2, 2001, 'spu', '', 'mall/product/spu/index', 0);
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('商品查询', 'product:spu:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('商品创建', 'product:spu:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('商品更新', 'product:spu:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('商品删除', 'product:spu:delete', 3, 4, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('商品导出', 'product:spu:export', 3, 5, @parentId, '', '', '', 0);
+
+
+-- 规格名称表
+drop table if exists product_property;
+create table product_property
+(
+    id          bigint NOT NULL AUTO_INCREMENT comment '主键',
+    name        varchar(64) comment '规格名称',
+    status      tinyint comment '状态: 0 开启 ,1 禁用',
+    create_time datetime        default current_timestamp comment '创建时间',
+    update_time datetime        default current_timestamp on update current_timestamp comment '更新时间',
+    creator     varchar(64) comment '创建人',
+    updater     varchar(64) comment '更新人',
+    tenant_id   bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
+    deleted   bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    primary key (id),
+    key         idx_name ( name (32)) comment '规格名称索引'
+) comment '规格名称' character set utf8mb4
+                 collate utf8mb4_general_ci;
+
+-- 规格值表
+drop table if exists product_property_value;
+create table product_property_value
+(
+    id          bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+    property_id bigint comment '规格键id',
+    name        varchar(128) comment '规格值名字',
+    status      tinyint comment '状态: 1 开启 ,2 禁用',
+    create_time datetime        default current_timestamp comment '创建时间',
+    update_time datetime        default current_timestamp on update current_timestamp comment '更新时间',
+    creator     varchar(64) comment '创建人',
+    updater     varchar(64) comment '更新人',
+    tenant_id   bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
+    deleted   bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    primary key (id)
+) comment '规格值' character set utf8mb4
+                collate utf8mb4_general_ci;
+
+-- spu
+drop table if exists product_spu;
+create table product_spu
+(
+    id          bigint        NOT NULL AUTO_INCREMENT COMMENT '主键',
+    name        varchar(128) comment '商品名称',
+    sell_point  varchar(128)  not null comment '卖点',
+    description text          not null comment '描述',
+    category_id bigint        not null comment '分类id',
+    pic_urls    varchar(1024) not null default '' comment '商品主图地址\n     *\n     * 数组,以逗号分隔\n 最多上传15张',
+    sort        int           not null default 0 comment '排序字段',
+    like_count  int comment '点赞初始人数',
+    price       int comment '价格 单位使用:分',
+    quantity    int comment '库存数量',
+    status      bit(1) comment '上下架状态: 0 上架(开启) 1 下架(禁用)',
+    create_time datetime               default current_timestamp comment '创建时间',
+    update_time datetime               default current_timestamp on update current_timestamp comment '更新时间',
+    creator     varchar(64) comment '创建人',
+    updater     varchar(64) comment '更新人',
+    tenant_id   bigint        NOT NULL DEFAULT '0' COMMENT '租户编号',
+    deleted   bit(1)        NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    primary key (id)
+) comment '商品spu' character set utf8mb4
+                  collate utf8mb4_general_ci;
+
+
+-- sku
+drop table if exists product_sku;
+create table product_sku
+(
+    id             bigint       NOT NULL AUTO_INCREMENT COMMENT '主键',
+    spu_id         bigint       not null comment 'spu编号',
+    properties     varchar(64)  not null comment '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
+    price          int          not null DEFAULT -1 comment '销售价格,单位:分',
+    original_price int          not null DEFAULT -1 comment '原价, 单位: 分',
+    cost_price     int          not null DEFAULT -1 comment '成本价,单位: 分',
+    bar_code       varchar(64)  not null comment '条形码',
+    pic_url        VARCHAR(128) not null comment '图片地址',
+    status         tinyint comment '状态: 0-正常 1-禁用',
+    create_time    datetime              default current_timestamp comment '创建时间',
+    update_time    datetime              default current_timestamp on update current_timestamp comment '更新时间',
+    creator        varchar(64) comment '创建人',
+    updater        varchar(64) comment '更新人',
+    tenant_id      bigint       NOT NULL DEFAULT '0' COMMENT '租户编号',
+    deleted      bit(1)       NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    primary key (id)
+) comment '商品sku' character set utf8mb4
+                  collate utf8mb4_general_ci;
+
+
+---Market-Banner管理SQL
+drop table if exists market_banner;
+CREATE TABLE `market_banner` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Banner编号',
+  `title` varchar(64) NOT NULL DEFAULT '' COMMENT 'Banner标题',
+  `pic_url` varchar(255) NOT NULL COMMENT '图片URL',
+  `status` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '活动状态',
+  `url` varchar(255) NOT NULL COMMENT '跳转地址',
+  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+  `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号',
+  `sort` tinyint(4) DEFAULT NULL COMMENT '排序',
+  `memo` varchar(255) DEFAULT NULL COMMENT '描述',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='Banner管理';
+-- 菜单 SQL
+INSERT INTO `system_menu`(`id`,`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES (2002, 'Banner管理', '', 2, 1, 2000, 'brand', '', 'mall/market/banner/index', 0);
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('Banner查询', 'market:banner:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('Banner创建', 'market:banner:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('Banner更新', 'market:banner:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
+VALUES ('Banner删除', 'market:banner:delete', 3, 4, @parentId, '', '', '', 0);

+ 130 - 47
sql/mysql/ruoyi-vue-pro.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80026
  File Encoding         : 65001
 
- Date: 13/05/2022 00:25:55
+ Date: 15/06/2022 08:11:25
 */
 
 SET NAMES utf8mb4;
@@ -300,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 = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义';
 
 -- ----------------------------
 -- Records of bpm_form
@@ -359,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 = 98 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 119 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
 
 -- ----------------------------
 -- Records of bpm_process_definition_ext
@@ -389,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 = 201 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
+) ENGINE = InnoDB AUTO_INCREMENT = 256 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
 
 -- ----------------------------
 -- Records of bpm_process_instance_ext
@@ -415,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 = 192 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
+) ENGINE = InnoDB AUTO_INCREMENT = 225 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
 
 -- ----------------------------
 -- Records of bpm_task_assign_rule
@@ -431,6 +431,7 @@ CREATE TABLE `bpm_task_ext`  (
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
   `assignee_user_id` bigint NULL DEFAULT NULL COMMENT '任务的审批人',
   `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '任务的名字',
+  `task_def_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程任务key',
   `task_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务的编号',
   `result` tinyint NOT NULL COMMENT '任务的结果',
   `reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '审批建议',
@@ -444,7 +445,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 = 214 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
+) ENGINE = InnoDB AUTO_INCREMENT = 295 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
 
 -- ----------------------------
 -- Records of bpm_task_ext
@@ -504,7 +505,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 = 29228 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 35150 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
 
 -- ----------------------------
 -- Records of infra_api_access_log
@@ -546,7 +547,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 = 429 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 627 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
 
 -- ----------------------------
 -- Records of infra_api_error_log
@@ -673,7 +674,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
@@ -688,6 +689,7 @@ DROP TABLE IF EXISTS `infra_file`;
 CREATE TABLE `infra_file`  (
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '文件编号',
   `config_id` bigint NULL DEFAULT NULL COMMENT '配置编号',
+  `name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件名',
   `path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径',
   `url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件 URL',
   `type` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型',
@@ -698,7 +700,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 = 39 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 85 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file
@@ -723,7 +725,7 @@ CREATE TABLE `infra_file_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 = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件配置表';
+) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件配置表';
 
 -- ----------------------------
 -- Records of infra_file_config
@@ -732,6 +734,9 @@ BEGIN;
 INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2022-03-26 21:39:26', b'0');
 INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5, '本地磁盘', 10, '测试下本地存储', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.local.LocalFileClientConfig\",\"basePath\":\"/Users/yunai/file_test\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:57:00', '1', '2022-03-26 21:39:26', b'0');
 INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (11, 'S3 - 七牛云', 20, NULL, b'1', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-03-19 18:00:03', '1', '2022-03-26 21:39:26', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (15, 'S3 - 七牛云', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-06-10 20:50:41', '1', '2022-06-10 20:50:41', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (16, 'S3 - 七牛云', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-06-11 20:32:08', '1', '2022-06-11 20:32:08', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (17, 'S3 - 七牛云', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-06-11 20:32:47', '1', '2022-06-11 20:32:47', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -862,7 +867,7 @@ CREATE TABLE `member_user`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   UNIQUE INDEX `uk_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
-) ENGINE = InnoDB AUTO_INCREMENT = 247 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户';
+) ENGINE = InnoDB AUTO_INCREMENT = 248 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户';
 
 -- ----------------------------
 -- Records of member_user
@@ -1153,11 +1158,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);
@@ -1186,7 +1191,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 = 1161 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
@@ -1258,7 +1263,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 (79, 2, '手动编辑', '2', 'system_error_code_type', 0, 'primary', '', NULL, '1', '2021-04-21 00:07:14', '1', '2022-02-16 13:57:24', 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 (80, 100, '账号登录', '100', 'system_login_type', 0, 'primary', '', '账号登录', '1', '2021-10-06 00:52:02', '1', '2022-02-16 13:11:34', 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 (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 (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');
@@ -1346,13 +1350,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 = 148 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');
@@ -1360,7 +1364,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');
@@ -1411,7 +1415,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 = 5458 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5832 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
 
 -- ----------------------------
 -- Records of system_error_code
@@ -1440,7 +1444,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 = 1341 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 1489 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
 
 -- ----------------------------
 -- Records of system_login_log
@@ -1744,10 +1748,11 @@ 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 '刷新令牌',
-  `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 '创建时间',
@@ -1756,7 +1761,7 @@ CREATE TABLE `system_oauth2_access_token`  (
   `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 = 56 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 231 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_access_token
@@ -1764,6 +1769,33 @@ CREATE TABLE `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
 -- ----------------------------
@@ -1779,9 +1811,9 @@ CREATE TABLE `system_oauth2_client`  (
   `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 地址',
-  `auto_approve` bit(1) NOT NULL COMMENT '是否自动授权',
   `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 '附加信息',
@@ -1797,8 +1829,37 @@ CREATE TABLE `system_oauth2_client`  (
 -- 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`, `auto_approve`, `authorized_grant_types`, `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\"]', b'1', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\"]', '[\"user_info\"]', '[\"system:user:query\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2022-05-12 01:00:20', 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`, `auto_approve`, `authorized_grant_types`, `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\"]', b'1', '[\"password\",\"authorization_code\",\"implicit\"]', '[]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2022-05-12 00:59:53', 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 (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;
 
 -- ----------------------------
@@ -1811,6 +1872,7 @@ CREATE TABLE `system_oauth2_refresh_token`  (
   `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 '创建时间',
@@ -1819,7 +1881,7 @@ CREATE TABLE `system_oauth2_refresh_token`  (
   `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 = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 165 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '刷新令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_refresh_token
@@ -1859,7 +1921,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 = 2097 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 2432 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
 
 -- ----------------------------
 -- Records of system_operate_log
@@ -1917,7 +1979,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
@@ -1925,10 +1987,11 @@ CREATE TABLE `system_role`  (
 BEGIN;
 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 (1, '超级管理员', 'super_admin', 1, 1, '', 0, 1, '超级管理员', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:21', b'0', 1);
 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 (2, '普通角色', 'common', 2, 2, '', 0, 1, '普通角色', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:20', b'0', 1);
-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 (101, '测试账号', 'test', 0, 1, '[]', 0, 2, '132', '', '2021-01-06 13:49:35', '1', '2022-04-01 21:37:13', b'0', 1);
+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 (101, '测试账号', 'test', 0, 1, '[]', 0, 2, '132', '', '2021-01-06 13:49:35', '1', '2022-05-28 16:11:05', b'0', 1);
 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;
 
 -- ----------------------------
@@ -1946,7 +2009,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
@@ -2143,6 +2206,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;
 
 -- ----------------------------
@@ -2177,7 +2257,7 @@ COMMIT;
 DROP TABLE IF EXISTS `system_sms_channel`;
 CREATE TABLE `system_sms_channel`  (
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
-  `signature` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信签名',
+  `signature` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信签名',
   `code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道编码',
   `status` tinyint NOT NULL COMMENT '开启状态',
   `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
@@ -2199,7 +2279,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;
 
 -- ----------------------------
@@ -2224,7 +2304,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 = 469 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
 
 -- ----------------------------
 -- Records of system_sms_code
@@ -2267,7 +2347,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 = 140 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 242 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
 
 -- ----------------------------
 -- Records of system_sms_log
@@ -2337,7 +2417,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 = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
 
 -- ----------------------------
 -- Records of system_social_user
@@ -2362,7 +2442,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 = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
 
 -- ----------------------------
 -- Records of system_social_user_bind
@@ -2391,14 +2471,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;
 
@@ -2442,7 +2522,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
@@ -2451,6 +2531,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;
 
 -- ----------------------------
@@ -2468,7 +2549,7 @@ CREATE TABLE `system_user_role`  (
   `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 = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
+) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
 
 -- ----------------------------
 -- Records of system_user_role
@@ -2479,7 +2560,6 @@ 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 (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);
@@ -2489,6 +2569,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 (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);
+INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (20, 104, 101, '1', '2022-05-28 15:43:57', '1', '2022-05-28 15:43:57', b'0', 1);
 COMMIT;
 
 -- ----------------------------
@@ -2546,16 +2628,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-13 00:12:13', 'admin', '2021-01-05 17:03:47', NULL, '2022-05-13 00:12:13', 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-06-15 08:03:23', 'admin', '2021-01-05 17:03:47', NULL, '2022-06-15 08:03:23', 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$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1]', '111@qq.com', '15601691200', 1, '', 0, '127.0.0.1', '2022-05-28 15:43:17', '', '2021-01-21 02:13:53', NULL, '2022-05-28 15:43:17', 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);
@@ -2564,7 +2646,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;

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 285 - 145
sql/oracle/ruoyi-vue-pro.sql


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 305 - 330
sql/postgresql/ruoyi-vue-pro.sql


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 3544 - 1
sql/sqlserver/ruoyi-vue-pro.sql


+ 29 - 8
yudao-dependencies/pom.xml

@@ -16,31 +16,31 @@
     <properties>
         <revision>1.6.2-snapshot</revision>
         <!-- 统一依赖管理 -->
-        <spring.boot.version>2.5.12</spring.boot.version>
+        <spring.boot.version>2.6.8</spring.boot.version>
         <!-- Web 相关 -->
-        <knife4j.version>3.0.2</knife4j.version>
-        <swagger-annotations.version>1.5.22</swagger-annotations.version>
+        <knife4j.version>3.0.3</knife4j.version>
+        <swagger-annotations.version>1.6.6</swagger-annotations.version>
         <servlet.versoin>2.5</servlet.versoin>
         <!-- DB 相关 -->
         <druid.version>1.2.8</druid.version>
-        <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
+        <mybatis-plus.version>3.5.2</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
         <dynamic-datasource.version>3.5.0</dynamic-datasource.version>
-        <redisson.version>3.17.0</redisson.version>
+        <redisson.version>3.17.3</redisson.version>
         <!-- Config 配置中心相关 -->
         <apollo.version>1.9.2</apollo.version>
         <!-- Job 定时任务相关 -->
         <!-- 服务保障相关 -->
         <lock4j.version>2.2.0</lock4j.version>
-        <resilience4j.version>1.7.0</resilience4j.version>
+        <resilience4j.version>1.7.1</resilience4j.version>
         <!-- 监控相关 -->
         <skywalking.version>8.7.0</skywalking.version>
-        <spring-boot-admin.version>2.6.2</spring-boot-admin.version>
+        <spring-boot-admin.version>2.6.7</spring-boot-admin.version>
         <opentracing.version>0.31.0</opentracing.version>
         <!-- Test 测试相关 -->
         <podam.version>7.2.6.RELEASE</podam.version>
         <jedis-mock.version>0.1.16</jedis-mock.version>
-        <mockito-inline.version>3.9.0</mockito-inline.version>
+        <mockito-inline.version>4.0.0</mockito-inline.version>
         <!-- Bpm 工作流相关 -->
         <activiti.version>7.1.0.M6</activiti.version>
         <flowable.version>6.7.0</flowable.version>
@@ -52,6 +52,7 @@
         <easyexcel.verion>2.2.7</easyexcel.verion>
         <velocity.version>2.2</velocity.version>
         <screw.version>1.0.5</screw.version>
+		<fastjson.version>2.0.5</fastjson.version>
         <guava.version>30.1.1-jre</guava.version>
         <guice.version>5.1.0</guice.version>
         <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
@@ -78,6 +79,11 @@
             </dependency>
 
             <!-- 业务组件 -->
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-banner</artifactId>
+                <version>${revision}</version>
+            </dependency>
             <dependency>
                 <groupId>cn.iocoder.boot</groupId>
                 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
@@ -118,6 +124,11 @@
                 <artifactId>yudao-spring-boot-starter-biz-social</artifactId>
                 <version>${revision}</version>
             </dependency>
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
+                <version>${revision}</version>
+            </dependency>
 
             <!-- Spring 核心 -->
             <dependency>
@@ -475,6 +486,12 @@
                 <version>${velocity.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>cn.smallbun.screw</groupId>
                 <artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->
@@ -483,6 +500,10 @@
                     <exclusion>
                         <groupId>org.freemarker</groupId>
                         <artifactId>freemarker</artifactId> <!-- 移除 Freemarker 依赖,采用 Velocity 作为模板引擎 -->
+                    </exclusion>
+				    <exclusion>
+                        <groupId>com.alibaba</groupId>
+                        <artifactId>fastjson</artifactId> <!-- 最新版screw-core1.0.5依赖fastjson1.2.73存在漏洞,移除。 -->
                     </exclusion>
                 </exclusions>
             </dependency>

+ 5 - 0
yudao-framework/pom.xml

@@ -11,6 +11,7 @@
     <packaging>pom</packaging>
     <modules>
         <module>yudao-common</module>
+        <module>yudao-spring-boot-starter-banner</module>
         <module>yudao-spring-boot-starter-mybatis</module>
         <module>yudao-spring-boot-starter-redis</module>
         <module>yudao-spring-boot-starter-web</module>
@@ -29,11 +30,15 @@
         <module>yudao-spring-boot-starter-biz-operatelog</module>
         <module>yudao-spring-boot-starter-biz-dict</module>
         <module>yudao-spring-boot-starter-biz-sms</module>
+
         <module>yudao-spring-boot-starter-biz-pay</module>
         <module>yudao-spring-boot-starter-biz-weixin</module>
         <module>yudao-spring-boot-starter-biz-social</module>
         <module>yudao-spring-boot-starter-biz-tenant</module>
         <module>yudao-spring-boot-starter-biz-data-permission</module>
+        <module>yudao-spring-boot-starter-biz-error-code</module>
+
+        <module>yudao-spring-boot-starter-activiti</module>
         <module>yudao-spring-boot-starter-flowable</module>
     </modules>
 

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

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

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

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

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

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

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

@@ -36,9 +36,15 @@ public interface GlobalErrorCodeConstants {
 
     ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
 
-   static boolean isMatch(Integer code) {
+    /**
+     * 是否为服务端错误,参考 HTTP 5XX 错误码段
+     *
+     * @param code 错误码
+     * @return 是否
+     */
+   static boolean isServerErrorCode(Integer code) {
        return code != null
-               && code >= SUCCESS.getCode() && code <= UNKNOWN.getCode();
+               && code >= INTERNAL_SERVER_ERROR.getCode() && code <= INTERNAL_SERVER_ERROR.getCode() + 99;
    }
 
 }

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>yudao-framework</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-spring-boot-starter-banner</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>Banner 用于在 console 控制台,打印开发文档、接口文档等</description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

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

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

+ 4 - 4
yudao-server/src/main/java/cn/iocoder/yudao/server/framework/tip/core/TipApplicationRunner.java → yudao-framework/yudao-spring-boot-starter-banner/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java

@@ -1,19 +1,19 @@
-package cn.iocoder.yudao.server.framework.tip.core;
+package cn.iocoder.yudao.framework.banner.core;
 
 import cn.hutool.core.thread.ThreadUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.ApplicationRunner;
-import org.springframework.stereotype.Component;
 
 import java.util.concurrent.TimeUnit;
 
 /**
  * 项目启动成功后,提供文档相关的地址
+ *
+ * @author 芋道源码
  */
-@Component
 @Slf4j
-public class TipApplicationRunner implements ApplicationRunner {
+public class BannerApplicationRunner implements ApplicationRunner {
 
     @Override
     public void run(ApplicationArguments args) throws Exception {

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

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

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

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

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


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

@@ -13,7 +13,7 @@ import org.springframework.context.annotation.Configuration;
 import java.util.List;
 
 /**
- * 数据全新啊的自动配置类
+ * 数据权限的自动配置类
  *
  * @author 芋道源码
  */

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

@@ -62,7 +62,7 @@ public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
             // 调用
             interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
             // 断言
-            pluginUtilsMock.verify(never(), () -> PluginUtils.mpBoundSql(boundSql));
+            pluginUtilsMock.verify(() -> PluginUtils.mpBoundSql(boundSql), never());
         }
     }
 

+ 13 - 0
yudao-framework/yudao-spring-boot-starter-biz-dict/pom.xml

@@ -26,5 +26,18 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
+
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/config/YudaoDictAutoConfiguration.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.dict.config;
 
-import cn.iocoder.yudao.framework.dict.core.service.DictDataFrameworkService;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -10,8 +10,8 @@ public class YudaoDictAutoConfiguration {
 
     @Bean
     @SuppressWarnings("InstantiationOfUtilityClass")
-    public DictFrameworkUtils dictUtils(DictDataFrameworkService service) {
-        DictFrameworkUtils.init(service);
+    public DictFrameworkUtils dictUtils(DictDataApi dictDataApi) {
+        DictFrameworkUtils.init(dictDataApi);
         return new DictFrameworkUtils();
     }
 

+ 4 - 0
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/package-info.java

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

+ 0 - 35
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/service/DictDataFrameworkService.java

@@ -1,35 +0,0 @@
-package cn.iocoder.yudao.framework.dict.core.service;
-
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
-
-import java.util.List;
-
-public interface DictDataFrameworkService {
-
-    /**
-     * 获得指定的字典数据,从缓存中
-     *
-     * @param type 字典类型
-     * @param value 字典数据值
-     * @return 字典数据
-     */
-    DictDataRespDTO getDictDataFromCache(String type, String value);
-
-    /**
-     * 解析获得指定的字典数据,从缓存中
-     *
-     * @param type 字典类型
-     * @param label 字典数据标签
-     * @return 字典数据
-     */
-    DictDataRespDTO parseDictDataFromCache(String type, String label);
-
-    /**
-     * 获得指定类型的字典数据,从缓存中
-     *
-     * @param type 字典类型
-     * @return 字典数据列表
-     */
-    List<DictDataRespDTO> listDictDatasFromCache(String type);
-
-}

+ 51 - 9
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java

@@ -1,28 +1,70 @@
 package cn.iocoder.yudao.framework.dict.core.util;
 
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
-import cn.iocoder.yudao.framework.dict.core.service.DictDataFrameworkService;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
+import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
+import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
+import java.time.Duration;
+
 /**
  * 字典工具类
+ *
+ * @author 芋道源码
  */
 @Slf4j
 public class DictFrameworkUtils {
 
-    private static DictDataFrameworkService service;
+    private static DictDataApi dictDataApi;
+
+    private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
+
+    /**
+     * 针对 {@link #getDictDataLabel(String, String)} 的缓存
+     */
+    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> getDictDataCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
+
+                @Override
+                public DictDataRespDTO load(KeyValue<String, String> key) {
+                    return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
+                }
+
+            });
+
+    /**
+     * 针对 {@link #parseDictDataValue(String, String)} 的缓存
+     */
+    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> parseDictDataCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
+
+                @Override
+                public DictDataRespDTO load(KeyValue<String, String> key) {
+                    return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
+                }
+
+            });
 
-    public static void init(DictDataFrameworkService service) {
-        DictFrameworkUtils.service = service;
+    public static void init(DictDataApi dictDataApi) {
+        DictFrameworkUtils.dictDataApi = dictDataApi;
         log.info("[init][初始化 DictFrameworkUtils 成功]");
     }
 
-    public static DictDataRespDTO getDictDataFromCache(String type, String value) {
-        return service.getDictDataFromCache(type, value);
+    @SneakyThrows
+    public static String getDictDataLabel(String dictType, String value) {
+        return getDictDataCache.get(new KeyValue<>(dictType, value)).getLabel();
     }
 
-    public static DictDataRespDTO parseDictDataFromCache(String type, String label) {
-        return service.parseDictDataFromCache(type, label);
+    @SneakyThrows
+    public static String parseDictDataValue(String dictType, String label) {
+        return parseDictDataCache.get(new KeyValue<>(dictType, label)).getValue();
     }
 
 }

+ 49 - 0
yudao-framework/yudao-spring-boot-starter-biz-error-code/pom.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>yudao-framework</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        错误码 ErrorCode 的自动配置功能,提供如下功能:
+        1. 远程读取:项目启动时,从 system-server 服务,读取数据库中的 ErrorCode 错误码,实现错误码的提水可配置;
+        2. 自动更新:管理员在管理后台修数据库中的 ErrorCode 错误码时,项目自动从 system-server 服务加载最新的 ErrorCode 错误码;
+        3. 自动写入:项目启动时,将项目本地的错误码写到 system-server 服务中,方便管理员在管理后台编辑;
+    </description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+
+        <!-- Spring 核心 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行操作日志的记录 -->
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,主要是 ErrorCodeProperties 使用到 -->
+        </dependency>
+    </dependencies>
+
+</project>

+ 5 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/errorcode/config/ErrorCodeProperties.java → yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/config/ErrorCodeProperties.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.framework.errorcode.config;
+package cn.iocoder.yudao.framework.errorcode.config;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -17,6 +17,10 @@ import java.util.List;
 @Validated
 public class ErrorCodeProperties {
 
+    /**
+     * 是否开启
+     */
+    private Boolean enable = true;
     /**
      * 错误码枚举类
      */

+ 15 - 13
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/errorcode/config/ErrorCodeConfiguration.java → yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/config/YudaoErrorCodeConfiguration.java

@@ -1,37 +1,39 @@
-package cn.iocoder.yudao.module.system.framework.errorcode.config;
+package cn.iocoder.yudao.framework.errorcode.config;
 
-import cn.iocoder.yudao.module.system.framework.errorcode.core.generator.ErrorCodeAutoGenerator;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.loader.ErrorCodeLoader;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.service.ErrorCodeFrameworkService;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.loader.ErrorCodeLoaderImpl;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.generator.ErrorCodeAutoGeneratorImpl;
+import cn.iocoder.yudao.framework.errorcode.core.generator.ErrorCodeAutoGenerator;
+import cn.iocoder.yudao.framework.errorcode.core.generator.ErrorCodeAutoGeneratorImpl;
+import cn.iocoder.yudao.framework.errorcode.core.loader.ErrorCodeLoader;
+import cn.iocoder.yudao.framework.errorcode.core.loader.ErrorCodeLoaderImpl;
+import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
-// TODO 芋艿:貌似放的位置有问题
 /**
  * 错误码配置类
+ *
+ * @author 芋道源码
  */
 @Configuration
+@ConditionalOnProperty(prefix = "yudao.error-code", value = "enable", matchIfMissing = true) // 允许使用 yudao.error-code.enable=false 禁用访问日志
 @EnableConfigurationProperties(ErrorCodeProperties.class)
 @EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码
-public class ErrorCodeConfiguration {
+public class YudaoErrorCodeConfiguration {
 
     @Bean
     public ErrorCodeAutoGenerator errorCodeAutoGenerator(@Value("${spring.application.name}") String applicationName,
                                                          ErrorCodeProperties errorCodeProperties,
-                                                         ErrorCodeFrameworkService errorCodeFrameworkService) {
-        return new ErrorCodeAutoGeneratorImpl(applicationName, errorCodeProperties.getConstantsClassList(),
-                errorCodeFrameworkService);
+                                                         ErrorCodeApi errorCodeApi) {
+        return new ErrorCodeAutoGeneratorImpl(applicationName, errorCodeProperties.getConstantsClassList(), errorCodeApi);
     }
 
     @Bean
     public ErrorCodeLoader errorCodeLoader(@Value("${spring.application.name}") String applicationName,
-                                           ErrorCodeFrameworkService errorCodeFrameworkService) {
-        return new ErrorCodeLoaderImpl(applicationName, errorCodeFrameworkService);
+                                           ErrorCodeApi errorCodeApi) {
+        return new ErrorCodeLoaderImpl(applicationName, errorCodeApi);
     }
 
 }

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/errorcode/core/generator/ErrorCodeAutoGenerator.java → yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/generator/ErrorCodeAutoGenerator.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.framework.errorcode.core.generator;
+package cn.iocoder.yudao.framework.errorcode.core.generator;
 
 /**
  * 错误码的自动生成器

+ 6 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/errorcode/core/generator/ErrorCodeAutoGeneratorImpl.java → yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/generator/ErrorCodeAutoGeneratorImpl.java

@@ -1,12 +1,12 @@
-package cn.iocoder.yudao.module.system.framework.errorcode.core.generator;
+package cn.iocoder.yudao.framework.errorcode.core.generator;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.util.ClassUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.dto.ErrorCodeAutoGenerateReqDTO;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.service.ErrorCodeFrameworkService;
+import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
+import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeAutoGenerateReqDTO;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.context.event.ApplicationReadyEvent;
@@ -36,9 +36,9 @@ public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
      */
     private final List<String> constantsClassList;
     /**
-     * 错误码 Service
+     * 错误码 Api
      */
-    private final ErrorCodeFrameworkService errorCodeService;
+    private final ErrorCodeApi errorCodeApi;
 
     @Override
     @EventListener(ApplicationReadyEvent.class)
@@ -49,7 +49,7 @@ public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
         log.info("[execute][解析到错误码数量为 ({}) 个]", autoGenerateDTOs.size());
 
         // 第二步,写入到 system 服务
-        errorCodeService.autoGenerateErrorCodes(autoGenerateDTOs);
+        errorCodeApi.autoGenerateErrorCodes(autoGenerateDTOs);
         log.info("[execute][写入到 system 组件完成]");
     }
 

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/errorcode/core/loader/ErrorCodeLoader.java → yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoader.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.framework.errorcode.core.loader;
+package cn.iocoder.yudao.framework.errorcode.core.loader;
 
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 

+ 6 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java → yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java

@@ -1,9 +1,9 @@
-package cn.iocoder.yudao.module.system.framework.errorcode.core.loader;
+package cn.iocoder.yudao.framework.errorcode.core.loader;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.dto.ErrorCodeRespDTO;
-import cn.iocoder.yudao.module.system.framework.errorcode.core.service.ErrorCodeFrameworkService;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
+import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeRespDTO;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.context.event.ApplicationReadyEvent;
@@ -34,9 +34,9 @@ public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
      */
     private final String applicationName;
     /**
-     * 错误码 Service
+     * 错误码 Api
      */
-    private final ErrorCodeFrameworkService errorCodeService;
+    private final ErrorCodeApi errorCodeApi;
 
     /**
      * 缓存错误码的最大更新时间,用于后续的增量轮询,判断是否有更新
@@ -55,7 +55,7 @@ public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
 
     private void loadErrorCodes0() {
         // 加载错误码
-        List<ErrorCodeRespDTO> errorCodeRespDTOs = errorCodeService.getErrorCodeList(applicationName, maxUpdateTime);
+        List<ErrorCodeRespDTO> errorCodeRespDTOs = errorCodeApi.getErrorCodeList(applicationName, maxUpdateTime);
         if (CollUtil.isEmpty(errorCodeRespDTOs)) {
             return;
         }

+ 10 - 0
yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/package-info.java

@@ -0,0 +1,10 @@
+/**
+ * 错误码 ErrorCode 的自动配置功能,提供如下功能:
+ *
+ * 1. 远程读取:项目启动时,从 system-service 服务,读取数据库中的 ErrorCode 错误码,实现错误码的提水可配置;
+ * 2. 自动更新:管理员在管理后台修数据库中的 ErrorCode 错误码时,项目自动从 system-service 服务加载最新的 ErrorCode 错误码;
+ * 3. 自动写入:项目启动时,将项目本地的错误码写到 system-server 服务中,方便管理员在管理后台编辑;
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.framework.errorcode;

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

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

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

@@ -34,6 +34,13 @@
             <scope>provided</scope>
         </dependency>
 
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行操作日志的记录 -->
+            <version>${revision}</version>
+        </dependency>
+
         <!-- 工具类相关 -->
         <dependency>
             <groupId>com.google.guava</groupId>

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogAutoConfiguration.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.framework.operatelog.config;
 
 import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
+import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
+import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkServiceImpl;
+import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -12,4 +15,9 @@ public class YudaoOperateLogAutoConfiguration {
         return new OperateLogAspect();
     }
 
+    @Bean
+    public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
+        return new OperateLogFrameworkServiceImpl(operateLogApi);
+    }
+
 }

+ 70 - 60
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java

@@ -9,9 +9,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
-import cn.iocoder.yudao.framework.operatelog.core.dto.OperateLogCreateReqDTO;
 import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
+import cn.iocoder.yudao.framework.operatelog.core.service.OperateLog;
 import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import com.google.common.collect.Maps;
@@ -45,7 +44,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
  * 满足如下任一条件,则会进行记录:
  * 1. 使用 @ApiOperation + 非 @GetMapping
  * 2. 使用 @OperateLog 注解
- *
+ * <p>
  * 但是,如果声明 @OperateLog 注解时,将 enable 属性设置为 false 时,强制不记录。
  *
  * @author 芋道源码
@@ -57,13 +56,13 @@ public class OperateLogAspect {
     /**
      * 用于记录操作内容的上下文
      *
-     * @see OperateLogCreateReqDTO#getContent()
+     * @see OperateLog#getContent()
      */
     private static final ThreadLocal<String> CONTENT = new ThreadLocal<>();
     /**
      * 用于记录拓展字段的上下文
      *
-     * @see OperateLogCreateReqDTO#getExts()
+     * @see OperateLog#getExts()
      */
     private static final ThreadLocal<Map<String, Object>> EXTS = new ThreadLocal<>();
 
@@ -73,16 +72,21 @@ public class OperateLogAspect {
     @Around("@annotation(apiOperation)")
     public Object around(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) throws Throwable {
         // 可能也添加了 @ApiOperation 注解
-        OperateLog operateLog = getMethodAnnotation(joinPoint, OperateLog.class);
+        cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog = getMethodAnnotation(joinPoint,
+                cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog.class);
         return around0(joinPoint, operateLog, apiOperation);
     }
 
-    @Around("!@annotation(io.swagger.annotations.ApiOperation) && @annotation(operateLog)") // 兼容处理,只添加 @OperateLog 注解的情况
-    public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable {
+    @Around("!@annotation(io.swagger.annotations.ApiOperation) && @annotation(operateLog)")
+    // 兼容处理,只添加 @OperateLog 注解的情况
+    public Object around(ProceedingJoinPoint joinPoint,
+                         cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog) throws Throwable {
         return around0(joinPoint, operateLog, null);
     }
 
-    private Object around0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation) throws Throwable {
+    private Object around0(ProceedingJoinPoint joinPoint,
+                           cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
+                           ApiOperation apiOperation) throws Throwable {
         // 目前,只有管理员,才记录操作日志!所以非管理员,直接调用,不进行记录
         Integer userType = WebFrameworkUtils.getLoginUserType();
         if (!Objects.equals(userType, UserTypeEnum.ADMIN.getValue())) {
@@ -121,7 +125,9 @@ public class OperateLogAspect {
         EXTS.remove();
     }
 
-    private void log(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation,
+    private void log(ProceedingJoinPoint joinPoint,
+                     cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
+                     ApiOperation apiOperation,
                      Date startTime, Object result, Throwable exception) {
         try {
             // 判断不记录的情况
@@ -136,113 +142,117 @@ public class OperateLogAspect {
         }
     }
 
-    private void log0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation,
+    private void log0(ProceedingJoinPoint joinPoint,
+                      cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
+                      ApiOperation apiOperation,
                       Date startTime, Object result, Throwable exception) {
-        OperateLogCreateReqDTO operateLogDTO = new OperateLogCreateReqDTO();
+        OperateLog operateLogObj = new OperateLog();
         // 补全通用字段
-        operateLogDTO.setTraceId(TracerUtils.getTraceId());
-        operateLogDTO.setStartTime(startTime);
+        operateLogObj.setTraceId(TracerUtils.getTraceId());
+        operateLogObj.setStartTime(startTime);
         // 补充用户信息
-        fillUserFields(operateLogDTO);
+        fillUserFields(operateLogObj);
         // 补全模块信息
-        fillModuleFields(operateLogDTO, joinPoint, operateLog, apiOperation);
+        fillModuleFields(operateLogObj, joinPoint, operateLog, apiOperation);
         // 补全请求信息
-        fillRequestFields(operateLogDTO);
+        fillRequestFields(operateLogObj);
         // 补全方法信息
-        fillMethodFields(operateLogDTO, joinPoint, operateLog, startTime, result, exception);
+        fillMethodFields(operateLogObj, joinPoint, operateLog, startTime, result, exception);
 
         // 异步记录日志
-        operateLogFrameworkService.createOperateLogAsync(operateLogDTO);
+        operateLogFrameworkService.createOperateLog(operateLogObj);
     }
 
-    private static void fillUserFields(OperateLogCreateReqDTO operateLogDTO) {
-        operateLogDTO.setUserId(WebFrameworkUtils.getLoginUserId());
-        operateLogDTO.setUserType(WebFrameworkUtils.getLoginUserType());
+    private static void fillUserFields(OperateLog operateLogObj) {
+        operateLogObj.setUserId(WebFrameworkUtils.getLoginUserId());
+        operateLogObj.setUserType(WebFrameworkUtils.getLoginUserType());
     }
 
-    private static void fillModuleFields(OperateLogCreateReqDTO operateLogDTO,
-                                         ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation) {
+    private static void fillModuleFields(OperateLog operateLogObj,
+                                         ProceedingJoinPoint joinPoint,
+                                         cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
+                                         ApiOperation apiOperation) {
         // module 属性
         if (operateLog != null) {
-            operateLogDTO.setModule(operateLog.module());
+            operateLogObj.setModule(operateLog.module());
         }
-        if (StrUtil.isEmpty(operateLogDTO.getModule())) {
+        if (StrUtil.isEmpty(operateLogObj.getModule())) {
             Api api = getClassAnnotation(joinPoint, Api.class);
             if (api != null) {
                 // 优先读取 @API 的 name 属性
                 if (StrUtil.isNotEmpty(api.value())) {
-                    operateLogDTO.setModule(api.value());
+                    operateLogObj.setModule(api.value());
                 }
                 // 没有的话,读取 @API 的 tags 属性
-                if (StrUtil.isEmpty(operateLogDTO.getModule()) && ArrayUtil.isNotEmpty(api.tags())) {
-                    operateLogDTO.setModule(api.tags()[0]);
+                if (StrUtil.isEmpty(operateLogObj.getModule()) && ArrayUtil.isNotEmpty(api.tags())) {
+                    operateLogObj.setModule(api.tags()[0]);
                 }
             }
         }
         // name 属性
         if (operateLog != null) {
-            operateLogDTO.setName(operateLog.name());
+            operateLogObj.setName(operateLog.name());
         }
-        if (StrUtil.isEmpty(operateLogDTO.getName()) && apiOperation != null) {
-            operateLogDTO.setName(apiOperation.value());
+        if (StrUtil.isEmpty(operateLogObj.getName()) && apiOperation != null) {
+            operateLogObj.setName(apiOperation.value());
         }
         // type 属性
         if (operateLog != null && ArrayUtil.isNotEmpty(operateLog.type())) {
-            operateLogDTO.setType(operateLog.type()[0].getType());
+            operateLogObj.setType(operateLog.type()[0].getType());
         }
-        if (operateLogDTO.getType() == null) {
+        if (operateLogObj.getType() == null) {
             RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
             OperateTypeEnum operateLogType = convertOperateLogType(requestMethod);
-            operateLogDTO.setType(operateLogType != null ? operateLogType.getType() : null);
+            operateLogObj.setType(operateLogType != null ? operateLogType.getType() : null);
         }
         // content 和 exts 属性
-        operateLogDTO.setContent(CONTENT.get());
-        operateLogDTO.setExts(EXTS.get());
+        operateLogObj.setContent(CONTENT.get());
+        operateLogObj.setExts(EXTS.get());
     }
 
-    private static void fillRequestFields(OperateLogCreateReqDTO operateLogDTO) {
+    private static void fillRequestFields(OperateLog operateLogObj) {
         // 获得 Request 对象
         HttpServletRequest request = ServletUtils.getRequest();
         if (request == null) {
             return;
         }
         // 补全请求信息
-        operateLogDTO.setRequestMethod(request.getMethod());
-        operateLogDTO.setRequestUrl(request.getRequestURI());
-        operateLogDTO.setUserIp(ServletUtil.getClientIP(request));
-        operateLogDTO.setUserAgent(ServletUtils.getUserAgent(request));
+        operateLogObj.setRequestMethod(request.getMethod());
+        operateLogObj.setRequestUrl(request.getRequestURI());
+        operateLogObj.setUserIp(ServletUtil.getClientIP(request));
+        operateLogObj.setUserAgent(ServletUtils.getUserAgent(request));
     }
 
-    private static void fillMethodFields(OperateLogCreateReqDTO operateLogDTO,
-                                         ProceedingJoinPoint joinPoint, OperateLog operateLog,
+    private static void fillMethodFields(OperateLog operateLogObj,
+                                         ProceedingJoinPoint joinPoint,
+                                         cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
                                          Date startTime, Object result, Throwable exception) {
         MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
-        operateLogDTO.setJavaMethod(methodSignature.toString());
+        operateLogObj.setJavaMethod(methodSignature.toString());
         if (operateLog == null || operateLog.logArgs()) {
-            operateLogDTO.setJavaMethodArgs(obtainMethodArgs(joinPoint));
+            operateLogObj.setJavaMethodArgs(obtainMethodArgs(joinPoint));
         }
         if (operateLog == null || operateLog.logResultData()) {
-            operateLogDTO.setResultData(obtainResultData(result));
+            operateLogObj.setResultData(obtainResultData(result));
         }
-        operateLogDTO.setDuration((int) (System.currentTimeMillis() - startTime.getTime()));
+        operateLogObj.setDuration((int) (System.currentTimeMillis() - startTime.getTime()));
         // (正常)处理 resultCode 和 resultMsg 字段
-        if (result != null) {
-            if (result instanceof CommonResult) {
-                CommonResult<?> commonResult = (CommonResult<?>) result;
-                operateLogDTO.setResultCode(commonResult.getCode());
-                operateLogDTO.setResultMsg(commonResult.getMsg());
-            } else {
-                operateLogDTO.setResultCode(SUCCESS.getCode());
-            }
+        if (result instanceof CommonResult) {
+            CommonResult<?> commonResult = (CommonResult<?>) result;
+            operateLogObj.setResultCode(commonResult.getCode());
+            operateLogObj.setResultMsg(commonResult.getMsg());
+        } else {
+            operateLogObj.setResultCode(SUCCESS.getCode());
         }
         // (异常)处理 resultCode 和 resultMsg 字段
         if (exception != null) {
-            operateLogDTO.setResultCode(INTERNAL_SERVER_ERROR.getCode());
-            operateLogDTO.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
+            operateLogObj.setResultCode(INTERNAL_SERVER_ERROR.getCode());
+            operateLogObj.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
         }
     }
 
-    private static boolean isLogEnable(ProceedingJoinPoint joinPoint, OperateLog operateLog) {
+    private static boolean isLogEnable(ProceedingJoinPoint joinPoint,
+                                       cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog) {
         // 有 @OperateLog 注解的情况下
         if (operateLog != null) {
             return operateLog.enable();
@@ -256,7 +266,7 @@ public class OperateLogAspect {
             return null;
         }
         return Arrays.stream(requestMethods).filter(requestMethod ->
-                           requestMethod == RequestMethod.POST
+                requestMethod == RequestMethod.POST
                         || requestMethod == RequestMethod.PUT
                         || requestMethod == RequestMethod.DELETE)
                 .findFirst().orElse(null);

+ 0 - 87
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/dto/OperateLogCreateReqDTO.java

@@ -1,87 +0,0 @@
-package cn.iocoder.yudao.framework.operatelog.core.dto;
-
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-import java.util.Date;
-import java.util.Map;
-
-/**
- * 操作日志创建 Request DTO
- */
-@Data
-public class OperateLogCreateReqDTO {
-
-    @ApiModelProperty(value = "链路追踪编号", required = true, example = "89aca178-a370-411c-ae02-3f0d672be4ab")
-    @NotEmpty(message = "链路追踪编号不能为空")
-    private String traceId;
-
-    @ApiModelProperty(value = "用户编号", required = true, example = "1024")
-    @NotNull(message = "用户编号不能为空")
-    private Long userId;
-    @ApiModelProperty(value = "用户类型", required = true, example = "1")
-    @NotNull(message = "用户类型不能为空")
-    private Integer userType;
-
-    @ApiModelProperty(value = "操作模块", required = true, example = "订单")
-    @NotEmpty(message = "操作模块不能为空")
-    private String module;
-
-    @ApiModelProperty(value = "操作名", required = true, example = "创建订单")
-    @NotEmpty(message = "操作名")
-    private String name;
-
-    @ApiModelProperty(value = "操作分类", required = true, example = "1", notes = "参见 SysOperateLogTypeEnum 枚举类")
-    @NotNull(message = "操作分类不能为空")
-    private Integer type;
-
-    @ApiModelProperty(value = "操作明细", example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。")
-    private String content;
-
-    @ApiModelProperty(value = "拓展字段", example = "{'orderId': 1}")
-    private Map<String, Object> exts;
-
-    @ApiModelProperty(value = "请求方法名", required = true, example = "GET")
-    @NotEmpty(message = "请求方法名不能为空")
-    private String requestMethod;
-
-    @ApiModelProperty(value = "请求地址", required = true, example = "/xxx/yyy")
-    @NotEmpty(message = "请求地址不能为空")
-    private String requestUrl;
-
-    @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1")
-    @NotEmpty(message = "用户 IP 不能为空")
-    private String userIp;
-
-    @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0")
-    @NotEmpty(message = "浏览器 UserAgent 不能为空")
-    private String userAgent;
-
-    @ApiModelProperty(value = "Java 方法名", required = true, example = "cn.iocoder.yudao.UserController.save(...)")
-    @NotEmpty(message = "Java 方法名不能为空")
-    private String javaMethod;
-
-    @ApiModelProperty(value = "Java 方法的参数")
-    private String javaMethodArgs;
-
-    @ApiModelProperty(value = "开始时间", required = true)
-    @NotNull(message = "开始时间不能为空")
-    private Date startTime;
-
-    @ApiModelProperty(value = "执行时长,单位:毫秒", required = true)
-    @NotNull(message = "执行时长不能为空")
-    private Integer duration;
-
-    @ApiModelProperty(value = "结果码", required = true)
-    @NotNull(message = "结果码不能为空")
-    private Integer resultCode;
-
-    @ApiModelProperty(value = "结果提示")
-    private String resultMsg;
-
-    @ApiModelProperty(value = "结果数据")
-    private String resultData;
-
-}

+ 110 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLog.java

@@ -0,0 +1,110 @@
+package cn.iocoder.yudao.framework.operatelog.core.service;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 操作日志
+ *
+ * @author 芋道源码
+ */
+@Data
+public class OperateLog {
+
+    /**
+     * 链路追踪编号
+     */
+    private String traceId;
+
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+
+    /**
+     * 操作模块
+     */
+    private String module;
+
+    /**
+     * 操作名
+     */
+    private String name;
+
+    /**
+     * 操作分类
+     */
+    private Integer type;
+
+    /**
+     * 操作明细
+     */
+    private String content;
+
+    /**
+     * 拓展字段
+     */
+    private Map<String, Object> exts;
+
+    /**
+     * 请求方法名
+     */
+    private String requestMethod;
+
+    /**
+     * 请求地址
+     */
+    private String requestUrl;
+
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+
+    /**
+     * 浏览器 UserAgent
+     */
+    private String userAgent;
+
+    /**
+     * Java 方法名
+     */
+    private String javaMethod;
+
+    /**
+     * Java 方法的参数
+     */
+    private String javaMethodArgs;
+
+    /**
+     * 开始时间
+     */
+    private Date startTime;
+
+    /**
+     * 执行时长,单位:毫秒
+     */
+    private Integer duration;
+
+    /**
+     * 结果码
+     */
+    private Integer resultCode;
+
+    /**
+     * 结果提示
+     */
+    private String resultMsg;
+
+    /**
+     * 结果数据
+     */
+    private String resultData;
+
+}

+ 8 - 8
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLogFrameworkService.java

@@ -1,17 +1,17 @@
 package cn.iocoder.yudao.framework.operatelog.core.service;
 
-import cn.iocoder.yudao.framework.operatelog.core.dto.OperateLogCreateReqDTO;
-
-import java.util.concurrent.Future;
-
+/**
+ * 操作日志 Framework Service 接口
+ *
+ * @author 芋道源码
+ */
 public interface OperateLogFrameworkService {
 
     /**
-     * 异步记录操作日志
+     * 记录操作日志
      *
-     * @param reqVO 操作日志请求
-     * @return true: 记录成功,false: 记录失败
+     * @param operateLog 操作日志请求
      */
-    Future<Boolean> createOperateLogAsync(OperateLogCreateReqDTO reqVO);
+    void createOperateLog(OperateLog operateLog);
 
 }

+ 28 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLogFrameworkServiceImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.framework.operatelog.core.service;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.scheduling.annotation.Async;
+
+/**
+ * 操作日志 Framework Service 实现类
+ *
+ * 基于 {@link OperateLogApi} 实现,记录操作日志
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+public class OperateLogFrameworkServiceImpl implements OperateLogFrameworkService {
+
+    private final OperateLogApi operateLogApi;
+
+    @Override
+    @Async
+    public void createOperateLog(OperateLog operateLog) {
+        OperateLogCreateReqDTO reqDTO = BeanUtil.copyProperties(operateLog, OperateLogCreateReqDTO.class);
+        operateLogApi.createOperateLog(reqDTO);
+    }
+
+}

+ 2 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
+import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXLitePayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXNativePayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
@@ -62,7 +63,7 @@ public class PayClientFactoryImpl implements PayClientFactory {
         // TODO @芋艿 WX_LITE WX_APP 如果不添加在 项目启动的时候去初始化会报错无法启动。所以我手动加了两个,具体需要你来配
         switch (channelEnum) {
             case WX_PUB: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
-            case WX_LITE: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
+            case WX_LITE: return (AbstractPayClient<Config>) new WXLitePayClient(channelId, (WXPayClientConfig) config); //微信小程序请求支付
             case WX_APP: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
             case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
             case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);

+ 13 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.http.HttpUtil;
 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
 import cn.iocoder.yudao.framework.pay.core.client.dto.*;
@@ -130,11 +131,20 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
         }
     }
 
+
+
+    /**
+     * 支付宝统一回调参数  str 转 map
+     *
+     * @param s 支付宝支付通知回调参数
+     * @return map 支付宝集合
+     */
     public static Map<String, String> strToMap(String s) {
+        // TODO @zxy:这个可以使用 hutool 的 HttpUtil decodeParams 方法么?
         Map<String, String> stringStringMap = new HashMap<>();
-        //调整时间格式
+        // 调整时间格式
         String s3 = s.replaceAll("%3A", ":");
-        //获取map
+        // 获取 map
         String s4 = s3.replace("+", " ");
         String[] split = s4.split("&");
         for (String s1 : split) {
@@ -143,4 +153,5 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
         }
         return stringStringMap;
     }
+
 }

+ 203 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java

@@ -0,0 +1,203 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.io.FileUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
+import cn.iocoder.yudao.framework.pay.core.client.dto.*;
+import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
+import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
+import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
+
+
+/**
+ * 微信小程序下支付
+ *
+ * @author zwy
+ */
+@Slf4j
+public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
+
+    private WxPayService client;
+
+    public WXLitePayClient(Long channelId, WXPayClientConfig config) {
+        super(channelId, PayChannelEnum.WX_LITE.getCode(), config, new WXCodeMapping());
+    }
+
+    @Override
+    protected void doInit() {
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtil.copyProperties(config, payConfig, "keyContent");
+        payConfig.setTradeType(WxPayConstants.TradeType.JSAPI); // 设置使用 JS API 支付方式
+//        if (StrUtil.isNotEmpty(config.getKeyContent())) {
+//            payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
+//        }
+        if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
+            // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
+            payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
+        }
+        if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
+            // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
+            payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
+        }
+        // 真实客户端
+        this.client = new WxPayServiceImpl();
+        client.setConfig(payConfig);
+    }
+
+    @Override
+    public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        WxPayMpOrderResult response;
+        try {
+            switch (config.getApiVersion()) {
+                case WXPayClientConfig.API_VERSION_V2:
+                    response = this.unifiedOrderV2(reqDTO);
+                    break;
+                case WXPayClientConfig.API_VERSION_V3:
+                    WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
+                    // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
+                    response = new WxPayMpOrderResult();
+                    BeanUtil.copyProperties(responseV3, response, true);
+                    break;
+                default:
+                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+            }
+        } catch (WxPayException e) {
+            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
+            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
+                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
+        }
+        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
+    }
+
+    private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
+        // 构建 WxPayUnifiedOrderRequest 对象
+        WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
+                .outTradeNo(reqDTO.getMerchantOrderId())
+                .body(reqDTO.getBody())
+                .totalFee(reqDTO.getAmount().intValue()) // 单位分
+                .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) // v2的时间格式
+                .spbillCreateIp(reqDTO.getUserIp())
+                .openid(getOpenid(reqDTO))
+                .notifyUrl(reqDTO.getNotifyUrl())
+                .build();
+        // 执行请求
+        return client.createOrder(request);
+    }
+
+    private WxPayUnifiedOrderV3Result.JsapiResult unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
+        // 构建 WxPayUnifiedOrderRequest 对象
+        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
+        request.setOutTradeNo(reqDTO.getMerchantOrderId());
+
+        request.setDescription(reqDTO.getBody());
+        request.setAmount(new WxPayUnifiedOrderV3Request
+                .Amount()
+                .setTotal(reqDTO
+                        .getAmount()
+                        .intValue())); // 单位分
+        request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式
+        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
+        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
+        request.setNotifyUrl(reqDTO.getNotifyUrl());
+        // 执行请求
+        return client.createOrderV3(TradeTypeEnum.JSAPI, request);
+    }
+
+    private static String getOpenid(PayOrderUnifiedReqDTO reqDTO) {
+        String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid");
+        if (StrUtil.isEmpty(openid)) {
+            throw new IllegalArgumentException("支付请求的 openid 不能为空!");
+        }
+        return openid;
+    }
+
+    /**
+     *
+     * 微信支付回调 分 v2 和v3 的处理方式
+     *
+     * @param data 通知结果
+     * @return 支付回调对象
+     * @throws WxPayException 微信异常类
+     */
+    @Override
+    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
+        log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody());
+        // 微信支付 v2 回调结果处理
+        switch (config.getApiVersion()) {
+            case WXPayClientConfig.API_VERSION_V2:
+                return parseOrderNotifyV2(data);
+            case WXPayClientConfig.API_VERSION_V3:
+                return parseOrderNotifyV3(data);
+            default:
+                throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+        }
+    }
+
+    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
+        WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
+        WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
+        // 转换结果
+        Assert.isTrue(Objects.equals(wxPayOrderNotifyV3Result.getResult().getTradeState(), "SUCCESS"),
+                "支付结果非 SUCCESS");
+
+        return PayOrderNotifyRespDTO
+                .builder()
+                .orderExtensionNo(result.getOutTradeNo())
+                .channelOrderNo(result.getTradeState())
+                .successTime(DateUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+                .data(data.getBody())
+                .build();
+    }
+
+    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
+        WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
+        Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
+        // 转换结果
+        return PayOrderNotifyRespDTO
+                .builder()
+                .orderExtensionNo(notifyResult.getOutTradeNo())
+                .channelOrderNo(notifyResult.getTransactionId())
+                .channelUserId(notifyResult.getOpenid())
+                .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
+                .data(data.getBody())
+                .build();
+
+    }
+
+    @Override
+    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
+        //TODO 需要实现
+        throw new UnsupportedOperationException("需要实现");
+    }
+
+
+    @Override
+    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
+        //TODO 需要实现
+        throw new UnsupportedOperationException();
+    }
+
+}

+ 58 - 13
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java

@@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.*;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
 import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
@@ -28,9 +29,14 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
 import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
 import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
 
-
+/**
+ * 微信 App 支付
+ *
+ * @author zwy
+ */
 @Slf4j
 public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
+
     private WxPayService client;
 
     public WXNativePayClient(Long channelId, WXPayClientConfig config) {
@@ -61,7 +67,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
     @Override
     public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
         // 这里原生的返回的是支付的 url 所以直接使用string接收
-        //"invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
+        // "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
         String responseV3;
         try {
             switch (config.getApiVersion()) {
@@ -84,7 +90,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
 
     private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
         //前端
-        String trade_type = reqDTO.getChannelExtras().get("trade_type");
+        String tradeType = reqDTO.getChannelExtras().get("trade_type");
         // 构建 WxPayUnifiedOrderRequest 对象
         WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest
                 .newBuilder()
@@ -94,7 +100,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
                 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
                 .spbillCreateIp(reqDTO.getUserIp())
                 .notifyUrl(reqDTO.getNotifyUrl())
-                .productId(trade_type)
+                .productId(tradeType)
                 .build();
         // 执行请求
         return client.createOrder(request);
@@ -109,33 +115,72 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
         request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
         request.setNotifyUrl(reqDTO.getNotifyUrl());
         // 执行请求
-//        log.info("支付字段request:{}",request.getTimeExpire());
-
         return client.createOrderV3(TradeTypeEnum.NATIVE, request);
     }
 
-
+    /**
+     *
+     * 微信支付回调 分v2 和v3 的处理方式
+     *
+     * @param data 通知结果
+     * @return 支付回调对象
+     * @throws WxPayException 微信异常类
+     */
     @Override
     public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
+        log.info("微信支付回调data数据:{}", data.getBody());
+        // 微信支付 v2 回调结果处理
+        switch (config.getApiVersion()) {
+            case WXPayClientConfig.API_VERSION_V2:
+                return parseOrderNotifyV2(data);
+            case WXPayClientConfig.API_VERSION_V3:
+                return parseOrderNotifyV3(data);
+            default:
+                throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+        }
+    }
+
+    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
+        WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
+        WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
+        // 转换结果
+        Assert.isTrue(Objects.equals(wxPayOrderNotifyV3Result.getResult().getTradeState(), "SUCCESS"),
+                "支付结果非 SUCCESS");
+        return PayOrderNotifyRespDTO
+                .builder()
+                .orderExtensionNo(result.getOutTradeNo())
+                .channelOrderNo(result.getTradeState())
+                .successTime(DateUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+                .data(data.getBody())
+                .build();
+    }
+
+    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
         WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
         Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
         // 转换结果
-        return PayOrderNotifyRespDTO.builder().orderExtensionNo(notifyResult.getOutTradeNo())
-                .channelOrderNo(notifyResult.getTransactionId()).channelUserId(notifyResult.getOpenid())
-                .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
-                .data(data.getBody()).build();
+        return PayOrderNotifyRespDTO
+                .builder()
+                .orderExtensionNo(notifyResult.getOutTradeNo())
+                .channelOrderNo(notifyResult.getTransactionId())
+                .channelUserId(notifyResult.getOpenid())
+                .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
+                .data(data.getBody())
+                .build();
+
     }
 
     @Override
     public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
-        //TODO 需要实现
+        // TODO 需要实现
         throw new UnsupportedOperationException("需要实现");
     }
 
 
     @Override
     protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
-        //TODO 需要实现
+        // TODO 需要实现
         throw new UnsupportedOperationException();
     }
+
 }

+ 0 - 3
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java

@@ -11,8 +11,6 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.util.Set;
 
-// TODO 芋艿:参数校验
-
 /**
  * 微信支付的 PayClientConfig 实现类
  * 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性
@@ -22,7 +20,6 @@ import java.util.Set;
 @Data
 public class WXPayClientConfig implements PayClientConfig {
 
-    // TODO 芋艿:V2 or V3 客户端
     /**
      * API 版本 - V2
      * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1

+ 47 - 8
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java

@@ -12,6 +12,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.*;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
 import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
@@ -95,7 +96,6 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
         // 构建 WxPayUnifiedOrderRequest 对象
         WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
                 .outTradeNo(reqDTO.getMerchantOrderId())
-                // TODO 芋艿:貌似没 title?
                 .body(reqDTO.getBody())
                 .totalFee(reqDTO.getAmount().intValue()) // 单位分
                 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
@@ -111,7 +111,6 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
         // 构建 WxPayUnifiedOrderRequest 对象
         WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
         request.setOutTradeNo(reqDTO.getMerchantOrderId());
-        // TODO 芋艿:貌似没 title?
         request.setDescription(reqDTO.getBody());
         request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
         request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
@@ -130,27 +129,67 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
         return openid;
     }
 
+    /**
+     *
+     * 微信支付回调 分v2 和v3 的处理方式
+     *
+     * @param data 通知结果
+     * @return 支付回调对象
+     * @throws WxPayException 微信异常类
+     */
     @Override
     public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
+        log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody());
+        // 微信支付 v2 回调结果处理
+        switch (config.getApiVersion()) {
+            case WXPayClientConfig.API_VERSION_V2:
+                return parseOrderNotifyV2(data);
+            case WXPayClientConfig.API_VERSION_V3:
+                return parseOrderNotifyV3(data);
+            default:
+                throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+        }
+    }
+
+    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
+        WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
+        WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
+        // 转换结果
+        Assert.isTrue(Objects.equals(wxPayOrderNotifyV3Result.getResult().getTradeState(), "SUCCESS"),
+                "支付结果非 SUCCESS");
+        return PayOrderNotifyRespDTO
+                .builder()
+                .orderExtensionNo(result.getOutTradeNo())
+                .channelOrderNo(result.getTradeState())
+                .successTime(DateUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+                .data(data.getBody())
+                .build();
+    }
+
+    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
         WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
         Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
         // 转换结果
-        return PayOrderNotifyRespDTO.builder().orderExtensionNo(notifyResult.getOutTradeNo())
-                .channelOrderNo(notifyResult.getTransactionId()).channelUserId(notifyResult.getOpenid())
+        return PayOrderNotifyRespDTO
+                .builder()
+                .orderExtensionNo(notifyResult.getOutTradeNo())
+                .channelOrderNo(notifyResult.getTransactionId())
+                .channelUserId(notifyResult.getOpenid())
                 .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
-                .data(data.getBody()).build();
+                .data(data.getBody())
+                .build();
+
     }
 
     @Override
     public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
-        //TODO 需要实现
+        // TODO 需要实现
         throw new UnsupportedOperationException("需要实现");
     }
 
-
     @Override
     protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
-        //TODO 需要实现
+        // TODO 需要实现
         throw new UnsupportedOperationException();
     }
 

+ 0 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java

@@ -22,7 +22,6 @@ public enum PayChannelEnum {
     WX_APP("wx_app", "微信 App 支付", WXPayClientConfig.class),
     WX_NATIVE("wx_native", "微信 native 支付", WXPayClientConfig.class),
 
-
     ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
     ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
     ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),

+ 2 - 1
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.framework.social.config;
 
-import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
 import com.xkcoding.http.HttpUtil;
 import com.xkcoding.http.support.hutool.HutoolImpl;
@@ -11,6 +10,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
 
 /**
  * 社交自动装配类
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Configuration;
 public class YudaoSocialAutoConfiguration {
 
     @Bean
+    @Primary
     @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
     public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
         // 需要修改 HttpUtil 使用的实现,避免类报错

+ 5 - 7
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.social.core;
 import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
-import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniProgramRequest;
+import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest;
 import com.xkcoding.justauth.AuthRequestFactory;
 import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
 import me.zhyd.oauth.cache.AuthStateCache;
@@ -20,7 +20,6 @@ import java.lang.reflect.Method;
  * @author timfruit
  * @date 2021-10-31
  */
-// TODO @timfruit:单测
 public class YudaoAuthRequestFactory extends AuthRequestFactory {
 
     protected JustAuthProperties properties;
@@ -69,15 +68,14 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory {
         if (config == null) {
             return null;
         }
-        // 配置 http config
-        ReflectUtil.invoke(this, configureHttpConfigMethod,
-                authExtendSource.name(), config, properties.getHttpConfig());
+        // 反射调用,配置 http config
+        ReflectUtil.invoke(this, configureHttpConfigMethod, authExtendSource.name(), config, properties.getHttpConfig());
 
         // 获得拓展的 Request
         // noinspection SwitchStatementWithTooFewBranches
         switch (authExtendSource) {
-            case WECHAT_MINI_PROGRAM:
-                return new AuthWeChatMiniProgramRequest(config, authStateCache);
+            case WECHAT_MINI_APP:
+                return new AuthWeChatMiniAppRequest(config, authStateCache);
             default:
                 return null;
         }

+ 8 - 8
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.social.core.enums;
 import me.zhyd.oauth.config.AuthSource;
 
 /**
- * 拓展JustAuth各api需要的url, 用枚举类分平台类型管理
+ * 拓展 JustAuth  api 需要的 url, 用枚举类分平台类型管理
  *
  * 默认配置 {@link me.zhyd.oauth.config.AuthDefaultSource}
  *
@@ -14,25 +14,25 @@ public enum AuthExtendSource implements AuthSource {
     /**
      * 微信小程序授权登录
      */
-    WECHAT_MINI_PROGRAM {
+    WECHAT_MINI_APP {
 
         @Override
         public String authorize() {
-            // https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
-            throw new UnsupportedOperationException("不支持获取授权url, 请使用小程序内置函数wx.login()登录获取code");
+            // 参见 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 文档
+            throw new UnsupportedOperationException("不支持获取授权 url,请使用小程序内置函数 wx.login() 登录获取 code");
         }
 
         @Override
         public String accessToken() {
-            // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
-            // 获取openid, unionid , session_key
+            // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档
+            // 获取 openid, unionId , session_key 等字段
             return "https://api.weixin.qq.com/sns/jscode2session";
         }
 
         @Override
         public String userInfo() {
-            //https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html
-            throw new UnsupportedOperationException("不支持获取用户信息url, 请使用小程序内置函数wx.getUserProfile()获取用户信息");
+            // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档
+            throw new UnsupportedOperationException("不支持获取用户信息 url,请使用小程序内置函数 wx.getUserProfile() 获取用户信息");
         }
     }
 

+ 0 - 23
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java

@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.framework.social.core.model;
-
-import lombok.*;
-import me.zhyd.oauth.model.AuthToken;
-
-/**
- * 授权所需的 token 拓展类
- *
- * @author timfruit
- * @date 2021-10-29
- */
-@Getter
-@Setter
-@NoArgsConstructor
-@AllArgsConstructor
-public class AuthExtendToken extends AuthToken {
-
-    /**
-     * 微信小程序 - 会话密钥
-     */
-    private String miniSessionKey;
-
-}

+ 97 - 100
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java → yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java

@@ -1,100 +1,97 @@
-package cn.iocoder.yudao.framework.social.core.request;
-
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
-import cn.iocoder.yudao.framework.social.core.model.AuthExtendToken;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Data;
-import me.zhyd.oauth.cache.AuthStateCache;
-import me.zhyd.oauth.config.AuthConfig;
-import me.zhyd.oauth.exception.AuthException;
-import me.zhyd.oauth.model.AuthCallback;
-import me.zhyd.oauth.model.AuthToken;
-import me.zhyd.oauth.model.AuthUser;
-import me.zhyd.oauth.request.AuthDefaultRequest;
-import me.zhyd.oauth.utils.HttpUtils;
-import me.zhyd.oauth.utils.UrlBuilder;
-
-/**
- * 微信小程序登陆
- *
- * @author timfruit
- * @date 2021-10-29
- */
-public class AuthWeChatMiniProgramRequest extends AuthDefaultRequest {
-
-    public AuthWeChatMiniProgramRequest(AuthConfig config) {
-        super(config, AuthExtendSource.WECHAT_MINI_PROGRAM);
-    }
-
-    public AuthWeChatMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) {
-        super(config, AuthExtendSource.WECHAT_MINI_PROGRAM, authStateCache);
-    }
-
-    @Override
-    protected AuthToken getAccessToken(AuthCallback authCallback) {
-        // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
-        String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode()));
-        CodeSessionResponse accessTokenObject = JsonUtils.parseObject(response, CodeSessionResponse.class);
-
-        this.checkResponse(accessTokenObject);
-
-        AuthExtendToken token = new AuthExtendToken();
-        token.setMiniSessionKey(accessTokenObject.sessionKey);
-        token.setOpenId(accessTokenObject.openid);
-        token.setUnionId(accessTokenObject.unionid);
-        return token;
-    }
-
-    @Override
-    protected AuthUser getUserInfo(AuthToken authToken) {
-        // https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html
-        // 如果需要用户信息,需要在小程序调用函数后传给后端
-        return AuthUser.builder()
-                .uuid(authToken.getOpenId())
-                //TODO 是使用默认值,还是有小程序获取用户信息 和 code 一起传过来
-                .nickname("")
-                .avatar("")
-                .token(authToken)
-                .source(source.toString())
-                .build();
-    }
-
-    /**
-     * 检查响应内容是否正确
-     *
-     * @param object 请求响应内容
-     */
-    private void checkResponse(CodeSessionResponse object) {
-        if (object.errcode != 0) {
-            throw new AuthException(object.errcode, object.errmsg);
-        }
-    }
-
-    /**
-     * 返回获取 accessToken 的 url
-     *
-     * @param code 授权码
-     * @return 返回获取 accessToken 的 url
-     */
-    @Override
-    protected String accessTokenUrl(String code) {
-        return UrlBuilder.fromBaseUrl(source.accessToken())
-                .queryParam("appid", config.getClientId())
-                .queryParam("secret", config.getClientSecret())
-                .queryParam("js_code", code)
-                .queryParam("grant_type", "authorization_code")
-                .build();
-    }
-
-    @Data
-    private static class CodeSessionResponse {
-        private int errcode;
-        private String errmsg;
-        @JsonProperty("session_key")
-        private String sessionKey;
-        private String openid;
-        private String unionid;
-    }
-
-}
+package cn.iocoder.yudao.framework.social.core.request;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import me.zhyd.oauth.utils.HttpUtils;
+import me.zhyd.oauth.utils.UrlBuilder;
+
+/**
+ * 微信小程序登陆 Request 请求
+ *
+ * 由于 JustAuth 定位是面向 Web 为主的三方登录,所以微信小程序只能自己封装
+ *
+ * @author timfruit
+ * @date 2021-10-29
+ */
+public class AuthWeChatMiniAppRequest extends AuthDefaultRequest {
+
+    public AuthWeChatMiniAppRequest(AuthConfig config, AuthStateCache authStateCache) {
+        super(config, AuthExtendSource.WECHAT_MINI_APP, authStateCache);
+    }
+
+    @Override
+    protected AuthToken getAccessToken(AuthCallback authCallback) {
+        // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档
+        // 使用 code 获取对应的 openId、unionId 等字段
+        String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode()));
+        JSCode2SessionResponse accessTokenObject = JsonUtils.parseObject(response, JSCode2SessionResponse.class);
+        assert accessTokenObject != null;
+        checkResponse(accessTokenObject);
+        // 拼装结果
+        return AuthToken.builder()
+                .openId(accessTokenObject.getOpenid())
+                .unionId(accessTokenObject.getUnionId())
+                .build();
+    }
+
+    @Override
+    protected AuthUser getUserInfo(AuthToken authToken) {
+        // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档
+        // 如果需要用户信息,需要在小程序调用函数后传给后端
+        return AuthUser.builder()
+                .username("")
+                .nickname("")
+                .avatar("")
+                .uuid(authToken.getOpenId())
+                .token(authToken)
+                .source(source.toString())
+                .build();
+    }
+
+    /**
+     * 检查响应内容是否正确
+     *
+     * @param response 请求响应内容
+     */
+    private void checkResponse(JSCode2SessionResponse response) {
+        if (response.getErrorCode() != 0) {
+            throw new AuthException(response.getErrorCode(), response.getErrorMsg());
+        }
+    }
+
+    @Override
+    protected String accessTokenUrl(String code) {
+        return UrlBuilder.fromBaseUrl(source.accessToken())
+                .queryParam("appid", config.getClientId())
+                .queryParam("secret", config.getClientSecret())
+                .queryParam("js_code", code) // 和父类不同,所以需要重写该方法
+                .queryParam("grant_type", "authorization_code")
+                .build();
+    }
+
+    @Data
+    @SuppressWarnings("SpellCheckingInspection")
+    private static class JSCode2SessionResponse {
+
+        @JsonProperty("errcode")
+        private int errorCode;
+        @JsonProperty("errmsg")
+        private String errorMsg;
+        @JsonProperty("session_key")
+        private String sessionKey;
+        private String openid;
+        @JsonProperty("unionid")
+        private String unionId;
+
+    }
+
+}

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml

@@ -56,6 +56,12 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java

@@ -11,9 +11,11 @@ import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
 import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
+import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkServiceImpl;
 import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
+import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
 import org.springframework.beans.BeansException;
@@ -29,6 +31,11 @@ import org.springframework.context.annotation.Configuration;
 @EnableConfigurationProperties(TenantProperties.class)
 public class YudaoTenantAutoConfiguration {
 
+    @Bean
+    public TenantFrameworkService tenantFrameworkService(TenantApi tenantApi) {
+        return new TenantFrameworkServiceImpl(tenantApi);
+    }
+
     // ========== AOP ==========
 
     @Bean

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/TenantRedisMessageInterceptor.java

@@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
 import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
+
 /**
  * 多租户 {@link AbstractRedisMessage} 拦截器
  *
@@ -15,8 +17,6 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  */
 public class TenantRedisMessageInterceptor implements RedisMessageInterceptor {
 
-    private static final String HEADER_TENANT_ID = "tenant-id";
-
     @Override
     public void sendMessageBefore(AbstractRedisMessage message) {
         Long tenantId = TenantContextHolder.getTenantId();

+ 73 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkServiceImpl.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.framework.tenant.core.service;
+
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
+import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * Tenant 框架 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+public class TenantFrameworkServiceImpl implements TenantFrameworkService {
+
+    private static final ServiceException SERVICE_EXCEPTION_NULL = new ServiceException();
+
+    private final TenantApi tenantApi;
+
+    /**
+     * 针对 {@link #getTenantIds()} 的缓存
+     */
+    private final LoadingCache<Object, List<Long>> getTenantIdsCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<Object, List<Long>>() {
+
+                @Override
+                public List<Long> load(Object key) {
+                    return tenantApi.getTenantIds();
+                }
+
+            });
+
+    /**
+     * 针对 {@link #validTenant(Long)} 的缓存
+     */
+    private final LoadingCache<Long, ServiceException> validTenantCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<Long, ServiceException>() {
+
+                @Override
+                public ServiceException load(Long id) {
+                    try {
+                        tenantApi.validTenant(id);
+                        return SERVICE_EXCEPTION_NULL;
+                    } catch (ServiceException ex) {
+                        return ex;
+                    }
+                }
+
+            });
+
+    @Override
+    @SneakyThrows
+    public List<Long> getTenantIds() {
+        return getTenantIdsCache.get(Boolean.TRUE);
+    }
+
+    @Override
+    public void validTenant(Long id) {
+        ServiceException serviceException = validTenantCache.getUnchecked(id);
+        if (serviceException != SERVICE_EXCEPTION_NULL) {
+            throw serviceException;
+        }
+    }
+
+}

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

@@ -18,8 +18,6 @@ import java.io.IOException;
  */
 public class TenantContextWebFilter extends OncePerRequestFilter {
 
-    private static final String HEADER_TENANT_ID = "tenant-id";
-
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {

+ 6 - 2
yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml

@@ -33,9 +33,13 @@
         <!-- 三方云服务相关 -->
         <dependency>
             <groupId>com.github.binarywang</groupId>
-<!--            <artifactId>weixin-java-mp</artifactId>-->
             <artifactId>wx-java-mp-spring-boot-starter</artifactId>
-            <version>4.1.9.B</version>
+            <version>4.3.4.B</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
+            <version>4.3.4.B</version>
         </dependency>
         <!-- TODO 芋艿:清理 -->
     </dependencies>

+ 7 - 8
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.framework.excel.core.convert;
 
 import cn.hutool.core.convert.Convert;
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import com.alibaba.excel.converters.Converter;
@@ -12,7 +11,7 @@ import com.alibaba.excel.metadata.property.ExcelContentProperty;
 import lombok.extern.slf4j.Slf4j;
 
 /**
- * Excel {@link DictDataRespDTO} 数据字典转换器
+ * Excel 数据字典转换器
  *
  * @author 芋道源码
  */
@@ -35,14 +34,14 @@ public class DictConvert implements Converter<Object> {
         // 使用字典解析
         String type = getType(contentProperty);
         String label = cellData.getStringValue();
-        DictDataRespDTO dictData = DictFrameworkUtils.parseDictDataFromCache(type, label);
-        if (dictData == null) {
+        String value = DictFrameworkUtils.parseDictDataValue(type, label);
+        if (value == null) {
             log.error("[convertToJavaData][type({}) 解析不掉 label({})]", type, label);
             return null;
         }
         // 将 String 的 value 转换成对应的属性
         Class<?> fieldClazz = contentProperty.getField().getType();
-        return Convert.convert(fieldClazz, dictData.getValue());
+        return Convert.convert(fieldClazz, value);
     }
 
     @Override
@@ -56,13 +55,13 @@ public class DictConvert implements Converter<Object> {
         // 使用字典格式化
         String type = getType(contentProperty);
         String value = String.valueOf(object);
-        DictDataRespDTO dictData = DictFrameworkUtils.getDictDataFromCache(type, value);
-        if (dictData == null) {
+        String label = DictFrameworkUtils.getDictDataLabel(type, value);
+        if (label == null) {
             log.error("[convertToExcelData][type({}) 转换不了 label({})]", type, value);
             return new CellData<>("");
         }
         // 生成 Excel 小表格
-        return new CellData<>(dictData.getLabel());
+        return new CellData<>(label);
     }
 
     private static String getType(ExcelContentProperty contentProperty) {

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-monitor/pom.xml

@@ -58,6 +58,12 @@
             <artifactId>apm-toolkit-opentracing</artifactId>
         </dependency>
 
+        <!-- Micrometer 对 Prometheus 的支持 -->
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>de.codecentric</groupId>
             <artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->

+ 27 - 0
yudao-framework/yudao-spring-boot-starter-monitor/src/main/java/cn/iocoder/yudao/framework/tracer/config/YudaoMetricsAutoConfiguration.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.framework.tracer.config;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Metrics 配置类
+ *
+ * @author 芋道源码
+ */
+@Configuration
+@ConditionalOnClass({MeterRegistryCustomizer.class})
+@ConditionalOnProperty(prefix = "yudao.metrics", value = "enable", matchIfMissing = true) // 允许使用 yudao.metrics.enable=false 禁用 Metrics
+public class YudaoMetricsAutoConfiguration {
+
+    @Bean
+    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(
+            @Value("${spring.application.name}") String applicationName) {
+        return registry -> registry.config().commonTags("application", applicationName);
+    }
+
+}

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

@@ -1,2 +1,3 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  cn.iocoder.yudao.framework.tracer.config.YudaoTracerAutoConfiguration
+  cn.iocoder.yudao.framework.tracer.config.YudaoTracerAutoConfiguration,\
+  cn.iocoder.yudao.framework.tracer.config.YudaoMetricsAutoConfiguration

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-security/pom.xml

@@ -44,6 +44,12 @@
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
 
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
         <!-- 业务组件 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

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

@@ -6,6 +6,8 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
+import java.util.Collections;
+import java.util.List;
 
 @ConfigurationProperties(prefix = "yudao.security")
 @Validated
@@ -16,18 +18,23 @@ public class SecurityProperties {
      * HTTP 请求时,访问令牌的请求 Header
      */
     @NotEmpty(message = "Token Header 不能为空")
-    private String tokenHeader;
+    private String tokenHeader = "Authorization";
 
     /**
      * mock 模式的开关
      */
     @NotNull(message = "mock 模式的开关不能为空")
-    private Boolean mockEnable;
+    private Boolean mockEnable = false;
     /**
      * mock 模式的密钥
      * 一定要配置密钥,保证安全性
      */
     @NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
-    private String mockSecret = "yudaoyuanma";
+    private String mockSecret = "test";
+
+    /**
+     * 免登录的 URL 列表
+     */
+    private List<String> permitAllUrls = Collections.emptyList();
 
 }

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

@@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPoint
 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.oauth2.OAuth2TokenApi;
 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -81,7 +81,7 @@ public class YudaoSecurityAutoConfiguration {
         return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
     }
 
-    @Bean("ss") // 使用 Spring Security 的缩写,方便
+    @Bean("ss") // 使用 Spring Security 的缩写,方便使
     public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) {
         return new SecurityFrameworkServiceImpl(permissionApi);
     }

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

@@ -2,7 +2,10 @@ package cn.iocoder.yudao.framework.security.config;
 
 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
@@ -14,9 +17,15 @@ 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.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
 
 import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * 自定义的 Spring Security 配置适配器实现
@@ -29,6 +38,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
 
     @Resource
     private WebProperties webProperties;
+    @Resource
+    private SecurityProperties securityProperties;
 
     /**
      * 认证失败处理类 Bean
@@ -54,6 +65,9 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
     @Resource
     private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;
 
+    @Resource
+    private ApplicationContext applicationContext;
+
     /**
      * 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入
      * 通过覆写父类的该方法,添加 @Bean 注解,解决该问题
@@ -98,13 +112,21 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
                     .accessDeniedHandler(accessDeniedHandler);
                 // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
 
+        // 获得 @PermitAll 带来的 URL 列表,免登录
+        Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();
         // 设置每个请求的权限
         httpSecurity
                 // ①:全局共享规则
                 .authorizeRequests()
                     // 静态资源,可匿名访问
                     .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
-                    .antMatchers(HttpMethod.GET, "/admin-ui/**").permitAll()
+                    // 设置 @PermitAll 无需认证
+                    .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
+                    .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
+                    .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
+                    .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
+                    // 基于 yudao.security.permit-all-urls 无需认证
+                    .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
                     // 设置 App API 无需认证
                     .antMatchers(buildAppApi("/**")).permitAll()
                 // ②:每个项目的自定义规则
@@ -118,9 +140,46 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
         // 添加 JWT Filter
         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
     }
-    
+
     private String buildAppApi(String url) {
         return webProperties.getAppApi().getPrefix() + url;
     }
 
+    private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
+        Multimap<HttpMethod, String> result = HashMultimap.create();
+        // 获得接口对应的 HandlerMethod 集合
+        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
+                applicationContext.getBean("requestMappingHandlerMapping");
+        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
+        // 获得有 @PermitAll 注解的接口
+        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
+            HandlerMethod handlerMethod = entry.getValue();
+            if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
+                continue;
+            }
+            if (entry.getKey().getPatternsCondition() == null) {
+                continue;
+            }
+            Set<String> urls = entry.getKey().getPatternsCondition().getPatterns();
+            // 根据请求方法,添加到 result 结果
+            entry.getKey().getMethodsCondition().getMethods().forEach(requestMethod -> {
+                switch (requestMethod) {
+                    case GET:
+                        result.putAll(HttpMethod.GET, urls);
+                        break;
+                    case POST:
+                        result.putAll(HttpMethod.POST, urls);
+                        break;
+                    case PUT:
+                        result.putAll(HttpMethod.PUT, urls);
+                        break;
+                    case DELETE:
+                        result.putAll(HttpMethod.DELETE, urls);
+                        break;
+                }
+            });
+        }
+        return result;
+    }
+
 }

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

@@ -10,8 +10,8 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
 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 cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.web.filter.OncePerRequestFilter;

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

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/pom.xml

@@ -53,6 +53,13 @@
             <scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
         </dependency>
 
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-infra-api</artifactId> <!-- 需要使用它,进行操作日志的记录 -->
+            <version>${revision}</version>
+        </dependency>
+
         <!-- 服务保障相关 -->
         <dependency>
             <groupId>io.github.resilience4j</groupId>

+ 18 - 1
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/config/YudaoApiLogAutoConfiguration.java

@@ -2,11 +2,17 @@ package cn.iocoder.yudao.framework.apilog.config;
 
 import cn.iocoder.yudao.framework.apilog.core.filter.ApiAccessLogFilter;
 import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
+import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl;
+import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
+import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkServiceImpl;
+import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
 import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration;
-import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
+import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
+import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -17,10 +23,21 @@ import javax.servlet.Filter;
 @AutoConfigureAfter(YudaoWebAutoConfiguration.class)
 public class YudaoApiLogAutoConfiguration {
 
+    @Bean
+    public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) {
+        return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi);
+    }
+
+    @Bean
+    public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) {
+        return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi);
+    }
+
     /**
      * 创建 ApiAccessLogFilter Bean,记录 API 请求日志
      */
     @Bean
+    @ConditionalOnProperty(prefix = "yudao.access-log", value = "enable", matchIfMissing = true) // 允许使用 yudao.access-log.enable=false 禁用访问日志
     public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties,
                                                                          @Value("${spring.application.name}") String applicationName,
                                                                          ApiAccessLogFrameworkService apiAccessLogFrameworkService) {

+ 4 - 4
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/filter/ApiAccessLogFilter.java

@@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.apilog.core.filter;
 import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.extra.servlet.ServletUtil;
+import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLog;
 import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateReqDTO;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
@@ -66,16 +66,16 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
 
     private void createApiAccessLog(HttpServletRequest request, Date beginTime,
                                     Map<String, String> queryString, String requestBody, Exception ex) {
-        ApiAccessLogCreateReqDTO accessLog = new ApiAccessLogCreateReqDTO();
+        ApiAccessLog accessLog = new ApiAccessLog();
         try {
             this.buildApiAccessLogDTO(accessLog, request, beginTime, queryString, requestBody, ex);
-            apiAccessLogFrameworkService.createApiAccessLogAsync(accessLog);
+            apiAccessLogFrameworkService.createApiAccessLog(accessLog);
         } catch (Throwable th) {
             log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th);
         }
     }
 
-    private void buildApiAccessLogDTO(ApiAccessLogCreateReqDTO accessLog, HttpServletRequest request, Date beginTime,
+    private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, Date beginTime,
                                       Map<String, String> queryString, String requestBody, Exception ex) {
         // 处理用户信息
         accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));

+ 85 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLog.java

@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.framework.apilog.core.service;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+/**
+ * API 访问日志
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ApiAccessLog {
+
+    /**
+     * 链路追踪编号
+     */
+    private String traceId;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 应用名
+     */
+    @NotNull(message = "应用名不能为空")
+    private String applicationName;
+
+    /**
+     * 请求方法名
+     */
+    @NotNull(message = "http 请求方法不能为空")
+    private String requestMethod;
+    /**
+     * 访问地址
+     */
+    @NotNull(message = "访问地址不能为空")
+    private String requestUrl;
+    /**
+     * 请求参数
+     */
+    @NotNull(message = "请求参数不能为空")
+    private String requestParams;
+    /**
+     * 用户 IP
+     */
+    @NotNull(message = "ip 不能为空")
+    private String userIp;
+    /**
+     * 浏览器 UA
+     */
+    @NotNull(message = "User-Agent 不能为空")
+    private String userAgent;
+
+    /**
+     * 开始请求时间
+     */
+    @NotNull(message = "开始请求时间不能为空")
+    private Date beginTime;
+    /**
+     * 结束请求时间
+     */
+    @NotNull(message = "结束请求时间不能为空")
+    private Date endTime;
+    /**
+     * 执行时长,单位:毫秒
+     */
+    @NotNull(message = "执行时长不能为空")
+    private Integer duration;
+    /**
+     * 结果码
+     */
+    @NotNull(message = "错误码不能为空")
+    private Integer resultCode;
+    /**
+     * 结果提示
+     */
+    private String resultMsg;
+
+}

+ 2 - 6
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java

@@ -1,9 +1,5 @@
 package cn.iocoder.yudao.framework.apilog.core.service;
 
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateReqDTO;
-
-import javax.validation.Valid;
-
 /**
  * API 访问日志 Framework Service 接口
  *
@@ -14,8 +10,8 @@ public interface ApiAccessLogFrameworkService {
     /**
      * 创建 API 访问日志
      *
-     * @param createDTO 创建信息
+     * @param apiAccessLog API 访问日志
      */
-    void createApiAccessLogAsync(@Valid ApiAccessLogCreateReqDTO createDTO);
+    void createApiAccessLog(ApiAccessLog apiAccessLog);
 
 }

+ 28 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.framework.apilog.core.service;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
+import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.scheduling.annotation.Async;
+
+/**
+ * API 访问日志 Framework Service 实现类
+ *
+ * 基于 {@link ApiAccessLogApi} 服务,记录访问日志
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkService {
+
+    private final ApiAccessLogApi apiAccessLogApi;
+
+    @Override
+    @Async
+    public void createApiAccessLog(ApiAccessLog apiAccessLog) {
+        ApiAccessLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiAccessLog, ApiAccessLogCreateReqDTO.class);
+        apiAccessLogApi.createApiAccessLog(reqDTO);
+    }
+
+}

+ 107 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLog.java

@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.framework.apilog.core.service;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+/**
+ * API 错误日志
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ApiErrorLog {
+
+    /**
+     * 链路编号
+     */
+    private String traceId;
+    /**
+     * 账号编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 应用名
+     */
+    @NotNull(message = "应用名不能为空")
+    private String applicationName;
+
+    /**
+     * 请求方法名
+     */
+    @NotNull(message = "http 请求方法不能为空")
+    private String requestMethod;
+    /**
+     * 访问地址
+     */
+    @NotNull(message = "访问地址不能为空")
+    private String requestUrl;
+    /**
+     * 请求参数
+     */
+    @NotNull(message = "请求参数不能为空")
+    private String requestParams;
+    /**
+     * 用户 IP
+     */
+    @NotNull(message = "ip 不能为空")
+    private String userIp;
+    /**
+     * 浏览器 UA
+     */
+    @NotNull(message = "User-Agent 不能为空")
+    private String userAgent;
+
+    /**
+     * 异常时间
+     */
+    @NotNull(message = "异常时间不能为空")
+    private Date exceptionTime;
+    /**
+     * 异常名
+     */
+    @NotNull(message = "异常名不能为空")
+    private String exceptionName;
+    /**
+     * 异常发生的类全名
+     */
+    @NotNull(message = "异常发生的类全名不能为空")
+    private String exceptionClassName;
+    /**
+     * 异常发生的类文件
+     */
+    @NotNull(message = "异常发生的类文件不能为空")
+    private String exceptionFileName;
+    /**
+     * 异常发生的方法名
+     */
+    @NotNull(message = "异常发生的方法名不能为空")
+    private String exceptionMethodName;
+    /**
+     * 异常发生的方法所在行
+     */
+    @NotNull(message = "异常发生的方法所在行不能为空")
+    private Integer exceptionLineNumber;
+    /**
+     * 异常的栈轨迹异常的栈轨迹
+     */
+    @NotNull(message = "异常的栈轨迹不能为空")
+    private String exceptionStackTrace;
+    /**
+     * 异常导致的根消息
+     */
+    @NotNull(message = "异常导致的根消息不能为空")
+    private String exceptionRootCauseMessage;
+    /**
+     * 异常导致的消息
+     */
+    @NotNull(message = "异常导致的消息不能为空")
+    private String exceptionMessage;
+
+
+}

+ 2 - 6
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java

@@ -1,9 +1,5 @@
 package cn.iocoder.yudao.framework.apilog.core.service;
 
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateReqDTO;
-
-import javax.validation.Valid;
-
 /**
  * API 错误日志 Framework Service 接口
  *
@@ -14,8 +10,8 @@ public interface ApiErrorLogFrameworkService {
     /**
      * 创建 API 错误日志
      *
-     * @param createDTO 创建信息
+     * @param apiErrorLog API 错误日志
      */
-    void createApiErrorLogAsync(@Valid ApiErrorLogCreateReqDTO createDTO);
+    void createApiErrorLog(ApiErrorLog apiErrorLog);
 
 }

+ 28 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.framework.apilog.core.service;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
+import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.scheduling.annotation.Async;
+
+/**
+ * API 错误日志 Framework Service 实现类
+ *
+ * 基于 {@link ApiErrorLogApi} 服务,记录错误日志
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkService {
+
+    private final ApiErrorLogApi apiErrorLogApi;
+
+    @Override
+    @Async
+    public void createApiErrorLog(ApiErrorLog apiErrorLog) {
+        ApiErrorLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiErrorLog, ApiErrorLogCreateReqDTO.class);
+        apiErrorLogApi.createApiErrorLog(reqDTO);
+    }
+
+}

+ 14 - 12
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.framework.swagger.config;
 
+import cn.iocoder.yudao.framework.swagger.core.SpringFoxHandlerProviderBeanPostProcessor;
 import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
@@ -21,6 +21,7 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2;
 import java.util.Collections;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
 import static springfox.documentation.builders.RequestHandlerSelectors.basePackage;
 
 /**
@@ -38,28 +39,27 @@ import static springfox.documentation.builders.RequestHandlerSelectors.basePacka
 public class YudaoSwaggerAutoConfiguration {
 
     @Bean
-    @ConditionalOnMissingBean
-    public SwaggerProperties swaggerProperties() {
-        return new SwaggerProperties();
+    public SpringFoxHandlerProviderBeanPostProcessor springFoxHandlerProviderBeanPostProcessor() {
+        return new SpringFoxHandlerProviderBeanPostProcessor();
     }
 
     @Bean
-    public Docket createRestApi() {
-        SwaggerProperties properties = swaggerProperties();
+    public Docket createRestApi(SwaggerProperties properties) {
         // 创建 Docket 对象
         return new Docket(DocumentationType.SWAGGER_2)
-                // 用来创建该 API 的基本信息,展示在文档的页面中(自定义展示的信息)
+                // 用来创建该 API 的基本信息,展示在文档的页面中(自定义展示的信息)
                 .apiInfo(apiInfo(properties))
-                // 设置扫描指定 package 包下的
+                // 设置扫描指定 package 包下的
                 .select()
                 .apis(basePackage(properties.getBasePackage()))
-//                .apis(basePackage("cn.iocoder.yudao.module.infra")) // 可用于 swagger 无法展示时使用
+//                .apis(basePackage("cn.iocoder.yudao.module.system")) // 可用于 swagger 无法展示时使用
                 .paths(PathSelectors.any())
                 .build()
+                // ③ 安全上下文(认证)
                 .securitySchemes(securitySchemes())
-                .globalRequestParameters(globalRequestParameters())
                 .securityContexts(securityContexts())
-                .host("http://localhost:48080");
+                // ④ 全局参数(多租户 header)
+                .globalRequestParameters(globalRequestParameters());
     }
 
     // ========== apiInfo ==========
@@ -94,6 +94,7 @@ public class YudaoSwaggerAutoConfiguration {
     private static List<SecurityContext> securityContexts() {
         return Collections.singletonList(SecurityContext.builder()
                 .securityReferences(securityReferences())
+                // 通过 PathSelectors.regex("^(?!auth).*$"),排除包含 "auth" 的接口不需要使用securitySchemes
                 .forPaths(PathSelectors.regex("^(?!auth).*$"))
                 .build());
     }
@@ -109,7 +110,8 @@ public class YudaoSwaggerAutoConfiguration {
     // ========== globalRequestParameters ==========
 
     private static List<RequestParameter> globalRequestParameters() {
-        RequestParameterBuilder tenantParameter = new RequestParameterBuilder().name("tenant-id").description("租户编号")
+        RequestParameterBuilder tenantParameter = new RequestParameterBuilder()
+                .name(HEADER_TENANT_ID).description("租户编号")
                 .in(ParameterType.HEADER).example(new ExampleBuilder().value(1L).build());
         return Collections.singletonList(tenantParameter.build());
     }

+ 43 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/core/SpringFoxHandlerProviderBeanPostProcessor.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.framework.swagger.core;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
+import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
+import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
+
+import java.util.List;
+
+/**
+ * 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题
+ * 该问题对应的 issue 为 https://github.com/springfox/springfox/issues/3462
+ *
+ * @author 芋道源码
+ */
+public class SpringFoxHandlerProviderBeanPostProcessor implements BeanPostProcessor {
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+        if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
+            customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
+        }
+        return bean;
+    }
+
+    private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
+        // 移除,只保留 patternParser
+        List<T> copy = CollectionUtils.filterList(mappings, mapping -> mapping.getPatternParser() == null);
+        // 添加到 mappings 中
+        mappings.clear();
+        mappings.addAll(copy);
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
+        return (List<RequestMappingInfoHandlerMapping>)
+                ReflectUtil.getFieldValue(bean, "handlerMappings");
+    }
+
+}

+ 6 - 2
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/WebProperties.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.framework.web.config;
 
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
@@ -15,14 +17,16 @@ import javax.validation.constraints.NotNull;
 public class WebProperties {
 
     @NotNull(message = "APP API 不能为空")
-    private Api appApi;
+    private Api appApi = new Api("/app-api", "**.controller.app.**");
     @NotNull(message = "Admin API 不能为空")
-    private Api adminApi;
+    private Api adminApi = new Api("/admin-api", "**.controller.admin.**");
 
     @NotNull(message = "Admin UI 不能为空")
     private Ui adminUi;
 
     @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
     @Valid
     public static class Api {
 

+ 7 - 7
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java

@@ -3,14 +3,14 @@ package cn.iocoder.yudao.framework.web.core.handler;
 import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.extra.servlet.ServletUtil;
+import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLog;
+import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateReqDTO;
-import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
-import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import io.github.resilience4j.ratelimiter.RequestNotPermitted;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -229,18 +229,18 @@ public class GlobalExceptionHandler {
 
     private void createExceptionLog(HttpServletRequest req, Throwable e) {
         // 插入错误日志
-        ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
+        ApiErrorLog errorLog = new ApiErrorLog();
         try {
             // 初始化 errorLog
             initExceptionLog(errorLog, req, e);
             // 执行插入 errorLog
-            apiErrorLogFrameworkService.createApiErrorLogAsync(errorLog);
+            apiErrorLogFrameworkService.createApiErrorLog(errorLog);
         } catch (Throwable th) {
             log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(),  JsonUtils.toJsonString(errorLog), th);
         }
     }
 
-    private void initExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {
+    private void initExceptionLog(ApiErrorLog errorLog, HttpServletRequest request, Throwable e) {
         // 处理用户信息
         errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
         errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));

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

@@ -23,7 +23,7 @@ public class WebFrameworkUtils {
 
     private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
 
-    private static final String HEADER_TENANT_ID = "tenant-id";
+    public static final String HEADER_TENANT_ID = "tenant-id";
 
     private static WebProperties properties;
 

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

@@ -21,6 +21,13 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-common</artifactId>
         </dependency>
+
+        <!-- 参数校验 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <optional>true</optional>
+        </dependency>
     </dependencies>
 
 </project>

+ 16 - 6
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java

@@ -1,7 +1,5 @@
 package cn.iocoder.yudao.module.infra.api.file;
 
-import cn.hutool.core.util.IdUtil;
-
 /**
  * 文件 API 接口
  *
@@ -15,17 +13,29 @@ public interface FileApi {
      * @param content 文件内容
      * @return 文件路径
      */
-   default String createFile(byte[] content) throws Exception {
-       return createFile(IdUtil.fastUUID(), content);
-   }
+    default String createFile(byte[] content) {
+        return createFile(null, null, content);
+    }
+
+    /**
+     * 保存文件,并返回文件的访问路径
+     *
+     * @param path 文件路径
+     * @param content 文件内容
+     * @return 文件路径
+     */
+    default String createFile(String path, byte[] content) {
+        return createFile(null, path, content);
+    }
 
     /**
      * 保存文件,并返回文件的访问路径
      *
+     * @param name 文件名称
      * @param path 文件路径
      * @param content 文件内容
      * @return 文件路径
      */
-    String createFile(String path, byte[] content) throws Exception;
+    String createFile(String name, String path, byte[] content);
 
 }

+ 21 - 0
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiAccessLogApi.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.infra.api.logger;
+
+import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
+
+import javax.validation.Valid;
+
+/**
+ * API 访问日志的 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface ApiAccessLogApi {
+
+    /**
+     * 创建 API 访问日志
+     *
+     * @param createDTO 创建信息
+     */
+    void createApiAccessLog(@Valid ApiAccessLogCreateReqDTO createDTO);
+
+}

+ 21 - 0
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiErrorLogApi.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.infra.api.logger;
+
+import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
+
+import javax.validation.Valid;
+
+/**
+ * API 错误日志的 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface ApiErrorLogApi {
+
+    /**
+     * 创建 API 错误日志
+     *
+     * @param createDTO 创建信息
+     */
+    void createApiErrorLog(@Valid ApiErrorLogCreateReqDTO createDTO);
+
+}

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/dto/ApiAccessLogCreateReqDTO.java → yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/dto/ApiAccessLogCreateReqDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.apilog.core.service.dto;
+package cn.iocoder.yudao.module.infra.api.logger.dto;
 
 import lombok.Data;
 
@@ -6,7 +6,7 @@ import javax.validation.constraints.NotNull;
 import java.util.Date;
 
 /**
- * API 访问日志创建 DTO
+ * API 访问日志
  *
  * @author 芋道源码
  */

+ 3 - 4
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/dto/ApiErrorLogCreateReqDTO.java → yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/dto/ApiErrorLogCreateReqDTO.java

@@ -1,18 +1,16 @@
-package cn.iocoder.yudao.framework.apilog.core.service.dto;
+package cn.iocoder.yudao.module.infra.api.logger.dto;
 
 import lombok.Data;
-import lombok.experimental.Accessors;
 
 import javax.validation.constraints.NotNull;
 import java.util.Date;
 
 /**
- * API 错误日志创建 DTO
+ * API 错误日志
  *
  * @author 芋道源码
  */
 @Data
-@Accessors(chain = true)
 public class ApiErrorLogCreateReqDTO {
 
     /**
@@ -105,4 +103,5 @@ public class ApiErrorLogCreateReqDTO {
     @NotNull(message = "异常导致的消息不能为空")
     private String exceptionMessage;
 
+
 }

+ 2 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java

@@ -19,8 +19,8 @@ public class FileApiImpl implements FileApi {
     private FileService fileService;
 
     @Override
-    public String createFile(String path, byte[] content) throws Exception {
-        return fileService.createFile(path, content);
+    public String createFile(String name, String path, byte[] content) {
+        return fileService.createFile(name, path, content);
     }
 
 }

+ 27 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiAccessLogApiImpl.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.infra.api.logger;
+
+import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
+import cn.iocoder.yudao.module.infra.service.logger.ApiAccessLogService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * API 访问日志的 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class ApiAccessLogApiImpl implements ApiAccessLogApi {
+
+    @Resource
+    private ApiAccessLogService apiAccessLogService;
+
+    @Override
+    public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {
+        apiAccessLogService.createApiAccessLog(createDTO);
+    }
+
+}

+ 27 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiErrorLogApiImpl.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.infra.api.logger;
+
+import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
+import cn.iocoder.yudao.module.infra.service.logger.ApiErrorLogService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * API 访问日志的 API 接口
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class ApiErrorLogApiImpl implements ApiErrorLogApi {
+
+    @Resource
+    private ApiErrorLogService apiErrorLogService;
+
+    @Override
+    public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {
+        apiErrorLogService.createApiErrorLog(createDTO);
+    }
+
+}

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä