Преглед изворни кода

同步V2.3.0版本、同步提交项至2024/12/1 17:41
不同步项:
1、WebSecurityConfigurerAdapter的默认app目录接口默认需要权限(保留为默认不需要权限,加@PreAuthenticated标记为声明需要权限)

rayson пре 1 година
родитељ
комит
8d7a7fe93a
100 измењених фајлова са 1782 додато и 662 уклоњено
  1. 3 3
      README.md
  2. 51 36
      citu-dependencies/pom.xml
  3. 18 2
      citu-framework/citu-common/src/main/java/com/citu/framework/common/util/collection/CollectionUtils.java
  4. 26 9
      citu-framework/citu-common/src/main/java/com/citu/framework/common/util/http/HttpUtils.java
  5. 7 0
      citu-framework/citu-common/src/main/java/com/citu/framework/common/util/object/BeanUtils.java
  6. 3 2
      citu-framework/citu-spring-boot-starter-biz-data-permission/src/main/java/com/citu/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
  7. 2 1
      citu-framework/citu-spring-boot-starter-biz-data-permission/src/test/java/com/citu/framework/datapermission/core/db/DataPermissionRuleHandlerTest.java
  8. 2 1
      citu-framework/citu-spring-boot-starter-biz-tenant/src/main/java/com/citu/framework/tenant/core/db/TenantDatabaseInterceptor.java
  9. 13 3
      citu-framework/citu-spring-boot-starter-biz-tenant/src/main/java/com/citu/framework/tenant/core/job/TenantJobAspect.java
  10. 5 0
      citu-framework/citu-spring-boot-starter-excel/pom.xml
  11. 12 0
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/config/CituMybatisAutoConfiguration.java
  12. 5 5
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java
  13. 95 0
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/enums/DbTypeEnum.java
  14. 0 21
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/enums/SqlConstants.java
  15. 6 14
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/mapper/BaseMapperX.java
  16. 10 10
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/query/QueryWrapperX.java
  17. 49 3
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/util/JdbcUtils.java
  18. 21 3
      citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/util/MyBatisUtils.java
  19. 15 10
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/operatelog/core/service/LogRecordServiceImpl.java
  20. 2 1
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/config/AuthorizeRequestsCustomizer.java
  21. 0 9
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/config/CituSecurityAutoConfiguration.java
  22. 18 19
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/config/CituWebSecurityConfigurerAdapter.java
  23. 5 0
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/LoginUser.java
  24. 2 2
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/annotations/PreAuthenticated.java
  25. 2 0
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/aop/PreAuthenticatedAspect.java
  26. 2 1
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/filter/TokenAuthenticationFilter.java
  27. 10 2
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/service/SecurityFrameworkServiceImpl.java
  28. 4 0
      citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/ut/BaseDbAndRedisUnitTest.java
  29. 4 0
      citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/ut/BaseDbUnitTest.java
  30. 4 0
      citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/ut/BaseRedisUnitTest.java
  31. 9 0
      citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/util/RandomUtils.java
  32. 10 25
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/config/CituApiLogAutoConfiguration.java
  33. 1 2
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/config/CituApiLogRpcAutoConfiguration.java
  34. 99 98
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/filter/ApiAccessLogFilter.java
  35. 0 19
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiAccessLogFrameworkService.java
  36. 0 33
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java
  37. 0 19
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiErrorLogFrameworkService.java
  38. 0 26
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java
  39. 1 1
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java
  40. 3 3
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/web/config/CituWebAutoConfiguration.java
  41. 49 17
      citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/web/core/handler/GlobalExceptionHandler.java
  42. 11 5
      citu-framework/citu-spring-boot-starter-websocket/src/main/java/com/citu/framework/websocket/config/CituWebSocketAutoConfiguration.java
  43. 3 2
      citu-framework/citu-spring-boot-starter-websocket/src/main/java/com/citu/framework/websocket/core/security/WebSocketAuthorizeRequestsCustomizer.java
  44. 4 2
      citu-framework/citu-spring-boot-starter-websocket/src/main/java/com/citu/framework/websocket/core/sender/AbstractWebSocketMessageSender.java
  45. 14 0
      citu-gateway/src/main/resources/application-dev.yaml
  46. 1 1
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/ApiConstants.java
  47. 1 4
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/DictTypeConstants.java
  48. 5 3
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/ErrorCodeConstants.java
  49. 25 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmBoundaryEventType.java
  50. 33 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmFieldPermissionEnum.java
  51. 1 1
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmModelFormTypeEnum.java
  52. 31 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmModelTypeEnum.java
  53. 1 1
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmProcessListenerType.java
  54. 1 1
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmProcessListenerValueType.java
  55. 36 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmSimpleModeConditionType.java
  56. 62 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmSimpleModelNodeType.java
  57. 47 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java
  58. 31 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java
  59. 33 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java
  60. 31 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java
  61. 35 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java
  62. 32 0
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java
  63. 3 2
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/message/BpmMessageEnum.java
  64. 13 2
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
  65. 13 9
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/task/BpmReasonEnum.java
  66. 10 1
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/task/BpmTaskStatusEnum.java
  67. 1 1
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/event/BpmProcessInstanceStatusEvent.java
  68. 1 1
      citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/event/BpmProcessInstanceStatusEventListener.java
  69. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/BpmServerApplication.java
  70. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/api/task/BpmProcessInstanceApiImpl.java
  71. 4 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/base/package-info.java
  72. 22 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java
  73. 9 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/BpmCategoryController.java
  74. 71 39
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/BpmModelController.java
  75. 14 8
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
  76. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java
  77. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java
  78. 0 17
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java
  79. 20 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java
  80. 65 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
  81. 8 16
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
  82. 8 5
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
  83. 0 47
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java
  84. 212 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
  85. 24 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java
  86. 10 4
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
  87. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/BpmOALeaveController.java
  88. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java
  89. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java
  90. 1 1
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java
  91. 0 39
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmActivityController.java
  92. 16 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmProcessInstanceController.http
  93. 18 9
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmProcessInstanceController.java
  94. 9 11
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java
  95. 12 7
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmTaskController.java
  96. 17 14
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java
  97. 37 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
  98. 106 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
  99. 43 0
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java
  100. 2 2
      citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java

+ 3 - 3
README.md

@@ -1,8 +1,8 @@
 <p align="center">
- <img src="https://img.shields.io/badge/Spring%20Cloud-2021-blue.svg" alt="Coverage Status">
- <img src="https://img.shields.io/badge/Spring%20Boot-2.7.18-blue.svg" alt="Downloads">
+ <img src="https://img.shields.io/badge/Spring%20Cloud-2023-blue.svg" alt="Coverage Status">
+ <img src="https://img.shields.io/badge/Spring%20Boot-3.3.4-blue.svg" alt="Downloads">
  <img src="https://img.shields.io/badge/Vue-3.2-blue.svg" alt="Downloads">
- <img src="https://img.shields.io/github/license/YunaiV/citu-cloud" alt="Downloads" />
+ <img src="https://img.shields.io/github/license/YunaiV/yudao-cloud" alt="Downloads" />
 </p>
 
 **严肃声明:现在、未来都不会有商业版本,所有代码全部开源!**

+ 51 - 36
citu-dependencies/pom.xml

@@ -14,9 +14,11 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>2.2.0-snapshot</revision>
+        <revision>2.3.0-snapshot</revision>
         <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
         <!-- 统一依赖管理 -->
+        <spring.framework.version>5.3.39</spring.framework.version>
+        <spring.security.version>5.8.14</spring.security.version>
         <spring.boot.version>2.7.18</spring.boot.version>
         <spring.cloud.version>2021.0.9</spring.cloud.version>
         <spring.cloud.alibaba.version>2021.0.6.1</spring.cloud.alibaba.version>
@@ -28,14 +30,15 @@
         <druid.version>1.2.23</druid.version>
         <mybatis.version>3.5.16</mybatis.version>
         <mybatis-plus.version>3.5.7</mybatis-plus.version>
-        <mybatis-plus-generator.version>3.5.7</mybatis-plus-generator.version>
         <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
         <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
-        <easy-trans.version>3.0.5</easy-trans.version>
-        <redisson.version>3.32.0</redisson.version>
-        <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
+        <easy-trans.version>3.0.6</easy-trans.version>
+        <redisson.version>3.36.0</redisson.version>
+        <dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
+        <kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
+        <opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
         <!-- 消息队列 -->
-        <rocketmq-spring.version>2.3.0</rocketmq-spring.version>
+        <rocketmq-spring.version>2.3.1</rocketmq-spring.version>
         <!-- RPC 相关 -->
         <!-- Config 配置中心相关 -->
         <!-- Job 定时任务相关 -->
@@ -48,7 +51,7 @@
         <opentracing.version>0.33.0</opentracing.version>
         <!-- Test 测试相关 -->
         <podam.version>7.2.11.RELEASE</podam.version> <!-- Spring Boot 2.X 最多使用 7.2.11 版本 -->
-        <jedis-mock.version>1.1.2</jedis-mock.version>
+        <jedis-mock.version>1.1.4</jedis-mock.version>
         <mockito-inline.version>4.11.0</mockito-inline.version>
         <!-- Bpm 工作流相关 -->
         <flowable.version>6.8.0</flowable.version>
@@ -56,10 +59,10 @@
         <captcha-plus.version>1.0.8</captcha-plus.version>
         <jsoup.version>1.18.1</jsoup.version>
         <lombok.version>1.18.34</lombok.version>
-        <mapstruct.version>1.5.5.Final</mapstruct.version>
-        <hutool.version>5.8.29</hutool.version>
-        <easyexcel.verion>3.3.4</easyexcel.verion>
-        <velocity.version>2.3</velocity.version>
+        <mapstruct.version>1.6.2</mapstruct.version>
+        <hutool.version>5.8.32</hutool.version>
+        <easyexcel.verion>4.0.3</easyexcel.verion>
+        <velocity.version>2.4</velocity.version>
         <screw.version>1.0.5</screw.version>
         <fastjson.version>2.0.53</fastjson.version>
         <guava.version>33.2.1-jre</guava.version>
@@ -70,23 +73,45 @@
         <ip2region.version>2.7.0</ip2region.version>
         <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
         <reflections.version>0.10.2</reflections.version>
+        <netty.version>4.1.113.Final</netty.version>
         <!-- 三方云服务相关 -->
-        <okio.version>3.5.0</okio.version>
-        <okhttp3.version>4.11.0</okhttp3.version>
-        <commons-io.version>2.15.1</commons-io.version>
-        <minio.version>8.5.7</minio.version>
+        <commons-io.version>2.17.0</commons-io.version>
+        <commons-compress.version>1.27.1</commons-compress.version>
+        <aws-java-sdk-s3.version>1.12.777</aws-java-sdk-s3.version>
         <justauth.version>1.0.8</justauth.version>
         <jimureport.version>1.7.8</jimureport.version>
-        <xercesImpl.version>2.12.2</xercesImpl.version>
         <weixin-java.version>4.6.0</weixin-java.version>
         <baidu.aip.version>4.16.19</baidu.aip.version>
         <!-- 规则引擎 -->
         <easy.rules.version>4.1.0</easy.rules.version>
+        <!-- 专属于 JDK8 安全漏洞升级 -->
+        <logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
     </properties>
 
     <dependencyManagement>
         <dependencies>
             <!-- 统一依赖管理 -->
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-bom</artifactId>
+                <version>${netty.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-framework-bom</artifactId> <!-- JDK8 版本独有:保证 Spring Framework 尽量高 -->
+                <version>${spring.framework.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.security</groupId>
+                <artifactId>spring-security-bom</artifactId> <!-- JDK8 版本独有:保证 Spring Security 尽量高 -->
+                <version>${spring.security.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
             <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-dependencies</artifactId>
@@ -216,7 +241,7 @@
             <dependency>
                 <groupId>com.baomidou</groupId>
                 <artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 -->
-                <version>${mybatis-plus-generator.version}</version>
+                <version>${mybatis-plus.version}</version>
             </dependency>
             <dependency>
                 <groupId>com.baomidou</groupId>
@@ -506,6 +531,11 @@
                 <artifactId>commons-io</artifactId>
                 <version>${commons-io.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-compress</artifactId>
+                <version>${commons-compress.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.apache.tika</groupId>
@@ -596,19 +626,9 @@
 
             <!-- 三方云服务相关 -->
             <dependency>
-                <groupId>com.squareup.okio</groupId>
-                <artifactId>okio</artifactId>
-                <version>${okio.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.squareup.okhttp3</groupId>
-                <artifactId>okhttp</artifactId>
-                <version>${okhttp3.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.minio</groupId>
-                <artifactId>minio</artifactId>
-                <version>${minio.version}</version>
+                <groupId>com.amazonaws</groupId>
+                <artifactId>aws-java-sdk-s3</artifactId>
+                <version>${aws-java-sdk-s3.version}</version>
             </dependency>
 
             <dependency>
@@ -651,11 +671,6 @@
                     </exclusion>
                 </exclusions>
             </dependency>
-            <dependency>
-                <groupId>xerces</groupId>
-                <artifactId>xercesImpl</artifactId>
-                <version>${xercesImpl.version}</version>
-            </dependency>
 
             <dependency>
                 <groupId>org.jeasy</groupId>
@@ -685,7 +700,7 @@
                 <artifactId>flatten-maven-plugin</artifactId>
                 <version>${flatten-maven-plugin.version}</version>
                 <configuration>
-                    <flattenMode>resolveCiFriendliesOnly</flattenMode>
+                    <flattenMode>bom</flattenMode>
                     <updatePomFile>true</updatePomFile>
                 </configuration>
                 <executions>

+ 18 - 2
citu-framework/citu-common/src/main/java/com/citu/framework/common/util/collection/CollectionUtils.java

@@ -3,6 +3,7 @@ package com.citu.framework.common.util.collection;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ArrayUtil;
+import com.citu.framework.common.pojo.PageResult;
 import com.google.common.collect.ImmutableMap;
 
 import java.util.*;
@@ -73,6 +74,13 @@ public class CollectionUtils {
         return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
     }
 
+    public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) {
+        if (ArrayUtil.isEmpty(from)) {
+            return new PageResult<>(from.getTotal());
+        }
+        return new PageResult<>(convertList(from.getList(), func), from.getTotal());
+    }
+
     public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
                                                       Function<T, ? extends Stream<? extends U>> func) {
         if (CollUtil.isEmpty(from)) {
@@ -290,7 +298,15 @@ public class CollectionUtils {
         return valueFunc.apply(t);
     }
 
-    public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
+    public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) {
+        if (CollUtil.isEmpty(from)) {
+            return null;
+        }
+        assert from.size() > 0; // 断言,避免告警
+        return from.stream().min(Comparator.comparing(valueFunc)).get();
+    }
+
+    public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
                                                                      BinaryOperator<V> accumulator) {
         return getSumValue(from, valueFunc, accumulator, null);
     }
@@ -316,7 +332,7 @@ public class CollectionUtils {
     }
 
     public static <T> List<T> newArrayList(List<List<T>> list) {
-        return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
+        return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
     }
 
 }

+ 26 - 9
citu-framework/citu-common/src/main/java/com/citu/framework/common/util/http/HttpUtils.java

@@ -35,14 +35,18 @@ public class HttpUtils {
         return builder.build();
     }
 
+    private String append(String base, Map<String, ?> query, boolean fragment) {
+        return append(base, query, null, fragment);
+    }
+
     /**
      * 拼接 URL
-     * <p>
+     *
      * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法
      *
-     * @param base     基础 URL
-     * @param query    查询参数
-     * @param keys     query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射
+     * @param base 基础 URL
+     * @param query 查询参数
+     * @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射
      * @param fragment URL 的 fragment,即拼接到 # 中
      * @return 拼接后的 URL
      */
@@ -122,11 +126,11 @@ public class HttpUtils {
 
     /**
      * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
-     * <p>
+     *
      * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
      *
-     * @param url         URL
-     * @param headers     请求头
+     * @param url URL
+     * @param headers 请求头
      * @param requestBody 请求体
      * @return 请求结果
      */
@@ -139,8 +143,21 @@ public class HttpUtils {
         }
     }
 
-    private String append(String base, Map<String, ?> query, boolean fragment) {
-        return append(base, query, null, fragment);
+    /**
+     * HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
+     *
+     * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
+     *
+     * @param url URL
+     * @param headers 请求头
+     * @return 请求结果
+     */
+    public static String get(String url, Map<String, String> headers) {
+        try (HttpResponse response = HttpRequest.get(url)
+                .addHeaders(headers)
+                .execute()) {
+            return response.body();
+        }
     }
 
 }

+ 7 - 0
citu-framework/citu-common/src/main/java/com/citu/framework/common/util/object/BeanUtils.java

@@ -59,4 +59,11 @@ public class BeanUtils {
         return new PageResult<>(list, source.getTotal());
     }
 
+    public static void copyProperties(Object source, Object target) {
+        if (source == null || target == null) {
+            return;
+        }
+        BeanUtil.copyProperties(source, target, false);
+    }
+
 }

+ 3 - 2
citu-framework/citu-spring-boot-starter-biz-data-permission/src/main/java/com/citu/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java

@@ -21,6 +21,7 @@ import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
 import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
+import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -141,7 +142,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
             return deptExpression;
         }
         // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
-        return new Parenthesis(new OrExpression(deptExpression, userExpression));
+        return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression));
     }
 
     private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
