Browse Source

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

# Conflicts:
#	yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java
#	yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityDetailRespVO.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityRespVO.java
#	yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
#	yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
#	yudao-server/src/main/resources/application-local.yaml
YunaiV 1 năm trước cách đây
mục cha
commit
5913ee0706
100 tập tin đã thay đổi với 1798 bổ sung1301 xóa
  1. 32 26
      README.md
  2. 5 5
      pom.xml
  3. 58 49
      sql/mysql/ruoyi-vue-pro.sql
  4. 1 2
      sql/sqlserver/ruoyi-vue-pro.sql
  5. 29 24
      yudao-dependencies/pom.xml
  6. 2 2
      yudao-example/yudao-sso-demo-by-code/pom.xml
  7. 2 2
      yudao-example/yudao-sso-demo-by-password/pom.xml
  8. 1 0
      yudao-framework/yudao-common/pom.xml
  9. 0 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
  10. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  11. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/AbstractSmsClient.java
  12. 36 36
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java
  13. 3 25
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
  14. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJob.java
  15. 56 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java
  16. 0 58
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerDecorator.java
  17. 2 1
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java
  18. 0 42
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobTest.java
  19. 2 1
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/job/TestJob.java
  20. 1 1
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java
  21. 309 0
      yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java
  22. 0 50
      yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/FlowableUtils.java
  23. 10 2
      yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoQuartzAutoConfiguration.java
  24. 17 1
      yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java
  25. 0 1
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  26. 2 0
      yudao-framework/yudao-spring-boot-starter-protection/pom.xml
  27. 2 0
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/config/YudaoLock4jConfiguration.java
  28. 0 6
      yudao-framework/yudao-spring-boot-starter-redis/pom.xml
  29. 1 1
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/TimeoutRedisCacheManager.java
  30. 0 7
      yudao-framework/yudao-spring-boot-starter-web/pom.xml
  31. 24 33
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java
  32. 8 7
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
  33. 1 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java
  34. 0 1
      yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  35. 48 43
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
  36. 2 1
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java
  37. 25 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
  38. 25 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java
  39. 24 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java
  40. 16 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSimpleRespVO.java
  41. 22 15
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
  42. 8 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
  43. 5 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
  44. 2 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
  45. 2 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java
  46. 26 7
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
  47. 235 35
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
  48. 44 44
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
  49. 3 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
  50. 52 65
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
  51. 5 5
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm
  52. 33 15
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm
  53. 6 5
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm
  54. 16 16
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm
  55. 33 26
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
  56. 1 0
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
  57. 42 42
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
  58. 53 53
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  59. 11 11
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/vo/article/AppArticleRespVO.java
  60. 5 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/vo/category/AppArticleCategoryRespVO.java
  61. 14 14
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityDetailRespVO.java
  62. 10 10
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityRespVO.java
  63. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/help/AppBargainHelpCreateReqVO.java
  64. 4 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/help/AppBargainHelpRespVO.java
  65. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/record/AppBargainRecordCreateReqVO.java
  66. 5 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/record/AppBargainRecordSummaryRespVO.java
  67. 29 30
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  68. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java
  69. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/express/AppDeliveryExpressRespVO.java
  70. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
  71. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
  72. 31 31
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  73. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java
  74. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java
  75. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java
  76. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java
  77. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java
  78. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  79. 42 42
      yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java
  80. 22 19
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
  81. 4 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  82. 4 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java
  83. 0 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java
  84. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java
  85. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java
  86. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java
  87. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java
  88. 9 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java
  89. 52 83
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java
  90. 5 9
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
  91. 2 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
  92. 28 50
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java
  93. 2 2
      yudao-module-report/yudao-module-report-api/src/main/java/cn/iocoder/yudao/module/report/enums/ErrorCodeConstants.java
  94. 0 5
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApi.java
  95. 153 153
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  96. 0 11
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApiImpl.java
  97. 2 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  98. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java
  99. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/sms/SmsChannelConvert.java
  100. 0 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dict/DictDataDO.java

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 32 - 26
README.md


+ 5 - 5
pom.xml

@@ -19,8 +19,8 @@
         <!--        <module>yudao-module-bpm</module>-->
 <!--        <module>yudao-module-report</module>-->
 <!--        <module>yudao-module-mp</module>-->
-        <module>yudao-module-pay</module>
-        <module>yudao-module-mall</module>
+<!--        <module>yudao-module-pay</module>-->
+<!--        <module>yudao-module-mall</module>-->
         <!-- 示例项目 -->
         <module>yudao-example</module>
     </modules>
@@ -30,7 +30,7 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.8.1-snapshot</revision>
+        <revision>1.8.2-snapshot</revision>
         <!-- Maven 相关 -->
         <java.version>1.8</java.version>
         <maven.compiler.source>${java.version}</maven.compiler.source>
@@ -39,8 +39,8 @@
         <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
         <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
         <!-- 看看咋放到 bom 里 -->
-        <lombok.version>1.18.28</lombok.version>
-        <spring.boot.version>2.7.14</spring.boot.version>
+        <lombok.version>1.18.30</lombok.version>
+        <spring.boot.version>2.7.16</spring.boot.version>
         <mapstruct.version>1.5.5.Final</mapstruct.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 58 - 49
sql/mysql/ruoyi-vue-pro.sql


+ 1 - 2
sql/sqlserver/ruoyi-vue-pro.sql

@@ -5815,8 +5815,7 @@ GO
 
 CREATE TABLE [dbo].[system_dict_type]
 (
-    [
-    id]
+    [id]
     bigint
     IDENTITY
 (

+ 29 - 24
yudao-dependencies/pom.xml

@@ -14,16 +14,16 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.8.1-snapshot</revision>
+        <revision>1.8.2-snapshot</revision>
         <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
         <!-- 统一依赖管理 -->
-        <spring.boot.version>2.7.14</spring.boot.version>
+        <spring.boot.version>2.7.16</spring.boot.version>
         <!-- Web 相关 -->
-        <springdoc.version>1.6.15</springdoc.version>
-        <knife4j.version>4.1.0</knife4j.version>
+        <springdoc.version>1.7.0</springdoc.version>
+        <knife4j.version>4.3.0</knife4j.version>
         <servlet.versoin>2.5</servlet.versoin>
         <!-- DB 相关 -->
-        <druid.version>1.2.18</druid.version>
+        <druid.version>1.2.19</druid.version>
         <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.3.2</mybatis-plus-generator.version>
         <dynamic-datasource.version>3.6.1</dynamic-datasource.version>
@@ -44,33 +44,32 @@
         <!-- Bpm 工作流相关 -->
         <flowable.version>6.8.0</flowable.version>
         <!-- 工具类相关 -->
-        <captcha-plus.version>1.0.6</captcha-plus.version>
-        <jsoup.version>1.15.4</jsoup.version>
-        <lombok.version>1.18.28</lombok.version>
+        <captcha-plus.version>1.0.8</captcha-plus.version>
+        <jsoup.version>1.16.1</jsoup.version>
+        <lombok.version>1.18.30</lombok.version>
         <mapstruct.version>1.5.5.Final</mapstruct.version>
-        <hutool.version>5.8.20</hutool.version>
+        <hutool.version>5.8.22</hutool.version>
         <easyexcel.verion>3.3.2</easyexcel.verion>
         <velocity.version>2.3</velocity.version>
         <screw.version>1.0.5</screw.version>
         <fastjson.version>1.2.83</fastjson.version>
-        <guava.version>32.0.1-jre</guava.version>
+        <guava.version>32.1.2-jre</guava.version>
         <guice.version>5.1.0</guice.version>
         <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
         <commons-net.version>3.9.0</commons-net.version>
         <jsch.version>0.1.55</jsch.version>
         <tika-core.version>2.7.0</tika-core.version>
-        <netty-all.version>4.1.90.Final</netty-all.version>
         <ip2region.version>2.7.0</ip2region.version>
         <!-- 三方云服务相关 -->
-        <okio.version>3.0.0</okio.version>
-        <okhttp3.version>4.10.0</okhttp3.version>
+        <okio.version>3.5.0</okio.version>
+        <okhttp3.version>4.11.0</okhttp3.version>
         <commons-io.version>2.11.0</commons-io.version>
-        <minio.version>8.5.4</minio.version>
-        <aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
+        <minio.version>8.5.6</minio.version>
+        <aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>
         <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
-        <tencentcloud-sdk-java.version>3.1.758</tencentcloud-sdk-java.version>
-        <justauth.version>1.0.3</justauth.version>
-        <jimureport.version>1.5.8</jimureport.version>
+        <tencentcloud-sdk-java.version>3.1.853</tencentcloud-sdk-java.version>
+        <justauth.version>1.0.5</justauth.version>
+        <jimureport.version>1.6.1</jimureport.version>
         <xercesImpl.version>2.12.2</xercesImpl.version>
         <weixin-java.version>4.5.0</weixin-java.version>
     </properties>
@@ -233,6 +232,12 @@
                 <groupId>org.redisson</groupId>
                 <artifactId>redisson-spring-boot-starter</artifactId>
                 <version>${redisson.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.springframework.boot</groupId>
+                        <artifactId>spring-boot-starter-actuator</artifactId>
+                    </exclusion>
+                </exclusions>
             </dependency>
 
             <dependency>
@@ -337,6 +342,12 @@
                 <groupId>de.codecentric</groupId>
                 <artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
                 <version>${spring-boot-admin.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>de.codecentric</groupId>
+                        <artifactId>spring-boot-admin-server-cloud</artifactId>
+                    </exclusion>
+                </exclusions>
             </dependency>
             <dependency>
                 <groupId>de.codecentric</groupId>
@@ -520,12 +531,6 @@
             </dependency>
 
             <dependency>
-                <groupId>io.netty</groupId>
-                <artifactId>netty-all</artifactId>
-                <version>${netty-all.version}</version>
-            </dependency>
-
-            <dependency>
                 <groupId>com.xingyuv</groupId>
                 <artifactId>spring-boot-starter-captcha-plus</artifactId>
                 <version>${captcha-plus.version}</version>

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

@@ -21,7 +21,7 @@
         <maven.compiler.target>8</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <!-- 统一依赖管理 -->
-        <spring.boot.version>2.7.14</spring.boot.version>
+        <spring.boot.version>2.7.16</spring.boot.version>
     </properties>
 
     <dependencyManagement>
@@ -52,7 +52,7 @@
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
-            <version>5.8.20</version>
+            <version>5.8.22</version>
         </dependency>
 
         <dependency>

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

@@ -21,7 +21,7 @@
         <maven.compiler.target>8</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <!-- 统一依赖管理 -->
-        <spring.boot.version>2.7.14</spring.boot.version>
+        <spring.boot.version>2.7.16</spring.boot.version>
     </properties>
 
     <dependencyManagement>
@@ -52,7 +52,7 @@
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
-            <version>5.8.20</version>
+            <version>5.8.22</version>
         </dependency>
 
         <dependency>

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

@@ -137,6 +137,7 @@
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 

+ 0 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java

@@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import lombok.SneakyThrows;
-import lombok.experimental.UtilityClass;
 import lombok.extern.slf4j.Slf4j;
 
 import java.io.IOException;
@@ -23,7 +22,6 @@ import java.util.List;
  *
  * @author 芋道源码
  */
-@UtilityClass
 @Slf4j
 public class JsonUtils {
 

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

@@ -50,7 +50,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
      */
     public final void init() {
         doInit();
-        log.info("[init][客户端({}) 初始化完成]", getId());
+        log.debug("[init][客户端({}) 初始化完成]", getId());
     }
 
     /**

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/AbstractSmsClient.java

@@ -40,7 +40,7 @@ public abstract class AbstractSmsClient implements SmsClient {
      */
     public final void init() {
         doInit();
-        log.info("[init][配置({}) 初始化完成]", properties);
+        log.debug("[init][配置({}) 初始化完成]", properties);
     }
 
     /**

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

@@ -1,36 +1,36 @@
-package cn.iocoder.yudao.framework.social.config;
-
-import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
-import com.xingyuv.http.HttpUtil;
-import com.xingyuv.http.support.hutool.HutoolImpl;
-import com.xingyuv.jushauth.cache.AuthStateCache;
-import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-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.Primary;
-
-/**
- * 社交自动装配类
- *
- * @author timfruit
- * @date 2021-10-30
- */
-@Slf4j
-@AutoConfiguration
-@EnableConfigurationProperties(JustAuthProperties.class)
-public class YudaoSocialAutoConfiguration {
-
-    @Bean
-    @Primary
-    @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
-    public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
-        // 需要修改 HttpUtil 使用的实现,避免类报错
-        HttpUtil.setHttp(new HutoolImpl());
-        // 创建 YudaoAuthRequestFactory
-        return new YudaoAuthRequestFactory(properties, authStateCache);
-    }
-
-}
+package cn.iocoder.yudao.framework.social.config;
+
+import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
+import com.xingyuv.http.HttpUtil;
+import com.xingyuv.http.support.hutool.HutoolImpl;
+import com.xingyuv.jushauth.cache.AuthStateCache;
+import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+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.Primary;
+
+/**
+ * 社交自动装配类
+ *
+ * @author timfruit
+ * @date 2021-10-30
+ */
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(JustAuthProperties.class)
+public class YudaoSocialAutoConfiguration {
+
+    @Bean
+    @Primary
+    @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
+    public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
+        // 需要修改 HttpUtil 使用的实现,避免类报错
+        HttpUtil.setHttp(new HutoolImpl());
+        // 创建 YudaoAuthRequestFactory
+        return new YudaoAuthRequestFactory(properties, authStateCache);
+    }
+
+}

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

@@ -1,14 +1,11 @@
 package cn.iocoder.yudao.framework.tenant.config;
 
-import cn.hutool.core.annotation.AnnotationUtil;
 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
-import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
 import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
-import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
 import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
@@ -20,8 +17,6 @@ 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;
-import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -100,25 +95,8 @@ public class YudaoTenantAutoConfiguration {
     // ========== Job ==========
 
     @Bean
-    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
-    public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
-        return new BeanPostProcessor() {
-
-            @Override
-            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
-                if (!(bean instanceof JobHandler)) {
-                    return bean;
-                }
-                // 有 TenantJob 注解的情况下,才会进行处理
-                if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
-                    return bean;
-                }
-
-                // 使用 TenantJobHandlerDecorator 装饰
-                return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
-            }
-
-        };
+    public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {
+        return new TenantJobAspect(tenantFrameworkService);
     }
 
     // ========== Redis ==========

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJob.java

@@ -8,7 +8,7 @@ import java.lang.annotation.Target;
 /**
  * 多租户 Job 注解
  */
-@Target({ElementType.TYPE})
+@Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface TenantJob {
 }

+ 56 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.framework.tenant.core.job;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 多租户 JobHandler AOP
+ * 任务执行时,会按照租户逐个执行 Job 的逻辑
+ *
+ * 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。
+ *
+ * @author 芋道源码
+ */
+@Aspect
+@RequiredArgsConstructor
+@Slf4j
+public class TenantJobAspect {
+
+    private final TenantFrameworkService tenantFrameworkService;
+
+    @Around("@annotation(tenantJob)")
+    public String around(ProceedingJoinPoint joinPoint, TenantJob tenantJob) {
+        // 获得租户列表
+        List<Long> tenantIds = tenantFrameworkService.getTenantIds();
+        if (CollUtil.isEmpty(tenantIds)) {
+            return null;
+        }
+
+        // 逐个租户,执行 Job
+        Map<Long, String> results = new ConcurrentHashMap<>();
+        tenantIds.parallelStream().forEach(tenantId -> {
+            // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
+            TenantUtils.execute(tenantId, () -> {
+                try {
+                    joinPoint.proceed();
+                } catch (Throwable e) {
+                    results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));
+                }
+            });
+        });
+        return JsonUtils.toJsonString(results);
+    }
+
+}

+ 0 - 58
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerDecorator.java

@@ -1,58 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.core.job;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
-import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
-import lombok.AllArgsConstructor;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * 多租户 JobHandler 装饰器
- * 任务执行时,会按照租户逐个执行 Job 的逻辑
- *
- * 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。
- *
- * @author 芋道源码
- */
-@AllArgsConstructor
-public class TenantJobHandlerDecorator implements JobHandler {
-
-    private final TenantFrameworkService tenantFrameworkService;
-    /**
-     * 被装饰的 Job
-     */
-    private final JobHandler jobHandler;
-
-    @Override
-    public final String execute(String param) throws Exception {
-        // 获得租户列表
-        List<Long> tenantIds = tenantFrameworkService.getTenantIds();
-        if (CollUtil.isEmpty(tenantIds)) {
-            return null;
-        }
-
-        // 逐个租户,执行 Job
-        Map<Long, String> results = new ConcurrentHashMap<>();
-        tenantIds.parallelStream().forEach(tenantId -> { // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
-            try {
-                // 设置租户
-                TenantContextHolder.setTenantId(tenantId);
-                // 执行 Job
-                String result = jobHandler.execute(param);
-                // 添加结果
-                results.put(tenantId, result);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            } finally {
-                TenantContextHolder.clear();
-            }
-        });
-        return JsonUtils.toJsonString(results);
-    }
-
-}

+ 2 - 1
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.tenant.core.redis;
 
+import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.cache.Cache;
@@ -15,7 +16,7 @@ import org.springframework.data.redis.cache.RedisCacheWriter;
  * @author airhead
  */
 @Slf4j
-public class TenantRedisCacheManager extends RedisCacheManager {
+public class TenantRedisCacheManager extends TimeoutRedisCacheManager {
 
     public TenantRedisCacheManager(RedisCacheWriter cacheWriter,
                                    RedisCacheConfiguration defaultCacheConfiguration) {

+ 0 - 42
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobTest.java

@@ -1,42 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.core.job;
-
-import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
-import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
-import com.google.common.collect.Lists;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-/**
- * 验证 job 租户逻辑
- * {@link TenantJobHandlerDecorator}
- *
- * @author gaibu
- */
-public class TenantJobTest extends BaseMockitoUnitTest {
-
-    @Mock
-    TenantFrameworkService tenantFrameworkService;
-
-    @Test
-    public void test() throws Exception {
-        // 准备测试租户 id
-        List<Long> tenantIds = Lists.newArrayList(1L, 2L, 3L);
-        // mock 数据
-        Mockito.doReturn(tenantIds).when(tenantFrameworkService).getTenantIds();
-        // 准备测试任务
-        TestJob testJob = new TestJob();
-        // 创建任务装饰器
-        TenantJobHandlerDecorator tenantJobHandlerDecorator = new TenantJobHandlerDecorator(tenantFrameworkService, testJob);
-
-        // 执行任务
-        tenantJobHandlerDecorator.execute(null);
-
-        // 断言返回值
-        assertEquals(testJob.getTenantIds(), tenantIds);
-    }
-}

+ 2 - 1
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/job/TestJob.java

@@ -9,12 +9,12 @@ import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 @Component
-@TenantJob // 标记多租户
 public class TestJob implements JobHandler {
 
     private final List<Long> tenantIds = new CopyOnWriteArrayList<>();
 
     @Override
+    @TenantJob // 标记多租户
     public String execute(String param) throws Exception {
         tenantIds.add(TenantContextHolder.getTenantId());
         return "success";
@@ -24,4 +24,5 @@ public class TestJob implements JobHandler {
         CollUtil.sort(tenantIds, Long::compareTo);
         return tenantIds;
     }
+
 }

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java

@@ -30,7 +30,7 @@ public abstract class AbstractFileClient<Config extends FileClientConfig> implem
      */
     public final void init() {
         doInit();
-        log.info("[init][配置({}) 初始化完成]", config);
+        log.debug("[init][配置({}) 初始化完成]", config);
     }
 
     /**

+ 309 - 0
yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java

@@ -0,0 +1,309 @@
+package cn.iocoder.yudao.framework.flowable.core.util;
+
+import cn.hutool.core.collection.CollUtil;
+import org.flowable.bpmn.converter.BpmnXMLConverter;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+
+import java.util.*;
+
+/**
+ * 流程模型转操作工具类
+ */
+public class BpmnModelUtils {
+
+    /**
+     * 根据节点,获取入口连线
+     *
+     * @param source 起始节点
+     * @return 入口连线列表
+     */
+    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
+        if (source instanceof FlowNode) {
+            return ((FlowNode) source).getIncomingFlows();
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 根据节点,获取出口连线
+     *
+     * @param source 起始节点
+     * @return 出口连线列表
+     */
+    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
+        if (source instanceof FlowNode) {
+            return ((FlowNode) source).getOutgoingFlows();
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 获取流程元素信息
+     *
+     * @param model         bpmnModel 对象
+     * @param flowElementId 元素 ID
+     * @return 元素信息
+     */
+    public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
+        Process process = model.getMainProcess();
+        return process.getFlowElement(flowElementId);
+    }
+
+    /**
+     * 获得 BPMN 流程中,指定的元素们
+     *
+     * @param model
+     * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
+     * @return 元素们
+     */
+    public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
+        List<T> result = new ArrayList<>();
+        model.getProcesses().forEach(process -> {
+            process.getFlowElements().forEach(flowElement -> {
+                if (flowElement.getClass().isAssignableFrom(clazz)) {
+                    result.add((T) flowElement);
+                }
+            });
+        });
+        return result;
+    }
+
+    /**
+     * 比较 两个bpmnModel 是否相同
+     * @param oldModel  老的bpmn model
+     * @param newModel 新的bpmn model
+     */
+    public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
+        // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
+        return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
+    }
+
+    /**
+     * 把 bpmnModel 转换成 byte[]
+     * @param model  bpmnModel
+     */
+    public static byte[] getBpmnBytes(BpmnModel model) {
+        if (model == null) {
+            return new byte[0];
+        }
+        BpmnXMLConverter converter = new BpmnXMLConverter();
+        return converter.convertToXML(model);
+    }
+
+    // ========== 遍历相关的方法 ==========
+
+    /**
+     * 找到 source 节点之前的所有用户任务节点
+     *
+     * @param source          起始节点
+     * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+     * @param userTaskList    已找到的用户任务节点
+     * @return 用户任务节点 数组
+     */
+    public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
+        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+        // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+        if (source instanceof StartEvent && source.getSubProcess() != null) {
+            userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
+        }
+
+        // 根据类型,获取入口连线
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+        if (sequenceFlows == null) {
+            return userTaskList;
+        }
+        // 循环找到目标元素
+        for (SequenceFlow sequenceFlow : sequenceFlows) {
+            // 如果发现连线重复,说明循环了,跳过这个循环
+            if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                continue;
+            }
+            // 添加已经走过的连线
+            hasSequenceFlow.add(sequenceFlow.getId());
+            // 类型为用户节点,则新增父级节点
+            if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
+                userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
+            }
+            // 类型为子流程,则添加子流程开始节点出口处相连的节点
+            if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
+                // 获取子流程用户任务节点
+                List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
+                // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
+                if (CollUtil.isNotEmpty(childUserTaskList)) {
+                    userTaskList.addAll(childUserTaskList);
+                }
+            }
+            // 继续迭代
+            userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
+        }
+        return userTaskList;
+    }
+
+    /**
+     * 迭代获取子流程用户任务节点
+     *
+     * @param source          起始节点
+     * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+     * @param userTaskList    需要撤回的用户任务列表
+     * @return 用户任务节点
+     */
+    public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+
+        // 根据类型,获取出口连线
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+        if (sequenceFlows == null) {
+            return userTaskList;
+        }
+        // 循环找到目标元素
+        for (SequenceFlow sequenceFlow : sequenceFlows) {
+            // 如果发现连线重复,说明循环了,跳过这个循环
+            if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                continue;
+            }
+            // 添加已经走过的连线
+            hasSequenceFlow.add(sequenceFlow.getId());
+            // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
+            if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
+                userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
+                continue;
+            }
+            // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
+            if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+                List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
+                // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
+                if (CollUtil.isNotEmpty(childUserTaskList)) {
+                    userTaskList.addAll(childUserTaskList);
+                    continue;
+                }
+            }
+            // 继续迭代
+            userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
+        }
+        return userTaskList;
+    }
+
+
+    /**
+     * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
+     * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
+     *
+     * @param source          起始节点
+     * @param target          目标节点
+     * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
+     * @return 结果
+     */
+    public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
+        visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
+        // 不能是开始事件和子流程
+        if (source instanceof StartEvent && isInEventSubprocess(source)) {
+            return false;
+        }
+
+        // 根据类型,获取入口连线
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+        if (CollUtil.isEmpty(sequenceFlows)) {
+            return true;
+        }
+        // 循环找到目标元素
+        for (SequenceFlow sequenceFlow : sequenceFlows) {
+            // 如果发现连线重复,说明循环了,跳过这个循环
+            if (visitedElements.contains(sequenceFlow.getId())) {
+                continue;
+            }
+            // 添加已经走过的连线
+            visitedElements.add(sequenceFlow.getId());
+            // 这条线路存在目标节点,这条线路完成,进入下个线路
+            FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
+            if (target.getId().equals(sourceFlowElement.getId())) {
+                continue;
+            }
+            // 如果目标节点为并行网关,则不继续
+            if (sourceFlowElement instanceof ParallelGateway) {
+                return false;
+            }
+            // 否则就继续迭代
+            if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 判断当前节点是否属于不同的子流程
+     *
+     * @param flowElement 被判断的节点
+     * @return true 表示属于子流程
+     */
+    private static boolean isInEventSubprocess(FlowElement flowElement) {
+        FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
+        while (flowElementsContainer != null) {
+            if (flowElementsContainer instanceof EventSubProcess) {
+                return true;
+            }
+
+            if (flowElementsContainer instanceof FlowElement) {
+                flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
+            } else {
+                flowElementsContainer = null;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
+     *
+     * @param source          起始节点
+     * @param runTaskKeyList  正在运行的任务 Key,用于校验任务节点是否是正在运行的节点
+     * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+     * @param userTaskList    需要撤回的用户任务列表
+     * @return 子级任务节点列表
+     */
+    public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
+                                                            Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+        // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+        if (source instanceof StartEvent && source.getSubProcess() != null) {
+            userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
+        }
+
+        // 根据类型,获取出口连线
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+        if (sequenceFlows == null) {
+            return userTaskList;
+        }
+        // 循环找到目标元素
+        for (SequenceFlow sequenceFlow : sequenceFlows) {
+            // 如果发现连线重复,说明循环了,跳过这个循环
+            if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                continue;
+            }
+            // 添加已经走过的连线
+            hasSequenceFlow.add(sequenceFlow.getId());
+            // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
+            if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
+                userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
+                continue;
+            }
+            // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
+            if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+                List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
+                // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
+                if (CollUtil.isNotEmpty(childUserTaskList)) {
+                    userTaskList.addAll(childUserTaskList);
+                    continue;
+                }
+            }
+            // 继续迭代
+            userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
+        }
+        return userTaskList;
+    }
+
+}

