Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/master' into feature/bpm-back

cuicui 3 anni fa
parent
commit
4015724417
100 ha cambiato i file con 1177 aggiunte e 501 eliminazioni
  1. 2 2
      Jenkinsfile
  2. 6 4
      README.md
  3. 15 20
      bin/deploy.sh
  4. 1 1
      pom.xml
  5. 0 0
      sql/bpm-activiti.sql
  6. 18 395
      sql/ruoyi-vue-pro.sql
  7. 22 3
      yudao-dependencies/pom.xml
  8. 23 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java
  9. 2 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  10. 0 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
  11. 0 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/IoUtils.java
  12. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java
  13. 2 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
  14. 17 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
  15. 141 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java
  16. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java
  17. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
  18. 2 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java
  19. 5 1
      yudao-framework/yudao-spring-boot-starter-biz-sms/pom.xml
  20. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/SmsClient.java
  21. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/SmsClientFactory.java
  22. 13 3
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/AbstractSmsClient.java
  23. 3 1
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/SmsClientFactoryImpl.java
  24. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java
  25. 41 0
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsChannelProperties.java
  26. 302 0
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClient.java
  27. 50 0
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsCodeMapping.java
  28. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java
  29. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/enums/SmsChannelEnum.java
  30. 4 0
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/enums/SmsFrameworkErrorCodeConstants.java
  31. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/property/SmsChannelProperties.java
  32. 222 0
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClientTest.java
  33. 50 0
      yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsCodeMappingTest.java
  34. 6 0
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java
  35. 5 1
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java
  36. 1 1
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java
  37. 5 0
      yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
  38. 9 1
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  39. 58 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java
  40. 29 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java
  41. 6 0
      yudao-framework/yudao-spring-boot-starter-redis/pom.xml
  42. 48 0
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java
  43. 0 2
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoRedisAutoConfiguration.java
  44. 2 1
      yudao-framework/yudao-spring-boot-starter-redis/src/main/resources/META-INF/spring.factories
  45. 1 0
      yudao-framework/yudao-spring-boot-starter-redis/《芋道 Spring Boot Cache 入门》.md
  46. 3 3
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
  47. 0 5
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
  48. 11 0
      yudao-framework/yudao-spring-boot-starter-test/pom.xml
  49. 1 1
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbAndRedisUnitTest.java
  50. 1 1
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbUnitTest.java
  51. 1 1
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseRedisUnitTest.java
  52. 11 17
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/filter/ApiAccessLogFilter.java
  53. 5 5
      yudao-module-bpm/pom.xml
  54. 1 0
      yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionExtMapper.java
  55. 4 4
      yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceTest.java
  56. 7 7
      yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java
  57. 1 1
      yudao-module-bpm/yudao-module-bpm-biz-activiti/pom.xml
  58. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java
  59. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java
  60. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
  61. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.http
  62. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
  63. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.http
  64. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.java
  65. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java
  66. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http
  67. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
  68. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.http
  69. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
  70. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java
  71. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java
  72. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
  73. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
  74. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmTaskAssignRuleConvert.java
  75. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/package-info.java
  76. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java
  77. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
  78. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
  79. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
  80. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/config/BpmActivitiConfiguration.java
  81. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmActivityBehaviorFactory.java
  82. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmUserTaskActivityBehavior.java
  83. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/package-info.java
  84. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java
  85. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderAbstractScript.java
  86. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX1Script.java
  87. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2Script.java
  88. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignStartUserScript.java
  89. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/identity/EmptyUserGroupManager.java
  90. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmProcessInstanceEventListener.java
  91. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTackActivitiEventListener.java
  92. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTaskEventListener.java
  93. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/package-info.java
  94. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java
  95. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java
  96. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
  97. 3 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
  98. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
  99. 3 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
  100. 0 0
      yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleService.java

+ 2 - 2
Jenkinsfile

@@ -21,7 +21,7 @@ pipeline {
         // GitHub 账号名
         GITHUB_ACCOUNT = 'https://gitee.com/zhijiantianya/ruoyi-vue-pro'
         // 应用名称
-        APP_NAME = 'yudao-admin-server'
+        APP_NAME = 'yudao-server'
         // 应用部署路径
         APP_DEPLOY_BASE_DIR = '/media/pi/KINGTON/data/work/projects/'
     }
@@ -57,4 +57,4 @@ pipeline {
             }
         }
     }
-}
+}

+ 6 - 4
README.md

@@ -7,19 +7,21 @@
 
 ## 🐯 平台简介
 
-**芋道**,一套**全部开源**的**企业级**的快速开发平台,毫无保留给个人及企业免费使用。
+**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
 
 > 有任何问题,或者想要的功能,可以在 _Issues_ 中提给艿艿。
+>
+> 😜 给项目点点 Star 吧,这对我们真的很重要!
 
 * 前端采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) ,正在支持 Vue 3 + ElementUI Plus 最新方案。
 * 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson。
 * 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统。
 * 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能。
 * 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装。
-* 工作流使用 Activiti ,支持动态表单、在线设计流程、多种任务分配方式。
+* 工作流使用 Activiti + Flowable,支持动态表单、在线设计流程、多种任务分配方式。
 * 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。
 * 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款。
-* 集成阿里云、腾讯云、云片等短信渠道,集成阿里云、腾讯云、七牛云等云存储服务。
+* 集成阿里云、腾讯云、云片等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务。
 
 | 项目名                | 说明                     | 传说门                                                                                                                                 |
 |--------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
@@ -150,7 +152,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
 
 | 框架                                                                                          | 说明               | 版本       | 学习指南                                                           |
 |---------------------------------------------------------------------------------------------|------------------|----------|----------------------------------------------------------------|
-| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架           | 2.5.10   | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
+| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架           | 2.5.12   | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
 | [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器           | 5.7      |                                                                |
 | [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件    | 1.2.8    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包    | 3.5.1    | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |

+ 15 - 20
bin/deploy.sh

@@ -1,20 +1,15 @@
 #!/bin/bash
 set -e
 
-# 基础
-# export JAVA_HOME=/work/programs/jdk/jdk1.8.0_181
-# export PATH=PATH=$PATH:$JAVA_HOME/bin
-# export CLASSPATH=$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
-
 DATE=$(date +%Y%m%d%H%M)
 # 基础路径
-BASE_PATH=/media/pi/KINGTON/data/work/projects/yudao-admin-server
+BASE_PATH=/work/projects/yudao-server
 # 编译后 jar 的地址。部署时,Jenkins 会上传 jar 包到该目录下
 SOURCE_PATH=$BASE_PATH/build
 # 服务名称。同时约定部署服务的 jar 包名字也为它。
-SERVER_NAME=yudao-admin-server
+SERVER_NAME=yudao-server
 # 环境
-PROFILES_ACTIVE=dev
+PROFILES_ACTIVE=development
 # 健康检查 URL
 HEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/
 
@@ -62,7 +57,7 @@ function transfer() {
     echo "[transfer] 转移 $SERVER_NAME.jar 完成"
 }
 
-# 停止
+# 停止:优雅关闭之前已经启动的服务
 function stop() {
     echo "[stop] 开始停止 $BASE_PATH/$SERVER_NAME"
     PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
@@ -71,8 +66,8 @@ function stop() {
         # 正常关闭
         echo "[stop] $BASE_PATH/$SERVER_NAME 运行中,开始 kill [$PID]"
         kill -15 $PID
-        # 等待最大 60 秒,直到关闭完成。
-        for ((i = 0; i < 60; i++))
+        # 等待最大 120 秒,直到关闭完成。
+        for ((i = 0; i < 120; i++))
             do
                 sleep 1
                 PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
@@ -95,7 +90,7 @@ function stop() {
     fi
 }
 
-# 启动
+# 启动:启动后端项目
 function start() {
     # 开启启动前,打印启动参数
     echo "[start] 开始启动 $BASE_PATH/$SERVER_NAME"
@@ -108,13 +103,13 @@ function start() {
     echo "[start] 启动 $BASE_PATH/$SERVER_NAME 完成"
 }
 
-# 健康检查
+# 健康检查:自动判断后端项目是否正常启动
 function healthCheck() {
     # 如果配置健康检查,则进行健康检查
     if [ -n "$HEALTH_CHECK_URL" ]; then
-        # 健康检查最大 60 秒,直到健康检查通过
+        # 健康检查最大 120 秒,直到健康检查通过
         echo "[healthCheck] 开始通过 $HEALTH_CHECK_URL 地址,进行健康检查";
-        for ((i = 0; i < 60; i++))
+        for ((i = 0; i < 120; i++))
             do
                 # 请求健康检查地址,只获取状态码。
                 result=`curl -I -m 10 -o /dev/null -s -w %{http_code} $HEALTH_CHECK_URL || echo "000"`
@@ -138,11 +133,11 @@ function healthCheck() {
         else
             tail -n 10 nohup.out
         fi
-    # 如果未配置健康检查,则 slepp 60 秒,人工看日志是否部署成功。
+    # 如果未配置健康检查,则 sleep 120 秒,人工看日志是否部署成功。
     else
-        echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 60 秒";
-        sleep 60
-        echo "[healthCheck] sleep 60 秒完成,查看日志,自行判断是否启动成功";
+        echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 120 秒";
+        sleep 120
+        echo "[healthCheck] sleep 120 秒完成,查看日志,自行判断是否启动成功";
         tail -n 50 nohup.out
     fi
 }
@@ -159,7 +154,7 @@ function deploy() {
     # 启动 Java 服务
     start
     # 健康检查
-#    healthCheck
+    healthCheck
 }
 
 deploy

+ 1 - 1
pom.xml

@@ -25,7 +25,7 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.6.1-snapshot</revision>
+        <revision>1.6.2-snapshot</revision>
         <!-- Maven 相关 -->
         <java.version>1.8</java.version>
         <maven.compiler.source>${java.version}</maven.compiler.source>

File diff suppressed because it is too large
+ 0 - 0
sql/bpm-activiti.sql


File diff suppressed because it is too large
+ 18 - 395
sql/ruoyi-vue-pro.sql


+ 22 - 3
yudao-dependencies/pom.xml

@@ -14,9 +14,9 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.6.1-snapshot</revision>
+        <revision>1.6.2-snapshot</revision>
         <!-- 统一依赖管理 -->
-        <spring.boot.version>2.5.10</spring.boot.version>
+        <spring.boot.version>2.5.12</spring.boot.version>
         <!-- Web 相关 -->
         <knife4j.version>3.0.2</knife4j.version>
         <swagger-annotations.version>1.5.22</swagger-annotations.version>
@@ -25,8 +25,9 @@
         <mysql.version>5.1.46</mysql.version>
         <druid.version>1.2.8</druid.version>
         <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
+        <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
         <dynamic-datasource.version>3.5.0</dynamic-datasource.version>
-        <redisson.version>3.16.6</redisson.version>
+        <redisson.version>3.17.0</redisson.version>
         <!-- Config 配置中心相关 -->
         <apollo.version>1.9.2</apollo.version>
         <!-- Job 定时任务相关 -->
@@ -45,6 +46,7 @@
         <activiti.version>7.1.0.M6</activiti.version>
         <flowable.version>6.7.0</flowable.version>
         <!-- 工具类相关 -->
+        <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version>
         <lombok.version>1.18.20</lombok.version>
         <mapstruct.version>1.4.1.Final</mapstruct.version>
         <hutool.version>5.6.1</hutool.version>
@@ -60,6 +62,7 @@
         <minio.version>8.2.2</minio.version>
         <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version>
         <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
+        <tencentcloud-sdk-java.version>3.1.471</tencentcloud-sdk-java.version>
         <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
         <justauth.version>1.4.0</justauth.version>
     </properties>
@@ -191,6 +194,11 @@
                 <artifactId>mybatis-plus-boot-starter</artifactId>
                 <version>${mybatis-plus.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 -->
+                <version>${mybatis-plus-generator.version}</version>
+            </dependency>
             <dependency>
                 <groupId>com.baomidou</groupId>
                 <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
@@ -427,6 +435,12 @@
                 <version>${revision}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.github.ulisesbocchio</groupId>
+                <artifactId>jasypt-spring-boot-starter</artifactId> <!-- 加解密 -->
+                <version>${jasypt-spring-boot-starter.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>cn.iocoder.boot</groupId>
                 <artifactId>yudao-spring-boot-starter-excel</artifactId>
@@ -552,6 +566,11 @@
                 <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
                 <version>${aliyun-java-sdk-dysmsapi.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.tencentcloudapi</groupId>
+                <artifactId>tencentcloud-sdk-java</artifactId>
+                <version>${tencentcloud-sdk-java.version}</version>
+            </dependency>
             <!-- SMS SDK end -->
 
             <dependency>

+ 23 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java

@@ -1,8 +1,19 @@
 package cn.iocoder.yudao.framework.common.util.collection;
 
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.TypeUtil;
+import org.springframework.cglib.core.TypeUtils;
 
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
 /**
  * Array 工具类
@@ -30,4 +41,16 @@ public class ArrayUtils {
         return result;
     }
 
+    public static <T, V> V[] toArray(Collection<T> from, Function<T, V> mapper) {
+        return toArray(convertList(from, mapper));
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T[] toArray(Collection<T> from) {
+        if (CollectionUtil.isEmpty(from)) {
+            return (T[]) (new Object[0]);
+        }
+        return ArrayUtil.toArray(from, (Class<T>) CollectionUtil.getElementType(from.iterator()));
+    }
+
 }

+ 2 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -115,7 +115,7 @@ public class CollectionUtils {
             return new HashMap<>();
         }
         return from.stream()
-                   .collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
+                .collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
     }
 
     // 暂时没想好名字,先以 2 结尾噶
@@ -169,4 +169,5 @@ public class CollectionUtils {
     public static <T> Collection<T> singleton(T deptId) {
         return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
     }
+
 }

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

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.framework.common.util.http;
 
-import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.map.TableMap;
 import cn.hutool.core.net.url.UrlBuilder;
-import cn.hutool.core.util.ReferenceUtil;
 import cn.hutool.core.util.ReflectUtil;
 
 import java.nio.charset.Charset;

+ 0 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/IoUtils.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.common.util.io;
 
 import cn.hutool.core.io.IORuntimeException;
 import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.util.CharsetUtil;
 import cn.hutool.core.util.StrUtil;
 
 import java.io.InputStream;

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

@@ -35,7 +35,7 @@ import java.util.Set;
  * 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
  *
  * 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
- * 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yudao-admin-server 采用该方案】
+ * 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yudao-server 采用该方案】
  * 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
  *  1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
  *      最终过滤条件是 WHERE dept_id = ?

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

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
+import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXNativePayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
@@ -63,6 +64,7 @@ public class PayClientFactoryImpl implements PayClientFactory {
             case WX_PUB: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
             case WX_LITE: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
             case WX_APP: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
+            case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
             case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
             case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
             case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);

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

@@ -18,6 +18,7 @@ import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
 import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
@@ -54,7 +55,8 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
      */
     @Override
     public  PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
-        Map<String, String> params = data.getParams();
+        Map<String, String> params = strToMap(data.getBody());
+
         return  PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
                 .channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
                 .tradeStatus(params.get("trade_status"))
@@ -64,7 +66,7 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
 
     @Override
     public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
-        Map<String, String> params = notifyData.getParams();
+        Map<String, String> params = strToMap(notifyData.getBody());
         PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
                 .tradeNo(params.get("out_trade_no"))
                 .reqNo(params.get("out_biz_no"))
@@ -128,4 +130,17 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
         }
     }
 
+    public static Map<String, String> strToMap(String s) {
+        Map<String, String> stringStringMap = new HashMap<>();
+        //调整时间格式
+        String s3 = s.replaceAll("%3A", ":");
+        //获取map
+        String s4 = s3.replace("+", " ");
+        String[] split = s4.split("&");
+        for (String s1 : split) {
+            String[] split1 = s1.split("=");
+            stringStringMap.put(split1[0], split1[1]);
+        }
+        return stringStringMap;
+    }
 }

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

@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.io.FileUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
+import cn.iocoder.yudao.framework.pay.core.client.dto.*;
+import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
+import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
+
+
+@Slf4j
+public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
+    private WxPayService client;
+
+    public WXNativePayClient(Long channelId, WXPayClientConfig config) {
+        super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config, new WXCodeMapping());
+    }
+
+    @Override
+    protected void doInit() {
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtil.copyProperties(config, payConfig, "keyContent");
+        payConfig.setTradeType(WxPayConstants.TradeType.NATIVE); // 设置使用 native 支付方式
+//        if (StrUtil.isNotEmpty(config.getKeyContent())) {
+//            payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
+//        }
+        if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
+            // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
+            payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
+        }
+        if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
+            // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
+            payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
+        }
+        // 真实客户端
+        this.client = new WxPayServiceImpl();
+        client.setConfig(payConfig);
+    }
+
+    @Override
+    public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        // 这里原生的返回的是支付的 url 所以直接使用string接收
+        //"invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
+        String responseV3;
+        try {
+            switch (config.getApiVersion()) {
+                case WXPayClientConfig.API_VERSION_V2:
+                    responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
+                    break;
+                case WXPayClientConfig.API_VERSION_V3:
+                  responseV3 = this.unifiedOrderV3(reqDTO);
+                    break;
+                default:
+                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+            }
+        } catch (WxPayException e) {
+            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
+            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
+                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
+        }
+        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
+    }
+
+    private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
+        //前端
+        String trade_type = reqDTO.getChannelExtras().get("trade_type");
+        // 构建 WxPayUnifiedOrderRequest 对象
+        WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest
+                .newBuilder()
+                .outTradeNo(reqDTO.getMerchantOrderId())
+                .body(reqDTO.getBody())
+                .totalFee(reqDTO.getAmount().intValue()) // 单位分
+                .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+                .spbillCreateIp(reqDTO.getUserIp())
+                .notifyUrl(reqDTO.getNotifyUrl())
+                .productId(trade_type)
+                .build();
+        // 执行请求
+        return client.createOrder(request);
+    }
+
+    private String unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
+        // 构建 WxPayUnifiedOrderRequest 对象
+        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
+        request.setOutTradeNo(reqDTO.getMerchantOrderId());
+        request.setDescription(reqDTO.getBody());
+        request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
+        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
+        request.setNotifyUrl(reqDTO.getNotifyUrl());
+        // 执行请求
+//        log.info("支付字段request:{}",request.getTimeExpire());
+
+        return client.createOrderV3(TradeTypeEnum.NATIVE, request);
+    }
+
+
+    @Override
+    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
+        WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
+        Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
+        // 转换结果
+        return PayOrderNotifyRespDTO.builder().orderExtensionNo(notifyResult.getOutTradeNo())
+                .channelOrderNo(notifyResult.getTransactionId()).channelUserId(notifyResult.getOpenid())
+                .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+                .data(data.getBody()).build();
+    }
+
+    @Override
+    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
+        //TODO 需要实现
+        throw new UnsupportedOperationException("需要实现");
+    }
+
+
+    @Override
+    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
+        //TODO 需要实现
+        throw new UnsupportedOperationException();
+    }
+}

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