@@ -156,7 +157,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
         }
         // 拼接条件
         return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
-                new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
+                new ParenthesedExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
     }
 
     private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {

+ 2 - 1
citu-framework/citu-spring-boot-starter-biz-data-permission/src/test/java/com/citu/framework/datapermission/core/db/DataPermissionRuleHandlerTest.java

@@ -12,6 +12,7 @@ import net.sf.jsqlparser.expression.Parenthesis;
 import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
+import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
 import net.sf.jsqlparser.schema.Column;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -81,7 +82,7 @@ public class DataPermissionRuleHandlerTest extends BaseMockitoUnitTest {
                 Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
                 ExpressionList<LongValue> values = new ExpressionList<>(new LongValue(10L),
                         new LongValue(20L));
-                return new InExpression(column, new Parenthesis((values)));
+                return new InExpression(column, new ParenthesedExpressionList((values)));
             }
 
         };

+ 2 - 1
citu-framework/citu-spring-boot-starter-biz-tenant/src/main/java/com/citu/framework/tenant/core/db/TenantDatabaseInterceptor.java

@@ -1,6 +1,7 @@
 package com.citu.framework.tenant.core.db;
 
 import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
 import com.citu.framework.tenant.config.TenantProperties;
 import com.citu.framework.tenant.core.context.TenantContextHolder;
 import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
@@ -37,7 +38,7 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
     @Override
     public boolean ignoreTable(String tableName) {
         return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户
-            || CollUtil.contains(ignoreTables, tableName); // 情况二,忽略多租户的表
+                || CollUtil.contains(ignoreTables, SqlParserUtils.removeWrapperSymbol(tableName)); // 情况二,忽略多租户的表
     }
 
 }

+ 13 - 3
citu-framework/citu-spring-boot-starter-biz-tenant/src/main/java/com/citu/framework/tenant/core/job/TenantJobAspect.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
 import com.citu.framework.common.util.json.JsonUtils;
 import com.citu.framework.tenant.core.service.TenantFrameworkService;
 import com.citu.framework.tenant.core.util.TenantUtils;
+import com.xxl.job.core.context.XxlJobContext;
 import com.xxl.job.core.context.XxlJobHelper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -17,6 +18,7 @@ import org.aspectj.lang.annotation.Aspect;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * 多租户 JobHandler AOP
@@ -43,21 +45,29 @@ public class TenantJobAspect {
 
         // 逐个租户,执行 Job
         Map<Long, String> results = new ConcurrentHashMap<>();
+        AtomicBoolean success = new AtomicBoolean(true); // 标记,是否存在失败的情况
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); // XXL-Job 上下文
         tenantIds.parallelStream().forEach(tenantId -> {
             // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
             TenantUtils.execute(tenantId, () -> {
                 try {
-                    joinPoint.proceed();
+                    XxlJobContext.setXxlJobContext(xxlJobContext);
+                    // 执行 Job
+                    Object result = joinPoint.proceed();
+                    results.put(tenantId, StrUtil.toStringOrEmpty(result));
                 } catch (Throwable e) {
                     results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));
+                    success.set(false);
                     // 打印异常
                     XxlJobHelper.log(StrUtil.format("[多租户({}) 执行任务({}),发生异常:{}]",
                             tenantId, joinPoint.getSignature(), ExceptionUtils.getStackTrace(e)));
                 }
             });
         });
-        // 如果 results 非空,说明发生了异常,标记 XXL-Job 执行失败
-        if (CollUtil.isNotEmpty(results)) {
+        // 记录执行结果
+        if (success.get()) {
+            XxlJobHelper.handleSuccess(JsonUtils.toJsonString(results));
+        } else {
             XxlJobHelper.handleFail(JsonUtils.toJsonString(results));
         }
     }

+ 5 - 0
citu-framework/citu-spring-boot-starter-excel/pom.xml

@@ -65,6 +65,11 @@
             <artifactId>guava</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId> <!-- 解决 https://github.com/alibaba/easyexcel/issues/3954 问题 -->
+        </dependency>
+
         <dependency>
             <groupId>com.citu</groupId>
             <artifactId>citu-spring-boot-starter-biz-ip</artifactId>

+ 12 - 0
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/config/CituMybatisAutoConfiguration.java

@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
 import com.baomidou.mybatisplus.extension.incrementer.*;
+import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
+import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 import com.citu.framework.mybatis.core.handler.DefaultDBFieldHandler;
@@ -16,6 +18,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.core.env.ConfigurableEnvironment;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * MyBaits 配置类
  *
@@ -27,6 +31,14 @@ import org.springframework.core.env.ConfigurableEnvironment;
         lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
 public class CituMybatisAutoConfiguration {
 
+    static {
+        // 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存
+        JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache(
+                (cache) -> cache.maximumSize(1024)
+                        .expireAfterWrite(5, TimeUnit.SECONDS))
+        );
+    }
+
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
         MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();

+ 5 - 5
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java

@@ -2,7 +2,6 @@ package com.citu.framework.mybatis.config;
 
 import cn.hutool.core.util.StrUtil;
 import com.citu.framework.common.util.collection.SetUtils;
-import com.citu.framework.mybatis.core.enums.SqlConstants;
 import com.citu.framework.mybatis.core.util.JdbcUtils;
 import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.annotation.IdType;
@@ -42,9 +41,6 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor
         // TODO 芋艿:暂时没有找到特别合适的地方,先放在这里
         setJobStoreDriverIfPresent(environment, dbType);
 
-        // 初始化 SQL 静态变量
-        SqlConstants.init(dbType);
-
         // 如果非 NONE,则不进行处理
         IdType idType = getIdType(environment);
         if (idType != IdType.NONE) {
@@ -55,7 +51,7 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor
             setIdType(environment, IdType.INPUT);
             return;
         }
-        // 情况二,自增 ID,适合 MySQL 等直接自增的数据库
+        // 情况二,自增 ID,适合 MySQL、DM 达梦等直接自增的数据库
         setIdType(environment, IdType.AUTO);
     }
 
@@ -86,6 +82,10 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor
             case SQL_SERVER2005:
                 driverClass = "org.quartz.impl.jdbcjobstore.MSSQLDelegate";
                 break;
+            case DM:
+            case KINGBASE_ES:
+                driverClass = "org.quartz.impl.jdbcjobstore.StdJDBCDelegate";
+                break;
         }
         // 设置 driverClass 变量
         if (StrUtil.isNotEmpty(driverClass)) {

+ 95 - 0
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/enums/DbTypeEnum.java

@@ -0,0 +1,95 @@
+package com.citu.framework.mybatis.core.enums;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.annotation.DbType;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 针对 MyBatis Plus 的 {@link DbType} 增强,补充更多信息
+ */
+@Getter
+@AllArgsConstructor
+public enum DbTypeEnum {
+
+    /**
+     * H2
+     *
+     * 注意:H2 不支持 find_in_set 函数
+     */
+    H2(DbType.H2, "H2", ""),
+
+    /**
+     * MySQL
+     */
+    MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET('#{value}', #{column}) <> 0"),
+
+    /**
+     * Oracle
+     */
+    ORACLE(DbType.ORACLE, "Oracle", "FIND_IN_SET('#{value}', #{column}) <> 0"),
+
+    /**
+     * PostgreSQL
+     *
+     * 华为 openGauss 使用 ProductName 与 PostgreSQL 相同
+     */
+    POSTGRE_SQL(DbType.POSTGRE_SQL,"PostgreSQL", "POSITION('#{value}' IN #{column}) <> 0"),
+
+    /**
+     * SQL Server
+     */
+    SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"),
+    /**
+     * SQL Server 2005
+     */
+    SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"),
+
+    /**
+     * 达梦
+     */
+    DM(DbType.DM, "DM DBMS", "FIND_IN_SET('#{value}', #{column}) <> 0"),
+
+    /**
+     * 人大金仓
+     */
+    KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"),
+    ;
+
+    public static final Map<String, DbTypeEnum> MAP_BY_NAME = Arrays.stream(values())
+            .collect(Collectors.toMap(DbTypeEnum::getProductName, Function.identity()));
+
+    public static final Map<DbType, DbTypeEnum> MAP_BY_MP = Arrays.stream(values())
+            .collect(Collectors.toMap(DbTypeEnum::getMpDbType, Function.identity()));
+
+    /**
+     * MyBatis Plus 类型
+     */
+    private final DbType mpDbType;
+    /**
+     * 数据库产品名
+     */
+    private final String productName;
+    /**
+     * SQL FIND_IN_SET 模板
+     */
+    private final String findInSetTemplate;
+
+    public static DbType find(String databaseProductName) {
+        if (StrUtil.isBlank(databaseProductName)) {
+            return null;
+        }
+        return MAP_BY_NAME.get(databaseProductName).getMpDbType();
+    }
+
+    public static String getFindInSetTemplate(DbType dbType) {
+        return Optional.of(MAP_BY_MP.get(dbType).getFindInSetTemplate())
+                .orElseThrow(() -> new IllegalArgumentException("FIND_IN_SET not supported"));
+    }
+}

+ 0 - 21
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/enums/SqlConstants.java

@@ -1,21 +0,0 @@
-package com.citu.framework.mybatis.core.enums;
-
-import com.baomidou.mybatisplus.annotation.DbType;
-
-/**
- * SQL相关常量类
- *
- * @author Rayson
- */
-public class SqlConstants {
-
-    /**
-     * 数据库的类型
-     */
-    public static DbType DB_TYPE;
-
-    public static void init(DbType dbType) {
-        DB_TYPE = dbType;
-    }
-
-}

+ 6 - 14
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/mapper/BaseMapperX.java

@@ -13,7 +13,7 @@ import com.citu.framework.common.pojo.PageParam;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.common.pojo.SortablePageParam;
 import com.citu.framework.common.pojo.SortingField;
-import com.citu.framework.mybatis.core.enums.SqlConstants;
+import com.citu.framework.mybatis.core.util.JdbcUtils;
 import com.citu.framework.mybatis.core.util.MyBatisUtils;
 import com.github.yulichang.base.MPJBaseMapper;
 import com.github.yulichang.interfaces.MPJBaseJoin;
@@ -56,7 +56,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
 
     default <D> PageResult<D> selectJoinPage(PageParam pageParam, Class<D> clazz, MPJLambdaWrapper<T> lambdaWrapper) {
         // 特殊:不分页,直接查询全部
-        if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageNo())) {
+        if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
             List<D> list = selectJoinList(clazz, lambdaWrapper);
             return new PageResult<>(list, (long) list.size());
         }
@@ -135,11 +135,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
         return selectList(new LambdaQueryWrapper<T>().in(field, values));
     }
 