+ 0 - 50
yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/FlowableUtils.java

@@ -1,14 +1,7 @@
 package cn.iocoder.yudao.framework.flowable.core.util;
 
-import org.flowable.bpmn.converter.BpmnXMLConverter;
-import org.flowable.bpmn.model.BpmnModel;
-import org.flowable.bpmn.model.FlowElement;
 import org.flowable.common.engine.impl.identity.Authentication;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 /**
  * Flowable 相关的工具方法
  *
@@ -26,49 +19,6 @@ public class FlowableUtils {
         Authentication.setAuthenticatedUserId(null);
     }
 
-    // ========== BPMN 相关的工具方法 ==========
-
-    /**
-     * 获得 BPMN 流程中,指定的元素们
-     *
-     * @param model
-     * @param clazz 指定元素。例如说,{@link org.flowable.bpmn.model.UserTask}、{@link org.flowable.bpmn.model.Gateway} 等等
-     * @return 元素们
-     */
-    public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
-        List<T> result = new ArrayList<>();
-        model.getProcesses().forEach(process -> {
-            process.getFlowElements().forEach(flowElement -> {
-                if (flowElement.getClass().isAssignableFrom(clazz)) {
-                    result.add((T) flowElement);
-                }
-            });
-        });
-        return result;
-    }
-
-    /**
-     * 比较 两个bpmnModel 是否相同
-     * @param oldModel  老的bpmn model
-     * @param newModel 新的bpmn model
-     */
-    public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
-        // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
-        return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
-    }
-
-    /**
-     * 把 bpmnModel 转换成 byte[]
-     * @param model  bpmnModel
-     */
-    public  static byte[] getBpmnBytes(BpmnModel model) {
-        if (model == null) {
-            return new byte[0];
-        }
-        BpmnXMLConverter converter = new BpmnXMLConverter();
-        return converter.convertToXML(model);
-    }
-
     // ========== Execution 相关的工具方法 ==========
 
     public static String formatCollectionVariable(String activityId) {

+ 10 - 2
yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoQuartzAutoConfiguration.java

@@ -1,21 +1,29 @@
 package cn.iocoder.yudao.framework.quartz.config;
 
 import cn.iocoder.yudao.framework.quartz.core.scheduler.SchedulerManager;
+import lombok.extern.slf4j.Slf4j;
 import org.quartz.Scheduler;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
+import java.util.Optional;
+
 /**
  * 定时任务 Configuration
  */
 @AutoConfiguration
 @EnableScheduling // 开启 Spring 自带的定时任务
+@Slf4j
 public class YudaoQuartzAutoConfiguration {
 
     @Bean
-    public SchedulerManager schedulerManager(Scheduler scheduler) {
-        return new SchedulerManager(scheduler);
+    public SchedulerManager schedulerManager(Optional<Scheduler> scheduler) {
+        if (!scheduler.isPresent()) {
+            log.info("[定时任务 - 已禁用][参考 https://doc.iocoder.cn/job/ 开启]");
+            return new SchedulerManager(null);
+        }
+        return new SchedulerManager(scheduler.get());
     }
 
 }

+ 17 - 1
yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java

@@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum;
 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker;
 import org.quartz.*;
 
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
+
 /**
  * {@link org.quartz.Scheduler} 的管理器,负责创建任务
  *
@@ -37,6 +40,7 @@ public class SchedulerManager {
     public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression,
                        Integer retryCount, Integer retryInterval)
             throws SchedulerException {
+        validateScheduler();
         // 创建 JobDetail 对象
         JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class)
                 .usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId)
@@ -61,6 +65,7 @@ public class SchedulerManager {
     public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression,
                           Integer retryCount, Integer retryInterval)
             throws SchedulerException {
+        validateScheduler();
         // 创建新 Trigger 对象
         Trigger newTrigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);
         // 修改调度
@@ -74,6 +79,7 @@ public class SchedulerManager {
      * @throws SchedulerException 删除异常
      */
     public void deleteJob(String jobHandlerName) throws SchedulerException {
+        validateScheduler();
         scheduler.deleteJob(new JobKey(jobHandlerName));
     }
 
@@ -84,6 +90,7 @@ public class SchedulerManager {
      * @throws SchedulerException 暂停异常
      */
     public void pauseJob(String jobHandlerName) throws SchedulerException {
+        validateScheduler();
         scheduler.pauseJob(new JobKey(jobHandlerName));
     }
 
@@ -94,6 +101,7 @@ public class SchedulerManager {
      * @throws SchedulerException 启动异常
      */
     public void resumeJob(String jobHandlerName) throws SchedulerException {
+        validateScheduler();
         scheduler.resumeJob(new JobKey(jobHandlerName));
         scheduler.resumeTrigger(new TriggerKey(jobHandlerName));
     }
@@ -108,11 +116,12 @@ public class SchedulerManager {
      */
     public void triggerJob(Long jobId, String jobHandlerName, String jobHandlerParam)
             throws SchedulerException {
+        validateScheduler();
+        // 触发任务
         JobDataMap data = new JobDataMap(); // 无需重试,所以不设置 retryCount 和 retryInterval
         data.put(JobDataKeyEnum.JOB_ID.name(), jobId);
         data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName);
         data.put(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam);
-        // 触发任务
         scheduler.triggerJob(new JobKey(jobHandlerName), data);
     }
 
@@ -127,4 +136,11 @@ public class SchedulerManager {
                 .build();
     }
 
+    private void validateScheduler() {
+        if (scheduler == null) {
+            throw exception0(NOT_IMPLEMENTED.getCode(),
+                    "[定时任务 - 已禁用][参考 https://doc.iocoder.cn/job/ 开启]");
+        }
+    }
+
 }

+ 0 - 1
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 import com.baomidou.mybatisplus.extension.toolkit.Db;

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-protection/pom.xml

@@ -26,11 +26,13 @@
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+            <optional>true</optional>
         </dependency>
 
         <dependency>
             <groupId>io.github.resilience4j</groupId>
             <artifactId>resilience4j-spring-boot2</artifactId>
+            <optional>true</optional>
         </dependency>
     </dependencies>
 

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

@@ -3,9 +3,11 @@ package cn.iocoder.yudao.framework.lock4j.config;
 import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
 import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.context.annotation.Bean;
 
 @AutoConfiguration(before = LockAutoConfiguration.class)
+@ConditionalOnClass(name = "com.baomidou.lock.annotation.Lock4j")
 public class YudaoLock4jConfiguration {
 
     @Bean

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

@@ -33,12 +33,6 @@
         </dependency>
 
         <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-all</artifactId>
-        </dependency>
-
-
-        <dependency>
             <groupId>com.fasterxml.jackson.datatype</groupId>
             <artifactId>jackson-datatype-jsr310</artifactId>
         </dependency>

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/TimeoutRedisCacheManager.java

@@ -45,7 +45,7 @@ public class TimeoutRedisCacheManager extends RedisCacheManager {
             Duration duration = parseDuration(names[1]);
             cacheConfig = cacheConfig.entryTtl(duration);
         }
-        return super.createRedisCache(names[0], cacheConfig);
+        return super.createRedisCache(name, cacheConfig);
     }
 
     /**

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

@@ -55,13 +55,6 @@
             <version>${revision}</version>
         </dependency>
 
-        <!-- 服务保障相关 -->
-        <dependency>
-            <groupId>io.github.resilience4j</groupId>
-            <artifactId>resilience4j-ratelimiter</artifactId>
-            <scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
-        </dependency>
-
         <!-- xss -->
         <dependency>
             <groupId>org.jsoup</groupId>

+ 24 - 33
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.jackson.config;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeDeserializer;
 import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeSerializer;
@@ -11,51 +12,41 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
 import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
 import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.util.List;
 
 @AutoConfiguration
 @Slf4j
 public class YudaoJacksonAutoConfiguration {
 
     @Bean
-    public BeanPostProcessor objectMapperBeanPostProcessor() {
-        return new BeanPostProcessor() {
-
-            @Override
-            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
-                if (!(bean instanceof ObjectMapper)) {
-                    return bean;
-                }
-                ObjectMapper objectMapper = (ObjectMapper) bean;
-                SimpleModule simpleModule = new SimpleModule();
-                /*
-                 * 1. 新增Long类型序列化规则,数值超过2^53-1,在JS会出现精度丢失问题,因此Long自动序列化为字符串类型
-                 * 2. 新增LocalDateTime序列化、反序列化规则
-                 */
-                simpleModule
-                        .addSerializer(Long.class, NumberSerializer.INSTANCE)
-                        .addSerializer(Long.TYPE, NumberSerializer.INSTANCE)
-                        .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)
-                        .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
-                        .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)
-                        .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
-                        .addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE)
-                        .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
-
-                objectMapper.registerModules(simpleModule);
-
-                JsonUtils.init(objectMapper);
-                log.info("初始化 jackson 自动配置");
-                return bean;
-            }
-        };
+    @SuppressWarnings("InstantiationOfUtilityClass")
+    public JsonUtils jsonUtils(List<ObjectMapper> objectMappers) {
+        // 1.1 创建 SimpleModule 对象
+        SimpleModule simpleModule = new SimpleModule();
+        simpleModule
+                // 新增 Long 类型序列化规则,数值超过 2^53-1,在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型
+                .addSerializer(Long.class, NumberSerializer.INSTANCE)
+                .addSerializer(Long.TYPE, NumberSerializer.INSTANCE)
+                .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)
+                .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
+                .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)
+                .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
+                // 新增 LocalDateTime 序列化、反序列化规则
+                .addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE)
+                .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
+        // 1.2 注册到 objectMapper
+        objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule));
+
+        // 2. 设置 objectMapper 到 JsonUtils {
+        JsonUtils.init(CollUtil.getFirst(objectMappers));
+        log.info("[init][初始化 JsonUtils 成功]");
+        return new JsonUtils();
     }
 
 }

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

@@ -11,7 +11,6 @@ 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;
 import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -33,6 +32,7 @@ import javax.validation.ConstraintViolationException;
 import javax.validation.ValidationException;
 import java.time.LocalDateTime;
 import java.util.Map;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
 
@@ -83,9 +83,6 @@ public class GlobalExceptionHandler {
         if (ex instanceof HttpRequestMethodNotSupportedException) {
             return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
         }
-        if (ex instanceof RequestNotPermitted) {
-            return requestNotPermittedExceptionHandler(request, (RequestNotPermitted) ex);
-        }
         if (ex instanceof ServiceException) {
             return serviceExceptionHandler((ServiceException) ex);
         }
@@ -186,8 +183,7 @@ public class GlobalExceptionHandler {
     /**
      * 处理 Resilience4j 限流抛出的异常
      */
-    @ExceptionHandler(value = RequestNotPermitted.class)
-    public CommonResult<?> requestNotPermittedExceptionHandler(HttpServletRequest req, RequestNotPermitted ex) {
+    public CommonResult<?> requestNotPermittedExceptionHandler(HttpServletRequest req, Throwable ex) {
         log.warn("[requestNotPermittedExceptionHandler][url({}) 访问过于频繁]", req.getRequestURL(), ex);
         return CommonResult.error(TOO_MANY_REQUESTS);
     }
@@ -226,7 +222,12 @@ public class GlobalExceptionHandler {
             return tableNotExistsResult;
         }
 
-        // 情况二:处理异常
+        // 情况二:部分特殊的库的处理
+        if (Objects.equals("io.github.resilience4j.ratelimiter.RequestNotPermitted", ex.getClass().getName())) {
+            return requestNotPermittedExceptionHandler(req, ex);
+        }
+
+        // 情况三:处理异常
         log.error("[defaultExceptionHandler]", ex);
         // 插入异常日志
         this.createExceptionLog(req, ex);

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java

@@ -21,6 +21,7 @@ import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.cr
 
 @AutoConfiguration
 @EnableConfigurationProperties(XssProperties.class)
+@ConditionalOnProperty(prefix = "yudao.xss", name = "enable", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
 public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
 
     /**

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

@@ -1,6 +1,5 @@
 cn.iocoder.yudao.framework.apilog.config.YudaoApiLogAutoConfiguration
 cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
-com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration
 cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
 cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
 cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration

+ 48 - 43
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java

@@ -4,61 +4,66 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 
 /**
  * Bpm 错误码枚举类
- *
+ * <p>
  * bpm 系统,使用 1-009-000-000 段
  */
 public interface ErrorCodeConstants {
 
-    // ==========  通用流程处理 模块 1009000000 ==========
-    ErrorCode HIGHLIGHT_IMG_ERROR = new ErrorCode(1009000002, "获取高亮流程图异常");
+    // ==========  通用流程处理 模块 1-009-000-000 ==========
+    ErrorCode HIGHLIGHT_IMG_ERROR = new ErrorCode(1_009_000_002, "获取高亮流程图异常");
 
-    // ========== OA 流程模块 1009001000 ==========
-    ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1009001001, "请假申请不存在");
-    ErrorCode OA_PM_POST_NOT_EXISTS = new ErrorCode(1009001002, "项目经理岗位未设置");
-    ErrorCode OA_DEPART_PM_POST_NOT_EXISTS = new ErrorCode(1009001009, "部门的项目经理不存在");
-    ErrorCode OA_BM_POST_NOT_EXISTS = new ErrorCode(1009001004, "部门经理岗位未设置");
-    ErrorCode OA_DEPART_BM_POST_NOT_EXISTS = new ErrorCode(1009001005, "部门的部门经理不存在");
-    ErrorCode OA_HR_POST_NOT_EXISTS = new ErrorCode(1009001006, "HR岗位未设置");
-    ErrorCode OA_DAY_LEAVE_ERROR = new ErrorCode(1009001007, "请假天数必须>=1");
+    // ========== OA 流程模块 1-009-001-000 ==========
+    ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1_009_001_001, "请假申请不存在");
+    ErrorCode OA_PM_POST_NOT_EXISTS = new ErrorCode(1_009_001_002, "项目经理岗位未设置");
+    ErrorCode OA_DEPART_PM_POST_NOT_EXISTS = new ErrorCode(1_009_001_009, "部门的项目经理不存在");
+    ErrorCode OA_BM_POST_NOT_EXISTS = new ErrorCode(1_009_001_004, "部门经理岗位未设置");
+    ErrorCode OA_DEPART_BM_POST_NOT_EXISTS = new ErrorCode(1_009_001_005, "部门的部门经理不存在");
+    ErrorCode OA_HR_POST_NOT_EXISTS = new ErrorCode(1_009_001_006, "HR岗位未设置");
+    ErrorCode OA_DAY_LEAVE_ERROR = new ErrorCode(1_009_001_007, "请假天数必须>=1");
 
-    // ========== 流程模型 1009002000 ==========
-    ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1009002000, "已经存在流程标识为【{}】的流程");
-    ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1009002001, "流程模型不存在");
-    ErrorCode MODEL_KEY_VALID = new ErrorCode(1009002002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!");
-    ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1009002003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置");
-    ErrorCode MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG = new ErrorCode(1009002004, "部署流程失败," +
+    // ========== 流程模型 1-009-002-000 ==========
+    ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1_009_002_000, "已经存在流程标识为【{}】的流程");
+    ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_009_002_001, "流程模型不存在");
+    ErrorCode MODEL_KEY_VALID = new ErrorCode(1_009_002_002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!");
+    ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1_009_002_003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置");
+    ErrorCode MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG = new ErrorCode(1_009_002_004, "部署流程失败," +
             "原因:用户任务({})未配置分配规则,请点击【修改流程】按钮进行配置");
-    ErrorCode MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS = new ErrorCode(1009003005, "流程定义部署失败,原因:信息未发生变化");
+    ErrorCode MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS = new ErrorCode(1_009_003_005, "流程定义部署失败,原因:信息未发生变化");
 
-    // ========== 流程定义 1009003000 ==========
-    ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1009003000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
-    ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1009003001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
-    ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1009003002, "流程定义不存在");
-    ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1009003003, "流程定义处于挂起状态");
-    ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1009003004, "流程定义的模型不存在");
+    // ========== 流程定义 1-009-003-000 ==========
+    ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
+    ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1_009_003_001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
+    ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_009_003_002, "流程定义不存在");
+    ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1_009_003_003, "流程定义处于挂起状态");
+    ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1_009_003_004, "流程定义的模型不存在");
 
-    // ========== 流程实例 1009004000 ==========
-    ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1009004000, "流程实例不存在");
-    ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1009004001, "流程取消失败,流程不处于运行中");
-    ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1009004002, "流程取消失败,该流程不是你发起的");
+    // ========== 流程实例 1-009-004-000 ==========
+    ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
+    ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
+    ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
 
-    // ========== 流程任务 1009005000 ==========
-    ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009005000, "审批任务失败,原因:该任务不处于未审批");
-    ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009005001, "审批任务失败,原因:该任务的审批人不是你");
+    // ========== 流程任务 1-009-005-000 ==========
+    ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
+    ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
+    ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
+    ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在");
+    ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
+    ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
+    ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
 
-    // ========== 流程任务分配规则 1009006000 ==========
-    ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1009006000, "流程({}) 的任务({}) 已经存在分配规则");
-    ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1009006001, "流程任务分配规则不存在");
-    ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1009006002, "只有流程模型的任务分配规则,才允许被修改");
-    ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1009006003, "操作失败,原因:找不到任务的审批人!");
-    ErrorCode TASK_ASSIGN_SCRIPT_NOT_EXISTS = new ErrorCode(1009006004, "操作失败,原因:任务分配脚本({}) 不存在");
+    // ========== 流程任务分配规则 1-009-006-000 ==========
+    ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则");
+    ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在");
+    ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1_009_006_002, "只有流程模型的任务分配规则,才允许被修改");
+    ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
+    ErrorCode TASK_ASSIGN_SCRIPT_NOT_EXISTS = new ErrorCode(1_009_006_004, "操作失败,原因:任务分配脚本({}) 不存在");
 