@@ -82,9 +82,9 @@ public class WXPayClientConfig implements PayClientConfig {
     @NotBlank(message = "apiclient_cert 不能为空", groups = V3.class)
     private String privateCertContent;
     /**
-     * apiV3 钥值
+     * apiV3 钥值
      */
-    @NotBlank(message = "apiV3 钥值 不能为空", groups = V3.class)
+    @NotBlank(message = "apiV3 钥值 不能为空", groups = V3.class)
     private String apiV3Key;
 
     /**

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

@@ -98,7 +98,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
                 // TODO 芋艿:貌似没 title?
                 .body(reqDTO.getBody())
                 .totalFee(reqDTO.getAmount().intValue()) // 单位分
-                .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"))
+                .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
                 .spbillCreateIp(reqDTO.getUserIp())
                 .openid(getOpenid(reqDTO))
                 .notifyUrl(reqDTO.getNotifyUrl())
@@ -114,7 +114,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
         // TODO 芋艿:貌似没 title?
         request.setDescription(reqDTO.getBody());
         request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
-        request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"));
+        request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
         request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
         request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
         request.setNotifyUrl(reqDTO.getNotifyUrl());

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

@@ -20,6 +20,8 @@ public enum PayChannelEnum {
     WX_PUB("wx_pub", "微信 JSAPI 支付", WXPayClientConfig.class), // 公众号网页
     WX_LITE("wx_lite", "微信小程序支付", WXPayClientConfig.class),
     WX_APP("wx_app", "微信 App 支付", WXPayClientConfig.class),
+    WX_NATIVE("wx_native", "微信 native 支付", WXPayClientConfig.class),
+
 
     ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
     ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),

+ 5 - 1
yudao-framework/yudao-spring-boot-starter-biz-sms/pom.xml

@@ -12,7 +12,7 @@
     <packaging>jar</packaging>
 
     <name>${project.artifactId}</name>
-    <description>短信拓展,支持阿里云、云片</description>
+    <description>短信拓展,支持阿里云、云片、腾讯云</description>
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <dependencies>
@@ -77,6 +77,10 @@
             <groupId>com.aliyun</groupId>
             <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.tencentcloudapi</groupId>
+            <artifactId>tencentcloud-sdk-java</artifactId>
+        </dependency>
         <!-- SMS SDK end -->
     </dependencies>
 

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

@@ -11,7 +11,7 @@ import java.util.List;
  * 短信客户端,用于对接各短信平台的 SDK,实现短信发送等功能
  *
  * @author zzf
- * @date 2021/1/25 14:14
+ * @since 2021/1/25 14:14
  */
 public interface SmsClient {
 

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

@@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
  * 短信客户端的工厂接口
  *
  * @author zzf
- * @date 2021/1/28 14:01
+ * @since 2021/1/28 14:01
  */
 public interface SmsClientFactory {
 

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

@@ -16,7 +16,7 @@ import java.util.List;
  * 短信客户端的抽象类,提供模板方法,减少子类的冗余代码
  *
  * @author zzf
- * @date 2021/2/1 9:28
+ * @since 2021/2/1 9:28
  */
 @Slf4j
 public abstract class AbstractSmsClient implements SmsClient {
@@ -31,7 +31,7 @@ public abstract class AbstractSmsClient implements SmsClient {
     protected final SmsCodeMapping codeMapping;
 
     public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) {
-        this.properties = properties;
+        this.properties = prepareProperties(properties);
         this.codeMapping = codeMapping;
     }
 
@@ -54,11 +54,21 @@ public abstract class AbstractSmsClient implements SmsClient {
             return;
         }
         log.info("[refresh][配置({})发生变化,重新初始化]", properties);
-        this.properties = properties;
+        this.properties = prepareProperties(properties);
         // 初始化
         this.init();
     }
 
+    /**
+     * 在赋值给{@link this#properties}前,子类可根据需要预处理短信渠道配置
+     *
+     * @param properties 数据库中存储的短信渠道配置
+     * @return 满足子类实现的短信渠道配置
+     */
+    protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
+        return properties;
+    }
+
     @Override
     public Long getId() {
         return properties.getId();

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

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
 import cn.iocoder.yudao.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
 import cn.iocoder.yudao.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
+import cn.iocoder.yudao.framework.sms.core.client.impl.tencent.TencentSmsClient;
 import cn.iocoder.yudao.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
 import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
@@ -44,7 +45,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
         Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {
             // 创建一个空的 SmsChannelProperties 对象
             SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode())
-                    .setApiKey("default").setApiSecret("default");
+                    .setApiKey("default default").setApiSecret("default");
             // 创建 Sms 客户端
             AbstractSmsClient smsClient = createSmsClient(properties);
             channelCodeClients.put(channel.getCode(), smsClient);
@@ -81,6 +82,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
             case ALIYUN: return new AliyunSmsClient(properties);
             case YUN_PIAN: return new YunpianSmsClient(properties);
             case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
+            case TENCENT: return new TencentSmsClient(properties);
         }
         // 创建失败,错误日志 + 抛出异常
         log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);

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

@@ -41,7 +41,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
  * 阿里短信客户端的实现类
  *
  * @author zzf
- * @date 2021/1/25 14:17
+ * @since 2021/1/25 14:17
  */
 @Slf4j
 public class AliyunSmsClient extends AbstractSmsClient {

+ 41 - 0
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsChannelProperties.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
+import lombok.Data;
+
+/**
+ * 腾讯云短信配置实现类
+ * 腾讯云发送短信时,需要额外的参数 sdkAppId,
+ *
+ * @author shiwp
+ */
+@Data
+public class TencentSmsChannelProperties extends SmsChannelProperties {
+
+    /**
+     * 应用 id
+     */
+    private String sdkAppId;
+
+    /**
+     * 考虑到不破坏原有的 apiKey + apiSecret 的结构,
+     * 所以腾讯云短信存储时,将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
+     * 因此在使用时,需要将 secretId 和 sdkAppId 解析出来,分别存储到对应字段中。
+     */
+    public static TencentSmsChannelProperties build(SmsChannelProperties properties) {
+        if (properties instanceof TencentSmsChannelProperties) {
+            return (TencentSmsChannelProperties) properties;
+        }
+        TencentSmsChannelProperties result = BeanUtil.toBean(properties, TencentSmsChannelProperties.class);
+        String combineKey = properties.getApiKey();
+        Assert.notEmpty(combineKey, "apiKey 不能为空");
+        String[] keys = combineKey.trim().split(" ");
+        Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");
+        Assert.notBlank(keys[0], "腾讯云短信 secretId 不能为空");
+        Assert.notBlank(keys[1], "腾讯云短信 sdkAppId 不能为空");
+        result.setSdkAppId(keys[1]).setApiKey(keys[0]);
+        return result;
+    }
+}

+ 302 - 0
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClient.java

@@ -0,0 +1,302 @@
+package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
+import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
+import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
+import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.annotations.VisibleForTesting;
+import com.tencentcloudapi.common.Credential;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.sms.v20210111.SmsClient;
+import com.tencentcloudapi.sms.v20210111.models.*;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
+
+/**
+ * 腾讯云短信功能实现
+ * <p>
+ * 参见 https://cloud.tencent.com/document/product/382/52077
+ *
+ * @author shiwp
+ */
+public class TencentSmsClient extends AbstractSmsClient {
+
+    /**
+     * 调用成功 code
+     */
+    public static final String API_SUCCESS_CODE = "Ok";
+
+    /**
+     * REGION,使用南京
+     */
+    private static final String ENDPOINT = "ap-nanjing";
+
+    /**
+     * 是否国际/港澳台短信:
+     * 0:表示国内短信。
+     * 1:表示国际/港澳台短信。
+     */
+    private static final long INTERNATIONAL = 0L;
+
+    private SmsClient client;
+
+    public TencentSmsClient(SmsChannelProperties properties) {
+        super(properties, new TencentSmsCodeMapping());
+        Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
+    }
+
+    @Override
+    protected void doInit() {
+        // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
+        Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret());
+        client = new SmsClient(credential, ENDPOINT);
+    }
+
+    @Override
+    protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId,
+                                                        String mobile,
+                                                        String apiTemplateId,
+                                                        List<KeyValue<String, Object>> templateParams) throws Throwable {
+        return invoke(() -> buildSendSmsRequest(sendLogId, mobile, apiTemplateId, templateParams),
+                this::doSendSms0,
+                response -> {
+                    SendStatus sendStatus = response.getSendStatusSet()[0];
+                    return SmsCommonResult.build(sendStatus.getCode(), sendStatus.getMessage(), response.getRequestId(),
+                            new SmsSendRespDTO().setSerialNo(sendStatus.getSerialNo()), codeMapping);
+                });
+    }
+
+
+    /**
+     * 腾讯云发放短信的时候,需要额外的参数 sdkAppId。
+     * 考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
+     * 因此,这边需要使用 TencentSmsChannelProperties 做拆分,重新封装到 properties 内。
+     *
+     * @param properties 数据库中存储的短信渠道配置
+     * @return TencentSmsChannelProperties
+     */
+    @Override
+    protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
+        return TencentSmsChannelProperties.build(properties);
+    }
+
+    /**
+     * 调用腾讯云 SDK 发送短信
+     *
+     * @param request 发送短信请求
+     * @return 发送短信响应
+     * @throws TencentCloudSDKException SDK 用来封装发送短信失败
+     */
+    private SendSmsResponse doSendSms0(SendSmsRequest request) throws TencentCloudSDKException {
+        return client.SendSms(request);
+    }
+
+    /**
+     * 封装腾讯云发送短信请求
+     *
+     * @param sendLogId      日志编号
+     * @param mobile         手机号
+     * @param apiTemplateId  短信 API 的模板编号
+     * @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
+     * @return 腾讯云发送短信请求
+     */
+    private SendSmsRequest buildSendSmsRequest(Long sendLogId,
+                                               String mobile,
+                                               String apiTemplateId,
+                                               List<KeyValue<String, Object>> templateParams) {
+        SendSmsRequest request = new SendSmsRequest();
+        request.setSmsSdkAppId(((TencentSmsChannelProperties) properties).getSdkAppId());
+        request.setPhoneNumberSet(new String[]{mobile});
+        request.setSignName(properties.getSignature());
+        request.setTemplateId(apiTemplateId);
+        request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
+        request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId)));
+        return request;
+    }
+
+    @Override
+    protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
+        List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);
+        return CollectionUtils.convertList(callback, status -> {
+            SmsReceiveRespDTO data = new SmsReceiveRespDTO();
+            data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription());
+            data.setReceiveTime(status.getReceiveTime()).setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()));
+            data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo());
+            SessionContext context;
+            Long logId;
+            Assert.notNull(context = status.getSessionContext(), "回执信息中未解析出 context,请联系腾讯云小助手");
+            Assert.notNull(logId = context.getLogId(), "回执信息中未解析出 logId,请联系腾讯云小助手");
+            data.setLogId(logId);
+            return data;
+        });
+    }
+
+    @Override
+    protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
+        return invoke(() -> this.buildSmsTemplateStatusRequest(apiTemplateId),
+                this::doGetSmsTemplate0,
+                response -> {
+                    SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]);
+                    return SmsCommonResult.build(API_SUCCESS_CODE, null, response.getRequestId(), data, codeMapping);
+                });
+    }
+
+    @VisibleForTesting
+    SmsTemplateRespDTO convertTemplateStatusDTO(DescribeTemplateListStatus templateStatus) {
+        if (templateStatus == null) {
+            return null;
+        }
+        SmsTemplateAuditStatusEnum auditStatus;
+        Assert.notNull(templateStatus.getStatusCode(),
+                StrUtil.format("短信模版审核状态为 null,模版 id{}", templateStatus.getTemplateId()));
+        switch (templateStatus.getStatusCode().intValue()) {
+            case -1:
+                auditStatus = SmsTemplateAuditStatusEnum.FAIL;
+                break;
+            case 0:
+                auditStatus = SmsTemplateAuditStatusEnum.SUCCESS;
+                break;
+            case 1:
+                auditStatus = SmsTemplateAuditStatusEnum.CHECKING;
+                break;
+            default:
+                throw new IllegalStateException(StrUtil.format("不能解析短信模版审核状态{},模版 id{}",
+                        templateStatus.getStatusCode(), templateStatus.getTemplateId()));
+        }
+        SmsTemplateRespDTO data = new SmsTemplateRespDTO();
+        data.setId(String.valueOf(templateStatus.getTemplateId())).setContent(templateStatus.getTemplateContent());
+        data.setAuditStatus(auditStatus.getStatus()).setAuditReason(templateStatus.getReviewReply());
+        return data;
+    }
+
+    /**
+     * 封装查询模版审核状态请求
+     * @param apiTemplateId api 的模版 id
+     * @return 查询模版审核状态请求
+     */
+    private DescribeSmsTemplateListRequest buildSmsTemplateStatusRequest(String apiTemplateId) {
+        DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
+        request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
+        // 地区 0:表示国内短信。1:表示国际/港澳台短信。
+        request.setInternational(INTERNATIONAL);
+        return request;
+    }
+
+    /**
+     * 调用腾讯云 SDK 查询短信模版状态
+     *
+     * @param request 查询短信模版状态请求
+     * @return 查询短信模版状态响应
+     * @throws TencentCloudSDKException SDK 用来封装查询短信模版状态失败
+     */
+    private DescribeSmsTemplateListResponse doGetSmsTemplate0(DescribeSmsTemplateListRequest request) throws TencentCloudSDKException {
+        return client.DescribeSmsTemplateList(request);
+    }
+
+    <Q, P, R> SmsCommonResult<R> invoke(Supplier<Q> requestSupplier,
+                                        SdkFunction<Q, P> responseSupplier,
+                                        Function<P, SmsCommonResult<R>> resultGen) {
+        // 构建请求body
+        Q request = requestSupplier.get();
+        P response;
+        // 调用腾讯云发送短信
+        try {
+            response = responseSupplier.apply(request);
+        } catch (TencentCloudSDKException e) {
+            // 调用异常,封装结果
+            return SmsCommonResult.build(e.getErrorCode(), e.getMessage(), e.getRequestId(), null, codeMapping);
+        }
+        return resultGen.apply(response);
+    }
+
+    @Data
+    private static class SmsReceiveStatus {
+
+        /**
+         * 短信接受成功 code
+         */
+        public static final String SUCCESS_CODE = "SUCCESS";
+
+        /**
+         * 用户实际接收到短信的时间
+         */
+        @JsonProperty("user_receive_time")
+        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+        private Date receiveTime;
+
+        /**
+         * 国家(或地区)码
+         */
+        @JsonProperty("nationcode")
+        private String nationCode;
+
+        /**
+         * 手机号码
+         */
+        private String mobile;
+
+        /**
+         * 实际是否收到短信接收状态,SUCCESS(成功)、FAIL(失败)
+         */
+        @JsonProperty("report_status")
+        private String status;
+
+        /**
+         * 用户接收短信状态码错误信息
+         */
+        @JsonProperty("errmsg")
+        private String errCode;
+
+        /**
+         * 用户接收短信状态描述
+         */
+        @JsonProperty("description")
+        private String description;
+
+        /**
+         * 本次发送标识 ID(与发送接口返回的SerialNo对应)
+         */
+        @JsonProperty("sid")
+        private String serialNo;
+
+        /**
+         * 用户的 session 内容(与发送接口的请求参数SessionContext一致)
+         */
+        @JsonProperty("ext")
+        private SessionContext sessionContext;
+
+    }
+
+    @VisibleForTesting
+    @Data
+    static class SessionContext {
+
+        /**
+         * 发送短信记录id
+         */
+        private Long logId;
+    }
+
+    private interface SdkFunction<T, R> {
+        R apply(T t) throws TencentCloudSDKException;
+    }
+
+}