-    @Deprecated
-    default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
-        return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
-    }
-
     default List<T> selectList(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
         return selectList(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
     }
@@ -151,7 +146,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
      */
     default Boolean insertBatch(Collection<T> entities) {
         // 特殊:SQL Server 批量插入后,获取 id 会报错,因此通过循环处理
-        if (Objects.equals(SqlConstants.DB_TYPE, DbType.SQL_SERVER)) {
+        DbType dbType = JdbcUtils.getDbType();
+        if (JdbcUtils.isSQLServer(dbType)) {
             entities.forEach(this::insert);
             return CollUtil.isNotEmpty(entities);
         }
@@ -166,7 +162,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
      */
     default Boolean insertBatch(Collection<T> entities, int size) {
         // 特殊:SQL Server 批量插入后,获取 id 会报错,因此通过循环处理
-        if (Objects.equals(SqlConstants.DB_TYPE, DbType.SQL_SERVER)) {
+        DbType dbType = JdbcUtils.getDbType();
+        if (JdbcUtils.isSQLServer(dbType)) {
             entities.forEach(this::insert);
             return CollUtil.isNotEmpty(entities);
         }
@@ -185,10 +182,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
         return Db.updateBatchById(entities, size);
     }
 
-    default Boolean insertOrUpdateBatch(Collection<T> collection) {
-        return Db.saveOrUpdateBatch(collection);
-    }
-
     default int delete(String field, String value) {
         return delete(new QueryWrapper<T>().eq(field, value));
     }
@@ -198,4 +191,3 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
     }
 
 }
-

+ 10 - 10
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/query/QueryWrapperX.java

@@ -1,17 +1,17 @@
 package com.citu.framework.mybatis.core.query;
 
-import cn.hutool.core.lang.Assert;
-import com.citu.framework.mybatis.core.enums.SqlConstants;
+import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.citu.framework.mybatis.core.util.JdbcUtils;
 import org.springframework.util.StringUtils;
 
 import java.util.Collection;
 
 /**
  * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
- *
+ * <p>
  * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
  *
  * @param <T> 数据类型
@@ -95,13 +95,13 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
     }
 
     public QueryWrapperX<T> betweenIfPresent(String column, Object[] values) {
-        if (values!= null && values.length != 0 && values[0] != null && values[1] != null) {
+        if (values != null && values.length != 0 && values[0] != null && values[1] != null) {
             return (QueryWrapperX<T>) super.between(column, values[0], values[1]);
         }
-        if (values!= null && values.length != 0 && values[0] != null) {
+        if (values != null && values.length != 0 && values[0] != null) {
             return (QueryWrapperX<T>) ge(column, values[0]);
         }
-        if (values!= null && values.length != 0 && values[1] != null) {
+        if (values != null && values.length != 0 && values[1] != null) {
             return (QueryWrapperX<T>) le(column, values[1]);
         }
         return this;
@@ -141,14 +141,14 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
 
     /**
      * 设置只返回最后一条
-     *
+     * <p>
      * TODO 芋艿:不是完美解,需要在思考下。如果使用多数据源,并且数据源是多种类型时,可能会存在问题:实现之返回一条的语法不同
      *
      * @return this
      */
     public QueryWrapperX<T> limitN(int n) {
-        Assert.notNull(SqlConstants.DB_TYPE, "获取不到数据库的类型");
-        switch (SqlConstants.DB_TYPE) {
+        DbType dbType = JdbcUtils.getDbType();
+        switch (dbType) {
             case ORACLE:
             case ORACLE_12C:
                 super.le("ROWNUM", n);
@@ -157,7 +157,7 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
             case SQL_SERVER2005:
                 super.select("TOP " + n + " *"); // 由于 SQL Server 是通过 SELECT TOP 1 实现限制一条,所以只好使用 * 查询剩余字段
                 break;
-            default:
+            default: // MySQL、PostgreSQL、DM 达梦、KingbaseES 大金都是采用 LIMIT 实现
                 super.last("LIMIT " + n);
         }
         return this;

+ 49 - 3
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/util/JdbcUtils.java

@@ -1,9 +1,15 @@
 package com.citu.framework.mybatis.core.util;
 
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
 import com.baomidou.mybatisplus.annotation.DbType;
-
+import com.citu.framework.common.util.spring.SpringUtils;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import com.citu.framework.common.util.object.ObjectUtils;
+import com.citu.framework.mybatis.core.enums.DbTypeEnum;
+import javax.sql.DataSource;
 import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.SQLException;
 
 /**
  * JDBC 工具类
@@ -35,8 +41,48 @@ public class JdbcUtils {
      * @return DB 类型
      */
     public static DbType getDbType(String url) {
-        String name = com.alibaba.druid.util.JdbcUtils.getDbType(url, null);
-        return DbType.getDbType(name);
+        return com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url);
+    }
+
+    /**
+     * 通过当前数据库连接获得对应的 DB 类型
+     *
+     * @return DB 类型
+     */
+    public static DbType getDbType() {
+        DataSource dataSource;
+        try {
+            DynamicRoutingDataSource dynamicRoutingDataSource = SpringUtils.getBean(DynamicRoutingDataSource.class);
+            dataSource = dynamicRoutingDataSource.determineDataSource();
+        } catch (NoSuchBeanDefinitionException e) {
+            dataSource = SpringUtils.getBean(DataSource.class);
+        }
+        try (Connection conn = dataSource.getConnection()) {
+            return DbTypeEnum.find(conn.getMetaData().getDatabaseProductName());
+        } catch (SQLException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 判断 JDBC 连接是否为 SQLServer 数据库
+     *
+     * @param url JDBC 连接
+     * @return 是否为 SQLServer 数据库
+     */
+    public static boolean isSQLServer(String url) {
+        DbType dbType = getDbType(url);
+        return isSQLServer(dbType);
+    }
+
+    /**
+     * 判断 JDBC 连接是否为 SQLServer 数据库
+     *
+     * @param dbType DB 类型
+     * @return 是否为 SQLServer 数据库
+     */
+    public static boolean isSQLServer(DbType dbType) {
+        return ObjectUtils.equalsAny(dbType, DbType.SQL_SERVER, DbType.SQL_SERVER2005);
     }
 
 }

+ 21 - 3
citu-framework/citu-spring-boot-starter-mybatis/src/main/java/com/citu/framework/mybatis/core/util/MyBatisUtils.java

@@ -1,6 +1,8 @@
 package com.citu.framework.mybatis.core.util;
 
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.annotation.DbType;
 import com.citu.framework.common.pojo.PageParam;
 import com.citu.framework.common.pojo.SortingField;
 import com.baomidou.mybatisplus.core.metadata.OrderItem;
@@ -8,6 +10,7 @@ import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.citu.framework.mybatis.core.enums.DbTypeEnum;
 import net.sf.jsqlparser.expression.Alias;
 import net.sf.jsqlparser.schema.Column;
 import net.sf.jsqlparser.schema.Table;
@@ -33,8 +36,9 @@ public class MyBatisUtils {
         Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
         // 排序字段
         if (!CollectionUtil.isEmpty(sortingFields)) {
-            page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
-                            OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
+            page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder())
+                            ? OrderItem.asc(StrUtil.toUnderlineCase(sortingField.getField()))
+                            : OrderItem.desc(StrUtil.toUnderlineCase(sortingField.getField())))
                     .collect(Collectors.toList()));
         }
         return page;
@@ -56,7 +60,7 @@ public class MyBatisUtils {
 
     /**
      * 获得 Table 对应的表名
-     *
+     * <p>
      * 兼容 MySQL 转义表名 `t_xxx`
      *
      * @param table 表
@@ -85,4 +89,18 @@ public class MyBatisUtils {
         return new Column(tableName + StringPool.DOT + column);
     }
 
+    /**
+     * 跨数据库的 find_in_set 实现
+     *
+     * @param column 字段名称
+     * @param value  查询值(不带单引号)
+     * @return sql
+     */
+    public static String findInSet(String column, Object value) {
+        DbType dbType = JdbcUtils.getDbType();
+        return DbTypeEnum.getFindInSetTemplate(dbType)
+                .replace("#{column}", column)
+                .replace("#{value}", StrUtil.toString(value));
+    }
+
 }

+ 15 - 10
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/operatelog/core/service/LogRecordServiceImpl.java

@@ -9,6 +9,7 @@ import com.citu.module.system.api.logger.dto.OperateLogCreateReqDTO;
 import com.mzt.logapi.beans.LogRecord;
 import com.mzt.logapi.service.ILogRecordService;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
@@ -29,18 +30,22 @@ public class LogRecordServiceImpl implements ILogRecordService {
 
     @Override
     public void record(LogRecord logRecord) {
-        // 1. 补全通用字段
         OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO();
-        reqDTO.setTraceId(TracerUtils.getTraceId());
-        // 补充用户信息
-        fillUserFields(reqDTO);
-        // 补全模块信息
-        fillModuleFields(reqDTO, logRecord);
-        // 补全请求信息
-        fillRequestFields(reqDTO);
+        try {
+            reqDTO.setTraceId(TracerUtils.getTraceId());
+            // 补充用户信息
+            fillUserFields(reqDTO);
+            // 补全模块信息
+            fillModuleFields(reqDTO, logRecord);
+            // 补全请求信息
+            fillRequestFields(reqDTO);
 
-        // 2. 异步记录日志
-        operateLogApi.createOperateLog(reqDTO);
+            // 2. 异步记录日志
+            operateLogApi.createOperateLogAsync(reqDTO);
+        } catch (Throwable ex) {
+            // 由于 @Async 异步调用,这里打印下日志,更容易跟进
+            log.error("[record][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
+        }
     }
 
     private static void fillUserFields(OperateLogCreateReqDTO reqDTO) {

+ 2 - 1
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/config/AuthorizeRequestsCustomizer.java

@@ -4,6 +4,7 @@ import com.citu.framework.web.config.WebProperties;
 import org.springframework.core.Ordered;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
 import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
 
 import javax.annotation.Resource;
@@ -15,7 +16,7 @@ import javax.annotation.Resource;
  * @author Rayson
  */
 public abstract class AuthorizeRequestsCustomizer
-        implements Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>, Ordered {
+        implements Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry>, Ordered {
 
     @Resource
     private WebProperties webProperties;

+ 0 - 9
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/config/CituSecurityAutoConfiguration.java

@@ -1,6 +1,5 @@
 package com.citu.framework.security.config;
 
-import com.citu.framework.security.core.aop.PreAuthenticatedAspect;
 import com.citu.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
 import com.citu.framework.security.core.filter.TokenAuthenticationFilter;
 import com.citu.framework.security.core.handler.AccessDeniedHandlerImpl;
@@ -39,14 +38,6 @@ public class CituSecurityAutoConfiguration {
     @Resource
     private SecurityProperties securityProperties;
 
-    /**
-     * 处理用户未登录拦截的切面的 Bean
-     */
-    @Bean
-    public PreAuthenticatedAspect preAuthenticatedAspect() {
-        return new PreAuthenticatedAspect();
-    }
-
     /**
      * 认证失败处理类 Bean
      */

+ 18 - 19
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/config/CituWebSecurityConfigurerAdapter.java

@@ -90,7 +90,7 @@ public class CituWebSecurityConfigurerAdapter {
 
     /**
      * 配置 URL 的安全配置
-     * <p>
+     *
      * anyRequest          |   匹配所有请求路径
      * access              |   SpringEl表达式结果为true时可以访问
      * anonymous           |   匿名可以访问
@@ -126,26 +126,25 @@ public class CituWebSecurityConfigurerAdapter {
         // 设置每个请求的权限
         httpSecurity
                 // ①:全局共享规则
-                .authorizeRequests()
-                // 1.1 静态资源,可匿名访问
-                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
-                // 1.2 设置 @PermitAll 无需认证
-                .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
-                .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
-                .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
-                .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
-                // 1.3 基于 citu.security.permit-all-urls 无需认证
-                .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
-                // 1.4 设置 App API 无需认证
-                .antMatchers(buildAppApi("/**")).permitAll()
-                // 1.5 验证码captcha 允许匿名访问
-                .antMatchers("/captcha/get", "/captcha/check").permitAll()
+                .authorizeHttpRequests(c -> c
+                        // 1.1 静态资源,可匿名访问
+                        .requestMatchers(HttpMethod.GET, "/*.html", "/*.html", "/*.css", "/*.js").permitAll()
+                        // 1.2 设置 @PermitAll 无需认证
+                        .requestMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
+                        .requestMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
+                        .requestMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
+                        .requestMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
+                        .requestMatchers(HttpMethod.HEAD, permitAllUrls.get(HttpMethod.HEAD).toArray(new String[0])).permitAll()
+                        .requestMatchers(HttpMethod.PATCH, permitAllUrls.get(HttpMethod.PATCH).toArray(new String[0])).permitAll()
+                        // 1.3 基于 citu.security.permit-all-urls 无需认证
+                        .requestMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
+                        // 1.4 设置 App API 默认无需认证
+                        .requestMatchers(buildAppApi("/**")).permitAll()
+                )
                 // ②:每个项目的自定义规则
-                .and().authorizeRequests(registry -> // 下面,循环设置自定义规则
-                        authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
+                .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
                 // ③:兜底规则,必须认证
-                .authorizeRequests()
-                .anyRequest().authenticated();
+                .authorizeHttpRequests(c -> c.anyRequest().authenticated());
 
         // 添加 Token Filter
         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

+ 5 - 0
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/LoginUser.java

@@ -5,6 +5,7 @@ import com.citu.framework.common.enums.UserTypeEnum;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +45,10 @@ public class LoginUser {
      * 授权范围
      */
     private List<String> scopes;
+    /**
+     * 过期时间
+     */
+    private LocalDateTime expiresTime;
 
     // ========== 上下文 ==========
     /**

+ 2 - 2
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/annotations/PreAuthenticated.java

@@ -4,10 +4,10 @@ import java.lang.annotation.*;
 
 /**
  * 声明用户需要登录
- *
+ * <p>
  * 为什么不使用 {@link org.springframework.security.access.prepost.PreAuthorize} 注解,原因是不通过时,抛出的是认证不通过,而不是未登录
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)

+ 2 - 0
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/aop/PreAuthenticatedAspect.java

@@ -1,5 +1,6 @@
 package com.citu.framework.security.core.aop;
 
+
 import com.citu.framework.security.core.annotations.PreAuthenticated;
 import com.citu.framework.security.core.util.SecurityFrameworkUtils;
 import lombok.extern.slf4j.Slf4j;
@@ -10,6 +11,7 @@ import org.aspectj.lang.annotation.Aspect;
 import static com.citu.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
 import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
 
+
 @Aspect
 @Slf4j
 public class PreAuthenticatedAspect {

+ 2 - 1
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/filter/TokenAuthenticationFilter.java

@@ -98,7 +98,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
             // 构建登录用户
             return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
                     .setInfo(accessToken.getUserInfo()) // 额外的用户信息
-                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
+                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
+                    .setExpiresTime(accessToken.getExpiresTime());
         } catch (ServiceException serviceException) {
             // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
             return null;

+ 10 - 2
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/service/SecurityFrameworkServiceImpl.java

@@ -70,7 +70,11 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
     @Override
     @SneakyThrows
     public boolean hasAnyPermissions(String... permissions) {
-        return hasAnyPermissionsCache.get(new KeyValue<>(getLoginUserId(), Arrays.asList(permissions)));
+        Long userId = getLoginUserId();
+        if (userId == null) {
+            return false;
+        }
+        return hasAnyPermissionsCache.get(new KeyValue<>(userId, Arrays.asList(permissions)));
     }
 
     @Override
@@ -81,7 +85,11 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
     @Override
     @SneakyThrows
     public boolean hasAnyRoles(String... roles) {
-        return hasAnyRolesCache.get(new KeyValue<>(getLoginUserId(), Arrays.asList(roles)));
+        Long userId = getLoginUserId();
+        if (userId == null) {
+            return false;
+        }
+        return hasAnyRolesCache.get(new KeyValue<>(userId, Arrays.asList(roles)));
     }
 
     @Override

+ 4 - 0
citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/ut/BaseDbAndRedisUnitTest.java

@@ -1,5 +1,6 @@
 package com.citu.framework.test.core.ut;
 
+import cn.hutool.extra.spring.SpringUtil;
 import com.citu.framework.datasource.config.CituDataSourceAutoConfiguration;
 import com.citu.framework.mybatis.config.CituMybatisAutoConfiguration;
 import com.citu.framework.redis.config.CituRedisAutoConfiguration;
@@ -45,6 +46,9 @@ public class BaseDbAndRedisUnitTest {
             RedisAutoConfiguration.class, // Spring Redis 自动配置类
             RedissonAutoConfiguration.class, // Redisson 自动高配置类
             RedissonAutoConfiguration.class, // Redisson 自动配置类
+
+            // 其它配置类
+            SpringUtil.class
     })
     public static class Application {
     }

+ 4 - 0
citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/ut/BaseDbUnitTest.java

@@ -1,5 +1,6 @@
 package com.citu.framework.test.core.ut;
 
+import cn.hutool.extra.spring.SpringUtil;
 import com.citu.framework.datasource.config.CituDataSourceAutoConfiguration;
 import com.citu.framework.mybatis.config.CituMybatisAutoConfiguration;
 import com.citu.framework.test.config.SqlInitializationTestConfiguration;
@@ -36,6 +37,9 @@ public class BaseDbUnitTest {
             CituMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
             MybatisPlusJoinAutoConfiguration.class, // MyBatis 的Join配置类
+
+            // 其它配置类
+            SpringUtil.class
     })
     public static class Application {
     }

+ 4 - 0
citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/ut/BaseRedisUnitTest.java

@@ -1,5 +1,6 @@
 package com.citu.framework.test.core.ut;
 
+import cn.hutool.extra.spring.SpringUtil;
 import com.citu.framework.redis.config.CituRedisAutoConfiguration;
 import com.citu.framework.test.config.RedisTestConfiguration;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
@@ -26,6 +27,9 @@ public class BaseRedisUnitTest {
             CituRedisAutoConfiguration.class, // 自己的 Redis 配置类
             RedissonAutoConfiguration.class, // Redisson 自动高配置类
             RedissonAutoConfiguration.class, // Redisson 自动配置类
+
+            // 其它配置类
+            SpringUtil.class
     })
     public static class Application {
     }

+ 9 - 0
citu-framework/citu-spring-boot-starter-test/src/main/java/com/citu/framework/test/core/util/RandomUtils.java

@@ -103,6 +103,10 @@ public class RandomUtils {
         return randomString() + "@qq.com";
     }
 
+    public static String randomMobile() {
+        return "13800138" + RandomUtil.randomNumbers(3);
+    }
+
     public static String randomURL() {
         return "https://www.iocoder.cn/" + randomString();
     }
@@ -130,6 +134,11 @@ public class RandomUtils {
     @SafeVarargs
     public static <T> List<T> randomPojoList(Class<T> clazz, Consumer<T>... consumers) {
         int size = RandomUtil.randomInt(1, RANDOM_COLLECTION_LENGTH);
+        return randomPojoList(clazz, size, consumers);
+    }
+
+    @SafeVarargs
+    public static <T> List<T> randomPojoList(Class<T> clazz, int size, Consumer<T>... consumers) {
         return Stream.iterate(0, i -> i).limit(size).map(o -> randomPojo(clazz, consumers))
                 .collect(Collectors.toList());
     }

+ 10 - 25
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/config/CituApiLogAutoConfiguration.java

@@ -2,15 +2,10 @@ package com.citu.framework.apilog.config;
 
 import com.citu.framework.apilog.core.filter.ApiAccessLogFilter;
 import com.citu.framework.apilog.core.interceptor.ApiAccessLogInterceptor;
-import com.citu.framework.apilog.core.service.ApiAccessLogFrameworkService;
-import com.citu.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl;
-import com.citu.framework.apilog.core.service.ApiErrorLogFrameworkService;
-import com.citu.framework.apilog.core.service.ApiErrorLogFrameworkServiceImpl;
 import com.citu.framework.common.enums.WebFilterOrderEnum;
-import com.citu.framework.web.config.WebProperties;
 import com.citu.framework.web.config.CituWebAutoConfiguration;
+import com.citu.framework.web.config.WebProperties;
 import com.citu.module.infra.api.logger.ApiAccessLogApi;
-import com.citu.module.infra.api.logger.ApiErrorLogApi;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -24,36 +19,26 @@ import javax.servlet.Filter;
 @AutoConfiguration(after = CituWebAutoConfiguration.class)
 public class CituApiLogAutoConfiguration implements WebMvcConfigurer {
 
-    @Bean
-    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
-    public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) {
-        return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi);
-    }
-
-    @Bean
-    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
-    public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) {
-        return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi);
+    private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
+        FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
+        bean.setOrder(order);
+        return bean;
     }
 
     /**
      * 创建 ApiAccessLogFilter Bean,记录 API 请求日志
      */
     @Bean
-    @ConditionalOnProperty(prefix = "citu.access-log", value = "enable", matchIfMissing = true) // 允许使用 citu.access-log.enable=false 禁用访问日志
+    @ConditionalOnProperty(prefix = "yudao.access-log", value = "enable", matchIfMissing = true)
+    // 允许使用 yudao.access-log.enable=false 禁用访问日志
+    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
     public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties,
                                                                          @Value("${spring.application.name}") String applicationName,
-                                                                         ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
-        ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogFrameworkService);
+                                                                         ApiAccessLogApi apiAccessLogApi) {
+        ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogApi);
         return createFilterBean(filter, WebFilterOrderEnum.API_ACCESS_LOG_FILTER);
     }
 
-    private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
-        FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
-        bean.setOrder(order);
-        return bean;
-    }
-
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(new ApiAccessLogInterceptor());

+ 1 - 2
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/config/CituApiLogRpcAutoConfiguration.java

@@ -12,7 +12,6 @@ import org.springframework.context.annotation.Configuration;
  * @author Rayson
  */
 @AutoConfiguration
-@EnableFeignClients(clients = {ApiAccessLogApi.class, // 主要是引入相关的 API 服务
-        ApiErrorLogApi.class})
+@EnableFeignClients(clients = {ApiAccessLogApi.class, ApiErrorLogApi.class}) // 主要是引入相关的 API 服务
 public class CituApiLogRpcAutoConfiguration {
 }

+ 99 - 98
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/filter/ApiAccessLogFilter.java

@@ -9,7 +9,6 @@ import cn.hutool.core.util.BooleanUtil;
 import cn.hutool.core.util.StrUtil;
 import com.citu.framework.apilog.core.annotation.ApiAccessLog;
 import com.citu.framework.apilog.core.enums.OperateTypeEnum;
-import com.citu.framework.apilog.core.service.ApiAccessLogFrameworkService;
 import com.citu.framework.common.exception.enums.GlobalErrorCodeConstants;
 import com.citu.framework.common.pojo.CommonResult;
 import com.citu.framework.common.util.json.JsonUtils;
@@ -18,6 +17,7 @@ import com.citu.framework.common.util.servlet.ServletUtils;
 import com.citu.framework.web.config.WebProperties;
 import com.citu.framework.web.core.filter.ApiRequestFilter;
 import com.citu.framework.web.core.util.WebFrameworkUtils;
+import com.citu.module.infra.api.logger.ApiAccessLogApi;
 import com.citu.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
 import com.fasterxml.jackson.databind.JsonNode;
 import io.swagger.v3.oas.annotations.Operation;
@@ -36,12 +36,12 @@ import java.time.temporal.ChronoUnit;
 import java.util.Iterator;
 import java.util.Map;
 
-import static com.citu.framework.apilog.core.interceptor.ApiAccessLogInterceptor.*;
+import static com.citu.framework.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD;
 import static com.citu.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
  * API 访问日志 Filter
- *
+ * <p>
  * 目的:记录 API 访问日志到数据库中
  *
  * @author Rayson
@@ -53,12 +53,103 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
 
     private final String applicationName;
 
-    private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
+    private final ApiAccessLogApi apiAccessLogApi;
 
-    public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
+    public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogApi apiAccessLogApi) {
         super(webProperties);
         this.applicationName = applicationName;
-        this.apiAccessLogFrameworkService = apiAccessLogFrameworkService;
+        this.apiAccessLogApi = apiAccessLogApi;
+    }
+
+    private static OperateTypeEnum parseOperateLogType(HttpServletRequest request) {
+        RequestMethod requestMethod = ArrayUtil.firstMatch(method ->
+                StrUtil.equalsAnyIgnoreCase(method.name(), request.getMethod()), RequestMethod.values());
+        if (requestMethod == null) {
+            return OperateTypeEnum.OTHER;
+        }
+        switch (requestMethod) {
+            case GET:
+                return OperateTypeEnum.GET;
+            case POST:
+                return OperateTypeEnum.CREATE;
+            case PUT:
+                return OperateTypeEnum.UPDATE;
+            case DELETE:
+                return OperateTypeEnum.DELETE;
+            default:
+                return OperateTypeEnum.OTHER;
+        }
+    }
+
+    private static String sanitizeMap(Map<String, ?> map, String[] sanitizeKeys) {
+        if (CollUtil.isEmpty(map)) {
+            return null;
+        }
+        if (sanitizeKeys != null) {
+            MapUtil.removeAny(map, sanitizeKeys);
+        }
+        MapUtil.removeAny(map, SANITIZE_KEYS);
+        return JsonUtils.toJsonString(map);
+    }
+
+    private static String sanitizeJson(String jsonString, String[] sanitizeKeys) {
+        if (StrUtil.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            JsonNode rootNode = JsonUtils.parseTree(jsonString);
+            sanitizeJson(rootNode, sanitizeKeys);
+            return JsonUtils.toJsonString(rootNode);
+        } catch (Exception e) {
+            // 脱敏失败的情况下,直接忽略异常,避免影响用户请求
+            log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
+            return jsonString;
+        }
+    }
+
+    // ========== 解析 @ApiAccessLog、@Swagger 注解  ==========
+
+    private static String sanitizeJson(CommonResult<?> commonResult, String[] sanitizeKeys) {
+        if (commonResult == null) {
+            return null;
+        }
+        String jsonString = toJsonString(commonResult);
+        try {
+            JsonNode rootNode = JsonUtils.parseTree(jsonString);
+            sanitizeJson(rootNode.get("data"), sanitizeKeys); // 只处理 data 字段,不处理 code、msg 字段,避免错误被脱敏掉
+            return JsonUtils.toJsonString(rootNode);
+        } catch (Exception e) {
+            // 脱敏失败的情况下,直接忽略异常,避免影响用户请求
+            log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
+            return jsonString;
+        }
+    }
+
+    // ========== 请求和响应的脱敏逻辑,移除类似 password、token 等敏感字段 ==========
+
+    private static void sanitizeJson(JsonNode node, String[] sanitizeKeys) {
+        // 情况一:数组,遍历处理
+        if (node.isArray()) {
+            for (JsonNode childNode : node) {
+                sanitizeJson(childNode, sanitizeKeys);
+            }
+            return;
+        }
+        // 情况二:非 Object,只是某个值,直接返回
+        if (!node.isObject()) {
+            return;
+        }
+        //  情况三:Object,遍历处理
+        Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
+        while (iterator.hasNext()) {
+            Map.Entry<String, JsonNode> entry = iterator.next();
+            if (ArrayUtil.contains(sanitizeKeys, entry.getKey())
+                    || ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) {
+                iterator.remove();
+                continue;
+            }
+            sanitizeJson(entry.getValue(), sanitizeKeys);
+        }
     }
 
     @Override
@@ -91,7 +182,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
             if (!enable) {
                 return;
             }
-            apiAccessLogFrameworkService.createApiAccessLog(accessLog);
+            apiAccessLogApi.createApiAccessLogAsync(accessLog);
         } catch (Throwable th) {
             log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th);
         }
@@ -157,95 +248,5 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
         return true;
     }
 
-    // ========== 解析 @ApiAccessLog、@Swagger 注解  ==========
-
-    private static OperateTypeEnum parseOperateLogType(HttpServletRequest request) {
-        RequestMethod requestMethod = ArrayUtil.firstMatch(method ->
-                StrUtil.equalsAnyIgnoreCase(method.name(), request.getMethod()), RequestMethod.values());
-        if (requestMethod == null) {
-            return OperateTypeEnum.OTHER;
-        }
-        switch (requestMethod) {
-            case GET:
-                return OperateTypeEnum.GET;
-            case POST:
-                return OperateTypeEnum.CREATE;
-            case PUT:
-                return OperateTypeEnum.UPDATE;
-            case DELETE:
-                return OperateTypeEnum.DELETE;
-            default:
-                return OperateTypeEnum.OTHER;
-        }
-    }
-
-    // ========== 请求和响应的脱敏逻辑,移除类似 password、token 等敏感字段 ==========
-
-    private static String sanitizeMap(Map<String, ?> map, String[] sanitizeKeys) {
-        if (CollUtil.isNotEmpty(map)) {
-            return null;
-        }
-        if (sanitizeKeys != null) {
-            MapUtil.removeAny(map, sanitizeKeys);
-        }
-        MapUtil.removeAny(map, SANITIZE_KEYS);
-        return JsonUtils.toJsonString(map);
-    }
-
-    private static String sanitizeJson(String jsonString, String[] sanitizeKeys) {
-        if (StrUtil.isEmpty(jsonString)) {
-            return null;
-        }
-        try {
-            JsonNode rootNode = JsonUtils.parseTree(jsonString);
-            sanitizeJson(rootNode, sanitizeKeys);
-            return JsonUtils.toJsonString(rootNode);
-        } catch (Exception e) {
-            // 脱敏失败的情况下,直接忽略异常,避免影响用户请求
-            log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
-            return jsonString;
-        }
-    }
-
-    private static String sanitizeJson(CommonResult<?> commonResult, String[] sanitizeKeys) {
-        if (commonResult == null) {
-            return null;
-        }
-        String jsonString = toJsonString(commonResult);
-        try {
-            JsonNode rootNode = JsonUtils.parseTree(jsonString);
-            sanitizeJson(rootNode.get("data"), sanitizeKeys); // 只处理 data 字段,不处理 code、msg 字段,避免错误被脱敏掉
-            return JsonUtils.toJsonString(rootNode);
-        } catch (Exception e) {
-            // 脱敏失败的情况下,直接忽略异常,避免影响用户请求
-            log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
-            return jsonString;
-        }
-    }
-
-    private static void sanitizeJson(JsonNode node, String[] sanitizeKeys) {
-        // 情况一:数组,遍历处理
-        if (node.isArray()) {
-            for (JsonNode childNode : node) {
-                sanitizeJson(childNode, sanitizeKeys);
-            }
-            return;
-        }
-        // 情况二:非 Object,只是某个值,直接返回
-        if (!node.isObject()) {
-            return;
-        }
-        //  情况三:Object,遍历处理
-        Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
-        while (iterator.hasNext()) {
-            Map.Entry<String, JsonNode> entry = iterator.next();
-            if (ArrayUtil.contains(sanitizeKeys, entry.getKey())
-                    || ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) {
-                iterator.remove();
-                continue;
-            }
-            sanitizeJson(entry.getValue(), sanitizeKeys);
-        }
-    }
-
 }