-    // ========== 动态表单模块 1009010000 ==========
-    ErrorCode FORM_NOT_EXISTS = new ErrorCode(1009010000, "动态表单不存在");
-    ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
+    // ========== 动态表单模块 1-009-010-000 ==========
+    ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");
+    ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1_009_010_001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
 
-    // ========== 用户组模块 1009011000 ==========
-    ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1009011000, "用户组不存在");
-    ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1009011001, "名字为【{}】的用户组已被禁用");
+    // ========== 用户组模块 1-009-011-000 ==========
+    ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1_009_011_000, "用户组不存在");
+    ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1_009_011_001, "名字为【{}】的用户组已被禁用");
 
 }

+ 2 - 1
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java

@@ -20,7 +20,8 @@ public enum BpmProcessInstanceResultEnum {
 
     // ========== 流程任务独有的状态 ==========
 
-    BACK(5, "退回/驳回");
+    BACK(5, "驳回"), // 退回
+    DELEGATE(6, "委派");
 
     /**
      * 结果

+ 25 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java

@@ -75,4 +75,29 @@ public class BpmTaskController {
         return success(true);
     }
 
+    @GetMapping("/get-return-list")
+    @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
+    @Parameter(name = "taskId", description = "当前任务ID", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<List<BpmTaskSimpleRespVO>> getReturnList(@RequestParam("taskId") String taskId) {
+        return success(taskService.getReturnTaskList(taskId));
+    }
+
+    @PutMapping("/return")
+    @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
+        taskService.returnTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @PutMapping("/delegate")
+    @Operation(summary = "委派任务", description = "用于【流程详情】的【委派】按钮。和向前【加签】有点像,唯一区别是【委托】没有单独创立任务")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> delegateTask(@Valid @RequestBody BpmTaskDelegateReqVO reqVO) {
+        // TODO @海:, 后面要有空格
+        taskService.delegateTask(reqVO,getLoginUserId());
+        return success(true);
+    }
+
 }

+ 25 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 委派流程任务的 Request VO")
+@Data
+public class BpmTaskDelegateReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "被委派人 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "被委派人 ID 不能为空")
+    private Long delegateUserId;
+
+    @Schema(description = "委派原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "做不了决定,需要你先帮忙瞅瞅")
+    @NotEmpty(message = "委派原因不能为空")
+    private String reason;
+
+}

+ 24 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 回退流程任务的 Request VO")
+@Data
+public class BpmTaskReturnReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "回退到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotEmpty(message = "回退到的任务 Key 不能为空")
+    private String targetDefinitionKey;
+
+    @Schema(description = "回退意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回")
+    @NotEmpty(message = "回退意见不能为空")
+    private String reason;
+
+}

+ 16 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSimpleRespVO.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 流程任务的精简 Response VO")
+@Data
+public class BpmTaskSimpleRespVO {
+
+    @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
+    private String definitionKey;
+
+    @Schema(description = "任务名词", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批")
+    private String name;
+
+}

+ 22 - 15
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java

@@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskSimpleRespVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import org.flowable.bpmn.model.FlowElement;
 import org.flowable.common.engine.impl.db.SuspensionState;
 import org.flowable.engine.history.HistoricProcessInstance;
 import org.flowable.engine.runtime.ProcessInstance;
@@ -55,8 +57,8 @@ public interface BpmTaskConvert {
     }
 
     default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks,
-        Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap,
-        Map<Long, AdminUserRespDTO> userMap) {
+                                                         Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap,
+                                                         Map<Long, AdminUserRespDTO> userMap) {
         return CollectionUtils.convertList(tasks, task -> {
             BpmTaskDonePageItemRespVO respVO = convert2(task);
             BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
@@ -82,8 +84,8 @@ public interface BpmTaskConvert {
     BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser);
 
     default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks,
-        Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance,
-        Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
+                                             Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance,
+                                             Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
         return CollectionUtils.convertList(tasks, task -> {
             BpmTaskRespVO respVO = convert3(task);
             BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
@@ -113,29 +115,34 @@ public interface BpmTaskConvert {
     void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to);
 
     @Mappings({@Mapping(source = "processInstance.id", target = "id"),
-        @Mapping(source = "processInstance.name", target = "name"),
-        @Mapping(source = "processInstance.startUserId", target = "startUserId"),
-        @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
-        @Mapping(source = "startUser.nickname", target = "startUserNickname")})
+            @Mapping(source = "processInstance.name", target = "name"),
+            @Mapping(source = "processInstance.startUserId", target = "startUserId"),
+            @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
+            @Mapping(source = "startUser.nickname", target = "startUserNickname")})
     BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance,
-        AdminUserRespDTO startUser);
+                                                      AdminUserRespDTO startUser);
 
     default BpmTaskExtDO convert2TaskExt(Task task) {
         BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId())
-            .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName())
-            .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId());
+                .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName())
+                .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId());
         taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime()));
         return taskExtDO;
     }
 
     default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
-        Task task) {
+                                                        Task task) {
         BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
         reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId())
-            .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId())
-            .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName())
-            .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()));
+                .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId())
+                .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName())
+                .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()));
         return reqDTO;
     }
 
+    default List<BpmTaskSimpleRespVO> convertList(List<? extends FlowElement> elementList) {
+        return CollectionUtils.convertList(elementList, element -> new BpmTaskSimpleRespVO()
+                .setName(element.getName())
+                .setDefinitionKey(element.getId()));
+    }
 }

+ 8 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java

@@ -74,4 +74,12 @@ public interface BpmModelService {
      */
     BpmnModel getBpmnModel(String id);
 
+    /**
+     * 获得流程定义编号对应的 BPMN Model
+     *
+     * @param processDefinitionId 流程定义编号
+     * @return BPMN Model
+     */
+    BpmnModel getBpmnModelByDefinitionId(String processDefinitionId);
+
 }

+ 5 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java

@@ -233,6 +233,11 @@ public class BpmModelServiceImpl implements BpmModelService {
         return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
     }
 
+    @Override
+    public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
+        return repositoryService.getBpmnModel(processDefinitionId);
+    }
+
     private void checkKeyNCName(String key) {
         if (!ValidationUtils.isXmlNCName(key)) {
             throw exception(MODEL_KEY_VALID);

+ 2 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java

@@ -5,7 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
-import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
@@ -200,7 +200,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
         BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes());
         BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId());
         // 对比字节变化
-        if (!FlowableUtils.equals(oldModel, newModel)) {
+        if (!BpmnModelUtils.equals(oldModel, newModel)) {
             return false;
         }
         // 最终发现都一致,则返回 true

+ 2 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java

@@ -7,7 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
-import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
@@ -114,7 +114,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
             return Collections.emptyList();
         }
         // 获得用户任务,只有用户任务才可以设置分配规则
-        List<UserTask> userTasks = FlowableUtils.getBpmnModelElements(model, UserTask.class);
+        List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(model, UserTask.class);
         if (CollUtil.isEmpty(userTasks)) {
             return Collections.emptyList();
         }

+ 26 - 7
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.bpm.service.task;
 
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
@@ -23,7 +22,6 @@ public interface BpmTaskService {
      *
      * @param userId    用户编号
      * @param pageReqVO 分页请求
-     *
      * @return 流程任务分页
      */
     PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageReqVO);
@@ -33,7 +31,6 @@ public interface BpmTaskService {
      *
      * @param userId    用户编号
      * @param pageReqVO 分页请求
-     *
      * @return 流程任务分页
      */
     PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageReqVO);
@@ -42,19 +39,17 @@ public interface BpmTaskService {
      * 获得流程任务 Map
      *
      * @param processInstanceIds 流程实例的编号数组
-     *
      * @return 流程任务 Map
      */
     default Map<String, List<Task>> getTaskMapByProcessInstanceIds(List<String> processInstanceIds) {
         return CollectionUtils.convertMultiMap(getTasksByProcessInstanceIds(processInstanceIds),
-            Task::getProcessInstanceId);
+                Task::getProcessInstanceId);
     }
 
     /**
      * 获得流程任务列表
      *
      * @param processInstanceIds 流程实例的编号数组
-     *
      * @return 流程任务列表
      */
     List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds);
@@ -63,7 +58,6 @@ public interface BpmTaskService {
      * 获得指令流程实例的流程任务列表,包括所有状态的
      *
      * @param processInstanceId 流程实例的编号
-     *
      * @return 流程任务列表
      */
     List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId);
@@ -128,4 +122,29 @@ public interface BpmTaskService {
      */
     void updateTaskExtAssign(Task task);
 
+    /**
+     * 获取当前任务的可回退的流程集合
+     *
+     * @param taskId 当前的任务 ID
+     * @return 可以回退的节点列表
+     */
+    List<BpmTaskSimpleRespVO> getReturnTaskList(String taskId);
+
+    /**
+     * 将任务回退到指定的 targetDefinitionKey 位置
+     *
+     * @param userId 用户编号
+     * @param reqVO 回退的任务key和当前所在的任务ID
+     */
+    void returnTask(Long userId, BpmTaskReturnReqVO reqVO);
+
+    // TODO @海:userId 放前面
+    /**
+     * 将指定任务委派给其他人处理,等接收人处理后再回到原审批人手中审批
+     *
+     * @param reqVO  被委派人和被委派的任务编号理由参数
+     * @param userId 用户编号
+     */
+    void delegateTask(BpmTaskDelegateReqVO reqVO, Long userId);
+
 }

+ 235 - 35
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -1,29 +1,36 @@
 package cn.iocoder.yudao.module.bpm.service.task;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
+import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
 import org.flowable.engine.HistoryService;
+import org.flowable.engine.RuntimeService;
 import org.flowable.engine.TaskService;
 import org.flowable.engine.history.HistoricProcessInstance;
 import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.task.api.DelegationState;
 import org.flowable.task.api.Task;
 import org.flowable.task.api.TaskQuery;
 import org.flowable.task.api.history.HistoricTaskInstance;
@@ -32,6 +39,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.util.Assert;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
@@ -39,8 +47,7 @@ import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
 
 /**
@@ -57,23 +64,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     private TaskService taskService;
     @Resource
     private HistoryService historyService;
+    @Resource
+    private RuntimeService runtimeService;
 
     @Resource
     private BpmProcessInstanceService processInstanceService;
     @Resource
+    private BpmModelService bpmModelService;
+    @Resource
+    private BpmMessageService messageService;
+
+    @Resource
     private AdminUserApi adminUserApi;
     @Resource
     private DeptApi deptApi;
+
     @Resource
     private BpmTaskExtMapper taskExtMapper;
-    @Resource
-    private BpmMessageService messageService;
 
     @Override
     public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
         // 查询待办任务
         TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己
-            .orderByTaskCreateTime().desc(); // 创建时间倒序
+                .orderByTaskCreateTime().desc(); // 创建时间倒序
         if (StrUtil.isNotBlank(pageVO.getName())) {
             taskQuery.taskNameLike("%" + pageVO.getName() + "%");
         }
@@ -91,21 +104,21 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
         // 获得 ProcessInstance Map
         Map<String, ProcessInstance> processInstanceMap =
-            processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
+                processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
         // 获得 User Map
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
-            convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
+                convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
         // 拼接结果
         return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap),
-            taskQuery.count());
+                taskQuery.count());
     }
 
     @Override
     public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) {
         // 查询已办任务
         HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
-            .taskAssignee(String.valueOf(userId)) // 分配给自己
-            .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
+                .taskAssignee(String.valueOf(userId)) // 分配给自己
+                .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
         if (StrUtil.isNotBlank(pageVO.getName())) {
             taskQuery.taskNameLike("%" + pageVO.getName() + "%");
         }
@@ -123,19 +136,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
         // 获得 TaskExtDO Map
         List<BpmTaskExtDO> bpmTaskExtDOs =
-            taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
+                taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
         Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
         // 获得 ProcessInstance Map
         Map<String, HistoricProcessInstance> historicProcessInstanceMap =
-            processInstanceService.getHistoricProcessInstanceMap(
-                convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
+                processInstanceService.getHistoricProcessInstanceMap(
+                        convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
         // 获得 User Map
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
-            convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
+                convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
         // 拼接结果
         return new PageResult<>(
-            BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
-            taskQuery.count());
+                BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
+                taskQuery.count());
     }
 
     @Override
@@ -176,27 +189,58 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) {
-        // 校验任务存在
-        Task task = checkTask(userId, reqVO.getId());
-        // 校验流程实例存在
+        // 1.1 校验任务存在
+        Task task = validateTask(userId, reqVO.getId());
+        // 1.2 校验流程实例存在
         ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
         if (instance == null) {
             throw exception(PROCESS_INSTANCE_NOT_EXISTS);
         }
 
+        // 情况一:被委派的任务,不调用 complete 去完成任务
+        if (DelegationState.PENDING.equals(task.getDelegationState())) {
+            approveDelegateTask(reqVO, task);
+            return;
+        }
+
+        // 情况二:自己审批的任务,调用 complete 去完成任务
         // 完成任务,审批通过
         taskService.complete(task.getId(), instance.getProcessVariables());
-
         // 更新任务拓展表为通过
         taskExtMapper.updateByTaskId(
-            new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
-                .setReason(reqVO.getReason()));
+                new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
+                        .setReason(reqVO.getReason()));
+    }
+
+    /**
+     * 审批被委派的任务
+     *
+     * @param reqVO 前端请求参数,包含当前任务ID,审批意见等
+     * @param task  当前被审批的任务
+     */
+    private void approveDelegateTask(BpmTaskApproveReqVO reqVO, Task task) {
+        // 1. 添加审批意见
+        AdminUserRespDTO currentUser = adminUserApi.getUser(WebFrameworkUtils.getLoginUserId());
+        AdminUserRespDTO sourceApproveUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner()));
+        Assert.notNull(sourceApproveUser, "委派任务找不到原审批人,需要检查数据");
+        String comment = StrUtil.format("[{}]完成委派任务,任务重新回到[{}]手中,审批意见为:{}", currentUser.getNickname(),
+                sourceApproveUser.getNickname(), reqVO.getReason());
+        taskService.addComment(reqVO.getId(), task.getProcessInstanceId(),
+                BpmProcessInstanceResultEnum.DELEGATE.getResult().toString(), comment);
+
+        // 2.1 调用 resolveTask 完成任务。
+        // 底层调用 TaskHelper.changeTaskAssignee(task, task.getOwner()):将 owner 设置为 assignee
+        taskService.resolveTask(task.getId());
+        // 2.2 更新任务拓展表为【处理中】
+        taskExtMapper.updateByTaskId(
+                new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult())
+                        .setReason(reqVO.getReason()));
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) {
-        Task task = checkTask(userId, reqVO.getId());
+        Task task = validateTask(userId, reqVO.getId());
         // 校验流程实例存在
         ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
         if (instance == null) {
@@ -208,14 +252,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
         // 更新任务拓展表为不通过
         taskExtMapper.updateByTaskId(
-            new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
-                    .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
+                new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
+                        .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
     }
 
     @Override
     public void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO) {
         // 校验任务存在
-        Task task = checkTask(userId, reqVO.getId());
+        Task task = validateTask(userId, reqVO.getId());
         // 更新负责人
         updateTaskAssignee(task.getId(), reqVO.getAssigneeUserId());
     }
@@ -231,13 +275,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
      * @param userId 用户 id
      * @param taskId task id
      */
-    private Task checkTask(Long userId, String taskId) {
+    private Task validateTask(Long userId, String taskId) {
         Task task = getTask(taskId);
         if (task == null) {
-            throw exception(TASK_COMPLETE_FAIL_NOT_EXISTS);
+            throw exception(TASK_NOT_EXISTS);
         }
         if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) {
-            throw exception(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
+            throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
         }
         return task;
     }
@@ -245,7 +289,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     @Override
     public void createTaskExt(Task task) {
         BpmTaskExtDO taskExtDO =
-            BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
+                BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
         taskExtMapper.insert(taskExtDO);
     }
 
@@ -293,27 +337,183 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     @Override
     public void updateTaskExtAssign(Task task) {
         BpmTaskExtDO taskExtDO =
-            new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId());
+                new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId());
         taskExtMapper.updateByTaskId(taskExtDO);
         // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
             @Override
             public void afterCommit() {
                 ProcessInstance processInstance =
-                    processInstanceService.getProcessInstance(task.getProcessInstanceId());
+                        processInstanceService.getProcessInstance(task.getProcessInstanceId());
                 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
                 messageService.sendMessageWhenTaskAssigned(
-                    BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
+                        BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
             }
         });
     }
 
     private Task getTask(String id) {
-        return taskService.createTaskQuery().taskId(id).singleResult();
+        Task task = taskService.createTaskQuery().taskId(id).singleResult();
+        if (null == task) {
+            throw exception(TASK_NOT_EXISTS);
+        }
+        return task;
     }
 
     private HistoricTaskInstance getHistoricTask(String id) {
         return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult();
     }
 
+    @Override
+    public List<BpmTaskSimpleRespVO> getReturnTaskList(String taskId) {
+        // 1. 校验当前任务 task 存在
+        Task task = getTask(taskId);
+        if (task == null) {
+            throw exception(TASK_NOT_EXISTS);
+        }
+        // 根据流程定义获取流程模型信息
+        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
+        FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
+        if (source == null) {
+            throw exception(TASK_NOT_EXISTS);
+        }
+
+        // 2.1 查询该任务的前置任务节点的 key 集合
+        List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null);
+        if (CollUtil.isEmpty(previousUserList)) {
+            return Collections.emptyList();
+        }
+        // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
+        previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
+        return BpmTaskConvert.INSTANCE.convertList(previousUserList);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) {
+        // 1.1 当前任务 task
+        Task task = validateTask(userId, reqVO.getId());
+        if (task.isSuspended()) {
+            throw exception(TASK_IS_PENDING);
+        }
+        // 1.2 校验源头和目标节点的关系,并返回目标元素
+        FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), reqVO.getTargetDefinitionKey(), task.getProcessDefinitionId());
+
+        // 2. 调用 flowable 框架的回退逻辑
+        returnTask0(task, targetElement, reqVO);
+
+        // 3. 更新任务扩展表
+        taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId())
+                .setResult(BpmProcessInstanceResultEnum.BACK.getResult())
+                .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
+    }
+
+    /**
+     * 回退流程节点时,校验目标任务节点是否可回退
+     *
+     * @param sourceKey           当前任务节点 Key
+     * @param targetKey           目标任务节点 key
+     * @param processDefinitionId 当前流程定义 ID
+     * @return 目标任务节点元素
+     */
+    private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
+        // 1.1 获取流程模型信息
+        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
+        // 1.3 获取当前任务节点元素
+        FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
+        // 1.3 获取跳转的节点元素
+        FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey);
+        if (target == null) {
+            throw exception(TASK_TARGET_NODE_NOT_EXISTS);
+        }
+
+        // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
+        if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
+            throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
+        }
+        return target;
+    }
+
+    /**
+     * 执行回退逻辑
+     *
+     * @param currentTask          当前回退的任务
+     * @param targetElement 需要回退到的目标任务
+     * @param reqVO         前端参数封装
+     */
+    public void returnTask0(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
+        // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤
+        // 1.1 获取所有正常进行的任务节点 Key
+        List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
+        List<String> runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey);
+        // 1.2 通过 targetElement 的出口连线,计算在 runTaskKeyList 有哪些 key 需要被撤回
+        // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F
+        List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
+        List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
+
+        // 2. 给当前要被回退的 task 数组,设置回退意见
+        taskList.forEach(task -> {
+            // 需要排除掉,不需要设置回退意见的任务
+            if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
+                return;
+            }
+            taskService.addComment(task.getId(), currentTask.getProcessInstanceId(),
+                    BpmProcessInstanceResultEnum.BACK.getResult().toString(), reqVO.getReason());
+        });
+
+        // 3. 执行驳回
+        runtimeService.createChangeActivityStateBuilder()
+                .processInstanceId(currentTask.getProcessInstanceId())
+                .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
+                        reqVO.getTargetDefinitionKey()) // targetKey 跳转到的节点(1)
+                .changeState();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delegateTask(BpmTaskDelegateReqVO reqVO, Long userId) {
+        // 1.1 校验任务
+        Task task = validateTaskCanDelegate(userId, reqVO);
+        // 1.2 校验目标用户存在
+        AdminUserRespDTO delegateUser = adminUserApi.getUser(reqVO.getDelegateUserId());
+        if (delegateUser == null) {
+            throw exception(TASK_DELEGATE_FAIL_USER_NOT_EXISTS);
+        }
+
+        // 2. 添加审批意见
+        AdminUserRespDTO currentUser = adminUserApi.getUser(userId);
+        String comment = StrUtil.format("[{}]将任务委派给[{}],委派理由为:{}", currentUser.getNickname(),
+                delegateUser.getNickname(), reqVO.getReason());
+        String taskId = reqVO.getId();
+        // TODO 海:后面改;感觉 comment 应该 type 做个枚举;不和 result 耦合在一起;
+        taskService.addComment(taskId, task.getProcessInstanceId(),
+                BpmProcessInstanceResultEnum.DELEGATE.getResult().toString(), comment);
+
+        // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
+        taskService.setOwner(taskId, task.getAssignee());
+        // 3.2 执行委派,将任务委派给 receiveId
+        taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
+        // 3.3 更新任务拓展表为【委派】
+        taskExtMapper.updateByTaskId(
+                new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.DELEGATE.getResult())
+                        .setReason(reqVO.getReason()));
+    }
+
+    /**
+     * 校验任务委派参数
+     *
+     * @param userId 用户编号
+     * @param reqVO 任务编号,接收人ID
+     * @return 当前任务信息
+     */
+    private Task validateTaskCanDelegate(Long userId, BpmTaskDelegateReqVO reqVO) {
+        // 校验任务
+        Task task = validateTask(userId, reqVO.getId());
+        // 校验当前审批人和被委派人不是同一人
+        if (task.getAssignee().equals(reqVO.getDelegateUserId().toString())) {
+            throw exception(TASK_DELEGATE_FAIL_USER_REPEAT);
+        }
+        return task;
+    }
+
 }