+ 50 - 0
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsCodeMapping.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
+import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
+
+import static cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.*;
+
+/**
+ * 腾讯云的 SmsCodeMapping 实现类
+ *
+ * 参见 https://cloud.tencent.com/document/api/382/52075#.E5.85.AC.E5.85.B1.E9.94.99.E8.AF.AF.E7.A0.81
+ *
+ * @author : shiwp
+ */
+public class TencentSmsCodeMapping implements SmsCodeMapping {
+
+    @Override
+    public ErrorCode apply(String apiCode) {
+        switch (apiCode) {
+            case TencentSmsClient.API_SUCCESS_CODE: return GlobalErrorCodeConstants.SUCCESS;
+            case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID;
+            case "FailedOperation.JsonParseFail":
+            case "MissingParameter.EmptyPhoneNumberSet":
+            case "LimitExceeded.PhoneNumberCountLimit":
+            case "FailedOperation.FailResolvePacket": return GlobalErrorCodeConstants.BAD_REQUEST;
+            case "FailedOperation.InsufficientBalanceInSmsPackage": return SMS_ACCOUNT_MONEY_NOT_ENOUGH;
+            case "FailedOperation.MarketingSendTimeConstraint": return SMS_SEND_MARKET_LIMIT_CONTROL;
+            case "FailedOperation.PhoneNumberInBlacklist": return SMS_MOBILE_BLACK;
+            case "FailedOperation.SignatureIncorrectOrUnapproved": return SMS_SIGN_INVALID;
+            case "FailedOperation.MissingTemplateToModify":
+            case "FailedOperation.TemplateIncorrectOrUnapproved": return SMS_TEMPLATE_INVALID;
+            case "InvalidParameterValue.IncorrectPhoneNumber": return SMS_MOBILE_INVALID;
+            case "InvalidParameterValue.SdkAppIdNotExist": return SMS_APP_ID_INVALID;
+            case "InvalidParameterValue.TemplateParameterLengthLimit":
+            case "InvalidParameterValue.TemplateParameterFormatError": return SMS_TEMPLATE_PARAM_ERROR;
+            case "LimitExceeded.PhoneNumberDailyLimit": return SMS_SEND_DAY_LIMIT_CONTROL;
+            case "LimitExceeded.PhoneNumberThirtySecondLimit":
+            case "LimitExceeded.PhoneNumberOneHourLimit": return SMS_SEND_BUSINESS_LIMIT_CONTROL;
+            case "UnauthorizedOperation.RequestPermissionDeny":
+            case "FailedOperation.ForbidAddMarketingTemplates":
+            case "FailedOperation.NotEnterpriseCertification":
+            case "UnauthorizedOperation.IndividualUserMarketingSmsPermissionDeny": return SMS_PERMISSION_DENY;
+            case "UnauthorizedOperation.RequestIpNotInWhitelist": return SMS_IP_DENY;
+            case "AuthFailure.SecretIdNotFound": return SMS_ACCOUNT_INVALID;
+        }
+        return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
+    }
+}

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