+

+ 0 - 19
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiAccessLogFrameworkService.java

@@ -1,19 +0,0 @@
-package com.citu.framework.apilog.core.service;
-
-import com.citu.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
-
-/**
- * API 访问日志 Framework Service 接口
- *
- * @author Rayson
- */
-public interface ApiAccessLogFrameworkService {
-
-    /**
-     * 创建 API 访问日志
-     *
-     * @param reqDTO API 访问日志
-     */
-    void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO);
-
-}

+ 0 - 33
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java

@@ -1,33 +0,0 @@
-package com.citu.framework.apilog.core.service;
-
-import com.citu.module.infra.api.logger.ApiAccessLogApi;
-import com.citu.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Async;
-
-/**
- * API 访问日志 Framework Service 实现类
- *
- * 基于 {@link ApiAccessLogApi} 服务,记录访问日志
- *
- * @author Rayson
- */
-@Slf4j
-@RequiredArgsConstructor
-public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkService {
-
-    private final ApiAccessLogApi apiAccessLogApi;
-
-    @Override
-    @Async
-    public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {
-        try {
-            apiAccessLogApi.createApiAccessLog(reqDTO);
-        } catch (Throwable ex) {
-            // 由于 @Async 异步调用,这里打印下日志,更容易跟进
-            log.error("[createApiAccessLog][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
-        }
-    }
-
-}

+ 0 - 19
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiErrorLogFrameworkService.java

@@ -1,19 +0,0 @@
-package com.citu.framework.apilog.core.service;
-
-import com.citu.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
-
-/**
- * API 错误日志 Framework Service 接口
- *
- * @author Rayson
- */
-public interface ApiErrorLogFrameworkService {
-
-    /**
-     * 创建 API 错误日志
-     *
-     * @param reqDTO API 错误日志
-     */
-    void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO);
-
-}

+ 0 - 26
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java

@@ -1,26 +0,0 @@
-package com.citu.framework.apilog.core.service;
-
-import com.citu.module.infra.api.logger.ApiErrorLogApi;
-import com.citu.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
-import lombok.RequiredArgsConstructor;
-import org.springframework.scheduling.annotation.Async;
-
-/**
- * API 错误日志 Framework Service 实现类
- *
- * 基于 {@link ApiErrorLogApi} 服务,记录错误日志
- *
- * @author Rayson
- */
-@RequiredArgsConstructor
-public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkService {
-
-    private final ApiErrorLogApi apiErrorLogApi;
-
-    @Override
-    @Async
-    public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {
-        apiErrorLogApi.createApiErrorLog(reqDTO);
-    }
-
-}