+ 44 - 44
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java

@@ -9,49 +9,49 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== 参数配置 1001000000 ==========
-    ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1001000001, "参数配置不存在");
-    ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
-    ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置");
-    ErrorCode CONFIG_GET_VALUE_ERROR_IF_VISIBLE = new ErrorCode(1001000004, "获取参数配置失败,原因:不允许获取不可见配置");
-
-    // ========== 定时任务 1001001000 ==========
-    ErrorCode JOB_NOT_EXISTS = new ErrorCode(1001001000, "定时任务不存在");
-    ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1001001001, "定时任务的处理器已经存在");
-    ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1001001002, "只允许修改为开启或者关闭状态");
-    ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1001001003, "定时任务已经处于该状态,无需修改");
-    ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1001001004, "只有开启状态的任务,才可以修改");
-    ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1001001005, "CRON 表达式不正确");
-
-    // ========== API 错误日志 1001002000 ==========
-    ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1001002000, "API 错误日志不存在");
-    ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1001002001, "API 错误日志已处理");
-
-    // ========= 文件相关 1001003000=================
-    ErrorCode FILE_PATH_EXISTS = new ErrorCode(1001003000, "文件路径已存在");
-    ErrorCode FILE_NOT_EXISTS = new ErrorCode(1001003001, "文件不存在");
-    ErrorCode FILE_IS_EMPTY = new ErrorCode(1001003002, "文件为空");
-
-    // ========== 代码生成器 1001004000 ==========
-    ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1003001000, "表定义已经存在");
-    ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1003001001, "导入的表不存在");
-    ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1003001002, "导入的字段不存在");
-    ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1003001004, "表定义不存在");
-    ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1003001005, "字段义不存在");
-    ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1003001006, "同步的字段不存在");
-    ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1003001007, "同步失败,不存在改变");
-    ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1003001008, "数据库的表注释未填写");
-    ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1003001009, "数据库的表字段({})注释未填写");
-
-    // ========== 字典类型(测试)1001005000 ==========
-    ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1001005000, "测试示例不存在");
-
-    // ========== 文件配置 1001006000 ==========
-    ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1001006000, "文件配置不存在");
-    ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1001006001, "该文件配置不允许删除,原因:它是主配置,删除会导致无法上传文件");
-
-    // ========== 数据源配置 1001007000 ==========
-    ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1001007000, "数据源配置不存在");
-    ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1001007001, "数据源配置不正确,无法进行连接");
+    // ========== 参数配置 1-001-000-000 ==========
+    ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1_001_000_001, "参数配置不存在");
+    ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1_001_000_002, "参数配置 key 重复");
+    ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1_001_000_003, "不能删除类型为系统内置的参数配置");
+    ErrorCode CONFIG_GET_VALUE_ERROR_IF_VISIBLE = new ErrorCode(1_001_000_004, "获取参数配置失败,原因:不允许获取不可见配置");
+
+    // ========== 定时任务 1-001-001-000 ==========
+    ErrorCode JOB_NOT_EXISTS = new ErrorCode(1_001_001_000, "定时任务不存在");
+    ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1_001_001_001, "定时任务的处理器已经存在");
+    ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1_001_001_002, "只允许修改为开启或者关闭状态");
+    ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1_001_001_003, "定时任务已经处于该状态,无需修改");
+    ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1_001_001_004, "只有开启状态的任务,才可以修改");
+    ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1_001_001_005, "CRON 表达式不正确");
+
+    // ========== API 错误日志 1-001-002-000 ==========
+    ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1_001_002_000, "API 错误日志不存在");
+    ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1_001_002_001, "API 错误日志已处理");
+
+    // ========= 文件相关 1-001-003-000 =================
+    ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在");
+    ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在");
+    ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空");
+
+    // ========== 代码生成器 1-001-004-000 ==========
+    ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_003_001_000, "表定义已经存在");
+    ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1_003_001_001, "导入的表不存在");
+    ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1_003_001_002, "导入的字段不存在");
+    ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1_003_001_004, "表定义不存在");
+    ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1_003_001_005, "字段义不存在");
+    ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1_003_001_006, "同步的字段不存在");
+    ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1_003_001_007, "同步失败,不存在改变");
+    ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1_003_001_008, "数据库的表注释未填写");
+    ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1_003_001_009, "数据库的表字段({})注释未填写");
+
+    // ========== 字典类型(测试)1-001-005-000 ==========
+    ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1_001_005_000, "测试示例不存在");
+
+    // ========== 文件配置 1-001-006-000 ==========
+    ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在");
+    ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1_001_006_001, "该文件配置不允许删除,原因:它是主配置,删除会导致无法上传文件");
+
+    // ========== 数据源配置 1-001-007-000 ==========
+    ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_007_000, "数据源配置不存在");
+    ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1_001_007_001, "数据源配置不正确,无法进行连接");
 
 }

+ 3 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java

@@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
 import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-
-import java.time.LocalDateTime;
 
 @Mapper
 public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
@@ -21,7 +18,8 @@ public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
                 .orderByDesc(FileConfigDO::getId));
     }
 
-    @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
-    Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
+    default FileConfigDO selectByMaster() {
+        return selectOne(FileConfigDO::getMaster, true);
+    }
 
 }

+ 52 - 65
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java

@@ -1,10 +1,8 @@
 package cn.iocoder.yudao.module.infra.service.file;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.resource.ResourceUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.yudao.framework.file.core.client.FileClient;
@@ -17,22 +15,22 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU
 import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.validation.Validator;
-import java.time.LocalDateTime;
-import java.util.List;
+import java.time.Duration;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
 
@@ -46,19 +44,29 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG
 @Slf4j
 public class FileConfigServiceImpl implements FileConfigService {
 
-    @Resource
-    private FileClientFactory fileClientFactory;
+    private static final Long CACHE_MASTER_ID = 0L;
 
     /**
-     * 文件配置的缓存
-     */
-    @Getter
-    private List<FileConfigDO> fileConfigCache;
-    /**
-     * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
+     * {@link FileClient} 缓存,通过它异步刷新 fileClientFactory
      */
     @Getter
-    private FileClient masterFileClient;
+    private final LoadingCache<Long, FileClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
+            new CacheLoader<Long, FileClient>() {
+
+                @Override
+                public FileClient load(Long id) {
+                    FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ?
+                            fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);
+                    if (config != null) {
+                        fileClientFactory.createOrUpdateFileClient(id, config.getStorage(), config.getConfig());
+                    }
+                    return fileClientFactory.getFileClient(id);
+                }
+
+             });
+
+    @Resource
+    private FileClientFactory fileClientFactory;
 
     @Resource
     private FileConfigMapper fileConfigMapper;
@@ -66,53 +74,12 @@ public class FileConfigServiceImpl implements FileConfigService {
     @Resource
     private Validator validator;
 
-    @PostConstruct
-    public void initLocalCache() {
-        // 第一步:查询数据
-        List<FileConfigDO> configs = fileConfigMapper.selectList();
-        log.info("[initLocalCache][缓存文件配置,数量为:{}]", configs.size());
-
-        // 第二步:构建缓存:创建或更新文件 Client
-        configs.forEach(config -> {
-            fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
-            // 如果是 master,进行设置
-            if (Boolean.TRUE.equals(config.getMaster())) {
-                masterFileClient = fileClientFactory.getFileClient(config.getId());
-            }
-        });
-        this.fileConfigCache = configs;
-    }
-
-    /**
-     * 通过定时任务轮询,刷新缓存
-     *
-     * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
-     */
-    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
-    public void refreshLocalCache() {
-        // 情况一:如果缓存里没有数据,则直接刷新缓存
-        if (CollUtil.isEmpty(fileConfigCache)) {
-            initLocalCache();
-            return;
-        }
-
-        // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
-        LocalDateTime maxTime = CollectionUtils.getMaxValue(fileConfigCache, FileConfigDO::getUpdateTime);
-        if (fileConfigMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
-            initLocalCache();
-        }
-    }
-
     @Override
     public Long createFileConfig(FileConfigCreateReqVO createReqVO) {
-        // 插入
         FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO)
                 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
                 .setMaster(false); // 默认非 master
         fileConfigMapper.insert(fileConfig);
-
-        // 刷新缓存
-        initLocalCache();
         return fileConfig.getId();
     }
 
@@ -125,8 +92,8 @@ public class FileConfigServiceImpl implements FileConfigService {
                 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
         fileConfigMapper.updateById(updateObj);
 
-        // 刷新缓存
-        initLocalCache();
+        // 清空缓存
+        clearCache(config.getId(), null);
     }
 
     @Override
@@ -139,8 +106,8 @@ public class FileConfigServiceImpl implements FileConfigService {
         // 更新
         fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
 
-        // 刷新缓存
-        initLocalCache();
+        // 清空缓存
+        clearCache(null, true);
     }
 
     private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
@@ -164,8 +131,23 @@ public class FileConfigServiceImpl implements FileConfigService {
         // 删除
         fileConfigMapper.deleteById(id);
 
-        // 刷新缓存
-        initLocalCache();
+        // 清空缓存
+        clearCache(id, null);
+    }
+
+    /**
+     * 清空指定文件配置
+     *
+     * @param id 配置编号
+     * @param master 是否主配置
+     */
+    private void clearCache(Long id, Boolean master) {
+        if (id != null) {
+            clientCache.invalidate(id);
+        }
+        if (Boolean.TRUE.equals(master)) {
+            clientCache.invalidate(CACHE_MASTER_ID);
+        }
     }
 
     private FileConfigDO validateFileConfigExists(Long id) {
@@ -192,12 +174,17 @@ public class FileConfigServiceImpl implements FileConfigService {
         validateFileConfigExists(id);
         // 上传文件
         byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
-        return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
+        return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
     }
 
     @Override
     public FileClient getFileClient(Long id) {
-        return fileClientFactory.getFileClient(id);
+        return clientCache.getUnchecked(id);
+    }
+
+    @Override
+    public FileClient getMasterFileClient() {
+        return clientCache.getUnchecked(CACHE_MASTER_ID);
     }
 
 }

+ 5 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm

@@ -3,27 +3,27 @@ import { defHttp } from '@/utils/http/axios'
 
 // 查询${table.classComment}列表
 export function get${simpleClassName}Page(params) {
-    return defHttp.get({ url: '${baseURL}/page', params })
+  return defHttp.get({ url: '${baseURL}/page', params })
 }
 
 // 查询${table.classComment}详情
 export function get${simpleClassName}(id: number) {
-    return defHttp.get({ url: '${baseURL}/get?id=' + id })
+  return defHttp.get({ url: `${baseURL}/get?id=${id}` })
 }
 
 // 新增${table.classComment}
 export function create${simpleClassName}(data) {
-    return defHttp.post({ url: '${baseURL}/create', data })
+  return defHttp.post({ url: '${baseURL}/create', data })
 }
 
 // 修改${table.classComment}
 export function update${simpleClassName}(data) {
-    return defHttp.put({ url: '${baseURL}/update', data })
+  return defHttp.put({ url: '${baseURL}/update', data })
 }
 
 // 删除${table.classComment}
 export function delete${simpleClassName}(id: number) {
-    return defHttp.delete({ url: '${baseURL}/delete?id=' + id })
+  return defHttp.delete({ url: `${baseURL}/delete?id=${id}` })
 }
 
 // 导出${table.classComment} Excel

+ 33 - 15
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm

@@ -1,4 +1,5 @@
-import { BasicColumn, FormSchema, useRender } from '@/components/Table'
+import type { BasicColumn, FormSchema } from '@/components/Table'
+import { useRender } from '@/components/Table'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 
 export const columns: BasicColumn[] = [
@@ -92,13 +93,13 @@ export const createFormSchema: FormSchema[] = [
   #elseif($column.htmlType == "imageUpload")## 图片上传
     component: 'FileUpload',
     componentProps: {
-      fileType: 'file',
+      fileType: 'image',
       maxCount: 1,
     },
   #elseif($column.htmlType == "fileUpload")## 文件上传
     component: 'FileUpload',
     componentProps: {
-      fileType: 'image',
+      fileType: 'file',
       maxCount: 1,
     },
   #elseif($column.htmlType == "editor")## 文本编辑器
@@ -132,6 +133,11 @@ export const createFormSchema: FormSchema[] = [
     },
   #elseif($column.htmlType == "datetime")## 时间框
     component: 'DatePicker',
+    componentProps: {
+      showTime: true,
+      format: 'YYYY-MM-DD HH:mm:ss',
+      valueFormat: 'x',
+    },
   #elseif($column.htmlType == "textarea")## 文本域
     component: 'InputTextArea',
   #end
@@ -154,7 +160,7 @@ export const updateFormSchema: FormSchema[] = [
 #set ($javaField = $column.javaField)
 #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
 #set ($comment = $column.columnComment)
-#if (!$column.primaryKey)## 忽略主键,不用在表单里
+  #if (!$column.primaryKey)## 忽略主键,不用在表单里
   {
     label: '${comment}',
     field: '${javaField}',
@@ -164,45 +170,57 @@ export const updateFormSchema: FormSchema[] = [
     #if ($column.htmlType == "input")
     component: 'Input',
     #elseif($column.htmlType == "imageUpload")## 图片上传
-    component: 'Upload',
+    component: 'FileUpload',
+    componentProps: {
+      fileType: 'image',
+      maxCount: 1,
+    },
     #elseif($column.htmlType == "fileUpload")## 文件上传
-    component: 'Upload',
-    #elseif($column.htmlType == "editor")## 文本编辑器
-    component: 'Editor',
+    component: 'FileUpload',
+    componentProps: {
+      fileType: 'file',
+      maxCount: 1,
+    },
+    #elseif($column.htmlType == "editor")## 文本编辑器component: 'Editor',
     #elseif($column.htmlType == "select")## 下拉框
     component: 'Select',
     componentProps: {
       #if ("" != $dictType)## 有数据字典
-        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+      options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
       #else##没数据字典
-        options:[],
+      options:[],
       #end
     },
     #elseif($column.htmlType == "checkbox")## 多选框
     component: 'Checkbox',
     componentProps: {
       #if ("" != $dictType)## 有数据字典
-        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+      options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
       #else##没数据字典
-        options:[],
+      options:[],
       #end
     },
     #elseif($column.htmlType == "radio")## 单选框
     component: 'RadioButtonGroup',
     componentProps: {
       #if ("" != $dictType)## 有数据字典
-        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+      options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
       #else##没数据字典
-        options:[],
+      options:[],
       #end
     },
     #elseif($column.htmlType == "datetime")## 时间框
     component: 'DatePicker',
+    componentProps: {
+      showTime: true,
+      format: 'YYYY-MM-DD HH:mm:ss',
+      valueFormat: 'x',
+    },
     #elseif($column.htmlType == "textarea")## 文本域
     component: 'InputTextArea',
     #end
   },
-#end
+  #end
 #end
 #end
 ]

+ 6 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm

@@ -9,9 +9,10 @@ import { create${simpleClassName}, get${simpleClassName}, update${simpleClassNam
 
 defineOptions({ name: '${table.className}Modal' })
 
+const emit = defineEmits(['success', 'register'])
+
 const { t } = useI18n()
 const { createMessage } = useMessage()
-const emit = defineEmits(['success', 'register'])
 const isUpdate = ref(true)
 
 const [registerForm, { setFieldsValue, resetFields, resetSchema, validate }] = useForm({
@@ -37,11 +38,11 @@ async function handleSubmit() {
   try {
     const values = await validate()
     setModalProps({ confirmLoading: true })
-    if (unref(isUpdate)) {
+    if (unref(isUpdate))
       await update${simpleClassName}(values)
-    } else {
+    else
       await create${simpleClassName}(values)
-    }
+
     closeModal()
     emit('success')
     createMessage.success(t('common.saveSuccessText'))
@@ -51,7 +52,7 @@ async function handleSubmit() {
 }
 </script>
 <template>
-  <BasicModal v-bind="$attrs" @register="registerModal" :title="isUpdate ? t('action.edit') : t('action.create')" @ok="handleSubmit">
+  <BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit">
     <BasicForm @register="registerForm" />
   </BasicModal>
 </template>

+ 16 - 16
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm

@@ -1,12 +1,12 @@
 <script lang="ts" setup>
-import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
+import ${ simpleClassName }Modal from './${simpleClassName}Modal.vue'
 import { columns, searchFormSchema } from './${classNameVar}.data'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useMessage } from '@/hooks/web/useMessage'
 import { useModal } from '@/components/Modal'
 import { IconEnum } from '@/enums/appEnum'
 import { BasicTable, useTable, TableAction } from '@/components/Table'
-import { delete${simpleClassName}, export${simpleClassName}, get${simpleClassName}Page } from '@/api/${table.moduleName}/${classNameVar}'
+import { delete${ simpleClassName }, export${ simpleClassName }, get${ simpleClassName } Page } from '@/api/${table.moduleName}/${classNameVar}'
 
 defineOptions({ name: '${table.className}' })
 
@@ -16,17 +16,17 @@ const [registerModal, { openModal }] = useModal()
 
 const [registerTable, { getForm, reload }] = useTable({
   title: '${table.classComment}列表',
-  api: get${simpleClassName}Page,
-  columns,
-  formConfig: { labelWidth: 120, schemas: searchFormSchema },
-  useSearchForm: true,
-  showTableSetting: true,
-  actionColumn: {
-    width: 140,
-    title: t('common.action'),
-    dataIndex: 'action',
-    fixed: 'right',
-  },
+  api: get${ simpleClassName }Page,
+    columns,
+    formConfig: { labelWidth: 120, schemas: searchFormSchema },
+    useSearchForm: true,
+    showTableSetting: true,
+    actionColumn: {
+      width: 140,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    },
 })
 
 function handleCreate() {
@@ -43,14 +43,14 @@ async function handleExport() {
     iconType: 'warning',
     content: t('common.exportMessage'),
     async onOk() {
-      await export${simpleClassName}(getForm().getFieldsValue())
+      await export${ simpleClassName } (getForm().getFieldsValue())
       createMessage.success(t('common.exportSuccessText'))
     },
   })
 }
 
 async function handleDelete(record: Recordable) {
-  await delete${simpleClassName}(record.id)
+  await delete${ simpleClassName } (record.id)
   createMessage.success(t('common.delSuccessText'))
   reload()
 }
@@ -89,4 +89,4 @@ async function handleDelete(record: Recordable) {
     </BasicTable>
     <${simpleClassName}Modal @register="registerModal" @success="reload()" />
   </div>
-</template>
+</template>

+ 33 - 26
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java

@@ -60,31 +60,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
     private FileClientFactory fileClientFactory;
 
     @Test
-    public void testInitLocalCache() {
-        // mock 数据
-        FileConfigDO configDO1 = randomFileConfigDO().setId(1L).setMaster(true);
-        fileConfigMapper.insert(configDO1);
-        FileConfigDO configDO2 = randomFileConfigDO().setId(2L).setMaster(false);
-        fileConfigMapper.insert(configDO2);
-        // mock fileClientFactory 获得 master
-        FileClient masterFileClient = mock(FileClient.class);
-        when(fileClientFactory.getFileClient(eq(1L))).thenReturn(masterFileClient);
-
-        // 调用
-        fileConfigService.initLocalCache();
-        // 断言 fileClientFactory 调用
-        verify(fileClientFactory).createOrUpdateFileClient(eq(1L),
-                eq(configDO1.getStorage()), eq(configDO1.getConfig()));
-        verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
-                eq(configDO2.getStorage()), eq(configDO2.getConfig()));
-        assertSame(masterFileClient, fileConfigService.getMasterFileClient());
-        // 断言 fileConfigCache 缓存
-        assertEquals(2, fileConfigService.getFileConfigCache().size());
-        assertEquals(configDO1, fileConfigService.getFileConfigCache().get(0));
-        assertEquals(configDO2, fileConfigService.getFileConfigCache().get(1));
-    }
-
-    @Test
     public void testCreateFileConfig_success() {
         // 准备参数
         Map<String, Object> config = MapUtil.<String, Object>builder().put("basePath", "/yunai")
@@ -102,6 +77,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
         assertFalse(fileConfig.getMaster());
         assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
         assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
+        // 验证 cache
+        assertNull(fileConfigService.getClientCache().getIfPresent(fileConfigId));
     }
 
     @Test
@@ -125,6 +102,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(reqVO, fileConfig, "config");
         assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
         assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
+        // 验证 cache
+        assertNull(fileConfigService.getClientCache().getIfPresent(fileConfig.getId()));
     }
 
     @Test
@@ -149,6 +128,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
         // 断言数据
         assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
         assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
+        // 验证 cache
+        assertNull(fileConfigService.getClientCache().getIfPresent(0L));
     }
 
     @Test
@@ -169,6 +150,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
         fileConfigService.deleteFileConfig(id);
        // 校验数据不存在了
        assertNull(fileConfigMapper.selectById(id));
+        // 验证 cache
+        assertNull(fileConfigService.getClientCache().getIfPresent(id));
     }
 
     @Test
@@ -250,14 +233,38 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
 
     @Test
     public void testGetFileClient() {
+        // mock 数据
+        FileConfigDO fileConfig = randomFileConfigDO().setMaster(false);
+        fileConfigMapper.insert(fileConfig);
         // 准备参数
-        Long id = randomLongId();
+        Long id = fileConfig.getId();
         // mock 获得 Client
         FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
         when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient);
 
         // 调用,并断言
         assertSame(fileClient, fileConfigService.getFileClient(id));
+        // 断言缓存
+        verify(fileClientFactory).createOrUpdateFileClient(eq(id), eq(fileConfig.getStorage()),
+                eq(fileConfig.getConfig()));
+    }
+
+    @Test
+    public void testGetMasterFileClient() {
+        // mock 数据
+        FileConfigDO fileConfig = randomFileConfigDO().setMaster(true);
+        fileConfigMapper.insert(fileConfig);
+        // 准备参数
+        Long id = fileConfig.getId();
+        // mock 获得 Client
+        FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
+        when(fileClientFactory.getFileClient(eq(0L))).thenReturn(fileClient);
+
+        // 调用,并断言
+        assertSame(fileClient, fileConfigService.getMasterFileClient());
+        // 断言缓存
+        verify(fileClientFactory).createOrUpdateFileClient(eq(0L), eq(fileConfig.getStorage()),
+                eq(fileConfig.getConfig()));
     }
 
     private FileConfigDO randomFileConfigDO() {

+ 1 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java

@@ -139,4 +139,5 @@ public class FileServiceImplTest extends BaseDbUnitTest {
         // 断言
         assertSame(result, content);
     }
+
 }

+ 42 - 42
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java

@@ -9,47 +9,47 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== 商品分类相关 1008001000 ============
-    ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1008001000, "商品分类不存在");
-    ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1008001001, "父分类不存在");
-    ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类");
-    ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除");
-    ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用");
-    ErrorCode CATEGORY_HAVE_BIND_SPU = new ErrorCode(1008001005, "类别下存在商品,无法删除");
-
-    // ========== 商品品牌相关编号 1008002000 ==========
-    ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在");
-    ErrorCode BRAND_DISABLED = new ErrorCode(1008002001, "品牌已禁用");
-    ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1008002002, "品牌名称已存在");
-
-    // ========== 商品属性项 1008003000 ==========
-    ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "属性项不存在");
-    ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "属性项的名称已存在");
-    ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1008003002, "属性项下存在属性值,无法删除");
-
-    // ========== 商品属性值 1008004000 ==========
-    ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "属性值不存在");
-    ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "属性值的名称已存在");
-
-    // ========== 商品 SPU 1008005000 ==========
-    ErrorCode SPU_NOT_EXISTS = new ErrorCode(1008005000, "商品 SPU 不存在");
-    ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1008005001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
-    ErrorCode SPU_NOT_ENABLE = new ErrorCode(1008005002, "商品 SPU 不处于上架状态");
-    ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1008005003, "商品 SPU 不处于回收站状态");
-
-    // ========== 商品 SKU 1008006000 ==========
-    ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品 SKU 不存在");
-    ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
-    ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其属性项必须一致");
-    ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU,必须不重复");
-    ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
-
-    // ========== 商品 评价 1008007000 ==========
-    ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1008007000, "商品评价不存在");
-    ErrorCode COMMENT_ORDER_EXISTS = new ErrorCode(1008007001, "订单的商品评价已存在");
-
-    // ========== 商品 收藏 1008008000 ==========
-    ErrorCode FAVORITE_EXISTS = new ErrorCode(1008008000, "该商品已经被收藏");
-    ErrorCode FAVORITE_NOT_EXISTS = new ErrorCode(1008008001, "商品收藏不存在");
+    // ========== 商品分类相关 1-008-001-000 ============
+    ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_008_001_000, "商品分类不存在");
+    ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_008_001_001, "父分类不存在");
+    ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_008_001_002, "父分类不能是二级分类");
+    ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_008_001_003, "存在子分类,无法删除");
+    ErrorCode CATEGORY_DISABLED = new ErrorCode(1_008_001_004, "商品分类({})已禁用,无法使用");
+    ErrorCode CATEGORY_HAVE_BIND_SPU = new ErrorCode(1_008_001_005, "类别下存在商品,无法删除");
+
+    // ========== 商品品牌相关编号 1-008-002-000 ==========
+    ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1_008_002_000, "品牌不存在");
+    ErrorCode BRAND_DISABLED = new ErrorCode(1_008_002_001, "品牌已禁用");
+    ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1_008_002_002, "品牌名称已存在");
+
+    // ========== 商品属性项 1-008-003-000 ==========
+    ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1_008_003_000, "属性项不存在");
+    ErrorCode PROPERTY_EXISTS = new ErrorCode(1_008_003_001, "属性项的名称已存在");
+    ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1_008_003_002, "属性项下存在属性值,无法删除");
+
+    // ========== 商品属性值 1-008-004-000 ==========
+    ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1_008_004_000, "属性值不存在");
+    ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1_008_004_001, "属性值的名称已存在");
+
+    // ========== 商品 SPU 1-008-005-000 ==========
+    ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
+    ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
+    ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU 不处于上架状态");
+    ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_003, "商品 SPU 不处于回收站状态");
+
+    // ========== 商品 SKU 1-008-006-000 ==========
+    ErrorCode SKU_NOT_EXISTS = new ErrorCode(1_008_006_000, "商品 SKU 不存在");
+    ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1_008_006_001, "商品 SKU 的属性组合存在重复");
+    ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1_008_006_002, "一个 SPU 下的每个 SKU,其属性项必须一致");
+    ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1_008_006_003, "一个 SPU 下的每个 SKU,必须不重复");
+    ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_008_006_004, "商品 SKU 库存不足");
+
+    // ========== 商品 评价 1-008-007-000 ==========
+    ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1_008_007_000, "商品评价不存在");
+    ErrorCode COMMENT_ORDER_EXISTS = new ErrorCode(1_008_007_001, "订单的商品评价已存在");
+
+    // ========== 商品 收藏 1-008-008-000 ==========
+    ErrorCode FAVORITE_EXISTS = new ErrorCode(1_008_008_000, "该商品已经被收藏");
+    ErrorCode FAVORITE_NOT_EXISTS = new ErrorCode(1_008_008_001, "商品收藏不存在");
 
 }