@@ -35,7 +35,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
  * 云片短信客户端的实现类
  *
  * @author zzf
- * @date 9:48 2021/3/5
+ * @since 9:48 2021/3/5
  */
 @Slf4j
 public class YunpianSmsClient extends AbstractSmsClient {

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/enums/SmsChannelEnum.java

@@ -8,7 +8,7 @@ import lombok.Getter;
  * 短信渠道枚举
  *
  * @author zzf
- * @date 2021/1/25 10:56
+ * @since 2021/1/25 10:56
  */
 @Getter
 @AllArgsConstructor
@@ -17,7 +17,7 @@ public enum SmsChannelEnum {
     DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
     YUN_PIAN("YUN_PIAN", "云片"),
     ALIYUN("ALIYUN", "阿里云"),
-//    TENCENT("TENCENT", "腾讯云"),
+    TENCENT("TENCENT", "腾讯云"),
 //    HUA_WEI("HUA_WEI", "华为云"),
     ;
 

+ 4 - 0
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/enums/SmsFrameworkErrorCodeConstants.java

@@ -26,6 +26,9 @@ public interface SmsFrameworkErrorCodeConstants {
 
     ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2001000104, "短信内容有敏感词");
 
+    // 腾讯云:为避免骚扰用户,营销短信只允许在8点到22点发送。
+    ErrorCode SMS_SEND_MARKET_LIMIT_CONTROL = new ErrorCode(2001000105, "营销短信发送时间限制");
+
     // ========== 模板相关 2001000200 ==========
     ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2001000200, "短信模板不合法"); // 包括短信模板不存在
     ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2001000201, "模板参数不正确");