+ 1 - 1
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java

@@ -17,7 +17,7 @@ public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
     public String desensitize(String origin, T annotation) {
         // 1. 判断是否禁用脱敏
         Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation));
-        if (Boolean.FALSE.equals(disable)) {
+        if (Boolean.TRUE.equals(disable)) {
             return origin;
         }
 

+ 3 - 3
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/web/config/CituWebAutoConfiguration.java

@@ -1,6 +1,5 @@
 package com.citu.framework.web.config;
 
-import com.citu.framework.apilog.core.service.ApiErrorLogFrameworkService;
 import com.citu.framework.common.enums.WebFilterOrderEnum;
 import com.citu.framework.i18n.config.I18nProperties;
 import com.citu.framework.web.core.filter.CacheRequestBodyFilter;
@@ -8,6 +7,7 @@ import com.citu.framework.web.core.filter.DemoFilter;
 import com.citu.framework.web.core.handler.GlobalExceptionHandler;
 import com.citu.framework.web.core.handler.GlobalResponseBodyHandler;
 import com.citu.framework.web.core.util.WebFrameworkUtils;
+import com.citu.module.infra.api.logger.ApiErrorLogApi;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -73,8 +73,8 @@ public class CituWebAutoConfiguration implements WebMvcConfigurer {
     }
 
     @Bean
-    public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogFrameworkService ApiErrorLogFrameworkService) {
-        return new GlobalExceptionHandler(applicationName, ApiErrorLogFrameworkService, i18nProperties.isEnable(), messageSource);
+    public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogApi apiErrorLogApi) {
+        return new GlobalExceptionHandler(applicationName, apiErrorLogApi, i18nProperties.isEnable(), messageSource);
     }
 
     @Bean

+ 49 - 17
citu-framework/citu-spring-boot-starter-web/src/main/java/com/citu/framework/web/core/handler/GlobalExceptionHandler.java

@@ -4,7 +4,6 @@ import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
-import com.citu.framework.apilog.core.service.ApiErrorLogFrameworkService;
 import com.citu.framework.common.exception.ErrorCode;
 import com.citu.framework.common.exception.ServiceException;
 import com.citu.framework.common.pojo.CommonResult;
@@ -14,12 +13,15 @@ import com.citu.framework.common.util.monitor.TracerUtils;
 import com.citu.framework.common.util.servlet.ServletUtils;
 import com.citu.framework.common.util.string.StrUtils;
 import com.citu.framework.web.core.util.WebFrameworkUtils;
+import com.citu.module.infra.api.logger.ApiErrorLogApi;
 import com.citu.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.InvalidPropertyException;
 import org.springframework.context.MessageSource;
 import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.util.Assert;
 import org.springframework.validation.BindException;
@@ -61,7 +63,7 @@ public class GlobalExceptionHandler {
     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
     private final String applicationName;
 
-    private final ApiErrorLogFrameworkService apiErrorLogFrameworkService;
+    private final ApiErrorLogApi apiErrorLogApi;
 
     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
     private final boolean enableI18n;
@@ -202,6 +204,24 @@ public class GlobalExceptionHandler {
         return CommonResult.error(BAD_REQUEST.getCode(), String.format("%s", fieldError.getDefaultMessage()));
     }
 
+    /**
+     * 处理 SpringMVC 请求参数类型错误
+     * <p>
+     * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
+     */
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
+        log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
+        if (ex.getCause() instanceof InvalidFormatException) {
+            InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
+//            return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
+            return CommonResult.error(BAD_REQUEST.getCode(), String.format("%s", invalidFormatException.getValue()));
+        } else {
+            return defaultExceptionHandler(ServletUtils.getRequest(), ex);
+        }
+    }
+
+
     /**
      * 处理 Validator 校验不通过产生的异常
      */
@@ -318,7 +338,7 @@ public class GlobalExceptionHandler {
             // 初始化 errorLog
             buildExceptionLog(errorLog, req, e);
             // 执行插入 errorLog
-            apiErrorLogFrameworkService.createApiErrorLog(errorLog);
+            apiErrorLogApi.createApiErrorLogAsync(errorLog);
         } catch (Throwable th) {
             log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
         }
@@ -367,45 +387,57 @@ public class GlobalExceptionHandler {
         }
         // 1. 数据报表
         if (message.contains("report_")) {
-            log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
+            log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
             return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                    "[报表模块 yudao-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
+                    "[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
         }
         // 2. 工作流
         if (message.contains("bpm_")) {
-            log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
+            log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
             return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                    "[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
+                    "[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
         }
         // 3. 微信公众号
         if (message.contains("mp_")) {
-            log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
+            log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
             return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                    "[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
+                    "[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
         }
         // 4. 商城系统
         if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
-            log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
+            log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
             return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                    "[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
+                    "[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
         }
         // 5. ERP 系统
         if (message.contains("erp_")) {
-            log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
+            log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
             return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                    "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
+                    "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
         }
         // 6. CRM 系统
         if (message.contains("crm_")) {
-            log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
+            log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
             return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                    "[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
+                    "[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
         }
         // 7. 支付平台
         if (message.contains("pay_")) {
-            log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
+            log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
+            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+                    "[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
+        }
+        // 8. AI 大模型
+        if (message.contains("ai_")) {
+            log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
+            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+                    "[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
+        }
+        // 9. IOT 物联网
+        if (message.contains("iot_")) {
+            log.error("[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
             return CommonResult.error(NOT_IMPLEMENTED.getCode(),
-                    "[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
+                    "[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
         }
         return null;
     }

+ 11 - 5
citu-framework/citu-spring-boot-starter-websocket/src/main/java/com/citu/framework/websocket/config/CituWebSocketAutoConfiguration.java

@@ -5,6 +5,7 @@ import com.citu.framework.mq.redis.core.RedisMQTemplate;
 import com.citu.framework.websocket.core.handler.JsonWebSocketMessageHandler;
 import com.citu.framework.websocket.core.listener.WebSocketMessageListener;
 import com.citu.framework.websocket.core.security.LoginUserHandshakeInterceptor;
+import com.citu.framework.websocket.core.security.WebSocketAuthorizeRequestsCustomizer;
 import com.citu.framework.websocket.core.sender.kafka.KafkaWebSocketMessageConsumer;
 import com.citu.framework.websocket.core.sender.kafka.KafkaWebSocketMessageSender;
 import com.citu.framework.websocket.core.sender.local.LocalWebSocketMessageSender;
@@ -76,10 +77,15 @@ public class CituWebSocketAutoConfiguration {
         return new WebSocketSessionManagerImpl();
     }
 
+    @Bean
+    public WebSocketAuthorizeRequestsCustomizer webSocketAuthorizeRequestsCustomizer(WebSocketProperties webSocketProperties) {
+        return new WebSocketAuthorizeRequestsCustomizer(webSocketProperties);
+    }
+
     // ==================== Sender 相关 ====================
 
     @Configuration
-    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "local", matchIfMissing = true)
+    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "local")
     public class LocalWebSocketMessageSenderConfiguration {
 
         @Bean
@@ -90,7 +96,7 @@ public class CituWebSocketAutoConfiguration {
     }
 
     @Configuration
-    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "redis", matchIfMissing = true)
+    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "redis")
     public class RedisWebSocketMessageSenderConfiguration {
 
         @Bean
@@ -108,7 +114,7 @@ public class CituWebSocketAutoConfiguration {
     }
 
     @Configuration
-    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "rocketmq", matchIfMissing = true)
+    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "rocketmq")
     public class RocketMQWebSocketMessageSenderConfiguration {
 
         @Bean
@@ -127,7 +133,7 @@ public class CituWebSocketAutoConfiguration {
     }
 
     @Configuration
-    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "rabbitmq", matchIfMissing = true)
+    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "rabbitmq")
     public class RabbitMQWebSocketMessageSenderConfiguration {
 
         @Bean
@@ -156,7 +162,7 @@ public class CituWebSocketAutoConfiguration {
     }
 
     @Configuration
-    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "kafka", matchIfMissing = true)
+    @ConditionalOnProperty(prefix = "citu.websocket", name = "sender-type", havingValue = "kafka")
     public class KafkaWebSocketMessageSenderConfiguration {
 
         @Bean

+ 3 - 2
citu-framework/citu-spring-boot-starter-websocket/src/main/java/com/citu/framework/websocket/core/security/WebSocketAuthorizeRequestsCustomizer.java

@@ -4,6 +4,7 @@ import com.citu.framework.security.config.AuthorizeRequestsCustomizer;
 import com.citu.framework.websocket.config.WebSocketProperties;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
 import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
 
 /**
@@ -17,8 +18,8 @@ public class WebSocketAuthorizeRequestsCustomizer extends AuthorizeRequestsCusto
     private final WebSocketProperties webSocketProperties;
 
     @Override
-    public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
-        registry.antMatchers(webSocketProperties.getPath()).permitAll();
+    public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
+        registry.requestMatchers(webSocketProperties.getPath()).permitAll();
     }
 
 }

+ 4 - 2
citu-framework/citu-spring-boot-starter-websocket/src/main/java/com/citu/framework/websocket/core/sender/AbstractWebSocketMessageSender.java

@@ -64,8 +64,10 @@ public abstract class AbstractWebSocketMessageSender implements WebSocketMessage
             sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType);
         }
         if (CollUtil.isEmpty(sessions)) {
-            log.info("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",
-                    sessionId, userType, userId, messageType, messageContent);
+            if (log.isDebugEnabled()) {
+                log.debug("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",
+                        sessionId, userType, userId, messageType, messageContent);
+            }
         }
         // 2. 执行发送
         doSend(sessions, messageType, messageContent);

+ 14 - 0
citu-gateway/src/main/resources/application-dev.yaml

@@ -0,0 +1,14 @@
+--- #################### 注册中心 + 配置中心相关配置 ####################
+
+spring:
+  cloud:
+    nacos:
+      server-addr: 127.0.0.1:8848 # Nacos 服务器地址
+      username: # Nacos 账号
+      password: # Nacos 密码
+      discovery: # 【配置中心】配置项
+        namespace: dev # 命名空间。这里使用 dev 开发环境
+        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
+      config: # 【注册中心】配置项
+        namespace: dev # 命名空间。这里使用 dev 开发环境
+        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP

+ 1 - 1
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/ApiConstants.java

@@ -5,7 +5,7 @@ import com.citu.framework.common.enums.RpcConstants;
 /**
  * API 相关的枚举
  *
- * @author Rayson
+ * @author 芋道源码
  */
 public class ApiConstants {
 

+ 1 - 4
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/DictTypeConstants.java

@@ -3,11 +3,8 @@ package com.citu.module.bpm.enums;
 /**
  * BPM 字典类型的枚举类
  *
- * @author Rayson
+ * @author 芋道源码
  */
 public interface DictTypeConstants {
 
-    String TASK_ASSIGN_RULE_TYPE = "bpm_task_assign_rule_type"; // 任务分配规则类型
-    String TASK_ASSIGN_SCRIPT = "bpm_task_assign_script"; // 任务分配自定义脚本
-
 }

+ 5 - 3
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/ErrorCodeConstants.java

@@ -23,6 +23,7 @@ public interface ErrorCodeConstants {
             "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
     ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
     ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
+    ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
 
     // ========== 流程定义 1-009-003-000 ==========
     ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
@@ -34,15 +35,16 @@ public interface ErrorCodeConstants {
     ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
     ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
     ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
-    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置");
-    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在");
+    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置");
+    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
+    ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
 
     // ========== 流程任务 1-009-005-000 ==========
     ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
     ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
     ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
     ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在");
-    ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
+    ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
     ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
     ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
     ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");

+ 25 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmBoundaryEventType.java

@@ -0,0 +1,25 @@
+package com.citu.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BPM 边界事件 (boundary event) 自定义类型枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmBoundaryEventType {
+
+    USER_TASK_TIMEOUT(1,"用户任务超时");
+
+    private final Integer type;
+    private final String name;
+
+    public static BpmBoundaryEventType typeOf(Integer type) {
+        return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
+    }
+
+}

+ 33 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmFieldPermissionEnum.java

@@ -0,0 +1,33 @@
+package com.citu.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BPM 表单权限的枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmFieldPermissionEnum {
+
+    READ(1, "只读"),
+    WRITE(2, "可编辑"),
+    NONE(3, "隐藏");
+
+    /**
+     * 权限
+     */
+    private final Integer permission;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    public static BpmFieldPermissionEnum valueOf(Integer permission) {
+        return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values());
+    }
+
+}

+ 1 - 1
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmModelFormTypeEnum.java

@@ -9,7 +9,7 @@ import java.util.Arrays;
 /**
  * BPM 模型的表单类型的枚举
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @Getter
 @AllArgsConstructor

+ 31 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmModelTypeEnum.java

@@ -0,0 +1,31 @@
+package com.citu.module.bpm.enums.definition;
+
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * BPM 模型的类型的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmModelTypeEnum implements IntArrayValuable {
+
+    BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/
+    SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelTypeEnum::getType).toArray();
+
+    private final Integer type;
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 1 - 1
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmProcessListenerType.java

@@ -6,7 +6,7 @@ import lombok.Getter;
 /**
  * BPM 流程监听器的类型
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @Getter
 @AllArgsConstructor

+ 1 - 1
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmProcessListenerValueType.java

@@ -6,7 +6,7 @@ import lombok.Getter;
 /**
  * BPM 流程监听器的值类型
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @Getter
 @AllArgsConstructor

+ 36 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmSimpleModeConditionType.java

@@ -0,0 +1,36 @@
+package com.citu.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 仿钉钉的流程器设计器条件节点的条件类型
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmSimpleModeConditionType implements IntArrayValuable {
+
+    EXPRESSION(1, "条件表达式"),
+    RULE(2, "条件规则");
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModeConditionType::getType).toArray();
+
+    private final Integer type;
+
+    private final String name;
+
+    public static BpmSimpleModeConditionType valueOf(Integer type) {
+        return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
+    }
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+}

+ 62 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmSimpleModelNodeType.java

@@ -0,0 +1,62 @@
+package com.citu.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * 仿钉钉的流程器设计器的模型节点类型
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmSimpleModelNodeType implements IntArrayValuable {
+
+    // 0 ~ 1 开始和结束
+    START_NODE(0, "开始", "startEvent"),
+    END_NODE(1, "结束", "endEvent"),
+
+    // 10 ~ 49 各种节点
+    START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定
+    APPROVE_NODE(11, "审批人", "userTask"),
+    COPY_NODE(12, "抄送人", "serviceTask"),
+
+    // 50 ~ 条件分支
+    CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
+    CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
+    PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"),
+    INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
+
+    private final Integer type;
+    private final String name;
+    private final String bpmnType;
+
+    /**
+     * 判断是否为分支节点
+     *
+     * @param type 节点类型
+     */
+    public static boolean isBranchNode(Integer type) {
+        return Objects.equals(CONDITION_BRANCH_NODE.getType(), type)
+                || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type)
+                || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type);
+    }
+
+    public static BpmSimpleModelNodeType valueOf(Integer type) {
+        return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
+    }
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 47 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java

@@ -0,0 +1,47 @@
+package com.citu.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * BPM 多人审批方式的枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable {
+
+    RANDOM(1, "随机挑选一人审批", null),
+    RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例)
+    ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人)
+    SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批
+
+    /**
+     * 审批方式
+     */
+    private final Integer method;
+    /**
+     * 名字
+     */
+    private final String name;
+    /**
+     * 完成表达式
+     */
+    private final String completionCondition;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray();
+
+    public static BpmUserTaskApproveMethodEnum valueOf(Integer method) {
+        return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values());
+    }
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+}