+ 53 - 53
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -9,47 +9,47 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== 促销活动相关 1013001000 ============
-    ErrorCode DISCOUNT_ACTIVITY_NOT_EXISTS = new ErrorCode(1013001000, "限时折扣活动不存在");
-    ErrorCode DISCOUNT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013001001, "存在商品参加了其它限时折扣活动");
-    ErrorCode DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013001002, "限时折扣活动已关闭,不能修改");
-    ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1013001003, "限时折扣活动未关闭,不能删除");
-    ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013001004, "限时折扣活动已关闭,不能重复关闭");
-    ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1013001005, "限时折扣活动已结束,不能关闭");
-
-    // ========== Banner 相关 1013002000 ============
-    ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1013002000, "Banner 不存在");
-
-    // ========== Coupon 相关 1013003000 ============
-    ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1013003000, "优惠劵没有可使用的商品!");
-    ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1013003001, "所结算的商品中未满足使用的金额");
-
-    // ========== 优惠劵模板 1013004000 ==========
-    ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1013004000, "优惠劵模板不存在");
-    ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1013004001, "发放数量不能小于已领取数量({})");
-    ErrorCode COUPON_TEMPLATE_NOT_ENOUGH = new ErrorCode(1013004002, "当前剩余数量不够领取");
-    ErrorCode COUPON_TEMPLATE_USER_ALREADY_TAKE = new ErrorCode(1013004003, "用户已领取过此优惠券");
-    ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1013004004, "优惠券已过期");
-    ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1013004005, "领取方式不正确");
-
-    // ========== 优惠劵 1013005000 ==========
-    ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1013005000, "优惠券不存在");
-    ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1013005001, "回收优惠劵失败,优惠劵已被使用");
-    ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1013005002, "优惠劵不处于待使用状态");
-    ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1013005003, "优惠券不在使用时间范围内");
-    ErrorCode COUPON_STATUS_NOT_USED = new ErrorCode(1013005004, "优惠劵不是已使用状态");
-
-    // ========== 满减送活动 1013006000 ==========
-    ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1013006000, "满减送活动不存在");
-    ErrorCode REWARD_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013006001, "存在商品参加了其它满减送活动");
-    ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013006002, "满减送活动已关闭,不能修改");
-    ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1013006003, "满减送活动未关闭,不能删除");
-    ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013006004, "满减送活动已关闭,不能重复关闭");
-    ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1013006005, "满减送活动已结束,不能关闭");
-
-    // ========== TODO 空着 1013007000 ============
-
-    // ========== 秒杀活动 1013008000 ==========
+    // ========== 促销活动相关 1-013-001-000 ============
+    ErrorCode DISCOUNT_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_001_000, "限时折扣活动不存在");
+    ErrorCode DISCOUNT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_001_001, "存在商品参加了其它限时折扣活动");
+    ErrorCode DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_002, "限时折扣活动已关闭,不能修改");
+    ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_001_003, "限时折扣活动未关闭,不能删除");
+    ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_004, "限时折扣活动已关闭,不能重复关闭");
+    ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1_013_001_005, "限时折扣活动已结束,不能关闭");
+
+    // ========== Banner 相关 1-013-002-000 ============
+    ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在");
+
+    // ========== Coupon 相关 1-013-003-000 ============
+    ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1_013_003_000, "优惠劵没有可使用的商品!");
+    ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1_013_003_001, "所结算的商品中未满足使用的金额");
+
+    // ========== 优惠劵模板 1-013-004-000 ==========
+    ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_013_004_000, "优惠劵模板不存在");
+    ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1_013_004_001, "发放数量不能小于已领取数量({})");
+    ErrorCode COUPON_TEMPLATE_NOT_ENOUGH = new ErrorCode(1_013_004_002, "当前剩余数量不够领取");
+    ErrorCode COUPON_TEMPLATE_USER_ALREADY_TAKE = new ErrorCode(1_013_004_003, "用户已领取过此优惠券");
+    ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1_013_004_004, "优惠券已过期");
+    ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1_013_004_005, "领取方式不正确");
+
+    // ========== 优惠劵 1-013-005-000 ==========
+    ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1_013_005_000, "优惠券不存在");
+    ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1_013_005_001, "回收优惠劵失败,优惠劵已被使用");
+    ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1_013_005_002, "优惠劵不处于待使用状态");
+    ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1_013_005_003, "优惠券不在使用时间范围内");
+    ErrorCode COUPON_STATUS_NOT_USED = new ErrorCode(1_013_005_004, "优惠劵不是已使用状态");
+
+    // ========== 满减送活动 1-013-006-000 ==========
+    ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_006_000, "满减送活动不存在");
+    ErrorCode REWARD_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_006_001, "存在商品参加了其它满减送活动");
+    ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_002, "满减送活动已关闭,不能修改");
+    ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_006_003, "满减送活动未关闭,不能删除");
+    ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_004, "满减送活动已关闭,不能重复关闭");
+    ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1_013_006_005, "满减送活动已结束,不能关闭");
+
+    // ========== TODO 空着 1-013-007-000 ============
+
+    // ========== 秒杀活动 1-013-008-000 ==========
     ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1013008000, "秒杀活动不存在");
     ErrorCode SECKILL_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013008002, "存在商品参加了其它秒杀活动,秒杀时段冲突");
     ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013008003, "秒杀活动已关闭,不能修改");
@@ -57,19 +57,19 @@ public interface ErrorCodeConstants {
     ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013008005, "秒杀活动已关闭,不能重复关闭");
     ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013008006, "秒杀失败,原因秒杀库存不足");
 
-    // ========== 秒杀时段 1013009000 ==========
-    ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1013009000, "秒杀时段不存在");
-    ErrorCode SECKILL_CONFIG_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
-    ErrorCode SECKILL_CONFIG_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
+    // ========== 秒杀时段 1-013-009-000 ==========
+    ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");
+    ErrorCode SECKILL_CONFIG_TIME_CONFLICTS = new ErrorCode(1_013_009_001, "秒杀时段冲突");
+    ErrorCode SECKILL_CONFIG_DISABLE = new ErrorCode(1_013_009_004, "秒杀时段已关闭");
 
-    // ========== 拼团活动 1013010000 ==========
+    // ========== 拼团活动 1-013-010-000 ==========
     ErrorCode COMBINATION_ACTIVITY_NOT_EXISTS = new ErrorCode(1013010000, "拼团活动不存在");
     ErrorCode COMBINATION_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013010001, "存在商品参加了其它拼团活动");
     ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE_NOT_UPDATE = new ErrorCode(1013010002, "拼团活动已关闭不能修改");
     ErrorCode COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013010003, "拼团活动未关闭或未结束,不能删除");
     ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013010004, "拼团失败,原因:拼团活动已关闭");
 
-    // ========== 拼团记录 1013011000 ==========
+    // ========== 拼团记录 1-013-011-000 ==========
     ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1013011000, "拼团不存在");
     ErrorCode COMBINATION_RECORD_EXISTS = new ErrorCode(1013011001, "拼团失败,已参与过该拼团");
     ErrorCode COMBINATION_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013011002, "拼团失败,父拼团不存在");
@@ -79,17 +79,17 @@ public interface ErrorCodeConstants {
     ErrorCode COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1013011006, "拼团失败,原因:单次限购超出");
     ErrorCode COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED = new ErrorCode(1013011007, "拼团失败,原因:超出总购买次数");
 
-    // ========== 砍价活动 1013012000 ==========
+    // ========== 砍价活动 1-013-012-000 ==========
     ErrorCode BARGAIN_ACTIVITY_NOT_EXISTS = new ErrorCode(1013012000, "砍价活动不存在");
     ErrorCode BARGAIN_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013012001, "存在商品参加了其它砍价活动");
     ErrorCode BARGAIN_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013012002, "砍价活动已关闭不能修改");
     ErrorCode BARGAIN_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013012003, "砍价活动未关闭或未结束,不能删除");
     ErrorCode BARGAIN_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013012004, "砍价失败,原因:该砍价活动库存不足");
 
-    // ========== 砍价记录 1013013000 ==========
-    ErrorCode BARGAIN_RECORD_NOT_EXISTS = new ErrorCode(1013013000, "砍价记录不存在");
-    ErrorCode BARGAIN_RECORD_EXISTS = new ErrorCode(1013013001, "砍价失败,已参与过该砍价");
-    ErrorCode BARGAIN_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013013002, "砍价失败,父砍价不存在");
-    ErrorCode BARGAIN_RECORD_USER_FULL = new ErrorCode(1013013003, "砍价失败,砍价人数已满");
+    // ========== 砍价记录 1-013-013-000 ==========
+    ErrorCode BARGAIN_RECORD_NOT_EXISTS = new ErrorCode(1_013_013_000, "砍价记录不存在");
+    ErrorCode BARGAIN_RECORD_EXISTS = new ErrorCode(1_013_013_001, "砍价失败,已参与过该砍价");
+    ErrorCode BARGAIN_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1_013_013_002, "砍价失败,父砍价不存在");
+    ErrorCode BARGAIN_RECORD_USER_FULL = new ErrorCode(1_013_013_003, "砍价失败,砍价人数已满");
 
 }

+ 11 - 11
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/vo/article/AppArticleRespVO.java

@@ -9,41 +9,41 @@ import java.time.LocalDateTime;
 @Data
 public class AppArticleRespVO {
 
-    @Schema(description = "文章编号", required = true, example = "1")
+    @Schema(description = "文章编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
-    @Schema(description = "文章标题", required = true, example = "芋道源码 - 促销模块")
+    @Schema(description = "文章标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码 - 促销模块")
     private String title;
 
-    @Schema(description = "文章作者", required = true, example = "芋道源码")
+    @Schema(description = "文章作者", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
     private String author;
 
-    @Schema(description = "分类编号", required = true, example = "2048")
+    @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
     private Long categoryId;
 
-    @Schema(description = "图文封面", required = true, example = "https://www.iocoder.cn/1.png")
+    @Schema(description = "图文封面", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
     private String picUrl;
 
-    @Schema(description = "文章简介", required = true, example = "我是简介")
+    @Schema(description = "文章简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是简介")
     private String introduction;
 
-    @Schema(description = "文章内容", required = true, example = "我是详细")
+    @Schema(description = "文章内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是详细")
     private String description;
 
-    @Schema(description = "发布时间", required = true)
+    @Schema(description = "发布时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
-    @Schema(description = "浏览量", required = true, example = "1024")
+    @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer browseCount;
 
     @Schema(description = "关联的商品 SPU 编号", example = "1024")
     private Long spuId;
 
 // TODO 芋艿:下面 2 个字段,后端要存储,前端不用返回;
-//    @Schema(description = "是否热卖推荐", required = true, example = "true")
+//    @Schema(description = "是否热卖推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
 //    private Boolean recommendHot;
 //
-//    @Schema(description = "是否 Banner 推荐", required = true, example = "true")
+//    @Schema(description = "是否 Banner 推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
 //    private Boolean recommendBanner;
 
 }

+ 5 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/vo/category/AppArticleCategoryRespVO.java

@@ -7,20 +7,20 @@ import lombok.Data;
 @Data
 public class AppArticleCategoryRespVO {
 
-    @Schema(description = "分类编号", required = true, example = "1")
+    @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
-    @Schema(description = "分类名称", required = true, example = "技术")
+    @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术")
     private String name;
 
-    @Schema(description = "分类图标", required = true, example = "https://www.iocoder.cn/1.png")
+    @Schema(description = "分类图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
     private String picUrl;
 
     // TODO 芋艿:下面 2 个字段,后端要存储,前端不用返回;
-//    @Schema(description = "状态", required = true, example = "1")
+//    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
 //    private Integer status;
 //
-//    @Schema(description = "排序", required = true, example = "1024")
+//    @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
 //    private Integer sort;
 
 }

+ 14 - 14
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityDetailRespVO.java

@@ -9,46 +9,46 @@ import java.time.LocalDateTime;
 @Data
 public class AppBargainActivityDetailRespVO {
 
-    @Schema(description = "砍价活动编号", required = true, example = "1024")
+    @Schema(description = "砍价活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
-    @Schema(description = "砍价活动名称", required = true, example = "618 大砍价")
+    @Schema(description = "砍价活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618 大砍价")
     private String name;
 
-    @Schema(description = "活动开始时间", required = true)
+    @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime startTime;
 
-    @Schema(description = "活动结束时间", required = true)
+    @Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime endTime;
 
-    @Schema(description = "商品 SPU 编号", required = true, example = "2048")
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
     private Long spuId;
 
-    @Schema(description = "商品 SKU 编号", required = true, example = "1024")
+    @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long skuId;
 
-    @Schema(description = "商品价格,单位:分", required = true, example = "100")
+    @Schema(description = "商品价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     private Integer price;
 
-    @Schema(description = "商品描述", required = true, example = "我要吃西红柿")
+    @Schema(description = "商品描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "我要吃西红柿")
     private String description;
 
-    @Schema(description = "砍价库存", required = true, example = "512")
+    @Schema(description = "砍价库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
     private Integer stock;
 
-    @Schema(description = "商品图片", required = true, example = "4096") // 从 SPU 的 picUrl 读取
+    @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") // 从 SPU 的 picUrl 读取
     private String picUrl;
 
-    @Schema(description = "商品市场价,单位:分", required = true, example = "50") // 从 SPU 的 marketPrice 读取
+    @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") // 从 SPU 的 marketPrice 读取
     private Integer marketPrice;
 
-    @Schema(description = "商品单位", required = true, example = "个") // 从 SPU 的 unit 读取,然后转换
+    @Schema(description = "商品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") // 从 SPU 的 unit 读取,然后转换
     private String unitName;
 
-    @Schema(description = "砍价最低金额,单位:分", required = true, example = "100")
+    @Schema(description = "砍价最低金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     private Integer bargainPrice;
 
-    @Schema(description = "砍价成功数量", required = true, example = "100")
+    @Schema(description = "砍价成功数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     private Integer successCount;
 
 }

+ 10 - 10
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityRespVO.java

@@ -9,34 +9,34 @@ import java.time.LocalDateTime;
 @Data
 public class AppBargainActivityRespVO {
 
-    @Schema(description = "砍价活动编号", required = true, example = "1024")
+    @Schema(description = "砍价活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
-    @Schema(description = "砍价活动名称", required = true, example = "618 大砍价")
+    @Schema(description = "砍价活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618 大砍价")
     private String name;
 
-    @Schema(description = "活动开始时间", required = true)
+    @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime startTime;
 
-    @Schema(description = "活动结束时间", required = true)
+    @Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime endTime;
 
-    @Schema(description = "商品 SPU 编号", required = true, example = "2048")
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
     private Long spuId;
 
-    @Schema(description = "商品 SKU 编号", required = true, example = "1024")
+    @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long skuId;
 
-    @Schema(description = "砍价库存", required = true, example = "512")
+    @Schema(description = "砍价库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
     private Integer stock;
 
-    @Schema(description = "商品图片", required = true, example = "4096") // 从 SPU 的 picUrl 读取
+    @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") // 从 SPU 的 picUrl 读取
     private String picUrl;
 
-    @Schema(description = "商品市场价,单位:分", required = true, example = "50") // 从 SPU 的 marketPrice 读取
+    @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") // 从 SPU 的 marketPrice 读取
     private Integer marketPrice;
 
-    @Schema(description = "砍价最低金额,单位:分", required = true, example = "100")
+    @Schema(description = "砍价最低金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     private Integer bargainPrice;
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/help/AppBargainHelpCreateReqVO.java

@@ -9,7 +9,7 @@ import javax.validation.constraints.NotNull;
 @Data
 public class AppBargainHelpCreateReqVO {
 
-    @Schema(description = "砍价记录编号", required = true, example = "1024")
+    @Schema(description = "砍价记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "砍价记录编号不能为空")
     private Long recordId;
 

+ 4 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/help/AppBargainHelpRespVO.java

@@ -9,16 +9,16 @@ import java.time.LocalDateTime;
 @Data
 public class AppBargainHelpRespVO {
 
-    @Schema(description = "助力用户的昵称", required = true, example = "1024")
+    @Schema(description = "助力用户的昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private String nickname;
 
-    @Schema(description = "助力用户的头像", required = true, example = "1024")
+    @Schema(description = "助力用户的头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private String avatar;
 
-    @Schema(description = "助力用户的砍价金额", required = true, example = "1024")
+    @Schema(description = "助力用户的砍价金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer reducePrice;
 
-    @Schema(description = "创建时间", required = true, example = "1024")
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private LocalDateTime createTime;
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/record/AppBargainRecordCreateReqVO.java

@@ -9,7 +9,7 @@ import javax.validation.constraints.NotNull;
 @Data
 public class AppBargainRecordCreateReqVO {
 
-    @Schema(description = "砍价活动编号", required = true, example = "1024")
+    @Schema(description = "砍价活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "砍价活动编号不能为空")
     private Long activityId;
 

+ 5 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/record/AppBargainRecordSummaryRespVO.java

@@ -9,23 +9,23 @@ import java.util.List;
 @Data
 public class AppBargainRecordSummaryRespVO {
 
-    @Schema(description = "砍价用户数量", required = true, example = "1024")
+    @Schema(description = "砍价用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer userCount;
 
-    @Schema(description = "成功砍价的记录", required = true) // 只返回最近的 7 个
+    @Schema(description = "成功砍价的记录", requiredMode = Schema.RequiredMode.REQUIRED) // 只返回最近的 7 个
     private List<Record> successRecords;
 
     @Schema(description = "成功砍价记录")
     @Data
     public static class Record {
 
-        @Schema(description = "用户昵称", required = true, example = "王**")
+        @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王**")
         private String nickname;
 
-        @Schema(description = "用户头像", required = true, example = "https://www.iocoder.cn/xxx.jpg")
+        @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
         private String avatar;
 
-        @Schema(description = "活动名称", required = true, example = "天蚕土豆")
+        @Schema(description = "活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "天蚕土豆")
         private String activityName;
 
     }

+ 29 - 30
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -11,7 +11,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== Order 模块 1011000000 ==========
+    // ========== Order 模块 1-011-000-000 ==========
     ErrorCode ORDER_ITEM_NOT_FOUND = new ErrorCode(1011000010, "交易订单项不存在");
     ErrorCode ORDER_NOT_FOUND = new ErrorCode(1011000011, "交易订单不存在");
     ErrorCode ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL = new ErrorCode(1011000012, "交易订单项更新售后状态失败,请重试");
@@ -33,45 +33,44 @@ public interface ErrorCodeConstants {
     ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1011000028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元");
     ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1011000029, "交易订单删除失败,订单不是【已取消】状态");
 
-    // ========== After Sale 模块 1011000100 ==========
-    ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在");
-    ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1011000101, "申请退款金额错误");
-    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1011000102, "订单已关闭,无法申请售后");
-    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID = new ErrorCode(1011000103, "订单未支付,无法申请售后");
-    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED = new ErrorCode(1011000104, "订单未发货,无法申请【退货退款】售后");
-    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1011000105, "订单项已申请售后,无法重复申请");
-    ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1011000106, "审批失败,售后状态不处于审批中");
-    ErrorCode AFTER_SALE_UPDATE_STATUS_FAIL = new ErrorCode(1011000107, "操作售后单失败,请刷新后重试");
-    ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1011000108, "退货失败,售后单状态不处于【待买家退货】");
-    ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1011000109, "确认收货失败,售后单状态不处于【待确认收货】");
-    ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1011000110, "退款失败,售后单状态不是【待退款】");
+    // ========== After Sale 模块 1-011-000-100 ==========
+    ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
+    ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1_011_000_101, "申请退款金额错误");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1_011_000_102, "订单已关闭,无法申请售后");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID = new ErrorCode(1_011_000_103, "订单未支付,无法申请售后");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED = new ErrorCode(1_011_000_104, "订单未发货,无法申请【退货退款】售后");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1_011_000_105, "订单项已申请售后,无法重复申请");
+    ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1_011_000_106, "审批失败,售后状态不处于审批中");
+    ErrorCode AFTER_SALE_UPDATE_STATUS_FAIL = new ErrorCode(1_011_000_107, "操作售后单失败,请刷新后重试");
+    ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1_011_000_108, "退货失败,售后单状态不处于【待买家退货】");
+    ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1_011_000_109, "确认收货失败,售后单状态不处于【待确认收货】");
+    ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】");
     ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
-            new ErrorCode(1011000111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
+            new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
 
-    // ========== Cart 模块 1011002000 ==========
-    ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1011002000, "购物车项不存在");
+    // ========== Cart 模块 1-011-002-000 ==========
+    ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
 
-    // ========== Price 相关 1011003000 ============
+    // ========== Price 相关 1-011-003-000 ============
     ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0");
     ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板");
     ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1011003004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
 
-    // ========== 物流 Express 模块 1011004000 ==========
-    ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在");
-    ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1011004001, "已经存在该编码的快递公司");
-    ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1011004002, "需要接入快递服务商,比如【快递100】");
-    ErrorCode EXPRESS_STATUS_NOT_ENABLE = new ErrorCode(1011004003, "快递公司未启用");
+    // ========== 物流 Express 模块 1-011-004-000 ==========
+    ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
+    ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1_011_004_001, "已经存在该编码的快递公司");
+    ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1_011_004_002, "需要接入快递服务商,比如【快递100】");
+    ErrorCode EXPRESS_STATUS_NOT_ENABLE = new ErrorCode(1_011_004_003, "快递公司未启用");
 
-    ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1011004101, "快递查询接口异常");
-    ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1011004102, "快递查询返回失败,原因:{}");
+    ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1_011_004_101, "快递查询接口异常");
+    ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1_011_004_102, "快递查询返回失败,原因:{}");
 
-    // ========== 物流 Template 模块 1011005000 ==========
-    ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011005000, "已经存在该运费模板名");
-    ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1011005001, "运费模板不存在");
-
-    // ========== 物流 PICK_UP 模块 1011006000 ==========
-    ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011006000, "自提门店不存在");
+    // ========== 物流 Template 模块 1-011-005-000 ==========
+    ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_011_005_000, "已经存在该运费模板名");
+    ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_011_005_001, "运费模板不存在");
 