@@ -41,6 +44,7 @@ public interface SmsFrameworkErrorCodeConstants {
     ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2001000900, "请求参数缺失");
     ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2001000901, "手机格式不正确");
     ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2001000902, "手机号在黑名单中");
+    ErrorCode SMS_APP_ID_INVALID = new ErrorCode(2001000903, "SdkAppId不合法");
 
     ErrorCode EXCEPTION = new ErrorCode(2001000999, "调用异常");
 

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/property/SmsChannelProperties.java

@@ -11,7 +11,7 @@ import javax.validation.constraints.NotNull;
  * 短信渠道配置类
  *
  * @author zzf
- * @date 2021/1/25 17:01
+ * @since 2021/1/25 17:01
  */
 @Data
 @Validated
@@ -40,9 +40,9 @@ public class SmsChannelProperties {
     @NotEmpty(message = "短信 API 的账号不能为空")
     private String apiKey;
     /**
-     * 短信 API 的
+     * 短信 API 的
      */
-    @NotEmpty(message = "短信 API 的钥不能为空")
+    @NotEmpty(message = "短信 API 的钥不能为空")
     private String apiSecret;
     /**
      * 短信发送回调 URL

+ 222 - 0
yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClientTest.java

@@ -0,0 +1,222 @@
+package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
+import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
+import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import com.google.common.collect.Lists;
+import com.tencentcloudapi.sms.v20210111.SmsClient;
+import com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse;
+import com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus;
+import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
+import com.tencentcloudapi.sms.v20210111.models.SendStatus;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link TencentSmsClient} 的单元测试
+ *
+ * @author shiwp
+ */
+public class TencentSmsClientTest extends BaseMockitoUnitTest {
+
+    private final SmsChannelProperties properties = new SmsChannelProperties()
+            .setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
+            .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
+            .setSignature("芋道源码");
+
+    @InjectMocks
+    private TencentSmsClient smsClient = new TencentSmsClient(properties);
+
+    @Mock
+    private SmsClient client;
+
+    @Test
+    public void testDoInit() {
+        // 准备参数
+        // mock 方法
+
+        // 调用
+        smsClient.doInit();
+        // 断言
+        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
+    }
+
+    @Test
+    public void testRefresh() {
+        // 准备参数
+        SmsChannelProperties p = new SmsChannelProperties()
+                .setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
+                .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
+                .setSignature("芋道源码");
+        // 调用
+        smsClient.refresh(p);
+        // 断言
+        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
+    }
+
+    @Test
+    public void testDoSendSms() throws Throwable {
+        // 准备参数
+        Long sendLogId = randomLongId();
+        String mobile = randomString();
+        String apiTemplateId = randomString();
+        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
+                new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
+        String requestId = randomString();
+        String serialNo = randomString();
+        // mock 方法
+        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
+            o.setRequestId(requestId);
+            SendStatus[] sendStatuses = new SendStatus[1];
+            o.setSendStatusSet(sendStatuses);
+            SendStatus sendStatus = new SendStatus();
+            sendStatuses[0] = sendStatus;
+            sendStatus.setCode(TencentSmsClient.API_SUCCESS_CODE);
+            sendStatus.setMessage("send success");
+            sendStatus.setSerialNo(serialNo);
+        });
+        when(client.SendSms(argThat(request -> {
+            assertEquals(mobile, request.getPhoneNumberSet()[0]);
+            assertEquals(properties.getSignature(), request.getSignName());
+            assertEquals(apiTemplateId, request.getTemplateId());
+            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
+                    toJsonString(request.getTemplateParamSet()));
+            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
+            return true;
+        }))).thenReturn(response);
+
+        // 调用
+        SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile,
+                apiTemplateId, templateParams);
+        // 断言
+        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
+        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
+        assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+        assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+        assertEquals(response.getRequestId(), result.getApiRequestId());
+        // 断言结果
+        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getData().getSerialNo());
+    }
+
+    @Test
+    public void testDoTParseSmsReceiveStatus() throws Throwable {
+        // 准备参数
+        String text = "[\n" +
+                "    {\n" +
+                "        \"user_receive_time\": \"2015-10-17 08:03:04\",\n" +
+                "        \"nationcode\": \"86\",\n" +
+                "        \"mobile\": \"13900000001\",\n" +
+                "        \"report_status\": \"SUCCESS\",\n" +
+                "        \"errmsg\": \"DELIVRD\",\n" +
+                "        \"description\": \"用户短信送达成功\",\n" +
+                "        \"sid\": \"12345\",\n" +
+                "        \"ext\": {\"logId\":\"67890\"}\n" +
+                "    }\n" +
+                "]";
+        // mock 方法
+
+        // 调用
+        List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
+        // 断言
+        assertEquals(1, statuses.size());
+        assertTrue(statuses.get(0).getSuccess());
+        assertEquals("DELIVRD", statuses.get(0).getErrorCode());
+        assertEquals("用户短信送达成功", statuses.get(0).getErrorMsg());
+        assertEquals("13900000001", statuses.get(0).getMobile());
+        assertEquals(DateUtils.buildTime(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());
+        assertEquals("12345", statuses.get(0).getSerialNo());
+        assertEquals(67890L, statuses.get(0).getLogId());
+    }
+
+    @Test
+    public void testDoGetSmsTemplate() throws Throwable {
+        // 准备参数
+        Long apiTemplateId = randomLongId();
+        String requestId = randomString();
+
+        // mock 方法
+        DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
+            DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
+            DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
+            templateStatus.setTemplateId(apiTemplateId);
+            templateStatus.setStatusCode(0L);// 设置模板通过
+            describeTemplateListStatuses[0] = templateStatus;
+            o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
+            o.setRequestId(requestId);
+        });
+        when(client.DescribeSmsTemplateList(argThat(request -> {
+            assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
+            return true;
+        }))).thenReturn(response);
+
+        // 调用
+        SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString());
+        // 断言
+        assertEquals(TencentSmsClient.API_SUCCESS_CODE, result.getApiCode());
+        assertNull(result.getApiMsg());
+        assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+        assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+        assertEquals(response.getRequestId(), result.getApiRequestId());
+        // 断言结果
+        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getData().getId());
+        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getData().getContent());
+        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
+        assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason());
+    }
+
+    @Test
+    public void testConvertSuccessTemplateStatus() {
+        testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L);
+    }
+
+    @Test
+    public void testConvertCheckingTemplateStatus() {
+        testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L);
+    }
+
+    @Test
+    public void testConvertFailTemplateStatus() {
+        testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L);
+    }
+
+    @Test
+    public void testConvertUnknownTemplateStatus() {
+        DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
+        templateStatus.setStatusCode(3L);
+        Long templateId = randomLongId();
+        // 调用,并断言结果
+        assertThrows(IllegalStateException.class, () -> smsClient.convertTemplateStatusDTO(templateStatus),
+                StrUtil.format("不能解析短信模版审核状态[3],模版id[{}]", templateId));
+    }
+
+    private void testTemplateStatus(SmsTemplateAuditStatusEnum expected, Long value) {
+        DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
+        templateStatus.setStatusCode(value);
+        SmsTemplateRespDTO result = smsClient.convertTemplateStatusDTO(templateStatus);
+        assertEquals(expected.getStatus(), result.getAuditStatus());
+    }
+
+}