+ 31 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java

@@ -0,0 +1,31 @@
+package com.citu.module.bpm.enums.definition;
+
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 用户任务的审批类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable {
+
+    USER(1), // 人工审批
+    AUTO_APPROVE(2), // 自动通过
+    AUTO_REJECT(3); // 自动拒绝
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray();
+
+    private final Integer type;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 33 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java

@@ -0,0 +1,33 @@
+package com.citu.module.bpm.enums.definition;
+
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * BPM 用户任务的审批人为空时,处理类型枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable {
+
+    APPROVE(1), // 自动通过
+    REJECT(2), // 自动拒绝
+    ASSIGN_USER(3), // 指定人员审批
+    ASSIGN_ADMIN(4), // 转交给流程管理员
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray();
+
+    private final Integer type;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 31 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java

@@ -0,0 +1,31 @@
+package com.citu.module.bpm.enums.definition;
+
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * BPM 用户任务的审批人与发起人相同时,处理类型枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable {
+
+    START_USER_AUDIT(1), // 由发起人对自己审批
+    SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过
+    TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray();
+
+    private final Integer type;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 35 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java

@@ -0,0 +1,35 @@
+package com.citu.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * BPM 用户任务拒绝处理类型枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmUserTaskRejectHandlerType implements IntArrayValuable {
+
+    FINISH_PROCESS_INSTANCE(1, "终止流程"),
+    RETURN_USER_TASK(2, "驳回到指定任务节点");
+
+    private final Integer type;
+    private final String name;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskRejectHandlerType::getType).toArray();
+
+    public static BpmUserTaskRejectHandlerType typeOf(Integer type) {
+        return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+    }
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+}

+ 32 - 0
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java

@@ -0,0 +1,32 @@
+package com.citu.module.bpm.enums.definition;
+
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 用户任务超时处理类型枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmUserTaskTimeoutHandlerTypeEnum implements IntArrayValuable {
+
+    REMINDER(1,"自动提醒"),
+    APPROVE(2, "自动同意"),
+    REJECT(3, "自动拒绝");
+
+    private final Integer type;
+    private final String name;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray();
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 3 - 2
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/message/BpmMessageEnum.java

@@ -6,7 +6,7 @@ import lombok.Getter;
 /**
  * Bpm 消息的枚举
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @AllArgsConstructor
 @Getter
@@ -14,7 +14,8 @@ public enum BpmMessageEnum {
 
     PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人
     PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人
-    TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人
+    TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人
+    TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人
 
     /**
      * 短信模板的标识

+ 13 - 2
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java

@@ -1,6 +1,7 @@
 package com.citu.module.bpm.enums.task;
 
 import com.citu.framework.common.core.IntArrayValuable;
+import com.citu.framework.common.util.object.ObjectUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -9,12 +10,13 @@ import java.util.Arrays;
 /**
  * 流程实例 ProcessInstance 的状态
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @Getter
 @AllArgsConstructor
 public enum BpmProcessInstanceStatusEnum implements IntArrayValuable {
 
+    NOT_START(-1, "未开始"),
     RUNNING(1, "审批中"),
     APPROVE(2, "审批通过"),
     REJECT(3, "审批不通过"),
@@ -33,7 +35,16 @@ public enum BpmProcessInstanceStatusEnum implements IntArrayValuable {
 
     @Override
     public int[] array() {
-        return new int[0];
+        return ARRAYS;
+    }
+
+    public static boolean isRejectStatus(Integer status) {
+        return REJECT.getStatus().equals(status);
+    }
+
+    public static boolean isProcessEndStatus(Integer status) {
+        return ObjectUtils.equalsAny(status,
+                APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus());
     }
 
 }

+ 13 - 9
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/task/BpmDeleteReasonEnum.java → citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/task/BpmReasonEnum.java

@@ -5,13 +5,13 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 /**
- * 流程实例/任务的删除原因枚举
+ * 流程实例/任务的的处理原因枚举
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @Getter
 @AllArgsConstructor
-public enum BpmDeleteReasonEnum {
+public enum BpmReasonEnum {
 
     // ========== 流程实例的独有原因 ==========
 
@@ -22,6 +22,16 @@ public enum BpmDeleteReasonEnum {
     // ========== 流程任务的独有原因 ==========
 
     CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等
+    TIMEOUT_APPROVE("审批超时,系统自动通过"),
+    TIMEOUT_REJECT("审批超时,系统自动不通过"),
+    ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
+    ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
+    ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
+    ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
+    ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
+    ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"),
+    APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
+    APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
     ;
 
     private final String reason;
@@ -36,10 +46,4 @@ public enum BpmDeleteReasonEnum {
         return StrUtil.format(reason, args);
     }
 
-    // ========== 逻辑 ==========
-
-    public static boolean isRejectReason(String reason) {
-        return StrUtil.startWith(reason, "审批不通过任务,原因:");
-    }
-
 }

+ 10 - 1
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/enums/task/BpmTaskStatusEnum.java

@@ -1,5 +1,6 @@
 package com.citu.module.bpm.enums.task;
 
+import cn.hutool.core.util.ObjUtil;
 import com.citu.framework.common.util.object.ObjectUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -13,13 +14,13 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum BpmTaskStatusEnum {
 
+    NOT_START(-1, "未开始"),
     RUNNING(1, "审批中"),
     APPROVE(2, "审批通过"),
     REJECT(3, "审批不通过"),
     CANCEL(4, "已取消"),
 
     RETURN(5, "已退回"),
-    DELEGATE(6, "委派中"),
 
     /**
      * 使用场景:
@@ -44,6 +45,10 @@ public enum BpmTaskStatusEnum {
      */
     private final String name;
 
+    public static boolean isRejectStatus(Integer status) {
+        return REJECT.getStatus().equals(status);
+    }
+
     /**
      * 判断该状态是否已经处于 End 最终状态
      * <p>
@@ -58,4 +63,8 @@ public enum BpmTaskStatusEnum {
                 RETURN.getStatus(), APPROVING.getStatus());
     }
 
+    public static boolean isCancelStatus(Integer status) {
+        return ObjUtil.equal(status, CANCEL.getStatus());
+    }
+
 }

+ 1 - 1
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/event/BpmProcessInstanceStatusEvent.java

@@ -8,7 +8,7 @@ import javax.validation.constraints.NotNull;
 /**
  * 流程实例的状态(结果)发生变化的 Event
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @SuppressWarnings("ALL")
 @Data

+ 1 - 1
citu-module-bpm/citu-module-bpm-api/src/main/java/com/citu/module/bpm/event/BpmProcessInstanceStatusEventListener.java

@@ -6,7 +6,7 @@ import org.springframework.context.ApplicationListener;
 /**
  * {@link BpmProcessInstanceStatusEvent} 的监听器
  *
- * @author Rayson
+ * @author 芋道源码
  */
 public abstract class BpmProcessInstanceStatusEventListener
         implements ApplicationListener<BpmProcessInstanceStatusEvent> {

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/BpmServerApplication.java

@@ -10,7 +10,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
  * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
  * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
  *
- * @author Rayson
+ * @author 芋道源码
  */
 @SpringBootApplication
 public class BpmServerApplication {

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/api/task/BpmProcessInstanceApiImpl.java

@@ -14,7 +14,7 @@ import static com.citu.framework.common.pojo.CommonResult.success;
 /**
  * Flowable 流程实例 Api 实现类
  *
- * @author Rayson
+ * @author 芋道源码
  * @author jason
  */
 @RestController

+ 4 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/base/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 基础包,放一些通用的 VO 类
+ */
+package com.citu.module.bpm.controller.admin.base;

+ 22 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java

@@ -0,0 +1,22 @@
+package com.citu.module.bpm.controller.admin.base.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户精简信息 VO")
+@Data
+public class UserSimpleBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    private String nickname;
+    @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
+    private String avatar;
+
+    @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long deptId;
+    @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
+    private String deptName;
+
+}

+ 9 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/BpmCategoryController.java

@@ -48,6 +48,15 @@ public class BpmCategoryController {
         return success(true);
     }
 
+    @PutMapping("/update-sort-batch")
+    @Operation(summary = "批量更新流程分类的排序")
+    @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3")
+    @PreAuthorize("@ss.hasPermission('bpm:category:update')")
+    public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) {
+        categoryService.updateCategorySortBatch(ids);
+        return success(true);
+    }
+
     @DeleteMapping("/delete")
     @Operation(summary = "删除流程分类")
     @Parameter(name = "id", description = "编号", required = true)

+ 71 - 39
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/BpmModelController.java

@@ -2,12 +2,9 @@ package com.citu.module.bpm.controller.admin.definition;
 
 import cn.hutool.core.collection.CollUtil;
 import com.citu.framework.common.pojo.CommonResult;
-import com.citu.framework.common.pojo.PageResult;
-import com.citu.framework.common.util.collection.CollectionUtils;
-import com.citu.framework.common.util.io.IoUtils;
-import com.citu.framework.common.util.json.JsonUtils;
-import com.citu.framework.common.util.object.BeanUtils;
 import com.citu.module.bpm.controller.admin.definition.vo.model.*;
+import com.citu.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import com.citu.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
 import com.citu.module.bpm.convert.definition.BpmModelConvert;
 import com.citu.module.bpm.dal.dataobject.definition.BpmCategoryDO;
 import com.citu.module.bpm.dal.dataobject.definition.BpmFormDO;
@@ -15,7 +12,8 @@ import com.citu.module.bpm.service.definition.BpmCategoryService;
 import com.citu.module.bpm.service.definition.BpmFormService;
 import com.citu.module.bpm.service.definition.BpmModelService;
 import com.citu.module.bpm.service.definition.BpmProcessDefinitionService;
-import com.citu.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
+import com.citu.module.system.api.user.AdminUserApi;
+import com.citu.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,15 +26,15 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
-import java.io.IOException;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import static com.citu.framework.common.pojo.CommonResult.success;
-import static com.citu.framework.common.util.collection.CollectionUtils.convertMap;
-import static com.citu.framework.common.util.collection.CollectionUtils.convertSet;
+import static com.citu.framework.common.util.collection.CollectionUtils.*;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Tag(name = "管理后台 - 流程模型")
 @RestController
@@ -53,32 +51,42 @@ public class BpmModelController {
     @Resource
     private BpmProcessDefinitionService processDefinitionService;
 
-    @GetMapping("/page")
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @GetMapping("/list")
     @Operation(summary = "获得模型分页")
-    public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
-        PageResult<Model> pageResult = modelService.getModelPage(pageVO);
-        if (CollUtil.isEmpty(pageResult.getList())) {
-            return success(PageResult.empty(pageResult.getTotal()));
+    @Parameter(name = "name", description = "模型名称", example = "芋艿")
+    public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) {
+        List<Model> list = modelService.getModelList(name);
+        if (CollUtil.isEmpty(list)) {
+            return success(Collections.emptyList());
         }
 
-        // 拼接数据
         // 获得 Form 表单
-        Set<Long> formIds = convertSet(pageResult.getList(), model -> {
-            BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
+        Set<Long> formIds = convertSet(list, model -> {
+            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
             return metaInfo != null ? metaInfo.getFormId() : null;
         });
         Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
         // 获得 Category Map
         Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
-                convertSet(pageResult.getList(), Model::getCategory));
+                convertSet(list, Model::getCategory));
         // 获得 Deployment Map
-        Set<String> deploymentIds = new HashSet<>();
-        pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
-        Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
+        Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
+                convertSet(list, Model::getDeploymentId));
         // 获得 ProcessDefinition Map
-        List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
+        List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
+                deploymentMap.keySet());
         Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
-        return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap));
+        // 获得 User Map
+        Set<Long> userIds = convertSetByFlatMap(list, model -> {
+            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
+            return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
+        });
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        return success(BpmModelConvert.INSTANCE.buildModelList(list,
+                formMap, categoryMap, deploymentMap, processDefinitionMap, userMap));
     }
 
     @GetMapping("/get")
@@ -97,26 +105,25 @@ public class BpmModelController {
     @PostMapping("/create")
     @Operation(summary = "新建模型")
     @PreAuthorize("@ss.hasPermission('bpm:model:create')")
-    public CommonResult<String> createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) {
-        return success(modelService.createModel(createRetVO, null));
+    public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) {
+        return success(modelService.createModel(createRetVO));
     }
 
+
     @PutMapping("/update")
     @Operation(summary = "修改模型")
     @PreAuthorize("@ss.hasPermission('bpm:model:update')")
-    public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) {
-        modelService.updateModel(modelVO);
+    public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) {
+        modelService.updateModel(getLoginUserId(), modelVO);
         return success(true);
     }
 
-    @PostMapping("/import")
-    @Operation(summary = "导入模型")
-    @PreAuthorize("@ss.hasPermission('bpm:model:import')")
-    public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
-        BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class);
-        // 读取文件
-        String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
-        return success(modelService.createModel(createReqVO, bpmnXml));
+    @PutMapping("/update-sort-batch")
+    @Operation(summary = "批量修改模型排序")
+    @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3")
+    public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) {
+        modelService.updateModelSortBatch(getLoginUserId(), ids);
+        return success(true);
     }
 
     @PostMapping("/deploy")
@@ -124,7 +131,7 @@ public class BpmModelController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('bpm:model:deploy')")
     public CommonResult<Boolean> deployModel(@RequestParam("id") String id) {
-        modelService.deployModel(id);
+        modelService.deployModel(getLoginUserId(), id);
         return success(true);
     }
 
@@ -132,7 +139,15 @@ public class BpmModelController {
     @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态")
     @PreAuthorize("@ss.hasPermission('bpm:model:update')")
     public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
-        modelService.updateModelState(reqVO.getId(), reqVO.getState());
+        modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState());
+        return success(true);
+    }
+
+    @PutMapping("/update-bpmn")
+    @Operation(summary = "修改模型的 BPMN")
+    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+    public CommonResult<Boolean> updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) {
+        modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml());
         return success(true);
     }
 
@@ -141,8 +156,25 @@ public class BpmModelController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('bpm:model:delete')")
     public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
-        modelService.deleteModel(id);
+        modelService.deleteModel(getLoginUserId(), id);
         return success(true);
     }
 
+    // ========== 仿钉钉/飞书的精简模型 =========
+
+    @GetMapping("/simple/get")
+    @Operation(summary = "获得仿钉钉流程设计模型")
+    @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
+    public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){
+        return success(modelService.getSimpleModel(modelId));
+    }
+
+    @PostMapping("/simple/update")
+    @Operation(summary = "保存仿钉钉流程设计模型")
+    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+    public CommonResult<Boolean> updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) {
+        modelService.updateSimpleModel(getLoginUserId(), reqVO);
+        return success(Boolean.TRUE);
+    }
+
 }

+ 14 - 8
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java

@@ -9,7 +9,6 @@ import com.citu.module.bpm.convert.definition.BpmProcessDefinitionConvert;
 import com.citu.module.bpm.dal.dataobject.definition.BpmCategoryDO;
 import com.citu.module.bpm.dal.dataobject.definition.BpmFormDO;
 import com.citu.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
-import com.citu.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy;
 import com.citu.module.bpm.service.definition.BpmCategoryService;
 import com.citu.module.bpm.service.definition.BpmFormService;
 import com.citu.module.bpm.service.definition.BpmProcessDefinitionService;
@@ -17,7 +16,6 @@ import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.flowable.bpmn.model.BpmnModel;
-import org.flowable.bpmn.model.UserTask;
 import org.flowable.engine.repository.Deployment;
 import org.flowable.engine.repository.ProcessDefinition;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -34,6 +32,7 @@ import java.util.Map;
 
 import static com.citu.framework.common.pojo.CommonResult.success;
 import static com.citu.framework.common.util.collection.CollectionUtils.convertSet;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Tag(name = "管理后台 - 流程定义")
 @RestController
@@ -77,17 +76,25 @@ public class BpmProcessDefinitionController {
     @GetMapping ("/list")
     @Operation(summary = "获得流程定义列表")
     @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举
-    @PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
     public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList(
             @RequestParam("suspensionState") Integer suspensionState) {
+        // 1.1 获得开启的流程定义
         List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState);
         if (CollUtil.isEmpty(list)) {
             return success(Collections.emptyList());
         }
-
-        // 获得 BpmProcessDefinitionInfoDO Map
+        // 1.2 移除不可见的流程定义
         Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
                 convertSet(list, ProcessDefinition::getId));
+        Long userId = getLoginUserId();
+        list.removeIf(processDefinition -> {
+            BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId());
+            return processDefinitionInfo == null // 不存在
+                    || Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见
+                    || !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起
+        });
+
+        // 2. 拼接 VO 返回
         return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList(
                 list, null, processDefinitionMap, null, null));
     }
@@ -96,7 +103,6 @@ public class BpmProcessDefinitionController {
     @Operation(summary = "获得流程定义")
     @Parameter(name = "id", description = "流程编号", required = true, example = "1024")
     @Parameter(name = "key", description = "流程定义标识", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
     public CommonResult<BpmProcessDefinitionRespVO> getProcessDefinition(
             @RequestParam(value = "id", required = false) String id,
             @RequestParam(value = "key", required = false) String key) {
@@ -105,10 +111,10 @@ public class BpmProcessDefinitionController {
         if (processDefinition == null) {
             return success(null);
         }
+        BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
-        List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
         return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
-                processDefinition, null, null, null, null, bpmnModel, userTaskList));
+                processDefinition, null, processDefinitionInfo, null, null, bpmnModel));
     }
 
 }

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java

@@ -16,7 +16,7 @@ public class BpmUserGroupRespVO {
     @Schema(description = "组名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
     private String name;
 
-    @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "Rayson")
+    @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
     private String description;
 
     @Schema(description = "成员编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java

@@ -17,7 +17,7 @@ public class BpmUserGroupSaveReqVO {
     @NotNull(message = "组名不能为空")
     private String name;
 
-    @Schema(description = "描述", example = "Rayson")
+    @Schema(description = "描述", example = "芋道源码")
     private String description;
 
     @Schema(description = "成员编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")

+ 0 - 17
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java

@@ -1,17 +0,0 @@
-package com.citu.module.bpm.controller.admin.definition.vo.model;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.validation.constraints.NotNull;
-
-@Schema(description = "管理后台 - 流程模型的导入 Request VO 相比流程模型的新建来说,只是多了一个 bpmnFile 文件")
-@Data
-public class BpmModeImportReqVO extends BpmModelCreateReqVO {
-
-    @Schema(description = "BPMN 文件", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "BPMN 文件不能为空")
-    private MultipartFile bpmnFile;
-
-}

+ 20 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java

@@ -0,0 +1,20 @@
+package com.citu.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO")
+@Data
+public class BpmModeUpdateBpmnReqVO {
+
+    @Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "流程编号不能为空")
+    private String id;
+
+    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "BPMN XML 不能为空")
+    private String bpmnXml;
+
+}

+ 65 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java

@@ -0,0 +1,65 @@
+package com.citu.module.bpm.controller.admin.definition.vo.model;
+
+import com.citu.framework.common.validation.InEnum;
+import com.citu.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import com.citu.module.bpm.enums.definition.BpmModelTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * BPM 流程 MetaInfo Response DTO
+ * 主要用于 { Model#setMetaInfo(String)} 的存储
+ *
+ * 最终,它的字段和
+ * {@link com.citu.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO}
+ * 是一致的
+ *
+ * @author 芋道源码
+ */
+@Data
+public class BpmModelMetaInfoVO {
+
+    @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
+    @NotEmpty(message = "流程图标不能为空")
+    @URL(message = "流程图标格式不正确")
+    private String icon;
+
+    @Schema(description = "流程描述", example = "我是描述")
+    private String description;
+
+    @Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @InEnum(BpmModelTypeEnum.class)
+    @NotNull(message = "流程类型不能为空")
+    private Integer type;
+
+    @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @InEnum(BpmModelFormTypeEnum.class)
+    @NotNull(message = "表单类型不能为空")
+    private Integer formType;
+    @Schema(description = "表单编号", example = "1024")
+    private Long formId; // formType 为 NORMAL 使用,必须非空
+    @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
+    private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
+    @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
+    private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
+
+    @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    @NotNull(message = "是否可见不能为空")
+    private Boolean visible;
+
+    @Schema(description = "可发起用户编号数组", example = "[1,2,3]")
+    private List<Long> startUserIds;
+
+    @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]")
+    @NotEmpty(message = "可管理用户编号数组不能为空")
+    private List<Long> managerUserIds;
+
+    @Schema(description = "排序", example = "1")
+    private Long sort; // 创建时,后端自动生成
+
+}

+ 8 - 16
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java

@@ -1,54 +1,46 @@
 package com.citu.module.bpm.controller.admin.definition.vo.model;
 
+import com.citu.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
 import com.citu.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "管理后台 - 流程模型 Response VO")
 @Data
-public class BpmModelRespVO {
+public class BpmModelRespVO extends BpmModelMetaInfoVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private String id;
 
-    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_citu")
+    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao")
     private String key;
 
     @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
     private String name;
 
-    @Schema(description = "流程图标", example = "https://www.iocoder.cn/citu.jpg")
+    @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
     private String icon;
 
-    @Schema(description = "流程描述", example = "我是描述")
-    private String description;
-
     @Schema(description = "流程分类编码", example = "1")
     private String category;
     @Schema(description = "流程分类名字", example = "请假")
     private String categoryName;
 
-    @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
-    private Integer formType;
-
-    @Schema(description = "表单编号", example = "1024")
-    private Long formId; // 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
     @Schema(description = "表单名字", example = "请假表单")
     private String formName;
 
-    @Schema(description = "自定义表单的提交路径", example = "/bpm/oa/leave/create")
-    private String formCustomCreatePath; // 使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
-    @Schema(description = "自定义表单的查看路径", example = "/bpm/oa/leave/view")
-    private String formCustomViewPath; // ,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
-
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
     @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
     private String bpmnXml;
 
+    @Schema(description = "可发起的用户数组")
+    private List<UserSimpleBaseVO> startUsers;
+
     /**
      * 最新部署的流程定义
      */

+ 8 - 5
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java → citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java

@@ -5,11 +5,14 @@ import lombok.Data;
 
 import javax.validation.constraints.NotEmpty;
 
-@Schema(description = "管理后台 - 流程模型的创建 Request VO")
+@Schema(description = "管理后台 - 流程模型的保存 Request VO")
 @Data
-public class BpmModelCreateReqVO {
+public class BpmModelSaveReqVO extends BpmModelMetaInfoVO {
 
-    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_citu")
+    @Schema(description = "编号", example = "1024")
+    private String id;
+
+    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao")
     @NotEmpty(message = "流程标识不能为空")
     private String key;
 
@@ -17,7 +20,7 @@ public class BpmModelCreateReqVO {
     @NotEmpty(message = "流程名称不能为空")
     private String name;
 
-    @Schema(description = "流程描述", example = "我是描述")
-    private String description;
+    @Schema(description = "流程分类", example = "1")
+    private String category;
 
 }

+ 0 - 47
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java

@@ -1,47 +0,0 @@
-package com.citu.module.bpm.controller.admin.definition.vo.model;
-
-import com.citu.framework.common.validation.InEnum;
-import com.citu.module.bpm.enums.definition.BpmModelFormTypeEnum;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import org.hibernate.validator.constraints.URL;
-
-import javax.validation.constraints.NotEmpty;
-
-@Schema(description = "管理后台 - 流程模型的更新 Request VO")
-@Data
-public class BpmModelUpdateReqVO {
-
-    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    @NotEmpty(message = "编号不能为空")
-    private String id;
-
-    @Schema(description = "流程名称", example = "芋道")
-    private String name;
-
-    @Schema(description = "流程图标", example = "https://www.iocoder.cn/citu.jpg")
-    @URL(message = "流程图标格式不正确")
-    private String icon;
-
-    @Schema(description = "流程描述", example = "我是描述")
-    private String description;
-
-    @Schema(description = "流程分类", example = "1")
-    private String category;
-
-    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String bpmnXml;
-
-    @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
-    @InEnum(BpmModelFormTypeEnum.class)
-    private Integer formType;
-    @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")
-    private Long formId;
-    @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",
-            example = "/bpm/oa/leave/create")
-    private String formCustomCreatePath;
-    @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",
-            example = "/bpm/oa/leave/view")
-    private String formCustomViewPath;
-
-}

+ 212 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java

@@ -0,0 +1,212 @@
+package com.citu.module.bpm.controller.admin.definition.vo.model.simple;
+
+import com.citu.framework.common.validation.InEnum;
+import com.citu.module.bpm.enums.definition.*;
+import com.citu.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BpmSimpleModelNodeVO {
+
+    @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1")
+    @NotEmpty(message = "模型节点编号不能为空")
+    private String id;
+
+    @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "模型节点类型不能为空")
+    @InEnum(BpmSimpleModelNodeType.class)
+    private Integer type;
+
+    @Schema(description = "模型节点名称", example = "领导审批")
+    private String name;
+
+    @Schema(description = "节点展示内容", example = "指定成员: 芋道源码")
+    private String showText;
+
+    @Schema(description = "子节点")
+    private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个
+
+    @Schema(description = "条件节点")
+    private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用
+
+    @Schema(description = "条件类型", example = "1")
+    @InEnum(BpmSimpleModeConditionType.class)
+    private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
+
+    @Schema(description = "条件表达式", example = "${day>3}")
+    private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
+
+    @Schema(description = "是否默认条件", example = "true")
+    private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
+    /**
+     * 条件组
+     */
+    private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
+
+    @Schema(description = "候选人策略", example = "30")
+    @InEnum(BpmTaskCandidateStrategyEnum.class)
+    private Integer candidateStrategy; // 用于审批,抄送节点
+
+    @Schema(description = "候选人参数")
+    private String candidateParam; // 用于审批,抄送节点
+
+    @Schema(description = "审批节点类型", example = "1")
+    @InEnum(BpmUserTaskApproveTypeEnum.class)
+    private Integer approveType; // 用于审批节点
+
+    @Schema(description = "多人审批方式", example = "1")
+    @InEnum(BpmUserTaskApproveMethodEnum.class)
+    private Integer approveMethod; // 用于审批节点
+
+    @Schema(description = "通过比例", example = "100")
+    private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置
+
+    @Schema(description = "表单权限", example = "[]")
+    private List<Map<String, String>> fieldsPermission;
+
+    @Schema(description = "操作按钮设置", example = "[]")
+    private List<OperationButtonSetting> buttonsSetting;  // 用于审批节点
+
+    /**
+     * 审批节点拒绝处理
+     */
+    private RejectHandler rejectHandler;
+
+    /**
+     * 审批节点超时处理
+     */
+    private TimeoutHandler timeoutHandler;
+
+    @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1")
+    @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
+    private Integer assignStartUserHandlerType;
+
+    /**
+     * 空处理策略
+     */
+    private AssignEmptyHandler assignEmptyHandler;
+
+    @Schema(description = "审批节点拒绝处理策略")
+    @Data
+    public static class RejectHandler {
+
+        @Schema(description = "拒绝处理类型", example = "1")
+        @InEnum(BpmUserTaskRejectHandlerType.class)
+        private Integer type;
+
+        @Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1")
+        private String returnNodeId;
+    }
+
+    @Schema(description = "审批节点超时处理策略")
+    @Valid
+    @Data
+    public static class TimeoutHandler {
+
+        @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+        @NotNull(message = "是否开启超时处理不能为空")
+        private Boolean enable;
+
+        @Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "任务超时未处理的行为不能为空")
+        @InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class)
+        private Integer type;
+
+        @Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H")
+        @NotEmpty(message = "超时时间不能为空")
+        private String timeDuration;
+
+        @Schema(description = "最大提醒次数", example = "1")
+        private Integer maxRemindCount;
+
+    }
+
+    @Schema(description = "空处理策略")
+    @Data
+    @Valid
+    public static class AssignEmptyHandler {
+
+        @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "空处理类型不能为空")
+        @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class)
+        private Integer type;
+
+        @Schema(description = "指定人员审批的用户编号数组", example = "1")
+        private List<Long> userIds;
+
+    }
+
+    @Schema(description = "操作按钮设置")
+    @Data
+    @Valid
+    public static class OperationButtonSetting {
+
+        // TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。
+        @Schema(description = "按钮 Id", example = "1")
+        private Integer id;
+
+        @Schema(description = "显示名称", example = "审批")
+        private String displayName;
+
+        @Schema(description = "是否启用", example = "true")
+        private Boolean enable;
+    }
+
+    @Schema(description = "条件组")
+    @Data
+    @Valid
+    public static class ConditionGroups {
+
+        @Schema(description = "条件组下的条件关系是否为与关系", example = "true")
+        @NotNull(message = "条件关系不能为空")
+        private Boolean and;
+
+        @Schema(description = "条件组下的条件", example = "[]")
+        @NotEmpty(message = "条件不能为空")
+        private List<Condition> conditions;
+    }
+
+    @Schema(description = "条件")
+    @Data
+    @Valid
+    public static class Condition {
+
+        @Schema(description = "条件下的规则关系是否为与关系", example = "true")
+        @NotNull(message = "规则关系不能为空")
+        private Boolean and;
+
+        @Schema(description = "条件下的规则", example = "[]")
+        @NotEmpty(message = "规则不能为空")
+        private List<ConditionRule> rules;
+    }
+
+    @Schema(description = "条件规则")
+    @Data
+    @Valid
+    public static class ConditionRule {
+
+        @Schema(description = "运行符号", example = "==")
+        @NotEmpty(message = "运行符号不能为空")
+        private String opCode;
+
+        @Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId")
+        @NotEmpty(message = "运算符左边的值不能为空")
+        private String leftSide;
+
+        @Schema(description = "运算符右边的值", example = "1")
+        @NotEmpty(message = "运算符右边的值不能为空")
+        private String rightSide;
+    }
+
+    // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持
+}

+ 24 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java

@@ -0,0 +1,24 @@
+package com.citu.module.bpm.controller.admin.definition.vo.model.simple;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+// TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下;
+@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO")
+@Data
+public class BpmSimpleModelUpdateReqVO {
+
+    @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotEmpty(message = "流程模型编号不能为空")
+    private String id; // 对应 Flowable act_re_model 表 ID_ 字段
+
+    @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "仿钉钉流程设计模型对象不能为空")
+    @Valid
+    private BpmSimpleModelNodeVO simpleModel;
+
+}

+ 10 - 4
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java

@@ -19,10 +19,10 @@ public class BpmProcessDefinitionRespVO {
     @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
     private String name;
 
-    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "citu")
+    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
     private String key;
 
-    @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/citu.jpg")
+    @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
     private String icon;
 
     @Schema(description = "流程描述", example = "我是描述")
@@ -33,6 +33,9 @@ public class BpmProcessDefinitionRespVO {
     @Schema(description = "流程分类名字", example = "请假")
     private String categoryName;
 
+    @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
+
     @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
     private Integer formType;
     @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")
@@ -59,9 +62,12 @@ public class BpmProcessDefinitionRespVO {
     @Schema(description = "BPMN XML")
     private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回
 
-    @Schema(description = "发起用户需要选择审批人的任务数组")
-    private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回
+    @Schema(description = "SIMPLE 设计器模型数据 json 格式")
+    private String simpleModel; // 非必须返回
 
+    @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long sort;
+    
     @Schema(description = "BPMN UserTask 用户任务")
     @Data
     public static class UserTask {

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/BpmOALeaveController.java

@@ -25,7 +25,7 @@ import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLo
  * OA 请假申请 Controller,用于演示自己存储数据,接入工作流的例子
  *
  * @author jason
- * @author Rayson
+ * @author 芋道源码
  */
 @Tag(name = "管理后台 - OA 请假申请")
 @RestController

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java

@@ -29,7 +29,7 @@ public class BpmOALeaveCreateReqVO {
     @Schema(description = "请假类型-参见 bpm_oa_type 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer type;
 
-    @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读Rayson")
+    @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读芋道源码")
     private String reason;
 
     @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}")

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java

@@ -19,7 +19,7 @@ public class BpmOALeavePageReqVO extends PageParam {
     @Schema(description = "请假类型,参见 bpm_oa_type", example = "1")
     private Integer type;
 
-    @Schema(description = "原因,模糊匹配", example = "阅读Rayson")
+    @Schema(description = "原因,模糊匹配", example = "阅读芋道源码")
     private String reason;
 
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)

+ 1 - 1
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java

@@ -15,7 +15,7 @@ public class BpmOALeaveRespVO {
     @Schema(description = "请假类型,参见 bpm_oa_type 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer type;
 
-    @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读Rayson")
+    @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读芋道源码")
     private String reason;
 
     @Schema(description = "申请时间", requiredMode = Schema.RequiredMode.REQUIRED)

+ 0 - 39
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmActivityController.java

@@ -1,39 +0,0 @@
-package com.citu.module.bpm.controller.admin.task;
-
-import com.citu.framework.common.pojo.CommonResult;
-import com.citu.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
-import com.citu.module.bpm.service.task.BpmActivityService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-import static com.citu.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - 流程活动实例")
-@RestController
-@RequestMapping("/bpm/activity")
-@Validated
-public class BpmActivityController {
-
-    @Resource
-    private BpmActivityService activityService;
-
-    @GetMapping("/list")
-    @Operation(summary = "生成指定流程实例的高亮流程图",
-            description = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
-    @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
-    @PreAuthorize("@ss.hasPermission('bpm:task:query')")
-    public CommonResult<List<BpmActivityRespVO>> getActivityList(
-            @RequestParam("processInstanceId") String processInstanceId) {
-        return success(activityService.getActivityListByProcessInstanceId(processInstanceId));
-    }
-}

+ 16 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmProcessInstanceController.http

@@ -0,0 +1,16 @@
+### 请求 /bpm/process-instance/get-bpmn 接口 => 成功
+GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
+Content-Type: application/json
+tenant-id: 1
+Authorization: Bearer {{token}}
+
+### 请求 /bpm/process-instance/get-bpmn 接口 => 失败
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b
+GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b
+Content-Type: application/json
+tenant-id: 1
+Authorization: Bearer {{token}}

+ 18 - 9
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmProcessInstanceController.java

@@ -4,14 +4,10 @@ import cn.hutool.core.collection.CollUtil;
 import com.citu.framework.common.pojo.CommonResult;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.common.util.number.NumberUtils;
-import com.citu.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
-import com.citu.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
-import com.citu.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO;
-import com.citu.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
+import com.citu.module.bpm.controller.admin.task.vo.instance.*;
 import com.citu.module.bpm.convert.task.BpmProcessInstanceConvert;
 import com.citu.module.bpm.dal.dataobject.definition.BpmCategoryDO;
 import com.citu.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
-import com.citu.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import com.citu.module.bpm.service.definition.BpmCategoryService;
 import com.citu.module.bpm.service.definition.BpmProcessDefinitionService;
 import com.citu.module.bpm.service.task.BpmProcessInstanceService;
@@ -131,15 +127,13 @@ public class BpmProcessInstanceController {
                 processInstance.getProcessDefinitionId());
         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
                 processInstance.getProcessDefinitionId());
-        String bpmnXml = BpmnModelUtils.getBpmnXml(
-                processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()));
         AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData();
         DeptRespDTO dept = null;
-        if (startUser != null) {
+        if (startUser != null && startUser.getDeptId() != null) {
             dept = deptApi.getDept(startUser.getDeptId()).getCheckedData();
         }
         return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance,
-                processDefinition, processDefinitionInfo, bpmnXml, startUser, dept));
+                processDefinition, processDefinitionInfo, startUser, dept));
     }
 
     @DeleteMapping("/cancel-by-start-user")
@@ -160,4 +154,19 @@ public class BpmProcessInstanceController {
         return success(true);
     }
 
+    @GetMapping("/get-approval-detail")
+    @Operation(summary = "获得审批详情")
+    @Parameter(name = "id", description = "流程实例的编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+    public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) {
+        return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
+    }
+
+    @GetMapping("/get-bpmn-model-view")
+    @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
+    @Parameter(name = "id", description = "流程实例的编号", required = true)
+    public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
+        return success(processInstanceService.getProcessInstanceBpmnModelView(id));
+    }
+
 }

+ 9 - 11
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java

@@ -6,12 +6,12 @@ import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.common.util.collection.MapUtils;
 import com.citu.framework.common.util.date.DateUtils;
 import com.citu.framework.common.util.object.BeanUtils;
+import com.citu.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
 import com.citu.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO;
 import com.citu.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
 import com.citu.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
 import com.citu.module.bpm.service.task.BpmProcessInstanceCopyService;
 import com.citu.module.bpm.service.task.BpmProcessInstanceService;
-import com.citu.module.bpm.service.task.BpmTaskService;
 import com.citu.module.system.api.user.AdminUserApi;
 import com.citu.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
@@ -29,8 +29,7 @@ import java.util.Map;
 import java.util.stream.Stream;
 
 import static com.citu.framework.common.pojo.CommonResult.success;
-import static com.citu.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
-import static com.citu.framework.common.util.collection.CollectionUtils.convertSet;
+import static com.citu.framework.common.util.collection.CollectionUtils.*;
 import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Tag(name = "管理后台 - 流程实例抄送")
@@ -43,8 +42,6 @@ public class BpmProcessInstanceCopyController {
     private BpmProcessInstanceCopyService processInstanceCopyService;
     @Resource
     private BpmProcessInstanceService processInstanceService;
-    @Resource
-    private BpmTaskService taskService;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -61,18 +58,19 @@ public class BpmProcessInstanceCopyController {
         }
 
         // 拼接返回
-        Map<String, String> taskNameMap = taskService.getTaskNameByTaskIds(
-                convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId));
         Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
                 convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
                 copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator()))));
-        return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> {
-            MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname()));
-            MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname()));
-            MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName);
+        return success(convertPage(pageResult, copy -> {
+            BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class);
+            MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()),
+                    user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
+            MapUtils.findAndThen(userMap, copy.getStartUserId(),
+                    user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
             MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
                     processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime())));
+            return copyVO;
         }));
     }
 

+ 12 - 7
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/BpmTaskController.java

@@ -117,24 +117,21 @@ public class BpmTaskController {
     @PreAuthorize("@ss.hasPermission('bpm:task:query')")
     public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
             @RequestParam("processInstanceId") String processInstanceId) {
-        List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId);
+        List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true);
         if (CollUtil.isEmpty(taskList)) {
             return success(Collections.emptyList());
         }
 
         // 拼接数据
-        HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
-        // 获得 User 和 Dept Map
         Set<Long> userIds = convertSetByFlatMap(taskList, task ->
                 Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
-        userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
                 convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
         // 获得 Form Map
         Map<Long, BpmFormDO> formMap = formService.getFormMap(
                 convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey())));
-        return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance,
+        return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList,
                 formMap, userMap, deptMap));
     }
 
@@ -155,7 +152,7 @@ public class BpmTaskController {
     }
 
     @GetMapping("/list-by-return")
-    @Operation(summary = "获取所有可退的节点", description = "用于【流程详情】的【退】按钮")
+    @Operation(summary = "获取所有可退的节点", description = "用于【流程详情】的【退】按钮")
     @Parameter(name = "taskId", description = "当前任务ID", required = true)
     @PreAuthorize("@ss.hasPermission('bpm:task:update')")
     public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) {
@@ -165,7 +162,7 @@ public class BpmTaskController {
     }
 
     @PutMapping("/return")
-    @Operation(summary = "退任务", description = "用于【流程详情】的【退】按钮")
+    @Operation(summary = "退任务", description = "用于【流程详情】的【退】按钮")
     @PreAuthorize("@ss.hasPermission('bpm:task:update')")
     public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
         taskService.returnTask(getLoginUserId(), reqVO);
@@ -204,6 +201,14 @@ public class BpmTaskController {
         return success(true);
     }
 
+    @PutMapping("/copy")
+    @Operation(summary = "抄送任务")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) {
+        taskService.copyTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
     @GetMapping("/list-by-parent-task-id")
     @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
     @Parameter(name = "parentTaskId", description = "父级任务编号", required = true)

+ 17 - 14
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java

@@ -1,5 +1,6 @@
 package com.citu.module.bpm.controller.admin.task.vo.cc;
 
+import com.citu.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -12,29 +13,31 @@ public class BpmProcessInstanceCopyRespVO {
     @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
-    @Schema(description = "发起人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
-    private Long startUserId;
-    @Schema(description = "发起人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
-    private String startUserName;
+    @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED)
+    private UserSimpleBaseVO startUser;
 
     @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233")
     private String processInstanceId;
-    @Schema(description = "流程实例的名称")
+    @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
     private String processInstanceName;
-    @Schema(description = "流程实例的发起时间")
+    @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime processInstanceStartTime;
 
-    @Schema(description = "发起抄送的任务编号")
+    @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String activityId;
+    @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String activityName;
+
+    @Schema(description = "流程活动的编号")
     private String taskId;
-    @Schema(description = "发起抄送的任务名称")
-    private String taskName;
 
-    @Schema(description = "抄送人")
-    private String creator;
-    @Schema(description = "抄送人昵称")
-    private String creatorName;
+    @Schema(description = "抄送人意见")
+    private String reason;
+
+    @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
+    private UserSimpleBaseVO createUser;
 
-    @Schema(description = "抄送时间")
+    @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
 }

+ 37 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java

@@ -0,0 +1,37 @@
+package com.citu.module.bpm.controller.admin.task.vo.instance;
+
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.AssertTrue;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 审批详情 Request VO")
+@Data
+public class BpmApprovalDetailReqVO {
+
+    @Schema(description = "流程定义的编号", example = "1024")
+    private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID
+
+    @Schema(description = "流程变量")
+    private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
+
+    @Schema(description = "流程实例的编号", example = "1024")
+    private String processInstanceId;  // 使用场景:流程已发起时候传流程实例 ID
+
+    // TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。
+    @Schema(description = "流程活动编号", example = "StartUserNode")
+    private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限;
+
+    @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
+    private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限
+
+    @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
+    @JsonIgnore
+    public boolean isValidProcessParam() {
+        return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
+    }
+
+}

+ 106 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java

@@ -0,0 +1,106 @@
+package com.citu.module.bpm.controller.admin.task.vo.instance;
+
+import com.citu.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import com.citu.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import com.citu.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+
+@Schema(description = "管理后台 - 审批详情 Response VO")
+@Data
+public class BpmApprovalDetailRespVO {
+
+    @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
+
+    @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<ActivityNode> activityNodes;
+
+    @Schema(description = "表单字段权限")
+    private Map<String, String> formFieldsPermission;
+
+    @Schema(description = "待办任务")
+    private BpmTaskRespVO todoTask;
+
+    /**
+     * 所属流程定义信息
+     */
+    private BpmProcessDefinitionRespVO processDefinition;
+
+    /**
+     * 所属流程实例信息
+     */
+    private BpmProcessInstanceRespVO processInstance;
+
+    @Schema(description = "活动节点信息")
+    @Data
+    public static class ActivityNode {
+
+        @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
+        private String id;
+
+        @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人")
+        private String name;
+
+        @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举
+
+        @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+        private Integer status; // 参见 BpmTaskStatusEnum 枚举
+
+        @Schema(description = "节点的开始时间")
+        private LocalDateTime startTime;
+        @Schema(description = "节点的结束时间")
+        private LocalDateTime endTime;
+
+        @Schema(description = "审批节点的任务信息")
+        private List<ActivityNodeTask> tasks;
+
+        @Schema(description = "候选人策略", example = "35")
+        private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选
+
+        @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
+        @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers
+        private List<Long> candidateUserIds;
+
+        @Schema(description = "候选人用户列表")
+        private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
+
+    }
+
+    @Schema(description = "活动节点的任务信息")
+    @Data
+    public static class ActivityNodeTask {
+
+        @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private String id;
+
+        @Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
+        @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
+        private Long owner;
+
+        @Schema(description = "任务所属人", example = "1024")
+        private UserSimpleBaseVO ownerUser;
+
+        @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+        @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
+        private Long assignee;
+
+        @Schema(description = "任务分配人", example = "2048")
+        private UserSimpleBaseVO assigneeUser;
+
+        @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Integer status;  // 参见 BpmTaskStatusEnum 枚举
+
+        @Schema(description = "审批意见", example = "同意")
+        private String reason;
+
+    }
+
+}

+ 43 - 0
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java

@@ -0,0 +1,43 @@
+package com.citu.module.bpm.controller.admin.task.vo.instance;
+
+import com.citu.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import com.citu.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Set;
+
+@Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO")
+@Data
+public class BpmProcessInstanceBpmnModelViewRespVO {
+
+    // ========== 基本信息 ==========
+
+    @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED)
+    private BpmProcessInstanceRespVO processInstance;
+
+    @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<BpmTaskRespVO> tasks;
+
+    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String bpmnXml;
+
+    @Schema(description = "SIMPLE 模型")
+    private BpmSimpleModelNodeVO simpleModel;
+
+    // ========== 进度信息 ==========
+
+    @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask
+
+    @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow
+
+    @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow
+
+    @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> rejectedTaskActivityIds; // 只包括 UserTask
+
+}

+ 2 - 2
citu-module-bpm/citu-module-bpm-biz/src/main/java/com/citu/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java

@@ -18,8 +18,8 @@ public class BpmProcessInstancePageReqVO extends PageParam {
     @Schema(description = "流程名称", example = "芋道")
     private String name;
 
-    @Schema(description = "流程定义的编号", example = "2048")
-    private String processDefinitionId;
+    @Schema(description = "流程定义的标识", example = "2048")
+    private String processDefinitionKey; // 精准匹配
 
     @Schema(description = "流程实例的状态", example = "1")
     @InEnum(BpmProcessInstanceStatusEnum.class)

Неке датотеке нису приказане због велике количине промена