+    // ==========  物流 PICK_UP 模块 1-011-006-000 ==========
+    ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在");
 
     // ========== 分销用户 模块 1011007000 ==========
     ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1011007000, "分销用户不存在");

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java

@@ -8,10 +8,10 @@ import lombok.Data;
 @Data
 public class AppDeliveryConfigRespVO {
 
-    @Schema(description = "腾讯地图 KEY", required = true, example = "123456")
+    @Schema(description = "腾讯地图 KEY", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
     private String tencentLbsKey;
 
-    @Schema(description = "是否开启自提", required = true, example = "true")
+    @Schema(description = "是否开启自提", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
     private Boolean pickUpEnable;
 
 }

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/express/AppDeliveryExpressRespVO.java

@@ -7,10 +7,10 @@ import lombok.Data;
 @Data
 public class AppDeliveryExpressRespVO {
 
-    @Schema(description = "编号", required = true, example = "1")
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
-    @Schema(description = "门店名称", required = true, example = "顺丰")
+    @Schema(description = "门店名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "顺丰")
     private String name;
 
 }

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java

@@ -25,12 +25,12 @@ public class AppTradeOrderSettlementReqVO {
     @Schema(description = "优惠劵编号", example = "1024")
     private Long couponId;
 
-    @Schema(description = "是否使用积分", required = true, example = "true")
+    @Schema(description = "是否使用积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
     @NotNull(message = "是否使用积分不能为空")
     private Boolean pointStatus;
 
     // ========== 配送相关相关字段 ==========
-    @Schema(description = "配送方式", example = "1")
+    @Schema(description = "配送方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确")
     private Integer deliveryType;
 

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java

@@ -13,7 +13,7 @@ import java.util.List;
 @Data
 public class AppTradeOrderSettlementRespVO {
 
-    @Schema(description = "交易类型", required = true, example = "1") // 对应 TradeOrderTypeEnum 枚举
+    @Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举
     private Integer type = 1; // TODO 芋艿:改成计算
 
     @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)

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

@@ -9,51 +9,51 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== 用户相关 1004001000============
+    // ========== 用户相关  1-004-001-000 ============
     ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在");
     ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1004001001, "手机号未注册用户");
     ErrorCode USER_MOBILE_USED = new ErrorCode(1004001002, "修改手机失败,该手机号({})已经被使用");
     ErrorCode USER_POINT_NOT_ENOUGH = new ErrorCode(1004001003, "用户积分余额不足");
 
-    // ========== AUTH 模块 1004003000 ==========
-    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
-    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
-    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
-    ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1004003006, "获得手机号失败");
-    ErrorCode AUTH_MOBILE_USED = new ErrorCode(1004003007, "手机号已经被使用");
+    // ========== AUTH 模块 1-004-003-000 ==========
+    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_004_003_000, "登录失败,账号密码不正确");
+    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_004_003_001, "登录失败,账号被禁用");
+    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_004_003_005, "未绑定账号,需要进行绑定");
+    ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_004_003_006, "获得手机号失败");
+    ErrorCode AUTH_MOBILE_USED = new ErrorCode(1_004_003_007, "手机号已经被使用");
 
-    // ========== 用户收件地址 1004004000 ==========
-    ErrorCode ADDRESS_NOT_EXISTS = new ErrorCode(1004004000, "用户收件地址不存在");
+    // ========== 用户收件地址 1-004-004-000 ==========
+    ErrorCode ADDRESS_NOT_EXISTS = new ErrorCode(1_004_004_000, "用户收件地址不存在");
 
-    //========== 用户标签 1004006000 ==========
-    ErrorCode TAG_NOT_EXISTS = new ErrorCode(1004006000, "用户标签不存在");
-    ErrorCode TAG_NAME_EXISTS = new ErrorCode(1004006001, "用户标签已经存在");
-    ErrorCode TAG_HAS_USER = new ErrorCode(1004006002, "用户标签下存在用户,无法删除");
+    //========== 用户标签 1-004-006-000 ==========
+    ErrorCode TAG_NOT_EXISTS = new ErrorCode(1_004_006_000, "用户标签不存在");
+    ErrorCode TAG_NAME_EXISTS = new ErrorCode(1_004_006_001, "用户标签已经存在");
+    ErrorCode TAG_HAS_USER = new ErrorCode(1_004_006_002, "用户标签下存在用户,无法删除");
 
-    //========== 积分配置 1004007000 ==========
+    //========== 积分配置 1-004-007-000 ==========
 
-    //========== 积分记录 1004008000 ==========
-    ErrorCode POINT_RECORD_BIZ_NOT_SUPPORT = new ErrorCode(1004008000, "用户积分记录业务类型不支持");
+    //========== 积分记录 1-004-008-000 ==========
+    ErrorCode POINT_RECORD_BIZ_NOT_SUPPORT = new ErrorCode(1_004_008_000, "用户积分记录业务类型不支持");
 
-    //========== 签到配置 1004009000 ==========
-    ErrorCode SIGN_IN_CONFIG_NOT_EXISTS = new ErrorCode(1004009000, "签到天数规则不存在");
-    ErrorCode SIGN_IN_CONFIG_EXISTS = new ErrorCode(1004009001, "签到天数规则已存在");
+    //========== 签到配置 1-004-009-000 ==========
+    ErrorCode SIGN_IN_CONFIG_NOT_EXISTS = new ErrorCode(1_004_009_000, "签到天数规则不存在");
+    ErrorCode SIGN_IN_CONFIG_EXISTS = new ErrorCode(1_004_009_001, "签到天数规则已存在");
 
-    //========== 签到配置 1004010000 ==========
+    //========== 签到配置 1-004-010-000 ==========
     ErrorCode SIGN_IN_RECORD_TODAY_EXISTS = new ErrorCode(1004010000,"今日已签到,请勿重复签到");
 
-    //========== 用户等级 1004011000 ==========
-    ErrorCode LEVEL_NOT_EXISTS = new ErrorCode(1004011000, "用户等级不存在");
-    ErrorCode LEVEL_NAME_EXISTS = new ErrorCode(1004011001, "用户等级名称[{}]已被使用");
-    ErrorCode LEVEL_VALUE_EXISTS = new ErrorCode(1004011002, "用户等级值[{}]已被[{}]使用");
-    ErrorCode LEVEL_EXPERIENCE_MIN = new ErrorCode(1004011003, "升级经验必须大于上一个等级[{}]设置的升级经验[{}]");
-    ErrorCode LEVEL_EXPERIENCE_MAX = new ErrorCode(1004011004, "升级经验必须小于下一个等级[{}]设置的升级经验[{}]");
-    ErrorCode LEVEL_HAS_USER = new ErrorCode(1004011005, "用户等级下存在用户,无法删除");
+    //========== 用户等级 1-004-011-000 ==========
+    ErrorCode LEVEL_NOT_EXISTS = new ErrorCode(1_004_011_000, "用户等级不存在");
+    ErrorCode LEVEL_NAME_EXISTS = new ErrorCode(1_004_011_001, "用户等级名称[{}]已被使用");
+    ErrorCode LEVEL_VALUE_EXISTS = new ErrorCode(1_004_011_002, "用户等级值[{}]已被[{}]使用");
+    ErrorCode LEVEL_EXPERIENCE_MIN = new ErrorCode(1_004_011_003, "升级经验必须大于上一个等级[{}]设置的升级经验[{}]");
+    ErrorCode LEVEL_EXPERIENCE_MAX = new ErrorCode(1_004_011_004, "升级经验必须小于下一个等级[{}]设置的升级经验[{}]");
+    ErrorCode LEVEL_HAS_USER = new ErrorCode(1_004_011_005, "用户等级下存在用户,无法删除");
 
-    ErrorCode EXPERIENCE_BIZ_NOT_SUPPORT = new ErrorCode(1004011201, "用户经验业务类型不支持");
+    ErrorCode EXPERIENCE_BIZ_NOT_SUPPORT = new ErrorCode(1_004_011_201, "用户经验业务类型不支持");
 
-    //========== 用户分组 1004012000 ==========
-    ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1004012000, "用户分组不存在");
-    ErrorCode GROUP_HAS_USER = new ErrorCode(1004012001, "用户分组下存在用户,无法删除");
+    //========== 用户分组 1-004-012-000 ==========
+    ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_004_012_000, "用户分组不存在");
+    ErrorCode GROUP_HAS_USER = new ErrorCode(1_004_012_001, "用户分组下存在用户,无法删除");
 
 }

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java

@@ -33,7 +33,7 @@ public class AppAuthLoginReqVO {
 
     // ========== 绑定社交登录时,需要传递如下参数 ==========
 
-    @Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @InEnum(SocialTypeEnum.class)
     private Integer socialType;
 
@@ -53,4 +53,4 @@ public class AppAuthLoginReqVO {
         return socialType == null || StrUtil.isNotEmpty(socialState);
     }
 
-}
+}

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java

@@ -35,7 +35,7 @@ public class AppAuthSmsLoginReqVO {
 
     // ========== 绑定社交登录时,需要传递如下参数 ==========
 
-    @Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @InEnum(SocialTypeEnum.class)
     private Integer socialType;
 
@@ -55,4 +55,4 @@ public class AppAuthSmsLoginReqVO {
         return socialType == null || StrUtil.isNotEmpty(socialState);
     }
 
-}
+}

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java

@@ -18,7 +18,7 @@ import javax.validation.constraints.NotNull;
 @Builder
 public class AppAuthSocialLoginReqVO {
 
-    @Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @InEnum(SocialTypeEnum.class)
     @NotNull(message = "社交平台的类型不能为空")
     private Integer type;
@@ -31,4 +31,4 @@ public class AppAuthSocialLoginReqVO {
     @NotEmpty(message = "state 不能为空")
     private String state;
 
-}
+}

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java

@@ -18,7 +18,7 @@ import javax.validation.constraints.NotNull;
 @Builder
 public class AppSocialUserBindReqVO {
 
-    @Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @InEnum(SocialTypeEnum.class)
     @NotNull(message = "社交平台的类型不能为空")
     private Integer type;
@@ -31,4 +31,4 @@ public class AppSocialUserBindReqVO {
     @NotEmpty(message = "state 不能为空")
     private String state;
 
-}
+}

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java

@@ -18,7 +18,7 @@ import javax.validation.constraints.NotNull;
 @Builder
 public class AppSocialUserUnbindReqVO {
 
-    @Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @InEnum(SocialTypeEnum.class)
     @NotNull(message = "社交平台的类型不能为空")
     private Integer type;
@@ -27,4 +27,4 @@ public class AppSocialUserUnbindReqVO {
     @NotEmpty(message = "社交用户的 openid 不能为空")
     private String openid;
 
-}
+}

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

@@ -222,10 +222,10 @@ public class MemberUserServiceImpl implements MemberUserService {
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的用户
         if (id == null) {
-            throw exception(USER_MOBILE_USED);
+            throw exception(USER_MOBILE_USED, mobile);
         }
         if (!user.getId().equals(id)) {
-            throw exception(USER_MOBILE_USED);
+            throw exception(USER_MOBILE_USED, mobile);
         }
     }
 

+ 42 - 42
yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java

@@ -9,56 +9,56 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== 公众号账号 1006000000============
-    ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1006000000, "公众号账号不存在");
-    ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1006000001, "生成公众号二维码失败,原因:{}");
-    ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000002, "清空公众号的 API 配额失败,原因:{}");
+    // ========== 公众号账号 1-006-000-000 ============
+    ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1_006_000_000, "公众号账号不存在");
+    ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1_006_000_001, "生成公众号二维码失败,原因:{}");
+    ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1_006_000_002, "清空公众号的 API 配额失败,原因:{}");
 
-    // ========== 公众号统计 1006001000============
-    ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取粉丝增减数据失败,原因:{}");
-    ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1006001001, "获得粉丝累计数据失败,原因:{}");
-    ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1006001002, "获得消息发送概况数据失败,原因:{}");
-    ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1006001003, "获得接口分析数据失败,原因:{}");
+    // ========== 公众号统计 1-006-001-000 ============
+    ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1_006_001_000, "获取粉丝增减数据失败,原因:{}");
+    ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1_006_001_001, "获得粉丝累计数据失败,原因:{}");
+    ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1_006_001_002, "获得消息发送概况数据失败,原因:{}");
+    ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1_006_001_003, "获得接口分析数据失败,原因:{}");
 
-    // ========== 公众号标签 1006002000============
-    ErrorCode TAG_NOT_EXISTS = new ErrorCode(1006002000, "标签不存在");
-    ErrorCode TAG_CREATE_FAIL = new ErrorCode(1006002001, "创建标签失败,原因:{}");
-    ErrorCode TAG_UPDATE_FAIL = new ErrorCode(1006002002, "更新标签失败,原因:{}");
-    ErrorCode TAG_DELETE_FAIL = new ErrorCode(1006002003, "删除标签失败,原因:{}");
-    ErrorCode TAG_GET_FAIL = new ErrorCode(1006002004, "获得标签失败,原因:{}");
+    // ========== 公众号标签 1-006-002-000 ============
+    ErrorCode TAG_NOT_EXISTS = new ErrorCode(1_006_002_000, "标签不存在");
+    ErrorCode TAG_CREATE_FAIL = new ErrorCode(1_006_002_001, "创建标签失败,原因:{}");
+    ErrorCode TAG_UPDATE_FAIL = new ErrorCode(1_006_002_002, "更新标签失败,原因:{}");
+    ErrorCode TAG_DELETE_FAIL = new ErrorCode(1_006_002_003, "删除标签失败,原因:{}");
+    ErrorCode TAG_GET_FAIL = new ErrorCode(1_006_002_004, "获得标签失败,原因:{}");
 
-    // ========== 公众号粉丝 1006003000============
-    ErrorCode USER_NOT_EXISTS = new ErrorCode(1006003000, "粉丝不存在");
-    ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新粉丝标签失败,原因:{}");
+    // ========== 公众号粉丝 1-006-003-000 ============
+    ErrorCode USER_NOT_EXISTS = new ErrorCode(1_006_003_000, "粉丝不存在");
+    ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1_006_003_001, "更新粉丝标签失败,原因:{}");
 
-    // ========== 公众号素材 1006004000============
-    ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1006004000, "素材不存在");
-    ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1006004001, "上传素材失败,原因:{}");
-    ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1006004002, "上传图片失败,原因:{}");
-    ErrorCode MATERIAL_DELETE_FAIL = new ErrorCode(1006004003, "删除素材失败,原因:{}");
+    // ========== 公众号素材 1-006-004-000 ============
+    ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1_006_004_000, "素材不存在");
+    ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1_006_004_001, "上传素材失败,原因:{}");
+    ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1_006_004_002, "上传图片失败,原因:{}");
+    ErrorCode MATERIAL_DELETE_FAIL = new ErrorCode(1_006_004_003, "删除素材失败,原因:{}");
 
-    // ========== 公众号消息 1006005000============
-    ErrorCode MESSAGE_SEND_FAIL = new ErrorCode(1006005000, "发送消息失败,原因:{}");
+    // ========== 公众号消息 1-006-005-000 ============
+    ErrorCode MESSAGE_SEND_FAIL = new ErrorCode(1_006_005_000, "发送消息失败,原因:{}");
 
-    // ========== 公众号发布能力 1006006000============
-    ErrorCode FREE_PUBLISH_LIST_FAIL = new ErrorCode(1006006000, "获得已成功发布列表失败,原因:{}");
-    ErrorCode FREE_PUBLISH_SUBMIT_FAIL = new ErrorCode(1006006001, "提交发布失败,原因:{}");
-    ErrorCode FREE_PUBLISH_DELETE_FAIL = new ErrorCode(1006006002, "删除发布失败,原因:{}");
+    // ========== 公众号发布能力 1-006-006-000 ============
+    ErrorCode FREE_PUBLISH_LIST_FAIL = new ErrorCode(1_006_006_000, "获得已成功发布列表失败,原因:{}");
+    ErrorCode FREE_PUBLISH_SUBMIT_FAIL = new ErrorCode(1_006_006_001, "提交发布失败,原因:{}");
+    ErrorCode FREE_PUBLISH_DELETE_FAIL = new ErrorCode(1_006_006_002, "删除发布失败,原因:{}");
 
-    // ========== 公众号草稿 1006007000============
-    ErrorCode DRAFT_LIST_FAIL = new ErrorCode(1006007000, "获得草稿列表失败,原因:{}");
-    ErrorCode DRAFT_CREATE_FAIL = new ErrorCode(1006007001, "创建草稿失败,原因:{}");
-    ErrorCode DRAFT_UPDATE_FAIL = new ErrorCode(1006007002, "更新草稿失败,原因:{}");
-    ErrorCode DRAFT_DELETE_FAIL = new ErrorCode(1006007003, "删除草稿失败,原因:{}");
+    // ========== 公众号草稿 1-006-007-000 ============
+    ErrorCode DRAFT_LIST_FAIL = new ErrorCode(1_006_007_000, "获得草稿列表失败,原因:{}");
+    ErrorCode DRAFT_CREATE_FAIL = new ErrorCode(1_006_007_001, "创建草稿失败,原因:{}");
+    ErrorCode DRAFT_UPDATE_FAIL = new ErrorCode(1_006_007_002, "更新草稿失败,原因:{}");
+    ErrorCode DRAFT_DELETE_FAIL = new ErrorCode(1_006_007_003, "删除草稿失败,原因:{}");
 
-    // ========== 公众号菜单 1006008000============
-    ErrorCode MENU_SAVE_FAIL = new ErrorCode(1006008000, "创建菜单失败,原因:{}");
-    ErrorCode MENU_DELETE_FAIL = new ErrorCode(1006008001, "删除菜单失败,原因:{}");
+    // ========== 公众号菜单 1-006-008-000 ============
+    ErrorCode MENU_SAVE_FAIL = new ErrorCode(1_006_008_000, "创建菜单失败,原因:{}");
+    ErrorCode MENU_DELETE_FAIL = new ErrorCode(1_006_008_001, "删除菜单失败,原因:{}");
 