+ 50 - 0
yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsCodeMappingTest.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
+
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link TencentSmsCodeMapping} 的单元测试
+ *
+ * @author : shiwp
+ */
+public class TencentSmsCodeMappingTest extends BaseMockitoUnitTest {
+
+    @InjectMocks
+    private TencentSmsCodeMapping codeMapping;
+
+    @Test
+    public void testApply() {
+        assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply(TencentSmsClient.API_SUCCESS_CODE));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord"));
+        assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail"));
+        assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet"));
+        assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("LimitExceeded.PhoneNumberCountLimit"));
+        assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.FailResolvePacket"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("FailedOperation.InsufficientBalanceInSmsPackage"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_MARKET_LIMIT_CONTROL, codeMapping.apply("FailedOperation.MarketingSendTimeConstraint"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_BLACK, codeMapping.apply("FailedOperation.PhoneNumberInBlacklist"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("FailedOperation.SignatureIncorrectOrUnapproved"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("FailedOperation.MissingTemplateToModify"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("FailedOperation.TemplateIncorrectOrUnapproved"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID, codeMapping.apply("InvalidParameterValue.IncorrectPhoneNumber"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_APP_ID_INVALID, codeMapping.apply("InvalidParameterValue.SdkAppIdNotExist"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("InvalidParameterValue.TemplateParameterLengthLimit"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("InvalidParameterValue.TemplateParameterFormatError"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberDailyLimit"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberThirtySecondLimit"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberOneHourLimit"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("UnauthorizedOperation.RequestPermissionDeny"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("FailedOperation.ForbidAddMarketingTemplates"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("FailedOperation.NotEnterpriseCertification"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("UnauthorizedOperation.IndividualUserMarketingSmsPermissionDeny"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_IP_DENY, codeMapping.apply("UnauthorizedOperation.RequestIpNotInWhitelist"));
+        assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("AuthFailure.SecretIdNotFound"));
+    }
+
+}

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

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.framework.social.config;
 
+import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
+import com.xkcoding.http.HttpUtil;
+import com.xkcoding.http.support.hutool.HutoolImpl;
 import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
 import lombok.extern.slf4j.Slf4j;
 import me.zhyd.oauth.cache.AuthStateCache;
@@ -23,6 +26,9 @@ public class YudaoSocialAutoConfiguration {
     @Bean
     @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);
     }
 

+ 5 - 1
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java

@@ -75,7 +75,7 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
             }
         }
 
-        //检查是否是忽略的 URL, 如果是则允许访问
+        // 如果非允许忽略租户的 URL,则校验租户是否合法
         if (!isIgnoreUrl(request)) {
             // 2. 如果请求未带租户的编号,不允许访问。
             if (tenantId == null) {
@@ -92,6 +92,10 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
                 ServletUtils.writeJSON(response, result);
                 return;
             }
+        } else { // 如果是允许忽略租户的 URL,若未传递租户编号,则默认忽略租户编号,避免报错
+            if (tenantId == null) {
+                TenantContextHolder.setIgnore(true);
+            }
         }
 
         // 继续过滤

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

@@ -44,7 +44,7 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
         String dir = StrUtil.removeSuffix(filePath, fileName);
         boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
         if (!success) {
-            throw new FtpException(StrUtil.format("上文件到目标目录 ({}) 失败", filePath));
+            throw new FtpException(StrUtil.format("上文件到目标目录 ({}) 失败", filePath));
         }
         // 拼接返回路径
         return super.formatFileUrl(config.getDomain(), path);

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

@@ -38,6 +38,11 @@
             <artifactId>mysql-connector-java</artifactId>
             <version>${mysql.version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc8</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>druid-spring-boot-starter</artifactId>

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

@@ -75,12 +75,20 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
         return selectList(new LambdaQueryWrapper<T>().in(field, values));
     }
 
+    /**
+     * 逐条插入,适合少量数据插入,或者对性能要求不高的场景
+     *
+     * 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法
+     * 使用示例,可见 RoleMenuBatchInsertMapper、UserRoleBatchInsertMapper 类
+     *
+     * @param entities 实体们
+     */
     default void insertBatch(Collection<T> entities) {
-        // TODO 芋艿:修改成支持批量的
         entities.forEach(this::insert);
     }
 
     default void updateBatch(T update) {
         update(update, new QueryWrapper<>());
     }
+
 }

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

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.framework.mybatis.core.type;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * List<String> 的类型转换器实现类,对应数据库的 varchar 类型
+ *
+ * @author 永不言败
+ * @since 2022 3/23 12:50:15
+ */
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes(List.class)
+public class StringLiSTTypeHandler implements TypeHandler<List<String>> {
+
+    private static final String COMMA = ",";
+
+    @Override
+    public void setParameter(PreparedStatement ps, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
+        // 设置占位符
+        ps.setString(i, CollUtil.join(strings, COMMA));
+    }
+
+    @Override
+    public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return getResult(value);
+    }
+
+    @Override
+    public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    @Override
+    public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    private List<String> getResult(String value) {
+        if (value == null) {
+            return null;
+        }
+        return StrUtil.splitTrim(value, COMMA);
+    }
+}

+ 29 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+
+/**
+ * JDBC 工具类
+ *
+ * @author 芋道源码
+ */
+public class JdbcUtils {
+
+    /**
+     * 判断连接是否正确
+     *
+     * @param url      数据源连接
+     * @param username 账号
+     * @param password 密码
+     * @return 是否正确
+     */
+    public static boolean isConnectionOK(String url, String username, String password) {
+        try (Connection ignored = DriverManager.getConnection(url, username, password)) {
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+}

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

@@ -26,6 +26,12 @@
             <groupId>org.redisson</groupId>
             <artifactId>redisson-spring-boot-starter</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
+        </dependency>
+
     </dependencies>
 
 </project>

+ 48 - 0
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.framework.redis.config;
+
+import org.springframework.boot.autoconfigure.cache.CacheProperties;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+/**
+ * Cache 配置类,基于 Redis 实现
+ */
+@Configuration
+@EnableCaching
+public class YudaoCacheAutoConfiguration {
+
+    /**
+     * RedisCacheConfiguration Bean
+     *
+     * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
+     */
+    @Bean
+    @Primary
+    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
+        // 设置使用 JSON 序列化方式
+        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
+        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
+
+        // 设置 CacheProperties.Redis 的属性
+        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
+        if (redisProperties.getTimeToLive() != null) {
+            config = config.entryTtl(redisProperties.getTimeToLive());
+        }
+        if (redisProperties.getKeyPrefix() != null) {
+            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
+        }
+        if (!redisProperties.isCacheNullValues()) {
+            config = config.disableCachingNullValues();
+        }
+        if (!redisProperties.isUseKeyPrefix()) {
+            config = config.disableKeyPrefix();
+        }
+        return config;
+    }
+
+}

+ 0 - 2
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoRedisAutoConfiguration.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.framework.redis.config;
 
-import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -11,7 +10,6 @@ import org.springframework.data.redis.serializer.RedisSerializer;
  * Redis 配置类
  */
 @Configuration
-@Slf4j
 public class YudaoRedisAutoConfiguration {
 
     /**

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

@@ -1,2 +1,3 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration
+  cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration,\
+  cn.iocoder.yudao.framework.redis.config.YudaoCacheAutoConfiguration

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-redis/《芋道 Spring Boot Cache 入门》.md

@@ -0,0 +1 @@
+<http://www.iocoder.cn/Spring-Boot/Cache/?yudao>

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

@@ -37,10 +37,10 @@ public class SecurityProperties {
     @NotNull(message = "mock 模式的开关不能为空")
     private Boolean mockEnable;
     /**
-     * mock 模式的
-     * 一定要配置钥,保证安全性
+     * mock 模式的
+     * 一定要配置钥,保证安全性
      */
-    @NotEmpty(message = "mock 模式的钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
+    @NotEmpty(message = "mock 模式的钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
     private String mockSecret = "yudaoyuanma";
 
 }

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

@@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 
 import java.util.*;
@@ -61,10 +60,6 @@ public class LoginUser implements UserDetails {
      * 部门编号
      */
     private Long deptId;
-    /**
-     * 所属岗位
-     */
-    private Set<Long> postIds;
 
     // ========== 上下文 ==========
     /**

+ 11 - 0
yudao-framework/yudao-spring-boot-starter-test/pom.xml

@@ -21,6 +21,17 @@
             <artifactId>yudao-common</artifactId>
         </dependency>
 
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
+        </dependency>
+
         <!-- Test 测试相关 -->
         <dependency>
             <groupId>org.mockito</groupId>

+ 1 - 1
yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/BaseDbAndRedisUnitTest.java → yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbAndRedisUnitTest.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.pay.test;
+package cn.iocoder.yudao.framework.test.core.ut;
 
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;

+ 1 - 1
yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/test/BaseDbUnitTest.java → yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbUnitTest.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.bpm.test;
+package cn.iocoder.yudao.framework.test.core.ut;
 
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/test/BaseRedisUnitTest.java → yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseRedisUnitTest.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.test;
+package cn.iocoder.yudao.framework.test.core.ut;
 
 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
 import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;

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

@@ -2,21 +2,18 @@ package cn.iocoder.yudao.framework.apilog.core.filter;
 
 import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.servlet.ServletUtil;
-import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
 import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateReqDTO;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
+import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
+import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
-import cn.iocoder.yudao.framework.common.util.date.DateUtils;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.filter.OncePerRequestFilter;
 
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
@@ -26,27 +23,24 @@ import java.io.IOException;
 import java.util.Date;
 import java.util.Map;
 
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.*;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
  * API 访问日志 Filter
  *
  * @author 芋道源码
  */
-@RequiredArgsConstructor
 @Slf4j
-public class ApiAccessLogFilter extends OncePerRequestFilter {
+public class ApiAccessLogFilter extends ApiRequestFilter {
 
-    private final WebProperties webProperties;
     private final String applicationName;
 
     private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
 
-    @Override
-    protected boolean shouldNotFilter(HttpServletRequest request) {
-        // 只过滤 API 请求的地址
-        return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAppApi().getPrefix(),
-                webProperties.getAppApi().getPrefix());
+    public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
+        super(webProperties);
+        this.applicationName = applicationName;
+        this.apiAccessLogFrameworkService = apiAccessLogFrameworkService;
     }
 
     @Override

+ 5 - 5
yudao-module-bpm/pom.xml

@@ -11,8 +11,8 @@
     <modules>
         <module>yudao-module-bpm-api</module>
         <module>yudao-module-bpm-base</module>
-        <module>yudao-module-bpm-impl-flowable</module>
-        <module>yudao-module-bpm-impl-activiti</module>
+        <module>yudao-module-bpm-biz-flowable</module>
+        <module>yudao-module-bpm-biz-activiti</module>
     </modules>
     <artifactId>yudao-module-bpm</artifactId>
     <packaging>pom</packaging>
@@ -24,9 +24,9 @@
         bpm 解释:https://baike.baidu.com/item/BPM/1933
 
         目前提供两套实现方案:
-            1. 基于 Activiti 7 实现的 yudao-module-bpm-impl-activiti
-            2. 基于 Flowable 6 实现的 yudao-module-bpm-impl-flowable
-        两套实现会存在共享的逻辑,所以会继承 yudao-module-impl-base
+            1. 基于 Activiti 7 实现的 yudao-module-bpm-biz-activiti
+            2. 基于 Flowable 6 实现的 yudao-module-bpm-biz-flowable
+        两套实现会存在共享的逻辑,所以会继承 yudao-module-bpm-base
     </description>
 
 </project>

+ 1 - 0
yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionExtMapper.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
 
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import org.apache.ibatis.annotations.Mapper;

+ 4 - 4
yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceTest.java

@@ -1,15 +1,15 @@
 package cn.iocoder.yudao.module.bpm.service.definition;
 
 import cn.hutool.core.util.RandomUtil;
-import cn.iocoder.yudao.module.bpm.test.BaseDbUnitTest;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
 import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmFormMapper;
 import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmFormFieldRespDTO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
@@ -18,12 +18,12 @@ import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.FORM_NOT_EXISTS;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 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.randomLongId;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.FORM_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**

+ 7 - 7
yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java

@@ -1,24 +1,24 @@
 package cn.iocoder.yudao.module.bpm.service.definition;
 
-import cn.iocoder.yudao.module.bpm.test.BaseDbUnitTest;
-import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
-import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
-import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
-import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
-import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmUserGroupMapper;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
 import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmUserGroupMapper;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 
-import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
 
 /**
 * {@link BpmUserGroupServiceImpl} 的单元测试类

+ 1 - 1
yudao-module-bpm/yudao-module-bpm-impl-activiti/pom.xml → yudao-module-bpm/yudao-module-bpm-biz-activiti/pom.xml

@@ -8,7 +8,7 @@
         <version>${revision}</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>yudao-module-bpm-impl-activiti</artifactId>
+    <artifactId>yudao-module-bpm-biz-activiti</artifactId>
     <packaging>jar</packaging>
 
     <name>${project.artifactId}</name>

+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.http → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.http


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.http → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.http


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.http → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.http


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


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmTaskAssignRuleConvert.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmTaskAssignRuleConvert.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/package-info.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/config/BpmActivitiConfiguration.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/config/BpmActivitiConfiguration.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmActivityBehaviorFactory.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmActivityBehaviorFactory.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmUserTaskActivityBehavior.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmUserTaskActivityBehavior.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/package-info.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderAbstractScript.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderAbstractScript.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX1Script.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX1Script.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2Script.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2Script.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignStartUserScript.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignStartUserScript.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/identity/EmptyUserGroupManager.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/identity/EmptyUserGroupManager.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmProcessInstanceEventListener.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmProcessInstanceEventListener.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTackActivitiEventListener.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTackActivitiEventListener.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTaskEventListener.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTaskEventListener.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/package-info.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java


+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java


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


+ 3 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java

@@ -215,6 +215,9 @@ public class BpmModelServiceImpl  implements BpmModelService {
         if (oldDefinition == null) {
             return;
         }
+        if(oldDefinition.isSuspended()) {
+            return;
+        }
         processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
     }
 

+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java


+ 3 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java

@@ -103,6 +103,9 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
         }
         // 执行查询
         List<ProcessDefinition> processDefinitions = definitionQuery.list();
+        if (CollUtil.isEmpty(processDefinitions)) {
+            return Collections.emptyList();
+        }
 
         // 获得 BpmProcessDefinitionDO Map
         List<BpmProcessDefinitionExtDO> processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(

+ 0 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleService.java → yudao-module-bpm/yudao-module-bpm-biz-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleService.java


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