-    // ========== 公众号自动回复 1006009000============
-    ErrorCode AUTO_REPLY_NOT_EXISTS = new ErrorCode(1006009000, "自动回复不存在");
-    ErrorCode AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS = new ErrorCode(1006009001, "操作失败,原因:已存在关注时的回复");
-    ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1006009002, "操作失败,原因:已存在该消息类型的回复");
-    ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1006009003, "操作失败,原因:已关在该关键字的回复");
+    // ========== 公众号自动回复 1-006-009-000 ============
+    ErrorCode AUTO_REPLY_NOT_EXISTS = new ErrorCode(1_006_009_000, "自动回复不存在");
+    ErrorCode AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS = new ErrorCode(1_006_009_001, "操作失败,原因:已存在关注时的回复");
+    ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复");
+    ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, "操作失败,原因:已关在该关键字的回复");
 
 }

+ 22 - 19
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java

@@ -9,36 +9,38 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== APP 模块 1007000000 ==========
-    ErrorCode APP_NOT_FOUND = new ErrorCode(1007000000, "App 不存在");
-    ErrorCode APP_IS_DISABLE = new ErrorCode(1007000002, "App 已经被禁用");
-    ErrorCode APP_EXIST_ORDER_CANT_DELETE =  new ErrorCode(1007000003, "支付应用存在支付订单,无法删除");
-    ErrorCode APP_EXIST_REFUND_CANT_DELETE =  new ErrorCode(1007000004, "支付应用存在退款订单,无法删除");
+    // ========== APP 模块 1-007-000-000 ==========
+    ErrorCode APP_NOT_FOUND = new ErrorCode(1_007_000_000, "App 不存在");
+    ErrorCode APP_IS_DISABLE = new ErrorCode(1_007_000_002, "App 已经被禁用");
+    ErrorCode APP_EXIST_ORDER_CANT_DELETE =  new ErrorCode(1_007_000_003, "支付应用存在支付订单,无法删除");
+    ErrorCode APP_EXIST_REFUND_CANT_DELETE =  new ErrorCode(1_007_000_004, "支付应用存在退款订单,无法删除");
 
-    // ========== CHANNEL 模块 1007001000 ==========
-    ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1007001000, "支付渠道的配置不存在");
-    ErrorCode CHANNEL_IS_DISABLE = new ErrorCode(1007001001, "支付渠道已经禁用");
-    ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001004, "已存在相同的渠道");
+    // ========== CHANNEL 模块 1-007-001-000 ==========
+    ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1_007_001_000, "支付渠道的配置不存在");
+    ErrorCode CHANNEL_IS_DISABLE = new ErrorCode(1_007_001_001, "支付渠道已经禁用");
+    ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1_007_001_004, "已存在相同的渠道");
 
-    // ========== ORDER 模块 1007002000 ==========
+    // ========== ORDER 模块 1-007-002-000 ==========
     ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1007002000, "支付订单不存在");
     ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付");
     ErrorCode PAY_ORDER_STATUS_IS_SUCCESS = new ErrorCode(1007002002, "订单已支付,请刷新页面");
     ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1007002003, "支付订单已经过期");
     ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1007002004, "发起支付报错,错误码:{},错误提示:{}");
     ErrorCode PAY_ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1007002005, "支付订单退款失败,原因:状态不是已支付或已退款");
+    ErrorCode ORDER_UPDATE_PRICE_FAIL_PAID = new ErrorCode(1_007_002_006, "支付订单调价失败,原因:支付订单已付款,不能调价");
+    ErrorCode ORDER_UPDATE_PRICE_FAIL_EQUAL = new ErrorCode(1007002007, "支付订单调价失败,原因:价格没有变化");
 
-    // ========== ORDER 模块(拓展单) 1007003000 ==========
+    // ========== ORDER 模块(拓展单) 1-007-003-000 ==========
     ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在");
     ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付");
     ErrorCode PAY_ORDER_EXTENSION_IS_PAID = new ErrorCode(1007003002, "订单已支付,请等待支付结果");
 
-    // ========== 支付模块(退款) 1007006000 ==========
-    ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
-    ErrorCode REFUND_HAS_REFUNDING = new ErrorCode(1007006002, "已经有退款在处理中");
-    ErrorCode REFUND_EXISTS = new ErrorCode(1007006003, "已经存在退款单");
-    ErrorCode REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
-    ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, "支付退款单不处于待退款");
+    // ========== 支付模块(退款) 1-007-006-000 ==========
+    ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1_007_006_000, "退款金额超过订单可退款金额");
+    ErrorCode REFUND_HAS_REFUNDING = new ErrorCode(1_007_006_002, "已经有退款在处理中");
+    ErrorCode REFUND_EXISTS = new ErrorCode(1_007_006_003, "已经存在退款单");
+    ErrorCode REFUND_NOT_FOUND = new ErrorCode(1_007_006_004, "支付退款单不存在");
+    ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_006_005, "支付退款单不处于待退款");
 
     // ========== 钱包模块 1007007000 ==========
     ErrorCode WALLET_NOT_FOUND = new ErrorCode(1007007000, "用户钱包不存在");
@@ -47,7 +49,7 @@ public interface ErrorCodeConstants {
     ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1007007003, "已经存在钱包退款");
     ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1007007004, "钱包冻结余额不足");
 
-    // ========== 钱包充值模块 1007008000 ==========
+    // ========== 钱包模块 1-007-007-000 ==========
     ErrorCode WALLET_RECHARGE_NOT_FOUND = new ErrorCode(1007008000, "钱包充值记录不存在");
     ErrorCode WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007008001, "钱包充值更新支付状态失败,钱包充值记录不是【未支付】状态");
     ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1007008002, "钱包充值更新支付状态失败,支付单编号不匹配");
@@ -59,7 +61,8 @@ public interface ErrorCodeConstants {
     ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1007008008, "钱包退款更新失败,钱包退款单编号不匹配");
     ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1007008009, "钱包退款更新失败,退款订单不存在");
     ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1007008010, "钱包退款更新失败,退款单金额不匹配");
-    // ========== 示例订单 1007900000 ==========
+
+    // ========== 示例订单 1-007-900-000 ==========
     ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");
     ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, "示例订单更新支付状态失败,订单不是【未支付】状态");
     ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1007900002, "示例订单更新支付状态失败,支付单编号不匹配");

+ 4 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java

@@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
-import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
@@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
+import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
@@ -53,9 +53,8 @@ public class PayNotifyController {
     private PayNotifyService notifyService;
     @Resource
     private PayAppService appService;
-
     @Resource
-    private PayClientFactory payClientFactory;
+    private PayChannelService channelService;
 
     @PostMapping(value = "/order/{channelId}")
     @Operation(summary = "支付渠道的统一【支付】回调")
@@ -66,7 +65,7 @@ public class PayNotifyController {
                               @RequestBody(required = false) String body) {
         log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body);
         // 1. 校验支付渠道是否存在
-        PayClient payClient = payClientFactory.getPayClient(channelId);
+        PayClient payClient = channelService.getPayClient(channelId);
         if (payClient == null) {
             log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
             throw exception(CHANNEL_NOT_FOUND);
@@ -87,7 +86,7 @@ public class PayNotifyController {
                               @RequestBody(required = false) String body) {
         log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
         // 1. 校验支付渠道是否存在
-        PayClient payClient = payClientFactory.getPayClient(channelId);
+        PayClient payClient = channelService.getPayClient(channelId);
         if (payClient == null) {
             log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
             throw exception(CHANNEL_NOT_FOUND);

+ 4 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java

@@ -13,16 +13,16 @@ import java.time.LocalDateTime;
 @ToString(callSuper = true)
 public class PayOrderDetailsRespVO extends PayOrderBaseVO {
 
-    @Schema(description = "支付订单编号", required = true, example = "1024")
+    @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
     @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
     private String appName;
 
-    @Schema(description = "创建时间", required = true)
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
-    @Schema(description = "更新时间", required = true)
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime updateTime;
 
     /**
@@ -34,7 +34,7 @@ public class PayOrderDetailsRespVO extends PayOrderBaseVO {
     @Schema(description = "支付订单扩展")
     public static class PayOrderExtension {
 
-        @Schema(description = "支付订单号", required = true, example = "1024")
+        @Schema(description = "支付订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
         private String no;
 
         @Schema(description = "支付异步通知的内容")

+ 0 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java

@@ -28,7 +28,4 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
                 .eq(PayChannelDO::getStatus, status));
     }
 
-    @Select("SELECT COUNT(*) FROM pay_channel WHERE update_time > #{maxUpdateTime}")
-    Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
 }

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java

@@ -15,7 +15,6 @@ import javax.annotation.Resource;
  * @author 芋道源码
  */
 @Component
-@TenantJob // 多租户
 @Slf4j
 public class PayNotifyJob implements JobHandler {
 
@@ -23,6 +22,7 @@ public class PayNotifyJob implements JobHandler {
     private PayNotifyService payNotifyService;
 
     @Override
+    @TenantJob
     public String execute(String param) throws Exception {
         int notifyCount = payNotifyService.executeNotify();
         return String.format("执行支付通知 %s 个", notifyCount);

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java

@@ -16,13 +16,13 @@ import javax.annotation.Resource;
  * @author 芋道源码
  */
 @Component
-@TenantJob
 public class PayOrderExpireJob implements JobHandler {
 
     @Resource
     private PayOrderService orderService;
 
     @Override
+    @TenantJob
     public String execute(String param) {
         int count = orderService.expireOrder();
         return StrUtil.format("支付过期 {} 个", count);

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java

@@ -18,7 +18,6 @@ import java.time.LocalDateTime;
  * @author 芋道源码
  */
 @Component
-@TenantJob
 public class PayOrderSyncJob implements JobHandler {
 
     /**
@@ -34,6 +33,7 @@ public class PayOrderSyncJob implements JobHandler {
     private PayOrderService orderService;
 
     @Override
+    @TenantJob
     public String execute(String param) {
         LocalDateTime minCreateTime = LocalDateTime.now().minus(CREATE_TIME_DURATION_BEFORE);
         int count = orderService.syncOrder(minCreateTime);

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java

@@ -16,13 +16,13 @@ import javax.annotation.Resource;
  * @author 芋道源码
  */
 @Component
-@TenantJob
 public class PayRefundSyncJob implements JobHandler {
 
     @Resource
     private PayRefundService refundService;
 
     @Override
+    @TenantJob
     public String execute(String param) {
         int count = refundService.syncRefund();
         return StrUtil.format("同步退款订单 {} 个", count);

+ 9 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.pay.service.channel;
 
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
@@ -92,4 +93,12 @@ public interface PayChannelService {
      */
     List<PayChannelDO> getEnableChannelList(Long appId);
 
+    /**
+     * 获得指定编号的支付客户端
+     *
+     * @param id 编号
+     * @return 支付客户端
+     */
+    PayClient getPayClient(Long id);
+
 }

+ 52 - 83
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java

@@ -1,38 +1,33 @@
 package cn.iocoder.yudao.module.pay.service.channel;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
-import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
 import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
-import cn.iocoder.yudao.module.pay.framework.pay.wallet.WalletPayClient;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import lombok.Getter;
-import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.validation.Validator;
-import java.time.LocalDateTime;
+import java.time.Duration;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 
 /**
@@ -45,71 +40,34 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 @Validated
 public class PayChannelServiceImpl implements PayChannelService {
 
-    @Getter // 为了方便测试,这里提供 getter 方法
-    @Setter
-    private volatile List<PayChannelDO> channelCache;
+    /**
+     * {@link PayClient} 缓存,通过它异步清空 smsClientFactory
+     */
+    @Getter
+    private final LoadingCache<Long, PayClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
+            new CacheLoader<Long, PayClient>() {
+
+                @Override
+                public PayClient load(Long id) {
+                    // 查询,然后尝试清空
+                    PayChannelDO channel = payChannelMapper.selectById(id);
+                    if (channel != null) {
+                        payClientFactory.createOrUpdatePayClient(channel.getId(), channel.getCode(), channel.getConfig());
+                    }
+                    return payClientFactory.getPayClient(id);
+                }
+
+            });
 
     @Resource
     private PayClientFactory payClientFactory;
 
     @Resource
-    private PayChannelMapper channelMapper;
+    private PayChannelMapper payChannelMapper;
 
     @Resource
     private Validator validator;
 
-    /**
-     * 初始化 {@link #payClientFactory} 缓存
-     */
-    @PostConstruct
-    public void initLocalCache() {
-        // 注册钱包支付 Class
-        payClientFactory.registerPayClientClass(PayChannelEnum.WALLET, WalletPayClient.class);
-
-        // 注意:忽略自动多租户,因为要全局初始化缓存
-        TenantUtils.executeIgnore(() -> {
-            // 第一步:查询数据
-            List<PayChannelDO> channels = Collections.emptyList();
-            try {
-                channels = channelMapper.selectList();
-            } catch (Throwable ex) {
-                if (!ex.getMessage().contains("doesn't exist")) {
-                    throw ex;
-                }
-                log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
-            }
-            log.info("[initLocalCache][缓存支付渠道,数量为:{}]", channels.size());
-
-            // 第二步:构建缓存:创建或更新支付 Client
-            channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
-                    payChannel.getCode(), payChannel.getConfig()));
-            this.channelCache = channels;
-        });
-    }
-
-    /**
-     * 通过定时任务轮询,刷新缓存
-     *
-     * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
-     */
-    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
-    public void refreshLocalCache() {
-        // 注意:忽略自动多租户,因为要全局初始化缓存
-        TenantUtils.executeIgnore(() -> {
-            // 情况一:如果缓存里没有数据,则直接刷新缓存
-            if (CollUtil.isEmpty(channelCache)) {
-                initLocalCache();
-                return;
-            }
-
-            // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
-            LocalDateTime maxTime = CollectionUtils.getMaxValue(channelCache, PayChannelDO::getUpdateTime);
-            if (channelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
-                initLocalCache();
-            }
-        });
-    }
-
     @Override
     public Long createChannel(PayChannelCreateReqVO reqVO) {
         // 断言是否有重复的
@@ -121,10 +79,7 @@ public class PayChannelServiceImpl implements PayChannelService {
         // 新增渠道
         PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO)
                 .setConfig(parseConfig(reqVO.getCode(), reqVO.getConfig()));
-        channelMapper.insert(channel);
-
-        // 刷新缓存
-        initLocalCache();
+        payChannelMapper.insert(channel);
         return channel.getId();
     }
 
@@ -136,10 +91,10 @@ public class PayChannelServiceImpl implements PayChannelService {
         // 更新
         PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO)
                 .setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig()));
-        channelMapper.updateById(channel);
+        payChannelMapper.updateById(channel);
 
-        // 刷新缓存
-        initLocalCache();
+        // 清空缓存
+        clearCache(channel.getId());
     }
 
     /**
@@ -169,14 +124,23 @@ public class PayChannelServiceImpl implements PayChannelService {
         validateChannelExists(id);
 
         // 删除
-        channelMapper.deleteById(id);
+        payChannelMapper.deleteById(id);
 
-        // 刷新缓存
-        initLocalCache();
+        // 清空缓存
+        clearCache(id);
+    }
+
+    /**
+     * 删除缓存
+     *
+     * @param id 渠道编号
+     */
+    private void clearCache(Long id) {
+        clientCache.invalidate(id);
     }
 
     private PayChannelDO validateChannelExists(Long id) {
-        PayChannelDO channel = channelMapper.selectById(id);
+        PayChannelDO channel = payChannelMapper.selectById(id);
         if (channel == null) {
             throw exception(CHANNEL_NOT_FOUND);
         }
@@ -185,29 +149,29 @@ public class PayChannelServiceImpl implements PayChannelService {
 
     @Override
     public PayChannelDO getChannel(Long id) {
-        return channelMapper.selectById(id);
+        return payChannelMapper.selectById(id);
     }
 
     @Override
     public List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds) {
-        return channelMapper.selectListByAppIds(appIds);
+        return payChannelMapper.selectListByAppIds(appIds);
     }
 
     @Override
     public PayChannelDO getChannelByAppIdAndCode(Long appId, String code) {
-        return channelMapper.selectByAppIdAndCode(appId, code);
+        return payChannelMapper.selectByAppIdAndCode(appId, code);
     }
 
     @Override
     public PayChannelDO validPayChannel(Long id) {
-        PayChannelDO channel = channelMapper.selectById(id);
+        PayChannelDO channel = payChannelMapper.selectById(id);
         validPayChannel(channel);
         return channel;
     }
 
     @Override
     public PayChannelDO validPayChannel(Long appId, String code) {
-        PayChannelDO channel = channelMapper.selectByAppIdAndCode(appId, code);
+        PayChannelDO channel = payChannelMapper.selectByAppIdAndCode(appId, code);
         validPayChannel(channel);
         return channel;
     }
@@ -223,7 +187,12 @@ public class PayChannelServiceImpl implements PayChannelService {
 
     @Override
     public List<PayChannelDO> getEnableChannelList(Long appId) {
-        return channelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus());
+        return payChannelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus());
+    }
+
+    @Override
+    public PayClient getPayClient(Long id) {
+        return clientCache.getUnchecked(id);
     }
 
 }

+ 5 - 9
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java

@@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
-import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
@@ -61,9 +60,6 @@ public class PayOrderServiceImpl implements PayOrderService {
     private PayProperties payProperties;
 
     @Resource
-    private PayClientFactory payClientFactory;
-
-    @Resource
     private PayOrderMapper orderMapper;
     @Resource
     private PayOrderExtensionMapper orderExtensionMapper;
@@ -134,7 +130,7 @@ public class PayOrderServiceImpl implements PayOrderService {
         PayOrderDO order = validateOrderCanSubmit(reqVO.getId());
         // 1.32 校验支付渠道是否有效
         PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
-        PayClient client = payClientFactory.getPayClient(channel.getId());
+        PayClient client = channelService.getPayClient(channel.getId());
 
         // 2. 插入 PayOrderExtensionDO
         String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
@@ -205,7 +201,7 @@ public class PayOrderServiceImpl implements PayOrderService {
                 throw exception(PAY_ORDER_EXTENSION_IS_PAID);
             }
             // 情况二:调用三方接口,查询支付单状态,是不是已支付
-            PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId());
+            PayClient payClient = channelService.getPayClient(orderExtension.getChannelId());
             if (payClient == null) {
                 log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
                 return;
@@ -224,7 +220,7 @@ public class PayOrderServiceImpl implements PayOrderService {
         appService.validPayApp(appId);
         // 校验支付渠道是否有效
         PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
-        PayClient client = payClientFactory.getPayClient(channel.getId());
+        PayClient client = channelService.getPayClient(channel.getId());
         if (client == null) {
             log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
             throw exception(CHANNEL_NOT_FOUND);
@@ -464,7 +460,7 @@ public class PayOrderServiceImpl implements PayOrderService {
     private boolean syncOrder(PayOrderExtensionDO orderExtension) {
         try {
             // 1.1 查询支付订单信息
-            PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId());
+            PayClient payClient = channelService.getPayClient(orderExtension.getChannelId());
             if (payClient == null) {
                 log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
                 return false;
@@ -519,7 +515,7 @@ public class PayOrderServiceImpl implements PayOrderService {
                     return false;
                 }
                 // 情况二:调用三方接口,查询支付单状态,是不是已支付/已退款
-                PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId());
+                PayClient payClient = channelService.getPayClient(orderExtension.getChannelId());
                 if (payClient == null) {
                     log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
                     return false;

+ 2 - 6
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java

@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
-import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
@@ -53,9 +52,6 @@ public class PayRefundServiceImpl implements PayRefundService {
     private PayProperties payProperties;
 
     @Resource
-    private PayClientFactory payClientFactory;
-
-    @Resource
     private PayRefundMapper refundMapper;
     @Resource
     private PayNoRedisDAO noRedisDAO;
@@ -102,7 +98,7 @@ public class PayRefundServiceImpl implements PayRefundService {
         PayOrderDO order = validatePayOrderCanRefund(reqDTO);
         // 1.3 校验支付渠道是否有效
         PayChannelDO channel = channelService.validPayChannel(order.getChannelId());
-        PayClient client = payClientFactory.getPayClient(channel.getId());
+        PayClient client = channelService.getPayClient(channel.getId());
         if (client == null) {
             log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
             throw exception(CHANNEL_NOT_FOUND);
@@ -305,7 +301,7 @@ public class PayRefundServiceImpl implements PayRefundService {
     private boolean syncRefund(PayRefundDO refund) {
         try {
             // 1.1 查询退款订单信息
-            PayClient payClient = payClientFactory.getPayClient(refund.getChannelId());
+            PayClient payClient = channelService.getPayClient(refund.getChannelId());
             if (payClient == null) {
                 log.error("[syncRefund][渠道编号({}) 找不到对应的支付客户端]", refund.getChannelId());
                 return false;

+ 28 - 50
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.channel;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 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.weixin.WxPayClientConfig;
@@ -12,30 +13,26 @@ import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateR
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
 import com.alibaba.fastjson.JSON;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import javax.validation.Validator;
-
-import java.time.Duration;
 import java.util.Collections;
 import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
 
 @Import({PayChannelServiceImpl.class})
 public class PayChannelServiceTest extends BaseDbUnitTest {
 
-    private static final String ALIPAY_SERVER_URL = "https://openapi.alipay.com/gateway.do";
-
     @Resource
     private PayChannelServiceImpl channelService;
 
@@ -47,45 +44,6 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
     @MockBean
     private Validator validator;
 
-    @BeforeEach
-    public void setUp() {
-        channelService.setChannelCache(null);
-    }
-
-    @Test
-    public void testInitLocalCache() {
-        // mock 数据
-        PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
-                o -> o.setConfig(randomWxPayClientConfig()));
-        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
-
-        // 调用
-        channelService.initLocalCache();
-        // 校验缓存
-        assertEquals(1, channelService.getChannelCache().size());
-        assertEquals(dbChannel, channelService.getChannelCache().get(0));
-    }
-
-    @Test
-    public void testRefreshLocalCache() {
-        // mock 数据 01
-        PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
-                o -> o.setConfig(randomWxPayClientConfig()).setUpdateTime(addTime(Duration.ofMinutes(-2))));
-        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
-        channelService.initLocalCache();
-        // mock 数据 02
-        PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class,
-                o -> o.setConfig(randomWxPayClientConfig()));
-        channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
-
-        // 调用
-        channelService.refreshLocalCache();
-        // 校验缓存
-        assertEquals(2, channelService.getChannelCache().size());
-        assertEquals(dbChannel, channelService.getChannelCache().get(0));
-        assertEquals(dbChannel02, channelService.getChannelCache().get(1));
-    }
-
     @Test
     public void testCreateChannel_success() {
         // 准备参数
@@ -103,8 +61,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         assertPojoEquals(reqVO, channel, "config");
         assertPojoEquals(config, channel.getConfig());
         // 校验缓存
-        assertEquals(1, channelService.getChannelCache().size());
-        assertEquals(channel, channelService.getChannelCache().get(0));
+        assertNull(channelService.getClientCache().getIfPresent(channelId));
     }
 
     @Test
@@ -146,8 +103,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         assertPojoEquals(reqVO, channel, "config");
         assertPojoEquals(config, channel.getConfig());
         // 校验缓存
-        assertEquals(1, channelService.getChannelCache().size());
-        assertEquals(channel, channelService.getChannelCache().get(0));
+        assertNull(channelService.getClientCache().getIfPresent(channel.getId()));
     }
 
     @Test
@@ -179,7 +135,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         // 校验数据不存在了
         assertNull(channelMapper.selectById(id));
         // 校验缓存
-        assertEquals(0, channelService.getChannelCache().size());
+        assertNull(channelService.getClientCache().getIfPresent(id));
     }
 
     @Test
@@ -344,6 +300,28 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         assertPojoEquals(channel, dbChannel03);
     }
 
+    @Test
+    public void testGetPayClient() {
+        // mock 数据
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+        });
+        channelMapper.insert(channel);
+        // mock 参数
+        Long id = channel.getId();
+        // mock 方法
+        PayClient mockClient = mock(PayClient.class);
+        when(payClientFactory.getPayClient(eq(id))).thenReturn(mockClient);
+
+        // 调用
+        PayClient client = channelService.getPayClient(id);
+        // 断言
+        assertSame(client, mockClient);
+        verify(payClientFactory).createOrUpdatePayClient(eq(id), eq(channel.getCode()),
+                eq(channel.getConfig()));
+    }
+
     public WxPayClientConfig randomWxPayClientConfig() {
         return new WxPayClientConfig()
                 .setAppId(randomString())

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

@@ -9,7 +9,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== AUTH 模块 1003000000 ==========
-    ErrorCode GO_VIEW_PROJECT_NOT_EXISTS = new ErrorCode(1003000000, "GoView 项目不存在");
+    // ========== AUTH 模块 1-003-000-000 ==========
+    ErrorCode GO_VIEW_PROJECT_NOT_EXISTS = new ErrorCode(1_003_000_000, "GoView 项目不存在");
 
 }

+ 0 - 5
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApi.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.system.api.notify;
 
 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
-import cn.iocoder.yudao.module.system.api.notify.dto.NotifyTemplateReqDTO;
 
 import javax.validation.Valid;
 
@@ -28,8 +27,4 @@ public interface NotifyMessageSendApi {
      */
     Long sendSingleMessageToMember(@Valid NotifySendSingleToUserReqDTO reqDTO);
 
-
-    boolean validateNotifyTemplate(String orderDelivery);
-
-    void createNotifyTemplate(NotifyTemplateReqDTO templateReqDTO);
 }

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

@@ -9,158 +9,158 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ========== AUTH 模块 1002000000 ==========
-    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
-    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
-    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确,原因:{}");
-    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
-    ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期");
-    ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1002000007, "手机号不存在");
-
-    // ========== 菜单模块 1002001000 ==========
-    ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002001000, "已经存在该名字的菜单");
-    ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1002001001, "父菜单不存在");
-    ErrorCode MENU_PARENT_ERROR = new ErrorCode(1002001002, "不能设置自己为父菜单");
-    ErrorCode MENU_NOT_EXISTS = new ErrorCode(1002001003, "菜单不存在");
-    ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1002001004, "存在子菜单,无法删除");
-    ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1002001005, "父菜单的类型必须是目录或者菜单");
-
-    // ========== 角色模块 1002002000 ==========
-    ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1002002000, "角色不存在");
-    ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1002002001, "已经存在名为【{}】的角色");
-    ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1002002002, "已经存在编码为【{}】的角色");
-    ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1002002003, "不能操作类型为系统内置的角色");
-    ErrorCode ROLE_IS_DISABLE = new ErrorCode(1002002004, "名字为【{}】的角色已被禁用");
-    ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1002002005, "编码【{}】不能使用");
-
-    // ========== 用户模块 1002003000 ==========
-    ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1002003000, "用户账号已经存在");
-    ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1002003001, "手机号已经存在");
-    ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1002003002, "邮箱已经存在");
-    ErrorCode USER_NOT_EXISTS = new ErrorCode(1002003003, "用户不存在");
-    ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002003004, "导入用户数据不能为空!");
-    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1002003005, "用户密码校验失败");
-    ErrorCode USER_IS_DISABLE = new ErrorCode(1002003006, "名字为【{}】的用户已被禁用");
-    ErrorCode USER_COUNT_MAX = new ErrorCode(1002003008, "创建用户失败,原因:超过租户最大租户配额({})!");
-
-    // ========== 部门模块 1002004000 ==========
-    ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1002004000, "已经存在该名字的部门");
-    ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1002004001,"父级部门不存在");
-    ErrorCode DEPT_NOT_FOUND = new ErrorCode(1002004002, "当前部门不存在");
-    ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1002004003, "存在子部门,无法删除");
-    ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1002004004, "不能设置自己为父部门");
-    ErrorCode DEPT_EXISTS_USER = new ErrorCode(1002004005, "部门中存在员工,无法删除");
-    ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1002004006, "部门({})不处于开启状态,不允许选择");
-    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1002004007, "不能设置自己的子部门为父部门");
-
-    // ========== 岗位模块 1002005000 ==========
-    ErrorCode POST_NOT_FOUND = new ErrorCode(1002005000, "当前岗位不存在");
-    ErrorCode POST_NOT_ENABLE = new ErrorCode(1002005001, "岗位({}) 不处于开启状态,不允许选择");
-    ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1002005002, "已经存在该名字的岗位");
-    ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1002005003, "已经存在该标识的岗位");
-
-    // ========== 字典类型 1002006000 ==========
-    ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1002006001, "当前字典类型不存在");
-    ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1002006002, "字典类型不处于开启状态,不允许选择");
-    ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1002006003, "已经存在该名字的字典类型");
-    ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1002006004, "已经存在该类型的字典类型");
-    ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1002006005, "无法删除,该字典类型还有字典数据");
-
-    // ========== 字典数据 1002007000 ==========
-    ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1002007001, "当前字典数据不存在");
-    ErrorCode DICT_DATA_NOT_ENABLE = new ErrorCode(1002007002, "字典数据({})不处于开启状态,不允许选择");
-    ErrorCode DICT_DATA_VALUE_DUPLICATE = new ErrorCode(1002007003, "已经存在该值的字典数据");
-
-    // ========== 通知公告 1002008000 ==========
-    ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1002008001, "当前通知公告不存在");
-
-    // ========== 短信渠道 1002011000 ==========
-    ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1002011000, "短信渠道不存在");
-    ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1002011001, "短信渠道不处于开启状态,不允许选择");
-    ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1002011002, "无法删除,该短信渠道还有短信模板");
-
-    // ========== 短信模板 1002012000 ==========
-    ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1002012000, "短信模板不存在");
-    ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002012001, "已经存在编码为【{}】的短信模板");
-
-    // ========== 短信发送 1002013000 ==========
-    ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1002013000, "手机号不存在");
-    ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002013001, "模板参数({})缺失");
-    ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1002013002, "短信模板不存在");
-
-    // ========== 短信验证码 1002014000 ==========
-    ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1002014000, "验证码不存在");
-    ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1002014001, "验证码已过期");
-    ErrorCode SMS_CODE_USED = new ErrorCode(1002014002, "验证码已使用");
-    ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1002014003, "验证码不正确");
-    ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1002014004, "超过每日短信发送数量");
-    ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1002014005, "短信发送过于频率");
-    ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1002014006, "手机号已被使用");
-    ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1002014007, "验证码未被使用");
-
-    // ========== 租户信息 1002015000 ==========
-    ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1002015000, "租户不存在");
-    ErrorCode TENANT_DISABLE = new ErrorCode(1002015001, "名字为【{}】的租户已被禁用");
-    ErrorCode TENANT_EXPIRE = new ErrorCode(1002015002, "名字为【{}】的租户已过期");
-    ErrorCode TENANT_CAN_NOT_UPDATE_SYSTEM = new ErrorCode(1002015003, "系统租户不能进行修改、删除等操作!");
-    ErrorCode TENANT_NAME_DUPLICATE = new ErrorCode(1002015004, "名字为【{}】的租户已存在");
-
-    // ========== 租户套餐 1002016000 ==========
-    ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1002016000, "租户套餐不存在");
-    ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1002016001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除");
-    ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1002016002, "名字为【{}】的租户套餐已被禁用");
-
-    // ========== 错误码模块 1002017000 ==========
-    ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002017000, "错误码不存在");
-    ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002017001, "已经存在编码为【{}】的错误码");
-
-    // ========== 社交用户 1002018000 ==========
-    ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1002018000, "社交授权失败,原因是:{}");
-    ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定");
-    ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户");
-
-    // ========== 系统敏感词 1002019000 =========
-    ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在");
-    ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在");
-
-    // ========== OAuth2 客户端 1002020000 =========
-    ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在");
-    ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1002020001, "OAuth2 客户端编号已存在");
-    ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1002020002, "OAuth2 客户端已禁用");
-    ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1002020003, "不支持该授权类型");
-    ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1002020004, "授权范围过大");
-    ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1002020005, "无效 redirect_uri: {}");
-    ErrorCode OAUTH2_CLIENT_CLIENT_SECRET_ERROR = new ErrorCode(1002020006, "无效 client_secret: {}");
-
-    // ========== OAuth2 授权 1002021000 =========
-    ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1002021000, "client_id 不匹配");
-    ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1002021001, "redirect_uri 不匹配");
-    ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1002021002, "state 不匹配");
-    ErrorCode OAUTH2_GRANT_CODE_NOT_EXISTS = new ErrorCode(1002021003, "code 不存在");
-
-    // ========== OAuth2 授权 1002022000 =========
-    ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1002022000, "code 不存在");
-    ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1002022001, "code 已过期");
-
-    // ========== 邮箱账号 1002023000 ==========
-    ErrorCode MAIL_ACCOUNT_NOT_EXISTS = new ErrorCode(1002023000, "邮箱账号不存在");
-    ErrorCode MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS = new ErrorCode(1002023001, "无法删除,该邮箱账号还有邮件模板");
-
-    // ========== 邮件模版 1002024000 ==========
-    ErrorCode MAIL_TEMPLATE_NOT_EXISTS = new ErrorCode(1002024000, "邮件模版不存在");
-    ErrorCode MAIL_TEMPLATE_CODE_EXISTS = new ErrorCode(1002024001, "邮件模版 code({}) 已存在");
-
-    // ========== 邮件发送 1002025000 ==========
-    ErrorCode MAIL_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1002025000, "模板参数({})缺失");
-    ErrorCode MAIL_SEND_MAIL_NOT_EXISTS = new ErrorCode(1002025001, "邮箱不存在");
-
-    // ========== 站内信模版 1002026000 ==========
-    ErrorCode NOTIFY_TEMPLATE_NOT_EXISTS = new ErrorCode(1002026000, "站内信模版不存在");
-    ErrorCode NOTIFY_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002026001, "已经存在编码为【{}】的站内信模板");
-
-    // ========== 站内信模版 1002027000 ==========
-
-    // ========== 站内信发送 1002028000 ==========
-    ErrorCode NOTIFY_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1002028000, "模板参数({})缺失");
+    // ========== AUTH 模块 1-002-000-000 ==========
+    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_002_000_000, "登录失败,账号密码不正确");
+    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_002_000_001, "登录失败,账号被禁用");
+    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_004, "验证码不正确,原因:{}");
+    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, "未绑定账号,需要进行绑定");
+    ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1_002_000_006, "Token 已经过期");
+    ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, "手机号不存在");
+
+    // ========== 菜单模块 1-002-001-000 ==========
+    ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, "已经存在该名字的菜单");
+    ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1_002_001_001, "父菜单不存在");
+    ErrorCode MENU_PARENT_ERROR = new ErrorCode(1_002_001_002, "不能设置自己为父菜单");
+    ErrorCode MENU_NOT_EXISTS = new ErrorCode(1_002_001_003, "菜单不存在");
+    ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1_002_001_004, "存在子菜单,无法删除");
+    ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1_002_001_005, "父菜单的类型必须是目录或者菜单");
+
+    // ========== 角色模块 1-002-002-000 ==========
+    ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在");
+    ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1_002_002_001, "已经存在名为【{}】的角色");
+    ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1_002_002_002, "已经存在编码为【{}】的角色");
+    ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, "不能操作类型为系统内置的角色");
+    ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, "名字为【{}】的角色已被禁用");
+    ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "编码【{}】不能使用");
+
+    // ========== 用户模块 1-002-003-000 ==========
+    ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, "用户账号已经存在");
+    ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1_002_003_001, "手机号已经存在");
+    ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1_002_003_002, "邮箱已经存在");
+    ErrorCode USER_NOT_EXISTS = new ErrorCode(1_002_003_003, "用户不存在");
+    ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_002_003_004, "导入用户数据不能为空!");
+    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1_002_003_005, "用户密码校验失败");
+    ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, "名字为【{}】的用户已被禁用");
+    ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!");
+
+    // ========== 部门模块 1-002-004-000 ==========
+    ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门");
+    ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1_002_004_001,"父级部门不存在");
+    ErrorCode DEPT_NOT_FOUND = new ErrorCode(1_002_004_002, "当前部门不存在");
+    ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, "存在子部门,无法删除");
+    ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, "不能设置自己为父部门");
+    ErrorCode DEPT_EXISTS_USER = new ErrorCode(1_002_004_005, "部门中存在员工,无法删除");
+    ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, "部门({})不处于开启状态,不允许选择");
+    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, "不能设置自己的子部门为父部门");
+
+    // ========== 岗位模块 1-002-005-000 ==========
+    ErrorCode POST_NOT_FOUND = new ErrorCode(1_002_005_000, "当前岗位不存在");
+    ErrorCode POST_NOT_ENABLE = new ErrorCode(1_002_005_001, "岗位({}) 不处于开启状态,不允许选择");
+    ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1_002_005_002, "已经存在该名字的岗位");
+    ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1_002_005_003, "已经存在该标识的岗位");
+
+    // ========== 字典类型 1-002-006-000 ==========
+    ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1_002_006_001, "当前字典类型不存在");
+    ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1_002_006_002, "字典类型不处于开启状态,不允许选择");
+    ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1_002_006_003, "已经存在该名字的字典类型");
+    ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1_002_006_004, "已经存在该类型的字典类型");
+    ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1_002_006_005, "无法删除,该字典类型还有字典数据");
+
+    // ========== 字典数据 1-002-007-000 ==========
+    ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1_002_007_001, "当前字典数据不存在");
+    ErrorCode DICT_DATA_NOT_ENABLE = new ErrorCode(1_002_007_002, "字典数据({})不处于开启状态,不允许选择");
+    ErrorCode DICT_DATA_VALUE_DUPLICATE = new ErrorCode(1_002_007_003, "已经存在该值的字典数据");
+
+    // ========== 通知公告 1-002-008-000 ==========
+    ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1_002_008_001, "当前通知公告不存在");
+
+    // ========== 短信渠道 1-002-011-000 ==========
+    ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1_002_011_000, "短信渠道不存在");
+    ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1_002_011_001, "短信渠道不处于开启状态,不允许选择");
+    ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1_002_011_002, "无法删除,该短信渠道还有短信模板");
+
+    // ========== 短信模板 1-002-012-000 ==========
+    ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, "短信模板不存在");
+    ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_012_001, "已经存在编码为【{}】的短信模板");
+
+    // ========== 短信发送 1-002-013-000 ==========
+    ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1_002_013_000, "手机号不存在");
+    ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_013_001, "模板参数({})缺失");
+    ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_013_002, "短信模板不存在");
+
+    // ========== 短信验证码 1-002-014-000 ==========
+    ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在");
+    ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期");
+    ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用");
+    ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1_002_014_003, "验证码不正确");
+    ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量");
+    ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频率");
+    ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1_002_014_006, "手机号已被使用");
+    ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1_002_014_007, "验证码未被使用");
+
+    // ========== 租户信息 1-002-015-000 ==========
+    ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在");
+    ErrorCode TENANT_DISABLE = new ErrorCode(1_002_015_001, "名字为【{}】的租户已被禁用");
+    ErrorCode TENANT_EXPIRE = new ErrorCode(1_002_015_002, "名字为【{}】的租户已过期");
+    ErrorCode TENANT_CAN_NOT_UPDATE_SYSTEM = new ErrorCode(1_002_015_003, "系统租户不能进行修改、删除等操作!");
+    ErrorCode TENANT_NAME_DUPLICATE = new ErrorCode(1_002_015_004, "名字为【{}】的租户已存在");
+
+    // ========== 租户套餐 1-002-016-000 ==========
+    ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1_002_016_000, "租户套餐不存在");
+    ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1_002_016_001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除");
+    ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1_002_016_002, "名字为【{}】的租户套餐已被禁用");
+
+    // ========== 错误码模块 1-002-017-000 ==========
+    ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1_002_017_000, "错误码不存在");
+    ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1_002_017_001, "已经存在编码为【{}】的错误码");
+
+    // ========== 社交用户 1-002-018-000 ==========
+    ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1_002_018_000, "社交授权失败,原因是:{}");
+    ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1_002_018_001, "社交解绑失败,非当前用户绑定");
+    ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1_002_018_002, "社交授权失败,找不到对应的用户");
+
+    // ========== 系统敏感词 1-002-019-000 =========
+    ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_002_019_000, "系统敏感词在所有标签中都不存在");
+    ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1_002_019_001, "系统敏感词已在标签中存在");
+
+    // ========== OAuth2 客户端 1-002-020-000 =========
+    ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在");
+    ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1_002_020_001, "OAuth2 客户端编号已存在");
+    ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1_002_020_002, "OAuth2 客户端已禁用");
+    ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1_002_020_003, "不支持该授权类型");
+    ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1_002_020_004, "授权范围过大");
+    ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1_002_020_005, "无效 redirect_uri: {}");
+    ErrorCode OAUTH2_CLIENT_CLIENT_SECRET_ERROR = new ErrorCode(1_002_020_006, "无效 client_secret: {}");
+
+    // ========== OAuth2 授权 1-002-021-000 =========
+    ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1_002_021_000, "client_id 不匹配");
+    ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1_002_021_001, "redirect_uri 不匹配");
+    ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1_002_021_002, "state 不匹配");
+    ErrorCode OAUTH2_GRANT_CODE_NOT_EXISTS = new ErrorCode(1_002_021_003, "code 不存在");
+
+    // ========== OAuth2 授权 1-002-022-000 =========
+    ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1_002_022_000, "code 不存在");
+    ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1_002_022_001, "code 已过期");
+
+    // ========== 邮箱账号 1-002-023-000 ==========
+    ErrorCode MAIL_ACCOUNT_NOT_EXISTS = new ErrorCode(1_002_023_000, "邮箱账号不存在");
+    ErrorCode MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS = new ErrorCode(1_002_023_001, "无法删除,该邮箱账号还有邮件模板");
+
+    // ========== 邮件模版 1-002-024-000 ==========
+    ErrorCode MAIL_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_024_000, "邮件模版不存在");
+    ErrorCode MAIL_TEMPLATE_CODE_EXISTS = new ErrorCode(1_002_024_001, "邮件模版 code({}) 已存在");
+
+    // ========== 邮件发送 1-002-025-000 ==========
+    ErrorCode MAIL_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_025_000, "模板参数({})缺失");
+    ErrorCode MAIL_SEND_MAIL_NOT_EXISTS = new ErrorCode(1_002_025_001, "邮箱不存在");
+
+    // ========== 站内信模版 1-002-026-000 ==========
+    ErrorCode NOTIFY_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_026_000, "站内信模版不存在");
+    ErrorCode NOTIFY_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_026_001, "已经存在编码为【{}】的站内信模板");
+
+    // ========== 站内信模版 1-002-027-000 ==========
+
+    // ========== 站内信发送 1-002-028-000 ==========
+    ErrorCode NOTIFY_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_028_000, "模板参数({})缺失");
 
 }

+ 0 - 11
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApiImpl.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.system.api.notify;
 
 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
-import cn.iocoder.yudao.module.system.api.notify.dto.NotifyTemplateReqDTO;
 import cn.iocoder.yudao.module.system.service.notify.NotifySendService;
 import org.springframework.stereotype.Service;
 
@@ -30,14 +29,4 @@ public class NotifyMessageSendApiImpl implements NotifyMessageSendApi {
                 reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
     }
 
-    @Override
-    public boolean validateNotifyTemplate(String orderDelivery) {
-        return false;
-    }
-
-    @Override
-    public void createNotifyTemplate(NotifyTemplateReqDTO templateReqDTO) {
-
-    }
-
 }

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

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.system.controller.admin.auth;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -153,4 +154,4 @@ public class AuthController {
         return success(authService.socialLogin(reqVO));
     }
 
-}
+}

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

@@ -41,7 +41,7 @@ public class AuthLoginReqVO {
 
     // ========== 绑定社交登录时,需要传递如下参数 ==========
 
-    @Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @InEnum(SocialTypeEnum.class)
     private Integer socialType;
 
@@ -66,4 +66,4 @@ public class AuthLoginReqVO {
         return socialType == null || StrUtil.isNotEmpty(socialState);
     }
 
-}
+}

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/sms/SmsChannelConvert.java

@@ -32,8 +32,8 @@ public interface SmsChannelConvert {
 
     PageResult<SmsChannelRespVO> convertPage(PageResult<SmsChannelDO> page);
 
-    List<SmsChannelProperties> convertList02(List<SmsChannelDO> list);
-
     List<SmsChannelSimpleRespVO> convertList03(List<SmsChannelDO> list);
 
+    SmsChannelProperties convert02(SmsChannelDO channel);
+
 }

+ 0 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dict/DictDataDO.java


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác