Browse Source

初始化

wangtuohang@126.com 1 month ago
commit
db5a0a0247
100 changed files with 9538 additions and 0 deletions
  1. 17 0
      Dockerfile
  2. 368 0
      pom.xml
  3. 24 0
      src/main/java/org/springblade/Application.java
  4. 70 0
      src/main/java/org/springblade/common/cache/CacheNames.java
  5. 125 0
      src/main/java/org/springblade/common/cache/DictBizCache.java
  6. 152 0
      src/main/java/org/springblade/common/cache/DictCache.java
  7. 71 0
      src/main/java/org/springblade/common/cache/ParamCache.java
  8. 66 0
      src/main/java/org/springblade/common/cache/RegionCache.java
  9. 334 0
      src/main/java/org/springblade/common/cache/SysCache.java
  10. 86 0
      src/main/java/org/springblade/common/cache/UserCache.java
  11. 95 0
      src/main/java/org/springblade/common/config/BladeConfiguration.java
  12. 52 0
      src/main/java/org/springblade/common/config/BladeHandlerConfiguration.java
  13. 67 0
      src/main/java/org/springblade/common/config/BladeLogConfiguration.java
  14. 26 0
      src/main/java/org/springblade/common/config/BladePreviewConfiguration.java
  15. 52 0
      src/main/java/org/springblade/common/config/BladeReportConfiguration.java
  16. 70 0
      src/main/java/org/springblade/common/config/SwaggerConfiguration.java
  17. 93 0
      src/main/java/org/springblade/common/constant/CommonConstant.java
  18. 47 0
      src/main/java/org/springblade/common/constant/DictConstant.java
  19. 96 0
      src/main/java/org/springblade/common/constant/LauncherConstant.java
  20. 75 0
      src/main/java/org/springblade/common/constant/TenantConstant.java
  21. 48 0
      src/main/java/org/springblade/common/enums/DictBizEnum.java
  22. 104 0
      src/main/java/org/springblade/common/enums/DictEnum.java
  23. 69 0
      src/main/java/org/springblade/common/event/ApiLogListener.java
  24. 71 0
      src/main/java/org/springblade/common/event/ErrorLogListener.java
  25. 67 0
      src/main/java/org/springblade/common/event/UsualLogListener.java
  26. 77 0
      src/main/java/org/springblade/common/filter/PreviewFilter.java
  27. 130 0
      src/main/java/org/springblade/common/handler/BladeScopeModelHandler.java
  28. 53 0
      src/main/java/org/springblade/common/launch/LauncherServiceImpl.java
  29. 35 0
      src/main/java/org/springblade/common/utils/CommonUtil.java
  30. 155 0
      src/main/java/org/springblade/flow/business/controller/WorkController.java
  31. 81 0
      src/main/java/org/springblade/flow/business/service/FlowBusinessService.java
  32. 86 0
      src/main/java/org/springblade/flow/business/service/IFlowService.java
  33. 342 0
      src/main/java/org/springblade/flow/business/service/impl/FlowBusinessServiceImpl.java
  34. 106 0
      src/main/java/org/springblade/flow/business/service/impl/FlowServiceImpl.java
  35. 70 0
      src/main/java/org/springblade/flow/core/constant/ProcessConstant.java
  36. 190 0
      src/main/java/org/springblade/flow/core/entity/BladeFlow.java
  37. 52 0
      src/main/java/org/springblade/flow/core/entity/FlowEntity.java
  38. 54 0
      src/main/java/org/springblade/flow/core/enums/FlowModeEnum.java
  39. 76 0
      src/main/java/org/springblade/flow/core/utils/FlowUtil.java
  40. 80 0
      src/main/java/org/springblade/flow/core/utils/TaskUtil.java
  41. 74 0
      src/main/java/org/springblade/flow/demo/leave/controller/LeaveController.java
  42. 78 0
      src/main/java/org/springblade/flow/demo/leave/entity/ProcessLeave.java
  43. 38 0
      src/main/java/org/springblade/flow/demo/leave/mapper/LeaveMapper.java
  44. 6 0
      src/main/java/org/springblade/flow/demo/leave/mapper/LeaveMapper.xml
  45. 46 0
      src/main/java/org/springblade/flow/demo/leave/service/ILeaveService.java
  46. 91 0
      src/main/java/org/springblade/flow/demo/leave/service/impl/LeaveServiceImpl.java
  47. 6 0
      src/main/java/org/springblade/flow/demo/package-info.java
  48. 53 0
      src/main/java/org/springblade/flow/engine/config/FlowableConfiguration.java
  49. 61 0
      src/main/java/org/springblade/flow/engine/constant/FlowEngineConstant.java
  50. 82 0
      src/main/java/org/springblade/flow/engine/controller/FlowFollowController.java
  51. 134 0
      src/main/java/org/springblade/flow/engine/controller/FlowManagerController.java
  52. 133 0
      src/main/java/org/springblade/flow/engine/controller/FlowModelController.java
  53. 107 0
      src/main/java/org/springblade/flow/engine/controller/FlowProcessController.java
  54. 61 0
      src/main/java/org/springblade/flow/engine/entity/FlowExecution.java
  55. 69 0
      src/main/java/org/springblade/flow/engine/entity/FlowModel.java
  56. 74 0
      src/main/java/org/springblade/flow/engine/entity/FlowProcess.java
  57. 55 0
      src/main/java/org/springblade/flow/engine/mapper/FlowMapper.java
  58. 53 0
      src/main/java/org/springblade/flow/engine/mapper/FlowMapper.xml
  59. 174 0
      src/main/java/org/springblade/flow/engine/service/FlowEngineService.java
  60. 568 0
      src/main/java/org/springblade/flow/engine/service/impl/FlowEngineServiceImpl.java
  61. 89 0
      src/main/java/org/springblade/flow/engine/utils/FlowCache.java
  62. 165 0
      src/main/java/org/springblade/job/controller/JobInfoController.java
  63. 163 0
      src/main/java/org/springblade/job/controller/JobServerController.java
  64. 51 0
      src/main/java/org/springblade/job/mapper/JobInfoMapper.java
  65. 52 0
      src/main/java/org/springblade/job/mapper/JobInfoMapper.xml
  66. 51 0
      src/main/java/org/springblade/job/mapper/JobServerMapper.java
  67. 27 0
      src/main/java/org/springblade/job/mapper/JobServerMapper.xml
  68. 56 0
      src/main/java/org/springblade/job/pojo/dto/JobDTO.java
  69. 202 0
      src/main/java/org/springblade/job/pojo/entity/JobInfo.java
  70. 76 0
      src/main/java/org/springblade/job/pojo/entity/JobServer.java
  71. 45 0
      src/main/java/org/springblade/job/pojo/vo/JobInfoVO.java
  72. 45 0
      src/main/java/org/springblade/job/pojo/vo/JobServerVO.java
  73. 35 0
      src/main/java/org/springblade/job/processor/ProcessorDemo.java
  74. 88 0
      src/main/java/org/springblade/job/service/IJobInfoService.java
  75. 64 0
      src/main/java/org/springblade/job/service/IJobServerService.java
  76. 344 0
      src/main/java/org/springblade/job/service/impl/JobInfoServiceImpl.java
  77. 71 0
      src/main/java/org/springblade/job/service/impl/JobServerServiceImpl.java
  78. 61 0
      src/main/java/org/springblade/modules/auth/config/BladeAuthConfiguration.java
  79. 22 0
      src/main/java/org/springblade/modules/auth/constant/BladeAuthConstant.java
  80. 104 0
      src/main/java/org/springblade/modules/auth/endpoint/Oauth2SmsEndpoint.java
  81. 74 0
      src/main/java/org/springblade/modules/auth/granter/CaptchaTokenGranter.java
  82. 157 0
      src/main/java/org/springblade/modules/auth/granter/RegisterTokenGranter.java
  83. 102 0
      src/main/java/org/springblade/modules/auth/granter/SmsTokenGranter.java
  84. 116 0
      src/main/java/org/springblade/modules/auth/granter/SocialTokenGranter.java
  85. 266 0
      src/main/java/org/springblade/modules/auth/handler/BladeAuthorizationHandler.java
  86. 64 0
      src/main/java/org/springblade/modules/auth/handler/BladePasswordHandler.java
  87. 34 0
      src/main/java/org/springblade/modules/auth/handler/BladeTokenHandler.java
  88. 67 0
      src/main/java/org/springblade/modules/auth/provider/UserType.java
  89. 37 0
      src/main/java/org/springblade/modules/auth/service/BladeClientDetailService.java
  90. 77 0
      src/main/java/org/springblade/modules/auth/service/BladeUserDetailService.java
  91. 86 0
      src/main/java/org/springblade/modules/auth/utils/TokenUtil.java
  92. 212 0
      src/main/java/org/springblade/modules/desk/controller/DashBoardController.java
  93. 155 0
      src/main/java/org/springblade/modules/desk/controller/NoticeController.java
  94. 59 0
      src/main/java/org/springblade/modules/desk/mapper/NoticeMapper.java
  95. 53 0
      src/main/java/org/springblade/modules/desk/mapper/NoticeMapper.xml
  96. 76 0
      src/main/java/org/springblade/modules/desk/pojo/entity/Notice.java
  97. 24 0
      src/main/java/org/springblade/modules/desk/pojo/vo/NoticeVO.java
  98. 48 0
      src/main/java/org/springblade/modules/desk/service/INoticeService.java
  99. 52 0
      src/main/java/org/springblade/modules/desk/service/impl/NoticeServiceImpl.java
  100. 73 0
      src/main/java/org/springblade/modules/desk/wrapper/NoticeWrapper.java

+ 17 - 0
Dockerfile

@@ -0,0 +1,17 @@
+# 网络问题无法下载可以改为阿里云镜像
+# FROM registry.cn-hangzhou.aliyuncs.com/bladex-repo/alpine-java:openjdk17_cn_slim
+FROM bladex/alpine-java:openjdk17_cn_slim
+
+LABEL maintainer="bladejava@qq.com"
+
+RUN mkdir -p /blade
+
+WORKDIR /blade
+
+EXPOSE 8800
+
+COPY ./target/blade-api.jar ./app.jar
+
+ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=test"]

+ 368 - 0
pom.xml

@@ -0,0 +1,368 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.springblade</groupId>
+    <artifactId>BladeX-Boot</artifactId>
+    <packaging>jar</packaging>
+    <version>4.3.0.RELEASE</version>
+
+    <properties>
+        <bladex.project.id>blade-api</bladex.project.id>
+        <bladex.project.version>4.3.0.RELEASE</bladex.project.version>
+
+        <java.version>17</java.version>
+        <maven.plugin.version>3.11.0</maven.plugin.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+        <!-- Docker仓库服务配置 -->
+        <docker.registry.url>192.168.0.188</docker.registry.url>
+        <docker.username>admin</docker.username>
+        <docker.password>admin12345</docker.password>
+        <docker.namespace>blade</docker.namespace>
+        <docker.fabric.version>0.42.0</docker.fabric.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- Blade -->
+            <dependency>
+                <groupId>org.springblade.platform</groupId>
+                <artifactId>blade-bom</artifactId>
+                <version>${bladex.project.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.springblade</groupId>
+                <artifactId>blade-core-boot</artifactId>
+                <version>${bladex.project.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.springblade</groupId>
+                        <artifactId>blade-core-cloud</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- ↓ BladeX依赖库 ↓ -->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-core-boot</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-core-oauth2</artifactId>
+        </dependency>
+        <!-- 多租户字段隔离模式 -->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-tenant</artifactId>
+        </dependency>
+        <!-- 多租户数据库隔离模式则引入此配置 -->
+        <!--<dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-tenant-dynamic</artifactId>
+        </dependency>-->
+        <!-- 集成sharding功能则引入此配置 -->
+        <!--<dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-sharding</artifactId>
+        </dependency>-->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-api-crypto</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-datascope</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-develop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-swagger</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-excel</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-social</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi3-ui</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-core-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- 报表 -->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-report</artifactId>
+        </dependency>
+        <!-- 验证码 -->
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+        </dependency>
+        <!-- Mybatis-Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+        </dependency>
+        <!--Oss-->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-oss</artifactId>
+        </dependency>
+        <!--Sms-->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-sms</artifactId>
+        </dependency>
+        <!--Aws S3-->
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-s3</artifactId>
+        </dependency>
+        <!--MinIO-->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+        </dependency>
+        <!--Alioss-->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+        </dependency>
+        <!--AliSms-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+        </dependency>
+        <!--华为云Obs-->
+        <dependency>
+            <groupId>com.huaweicloud</groupId>
+            <artifactId>esdk-obs-java</artifactId>
+        </dependency>
+        <!--腾讯COS-->
+        <dependency>
+            <groupId>com.qcloud</groupId>
+            <artifactId>cos_api</artifactId>
+        </dependency>
+        <!--腾讯SMS-->
+        <dependency>
+            <groupId>com.github.qcloudsms</groupId>
+            <artifactId>qcloudsms</artifactId>
+        </dependency>
+        <!--QiNiu-->
+        <dependency>
+            <groupId>com.qiniu</groupId>
+            <artifactId>qiniu-java-sdk</artifactId>
+        </dependency>
+        <!--YunPian-->
+        <dependency>
+            <groupId>com.yunpian.sdk</groupId>
+            <artifactId>yunpian-java-sdk</artifactId>
+        </dependency>
+        <!-- liteflow -->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-liteflow</artifactId>
+        </dependency>
+        <!-- Oracle -->
+        <!--<dependency>
+            <groupId>com.oracle</groupId>
+            <artifactId>ojdbc7</artifactId>
+        </dependency>-->
+        <!-- PostgreSql -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <!-- SqlServer -->
+        <!--<dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+        </dependency>-->
+        <!-- DaMeng -->
+        <!--<dependency>
+            <groupId>com.dameng</groupId>
+            <artifactId>DmJdbcDriver18</artifactId>
+        </dependency>-->
+        <!--YashanDB-->
+        <!--<dependency>
+            <groupId>com.yashandb.jdbc</groupId>
+            <artifactId>yasdb-jdbc</artifactId>
+        </dependency>-->
+        <!-- 任务调度 -->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-powerjob</artifactId>
+        </dependency>
+        <!-- 工作流 -->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-starter-flowable</artifactId>
+        </dependency>
+        <!-- auto -->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-core-auto</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- ↑ BladeX依赖库 ↑ -->
+
+        <!-- lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.39.282.ALL</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>${bladex.project.id}</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-maven-plugin</artifactId>
+                    <version>3.2.10</version>
+                    <configuration>
+                        <finalName>${project.build.finalName}</finalName>
+                        <excludes>
+                            <!-- 打包的 jar 中排除 lombok -->
+                            <exclude>
+                                <groupId>org.projectlombok</groupId>
+                                <artifactId>lombok</artifactId>
+                            </exclude>
+                        </excludes>
+                    </configuration>
+                    <executions>
+                        <execution>
+                            <goals>
+                                <goal>repackage</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+                </plugin>
+                <plugin>
+                    <groupId>io.fabric8</groupId>
+                    <artifactId>docker-maven-plugin</artifactId>
+                    <version>${docker.fabric.version}</version>
+                    <configuration>
+                        <authConfig>
+                            <username>${docker.username}</username>
+                            <password>${docker.password}</password>
+                        </authConfig>
+                        <registry>${docker.registry.url}</registry>
+                        <images>
+                            <image>
+                                <name>${docker.namespace}/${project.build.finalName}:${project.version}</name>
+                                <alias>${project.name}</alias>
+                                <build>
+                                    <dockerFile>${project.basedir}/Dockerfile</dockerFile>
+                                </build>
+                            </image>
+                        </images>
+                        <buildArgs>
+                            <JAR_FILE>${basedir}/target/${project.build.finalName}.jar</JAR_FILE>
+                        </buildArgs>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>${maven.plugin.version}</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>UTF-8</encoding>
+                    <compilerArgs>
+                        <arg>-parameters</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>aliyun-repos</id>
+            <name>Aliyun Public Repository</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>bladex</id>
+            <name>BladeX Release Repository</name>
+            <url>https://center.javablade.com/api/packages/blade/maven</url>
+        </repository>
+    </repositories>
+    <pluginRepositories>
+        <pluginRepository>
+            <id>aliyun-plugin</id>
+            <name>Aliyun Public Plugin</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+    <distributionManagement>
+        <repository>
+            <id>bladex</id>
+            <name>BladeX Release Repository</name>
+            <url>https://center.javablade.com/api/packages/blade/maven</url>
+        </repository>
+        <snapshotRepository>
+            <id>bladex</id>
+            <name>BladeX Snapshot Repository</name>
+            <url>https://center.javablade.com/api/packages/blade/maven</url>
+        </snapshotRepository>
+    </distributionManagement>
+
+</project>

+ 24 - 0
src/main/java/org/springblade/Application.java

@@ -0,0 +1,24 @@
+package org.springblade;
+
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.core.launch.BladeApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
+
+/**
+ * 启动器e
+ *
+ * @author Chill
+ */
+@EnableScheduling
+@EnableRedisHttpSession
+@SpringBootApplication
+public class Application {
+
+	public static void main(String[] args) {
+		BladeApplication.run(CommonConstant.APPLICATION_NAME, Application.class, args);
+	}
+
+}
+

+ 70 - 0
src/main/java/org/springblade/common/cache/CacheNames.java

@@ -0,0 +1,70 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.cache;
+
+import org.springblade.core.tool.utils.StringPool;
+
+/**
+ * 缓存名
+ *
+ * @author Chill
+ */
+public interface CacheNames {
+
+	/**
+	 * 返回拼接后的key
+	 *
+	 * @param cacheKey      缓存key
+	 * @param cacheKeyValue 缓存key值
+	 * @return tenantKey
+	 */
+	static String cacheKey(String cacheKey, String cacheKeyValue) {
+		return cacheKey.concat(cacheKeyValue);
+	}
+
+	/**
+	 * 返回租户格式的key
+	 *
+	 * @param tenantId      租户编号
+	 * @param cacheKey      缓存key
+	 * @param cacheKeyValue 缓存key值
+	 * @return tenantKey
+	 */
+	static String tenantKey(String tenantId, String cacheKey, String cacheKeyValue) {
+		return tenantId.concat(StringPool.COLON).concat(cacheKey).concat(cacheKeyValue);
+	}
+
+	/**
+	 * 验证码key
+	 */
+	String CAPTCHA_KEY = "blade:auth::blade:captcha:";
+
+	/**
+	 * 登录失败key
+	 */
+	String USER_FAIL_KEY = "blade:user::blade:fail:";
+
+}

+ 125 - 0
src/main/java/org/springblade/common/cache/DictBizCache.java

@@ -0,0 +1,125 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.cache;
+
+import org.springblade.common.enums.DictBizEnum;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.modules.system.pojo.entity.DictBiz;
+import org.springblade.modules.system.service.IDictBizService;
+
+import java.util.List;
+
+import static org.springblade.core.cache.constant.CacheConstant.DICT_CACHE;
+
+/**
+ * 业务字典缓存工具类
+ *
+ * @author Chill
+ */
+public class DictBizCache {
+
+	private static final String DICT_ID = "dictBiz:id";
+	private static final String DICT_VALUE = "dictBiz:value";
+	private static final String DICT_LIST = "dictBiz:list";
+
+	private static final IDictBizService dictService;
+
+	static {
+		dictService = SpringUtil.getBean(IDictBizService.class);
+	}
+
+	/**
+	 * 获取字典实体
+	 *
+	 * @param id 主键
+	 * @return DictBiz
+	 */
+	public static DictBiz getById(Long id) {
+		String keyPrefix = DICT_ID.concat(StringPool.DASH).concat(AuthUtil.getTenantId()).concat(StringPool.COLON);
+		return CacheUtil.get(DICT_CACHE, keyPrefix, id, () -> dictService.getById(id));
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号枚举
+	 * @param dictKey Integer型字典键
+	 * @return String
+	 */
+	public static String getValue(DictBizEnum code, Integer dictKey) {
+		return getValue(code.getName(), dictKey);
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号
+	 * @param dictKey Integer型字典键
+	 * @return String
+	 */
+	public static String getValue(String code, Integer dictKey) {
+		String keyPrefix = DICT_VALUE.concat(StringPool.DASH).concat(AuthUtil.getTenantId()).concat(StringPool.COLON);
+		return CacheUtil.get(DICT_CACHE, keyPrefix + code + StringPool.COLON, String.valueOf(dictKey), () -> dictService.getValue(code, String.valueOf(dictKey)));
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号枚举
+	 * @param dictKey String型字典键
+	 * @return String
+	 */
+	public static String getValue(DictBizEnum code, String dictKey) {
+		return getValue(code.getName(), dictKey);
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号
+	 * @param dictKey String型字典键
+	 * @return String
+	 */
+	public static String getValue(String code, String dictKey) {
+		String keyPrefix = DICT_VALUE.concat(StringPool.DASH).concat(AuthUtil.getTenantId()).concat(StringPool.COLON);
+		return CacheUtil.get(DICT_CACHE, keyPrefix + code + StringPool.COLON, dictKey, () -> dictService.getValue(code, dictKey));
+	}
+
+	/**
+	 * 获取字典集合
+	 *
+	 * @param code 字典编号
+	 * @return List<DictBiz>
+	 */
+	public static List<DictBiz> getList(String code) {
+		String keyPrefix = DICT_LIST.concat(StringPool.DASH).concat(AuthUtil.getTenantId()).concat(StringPool.COLON);
+		return CacheUtil.get(DICT_CACHE, keyPrefix, code, () -> dictService.getList(code));
+	}
+
+}

+ 152 - 0
src/main/java/org/springblade/common/cache/DictCache.java

@@ -0,0 +1,152 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.cache;
+
+import org.springblade.common.enums.DictEnum;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.modules.system.pojo.entity.Dict;
+import org.springblade.modules.system.service.IDictService;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.springblade.core.cache.constant.CacheConstant.DICT_CACHE;
+
+/**
+ * 字典缓存工具类
+ *
+ * @author Chill
+ */
+public class DictCache {
+
+	private static final String DICT_ID = "dict:id:";
+	private static final String DICT_KEY = "dict:key:";
+	private static final String DICT_VALUE = "dict:value:";
+	private static final String DICT_LIST = "dict:list:";
+
+	private static final Boolean TENANT_MODE = Boolean.FALSE;
+
+	private static final IDictService dictService;
+
+	static {
+		dictService = SpringUtil.getBean(IDictService.class);
+	}
+
+	/**
+	 * 获取字典实体
+	 *
+	 * @param id 主键
+	 * @return Dict
+	 */
+	public static Dict getById(Long id) {
+		return CacheUtil.get(DICT_CACHE, DICT_ID, id, () -> dictService.getById(id), TENANT_MODE);
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code      字典编号枚举
+	 * @param dictValue 字典值
+	 * @return String
+	 */
+	public static String getKey(DictEnum code, String dictValue) {
+		return getKey(code.getName(), dictValue);
+	}
+
+	/**
+	 * 获取字典键
+	 *
+	 * @param code      字典编号
+	 * @param dictValue 字典值
+	 * @return String
+	 */
+	public static String getKey(String code, String dictValue) {
+		return CacheUtil.get(DICT_CACHE, DICT_KEY + code + StringPool.COLON, dictValue, () -> {
+			List<Dict> list = getList(code);
+			Optional<String> key = list.stream().filter(
+				dict -> dict.getDictValue().equalsIgnoreCase(dictValue)
+			).map(Dict::getDictKey).findFirst();
+			return key.orElse(StringPool.EMPTY);
+		}, TENANT_MODE);
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号枚举
+	 * @param dictKey Integer型字典键
+	 * @return String
+	 */
+	public static String getValue(DictEnum code, Integer dictKey) {
+		return getValue(code.getName(), dictKey);
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号
+	 * @param dictKey Integer型字典键
+	 * @return String
+	 */
+	public static String getValue(String code, Integer dictKey) {
+		return CacheUtil.get(DICT_CACHE, DICT_VALUE + code + StringPool.COLON, String.valueOf(dictKey), () -> dictService.getValue(code, String.valueOf(dictKey)), TENANT_MODE);
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号枚举
+	 * @param dictKey String型字典键
+	 * @return String
+	 */
+	public static String getValue(DictEnum code, String dictKey) {
+		return getValue(code.getName(), dictKey);
+	}
+
+	/**
+	 * 获取字典值
+	 *
+	 * @param code    字典编号
+	 * @param dictKey String型字典键
+	 * @return String
+	 */
+	public static String getValue(String code, String dictKey) {
+		return CacheUtil.get(DICT_CACHE, DICT_VALUE + code + StringPool.COLON, dictKey, () -> dictService.getValue(code, dictKey), TENANT_MODE);
+	}
+
+	/**
+	 * 获取字典集合
+	 *
+	 * @param code 字典编号
+	 * @return List<Dict>
+	 */
+	public static List<Dict> getList(String code) {
+		return CacheUtil.get(DICT_CACHE, DICT_LIST, code, () -> dictService.getList(code), TENANT_MODE);
+	}
+
+}

+ 71 - 0
src/main/java/org/springblade/common/cache/ParamCache.java

@@ -0,0 +1,71 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.cache;
+
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.modules.system.pojo.entity.Param;
+import org.springblade.modules.system.service.IParamService;
+
+import static org.springblade.core.cache.constant.CacheConstant.PARAM_CACHE;
+
+/**
+ * 参数缓存工具类
+ *
+ * @author Chill
+ */
+public class ParamCache {
+
+	private static final String PARAM_ID = "param:id:";
+	private static final String PARAM_VALUE = "param:value:";
+
+	private static final IParamService paramService;
+
+	static {
+		paramService = SpringUtil.getBean(IParamService.class);
+	}
+
+	/**
+	 * 获取参数实体
+	 *
+	 * @param id 主键
+	 * @return Param
+	 */
+	public static Param getById(Long id) {
+		return CacheUtil.get(PARAM_CACHE, PARAM_ID, id, () -> paramService.getById(id));
+	}
+
+	/**
+	 * 获取参数配置
+	 *
+	 * @param paramKey 参数值
+	 * @return String
+	 */
+	public static String getValue(String paramKey) {
+		return CacheUtil.get(PARAM_CACHE, PARAM_VALUE, paramKey, () -> paramService.getValue(paramKey));
+	}
+
+}

+ 66 - 0
src/main/java/org/springblade/common/cache/RegionCache.java

@@ -0,0 +1,66 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.cache;
+
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.modules.system.pojo.entity.Region;
+import org.springblade.modules.system.service.IRegionService;
+
+import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
+
+/**
+ * 行政区划缓存工具类
+ *
+ * @author Chill
+ */
+public class RegionCache {
+	public static final String MAIN_CODE = "00";
+	public static final int PROVINCE_LEVEL = 1;
+	public static final int CITY_LEVEL = 2;
+	public static final int DISTRICT_LEVEL = 3;
+	public static final int TOWN_LEVEL = 4;
+	public static final int VILLAGE_LEVEL = 5;
+
+	private static final String REGION_CODE = "region:code:";
+
+	private static final IRegionService regionService;
+
+	static {
+		regionService = SpringUtil.getBean(IRegionService.class);
+	}
+
+	/**
+	 * 获取行政区划实体
+	 *
+	 * @param code 区划编号
+	 * @return Param
+	 */
+	public static Region getByCode(String code) {
+		return CacheUtil.get(SYS_CACHE, REGION_CODE, code, () -> regionService.getById(code));
+	}
+
+}

+ 334 - 0
src/main/java/org/springblade/common/cache/SysCache.java

@@ -0,0 +1,334 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.cache;
+
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.mp.enums.StatusType;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.modules.system.pojo.entity.*;
+import org.springblade.modules.system.service.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
+
+/**
+ * 系统缓存
+ *
+ * @author Chill
+ */
+public class SysCache {
+	private static final String MENU_ID = "menu:id:";
+	private static final String DEPT_ID = "dept:id:";
+	private static final String DEPT_NAME = "dept:name:";
+	private static final String DEPT_NAME_FUZZY = "dept:nameFuzzy:";
+	private static final String DEPT_NAME_ID = "deptName:id:";
+	private static final String DEPT_NAMES_ID = "deptNames:id:";
+	private static final String DEPT_CHILD_ID = "deptChild:id:";
+	private static final String DEPT_CHILDIDS_ID = "deptChildIds:id:";
+	private static final String POST_ID = "post:id:";
+	private static final String POST_NAME = "post:name:";
+	private static final String POST_NAME_FUZZY = "post:nameFuzzy:";
+	private static final String POST_NAME_ID = "postName:id:";
+	private static final String POST_NAMES_ID = "postNames:id:";
+	private static final String ROLE_ID = "role:id:";
+	private static final String ROLE_NAME = "role:name:";
+	private static final String ROLE_NAME_ID = "roleName:id:";
+	private static final String ROLE_NAMES_ID = "roleNames:id:";
+	private static final String ROLE_ALIAS_ID = "roleAlias:id:";
+	private static final String ROLE_ALIASES_ID = "roleAliases:id:";
+	public static final String TENANT_ID = "tenant:id:";
+	public static final String TENANT_TENANT_ID = "tenant:tenantId:";
+	public static final String TENANT_PACKAGE_ID = "tenant:packageId:";
+
+	private static final IMenuService menuService;
+	private static final IDeptService deptService;
+	private static final IPostService postService;
+	private static final IRoleService roleService;
+	private static final ITenantService tenantService;
+	private static final ITenantPackageService tenantPackageService;
+
+	static {
+		menuService = SpringUtil.getBean(IMenuService.class);
+		deptService = SpringUtil.getBean(IDeptService.class);
+		postService = SpringUtil.getBean(IPostService.class);
+		roleService = SpringUtil.getBean(IRoleService.class);
+		tenantService = SpringUtil.getBean(ITenantService.class);
+		tenantPackageService = SpringUtil.getBean(ITenantPackageService.class);
+	}
+
+	/**
+	 * 获取菜单
+	 *
+	 * @param id 主键
+	 * @return 菜单
+	 */
+	public static Menu getMenu(Long id) {
+		return CacheUtil.get(SYS_CACHE, MENU_ID, id, () -> menuService.getById(id));
+	}
+
+	/**
+	 * 获取部门
+	 *
+	 * @param id 主键
+	 * @return 部门
+	 */
+	public static Dept getDept(Long id) {
+		return CacheUtil.get(SYS_CACHE, DEPT_ID, id, () -> deptService.getById(id));
+	}
+
+	/**
+	 * 获取部门id
+	 *
+	 * @param tenantId  租户id
+	 * @param deptNames 部门名
+	 * @return 部门id
+	 */
+	public static String getDeptIds(String tenantId, String deptNames) {
+		return CacheUtil.get(SYS_CACHE, DEPT_NAME, tenantId + StringPool.DASH + deptNames, () -> deptService.getDeptIds(tenantId, deptNames));
+	}
+
+	/**
+	 * 获取部门id
+	 *
+	 * @param tenantId  租户id
+	 * @param deptNames 部门名模糊查询
+	 * @return 部门id
+	 */
+	public static String getDeptIdsByFuzzy(String tenantId, String deptNames) {
+		return CacheUtil.get(SYS_CACHE, DEPT_NAME_FUZZY, tenantId + StringPool.DASH + deptNames, () -> deptService.getDeptIdsByFuzzy(tenantId, deptNames));
+	}
+
+	/**
+	 * 获取部门名
+	 *
+	 * @param id 主键
+	 * @return 部门名
+	 */
+	public static String getDeptName(Long id) {
+		return CacheUtil.get(SYS_CACHE, DEPT_NAME_ID, id, () -> deptService.getById(id).getDeptName());
+	}
+
+
+	/**
+	 * 获取部门名集合
+	 *
+	 * @param deptIds 主键集合
+	 * @return 部门名
+	 */
+	public static List<String> getDeptNames(String deptIds) {
+		return CacheUtil.get(SYS_CACHE, DEPT_NAMES_ID, deptIds, () -> deptService.getDeptNames(deptIds));
+	}
+
+	/**
+	 * 获取子部门集合
+	 *
+	 * @param deptId 主键
+	 * @return 子部门
+	 */
+	public static List<Dept> getDeptChild(Long deptId) {
+		return CacheUtil.get(SYS_CACHE, DEPT_CHILD_ID, deptId, () -> deptService.getDeptChild(deptId));
+	}
+
+	/**
+	 * 获取子部门ID集合
+	 *
+	 * @param deptId 主键
+	 * @return 子部门ID
+	 */
+	public static List<Long> getDeptChildIds(Long deptId) {
+		if (deptId == null) {
+			return null;
+		}
+		List<Long> deptIdList = CacheUtil.get(SYS_CACHE, DEPT_CHILDIDS_ID, deptId, List.class);
+		if (deptIdList == null) {
+			deptIdList = new ArrayList<>();
+			List<Dept> deptChild = getDeptChild(deptId);
+			if (deptChild != null) {
+				List<Long> collect = deptChild.stream().map(Dept::getId).collect(Collectors.toList());
+				deptIdList.addAll(collect);
+			}
+			deptIdList.add(deptId);
+			CacheUtil.put(SYS_CACHE, DEPT_CHILDIDS_ID, deptId, deptIdList);
+		}
+		return deptIdList;
+	}
+
+	/**
+	 * 获取岗位
+	 *
+	 * @param id 主键
+	 * @return
+	 */
+	public static Post getPost(Long id) {
+		return CacheUtil.get(SYS_CACHE, POST_ID, id, () -> postService.getById(id));
+	}
+
+	/**
+	 * 获取岗位id
+	 *
+	 * @param tenantId  租户id
+	 * @param postNames 岗位名
+	 * @return
+	 */
+	public static String getPostIds(String tenantId, String postNames) {
+		return CacheUtil.get(SYS_CACHE, POST_NAME, tenantId + StringPool.DASH + postNames, () -> postService.getPostIds(tenantId, postNames));
+	}
+
+	/**
+	 * 获取岗位id
+	 *
+	 * @param tenantId  租户id
+	 * @param postNames 岗位名模糊查询
+	 * @return
+	 */
+	public static String getPostIdsByFuzzy(String tenantId, String postNames) {
+		return CacheUtil.get(SYS_CACHE, POST_NAME_FUZZY, tenantId + StringPool.DASH + postNames, () -> postService.getPostIdsByFuzzy(tenantId, postNames));
+	}
+
+	/**
+	 * 获取岗位名
+	 *
+	 * @param id 主键
+	 * @return 岗位名
+	 */
+	public static String getPostName(Long id) {
+		return CacheUtil.get(SYS_CACHE, POST_NAME_ID, id, () -> postService.getById(id).getPostName());
+	}
+
+	/**
+	 * 获取岗位名集合
+	 *
+	 * @param postIds 主键集合
+	 * @return 岗位名
+	 */
+	public static List<String> getPostNames(String postIds) {
+		return CacheUtil.get(SYS_CACHE, POST_NAMES_ID, postIds, () -> postService.getPostNames(postIds));
+	}
+
+	/**
+	 * 获取角色
+	 *
+	 * @param id 主键
+	 * @return Role
+	 */
+	public static Role getRole(Long id) {
+		return CacheUtil.get(SYS_CACHE, ROLE_ID, id, () -> roleService.getById(id));
+	}
+
+	/**
+	 * 获取角色id
+	 *
+	 * @param tenantId  租户id
+	 * @param roleNames 角色名
+	 * @return
+	 */
+	public static String getRoleIds(String tenantId, String roleNames) {
+		return CacheUtil.get(SYS_CACHE, ROLE_NAME, tenantId + StringPool.DASH + roleNames, () -> roleService.getRoleIds(tenantId, roleNames));
+	}
+
+	/**
+	 * 获取角色名
+	 *
+	 * @param id 主键
+	 * @return 角色名
+	 */
+	public static String getRoleName(Long id) {
+		return CacheUtil.get(SYS_CACHE, ROLE_NAME_ID, id, () -> roleService.getById(id).getRoleName());
+	}
+
+	/**
+	 * 获取角色名集合
+	 *
+	 * @param roleIds 主键集合
+	 * @return 角色名
+	 */
+	public static List<String> getRoleNames(String roleIds) {
+		return CacheUtil.get(SYS_CACHE, ROLE_NAMES_ID, roleIds, () -> roleService.getRoleNames(roleIds));
+	}
+
+	/**
+	 * 获取角色别名
+	 *
+	 * @param id 主键
+	 * @return 角色别名
+	 */
+	public static String getRoleAlias(Long id) {
+		return CacheUtil.get(SYS_CACHE, ROLE_ALIAS_ID, id, () -> roleService.getById(id).getRoleAlias());
+	}
+
+	/**
+	 * 获取角色别名集合
+	 *
+	 * @param roleIds 主键集合
+	 * @return 角色别名
+	 */
+	public static List<String> getRoleAliases(String roleIds) {
+		return CacheUtil.get(SYS_CACHE, ROLE_ALIASES_ID, roleIds, () -> roleService.getRoleAliases(roleIds));
+	}
+
+	/**
+	 * 获取租户
+	 *
+	 * @param id 主键
+	 * @return Tenant
+	 */
+	public static Tenant getTenant(Long id) {
+		return CacheUtil.get(SYS_CACHE, TENANT_ID, id, () -> Optional.ofNullable(tenantService.getById(id))
+			.filter(tenant -> tenant.getStatus() != null)
+			.filter(tenant -> tenant.getStatus() == StatusType.ACTIVE.getType())
+			.orElse(null), Boolean.FALSE);
+	}
+
+	/**
+	 * 获取租户
+	 *
+	 * @param tenantId 租户id
+	 * @return Tenant
+	 */
+	public static Tenant getTenant(String tenantId) {
+		return CacheUtil.get(SYS_CACHE, TENANT_TENANT_ID, tenantId, () -> Optional.ofNullable(tenantService.getByTenantId(tenantId))
+			.filter(tenant -> tenant.getStatus() != null)
+			.filter(tenant -> tenant.getStatus() == StatusType.ACTIVE.getType())
+			.orElse(null), Boolean.FALSE);
+	}
+
+	/**
+	 * 获取租户产品包
+	 *
+	 * @param tenantId 租户id
+	 * @return Tenant
+	 */
+	public static TenantPackage getTenantPackage(String tenantId) {
+		Tenant tenant = getTenant(tenantId);
+		return CacheUtil.get(SYS_CACHE, TENANT_PACKAGE_ID, tenantId, () -> tenantPackageService.getById(tenant.getPackageId()), Boolean.FALSE);
+	}
+
+}

+ 86 - 0
src/main/java/org/springblade/common/cache/UserCache.java

@@ -0,0 +1,86 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.cache;
+
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.modules.system.pojo.entity.User;
+import org.springblade.modules.system.service.IUserService;
+
+import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE;
+import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
+
+/**
+ * 系统缓存
+ *
+ * @author Chill
+ */
+public class UserCache {
+	private static final String USER_CACHE_ID = "user:id:";
+	private static final String USER_CACHE_ACCOUNT = "user:account:";
+
+	private static final IUserService userService;
+
+	static {
+		userService = SpringUtil.getBean(IUserService.class);
+	}
+
+	/**
+	 * 根据任务用户id获取用户信息
+	 *
+	 * @param taskUserId 任务用户id
+	 * @return
+	 */
+	public static User getUserByTaskUser(String taskUserId) {
+		Long userId = Func.toLong(StringUtil.removePrefix(taskUserId, TASK_USR_PREFIX));
+		return getUser(userId);
+	}
+
+	/**
+	 * 获取用户
+	 *
+	 * @param userId 用户id
+	 * @return
+	 */
+	public static User getUser(Long userId) {
+		return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> userService.getById(userId));
+	}
+
+	/**
+	 * 获取用户
+	 *
+	 * @param tenantId 租户id
+	 * @param account  账号名
+	 * @return
+	 */
+	public static User getUser(String tenantId, String account) {
+		return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, tenantId + StringPool.DASH + account, () -> userService.userByAccount(tenantId, account));
+	}
+
+}

+ 95 - 0
src/main/java/org/springblade/common/config/BladeConfiguration.java

@@ -0,0 +1,95 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.config;
+
+
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.oauth2.endpoint.OAuth2SocialEndpoint;
+import org.springblade.core.oauth2.endpoint.OAuth2TokenEndPoint;
+import org.springblade.core.secure.registry.SecureRegistry;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.modules.auth.endpoint.Oauth2SmsEndpoint;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Blade配置
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+public class BladeConfiguration implements WebMvcConfigurer {
+
+	/**
+	 * 安全框架配置
+	 */
+	@Bean
+	public SecureRegistry secureRegistry() {
+		SecureRegistry secureRegistry = new SecureRegistry();
+		secureRegistry.setEnabled(true);
+		secureRegistry.excludePathPatterns("/blade-auth/**");
+		secureRegistry.excludePathPatterns("/blade-system/tenant/info");
+		secureRegistry.excludePathPatterns("/blade-flow/process/resource-view");
+		secureRegistry.excludePathPatterns("/blade-flow/process/diagram-view");
+		secureRegistry.excludePathPatterns("/blade-flow/manager/check-upload");
+		secureRegistry.excludePathPatterns("/doc.html");
+		secureRegistry.excludePathPatterns("/swagger-ui.html");
+		secureRegistry.excludePathPatterns("/static/**");
+		secureRegistry.excludePathPatterns("/webjars/**");
+		secureRegistry.excludePathPatterns("/swagger-resources/**");
+		secureRegistry.excludePathPatterns("/druid/**");
+		return secureRegistry;
+	}
+
+	/**
+	 * 跨域配置
+	 */
+	@Override
+	public void addCorsMappings(CorsRegistry registry) {
+		registry.addMapping("/cors/**")
+			.allowedOriginPatterns("*")
+			.allowedHeaders("*")
+			.allowedMethods("*")
+			.maxAge(3600)
+			.allowCredentials(true);
+	}
+
+	/**
+	 * 给OAuth2服务端添加前缀
+	 */
+	@Override
+	public void configurePathMatch(PathMatchConfigurer configurer) {
+		configurer.addPathPrefix(StringPool.SLASH + AppConstant.APPLICATION_AUTH_NAME,
+			c -> c.isAnnotationPresent(RestController.class) && (
+				OAuth2TokenEndPoint.class.equals(c) || OAuth2SocialEndpoint.class.equals(c) || Oauth2SmsEndpoint.class.equals(c))
+		);
+	}
+
+}

+ 52 - 0
src/main/java/org/springblade/common/config/BladeHandlerConfiguration.java

@@ -0,0 +1,52 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+
+package org.springblade.common.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.common.handler.BladeScopeModelHandler;
+import org.springblade.core.datascope.handler.ScopeModelHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * Blade处理器自动配置
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+@AllArgsConstructor
+public class BladeHandlerConfiguration {
+
+	private final JdbcTemplate jdbcTemplate;
+
+	@Bean
+	public ScopeModelHandler scopeModelHandler() {
+		return new BladeScopeModelHandler(jdbcTemplate);
+	}
+
+}

+ 67 - 0
src/main/java/org/springblade/common/config/BladeLogConfiguration.java

@@ -0,0 +1,67 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+
+package org.springblade.common.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.common.event.ApiLogListener;
+import org.springblade.common.event.ErrorLogListener;
+import org.springblade.common.event.UsualLogListener;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.modules.system.service.ILogService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 日志工具自动配置
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+@AllArgsConstructor
+public class BladeLogConfiguration {
+
+	private final ILogService logService;
+	private final ServerInfo serverInfo;
+	private final BladeProperties bladeProperties;
+
+	@Bean(name = "apiLogListener")
+	public ApiLogListener apiLogListener() {
+		return new ApiLogListener(logService, serverInfo, bladeProperties);
+	}
+
+	@Bean(name = "errorEventListener")
+	public ErrorLogListener errorEventListener() {
+		return new ErrorLogListener(logService, serverInfo, bladeProperties);
+	}
+
+	@Bean(name = "usualEventListener")
+	public UsualLogListener usualEventListener() {
+		return new UsualLogListener(logService, serverInfo, bladeProperties);
+	}
+
+}

+ 26 - 0
src/main/java/org/springblade/common/config/BladePreviewConfiguration.java

@@ -0,0 +1,26 @@
+package org.springblade.common.config;
+
+import org.springblade.common.filter.PreviewFilter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 演示配置类
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnProperty(value = "blade.preview.enabled", havingValue = "true")
+public class BladePreviewConfiguration {
+
+	/**
+	 * 演示模式配置
+	 */
+	@Bean
+	public PreviewFilter previewFilter() {
+		return new PreviewFilter();
+	}
+
+
+}

+ 52 - 0
src/main/java/org/springblade/common/config/BladeReportConfiguration.java

@@ -0,0 +1,52 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.config;
+
+import org.springblade.core.report.datasource.ReportDataSource;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+/**
+ * 报表配置类
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnProperty(value = "report.enabled", havingValue = "true", matchIfMissing = true)
+public class BladeReportConfiguration {
+
+	/**
+	 * 自定义报表可选数据源
+	 */
+	@Bean
+	public ReportDataSource reportDataSource(DataSource dataSource) {
+		return new ReportDataSource(dataSource);
+	}
+
+}

+ 70 - 0
src/main/java/org/springblade/common/config/SwaggerConfiguration.java

@@ -0,0 +1,70 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Swagger配置类
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+@AllArgsConstructor
+@ConditionalOnProperty(value = "swagger.enabled", havingValue = "true", matchIfMissing = true)
+public class SwaggerConfiguration {
+
+	@Bean
+	public GroupedOpenApi authApi() {
+		return GroupedOpenApi.builder()
+			.group("授权模块")
+			.packagesToScan(AppConstant.BASE_PACKAGES + ".core.oauth2", AppConstant.BASE_PACKAGES + ".modules.auth")
+			.build();
+	}
+
+	@Bean
+	public GroupedOpenApi sysApi() {
+		return GroupedOpenApi.builder()
+			.group("系统模块")
+			.packagesToScan(AppConstant.BASE_PACKAGES + ".modules.system", AppConstant.BASE_PACKAGES + ".modules.resource")
+			.build();
+	}
+
+	@Bean
+	public GroupedOpenApi flowApi() {
+		// 创建并返回GroupedOpenApi对象
+		return GroupedOpenApi.builder()
+			.group("工作流模块")
+			.packagesToScan(AppConstant.BASE_PACKAGES + ".flow")
+			.build();
+	}
+
+}

+ 93 - 0
src/main/java/org/springblade/common/constant/CommonConstant.java

@@ -0,0 +1,93 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.constant;
+
+import org.springblade.core.launch.constant.AppConstant;
+
+/**
+ * 通用常量
+ *
+ * @author Chill
+ */
+public interface CommonConstant {
+
+	/**
+	 * app name
+	 */
+	String APPLICATION_NAME = AppConstant.APPLICATION_NAME_PREFIX + "api";
+
+	/**
+	 * sword 系统名
+	 */
+	String SWORD_NAME = "sword";
+
+	/**
+	 * saber 系统名
+	 */
+	String SABER_NAME = "saber";
+
+	/**
+	 * 顶级父节点id
+	 */
+	Long TOP_PARENT_ID = 0L;
+
+	/**
+	 * 顶级父节点名称
+	 */
+	String TOP_PARENT_NAME = "顶级";
+
+	/**
+	 * 未封存状态值
+	 */
+	Integer NOT_SEALED_ID = 0;
+
+	/**
+	 * 默认密码
+	 */
+	String DEFAULT_PASSWORD = "123456";
+
+	/**
+	 * 默认密码参数值
+	 */
+	String DEFAULT_PARAM_PASSWORD = "account.initPassword";
+
+	/**
+	 * 默认排序字段
+	 */
+	String SORT_FIELD = "sort";
+
+	/**
+	 * 数据权限类型
+	 */
+	Integer DATA_SCOPE_CATEGORY = 1;
+
+	/**
+	 * 接口权限类型
+	 */
+	Integer API_SCOPE_CATEGORY = 2;
+
+
+}

+ 47 - 0
src/main/java/org/springblade/common/constant/DictConstant.java

@@ -0,0 +1,47 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.constant;
+
+/**
+ * 字典常量.
+ *
+ * @author zhuangqian
+ */
+public interface DictConstant {
+
+	String SEX_CODE = "sex";
+
+	String NOTICE_CODE = "notice";
+
+	String MENU_CATEGORY_CODE = "menu_category";
+
+	String BUTTON_FUNC_CODE = "button_func";
+
+	String YES_NO_CODE = "yes_no";
+
+	String FLOW_CATEGORY_CODE = "flow_category";
+
+}

+ 96 - 0
src/main/java/org/springblade/common/constant/LauncherConstant.java

@@ -0,0 +1,96 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.constant;
+
+import org.springblade.core.launch.constant.AppConstant;
+
+/**
+ * 启动常量
+ *
+ * @author Chill
+ */
+public interface LauncherConstant {
+
+	/**
+	 * sentinel dev 地址
+	 */
+	String SENTINEL_DEV_ADDR = "127.0.0.1:8858";
+
+	/**
+	 * sentinel prod 地址
+	 */
+	String SENTINEL_PROD_ADDR = "10.211.55.5:8858";
+
+	/**
+	 * sentinel test 地址
+	 */
+	String SENTINEL_TEST_ADDR = "172.30.0.58:8858";
+
+	/**
+	 * elk dev 地址
+	 */
+	String ELK_DEV_ADDR = "127.0.0.1:9000";
+
+	/**
+	 * elk prod 地址
+	 */
+	String ELK_PROD_ADDR = "172.30.0.58:9000";
+
+	/**
+	 * elk test 地址
+	 */
+	String ELK_TEST_ADDR = "172.30.0.58:9000";
+
+	/**
+	 * 动态获取sentinel地址
+	 *
+	 * @param profile 环境变量
+	 * @return addr
+	 */
+	static String sentinelAddr(String profile) {
+		return switch (profile) {
+			case (AppConstant.PROD_CODE) -> SENTINEL_PROD_ADDR;
+			case (AppConstant.TEST_CODE) -> SENTINEL_TEST_ADDR;
+			default -> SENTINEL_DEV_ADDR;
+		};
+	}
+
+	/**
+	 * 动态获取elk地址
+	 *
+	 * @param profile 环境变量
+	 * @return addr
+	 */
+	static String elkAddr(String profile) {
+		return switch (profile) {
+			case (AppConstant.PROD_CODE) -> ELK_PROD_ADDR;
+			case (AppConstant.TEST_CODE) -> ELK_TEST_ADDR;
+			default -> ELK_DEV_ADDR;
+		};
+	}
+
+
+}

+ 75 - 0
src/main/java/org/springblade/common/constant/TenantConstant.java

@@ -0,0 +1,75 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.constant;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 租户常量
+ *
+ * @author Chill
+ */
+public interface TenantConstant {
+
+	/**
+	 * 租户默认密码KEY
+	 */
+	String PASSWORD_KEY = "tenant.default.password";
+
+	/**
+	 * 租户默认账号额度KEY
+	 */
+	String ACCOUNT_NUMBER_KEY = "tenant.default.accountNumber";
+
+	/**
+	 * 租户默认菜单集合KEY
+	 */
+	String ACCOUNT_MENU_CODE_KEY = "tenant.default.menuCode";
+
+	/**
+	 * 租户默认密码
+	 */
+	String DEFAULT_PASSWORD = "123456";
+
+	/**
+	 * 租户授权码默认16位密钥
+	 */
+	String DES_KEY = "0000000000000000";
+
+	/**
+	 * 租户默认账号额度
+	 */
+	Integer DEFAULT_ACCOUNT_NUMBER = -1;
+
+	/**
+	 * 租户默认菜单集合
+	 */
+	List<String> MENU_CODES = Arrays.asList(
+		"desk", "flow", "work", "monitor", "resource", "role", "user", "dept", "dictbiz", "topmenu"
+	);
+
+}

+ 48 - 0
src/main/java/org/springblade/common/enums/DictBizEnum.java

@@ -0,0 +1,48 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 业务字典枚举类
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum DictBizEnum {
+
+	/**
+	 * 测试
+	 */
+	TEST("test"),
+	;
+
+	final String name;
+
+}

+ 104 - 0
src/main/java/org/springblade/common/enums/DictEnum.java

@@ -0,0 +1,104 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 系统字典枚举类
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum DictEnum {
+
+	/**
+	 * 性别
+	 */
+	SEX("sex"),
+	/**
+	 * 通知类型
+	 */
+	NOTICE("notice"),
+	/**
+	 * 菜单类型
+	 */
+	MENU_CATEGORY("menu_category"),
+	/**
+	 * 按钮功能
+	 */
+	BUTTON_FUNC("button_func"),
+	/**
+	 * 是否
+	 */
+	YES_NO("yes_no"),
+	/**
+	 * 流程类型
+	 */
+	FLOW("flow"),
+	/**
+	 * 机构类型
+	 */
+	ORG_CATEGORY("org_category"),
+	/**
+	 * 数据权限
+	 */
+	DATA_SCOPE_TYPE("data_scope_type"),
+	/**
+	 * 接口权限
+	 */
+	API_SCOPE_TYPE("api_scope_type"),
+	/**
+	 * 权限类型
+	 */
+	SCOPE_CATEGORY("scope_category"),
+	/**
+	 * 对象存储类型
+	 */
+	OSS("oss"),
+	/**
+	 * 短信服务类型
+	 */
+	SMS("sms"),
+	/**
+	 * 岗位类型
+	 */
+	POST_CATEGORY("post_category"),
+	/**
+	 * 行政区划
+	 */
+	REGION("region"),
+	/**
+	 * 用户平台
+	 */
+	USER_TYPE("user_type"),
+	;
+
+	final String name;
+
+}

+ 69 - 0
src/main/java/org/springblade/common/event/ApiLogListener.java

@@ -0,0 +1,69 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+
+package org.springblade.common.event;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.event.ApiLogEvent;
+import org.springblade.core.log.model.LogApi;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springblade.modules.system.service.ILogService;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+
+/**
+ * 异步监听日志事件
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class ApiLogListener {
+
+	private final ILogService logService;
+	private final ServerInfo serverInfo;
+	private final BladeProperties bladeProperties;
+
+
+	@Async
+	@Order
+	@EventListener(ApiLogEvent.class)
+	public void saveApiLog(ApiLogEvent event) {
+		Map<String, Object> source = (Map<String, Object>) event.getSource();
+		LogApi logApi = (LogApi) source.get(EventConstant.EVENT_LOG);
+		LogAbstractUtil.addOtherInfoToLog(logApi, bladeProperties, serverInfo);
+		logService.saveApiLog(logApi);
+	}
+
+}

+ 71 - 0
src/main/java/org/springblade/common/event/ErrorLogListener.java

@@ -0,0 +1,71 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.event;
+
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.event.ErrorLogEvent;
+import org.springblade.core.log.model.LogError;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springblade.modules.system.service.ILogService;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+/**
+ * 异步监听错误日志事件
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class ErrorLogListener {
+
+	private final ILogService logService;
+	private final ServerInfo serverInfo;
+	private final BladeProperties bladeProperties;
+
+	@Async
+	@Order
+	@EventListener(ErrorLogEvent.class)
+	public void saveErrorLog(ErrorLogEvent event) {
+		try {
+			Map<String, Object> source = (Map<String, Object>) event.getSource();
+			LogError logError = (LogError) source.get(EventConstant.EVENT_LOG);
+			LogAbstractUtil.addOtherInfoToLog(logError, bladeProperties, serverInfo);
+			logService.saveErrorLog(logError);
+		} catch (Exception e) {
+			// 可以根据需要进行更多的异常处理,例如发送警报等
+			log.error("保存错误日志时发生异常", e);
+		}
+	}
+}

+ 67 - 0
src/main/java/org/springblade/common/event/UsualLogListener.java

@@ -0,0 +1,67 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.event;
+
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.event.UsualLogEvent;
+import org.springblade.core.log.model.LogUsual;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springblade.modules.system.service.ILogService;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+/**
+ * 异步监听日志事件
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class UsualLogListener {
+
+	private final ILogService logService;
+	private final ServerInfo serverInfo;
+	private final BladeProperties bladeProperties;
+
+	@Async
+	@Order
+	@EventListener(UsualLogEvent.class)
+	public void saveUsualLog(UsualLogEvent event) {
+		Map<String, Object> source = (Map<String, Object>) event.getSource();
+		LogUsual logUsual = (LogUsual) source.get(EventConstant.EVENT_LOG);
+		LogAbstractUtil.addOtherInfoToLog(logUsual, bladeProperties, serverInfo);
+		logService.saveUsualLog(logUsual);
+	}
+
+}

+ 77 - 0
src/main/java/org/springblade/common/filter/PreviewFilter.java

@@ -0,0 +1,77 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.filter;
+
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springblade.core.log.exception.ServiceException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 演示过滤器
+ *
+ * @author Chill
+ */
+public class PreviewFilter implements Filter {
+
+	private static final List<String> KEYS = new ArrayList<>();
+
+	static {
+		KEYS.add("notice");
+		KEYS.add("process");
+		KEYS.add("work");
+		KEYS.add("token");
+	}
+
+
+	@Override
+	public void init(FilterConfig filterConfig) {
+	}
+
+
+	@Override
+	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+
+		HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
+		String path = httpServletRequest.getServletPath();
+		String method = httpServletRequest.getMethod();
+
+		String get = "GET";
+		if (method.equals(get) || KEYS.stream().anyMatch(path::contains)) {
+			filterChain.doFilter(servletRequest, servletResponse);
+		} else {
+			throw new ServiceException("演示环境暂时无法操作!");
+		}
+
+	}
+
+	@Override
+	public void destroy() {
+	}
+}

+ 130 - 0
src/main/java/org/springblade/common/handler/BladeScopeModelHandler.java

@@ -0,0 +1,130 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.datascope.constant.DataScopeConstant;
+import org.springblade.core.datascope.handler.ScopeModelHandler;
+import org.springblade.core.datascope.model.DataScopeModel;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
+
+/**
+ * BladeScopeModelHandler
+ *
+ * @author Chill
+ */
+//若开启动态数据源功能,则加上@Master注解指定权限数据库为主库
+//@Master
+@RequiredArgsConstructor
+public class BladeScopeModelHandler implements ScopeModelHandler {
+
+	private static final String SCOPE_CACHE_CODE = "dataScope:code:";
+	private static final String SCOPE_CACHE_CLASS = "dataScope:class:";
+	private static final String DEPT_CACHE_ANCESTORS = "dept:ancestors:";
+	private static final DataScopeModel SEARCHED_DATA_SCOPE_MODEL = new DataScopeModel(Boolean.TRUE);
+
+	private final JdbcTemplate jdbcTemplate;
+
+	/**
+	 * 获取数据权限
+	 *
+	 * @param mapperId 数据权限mapperId
+	 * @param roleId   用户角色集合
+	 * @return DataScopeModel
+	 */
+	@Override
+	public DataScopeModel getDataScopeByMapper(String mapperId, String roleId) {
+		List<Object> args = new ArrayList<>(Collections.singletonList(mapperId));
+		List<Long> roleIds = Func.toLongList(roleId);
+		args.addAll(roleIds);
+		// 增加searched字段防止未配置的参数重复读库导致缓存击穿
+		// 后续若有新增配置则会清空缓存重新加载
+		DataScopeModel dataScope = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CLASS, mapperId + StringPool.COLON + roleId, DataScopeModel.class, Boolean.FALSE);
+		if (dataScope == null || !dataScope.getSearched()) {
+			List<DataScopeModel> list = jdbcTemplate.query(DataScopeConstant.dataByMapper(roleIds.size()), args.toArray(), new BeanPropertyRowMapper<>(DataScopeModel.class));
+			if (CollectionUtil.isNotEmpty(list)) {
+				dataScope = list.iterator().next();
+				dataScope.setSearched(Boolean.TRUE);
+			} else {
+				dataScope = SEARCHED_DATA_SCOPE_MODEL;
+			}
+			CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CLASS, mapperId + StringPool.COLON + roleId, dataScope, Boolean.FALSE);
+		}
+		return StringUtil.isNotBlank(dataScope.getResourceCode()) ? dataScope : null;
+	}
+
+	/**
+	 * 获取数据权限
+	 *
+	 * @param code 数据权限资源编号
+	 * @return DataScopeModel
+	 */
+	@Override
+	public DataScopeModel getDataScopeByCode(String code) {
+		DataScopeModel dataScope = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CODE, code, DataScopeModel.class, Boolean.FALSE);
+		// 增加searched字段防止未配置的参数重复读库导致缓存击穿
+		// 后续若有新增配置则会清空缓存重新加载
+		if (dataScope == null || !dataScope.getSearched()) {
+			List<DataScopeModel> list = jdbcTemplate.query(DataScopeConstant.DATA_BY_CODE, new Object[]{code}, new BeanPropertyRowMapper<>(DataScopeModel.class));
+			if (CollectionUtil.isNotEmpty(list)) {
+				dataScope = list.iterator().next();
+				dataScope.setSearched(Boolean.TRUE);
+			} else {
+				dataScope = SEARCHED_DATA_SCOPE_MODEL;
+			}
+			CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CODE, code, dataScope, Boolean.FALSE);
+		}
+		return StringUtil.isNotBlank(dataScope.getResourceCode()) ? dataScope : null;
+	}
+
+	/**
+	 * 获取部门子级
+	 *
+	 * @param deptId 部门id
+	 * @return deptIds
+	 */
+	@Override
+	public List<Long> getDeptAncestors(Long deptId) {
+		List ancestors = CacheUtil.get(SYS_CACHE, DEPT_CACHE_ANCESTORS, deptId, List.class);
+		if (CollectionUtil.isEmpty(ancestors)) {
+			ancestors = jdbcTemplate.queryForList(DataScopeConstant.DATA_BY_DEPT, new Object[]{deptId}, Long.class);
+			CacheUtil.put(SYS_CACHE, DEPT_CACHE_ANCESTORS, deptId, ancestors);
+		}
+		return ancestors;
+	}
+}

+ 53 - 0
src/main/java/org/springblade/common/launch/LauncherServiceImpl.java

@@ -0,0 +1,53 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.launch;
+
+import org.springblade.common.constant.LauncherConstant;
+import org.springblade.core.auto.service.AutoService;
+import org.springblade.core.launch.service.LauncherService;
+import org.springblade.core.launch.utils.PropsUtil;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+import java.util.Properties;
+
+/**
+ * 启动参数拓展
+ *
+ * @author smallchil
+ */
+@AutoService(LauncherService.class)
+public class LauncherServiceImpl implements LauncherService {
+
+	@Override
+	public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
+		Properties props = System.getProperties();
+		PropsUtil.setProperty(props, "spring.cloud.sentinel.transport.dashboard", LauncherConstant.sentinelAddr(profile));
+		PropsUtil.setProperty(props, "spring.datasource.dynamic.enabled", "false");
+		// 开启elk日志
+		//PropsUtil.setProperty(props, "blade.log.elk.destination", LauncherConstant.elkAddr(profile));
+	}
+
+}

+ 35 - 0
src/main/java/org/springblade/common/utils/CommonUtil.java

@@ -0,0 +1,35 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.utils;
+
+/**
+ * 通用工具类
+ *
+ * @author Chill
+ */
+public class CommonUtil {
+
+}

+ 155 - 0
src/main/java/org/springblade/flow/business/controller/WorkController.java

@@ -0,0 +1,155 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.business.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.flowable.engine.TaskService;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.flow.business.service.FlowBusinessService;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 流程事务通用接口
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/work")
+@Tag(name = "流程事务通用接口", description = "流程事务通用接口")
+public class WorkController {
+
+	private final TaskService taskService;
+	private final FlowEngineService flowEngineService;
+	private final FlowBusinessService flowBusinessService;
+
+	/**
+	 * 发起事务列表页
+	 */
+	@GetMapping("start-list")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "发起事务列表页", description = "传入流程类型")
+	public R<IPage<FlowProcess>> startList(@Parameter(description = "流程类型") String category, Query query, @RequestParam(required = false, defaultValue = "1") Integer mode) {
+		IPage<FlowProcess> pages = flowEngineService.selectProcessPage(Condition.getPage(query), category, mode);
+		return R.data(pages);
+	}
+
+	/**
+	 * 待签事务列表页
+	 */
+	@GetMapping("claim-list")
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "待签事务列表页", description = "传入流程信息")
+	public R<IPage<BladeFlow>> claimList(@Parameter(description = "流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectClaimPage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 待办事务列表页
+	 */
+	@GetMapping("todo-list")
+	@ApiOperationSupport(order = 3)
+	@Operation(summary = "待办事务列表页", description = "传入流程信息")
+	public R<IPage<BladeFlow>> todoList(@Parameter(description = "流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectTodoPage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 已发事务列表页
+	 */
+	@GetMapping("send-list")
+	@ApiOperationSupport(order = 4)
+	@Operation(summary = "已发事务列表页", description = "传入流程信息")
+	public R<IPage<BladeFlow>> sendList(@Parameter(description = "流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectSendPage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 办结事务列表页
+	 */
+	@GetMapping("done-list")
+	@ApiOperationSupport(order = 5)
+	@Operation(summary = "办结事务列表页", description = "传入流程信息")
+	public R<IPage<BladeFlow>> doneList(@Parameter(description = "流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectDonePage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 签收事务
+	 *
+	 * @param taskId 任务id
+	 */
+	@PostMapping("claim-task")
+	@ApiOperationSupport(order = 6)
+	@Operation(summary = "签收事务", description = "传入流程信息")
+	public R claimTask(@Parameter(description = "任务id") String taskId) {
+		taskService.claim(taskId, TaskUtil.getTaskUser());
+		return R.success("签收事务成功");
+	}
+
+	/**
+	 * 完成任务
+	 *
+	 * @param flow 请假信息
+	 */
+	@PostMapping("complete-task")
+	@ApiOperationSupport(order = 7)
+	@Operation(summary = "完成任务", description = "传入流程信息")
+	public R completeTask(@Parameter(description = "任务信息") @RequestBody BladeFlow flow) {
+		return R.status(flowBusinessService.completeTask(flow));
+	}
+
+	/**
+	 * 删除任务
+	 *
+	 * @param taskId 任务id
+	 * @param reason 删除原因
+	 */
+	@PostMapping("delete-task")
+	@ApiOperationSupport(order = 8)
+	@Operation(summary = "删除任务", description = "传入流程信息")
+	public R deleteTask(@Parameter(description = "任务id") String taskId, @Parameter(description = "删除原因") String reason) {
+		taskService.deleteTask(taskId, reason);
+		return R.success("删除任务成功");
+	}
+
+}

+ 81 - 0
src/main/java/org/springblade/flow/business/service/FlowBusinessService.java

@@ -0,0 +1,81 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.business.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.flow.core.entity.BladeFlow;
+
+/**
+ * 流程业务类
+ *
+ * @author Chill
+ */
+public interface FlowBusinessService {
+
+	/**
+	 * 流程待签列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectClaimPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 流程待办列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectTodoPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 流程已发列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectSendPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 流程办结列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectDonePage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 完成任务
+	 *
+	 * @param leave 请假信息
+	 * @return boolean
+	 */
+	boolean completeTask(BladeFlow leave);
+}

+ 86 - 0
src/main/java/org/springblade/flow/business/service/IFlowService.java

@@ -0,0 +1,86 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.business.service;
+
+import org.springblade.flow.core.entity.BladeFlow;
+
+import java.util.Map;
+
+/**
+ * 工作流调用接口.
+ *
+ * @author Chill
+ */
+public interface IFlowService {
+
+	/**
+	 * 开启流程
+	 *
+	 * @param processDefinitionId 流程id
+	 * @param businessKey         业务key
+	 * @param variables           参数
+	 * @return BladeFlow
+	 */
+	BladeFlow startProcessInstanceById(String processDefinitionId, String businessKey, Map<String, Object> variables);
+
+	/**
+	 * 开启流程
+	 *
+	 * @param processDefinitionKey 流程标识
+	 * @param businessKey          业务key
+	 * @param variables            参数
+	 * @return BladeFlow
+	 */
+	BladeFlow startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables);
+
+	/**
+	 * 完成任务
+	 *
+	 * @param taskId            任务id
+	 * @param processInstanceId 流程实例id
+	 * @param comment           评论
+	 * @param variables         参数
+	 * @return R
+	 */
+	boolean completeTask(String taskId, String processInstanceId, String comment, Map<String, Object> variables);
+
+	/**
+	 * 获取流程变量
+	 *
+	 * @param taskId       任务id
+	 * @param variableName 变量名
+	 * @return R
+	 */
+	Object taskVariable(String taskId, String variableName);
+
+	/**
+	 * 获取流程变量集合
+	 *
+	 * @param taskId 任务id
+	 * @return R
+	 */
+	Map<String, Object> taskVariables(String taskId);
+}

+ 342 - 0
src/main/java/org/springblade/flow/business/service/impl/FlowBusinessServiceImpl.java

@@ -0,0 +1,342 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.business.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.AllArgsConstructor;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.history.HistoricProcessInstanceQuery;
+import org.flowable.task.api.TaskQuery;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.flowable.task.api.history.HistoricTaskInstanceQuery;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.business.service.FlowBusinessService;
+import org.springblade.flow.core.constant.ProcessConstant;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springblade.flow.engine.constant.FlowEngineConstant;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springblade.flow.engine.utils.FlowCache;
+import org.springframework.stereotype.Service;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 流程业务实现类
+ *
+ * @author Chill
+ */
+@Service
+@AllArgsConstructor
+public class FlowBusinessServiceImpl implements FlowBusinessService {
+
+	private final TaskService taskService;
+	private final HistoryService historyService;
+
+	@Override
+	public IPage<BladeFlow> selectClaimPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		String taskGroup = TaskUtil.getCandidateGroup();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		// 个人等待签收的任务
+		TaskQuery claimUserQuery = taskService.createTaskQuery().taskCandidateUser(taskUser)
+			.includeProcessVariables().active().orderByTaskCreateTime().desc();
+		// 定制流程等待签收的任务
+		TaskQuery claimRoleWithTenantIdQuery = taskService.createTaskQuery().taskTenantId(AuthUtil.getTenantId()).taskCandidateGroupIn(Func.toStrList(taskGroup))
+			.includeProcessVariables().active().orderByTaskCreateTime().desc();
+		// 通用流程等待签收的任务
+		TaskQuery claimRoleWithoutTenantIdQuery = taskService.createTaskQuery().taskWithoutTenantId().taskCandidateGroupIn(Func.toStrList(taskGroup))
+			.includeProcessVariables().active().orderByTaskCreateTime().desc();
+
+		// 构建列表数据
+		buildFlowTaskList(bladeFlow, flowList, claimUserQuery, FlowEngineConstant.STATUS_CLAIM);
+		buildFlowTaskList(bladeFlow, flowList, claimRoleWithTenantIdQuery, FlowEngineConstant.STATUS_CLAIM);
+		buildFlowTaskList(bladeFlow, flowList, claimRoleWithoutTenantIdQuery, FlowEngineConstant.STATUS_CLAIM);
+
+		// 计算总数
+		long count = claimUserQuery.count() + claimRoleWithTenantIdQuery.count() + claimRoleWithoutTenantIdQuery.count();
+		// 设置页数
+		page.setSize(count);
+		// 设置总数
+		page.setTotal(count);
+		// 设置数据
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public IPage<BladeFlow> selectTodoPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		// 已签收的任务
+		TaskQuery todoQuery = taskService.createTaskQuery().taskAssignee(taskUser).active()
+			.includeProcessVariables().orderByTaskCreateTime().desc();
+
+		// 构建列表数据
+		buildFlowTaskList(bladeFlow, flowList, todoQuery, FlowEngineConstant.STATUS_TODO);
+
+		// 计算总数
+		long count = todoQuery.count();
+		// 设置页数
+		page.setSize(count);
+		// 设置总数
+		page.setTotal(count);
+		// 设置数据
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public IPage<BladeFlow> selectSendPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		HistoricProcessInstanceQuery historyQuery = historyService.createHistoricProcessInstanceQuery().startedBy(taskUser).orderByProcessInstanceStartTime().desc();
+
+		if (bladeFlow.getCategory() != null) {
+			historyQuery.processDefinitionCategory(bladeFlow.getCategory());
+		}
+		if (bladeFlow.getProcessDefinitionName() != null) {
+			historyQuery.processDefinitionName(bladeFlow.getProcessDefinitionName());
+		}
+		if (bladeFlow.getBeginDate() != null) {
+			historyQuery.startedAfter(bladeFlow.getBeginDate());
+		}
+		if (bladeFlow.getEndDate() != null) {
+			historyQuery.startedBefore(bladeFlow.getEndDate());
+		}
+
+		// 查询列表
+		List<HistoricProcessInstance> historyList = historyQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
+
+		historyList.forEach(historicProcessInstance -> {
+			BladeFlow flow = new BladeFlow();
+			// historicProcessInstance
+			flow.setCreateTime(historicProcessInstance.getStartTime());
+			flow.setEndTime(historicProcessInstance.getEndTime());
+			flow.setVariables(historicProcessInstance.getProcessVariables());
+			String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
+			if (businessKey.length > 1) {
+				flow.setBusinessTable(businessKey[0]);
+				flow.setBusinessId(businessKey[1]);
+			}
+			flow.setHistoryActivityName(historicProcessInstance.getName());
+			flow.setProcessInstanceId(historicProcessInstance.getId());
+			flow.setHistoryProcessInstanceId(historicProcessInstance.getId());
+			// ProcessDefinition
+			FlowProcess processDefinition = FlowCache.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
+			flow.setProcessDefinitionId(processDefinition.getId());
+			flow.setProcessDefinitionName(processDefinition.getName());
+			flow.setProcessDefinitionVersion(processDefinition.getVersion());
+			flow.setProcessDefinitionKey(processDefinition.getKey());
+			flow.setCategory(processDefinition.getCategory());
+			flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+			flow.setProcessInstanceId(historicProcessInstance.getId());
+			// HistoricTaskInstance
+			List<HistoricTaskInstance> historyTasks = historyService.createHistoricTaskInstanceQuery().processInstanceId(historicProcessInstance.getId()).orderByHistoricTaskInstanceEndTime().desc().list();
+			if (Func.isNotEmpty(historyTasks)) {
+				HistoricTaskInstance historyTask = historyTasks.iterator().next();
+				flow.setTaskId(historyTask.getId());
+				flow.setTaskName(historyTask.getName());
+				flow.setTaskDefinitionKey(historyTask.getTaskDefinitionKey());
+			}
+			// Status
+			if (historicProcessInstance.getEndActivityId() != null) {
+				flow.setProcessIsFinished(FlowEngineConstant.STATUS_FINISHED);
+			} else {
+				flow.setProcessIsFinished(FlowEngineConstant.STATUS_UNFINISHED);
+			}
+			flow.setStatus(FlowEngineConstant.STATUS_FINISH);
+			flowList.add(flow);
+		});
+
+		// 计算总数
+		long count = historyQuery.count();
+		// 设置总数
+		page.setTotal(count);
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public IPage<BladeFlow> selectDonePage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		HistoricTaskInstanceQuery doneQuery = historyService.createHistoricTaskInstanceQuery().taskAssignee(taskUser).finished()
+			.includeProcessVariables().orderByHistoricTaskInstanceEndTime().desc();
+
+		if (bladeFlow.getCategory() != null) {
+			doneQuery.processCategoryIn(Func.toStrList(bladeFlow.getCategory()));
+		}
+		if (bladeFlow.getProcessDefinitionName() != null) {
+			doneQuery.processDefinitionName(bladeFlow.getProcessDefinitionName());
+		}
+		if (bladeFlow.getBeginDate() != null) {
+			doneQuery.taskCompletedAfter(bladeFlow.getBeginDate());
+		}
+		if (bladeFlow.getEndDate() != null) {
+			doneQuery.taskCompletedBefore(bladeFlow.getEndDate());
+		}
+
+		// 查询列表
+		List<HistoricTaskInstance> doneList = doneQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
+		doneList.forEach(historicTaskInstance -> {
+			BladeFlow flow = new BladeFlow();
+			flow.setTaskId(historicTaskInstance.getId());
+			flow.setTaskDefinitionKey(historicTaskInstance.getTaskDefinitionKey());
+			flow.setTaskName(historicTaskInstance.getName());
+			flow.setAssignee(historicTaskInstance.getAssignee());
+			flow.setCreateTime(historicTaskInstance.getCreateTime());
+			flow.setExecutionId(historicTaskInstance.getExecutionId());
+			flow.setHistoryTaskEndTime(historicTaskInstance.getEndTime());
+			flow.setVariables(historicTaskInstance.getProcessVariables());
+
+			FlowProcess processDefinition = FlowCache.getProcessDefinition(historicTaskInstance.getProcessDefinitionId());
+			flow.setProcessDefinitionId(processDefinition.getId());
+			flow.setProcessDefinitionName(processDefinition.getName());
+			flow.setProcessDefinitionKey(processDefinition.getKey());
+			flow.setProcessDefinitionVersion(processDefinition.getVersion());
+			flow.setCategory(processDefinition.getCategory());
+			flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+
+			flow.setProcessInstanceId(historicTaskInstance.getProcessInstanceId());
+			flow.setHistoryProcessInstanceId(historicTaskInstance.getProcessInstanceId());
+			HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance((historicTaskInstance.getProcessInstanceId()));
+			if (Func.isNotEmpty(historicProcessInstance)) {
+				String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
+				flow.setBusinessTable(businessKey[0]);
+				flow.setBusinessId(businessKey[1]);
+				if (historicProcessInstance.getEndActivityId() != null) {
+					flow.setProcessIsFinished(FlowEngineConstant.STATUS_FINISHED);
+				} else {
+					flow.setProcessIsFinished(FlowEngineConstant.STATUS_UNFINISHED);
+				}
+			}
+			flow.setStatus(FlowEngineConstant.STATUS_FINISH);
+			flowList.add(flow);
+		});
+		// 计算总数
+		long count = doneQuery.count();
+		// 设置总数
+		page.setTotal(count);
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public boolean completeTask(BladeFlow flow) {
+		String taskId = flow.getTaskId();
+		String processInstanceId = flow.getProcessInstanceId();
+		String comment = Func.toStr(flow.getComment(), ProcessConstant.PASS_COMMENT);
+		// 增加评论
+		if (StringUtil.isNoneBlank(processInstanceId, comment)) {
+			taskService.addComment(taskId, processInstanceId, comment);
+		}
+		// 创建变量
+		Map<String, Object> variables = flow.getVariables();
+		if (variables == null) {
+			variables = Kv.create();
+		}
+		variables.put(ProcessConstant.PASS_KEY, flow.isPass());
+		// 完成任务
+		taskService.complete(taskId, variables);
+		return true;
+	}
+
+	/**
+	 * 构建流程
+	 *
+	 * @param bladeFlow 流程通用类
+	 * @param flowList  流程列表
+	 * @param taskQuery 任务查询类
+	 * @param status    状态
+	 */
+	private void buildFlowTaskList(BladeFlow bladeFlow, List<BladeFlow> flowList, TaskQuery taskQuery, String status) {
+		if (bladeFlow.getCategory() != null) {
+			taskQuery.processCategoryIn(Func.toStrList(bladeFlow.getCategory()));
+		}
+		if (bladeFlow.getProcessDefinitionName() != null) {
+			taskQuery.processDefinitionName(bladeFlow.getProcessDefinitionName());
+		}
+		if (bladeFlow.getBeginDate() != null) {
+			taskQuery.taskCreatedAfter(bladeFlow.getBeginDate());
+		}
+		if (bladeFlow.getEndDate() != null) {
+			taskQuery.taskCreatedBefore(bladeFlow.getEndDate());
+		}
+		taskQuery.list().forEach(task -> {
+			BladeFlow flow = new BladeFlow();
+			flow.setTaskId(task.getId());
+			flow.setTaskDefinitionKey(task.getTaskDefinitionKey());
+			flow.setTaskName(task.getName());
+			flow.setAssignee(task.getAssignee());
+			flow.setCreateTime(task.getCreateTime());
+			flow.setClaimTime(task.getClaimTime());
+			flow.setExecutionId(task.getExecutionId());
+			flow.setVariables(task.getProcessVariables());
+
+			HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(task.getProcessInstanceId());
+			if (Func.isNotEmpty(historicProcessInstance)) {
+				String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
+				flow.setBusinessTable(businessKey[0]);
+				flow.setBusinessId(businessKey[1]);
+			}
+
+			FlowProcess processDefinition = FlowCache.getProcessDefinition(task.getProcessDefinitionId());
+			flow.setCategory(processDefinition.getCategory());
+			flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+			flow.setProcessDefinitionId(processDefinition.getId());
+			flow.setProcessDefinitionName(processDefinition.getName());
+			flow.setProcessDefinitionKey(processDefinition.getKey());
+			flow.setProcessDefinitionVersion(processDefinition.getVersion());
+			flow.setProcessInstanceId(task.getProcessInstanceId());
+			flow.setStatus(status);
+			flowList.add(flow);
+		});
+	}
+
+	/**
+	 * 获取历史流程
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @return HistoricProcessInstance
+	 */
+	private HistoricProcessInstance getHistoricProcessInstance(String processInstanceId) {
+		return historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+	}
+
+}

+ 106 - 0
src/main/java/org/springblade/flow/business/service/impl/FlowServiceImpl.java

@@ -0,0 +1,106 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.business.service.impl;
+
+import lombok.AllArgsConstructor;
+import org.flowable.engine.IdentityService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.business.service.IFlowService;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * 流程实现类
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+public class FlowServiceImpl implements IFlowService {
+
+	private final RuntimeService runtimeService;
+	private final IdentityService identityService;
+	private final TaskService taskService;
+
+	@Override
+	public BladeFlow startProcessInstanceById(String processDefinitionId, String businessKey, Map<String, Object> variables) {
+		// 设置流程启动用户
+		identityService.setAuthenticatedUserId(TaskUtil.getTaskUser());
+		// 开启流程
+		ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variables);
+		// 组装流程通用类
+		BladeFlow flow = new BladeFlow();
+		flow.setProcessInstanceId(processInstance.getId());
+		return flow;
+	}
+
+	@Override
+	public BladeFlow startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables) {
+		// 设置流程启动用户
+		identityService.setAuthenticatedUserId(TaskUtil.getTaskUser());
+		// 开启流程
+		ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
+		// 组装流程通用类
+		BladeFlow flow = new BladeFlow();
+		flow.setProcessInstanceId(processInstance.getId());
+		return flow;
+	}
+
+	@Override
+	public boolean completeTask(String taskId, String processInstanceId, String comment, Map<String, Object> variables) {
+		// 增加评论
+		if (StringUtil.isNoneBlank(processInstanceId, comment)) {
+			taskService.addComment(taskId, processInstanceId, comment);
+		}
+		// 非空判断
+		if (Func.isEmpty(variables)) {
+			variables = Kv.create();
+		}
+		// 完成任务
+		taskService.complete(taskId, variables);
+		return true;
+	}
+
+	@Override
+	public Object taskVariable(String taskId, String variableName) {
+		return R.data(taskService.getVariable(taskId, variableName));
+	}
+
+	@Override
+	public Map<String, Object> taskVariables(String taskId) {
+		return taskService.getVariables(taskId);
+	}
+
+}

+ 70 - 0
src/main/java/org/springblade/flow/core/constant/ProcessConstant.java

@@ -0,0 +1,70 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.core.constant;
+
+/**
+ * 流程常量.
+ *
+ * @author Chill
+ */
+public interface ProcessConstant {
+
+	/**
+	 * 请假流程标识
+	 */
+	String LEAVE_KEY = "Leave";
+
+	/**
+	 * 报销流程标识
+	 */
+	String EXPENSE_KEY = "Expense";
+
+	/**
+	 * 同意标识
+	 */
+	String PASS_KEY = "pass";
+
+	/**
+	 * 同意代号
+	 */
+	String PASS_ALIAS = "ok";
+
+	/**
+	 * 同意默认批复
+	 */
+	String PASS_COMMENT = "同意";
+
+	/**
+	 * 驳回默认批复
+	 */
+	String NOT_PASS_COMMENT = "驳回";
+
+	/**
+	 * 创建人变量名
+	 */
+	String TASK_VARIABLE_CREATE_USER = "createUser";
+
+}

+ 190 - 0
src/main/java/org/springblade/flow/core/entity/BladeFlow.java

@@ -0,0 +1,190 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.core.entity;
+
+import lombok.Data;
+import org.springblade.flow.core.constant.ProcessConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 工作流通用实体类
+ *
+ * @author Chill
+ */
+@Data
+public class BladeFlow implements Serializable {
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 任务编号
+	 */
+	private String taskId;
+	/**
+	 * 任务名称
+	 */
+	private String taskName;
+	/**
+	 * 任务定义Key
+	 */
+	private String taskDefinitionKey;
+	/**
+	 * 任务执行人编号
+	 */
+	private String assignee;
+	/**
+	 * 任务执行人名称
+	 */
+	private String assigneeName;
+	/**
+	 * 流程分类
+	 */
+	private String category;
+	/**
+	 * 流程分类名
+	 */
+	private String categoryName;
+	/**
+	 * 创建时间
+	 */
+	private Date createTime;
+	/**
+	 * 结束时间
+	 */
+	private Date endTime;
+	/**
+	 * 签收时间
+	 */
+	private Date claimTime;
+	/**
+	 * 历史任务结束时间
+	 */
+	private Date historyTaskEndTime;
+	/**
+	 * 执行ID
+	 */
+	private String executionId;
+	/**
+	 * 流程实例ID
+	 */
+	private String processInstanceId;
+	/**
+	 * 流程ID
+	 */
+	private String processDefinitionId;
+	/**
+	 * 流程标识
+	 */
+	private String processDefinitionKey;
+	/**
+	 * 流程名
+	 */
+	private String processDefinitionName;
+	/**
+	 * 流程版本
+	 */
+	private int processDefinitionVersion;
+	/**
+	 * 流程说明
+	 */
+	private String processDefinitionDesc;
+	/**
+	 * 流程简图名
+	 */
+	private String processDefinitionDiagramResName;
+	/**
+	 * 流程重命名
+	 */
+	private String processDefinitionResName;
+	/**
+	 * 历史任务流程实例ID 查看流程图会用到
+	 */
+	private String historyProcessInstanceId;
+	/**
+	 * 流程实例是否结束
+	 */
+	private String processIsFinished;
+	/**
+	 * 历史活动ID
+	 */
+	private String historyActivityId;
+	/**
+	 * 历史活动流程
+	 */
+	private String historyActivityName;
+	/**
+	 * 历史活动耗时
+	 */
+	private String historyActivityDurationTime;
+	/**
+	 * 业务绑定Table
+	 */
+	private String businessTable;
+	/**
+	 * 业务绑定ID
+	 */
+	private String businessId;
+	/**
+	 * 任务状态
+	 */
+	private String status;
+	/**
+	 * 任务意见
+	 */
+	private String comment;
+	/**
+	 * 是否通过
+	 */
+	private boolean isPass;
+	/**
+	 * 是否通过代号
+	 */
+	private String flag;
+	/**
+	 * 开始查询日期
+	 */
+	private Date beginDate;
+	/**
+	 * 结束查询日期
+	 */
+	private Date endDate;
+	/**
+	 * 流程参数
+	 */
+	private Map<String, Object> variables;
+
+	/**
+	 * 获取是否通过
+	 */
+	public boolean isPass() {
+		return ProcessConstant.PASS_ALIAS.equals(flag) || ProcessConstant.PASS_COMMENT.equals(comment);
+	}
+
+}

+ 52 - 0
src/main/java/org/springblade/flow/core/entity/FlowEntity.java

@@ -0,0 +1,52 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.core.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * FlowEntity
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FlowEntity extends BaseEntity {
+
+	@TableField(exist = false)
+	private BladeFlow flow;
+
+	public BladeFlow getFlow() {
+		if (flow == null) {
+			flow = new BladeFlow();
+		}
+		return flow;
+	}
+
+}

+ 54 - 0
src/main/java/org/springblade/flow/core/enums/FlowModeEnum.java

@@ -0,0 +1,54 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 流程类型枚举
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum FlowModeEnum {
+
+	/**
+	 * 通用流程
+	 */
+	COMMON("common", 1),
+
+	/**
+	 * 定制流程
+	 */
+	CUSTOM("custom", 2),
+	;
+
+	final String name;
+	final int mode;
+
+}

+ 76 - 0
src/main/java/org/springblade/flow/core/utils/FlowUtil.java

@@ -0,0 +1,76 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.core.utils;
+
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.core.constant.ProcessConstant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 工作流工具类
+ *
+ * @author Chill
+ */
+public class FlowUtil {
+
+	/**
+	 * 定义流程key对应的表名
+	 */
+	private final static Map<String, String> BUSINESS_TABLE = new HashMap<>();
+
+	static {
+		BUSINESS_TABLE.put(ProcessConstant.LEAVE_KEY, "blade_process_leave");
+	}
+
+	/**
+	 * 通过流程key获取业务表名
+	 *
+	 * @param key 流程key
+	 */
+	public static String getBusinessTable(String key) {
+		String businessTable = BUSINESS_TABLE.get(key);
+		if (Func.isEmpty(businessTable)) {
+			return StringPool.EMPTY;
+		}
+		return businessTable;
+	}
+
+	/**
+	 * 获取业务标识
+	 *
+	 * @param businessTable 业务表
+	 * @param businessId    业务表主键
+	 * @return businessKey
+	 */
+	public static String getBusinessKey(String businessTable, String businessId) {
+		return StringUtil.format("{}:{}", businessTable, businessId);
+	}
+
+}

+ 80 - 0
src/main/java/org/springblade/flow/core/utils/TaskUtil.java

@@ -0,0 +1,80 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.core.utils;
+
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+
+import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
+
+/**
+ * 工作流任务工具类
+ *
+ * @author Chill
+ */
+public class TaskUtil {
+
+	/**
+	 * 获取任务用户格式
+	 *
+	 * @return taskUser
+	 */
+	public static String getTaskUser() {
+		return StringUtil.format("{}{}", TASK_USR_PREFIX, AuthUtil.getUserId());
+	}
+
+	/**
+	 * 获取任务用户格式
+	 *
+	 * @param userId 用户id
+	 * @return taskUser
+	 */
+	public static String getTaskUser(String userId) {
+		return StringUtil.format("{}{}", TASK_USR_PREFIX, userId);
+	}
+
+
+	/**
+	 * 获取用户主键
+	 *
+	 * @param taskUser 任务用户
+	 * @return userId
+	 */
+	public static Long getUserId(String taskUser) {
+		return Func.toLong(StringUtil.removePrefix(taskUser, TASK_USR_PREFIX));
+	}
+
+	/**
+	 * 获取用户组格式
+	 *
+	 * @return candidateGroup
+	 */
+	public static String getCandidateGroup() {
+		return AuthUtil.getUserRole();
+	}
+
+}

+ 74 - 0
src/main/java/org/springblade/flow/demo/leave/controller/LeaveController.java

@@ -0,0 +1,74 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.demo.leave.controller;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import lombok.AllArgsConstructor;
+import org.springblade.common.cache.UserCache;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.flow.demo.leave.entity.ProcessLeave;
+import org.springblade.flow.demo.leave.service.ILeaveService;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 控制器
+ *
+ * @author Chill
+ */
+@NonDS
+@Hidden
+@RestController
+@RequestMapping(AppConstant.APPLICATION_DESK_NAME + "/process/leave")
+@AllArgsConstructor
+public class LeaveController {
+
+	private final ILeaveService leaveService;
+
+	/**
+	 * 详情
+	 *
+	 * @param businessId 主键
+	 */
+	@GetMapping("detail")
+	public R<ProcessLeave> detail(Long businessId) {
+		ProcessLeave detail = leaveService.getById(businessId);
+		detail.getFlow().setAssigneeName(UserCache.getUser(detail.getCreateUser()).getName());
+		return R.data(detail);
+	}
+
+	/**
+	 * 新增或修改
+	 *
+	 * @param leave 请假信息
+	 */
+	@PostMapping("start-process")
+	public R startProcess(@RequestBody ProcessLeave leave) {
+		return R.status(leaveService.startProcess(leave));
+	}
+
+}

+ 78 - 0
src/main/java/org/springblade/flow/demo/leave/entity/ProcessLeave.java

@@ -0,0 +1,78 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.demo.leave.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.flow.core.entity.FlowEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 请假流程实体类
+ *
+ * @author Chill
+ */
+@Data
+@TableName("blade_process_leave")
+@EqualsAndHashCode(callSuper = true)
+public class ProcessLeave extends FlowEntity {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 流程定义id
+	 */
+	private String processDefinitionId;
+	/**
+	 * 流程实例id
+	 */
+	private String processInstanceId;
+	/**
+	 * 请假开始时间
+	 */
+	private Date startTime;
+	/**
+	 * 请假结束时间
+	 */
+	private Date endTime;
+	/**
+	 * 请假理由
+	 */
+	private String reason;
+	/**
+	 * 审批人
+	 */
+	private String taskUser;
+	/**
+	 * 流程申请时间
+	 */
+	private Date applyTime;
+
+}

+ 38 - 0
src/main/java/org/springblade/flow/demo/leave/mapper/LeaveMapper.java

@@ -0,0 +1,38 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.demo.leave.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.flow.demo.leave.entity.ProcessLeave;
+
+/**
+ * Mapper 接口
+ *
+ * @author Chill
+ */
+public interface LeaveMapper extends BaseMapper<ProcessLeave> {
+
+}

+ 6 - 0
src/main/java/org/springblade/flow/demo/leave/mapper/LeaveMapper.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.flow.demo.leave.mapper.LeaveMapper">
+
+
+</mapper>

+ 46 - 0
src/main/java/org/springblade/flow/demo/leave/service/ILeaveService.java

@@ -0,0 +1,46 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.demo.leave.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.flow.demo.leave.entity.ProcessLeave;
+
+/**
+ * 服务类
+ *
+ * @author Chill
+ */
+public interface ILeaveService extends BaseService<ProcessLeave> {
+
+	/**
+	 * 开启流程
+	 *
+	 * @param leave 请假实体
+	 * @return boolean
+	 */
+	boolean startProcess(ProcessLeave leave);
+
+}

+ 91 - 0
src/main/java/org/springblade/flow/demo/leave/service/impl/LeaveServiceImpl.java

@@ -0,0 +1,91 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.demo.leave.service.impl;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.flow.business.service.IFlowService;
+import org.springblade.flow.core.constant.ProcessConstant;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.FlowUtil;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springblade.flow.demo.leave.entity.ProcessLeave;
+import org.springblade.flow.demo.leave.mapper.LeaveMapper;
+import org.springblade.flow.demo.leave.service.ILeaveService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 服务实现类
+ *
+ * @author Chill
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class LeaveServiceImpl extends BaseServiceImpl<LeaveMapper, ProcessLeave> implements ILeaveService {
+
+	private final IFlowService flowService;
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public boolean startProcess(ProcessLeave leave) {
+		String businessTable = FlowUtil.getBusinessTable(ProcessConstant.LEAVE_KEY);
+		if (Func.isEmpty(businessTable)) {
+			throw new ServiceException("流程启动失败,未找到相关业务表");
+		}
+		if (Func.isEmpty(leave.getId())) {
+			// 保存leave
+			leave.setApplyTime(DateUtil.now());
+			save(leave);
+			// 启动流程
+			Kv variables = Kv.create()
+				.set(ProcessConstant.TASK_VARIABLE_CREATE_USER, AuthUtil.getUserName())
+				.set("taskUser", TaskUtil.getTaskUser(leave.getTaskUser()))
+				.set("days", DateUtil.between(leave.getStartTime(), leave.getEndTime()).toDays());
+			BladeFlow flow = flowService.startProcessInstanceById(leave.getProcessDefinitionId(), FlowUtil.getBusinessKey(businessTable, String.valueOf(leave.getId())), variables);
+			if (Func.isNotEmpty(flow)) {
+				log.debug("流程已启动,流程ID:" + flow.getProcessInstanceId());
+				// 返回流程id写入leave
+				leave.setProcessInstanceId(flow.getProcessInstanceId());
+				updateById(leave);
+			} else {
+				throw new ServiceException("开启流程失败");
+			}
+		} else {
+
+			updateById(leave);
+		}
+		return true;
+	}
+
+}

+ 6 - 0
src/main/java/org/springblade/flow/demo/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * Created by Blade.
+ *
+ * @author zhuangqian
+ */
+package org.springblade.flow.demo;

+ 53 - 0
src/main/java/org/springblade/flow/engine/config/FlowableConfiguration.java

@@ -0,0 +1,53 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.config;
+
+import lombok.AllArgsConstructor;
+import org.flowable.spring.SpringProcessEngineConfiguration;
+import org.flowable.spring.boot.EngineConfigurationConfigurer;
+import org.flowable.spring.boot.FlowableProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Flowable配置类
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+@AllArgsConstructor
+@EnableConfigurationProperties(FlowableProperties.class)
+public class FlowableConfiguration implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
+	private final FlowableProperties flowableProperties;
+
+	@Override
+	public void configure(SpringProcessEngineConfiguration engineConfiguration) {
+		engineConfiguration.setActivityFontName(flowableProperties.getActivityFontName());
+		engineConfiguration.setLabelFontName(flowableProperties.getLabelFontName());
+		engineConfiguration.setAnnotationFontName(flowableProperties.getAnnotationFontName());
+	}
+
+}

+ 61 - 0
src/main/java/org/springblade/flow/engine/constant/FlowEngineConstant.java

@@ -0,0 +1,61 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.constant;
+
+/**
+ * 流程常量.
+ *
+ * @author zhuangqian
+ */
+public interface FlowEngineConstant {
+
+	String FLOWABLE_BASE_PACKAGES = "org.flowable.ui";
+
+	String SUFFIX = ".bpmn20.xml";
+
+	String ACTIVE = "active";
+
+	String SUSPEND = "suspend";
+
+	String STATUS_TODO = "todo";
+
+	String STATUS_CLAIM = "claim";
+
+	String STATUS_SEND = "send";
+
+	String STATUS_DONE = "done";
+
+	String STATUS_FINISHED = "finished";
+
+	String STATUS_UNFINISHED = "unfinished";
+
+	String STATUS_FINISH = "finish";
+
+	String START_EVENT = "startEvent";
+
+	String END_EVENT = "endEvent";
+
+}

+ 82 - 0
src/main/java/org/springblade/flow/engine/controller/FlowFollowController.java

@@ -0,0 +1,82 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.flow.engine.entity.FlowExecution;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 流程状态控制器
+ *
+ * @author Chill
+ */
+@NonDS
+@RestController
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/follow")
+@AllArgsConstructor
+@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
+@Hidden
+public class FlowFollowController {
+
+	private final FlowEngineService flowEngineService;
+
+	/**
+	 * 流程状态列表
+	 */
+	@GetMapping("list")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "分页", description = "传入notice")
+	public R<IPage<FlowExecution>> list(Query query, @Parameter(description = "流程实例id") String processInstanceId, @Parameter(description = "流程key") String processDefinitionKey) {
+		IPage<FlowExecution> pages = flowEngineService.selectFollowPage(Condition.getPage(query), processInstanceId, processDefinitionKey);
+		return R.data(pages);
+	}
+
+	/**
+	 * 删除流程实例
+	 */
+	@PostMapping("delete-process-instance")
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "删除", description = "传入主键集合")
+	public R deleteProcessInstance(@Parameter(description = "流程实例id") @RequestParam String processInstanceId, @Parameter(description = "删除原因") @RequestParam String deleteReason) {
+		boolean temp = flowEngineService.deleteProcessInstance(processInstanceId, deleteReason);
+		return R.status(temp);
+	}
+
+}

+ 134 - 0
src/main/java/org/springblade/flow/engine/controller/FlowManagerController.java

@@ -0,0 +1,134 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.flow.engine.constant.FlowEngineConstant;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 流程管理接口
+ *
+ * @author Chill
+ */
+@NonDS
+@RestController
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/manager")
+@AllArgsConstructor
+@Tag(name = "流程管理接口", description = "流程管理接口")
+@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
+@Hidden
+public class FlowManagerController {
+
+	private final FlowEngineService flowEngineService;
+
+	/**
+	 * 分页
+	 */
+	@GetMapping("list")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "分页", description = "传入流程类型")
+	public R<IPage<FlowProcess>> list(@Parameter(description = "流程类型") String category, Query query, @RequestParam(required = false, defaultValue = "1") Integer mode) {
+		IPage<FlowProcess> pages = flowEngineService.selectProcessPage(Condition.getPage(query), category, mode);
+		return R.data(pages);
+	}
+
+	/**
+	 * 变更流程状态
+	 *
+	 * @param state     状态
+	 * @param processId 流程id
+	 */
+	@PostMapping("change-state")
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "变更流程状态", description = "传入state,processId")
+	public R changeState(@RequestParam String state, @RequestParam String processId) {
+		String msg = flowEngineService.changeState(state, processId);
+		return R.success(msg);
+	}
+
+	/**
+	 * 删除部署流程
+	 *
+	 * @param deploymentIds 部署流程id集合
+	 */
+	@PostMapping("delete-deployment")
+	@ApiOperationSupport(order = 3)
+	@Operation(summary = "删除部署流程", description = "部署流程id集合")
+	public R deleteDeployment(String deploymentIds) {
+		return R.status(flowEngineService.deleteDeployment(deploymentIds));
+	}
+
+	/**
+	 * 检查流程文件格式
+	 *
+	 * @param file 流程文件
+	 */
+	@PostMapping("check-upload")
+	@ApiOperationSupport(order = 4)
+	@Operation(summary = "上传部署流程文件", description = "传入文件")
+	public R checkUpload(@RequestParam MultipartFile file) {
+		boolean temp = Objects.requireNonNull(file.getOriginalFilename()).endsWith(FlowEngineConstant.SUFFIX);
+		return R.data(Kv.create().set("name", file.getOriginalFilename()).set("success", temp));
+	}
+
+	/**
+	 * 上传部署流程文件
+	 *
+	 * @param files    流程文件
+	 * @param category 类型
+	 */
+	@PostMapping("deploy-upload")
+	@ApiOperationSupport(order = 5)
+	@Operation(summary = "上传部署流程文件", description = "传入文件")
+	public R deployUpload(@RequestParam List<MultipartFile> files,
+						  @RequestParam String category,
+						  @RequestParam(required = false, defaultValue = "") String tenantIds) {
+		return R.status(flowEngineService.deployUpload(files, category, Func.toStrList(tenantIds)));
+	}
+
+}

+ 133 - 0
src/main/java/org/springblade/flow/engine/controller/FlowModelController.java

@@ -0,0 +1,133 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.xss.annotation.XssIgnore;
+import org.springblade.flow.engine.entity.FlowModel;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 流程模型控制器
+ *
+ * @author Chill
+ */
+@NonDS
+@RestController
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/model")
+@AllArgsConstructor
+@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
+@Hidden
+public class FlowModelController {
+
+	private final FlowEngineService flowEngineService;
+
+	/**
+	 * 分页
+	 */
+	@GetMapping("/list")
+	@Parameters({
+		@Parameter(name = "modelKey", description = "模型标识", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
+		@Parameter(name = "name", description = "模型名称", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
+	})
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "分页", description = "传入notice")
+	public R<IPage<FlowModel>> list(@Parameter(hidden = true) @RequestParam Map<String, Object> flow, Query query) {
+		IPage<FlowModel> pages = flowEngineService.page(Condition.getPage(query), Condition.getQueryWrapper(flow, FlowModel.class)
+			.select("id,model_key modelKey,name,description,version,created,last_updated lastUpdated")
+			.orderByDesc("last_updated"));
+		return R.data(pages);
+	}
+
+	/**
+	 * 删除
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "删除", description = "传入主键集合")
+	public R remove(@Parameter(description = "主键集合") @RequestParam String ids) {
+		boolean temp = flowEngineService.removeByIds(Func.toStrList(ids));
+		return R.status(temp);
+	}
+
+	/**
+	 * 部署
+	 */
+	@PostMapping("/deploy")
+	@ApiOperationSupport(order = 3)
+	@Operation(summary = "部署", description = "传入模型id和分类")
+	public R deploy(@Parameter(description = "模型id") @RequestParam String modelId,
+					@Parameter(description = "工作流分类") @RequestParam String category,
+					@Parameter(description = "租户ID") @RequestParam(required = false, defaultValue = "") String tenantIds) {
+		boolean temp = flowEngineService.deployModel(modelId, category, Func.toStrList(tenantIds));
+		return R.status(temp);
+	}
+
+	@XssIgnore
+	@PostMapping("submit")
+	@ApiOperationSupport(order = 4)
+	@Operation(summary = "保存/编辑")
+	@Parameters({
+		@Parameter(name = "id", description = "模型id"),
+		@Parameter(name = "name", description = "模型名称", required = true),
+		@Parameter(name = "modelKey", description = "模型key", required = true),
+		@Parameter(name = "description", description = "模型描述"),
+		@Parameter(name = "xml", description = "模型xml", required = true),
+	})
+	public R<FlowModel> submit(@RequestBody @Parameter(hidden = true) FlowModel model) {
+		return R.data(flowEngineService.submitModel(model));
+	}
+
+	@GetMapping("detail")
+	@Operation(summary = "详情")
+	@ApiOperationSupport(order = 5)
+	@Parameters({
+		@Parameter(name = "id", description = "模型id", required = true),
+	})
+	public R<FlowModel> detail(String id) {
+		return R.data(flowEngineService.getById(id));
+	}
+
+}

+ 107 - 0
src/main/java/org/springblade/flow/engine/controller/FlowProcessController.java

@@ -0,0 +1,107 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.engine.service.FlowEngineService;
+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 java.util.List;
+
+/**
+ * 流程通用控制器
+ *
+ * @author Chill
+ */
+@NonDS
+@Slf4j
+@RestController
+@AllArgsConstructor
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/process")
+@Hidden
+public class FlowProcessController {
+
+	private static final String IMAGE_NAME = "image";
+	private final FlowEngineService flowEngineService;
+
+	/**
+	 * 获取流转历史列表
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @param startActivityId   开始节点id
+	 * @param endActivityId     结束节点id
+	 */
+	@GetMapping(value = "history-flow-list")
+	public R<List<BladeFlow>> historyFlowList(@RequestParam String processInstanceId, String startActivityId, String endActivityId) {
+		return R.data(flowEngineService.historyFlowList(processInstanceId, startActivityId, endActivityId));
+	}
+
+	/**
+	 * 流程节点进程图
+	 *
+	 * @param processDefinitionId 流程id
+	 * @param processInstanceId 流程实例id
+	 */
+	@GetMapping(value = "model-view")
+	public R modelView(String processDefinitionId, String processInstanceId) {
+		return R.data(flowEngineService.modelView(processDefinitionId, processInstanceId));
+	}
+
+	/**
+	 * 流程节点进程图
+	 *
+	 * @param processInstanceId   流程实例id
+	 * @param httpServletResponse http响应
+	 */
+	@GetMapping(value = "diagram-view")
+	public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) {
+		flowEngineService.diagramView(processInstanceId, httpServletResponse);
+	}
+
+	/**
+	 * 流程图展示
+	 *
+	 * @param processDefinitionId 流程id
+	 * @param processInstanceId   实例id
+	 * @param resourceType        资源类型
+	 * @param response            响应
+	 */
+	@GetMapping("resource-view")
+	public void resourceView(@RequestParam String processDefinitionId, String processInstanceId, @RequestParam(defaultValue = IMAGE_NAME) String resourceType, HttpServletResponse response) {
+		flowEngineService.resourceView(processDefinitionId, processInstanceId, resourceType, response);
+	}
+
+}

+ 61 - 0
src/main/java/org/springblade/flow/engine/entity/FlowExecution.java

@@ -0,0 +1,61 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.entity;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 运行实体类
+ *
+ * @author Chill
+ */
+@Data
+public class FlowExecution implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	private String id;
+	private String name;
+	private String startUserId;
+	private String startUser;
+	private Date startTime;
+	private String taskDefinitionId;
+	private String taskDefinitionKey;
+	private String category;
+	private String categoryName;
+	private String processInstanceId;
+	private String processDefinitionId;
+	private String processDefinitionKey;
+	private String activityId;
+	private int suspensionState;
+	private String executionId;
+
+}

+ 69 - 0
src/main/java/org/springblade/flow/engine/entity/FlowModel.java

@@ -0,0 +1,69 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 流程模型
+ *
+ * @author Chill
+ */
+@Data
+@TableName("ACT_DE_MODEL")
+public class FlowModel implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	public static final int MODEL_TYPE_BPMN = 0;
+	public static final int MODEL_TYPE_FORM = 2;
+	public static final int MODEL_TYPE_APP = 3;
+	public static final int MODEL_TYPE_DECISION_TABLE = 4;
+	public static final int MODEL_TYPE_CMMN = 5;
+
+	private String id;
+	private String name;
+	private String modelKey;
+	private String description;
+	private Date created;
+	private Date lastUpdated;
+	private String createdBy;
+	private String lastUpdatedBy;
+	private Integer version;
+	private String modelEditorJson;
+	private String modelComment;
+	private Integer modelType;
+	private String tenantId;
+	private byte[] thumbnail;
+	private String modelEditorXml;
+
+}

+ 74 - 0
src/main/java/org/springblade/flow/engine/entity/FlowProcess.java

@@ -0,0 +1,74 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.entity;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
+import org.springblade.flow.engine.utils.FlowCache;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * FlowProcess
+ *
+ * @author Chill
+ */
+@Data
+@NoArgsConstructor
+public class FlowProcess implements Serializable {
+
+	private String id;
+	private String tenantId;
+	private String name;
+	private String key;
+	private String category;
+	private String categoryName;
+	private Integer version;
+	private String deploymentId;
+	private String resourceName;
+	private String diagramResourceName;
+	private Integer suspensionState;
+	private Date deploymentTime;
+
+	public FlowProcess(ProcessDefinitionEntityImpl entity) {
+		if (entity != null) {
+			this.id = entity.getId();
+			this.tenantId = entity.getTenantId();
+			this.name = entity.getName();
+			this.key = entity.getKey();
+			this.category = entity.getCategory();
+			this.categoryName = FlowCache.getCategoryName(entity.getCategory());
+			this.version = entity.getVersion();
+			this.deploymentId = entity.getDeploymentId();
+			this.resourceName = entity.getResourceName();
+			this.diagramResourceName = entity.getDiagramResourceName();
+			this.suspensionState = entity.getSuspensionState();
+		}
+	}
+
+}

+ 55 - 0
src/main/java/org/springblade/flow/engine/mapper/FlowMapper.java

@@ -0,0 +1,55 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.flow.engine.entity.FlowModel;
+
+import java.util.List;
+
+/**
+ * FlowMapper.
+ *
+ * @author Chill
+ */
+public interface FlowMapper extends BaseMapper<FlowModel> {
+
+	/**
+	 * 自定义分页
+	 * @param page
+	 * @param flowModel
+	 * @return
+	 */
+	List<FlowModel> selectFlowPage(IPage page, FlowModel flowModel);
+
+	/**
+	 * 获取模型
+	 * @param parentModelId
+	 * @return
+	 */
+	List<FlowModel> findByParentModelId(String parentModelId);
+}

+ 53 - 0
src/main/java/org/springblade/flow/engine/mapper/FlowMapper.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.flow.engine.mapper.FlowMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="flowModelResultMap" type="org.springblade.flow.engine.entity.FlowModel">
+        <result column="id" property="id"/>
+        <result column="name" property="name"/>
+        <result column="model_key" property="modelKey"/>
+        <result column="description" property="description"/>
+        <result column="model_comment" property="modelComment"/>
+        <result column="created" property="created"/>
+        <result column="created_by" property="createdBy"/>
+        <result column="last_updated" property="lastUpdated"/>
+        <result column="last_updated_by" property="lastUpdatedBy"/>
+        <result column="version" property="version"/>
+        <result column="model_editor_json" property="modelEditorJson"/>
+        <result column="thumbnail" property="thumbnail"/>
+        <result column="model_type" property="modelType"/>
+        <result column="tenant_id" property="tenantId"/>
+    </resultMap>
+
+    <select id="selectFlowPage" resultMap="flowModelResultMap">
+        SELECT
+            a.id,
+            a.name,
+            a.model_key,
+            a.description,
+            a.model_comment,
+            a.created,
+            a.created_by,
+            a.last_updated,
+            a.last_updated_by,
+            a.version,
+            a.model_editor_json,
+            a.thumbnail,
+            a.model_type,
+            a.tenant_id
+        FROM
+            ACT_DE_MODEL a
+        WHERE
+            1 = 1
+        ORDER BY
+            a.created DESC
+    </select>
+
+    <select id="findByParentModelId" parameterType="string" resultMap="flowModelResultMap">
+        select model.* from ACT_DE_MODEL_RELATION modelrelation
+                                inner join ACT_DE_MODEL model on modelrelation.model_id = model.id
+        where modelrelation.parent_model_id = #{_parameter}
+    </select>
+
+</mapper>

+ 174 - 0
src/main/java/org/springblade/flow/engine/service/FlowEngineService.java

@@ -0,0 +1,174 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.engine.entity.FlowExecution;
+import org.springblade.flow.engine.entity.FlowModel;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * FlowEngineService
+ *
+ * @author Chill
+ */
+public interface FlowEngineService extends IService<FlowModel> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page      分页工具
+	 * @param flowModel 流程模型
+	 * @return
+	 */
+	IPage<FlowModel> selectFlowPage(IPage<FlowModel> page, FlowModel flowModel);
+
+	/**
+	 * 流程管理列表
+	 *
+	 * @param page     分页工具
+	 * @param category 分类
+	 * @param mode     形态
+	 * @return
+	 */
+	IPage<FlowProcess> selectProcessPage(IPage<FlowProcess> page, String category, Integer mode);
+
+	/**
+	 * 流程管理列表
+	 *
+	 * @param page                 分页工具
+	 * @param processInstanceId    流程实例id
+	 * @param processDefinitionKey 流程key
+	 * @return
+	 */
+	IPage<FlowExecution> selectFollowPage(IPage<FlowExecution> page, String processInstanceId, String processDefinitionKey);
+
+	/**
+	 * 获取流转历史列表
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @param startActivityId   开始节点id
+	 * @param endActivityId     结束节点id
+	 * @return
+	 */
+	List<BladeFlow> historyFlowList(String processInstanceId, String startActivityId, String endActivityId);
+
+	/**
+	 * 变更流程状态
+	 *
+	 * @param state     状态
+	 * @param processId 流程ID
+	 * @return
+	 */
+	String changeState(String state, String processId);
+
+	/**
+	 * 删除部署流程
+	 *
+	 * @param deploymentIds 部署流程id集合
+	 * @return
+	 */
+	boolean deleteDeployment(String deploymentIds);
+
+	/**
+	 * 上传部署流程
+	 *
+	 * @param files        流程配置文件
+	 * @param category     流程分类
+	 * @param tenantIdList 租户id集合
+	 * @return
+	 */
+	boolean deployUpload(List<MultipartFile> files, String category, List<String> tenantIdList);
+
+	/**
+	 * 部署流程
+	 *
+	 * @param modelId      模型id
+	 * @param category     分类
+	 * @param tenantIdList 租户id集合
+	 * @return
+	 */
+	boolean deployModel(String modelId, String category, List<String> tenantIdList);
+
+	/**
+	 * 删除流程实例
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @param deleteReason      删除原因
+	 * @return
+	 */
+	boolean deleteProcessInstance(String processInstanceId, String deleteReason);
+
+	/**
+	 * 保存/更新模型
+	 *
+	 * @param model 模型
+	 * @return 模型
+	 */
+	FlowModel submitModel(FlowModel model);
+
+	/**
+	 * 流程节点进程图
+	 *
+	 * @param processDefinitionId
+	 * @param processInstanceId
+	 * @return
+	 */
+	Map<String, Object> modelView(String processDefinitionId, String processInstanceId);
+
+	/**
+	 * 流程节点进程图
+	 *
+	 * @param processInstanceId
+	 * @param httpServletResponse
+	 */
+	void diagramView(String processInstanceId, HttpServletResponse httpServletResponse);
+
+	/**
+	 * 流程图展示
+	 *
+	 * @param processDefinitionId
+	 * @param processInstanceId
+	 * @param resourceType
+	 * @param response
+	 */
+	void resourceView(String processDefinitionId, String processInstanceId, String resourceType, HttpServletResponse response);
+
+	/**
+	 * 获取XML
+	 *
+	 * @param model
+	 * @return
+	 */
+	byte[] getModelEditorXML(FlowModel model);
+}

+ 568 - 0
src/main/java/org/springblade/flow/engine/service/impl/FlowEngineServiceImpl.java

@@ -0,0 +1,568 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.bpmn.converter.BpmnXMLConverter;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.Process;
+import org.flowable.common.engine.impl.util.IoUtil;
+import org.flowable.common.engine.impl.util.io.StringStreamSource;
+import org.flowable.editor.language.json.converter.BpmnJsonConverter;
+import org.flowable.editor.language.json.converter.BpmnJsonConverterContext;
+import org.flowable.editor.language.json.converter.CustomBpmnJsonConverterContext;
+import org.flowable.engine.*;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
+import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.repository.ProcessDefinitionQuery;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.engine.runtime.ProcessInstanceQuery;
+import org.flowable.engine.task.Comment;
+import org.flowable.image.ProcessDiagramGenerator;
+import org.springblade.common.cache.UserCache;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.FileUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.enums.FlowModeEnum;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springblade.flow.engine.constant.FlowEngineConstant;
+import org.springblade.flow.engine.entity.FlowExecution;
+import org.springblade.flow.engine.entity.FlowModel;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springblade.flow.engine.mapper.FlowMapper;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springblade.flow.engine.utils.FlowCache;
+import org.springblade.modules.system.pojo.entity.User;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+
+/**
+ * 工作流服务实现类
+ *
+ * @author Chill
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class FlowEngineServiceImpl extends ServiceImpl<FlowMapper, FlowModel> implements FlowEngineService {
+	private static final String ALREADY_IN_STATE = "already in state";
+	private static final String USR_TASK = "userTask";
+	private static final String IMAGE_NAME = "image";
+	private static final String XML_NAME = "xml";
+	private static final Integer INT_1024 = 1024;
+	private static final BpmnJsonConverter BPMN_JSON_CONVERTER = new BpmnJsonConverter();
+	private static final BpmnXMLConverter BPMN_XML_CONVERTER = new BpmnXMLConverter();
+	private final ObjectMapper objectMapper;
+	private final RepositoryService repositoryService;
+	private final RuntimeService runtimeService;
+	private final HistoryService historyService;
+	private final TaskService taskService;
+	private final ProcessEngine processEngine;
+
+	@Override
+	public IPage<FlowModel> selectFlowPage(IPage<FlowModel> page, FlowModel flowModel) {
+		return page.setRecords(baseMapper.selectFlowPage(page, flowModel));
+	}
+
+	@Override
+	public IPage<FlowProcess> selectProcessPage(IPage<FlowProcess> page, String category, Integer mode) {
+		ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().latestVersion().orderByProcessDefinitionKey().asc();
+		// 通用流程
+		if (mode == FlowModeEnum.COMMON.getMode()) {
+			processDefinitionQuery.processDefinitionWithoutTenantId();
+		}
+		// 定制流程
+		else if (!AuthUtil.isAdministrator()) {
+			processDefinitionQuery.processDefinitionTenantId(AuthUtil.getTenantId());
+		}
+		if (StringUtils.isNotEmpty(category)) {
+			processDefinitionQuery.processDefinitionCategory(category);
+		}
+		List<ProcessDefinition> processDefinitionList = processDefinitionQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
+		List<FlowProcess> flowProcessList = new ArrayList<>();
+		processDefinitionList.forEach(processDefinition -> {
+			String deploymentId = processDefinition.getDeploymentId();
+			Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
+			FlowProcess flowProcess = new FlowProcess((ProcessDefinitionEntityImpl) processDefinition);
+			flowProcess.setDeploymentTime(deployment.getDeploymentTime());
+			flowProcessList.add(flowProcess);
+		});
+		page.setTotal(processDefinitionQuery.count());
+		page.setRecords(flowProcessList);
+		return page;
+	}
+
+	@Override
+	public IPage<FlowExecution> selectFollowPage(IPage<FlowExecution> page, String processInstanceId, String processDefinitionKey) {
+		ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
+		if (StringUtil.isNotBlank(processInstanceId)) {
+			processInstanceQuery.processInstanceId(processInstanceId);
+		}
+		if (StringUtil.isNotBlank(processDefinitionKey)) {
+			processInstanceQuery.processDefinitionKey(processDefinitionKey);
+		}
+		List<FlowExecution> flowList = new ArrayList<>();
+		List<ProcessInstance> procInsList = processInstanceQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
+		procInsList.forEach(processInstance -> {
+			ExecutionEntityImpl execution = (ExecutionEntityImpl) processInstance;
+			FlowExecution flowExecution = new FlowExecution();
+			flowExecution.setId(execution.getId());
+			flowExecution.setName(execution.getName());
+			flowExecution.setStartUserId(execution.getStartUserId());
+			User taskUser = UserCache.getUserByTaskUser(execution.getStartUserId());
+			if (taskUser != null) {
+				flowExecution.setStartUser(taskUser.getName());
+			}
+			flowExecution.setStartTime(execution.getStartTime());
+			flowExecution.setExecutionId(execution.getId());
+			flowExecution.setProcessInstanceId(execution.getProcessInstanceId());
+			flowExecution.setProcessDefinitionId(execution.getProcessDefinitionId());
+			flowExecution.setProcessDefinitionKey(execution.getProcessDefinitionKey());
+			flowExecution.setSuspensionState(execution.getSuspensionState());
+			FlowProcess processDefinition = FlowCache.getProcessDefinition(execution.getProcessDefinitionId());
+			flowExecution.setCategory(processDefinition.getCategory());
+			flowExecution.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+			flowList.add(flowExecution);
+		});
+		page.setTotal(processInstanceQuery.count());
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public List<BladeFlow> historyFlowList(String processInstanceId, String startActivityId, String endActivityId) {
+		List<BladeFlow> flowList = new LinkedList<>();
+		List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().orderByHistoricActivityInstanceEndTime().asc().list();
+		boolean start = false;
+		Map<String, Integer> activityMap = new HashMap<>(16);
+		for (int i = 0; i < historicActivityInstanceList.size(); i++) {
+			HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(i);
+			// 过滤开始节点前的节点
+			if (StringUtil.isNotBlank(startActivityId) && startActivityId.equals(historicActivityInstance.getActivityId())) {
+				start = true;
+			}
+			if (StringUtil.isNotBlank(startActivityId) && !start) {
+				continue;
+			}
+			// 显示开始节点和结束节点,并且执行人不为空的任务
+			if (StringUtils.equals(USR_TASK, historicActivityInstance.getActivityType())
+				|| FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType())
+				|| FlowEngineConstant.END_EVENT.equals(historicActivityInstance.getActivityType())) {
+				// 给节点增加序号
+				activityMap.computeIfAbsent(historicActivityInstance.getActivityId(), k -> activityMap.size());
+				BladeFlow flow = new BladeFlow();
+				flow.setHistoryActivityId(historicActivityInstance.getActivityId());
+				flow.setHistoryActivityName(historicActivityInstance.getActivityName());
+				flow.setCreateTime(historicActivityInstance.getStartTime());
+				flow.setEndTime(historicActivityInstance.getEndTime());
+				String durationTime = DateUtil.secondToTime(Func.toLong(historicActivityInstance.getDurationInMillis(), 0L) / 1000);
+				flow.setHistoryActivityDurationTime(durationTime);
+				// 获取流程发起人名称
+				if (FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType())) {
+					List<HistoricProcessInstance> processInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).orderByProcessInstanceStartTime().asc().list();
+					if (!processInstanceList.isEmpty()) {
+						if (StringUtil.isNotBlank(processInstanceList.get(0).getStartUserId())) {
+							String taskUser = processInstanceList.get(0).getStartUserId();
+							User user = UserCache.getUser(TaskUtil.getUserId(taskUser));
+							if (user != null) {
+								flow.setAssignee(historicActivityInstance.getAssignee());
+								flow.setAssigneeName(user.getName());
+							}
+						}
+					}
+				}
+				// 获取任务执行人名称
+				if (StringUtil.isNotBlank(historicActivityInstance.getAssignee())) {
+					User user = UserCache.getUser(TaskUtil.getUserId(historicActivityInstance.getAssignee()));
+					if (user != null) {
+						flow.setAssignee(historicActivityInstance.getAssignee());
+						flow.setAssigneeName(user.getName());
+					}
+				}
+				// 获取意见评论内容
+				if (StringUtil.isNotBlank(historicActivityInstance.getTaskId())) {
+					List<Comment> commentList = taskService.getTaskComments(historicActivityInstance.getTaskId());
+					if (!commentList.isEmpty()) {
+						flow.setComment(commentList.get(0).getFullMessage());
+					}
+				}
+				flowList.add(flow);
+			}
+			// 过滤结束节点后的节点
+			if (StringUtils.isNotBlank(endActivityId) && endActivityId.equals(historicActivityInstance.getActivityId())) {
+				boolean temp = false;
+				Integer activityNum = activityMap.get(historicActivityInstance.getActivityId());
+				// 该活动节点,后续节点是否在结束节点之前,在后续节点中是否存在
+				for (int j = i + 1; j < historicActivityInstanceList.size(); j++) {
+					HistoricActivityInstance hi = historicActivityInstanceList.get(j);
+					Integer activityNumA = activityMap.get(hi.getActivityId());
+					boolean numberTemp = activityNumA != null && activityNumA < activityNum;
+					boolean equalsTemp = StringUtils.equals(hi.getActivityId(), historicActivityInstance.getActivityId());
+					if (numberTemp || equalsTemp) {
+						temp = true;
+					}
+				}
+				if (!temp) {
+					break;
+				}
+			}
+		}
+		return flowList;
+	}
+
+	@Override
+	public String changeState(String state, String processId) {
+		try {
+			if (state.equals(FlowEngineConstant.ACTIVE)) {
+				repositoryService.activateProcessDefinitionById(processId, true, null);
+				return StringUtil.format("激活ID为 [{}] 的流程成功", processId);
+			} else if (state.equals(FlowEngineConstant.SUSPEND)) {
+				repositoryService.suspendProcessDefinitionById(processId, true, null);
+				return StringUtil.format("挂起ID为 [{}] 的流程成功", processId);
+			} else {
+				return "暂无流程变更";
+			}
+		} catch (Exception e) {
+			if (e.getMessage().contains(ALREADY_IN_STATE)) {
+				return StringUtil.format("ID为 [{}] 的流程已是此状态,无需操作", processId);
+			}
+			return e.getMessage();
+		}
+	}
+
+	@Override
+	public boolean deleteDeployment(String deploymentIds) {
+		Func.toStrList(deploymentIds).forEach(deploymentId -> repositoryService.deleteDeployment(deploymentId, true));
+		return true;
+	}
+
+	@Override
+	public boolean deployUpload(List<MultipartFile> files, String category, List<String> tenantIdList) {
+		files.forEach(file -> {
+			try {
+				String fileName = file.getOriginalFilename();
+				InputStream fileInputStream = file.getInputStream();
+				byte[] bytes = FileUtil.copyToByteArray(fileInputStream);
+				if (Func.isNotEmpty(tenantIdList)) {
+					tenantIdList.forEach(tenantId -> {
+						Deployment deployment = repositoryService.createDeployment().addBytes(fileName, bytes).tenantId(tenantId).deploy();
+						deploy(deployment, category);
+					});
+				} else {
+					Deployment deployment = repositoryService.createDeployment().addBytes(fileName, bytes).deploy();
+					deploy(deployment, category);
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		});
+		return true;
+	}
+
+	@Override
+	public boolean deployModel(String modelId, String category, List<String> tenantIdList) {
+		FlowModel model = this.getById(modelId);
+		if (model == null) {
+			throw new ServiceException("未找到模型 id: " + modelId);
+		}
+		byte[] bytes = getBpmnXML(model);
+		String processName = model.getName();
+		if (!StringUtil.endsWithIgnoreCase(processName, FlowEngineConstant.SUFFIX)) {
+			processName += FlowEngineConstant.SUFFIX;
+		}
+		String finalProcessName = processName;
+		if (Func.isNotEmpty(tenantIdList)) {
+			tenantIdList.forEach(tenantId -> {
+				Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).tenantId(tenantId).deploy();
+				deploy(deployment, category);
+			});
+		} else {
+			Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).deploy();
+			deploy(deployment, category);
+		}
+		return true;
+	}
+
+	@Override
+	public boolean deleteProcessInstance(String processInstanceId, String deleteReason) {
+		runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
+		return true;
+	}
+
+	private void deploy(Deployment deployment, String category) {
+		log.debug("流程部署--------deploy:  " + deployment + "  分类---------->" + category);
+		List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
+		StringBuilder logBuilder = new StringBuilder(500);
+		List<Object> logArgs = new ArrayList<>();
+		// 设置流程分类
+		for (ProcessDefinition processDefinition : list) {
+			if (StringUtil.isNotBlank(category)) {
+				repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category);
+			}
+			logBuilder.append("部署成功,流程ID={} \n");
+			logArgs.add(processDefinition.getId());
+		}
+		if (list.isEmpty()) {
+			throw new ServiceException("部署失败,未找到流程");
+		} else {
+			log.info(logBuilder.toString(), logArgs.toArray());
+		}
+	}
+
+	@Override
+	public FlowModel submitModel(FlowModel model) {
+		FlowModel flowModel = new FlowModel();
+		flowModel.setId(model.getId());
+		flowModel.setVersion(Func.toInt(model.getVersion(), 0) + 1);
+		flowModel.setName(model.getName());
+		flowModel.setModelKey(model.getModelKey());
+		flowModel.setModelType(FlowModel.MODEL_TYPE_BPMN);
+		flowModel.setCreatedBy(TaskUtil.getTaskUser());
+		flowModel.setDescription(model.getDescription());
+		flowModel.setLastUpdated(Calendar.getInstance().getTime());
+		flowModel.setLastUpdatedBy(TaskUtil.getTaskUser());
+		flowModel.setTenantId(AuthUtil.getTenantId());
+		flowModel.setModelEditorXml(model.getModelEditorXml());
+		if (StringUtil.isBlank(model.getId())) {
+			flowModel.setCreated(Calendar.getInstance().getTime());
+		}
+		if (StringUtil.isNotBlank(model.getModelEditorXml())) {
+			flowModel.setModelEditorJson(getBpmnJson(model.getModelEditorXml()));
+		}
+		this.saveOrUpdate(flowModel);
+		return flowModel;
+	}
+
+	@Override
+	public Map<String, Object> modelView(String processDefinitionId, String processInstanceId) {
+		Map<String, Object> result = new HashMap<>();
+		// 节点标记
+		if (StringUtil.isNotBlank(processInstanceId)) {
+			result.put("flow", this.historyFlowList(processInstanceId, null, null));
+			HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
+				.processInstanceId(processInstanceId)
+				.singleResult();
+			processDefinitionId = processInstance.getProcessDefinitionId();
+		}
+		BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
+		// 流程图展示
+		result.put("xml", new String(new BpmnXMLConverter().convertToXML(bpmnModel)));
+		return result;
+	}
+
+	@Override
+	public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) {
+		// 获得当前活动的节点
+		String processDefinitionId;
+		// 如果流程已经结束,则得到结束节点
+		if (this.isFinished(processInstanceId)) {
+			HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+			processDefinitionId = pi.getProcessDefinitionId();
+		} else {
+			// 如果流程没有结束,则取当前活动节点
+			// 根据流程实例ID获得当前处于活动状态的ActivityId合集
+			ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+			processDefinitionId = pi.getProcessDefinitionId();
+		}
+		List<String> highLightedActivities = new ArrayList<>();
+
+		// 获得活动的节点
+		List<HistoricActivityInstance> highLightedActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
+
+		for (HistoricActivityInstance tempActivity : highLightedActivityList) {
+			String activityId = tempActivity.getActivityId();
+			highLightedActivities.add(activityId);
+		}
+
+		List<String> flows = new ArrayList<>();
+		// 获取流程图
+		BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
+		ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
+
+		ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator();
+		InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivities, flows, engConf.getActivityFontName(),
+			engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true);
+		OutputStream out = null;
+		byte[] buf = new byte[1024];
+		int length;
+		try {
+			out = httpServletResponse.getOutputStream();
+			while ((length = in.read(buf)) != -1) {
+				out.write(buf, 0, length);
+			}
+		} catch (IOException e) {
+			log.error("操作异常", e);
+		} finally {
+			IoUtil.closeSilently(out);
+			IoUtil.closeSilently(in);
+		}
+	}
+
+	@Override
+	public void resourceView(String processDefinitionId, String processInstanceId, String resourceType, HttpServletResponse response) {
+		if (StringUtil.isAllBlank(processDefinitionId, processInstanceId)) {
+			return;
+		}
+		if (StringUtil.isBlank(processDefinitionId)) {
+			ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+			processDefinitionId = processInstance.getProcessDefinitionId();
+		}
+		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
+		String resourceName = "";
+		if (resourceType.equals(IMAGE_NAME)) {
+			resourceName = processDefinition.getDiagramResourceName();
+		} else if (resourceType.equals(XML_NAME)) {
+			resourceName = processDefinition.getResourceName();
+		}
+		try {
+			InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
+			byte[] b = new byte[1024];
+			int len;
+			while ((len = resourceAsStream.read(b, 0, INT_1024)) != -1) {
+				response.getOutputStream().write(b, 0, len);
+			}
+		} catch (Exception exception) {
+			exception.printStackTrace();
+		}
+	}
+
+	@Override
+	public byte[] getModelEditorXML(FlowModel model) {
+		return getBpmnXML(model);
+	}
+
+	/**
+	 * 是否已完结
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @return bool
+	 */
+	private boolean isFinished(String processInstanceId) {
+		return historyService.createHistoricProcessInstanceQuery().finished()
+			.processInstanceId(processInstanceId).count() > 0;
+	}
+
+
+	/**
+	 * xml转bpmn json
+	 *
+	 * @param xml xml
+	 * @return json
+	 */
+	private String getBpmnJson(String xml) {
+		return BPMN_JSON_CONVERTER.convertToJson(getBpmnModel(xml)).toString();
+	}
+
+	/**
+	 * xml转bpmnModel
+	 *
+	 * @param xml xml
+	 * @return bpmnModel
+	 */
+	private BpmnModel getBpmnModel(String xml) {
+		return BPMN_XML_CONVERTER.convertToBpmnModel(new StringStreamSource(xml), false, false);
+	}
+
+	private byte[] getBpmnXML(FlowModel model) {
+		BpmnModel bpmnModel = getBpmnModel(model);
+		return getBpmnXML(bpmnModel);
+	}
+
+	private byte[] getBpmnXML(BpmnModel bpmnModel) {
+		for (Process process : bpmnModel.getProcesses()) {
+			if (StringUtils.isNotEmpty(process.getId())) {
+				char firstCharacter = process.getId().charAt(0);
+				if (Character.isDigit(firstCharacter)) {
+					process.setId("a" + process.getId());
+				}
+			}
+		}
+		return BPMN_XML_CONVERTER.convertToXML(bpmnModel);
+	}
+
+	private BpmnModel getBpmnModel(FlowModel model) {
+		BpmnModel bpmnModel;
+		try {
+			Map<String, FlowModel> formMap = new HashMap<>(16);
+			Map<String, FlowModel> decisionTableMap = new HashMap<>(16);
+
+			List<FlowModel> referencedModels = baseMapper.findByParentModelId(model.getId());
+			for (FlowModel childModel : referencedModels) {
+				if (FlowModel.MODEL_TYPE_FORM == childModel.getModelType()) {
+					formMap.put(childModel.getId(), childModel);
+
+				} else if (FlowModel.MODEL_TYPE_DECISION_TABLE == childModel.getModelType()) {
+					decisionTableMap.put(childModel.getId(), childModel);
+				}
+			}
+			bpmnModel = getBpmnModel(model, formMap, decisionTableMap);
+		} catch (Exception e) {
+			log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e);
+			throw new ServiceException("Could not generate BPMN 2.0 model");
+		}
+		return bpmnModel;
+	}
+
+	private BpmnModel getBpmnModel(FlowModel model, Map<String, FlowModel> formMap, Map<String, FlowModel> decisionTableMap) {
+		try {
+			ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
+			Map<String, String> formKeyMap = new HashMap<>(16);
+			for (FlowModel formModel : formMap.values()) {
+				formKeyMap.put(formModel.getId(), formModel.getModelKey());
+			}
+			Map<String, String> decisionTableKeyMap = new HashMap<>(16);
+			for (FlowModel decisionTableModel : decisionTableMap.values()) {
+				decisionTableKeyMap.put(decisionTableModel.getId(), decisionTableModel.getModelKey());
+			}
+			BpmnJsonConverterContext converterContext = new CustomBpmnJsonConverterContext(formKeyMap, decisionTableKeyMap);
+			return BPMN_JSON_CONVERTER.convertToBpmnModel(editorJsonNode, converterContext);
+		} catch (Exception e) {
+			log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e);
+			throw new ServiceException("Could not generate BPMN 2.0 model");
+		}
+	}
+
+}

+ 89 - 0
src/main/java/org/springblade/flow/engine/utils/FlowCache.java

@@ -0,0 +1,89 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.flow.engine.utils;
+
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springblade.common.cache.DictCache;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.flow.engine.entity.FlowProcess;
+
+/**
+ * 流程缓存
+ *
+ * @author Chill
+ */
+public class FlowCache {
+
+	private static final String FLOW_CACHE = "flow:process";
+	private static final String FLOW_DEFINITION_ID = "definition:id";
+	private static RepositoryService repositoryService;
+
+	private static RepositoryService getRepositoryService() {
+		if (repositoryService == null) {
+			repositoryService = SpringUtil.getBean(RepositoryService.class);
+		}
+		return repositoryService;
+	}
+
+	/**
+	 * 获得流程定义对象
+	 *
+	 * @param processDefinitionId 流程对象id
+	 * @return
+	 */
+	public static FlowProcess getProcessDefinition(String processDefinitionId) {
+		return CacheUtil.get(FLOW_CACHE, FLOW_DEFINITION_ID, processDefinitionId, () -> {
+			ProcessDefinition processDefinition = getRepositoryService().createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
+			ProcessDefinitionEntityImpl processDefinitionEntity = BeanUtil.copyProperties(processDefinition, ProcessDefinitionEntityImpl.class);
+			return new FlowProcess(processDefinitionEntity);
+		});
+	}
+
+	/**
+	 * 获取流程类型名
+	 *
+	 * @param category 流程类型
+	 * @return
+	 */
+	public static String getCategoryName(String category) {
+		if (Func.isEmpty(category)) {
+			return StringPool.EMPTY;
+		}
+		String[] categoryArr = category.split(StringPool.UNDERSCORE);
+		if (categoryArr.length <= 1) {
+			return StringPool.EMPTY;
+		} else {
+			return DictCache.getValue(category.split(StringPool.UNDERSCORE)[0], Func.toInt(category.split(StringPool.UNDERSCORE)[1]));
+		}
+	}
+
+}

+ 165 - 0
src/main/java/org/springblade/job/controller/JobInfoController.java

@@ -0,0 +1,165 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.job.pojo.entity.JobInfo;
+import org.springblade.job.service.IJobInfoService;
+import org.springblade.job.pojo.vo.JobInfoVO;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 任务信息表 控制器
+ *
+ * @author BladeX
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping(AppConstant.APPLICATION_JOB_NAME + "/job-info")
+@Tag(name = "任务信息表", description = "任务信息表接口")
+public class JobInfoController extends BladeController {
+
+	private final IJobInfoService jobInfoService;
+
+	/**
+	 * 任务信息表 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "详情", description = "传入jobInfo")
+	public R<JobInfo> detail(JobInfo jobInfo) {
+		JobInfo detail = jobInfoService.getOne(Condition.getQueryWrapper(jobInfo));
+		return R.data(detail);
+	}
+
+	/**
+	 * 任务信息表 分页
+	 */
+	@GetMapping("/list")
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "分页", description = "传入jobInfo")
+	public R<IPage<JobInfo>> list(@Parameter(hidden = true) @RequestParam Map<String, Object> jobInfo, Query query) {
+		IPage<JobInfo> pages = jobInfoService.page(Condition.getPage(query), Condition.getQueryWrapper(jobInfo, JobInfo.class));
+		return R.data(pages);
+	}
+
+	/**
+	 * 任务信息表 自定义分页
+	 */
+	@GetMapping("/page")
+	@ApiOperationSupport(order = 3)
+	@Operation(summary = "分页", description = "传入jobInfo")
+	public R<IPage<JobInfoVO>> page(JobInfoVO jobInfo, Query query) {
+		IPage<JobInfoVO> pages = jobInfoService.selectJobInfoPage(Condition.getPage(query), jobInfo);
+		return R.data(pages);
+	}
+
+	/**
+	 * 任务信息表 新增
+	 */
+	@PostMapping("/save")
+	@ApiOperationSupport(order = 4)
+	@Operation(summary = "新增", description = "传入jobInfo")
+	public R save(@Valid @RequestBody JobInfo jobInfo) {
+		return R.status(jobInfoService.save(jobInfo));
+	}
+
+	/**
+	 * 任务信息表 修改
+	 */
+	@PostMapping("/update")
+	@ApiOperationSupport(order = 5)
+	@Operation(summary = "修改", description = "传入jobInfo")
+	public R update(@Valid @RequestBody JobInfo jobInfo) {
+		return R.status(jobInfoService.updateById(jobInfo));
+	}
+
+	/**
+	 * 任务信息表 新增或修改
+	 */
+	@PostMapping("/submit")
+	@ApiOperationSupport(order = 6)
+	@Operation(summary = "新增或修改", description = "传入jobInfo")
+	public R submit(@Valid @RequestBody JobInfo jobInfo) {
+		return R.status(jobInfoService.submitAndSync(jobInfo));
+	}
+
+	/**
+	 * 任务信息表 删除
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 7)
+	@Operation(summary = "删除", description = "传入ids")
+	public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(jobInfoService.removeAndSync(Func.toLongList(ids)));
+	}
+
+	/**
+	 * 任务信息表 变更状态
+	 */
+	@PostMapping("/change")
+	@ApiOperationSupport(order = 8)
+	@Operation(summary = "变更状态", description = "传入id与status")
+	public R change(@Parameter(description = "主键", required = true) @RequestParam Long id, @Parameter(description = "是否启用", required = true) @RequestParam Integer enable) {
+		return R.status(jobInfoService.changeServerJob(id, enable));
+	}
+
+	/**
+	 * 运行服务
+	 */
+	@PostMapping("run")
+	@ApiOperationSupport(order = 9)
+	@Operation(summary = "运行服务", description = "传入jobInfoId")
+	public R run(@Parameter(description = "主键", required = true) @RequestParam Long id) {
+		return R.status(jobInfoService.runServerJob(id));
+	}
+
+
+	/**
+	 * 任务信息数据同步
+	 */
+	@PostMapping("sync")
+	@ApiOperationSupport(order = 10)
+	@Operation(summary = "任务信息数据同步", description = "任务信息数据同步")
+	public R sync() {
+		return R.status(jobInfoService.sync());
+	}
+
+}

+ 163 - 0
src/main/java/org/springblade/job/controller/JobServerController.java

@@ -0,0 +1,163 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.job.pojo.entity.JobServer;
+import org.springblade.job.service.IJobServerService;
+import org.springblade.job.pojo.vo.JobServerVO;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 任务服务表 控制器
+ *
+ * @author BladeX
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping(AppConstant.APPLICATION_JOB_NAME + "/job-server")
+@Tag(name = "任务服务表", description = "任务服务表接口")
+public class JobServerController extends BladeController {
+
+	private final IJobServerService jobServerService;
+
+	/**
+	 * 任务服务表 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "详情", description = "传入jobServer")
+	public R<JobServer> detail(JobServer jobServer) {
+		JobServer detail = jobServerService.getOne(Condition.getQueryWrapper(jobServer));
+		return R.data(detail);
+	}
+
+	/**
+	 * 任务服务表 分页
+	 */
+	@GetMapping("/list")
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "分页", description = "传入jobServer")
+	public R<IPage<JobServer>> list(@Parameter(hidden = true) @RequestParam Map<String, Object> jobServer, Query query) {
+		IPage<JobServer> pages = jobServerService.page(Condition.getPage(query), Condition.getQueryWrapper(jobServer, JobServer.class));
+		return R.data(pages);
+	}
+
+	/**
+	 * 任务服务表 自定义分页
+	 */
+	@GetMapping("/page")
+	@ApiOperationSupport(order = 3)
+	@Operation(summary = "分页", description = "传入jobServer")
+	public R<IPage<JobServerVO>> page(JobServerVO jobServer, Query query) {
+		IPage<JobServerVO> pages = jobServerService.selectJobServerPage(Condition.getPage(query), jobServer);
+		return R.data(pages);
+	}
+
+	/**
+	 * 任务服务表 新增
+	 */
+	@PostMapping("/save")
+	@ApiOperationSupport(order = 4)
+	@Operation(summary = "新增", description = "传入jobServer")
+	public R save(@Valid @RequestBody JobServer jobServer) {
+		return R.status(jobServerService.save(jobServer));
+	}
+
+	/**
+	 * 任务服务表 修改
+	 */
+	@PostMapping("/update")
+	@ApiOperationSupport(order = 5)
+	@Operation(summary = "修改", description = "传入jobServer")
+	public R update(@Valid @RequestBody JobServer jobServer) {
+		return R.status(jobServerService.updateById(jobServer));
+	}
+
+	/**
+	 * 任务服务表 新增或修改
+	 */
+	@PostMapping("/submit")
+	@ApiOperationSupport(order = 6)
+	@Operation(summary = "新增或修改", description = "传入jobServer")
+	public R submit(@Valid @RequestBody JobServer jobServer) {
+		return R.status(jobServerService.submitAndSync(jobServer));
+	}
+
+	/**
+	 * 任务服务表 删除
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 7)
+	@Operation(summary = "逻辑删除", description = "传入ids")
+	public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(jobServerService.deleteLogic(Func.toLongList(ids)));
+	}
+
+	/**
+	 * 应用服务信息 列表
+	 */
+	@GetMapping("/select")
+	@ApiOperationSupport(order = 8)
+	@Operation(summary = "应用服务信息", description = "应用服务信息")
+	public R select() {
+		List<JobServer> list = jobServerService.list();
+		list.forEach(jobServer -> jobServer.setJobAppName(
+			jobServer.getJobAppName() + StringPool.COLON + StringPool.SPACE + StringPool.LEFT_BRACKET +
+				jobServer.getJobServerName() + StringPool.SPACE + StringPool.DASH + StringPool.SPACE + jobServer.getJobServerUrl() + StringPool.RIGHT_BRACKET)
+		);
+		return R.data(list);
+	}
+
+	/**
+	 * 任务服务数据同步
+	 */
+	@PostMapping("sync")
+	@ApiOperationSupport(order = 9)
+	@Operation(summary = "任务服务数据同步", description = "任务服务数据同步")
+	public R sync() {
+		jobServerService.list().forEach(jobServerService::sync);
+		return R.success("同步完毕");
+	}
+
+
+}

+ 51 - 0
src/main/java/org/springblade/job/mapper/JobInfoMapper.java

@@ -0,0 +1,51 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.job.pojo.entity.JobInfo;
+import org.springblade.job.pojo.vo.JobInfoVO;
+
+import java.util.List;
+
+/**
+ * 任务信息表 Mapper 接口
+ *
+ * @author BladeX
+ */
+public interface JobInfoMapper extends BaseMapper<JobInfo> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param jobInfo
+	 * @return
+	 */
+	List<JobInfoVO> selectJobInfoPage(IPage page, JobInfoVO jobInfo);
+
+}

+ 52 - 0
src/main/java/org/springblade/job/mapper/JobInfoMapper.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.job.mapper.JobInfoMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="jobInfoResultMap" type="org.springblade.job.pojo.entity.JobInfo">
+        <result column="id" property="id"/>
+        <result column="job_server_id" property="jobServerId"/>
+        <result column="job_id" property="jobId"/>
+        <result column="job_name" property="jobName"/>
+        <result column="job_description" property="jobDescription"/>
+        <result column="job_params" property="jobParams"/>
+        <result column="time_expression_type" property="timeExpressionType"/>
+        <result column="time_expression" property="timeExpression"/>
+        <result column="execute_type" property="executeType"/>
+        <result column="processor_type" property="processorType"/>
+        <result column="processor_info" property="processorInfo"/>
+        <result column="max_instance_num" property="maxInstanceNum"/>
+        <result column="concurrency" property="concurrency"/>
+        <result column="instance_time_limit" property="instanceTimeLimit"/>
+        <result column="instance_retry_num" property="instanceRetryNum"/>
+        <result column="task_retry_num" property="taskRetryNum"/>
+        <result column="min_cpu_cores" property="minCpuCores"/>
+        <result column="min_memory_space" property="minMemorySpace"/>
+        <result column="min_disk_space" property="minDiskSpace"/>
+        <result column="designated_workers" property="designatedWorkers"/>
+        <result column="max_worker_count" property="maxWorkerCount"/>
+        <result column="notify_user_ids" property="notifyUserIds"/>
+        <result column="enable" property="enable"/>
+        <result column="dispatch_strategy" property="dispatchStrategy"/>
+        <result column="lifecycle" property="lifecycle"/>
+        <result column="alert_threshold" property="alertThreshold"/>
+        <result column="statistic_window_len" property="statisticWindowLen"/>
+        <result column="silence_window_len" property="silenceWindowLen"/>
+        <result column="log_type" property="logType"/>
+        <result column="log_level" property="logLevel"/>
+        <result column="extra" property="extra"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_dept" property="createDept"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+    </resultMap>
+
+
+    <select id="selectJobInfoPage" resultMap="jobInfoResultMap">
+        select * from blade_job_info where is_deleted = 0
+    </select>
+
+</mapper>

+ 51 - 0
src/main/java/org/springblade/job/mapper/JobServerMapper.java

@@ -0,0 +1,51 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.job.pojo.entity.JobServer;
+import org.springblade.job.pojo.vo.JobServerVO;
+
+import java.util.List;
+
+/**
+ * 任务服务表 Mapper 接口
+ *
+ * @author BladeX
+ */
+public interface JobServerMapper extends BaseMapper<JobServer> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param jobServer
+	 * @return
+	 */
+	List<JobServerVO> selectJobServerPage(IPage page, JobServerVO jobServer);
+
+}

+ 27 - 0
src/main/java/org/springblade/job/mapper/JobServerMapper.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.job.mapper.JobServerMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="jobServerResultMap" type="org.springblade.job.pojo.entity.JobServer">
+        <result column="id" property="id"/>
+        <result column="job_server_name" property="jobServerName"/>
+        <result column="job_server_url" property="jobServerUrl"/>
+        <result column="job_app_name" property="jobAppName"/>
+        <result column="job_app_password" property="jobAppPassword"/>
+        <result column="job_remark" property="jobRemark"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_dept" property="createDept"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+    </resultMap>
+
+
+    <select id="selectJobServerPage" resultMap="jobServerResultMap">
+        select * from blade_job_server where is_deleted = 0
+    </select>
+
+</mapper>

+ 56 - 0
src/main/java/org/springblade/job/pojo/dto/JobDTO.java

@@ -0,0 +1,56 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.pojo.dto;
+
+import lombok.Data;
+import org.springblade.job.pojo.entity.JobInfo;
+import org.springblade.job.pojo.entity.JobServer;
+import tech.powerjob.client.PowerJobClient;
+
+/**
+ * 任务数据DTO
+ *
+ * @author Chill
+ */
+@Data
+public class JobDTO {
+
+	/**
+	 * 任务信息类
+	 */
+	private JobInfo jobInfo;
+
+	/**
+	 * 任务服务类
+	 */
+	private JobServer jobServer;
+
+	/**
+	 * 任务客户端类
+	 */
+	private PowerJobClient powerJobClient;
+
+}

+ 202 - 0
src/main/java/org/springblade/job/pojo/entity/JobInfo.java

@@ -0,0 +1,202 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+
+/**
+ * 任务信息表 实体类
+ *
+ * @author BladeX
+ */
+@Data
+@TableName("blade_job_info")
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "任务信息表")
+public class JobInfo extends BaseEntity {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 任务服务ID
+	 */
+	@Schema(description = "任务服务ID")
+	private Long jobServerId;
+	/**
+	 * 任务 ID,可选,null 代表创建任务,否则填写需要修改的任务 ID
+	 */
+	@Schema(description = "任务 ID,可选,null 代表创建任务,否则填写需要修改的任务 ID")
+	private Long jobId;
+	/**
+	 * 任务名称
+	 */
+	@Schema(description = "任务名称")
+	private String jobName;
+	/**
+	 * 任务描述
+	 */
+	@Schema(description = "任务描述")
+	private String jobDescription;
+	/**
+	 * 任务参数,Processor#process 方法入参 TaskContext 对象的 jobParams 字段
+	 */
+	@Schema(description = "任务参数,Processor#process 方法入参 TaskContext 对象的 jobParams 字段")
+	private String jobParams;
+	/**
+	 * 时间表达式类型,枚举值
+	 */
+	@Schema(description = "时间表达式类型,枚举值")
+	private Integer timeExpressionType;
+	/**
+	 * 时间表达式,填写类型由 timeExpressionType 决定,比如 CRON 需要填写 CRON 表达式
+	 */
+	@Schema(description = "时间表达式,填写类型由 timeExpressionType 决定,比如 CRON 需要填写 CRON 表达式")
+	private String timeExpression;
+	/**
+	 * 执行类型,枚举值
+	 */
+	@Schema(description = "执行类型,枚举值")
+	private Integer executeType;
+	/**
+	 * 处理器类型,枚举值
+	 */
+	@Schema(description = "处理器类型,枚举值")
+	private Integer processorType;
+	/**
+	 * 处理器参数,填写类型由 processorType 决定,如Java 处理器需要填写全限定类名,如:com.github.kfcfans.oms.processors.demo.MapReduceProcessorDemo
+	 */
+	@Schema(description = "处理器参数,填写类型由 processorType 决定,如Java 处理器需要填写全限定类名,如:com.github.kfcfans.oms.processors.demo.MapReduceProcessorDemo")
+	private String processorInfo;
+	/**
+	 * 最大实例数,该任务同时执行的数量(任务和实例就像是类和对象的关系,任务被调度执行后被称为实例)
+	 */
+	@Schema(description = "最大实例数,该任务同时执行的数量(任务和实例就像是类和对象的关系,任务被调度执行后被称为实例)")
+	private Integer maxInstanceNum;
+	/**
+	 * 单机线程并发数,表示该实例执行过程中每个Worker 使用的线程数量
+	 */
+	@Schema(description = "单机线程并发数,表示该实例执行过程中每个Worker 使用的线程数量")
+	private Integer concurrency;
+	/**
+	 * 任务实例运行时间限制,0 代表无任何限制,超时会被打断并判定为执行失败
+	 */
+	@Schema(description = "任务实例运行时间限制,0 代表无任何限制,超时会被打断并判定为执行失败")
+	private Long instanceTimeLimit;
+	/**
+	 * instanceRetryNum	任务实例重试次数,整个任务失败时重试,代价大,不推荐使用
+	 */
+	@Schema(description = "instanceRetryNum	任务实例重试次数,整个任务失败时重试,代价大,不推荐使用")
+	private Integer instanceRetryNum;
+	/**
+	 * taskRetryNum	Task 重试次数,每个子 Task 失败后单独重试,代价小,推荐使用
+	 */
+	@Schema(description = "taskRetryNum	Task 重试次数,每个子 Task 失败后单独重试,代价小,推荐使用")
+	private Integer taskRetryNum;
+	/**
+	 * minCpuCores	最小可用 CPU 核心数,CPU 可用核心数小于该值的 Worker 将不会执行该任务,0 代表无任何限制
+	 */
+	@Schema(description = "minCpuCores	最小可用 CPU 核心数,CPU 可用核心数小于该值的 Worker 将不会执行该任务,0 代表无任何限制")
+	private BigDecimal minCpuCores;
+	/**
+	 * 最小内存大小(GB),可用内存小于该值的Worker 将不会执行该任务,0 代表无任何限制
+	 */
+	@Schema(description = "最小内存大小(GB),可用内存小于该值的Worker 将不会执行该任务,0 代表无任何限制")
+	private BigDecimal minMemorySpace;
+	/**
+	 * 最小磁盘大小(GB),可用磁盘空间小于该值的Worker 将不会执行该任务,0 代表无任何限制
+	 */
+	@Schema(description = "最小磁盘大小(GB),可用磁盘空间小于该值的Worker 将不会执行该任务,0 代表无任何限制")
+	private BigDecimal minDiskSpace;
+	/**
+	 * 指定机器执行,设置该参数后只有列表中的机器允许执行该任务,空代表不指定机器
+	 */
+	@Schema(description = "指定机器执行,设置该参数后只有列表中的机器允许执行该任务,空代表不指定机器")
+	private String designatedWorkers;
+	/**
+	 * 最大执行机器数量,限定调动执行的机器数量,0代表无限制
+	 */
+	@Schema(description = "最大执行机器数量,限定调动执行的机器数量,0代表无限制")
+	private Integer maxWorkerCount;
+	/**
+	 * 接收报警的用户 ID 列表
+	 */
+	@Schema(description = "接收报警的用户 ID 列表")
+	private String notifyUserIds;
+	/**
+	 * 是否启用该任务,未启用的任务不会被调度
+	 */
+	@Schema(description = "是否启用该任务,未启用的任务不会被调度")
+	private Integer enable;
+	/**
+	 * 调度策略,枚举,目前支持随机(RANDOM)和 健康度优先(HEALTH_FIRST)
+	 */
+	@Schema(description = "调度策略,枚举,目前支持随机(RANDOM)和 健康度优先(HEALTH_FIRST)")
+	private Integer dispatchStrategy;
+	/**
+	 * lifecycle	生命周期(预留,用于指定定时调度任务的生效时间范围)
+	 */
+	@Schema(description = "lifecycle	生命周期(预留,用于指定定时调度任务的生效时间范围)")
+	private String lifecycle;
+	/**
+	 * 错误阈值,0代表不限制
+	 */
+	@Schema(description = "错误阈值,0代表不限制")
+	private Integer alertThreshold;
+	/**
+	 * 统计的窗口长度(s),0代表不限制
+	 */
+	@Schema(description = "统计的窗口长度(s),0代表不限制")
+	private Integer statisticWindowLen;
+	/**
+	 * 沉默时间窗口(s),0代表不限制
+	 */
+	@Schema(description = "沉默时间窗口(s),0代表不限制")
+	private Integer silenceWindowLen;
+	/**
+	 * 日志配置
+	 */
+	@Schema(description = "日志配置")
+	private Integer logType;
+	/**
+	 * 日志配置
+	 */
+	@Schema(description = "日志级别")
+	private Integer logLevel;
+	/**
+	 * 扩展字段(供开发者使用,用于功能扩展,powerjob 自身不会使用该字段)
+	 */
+	@Schema(description = "扩展字段(供开发者使用,用于功能扩展,powerjob 自身不会使用该字段)")
+	private String extra;
+
+}

+ 76 - 0
src/main/java/org/springblade/job/pojo/entity/JobServer.java

@@ -0,0 +1,76 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+
+import java.io.Serial;
+
+/**
+ * 任务服务表 实体类
+ *
+ * @author BladeX
+ */
+@Data
+@TableName("blade_job_server")
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "任务服务表")
+public class JobServer extends BaseEntity {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 任务服务名称
+	 */
+	@Schema(description = "任务服务名称")
+	private String jobServerName;
+	/**
+	 * 任务服务器地址
+	 */
+	@Schema(description = "任务服务器地址")
+	private String jobServerUrl;
+	/**
+	 * 任务应用名称
+	 */
+	@Schema(description = "任务应用名称")
+	private String jobAppName;
+	/**
+	 * 任务应用密码
+	 */
+	@Schema(description = "任务应用密码")
+	private String jobAppPassword;
+	/**
+	 * 任务备注
+	 */
+	@Schema(description = "任务备注")
+	private String jobRemark;
+
+}

+ 45 - 0
src/main/java/org/springblade/job/pojo/vo/JobInfoVO.java

@@ -0,0 +1,45 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.pojo.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.job.pojo.entity.JobInfo;
+
+import java.io.Serial;
+
+/**
+ * 任务信息表 视图实体类
+ *
+ * @author BladeX
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class JobInfoVO extends JobInfo {
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+}

+ 45 - 0
src/main/java/org/springblade/job/pojo/vo/JobServerVO.java

@@ -0,0 +1,45 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.pojo.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.job.pojo.entity.JobServer;
+
+import java.io.Serial;
+
+/**
+ * 任务服务表 视图实体类
+ *
+ * @author BladeX
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class JobServerVO extends JobServer {
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+}

+ 35 - 0
src/main/java/org/springblade/job/processor/ProcessorDemo.java

@@ -0,0 +1,35 @@
+package org.springblade.job.processor;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import tech.powerjob.worker.core.processor.ProcessResult;
+import tech.powerjob.worker.core.processor.TaskContext;
+import tech.powerjob.worker.core.processor.sdk.BasicProcessor;
+import tech.powerjob.worker.log.OmsLogger;
+
+
+// 支持 SpringBean 的形式
+@Slf4j
+@Component
+public class ProcessorDemo implements BasicProcessor {
+
+	@Override
+	public ProcessResult process(TaskContext context) {
+
+		// 在线日志功能,可以直接在控制台查看任务日志,非常便捷
+		OmsLogger omsLogger = context.getOmsLogger();
+		omsLogger.info("BasicProcessorDemo start to process, current JobParams is {}.", context.getJobParams());
+
+		// TaskContext为任务的上下文信息,包含了在控制台录入的任务元数据,常用字段为
+		// jobParams(任务参数,在控制台录入),instanceParams(任务实例参数,通过 OpenAPI 触发的任务实例才可能存在该参数)
+
+		// 进行实际处理...
+		log.info("============== ProcessorDemo#process ==============");
+		log.info("hello blade");
+		log.info("============== ProcessorDemo#process ==============");
+
+		// 返回结果,该结果会被持久化到数据库,在前端页面直接查看,极为方便
+		return new ProcessResult(true, "result is success");
+	}
+
+}

+ 88 - 0
src/main/java/org/springblade/job/service/IJobInfoService.java

@@ -0,0 +1,88 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.job.pojo.entity.JobInfo;
+import org.springblade.job.pojo.vo.JobInfoVO;
+
+import java.util.List;
+
+/**
+ * 任务信息表 服务类
+ *
+ * @author BladeX
+ */
+public interface IJobInfoService extends BaseService<JobInfo> {
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param jobInfo
+	 * @return
+	 */
+	IPage<JobInfoVO> selectJobInfoPage(IPage<JobInfoVO> page, JobInfoVO jobInfo);
+
+	/**
+	 * 保存并同步
+	 *
+	 * @return
+	 */
+	Boolean submitAndSync(JobInfo jobInfo);
+
+	/**
+	 * 删除并同步
+	 *
+	 * @return
+	 */
+	Boolean removeAndSync(List<Long> ids);
+
+	/**
+	 * 启用禁用服务
+	 *
+	 * @param id     任务服务ID
+	 * @param enable 是否启用
+	 * @return
+	 */
+	Boolean changeServerJob(Long id, Integer enable);
+
+	/**
+	 * 运行服务
+	 *
+	 * @param id 任务服务ID
+	 * @return
+	 */
+	Boolean runServerJob(Long id);
+
+	/**
+	 * 数据同步
+	 *
+	 * @return
+	 */
+	Boolean sync();
+
+}

+ 64 - 0
src/main/java/org/springblade/job/service/IJobServerService.java

@@ -0,0 +1,64 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.job.pojo.entity.JobServer;
+import org.springblade.job.pojo.vo.JobServerVO;
+
+/**
+ * 任务服务表 服务类
+ *
+ * @author BladeX
+ */
+public interface IJobServerService extends BaseService<JobServer> {
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param jobServer
+	 * @return
+	 */
+	IPage<JobServerVO> selectJobServerPage(IPage<JobServerVO> page, JobServerVO jobServer);
+
+	/**
+	 * 保存并同步
+	 *
+	 * @param jobServer
+	 * @return
+	 */
+	Boolean submitAndSync(JobServer jobServer);
+
+	/**
+	 * 同步数据
+	 *
+	 * @param jobServer
+	 * @return
+	 */
+	Boolean sync(JobServer jobServer);
+
+}

+ 344 - 0
src/main/java/org/springblade/job/service/impl/JobInfoServiceImpl.java

@@ -0,0 +1,344 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.powerjob.constant.PowerJobConstant;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.ConvertUtil;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.job.pojo.dto.JobDTO;
+import org.springblade.job.pojo.entity.JobInfo;
+import org.springblade.job.pojo.entity.JobServer;
+import org.springblade.job.mapper.JobInfoMapper;
+import org.springblade.job.service.IJobInfoService;
+import org.springblade.job.service.IJobServerService;
+import org.springblade.job.pojo.vo.JobInfoVO;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import tech.powerjob.client.PowerJobClient;
+import tech.powerjob.common.enums.DispatchStrategy;
+import tech.powerjob.common.enums.ExecuteType;
+import tech.powerjob.common.enums.ProcessorType;
+import tech.powerjob.common.enums.TimeExpressionType;
+import tech.powerjob.common.model.AlarmConfig;
+import tech.powerjob.common.model.LifeCycle;
+import tech.powerjob.common.model.LogConfig;
+import tech.powerjob.common.request.http.SaveJobInfoRequest;
+import tech.powerjob.common.response.JobInfoDTO;
+import tech.powerjob.common.response.ResultDTO;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 任务信息表 服务实现类
+ *
+ * @author BladeX
+ */
+@Service
+@AllArgsConstructor
+public class JobInfoServiceImpl extends BaseServiceImpl<JobInfoMapper, JobInfo> implements IJobInfoService {
+	private final IJobServerService jobServerService;
+
+	@Override
+	public IPage<JobInfoVO> selectJobInfoPage(IPage<JobInfoVO> page, JobInfoVO jobInfo) {
+		return page.setRecords(baseMapper.selectJobInfoPage(page, jobInfo));
+	}
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public Boolean submitAndSync(JobInfo jobInfo) {
+		//获取应用分组服务端信息
+		JobServer jobServer = jobServerService.getById(jobInfo.getJobServerId());
+		//构建Job客户端
+		PowerJobClient client = new PowerJobClient(jobServer.getJobServerUrl(), jobServer.getJobAppName(), jobServer.getJobAppPassword());
+		SaveJobInfoRequest request = convertToServer(jobInfo);
+		//获取上传结果
+		ResultDTO<Long> result = client.saveJob(request);
+		if (result.isSuccess()) {
+			jobInfo.setJobId(result.getData());
+			return this.saveOrUpdate(jobInfo);
+		} else {
+			throw new ServiceException(result.getMessage());
+		}
+	}
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public Boolean removeAndSync(List<Long> ids) {
+		ids.forEach(id -> {
+			JobDTO jobDTO = JobData(id);
+			if (Func.isNotEmpty(jobDTO)) {
+				JobInfo jobInfo = jobDTO.getJobInfo();
+				PowerJobClient powerJobClient = jobDTO.getPowerJobClient();
+				//删除服务数据
+				ResultDTO<Void> result = powerJobClient.deleteJob(jobInfo.getJobId());
+				if (result.isSuccess()) {
+					this.removeById(id);
+				} else {
+					throw new ServiceException(result.getMessage());
+				}
+			}
+		});
+		return true;
+	}
+
+	@Override
+	public Boolean changeServerJob(Long id, Integer enable) {
+		JobDTO jobDTO = JobData(id);
+		if (Func.isNotEmpty(jobDTO)) {
+			JobInfo jobInfo = jobDTO.getJobInfo();
+			PowerJobClient powerJobClient = jobDTO.getPowerJobClient();
+			//更换服务端状态
+			ResultDTO<Void> result = (enable == PowerJobConstant.JOB_ENABLED) ?
+				powerJobClient.enableJob(jobInfo.getJobId()) :
+				powerJobClient.disableJob(jobInfo.getJobId());
+			//删除客户端数据
+			if (result.isSuccess()) {
+				return this.update(Wrappers.<JobInfo>update().lambda().set(JobInfo::getEnable, enable).eq(JobInfo::getId, id));
+			} else {
+				throw new ServiceException(result.getMessage());
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public Boolean runServerJob(Long id) {
+		JobDTO jobDTO = JobData(id);
+		if (Func.isNotEmpty(jobDTO)) {
+			JobInfo jobInfo = jobDTO.getJobInfo();
+			PowerJobClient powerJobClient = jobDTO.getPowerJobClient();
+			ResultDTO<Long> result = powerJobClient.runJob(jobInfo.getJobId());
+			return result.isSuccess();
+		}
+		return false;
+	}
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public Boolean sync() {
+		//任务信息列表
+		List<JobInfo> jobInfos = this.list();
+		//任务服务列表
+		List<JobServer> jobServers = jobServerService.list();
+		//按应用分组
+		Map<Long, List<JobInfo>> jobGroups = jobInfos.stream().collect(Collectors.groupingBy(JobInfo::getJobServerId));
+		//处理服务端数据下载
+		jobServers.forEach(jobServer -> {
+			//构建Job客户端
+			PowerJobClient client = new PowerJobClient(jobServer.getJobServerUrl(), jobServer.getJobAppName(), jobServer.getJobAppPassword());
+			//从服务端获取数据
+			List<JobInfoDTO> serverInfoList = Optional.ofNullable(client.fetchAllJob())
+				.filter(ResultDTO::isSuccess)
+				.map(ResultDTO::getData)
+				.orElseGet(ArrayList::new);
+			//获取客户端数据
+			List<JobInfo> localInfoList = jobGroups.get(jobServer.getId());
+			//处理需要从服务端下载的数据
+			List<JobInfoDTO> jobInfoDTOList = serverInfoList.stream()
+				.filter(serverData -> serverData.getStatus() != PowerJobConstant.JOB_DELETED)
+				.filter(serverData -> Func.isEmpty(localInfoList) || localInfoList.stream().noneMatch(localData -> Func.equalsSafe(localData.getJobId(), serverData.getId())))
+				.collect(Collectors.toList());
+			List<JobInfo> dataToDownload = convertToLocalList(jobInfoDTOList, jobServer.getId());
+			//调用本地Service保存数据
+			this.saveBatch(dataToDownload);
+		});
+		//处理客户端数据上传
+		jobGroups.forEach((jobServerId, localInfoList) -> {
+			//获取应用分组服务端信息
+			JobServer jobServer = jobServers.stream().filter(js -> Func.equalsSafe(js.getId(), jobServerId))
+				.findFirst().orElseThrow(() -> new ServiceException(PowerJobConstant.JOB_SYNC_ALERT));
+			//构建Job客户端
+			PowerJobClient client = new PowerJobClient(jobServer.getJobServerUrl(), jobServer.getJobAppName(), jobServer.getJobAppPassword());
+			//处理需要上传到服务端的数据
+			localInfoList.forEach(localData -> {
+				//转换数据格式
+				SaveJobInfoRequest data = convertToServer(localData);
+				//调用OpenAPI接口上传数据
+				ResultDTO<Long> saveResult = client.saveJob(data);
+				if (saveResult.isSuccess()) {
+					//更新服务端JobId至客户端
+					this.update(Wrappers.<JobInfo>update().lambda().set(JobInfo::getJobId, saveResult.getData()).eq(JobInfo::getId, localData.getId()));
+				} else {
+					throw new ServiceException(saveResult.getMessage());
+				}
+			});
+		});
+		return true;
+	}
+
+	/**
+	 * 获取Job数据集合
+	 *
+	 * @param jobInfoId 服务信息ID
+	 * @return PowerJobClient
+	 */
+	public JobDTO JobData(Long jobInfoId) {
+		//构建DTO类
+		JobDTO jobDTO = new JobDTO();
+		//获取任务信息
+		JobInfo jobInfo = this.getById(jobInfoId);
+		jobDTO.setJobInfo(jobInfo);
+		if (Func.isEmpty(jobInfo.getJobId())) {
+			throw new ServiceException(PowerJobConstant.JOB_SYNC_ALERT);
+		}
+		if (Func.isNotEmpty(jobInfo.getJobServerId())) {
+			//获取应用分组服务端信息
+			JobServer jobServer = jobServerService.getById(jobInfo.getJobServerId());
+			jobDTO.setJobServer(jobServer);
+			//构建Job客户端
+			PowerJobClient powerJobClient = new PowerJobClient(jobServer.getJobServerUrl(), jobServer.getJobAppName(), jobServer.getJobAppPassword());
+			jobDTO.setPowerJobClient(powerJobClient);
+			return jobDTO;
+		}
+		return null;
+	}
+
+	/**
+	 * 服务端Job列表转换
+	 *
+	 * @param jobInfoList 本地任务信息列表
+	 * @return List<SaveJobInfoRequest>
+	 */
+	public List<SaveJobInfoRequest> convertToServerList(List<JobInfo> jobInfoList) {
+		return jobInfoList.stream().map(this::convertToServer).collect(Collectors.toList());
+	}
+
+	/**
+	 * 本地Job列表转换
+	 *
+	 * @param jobInfoDTOList 服务端任务信息列表
+	 * @return List<JobInfo>
+	 */
+	public List<JobInfo> convertToLocalList(List<JobInfoDTO> jobInfoDTOList, Long jobServerId) {
+		return jobInfoDTOList.stream().map(jobInfoDTO -> convertToLocal(jobInfoDTO, jobServerId)).collect(Collectors.toList());
+	}
+
+	/**
+	 * 服务端Job单个转换
+	 *
+	 * @param jobInfo 本地任务信息
+	 * @return SaveJobInfoRequest
+	 */
+	public SaveJobInfoRequest convertToServer(JobInfo jobInfo) {
+		SaveJobInfoRequest saveJobInfoRequest = new SaveJobInfoRequest();
+		if (Func.toLong(jobInfo.getJobId()) > 0L) {
+			saveJobInfoRequest.setId(jobInfo.getJobId());
+		}
+		saveJobInfoRequest.setJobName(jobInfo.getJobName());
+		saveJobInfoRequest.setJobDescription(jobInfo.getJobDescription());
+		saveJobInfoRequest.setJobParams(jobInfo.getJobParams());
+		saveJobInfoRequest.setTimeExpressionType(TimeExpressionType.of(jobInfo.getTimeExpressionType()));
+		saveJobInfoRequest.setTimeExpression(jobInfo.getTimeExpression());
+		saveJobInfoRequest.setExecuteType(ExecuteType.of(jobInfo.getExecuteType()));
+		saveJobInfoRequest.setProcessorType(ProcessorType.of(jobInfo.getProcessorType()));
+		saveJobInfoRequest.setProcessorInfo(jobInfo.getProcessorInfo());
+		saveJobInfoRequest.setMaxInstanceNum(jobInfo.getMaxInstanceNum());
+		saveJobInfoRequest.setConcurrency(jobInfo.getConcurrency());
+		saveJobInfoRequest.setInstanceTimeLimit(jobInfo.getInstanceTimeLimit());
+		saveJobInfoRequest.setInstanceRetryNum(jobInfo.getInstanceRetryNum());
+		saveJobInfoRequest.setTaskRetryNum(jobInfo.getTaskRetryNum());
+		saveJobInfoRequest.setMinCpuCores(jobInfo.getMinCpuCores().doubleValue());
+		saveJobInfoRequest.setMinMemorySpace(jobInfo.getMinMemorySpace().doubleValue());
+		saveJobInfoRequest.setMinDiskSpace(jobInfo.getMinDiskSpace().doubleValue());
+		saveJobInfoRequest.setDesignatedWorkers(jobInfo.getDesignatedWorkers());
+		saveJobInfoRequest.setMaxWorkerCount(jobInfo.getMaxWorkerCount());
+		saveJobInfoRequest.setNotifyUserIds(Func.toLongList(jobInfo.getNotifyUserIds()));
+		saveJobInfoRequest.setEnable(jobInfo.getEnable() == 1);
+		saveJobInfoRequest.setDispatchStrategy(DispatchStrategy.of(jobInfo.getDispatchStrategy()));
+		saveJobInfoRequest.setAlarmConfig(new AlarmConfig(jobInfo.getAlertThreshold(), jobInfo.getStatisticWindowLen(), jobInfo.getSilenceWindowLen()));
+		saveJobInfoRequest.setLogConfig(new LogConfig().setLevel(jobInfo.getLogLevel()).setType(jobInfo.getLogType()));
+		if (Func.isNotEmpty(jobInfo.getLifecycle())) {
+			LifeCycle lifeCycle = new LifeCycle();
+			String[] lifeCycleArr = Func.toStrArray(jobInfo.getLifecycle());
+			lifeCycle.setStart(DateUtil.parse(lifeCycleArr[0], DateUtil.PATTERN_DATETIME).getTime());
+			lifeCycle.setEnd(DateUtil.parse(lifeCycleArr[1], DateUtil.PATTERN_DATETIME).getTime());
+			saveJobInfoRequest.setLifeCycle(lifeCycle);
+		}
+		saveJobInfoRequest.setExtra(jobInfo.getExtra());
+		return saveJobInfoRequest;
+	}
+
+	/**
+	 * 本地Job单个转换
+	 *
+	 * @param jobInfoDTO 服务端任务信息
+	 * @return SaveJobInfoRequest
+	 */
+	public JobInfo convertToLocal(JobInfoDTO jobInfoDTO, Long jobServerId) {
+		JobInfo jobInfo = new JobInfo();
+		jobInfo.setJobServerId(jobServerId);
+		jobInfo.setJobId(jobInfoDTO.getId());
+		jobInfo.setJobName(jobInfoDTO.getJobName());
+		jobInfo.setJobDescription(jobInfoDTO.getJobDescription());
+		jobInfo.setJobParams(jobInfoDTO.getJobParams());
+		jobInfo.setTimeExpressionType(jobInfoDTO.getTimeExpressionType());
+		jobInfo.setTimeExpression(jobInfoDTO.getTimeExpression());
+		jobInfo.setExecuteType(jobInfoDTO.getExecuteType());
+		jobInfo.setProcessorType(jobInfoDTO.getProcessorType());
+		jobInfo.setProcessorInfo(jobInfoDTO.getProcessorInfo());
+		jobInfo.setMaxInstanceNum(jobInfoDTO.getMaxInstanceNum());
+		jobInfo.setConcurrency(jobInfoDTO.getConcurrency());
+		jobInfo.setInstanceTimeLimit(jobInfoDTO.getInstanceTimeLimit());
+		jobInfo.setInstanceRetryNum(jobInfoDTO.getInstanceRetryNum());
+		jobInfo.setTaskRetryNum(jobInfoDTO.getTaskRetryNum());
+		jobInfo.setMinCpuCores(ConvertUtil.convert(jobInfoDTO.getMinCpuCores(), BigDecimal.class));
+		jobInfo.setMinMemorySpace(ConvertUtil.convert(jobInfoDTO.getMinMemorySpace(), BigDecimal.class));
+		jobInfo.setMinDiskSpace(ConvertUtil.convert(jobInfoDTO.getMinDiskSpace(), BigDecimal.class));
+		jobInfo.setDesignatedWorkers(jobInfoDTO.getDesignatedWorkers());
+		jobInfo.setMaxWorkerCount(jobInfoDTO.getMaxWorkerCount());
+		jobInfo.setNotifyUserIds(jobInfoDTO.getNotifyUserIds());
+		jobInfo.setEnable(jobInfoDTO.getStatus());
+		jobInfo.setDispatchStrategy(jobInfoDTO.getDispatchStrategy());
+		if (Func.isNotEmpty(jobInfoDTO.getLifecycle()) && !Func.equalsSafe(jobInfoDTO.getLifecycle(), StringPool.EMPTY_JSON)) {
+			LifeCycle lifeCycle = JsonUtil.parse(jobInfoDTO.getLifecycle(), LifeCycle.class);
+			String start = DateUtil.format(new Date(lifeCycle.getStart()), DateUtil.PATTERN_DATETIME);
+			String end = DateUtil.format(new Date(lifeCycle.getEnd()), DateUtil.PATTERN_DATETIME);
+			jobInfo.setLifecycle(start + StringPool.COMMA + end);
+		}
+		if (Func.isNotEmpty(jobInfoDTO.getAlarmConfig())) {
+			jobInfo.setAlertThreshold(jobInfoDTO.getAlarmConfig().getAlertThreshold());
+			jobInfo.setStatisticWindowLen(jobInfoDTO.getAlarmConfig().getStatisticWindowLen());
+			jobInfo.setSilenceWindowLen(jobInfoDTO.getAlarmConfig().getSilenceWindowLen());
+		}
+		if (Func.isNotEmpty(jobInfoDTO.getLogConfig())) {
+			jobInfo.setLogType(jobInfoDTO.getLogConfig().getType());
+			jobInfo.setLogLevel(jobInfoDTO.getLogConfig().getLevel());
+		}
+		jobInfo.setExtra(jobInfoDTO.getExtra());
+		return jobInfo;
+	}
+
+}

+ 71 - 0
src/main/java/org/springblade/job/service/impl/JobServerServiceImpl.java

@@ -0,0 +1,71 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.job.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.springblade.core.http.util.HttpUtil;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.job.pojo.entity.JobServer;
+import org.springblade.job.mapper.JobServerMapper;
+import org.springblade.job.service.IJobServerService;
+import org.springblade.job.pojo.vo.JobServerVO;
+import org.springframework.stereotype.Service;
+import tech.powerjob.common.response.ResultDTO;
+
+/**
+ * 任务服务表 服务实现类
+ *
+ * @author BladeX
+ */
+@Service
+public class JobServerServiceImpl extends BaseServiceImpl<JobServerMapper, JobServer> implements IJobServerService {
+
+	@Override
+	public IPage<JobServerVO> selectJobServerPage(IPage<JobServerVO> page, JobServerVO jobServer) {
+		return page.setRecords(baseMapper.selectJobServerPage(page, jobServer));
+	}
+
+	@Override
+	public Boolean submitAndSync(JobServer jobServer) {
+		if (Func.isEmpty(jobServer.getId())) {
+			this.sync(jobServer);
+		}
+		return this.saveOrUpdate(jobServer);
+	}
+
+	@Override
+	public Boolean sync(JobServer jobServer) {
+		Kv appInfo = Kv.create().set("appName", jobServer.getJobAppName()).set("password", jobServer.getJobAppPassword());
+		String data = HttpUtil.postJson(jobServer.getJobServerUrl() + "/appInfo/save", JsonUtil.toJson(appInfo));
+		ResultDTO<Void> result = JsonUtil.parse(data, new TypeReference<ResultDTO<Void>>() {});
+		return result.isSuccess();
+	}
+
+}

+ 61 - 0
src/main/java/org/springblade/modules/auth/config/BladeAuthConfiguration.java

@@ -0,0 +1,61 @@
+package org.springblade.modules.auth.config;
+
+import org.springblade.core.jwt.props.JwtProperties;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.oauth2.config.OAuth2AutoConfiguration;
+import org.springblade.core.oauth2.handler.AuthorizationHandler;
+import org.springblade.core.oauth2.handler.PasswordHandler;
+import org.springblade.core.oauth2.handler.TokenHandler;
+import org.springblade.core.oauth2.props.OAuth2Properties;
+import org.springblade.core.oauth2.service.OAuth2ClientService;
+import org.springblade.core.oauth2.service.OAuth2UserService;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.tenant.BladeTenantProperties;
+import org.springblade.modules.auth.handler.BladeAuthorizationHandler;
+import org.springblade.modules.auth.handler.BladePasswordHandler;
+import org.springblade.modules.auth.handler.BladeTokenHandler;
+import org.springblade.modules.auth.service.BladeClientDetailService;
+import org.springblade.modules.auth.service.BladeUserDetailService;
+import org.springblade.modules.system.service.IUserService;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * BladeAuthConfiguration
+ *
+ * @author Chill
+ */
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureBefore(OAuth2AutoConfiguration.class)
+public class BladeAuthConfiguration {
+	@Bean
+	public AuthorizationHandler authorizationHandler(BladeRedis bladeRedis,
+													 BladeProperties bladeProperties,
+													 BladeTenantProperties tenantProperties,
+													 OAuth2Properties oAuth2Properties) {
+		return new BladeAuthorizationHandler(bladeRedis, bladeProperties, tenantProperties, oAuth2Properties);
+	}
+
+	@Bean
+	public PasswordHandler passwordHandler(OAuth2Properties properties) {
+		return new BladePasswordHandler(properties);
+	}
+
+	@Bean
+	public TokenHandler tokenHandler(JwtProperties jwtProperties) {
+		return new BladeTokenHandler(jwtProperties);
+	}
+
+	@Bean
+	public OAuth2ClientService oAuth2ClientService(JdbcTemplate jdbcTemplate) {
+		return new BladeClientDetailService(jdbcTemplate);
+	}
+
+	@Bean
+	public OAuth2UserService oAuth2UserService(IUserService userService) {
+		return new BladeUserDetailService(userService);
+	}
+
+}

+ 22 - 0
src/main/java/org/springblade/modules/auth/constant/BladeAuthConstant.java

@@ -0,0 +1,22 @@
+package org.springblade.modules.auth.constant;
+
+/**
+ * AuthorizationConstant
+ *
+ * @author Chill
+ */
+public interface BladeAuthConstant {
+
+	/**
+	 * 是否开启注册参数key
+	 */
+	String REGISTER_USER_VALUE = "account.registerUser";
+	/**
+	 * 账号锁定错误次数参数key
+	 */
+	String FAIL_COUNT_VALUE = "account.failCount";
+	/**
+	 * 账号锁定默认错误次数
+	 */
+	Integer FAIL_COUNT = 5;
+}

+ 104 - 0
src/main/java/org/springblade/modules/auth/endpoint/Oauth2SmsEndpoint.java

@@ -0,0 +1,104 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.endpoint;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oauth2.props.OAuth2Properties;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.oauth2.service.OAuth2UserService;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.SM2Util;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.modules.resource.builder.SmsBuilder;
+import org.springblade.modules.resource.utils.SmsUtil;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+import static org.springblade.core.oauth2.constant.OAuth2TokenConstant.USER_PHONE_NOT_FOUND;
+import static org.springblade.modules.resource.utils.SmsUtil.*;
+
+/**
+ * Oauth2SmsEndpoint
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@Tag(name = "用户短信认证", description = "4 - OAuth2短信认证端点")
+public class Oauth2SmsEndpoint {
+
+	/**
+	 * 短信服务构建类
+	 */
+	private final SmsBuilder smsBuilder;
+
+	/**
+	 * 用户服务类
+	 */
+	private final OAuth2UserService userService;
+
+	/**
+	 * OAuth2配置类
+	 */
+	private final OAuth2Properties properties;
+
+	/**
+	 * 短信验证码发送
+	 *
+	 * @param tenantId 租户ID
+	 * @param phone    手机号
+	 */
+	@SneakyThrows
+	@PostMapping("/oauth/sms/send-validate")
+	public R sendValidate(@RequestParam String tenantId, @RequestParam String phone) {
+		// 校验手机加密认证,防止恶意发送验证码
+		String decryptedPhone = SM2Util.decrypt(phone, properties.getPublicKey(), properties.getPrivateKey());
+		if (StringUtil.isBlank(decryptedPhone)) {
+			return R.fail(USER_PHONE_NOT_FOUND);
+		}
+		// 校验手机是否已注册,防止恶意发送验证码
+		OAuth2Request request = OAuth2Request.create();
+		request.setTenantId(tenantId);
+		OAuth2User oAuth2User = userService.loadByPhone(decryptedPhone, request);
+		if (oAuth2User == null) {
+			return R.fail(USER_PHONE_NOT_FOUND);
+		}
+		// 用户存在则发送验证码
+		Map<String, String> params = SmsUtil.getValidateParams();
+		SmsCode smsCode = smsBuilder.template(tenantId, StringPool.EMPTY).sendValidate(new SmsData(params).setKey(PARAM_KEY), decryptedPhone);
+		return smsCode.isSuccess() ? R.data(smsCode, SEND_SUCCESS) : R.fail(SEND_FAIL);
+	}
+
+}

+ 74 - 0
src/main/java/org/springblade/modules/auth/granter/CaptchaTokenGranter.java

@@ -0,0 +1,74 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.granter;
+
+import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
+import org.springblade.core.oauth2.exception.UserInvalidException;
+import org.springblade.core.oauth2.granter.PasswordTokenGranter;
+import org.springblade.core.oauth2.handler.PasswordHandler;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.service.OAuth2ClientService;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.oauth2.service.OAuth2UserService;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.stereotype.Component;
+
+/**
+ * CaptchaTokenGranter
+ *
+ * @author BladeX
+ */
+@Component
+public class CaptchaTokenGranter extends PasswordTokenGranter {
+
+	private final BladeRedis bladeRedis;
+
+	public CaptchaTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, BladeRedis bladeRedis) {
+		super(clientService, userService, passwordHandler);
+		this.bladeRedis = bladeRedis;
+	}
+
+	@Override
+	public String type() {
+		return CAPTCHA;
+	}
+
+	@Override
+	public OAuth2User user(OAuth2Request request) {
+		// 获取验证码信息
+		String key = request.getCaptchaKey();
+		String code = request.getCaptchaCode();
+		// 获取验证码
+		String redisCode = bladeRedis.get(OAuth2TokenConstant.CAPTCHA_CACHE_KEY + key);
+		// 判断验证码
+		if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) {
+			bladeRedis.del(OAuth2TokenConstant.CAPTCHA_CACHE_KEY + key);
+			throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT);
+		}
+		return super.user(request);
+	}
+}

+ 157 - 0
src/main/java/org/springblade/modules/auth/granter/RegisterTokenGranter.java

@@ -0,0 +1,157 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.granter;
+
+import org.jetbrains.annotations.NotNull;
+import org.springblade.common.cache.ParamCache;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.oauth2.exception.ExceptionCode;
+import org.springblade.core.oauth2.exception.UserInvalidException;
+import org.springblade.core.oauth2.granter.AbstractTokenGranter;
+import org.springblade.core.oauth2.handler.PasswordHandler;
+import org.springblade.core.oauth2.props.OAuth2Properties;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.provider.OAuth2Token;
+import org.springblade.core.oauth2.service.OAuth2Client;
+import org.springblade.core.oauth2.service.OAuth2ClientService;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.oauth2.service.OAuth2UserService;
+import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SM2Util;
+import org.springblade.modules.auth.provider.UserType;
+import org.springblade.modules.system.pojo.entity.User;
+import org.springblade.modules.system.service.IUserService;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.function.Predicate;
+
+import static org.springblade.modules.auth.constant.BladeAuthConstant.REGISTER_USER_VALUE;
+
+/**
+ * RegisterTokenGranter
+ *
+ * @author BladeX
+ */
+@Component
+public class RegisterTokenGranter extends AbstractTokenGranter {
+
+	private final IUserService service;
+	private final OAuth2Properties properties;
+
+	public RegisterTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, IUserService service, OAuth2Properties properties) {
+		super(clientService, userService, passwordHandler);
+		this.service = service;
+		this.properties = properties;
+	}
+
+	@Override
+	public String type() {
+		return REGISTER;
+	}
+
+	@Override
+	public OAuth2User user(OAuth2Request request) {
+		// 校验注册功能是否开启
+		Boolean registerOpen = Func.toBoolean(ParamCache.getValue(REGISTER_USER_VALUE), false);
+		if (!registerOpen) {
+			throw new UserInvalidException("注册功能暂未开启,请联系管理员");
+		}
+
+		// 用户注册信息
+		User user = new User();
+		user.setUserType(UserType.WEB.getCategory());
+		user.setTenantId(request.getTenantId());
+		user.setAccount(request.getUsername());
+		user.setPassword(SM2Util.decrypt(request.getPassword(), properties.getPublicKey(), properties.getPrivateKey()));
+		user.setName(request.getName());
+		user.setRealName(request.getName());
+		user.setPhone(request.getPhone());
+		user.setEmail(request.getEmail());
+
+		// 校验用户格式
+		validateUser(user);
+
+		// 执行用户注册
+		if (service.registerUser(user)) {
+			// 构建oauth2所需用户信息
+			return convertOAuth2UserDetail(user, client(request));
+		}
+		throw new UserInvalidException(ExceptionCode.INVALID_USER.getMessage());
+	}
+
+	@Override
+	public OAuth2Token token(OAuth2User user, OAuth2Request request) {
+		// 移除注册后返回的令牌与刷新令牌,防止外部攻击采用注册接口获取令牌并调用低权接口
+		// 注意:
+		// 1. 框架已默认开启严格模式,blade.secure.strict-token=true,不移除令牌则不受影响,注册令牌会被框架校验并拒绝
+		// 2. 若自行关闭严格模式,blade.secure.strict-token=false,必须将令牌移除,否则注册获取令牌后可调用低权接口
+		OAuth2Token token = super.token(user, request);
+		token.getArgs().remove(TokenConstant.ACCESS_TOKEN);
+		token.getArgs().remove(TokenConstant.REFRESH_TOKEN);
+		return token;
+	}
+
+	private void validateUser(User user) {
+		Predicate<String> isNameValid = name -> name.matches("^([\\u4e00-\\u9fa5]{2,20}|[a-zA-Z]{2,10})$");
+		Predicate<String> isUsernameValid = username -> username.matches("^(?=.*[a-zA-Z])[a-zA-Z0-9_\\-@]{3,20}$");
+		Predicate<String> isPasswordValid = password -> password.matches("^(?=.*[0-9])(?=.*[a-zA-Z])[\\w@-]{6,20}$");
+		Predicate<String> isPhoneValid = phone -> phone.matches("^1[3-9]\\d{9}$");
+		Predicate<String> isEmailValid = email -> email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
+		if (!isNameValid.test(user.getName())) {
+			throw new UserInvalidException("用户姓名长度必须在2-10之间,且仅能设置纯中文或纯英文");
+		}
+		if (!isUsernameValid.test(user.getAccount())) {
+			throw new UserInvalidException("用户账号长度必须在3-20之间,且需要包含英文,可额外携带数字、下划线、横杠、@");
+		}
+		if (!isPasswordValid.test(user.getPassword())) {
+			throw new UserInvalidException("用户密码长度必须在6-20之间,且需要包含英文与数字,可额外携带下划线、横杠、@");
+		}
+		if (!isPhoneValid.test(user.getPhone())) {
+			throw new UserInvalidException("手机号格式不正确");
+		}
+		if (!isEmailValid.test(user.getEmail())) {
+			throw new UserInvalidException("邮箱格式不正确");
+		}
+	}
+
+	@NotNull
+	private OAuth2UserDetail convertOAuth2UserDetail(User user, OAuth2Client client) {
+		OAuth2UserDetail userDetail = new OAuth2UserDetail();
+		userDetail.setUserId(String.valueOf(user.getId()));
+		userDetail.setTenantId(user.getTenantId());
+		userDetail.setName(user.getName());
+		userDetail.setRealName(user.getName());
+		userDetail.setAccount(user.getAccount());
+		userDetail.setPassword(user.getPassword());
+		userDetail.setPhone(user.getPhone());
+		userDetail.setEmail(user.getEmail());
+		userDetail.setAuthorities(Collections.singletonList(REGISTER));
+		userDetail.setClient(client);
+		return userDetail;
+	}
+}

+ 102 - 0
src/main/java/org/springblade/modules/auth/granter/SmsTokenGranter.java

@@ -0,0 +1,102 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.granter;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
+import org.springblade.core.oauth2.exception.UserInvalidException;
+import org.springblade.core.oauth2.granter.AbstractTokenGranter;
+import org.springblade.core.oauth2.handler.PasswordHandler;
+import org.springblade.core.oauth2.props.OAuth2Properties;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.service.OAuth2ClientService;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.oauth2.service.OAuth2UserService;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.tool.utils.SM2Util;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springblade.modules.resource.builder.SmsBuilder;
+import org.springframework.stereotype.Component;
+
+/**
+ * SmsTokenGranter
+ *
+ * @author BladeX
+ */
+@Component
+public class SmsTokenGranter extends AbstractTokenGranter {
+
+	private final OAuth2UserService userService;
+	private final SmsBuilder smsBuilder;
+	private final OAuth2Properties properties;
+
+	public SmsTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, SmsBuilder smsBuilder, OAuth2Properties properties) {
+		super(clientService, userService, passwordHandler);
+		this.userService = userService;
+		this.smsBuilder = smsBuilder;
+		this.properties = properties;
+	}
+
+	@Override
+	public String type() {
+		return SMS_CODE;
+	}
+
+	@Override
+	public OAuth2User user(OAuth2Request request) {
+		// 获取基础信息
+		String tenantId = request.getTenantId();
+		SmsCode smsCode = buildSmsCode();
+		// 校验手机加密认证
+		String decryptedPhone = SM2Util.decrypt(smsCode.getPhone(), properties.getPublicKey(), properties.getPrivateKey());
+		if (StringUtil.isBlank(decryptedPhone)) {
+			throw new UserInvalidException(OAuth2TokenConstant.USER_PHONE_NOT_FOUND);
+		}
+		// 获取短信验证信息
+		boolean temp = smsBuilder.template(tenantId, StringPool.EMPTY).validateMessage(smsCode.setPhone(decryptedPhone));
+		if (!temp) {
+			throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT);
+		}
+		// 获取用户信息
+		OAuth2User user = userService.loadByPhone(decryptedPhone, request);
+		// 校验用户信息
+		if (!userService.validateUser(user)) {
+			throw new UserInvalidException(OAuth2TokenConstant.TOKEN_NOT_CORRECT);
+		}
+		// 设置客户端信息
+		user.setClient(client(request));
+		return user;
+	}
+
+	private SmsCode buildSmsCode() {
+		HttpServletRequest request = WebUtil.getRequest();
+		return new SmsCode().setId(request.getParameter("id"))
+			.setPhone(request.getParameter("phone"))
+			.setValue(request.getParameter("value"));
+	}
+}

+ 116 - 0
src/main/java/org/springblade/modules/auth/granter/SocialTokenGranter.java

@@ -0,0 +1,116 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.granter;
+
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
+import org.springblade.core.oauth2.granter.AbstractTokenGranter;
+import org.springblade.core.oauth2.handler.PasswordHandler;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.service.OAuth2ClientService;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.oauth2.service.OAuth2UserService;
+import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
+import org.springblade.core.social.props.SocialProperties;
+import org.springblade.core.social.utils.SocialUtil;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.modules.auth.utils.TokenUtil;
+import org.springblade.modules.system.pojo.entity.UserInfo;
+import org.springblade.modules.system.pojo.entity.UserOauth;
+import org.springblade.modules.system.service.IUserService;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+/**
+ * SocialTokenGranter
+ *
+ * @author Chill
+ */
+@Component
+public class SocialTokenGranter extends AbstractTokenGranter {
+
+
+	private static final Integer AUTH_SUCCESS_CODE = 2000;
+
+	private final IUserService userService;
+	private final SocialProperties socialProperties;
+
+
+	public SocialTokenGranter(OAuth2ClientService clientService, OAuth2UserService oAuth2UserService, PasswordHandler passwordHandler, IUserService userService, SocialProperties socialProperties) {
+		super(clientService, oAuth2UserService, passwordHandler);
+		this.userService = userService;
+		this.socialProperties = socialProperties;
+	}
+
+	@Override
+	public String type() {
+		return SOCIAL;
+	}
+
+	@Override
+	public OAuth2User user(OAuth2Request request) {
+		String tenantId = request.getTenantId();
+		// 开放平台来源
+		String sourceParameter = request.getSource();
+		// 匹配是否有别名定义
+		String source = socialProperties.getAlias().getOrDefault(sourceParameter, sourceParameter);
+		// 开放平台授权码
+		String code = request.getCode();
+		// 开放平台状态吗
+		String state = request.getState();
+
+		// 获取开放平台授权数据
+		AuthRequest authRequest = SocialUtil.getAuthRequest(source, socialProperties);
+		AuthCallback authCallback = new AuthCallback();
+		authCallback.setCode(code);
+		authCallback.setState(state);
+		AuthResponse<?> authResponse = authRequest.login(authCallback);
+		AuthUser authUser = null;
+		if (authResponse.getCode() == AUTH_SUCCESS_CODE) {
+			authUser = (AuthUser) authResponse.getData();
+		} else {
+			OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
+		}
+
+		// 组装数据
+		UserOauth userOauth = Objects.requireNonNull(BeanUtil.copyProperties(authUser, UserOauth.class));
+		userOauth.setSource(authUser.getSource());
+		userOauth.setTenantId(tenantId);
+		userOauth.setUuid(authUser.getUuid());
+		UserInfo userInfo = userService.userInfo(userOauth);
+
+		// 设置Oauth2用户信息
+		OAuth2User user = TokenUtil.convertUser(userInfo, request);
+		// 设置客户端信息
+		user.setClient(client(request));
+		return user;
+	}
+
+}

+ 266 - 0
src/main/java/org/springblade/modules/auth/handler/BladeAuthorizationHandler.java

@@ -0,0 +1,266 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.handler;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.common.cache.CacheNames;
+import org.springblade.common.cache.ParamCache;
+import org.springblade.common.cache.SysCache;
+import org.springblade.common.constant.TenantConstant;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.oauth2.exception.ExceptionCode;
+import org.springblade.core.oauth2.handler.AbstractAuthorizationHandler;
+import org.springblade.core.oauth2.props.OAuth2Properties;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.provider.OAuth2Validation;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.tenant.BladeTenantProperties;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.DesUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SM2Util;
+import org.springblade.modules.system.pojo.entity.Tenant;
+
+import java.time.Duration;
+import java.util.Date;
+import java.util.List;
+
+import static org.springblade.modules.auth.constant.BladeAuthConstant.FAIL_COUNT;
+import static org.springblade.modules.auth.constant.BladeAuthConstant.FAIL_COUNT_VALUE;
+
+/**
+ * BladeAuthorizationHandler
+ *
+ * @author BladeX
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class BladeAuthorizationHandler extends AbstractAuthorizationHandler {
+
+	private final BladeRedis bladeRedis;
+	private final BladeProperties bladeProperties;
+	private final BladeTenantProperties tenantProperties;
+	private final OAuth2Properties oAuth2Properties;
+
+	/**
+	 * 自定义弱密码列表
+	 */
+	private static final List<String> WEAK_PASSWORDS = List.of("admin", "administrator", "hr", "manager", "boss");
+
+	/**
+	 * 认证前校验
+	 *
+	 * @param request 请求信息
+	 * @return boolean
+	 */
+	@Override
+	public OAuth2Validation preValidation(OAuth2Request request) {
+		if (request.isPassword() || request.isCaptchaCode()) {
+			// 生产环境弱密码校验
+			if (bladeProperties.isProd() && isWeakPassword(request.getPassword())) {
+				return buildValidationFailure(ExceptionCode.INVALID_USER_PASSWORD);
+			}
+			// 判断登录是否锁定
+			OAuth2Validation failCountValidation = validateFailCount(request.getTenantId(), request.getUsername());
+			if (!failCountValidation.isSuccess()) {
+				return failCountValidation;
+			}
+		}
+		return super.preValidation(request);
+	}
+
+	/**
+	 * 认证前失败回调
+	 *
+	 * @param validation 失败信息
+	 */
+	@Override
+	public void preFailure(OAuth2Request request, OAuth2Validation validation){
+		// 增加错误锁定次数
+		addFailCount(request.getTenantId(), request.getUsername());
+
+		log.error("用户:{},认证失败,失败原因:{}", request.getUsername(), validation.getMessage());
+	}
+
+
+	/**
+	 * 认证校验
+	 *
+	 * @param user    用户信息
+	 * @param request 请求信息
+	 * @return boolean
+	 */
+	@Override
+	public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) {
+		// 密码模式、刷新token模式、验证码模式需要校验租户状态
+		if (request.isPassword() || request.isRefreshToken() || request.isCaptchaCode()) {
+			// 租户校验
+			OAuth2Validation tenantValidation = validateTenant(user.getTenantId());
+			if (!tenantValidation.isSuccess()) {
+				return tenantValidation;
+			}
+		}
+		return super.authValidation(user, request);
+	}
+
+	/**
+	 * 认证成功回调
+	 *
+	 * @param user 用户信息
+	 */
+	@Override
+	public void authSuccessful(OAuth2User user, OAuth2Request request) {
+		// 清空错误锁定次数
+		delFailCount(user.getTenantId(), user.getAccount());
+
+		log.info("用户:{},认证成功", user.getAccount());
+	}
+
+	/**
+	 * 认证失败回调
+	 *
+	 * @param user       用户信息
+	 * @param validation 失败信息
+	 */
+	@Override
+	public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) {
+		// 自定义认证失败回调
+	}
+
+	/**
+	 * 判断是否为弱密码
+	 *
+	 * @param rawPassword      加密密码
+	 * @return boolean
+	 */
+	private boolean isWeakPassword(String rawPassword) {
+		// 获取公钥
+		String publicKey = oAuth2Properties.getPublicKey();
+		// 获取私钥
+		String privateKey = oAuth2Properties.getPrivateKey();
+		// 解密密码
+		String decryptPassword = SM2Util.decrypt(rawPassword, publicKey, privateKey);
+		return WEAK_PASSWORDS.stream()
+			.anyMatch(weakPass -> weakPass.equalsIgnoreCase(decryptPassword));
+	}
+
+	/**
+	 * 租户授权校验
+	 *
+	 * @param tenantId 租户id
+	 * @return OAuth2Validation
+	 */
+	private OAuth2Validation validateTenant(String tenantId) {
+		// 租户校验
+		Tenant tenant = SysCache.getTenant(tenantId);
+		if (tenant == null) {
+			return buildValidationFailure(ExceptionCode.USER_TENANT_NOT_FOUND);
+		}
+		// 租户授权时间校验
+		Date expireTime = tenant.getExpireTime();
+		if (tenantProperties.getLicense()) {
+			String licenseKey = tenant.getLicenseKey();
+			String decrypt = DesUtil.decryptFormHex(licenseKey, TenantConstant.DES_KEY);
+			Tenant license = JsonUtil.parse(decrypt, Tenant.class);
+			if (license == null || !license.getId().equals(tenant.getId())) {
+				return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
+			}
+			expireTime = license.getExpireTime();
+		}
+		if (expireTime != null && expireTime.before(DateUtil.now())) {
+			return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
+		}
+		return new OAuth2Validation();
+	}
+
+	/**
+	 * 判断登录是否锁定
+	 *
+	 * @param tenantId 租户id
+	 * @param account  账号
+	 * @return OAuth2Validation
+	 */
+	private OAuth2Validation validateFailCount(String tenantId, String account) {
+		int cnt = getFailCount(tenantId, account);
+		int failCount = Func.toInt(ParamCache.getValue(FAIL_COUNT_VALUE), FAIL_COUNT);
+		if (cnt >= failCount) {
+			return buildValidationFailure(ExceptionCode.USER_TOO_MANY_FAILS);
+		}
+		return new OAuth2Validation();
+	}
+
+	/**
+	 * 获取账号错误次数
+	 *
+	 * @param tenantId 租户id
+	 * @param username 账号
+	 * @return int
+	 */
+	private int getFailCount(String tenantId, String username) {
+		if (Func.hasEmpty(tenantId, username)) {
+			return 0;
+		}
+		return Func.toInt(bladeRedis.get(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username)), 0);
+	}
+
+	/**
+	 * 设置账号错误次数
+	 *
+	 * @param tenantId 租户id
+	 * @param username 账号
+	 */
+	private void addFailCount(String tenantId, String username) {
+		if (Func.hasEmpty(tenantId, username)) {
+			return;
+		}
+		int count = getFailCount(tenantId, username);
+		bladeRedis.setEx(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username), count + 1, Duration.ofMinutes(30));
+	}
+
+	/**
+	 * 设置账号错误次数
+	 *
+	 * @param tenantId 租户id
+	 * @param username 账号
+	 * @param count    次数
+	 */
+	private void setFailCount(String tenantId, String username, int count) {
+		bladeRedis.setEx(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username), count + 1, Duration.ofMinutes(30));
+	}
+
+	/**
+	 * 清空账号错误次数
+	 *
+	 * @param tenantId 租户id
+	 * @param username 账号
+	 */
+	private void delFailCount(String tenantId, String username) {
+		bladeRedis.del(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username));
+	}
+}

+ 64 - 0
src/main/java/org/springblade/modules/auth/handler/BladePasswordHandler.java

@@ -0,0 +1,64 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.handler;
+
+import org.springblade.core.oauth2.handler.OAuth2PasswordHandler;
+import org.springblade.core.oauth2.props.OAuth2Properties;
+
+/**
+ * BladePasswordHandler
+ *
+ * @author BladeX
+ */
+public class BladePasswordHandler extends OAuth2PasswordHandler {
+
+	public BladePasswordHandler(OAuth2Properties properties) {
+		super(properties);
+	}
+
+	/**
+	 * 判断密码是否匹配
+	 *
+	 * @param rawPassword     请求时提交的原密码
+	 * @param encodedPassword 数据库加密后的密码
+	 * @return boolean
+	 */
+	@Override
+	public boolean matches(String rawPassword, String encodedPassword) {
+		return super.matches(rawPassword, encodedPassword);
+	}
+
+	/**
+	 * 加密密码规则
+	 *
+	 * @param rawPassword 密码
+	 * @return 加密后的密码
+	 */
+	@Override
+	public String encode(String rawPassword) {
+		return super.encode(rawPassword);
+	}
+}

+ 34 - 0
src/main/java/org/springblade/modules/auth/handler/BladeTokenHandler.java

@@ -0,0 +1,34 @@
+package org.springblade.modules.auth.handler;
+
+import org.springblade.core.jwt.props.JwtProperties;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.oauth2.handler.OAuth2TokenHandler;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.provider.OAuth2Token;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.tool.support.Kv;
+
+/**
+ * BladeTokenHandler
+ *
+ * @author BladeX
+ */
+public class BladeTokenHandler extends OAuth2TokenHandler {
+
+	public BladeTokenHandler(JwtProperties properties) {
+		super(properties);
+	}
+
+	@Override
+	public OAuth2Token enhance(OAuth2User user, OAuth2Token token, OAuth2Request request) {
+		// 父类令牌状态配置
+		OAuth2Token enhanceToken = super.enhance(user, token, request);
+
+		// 令牌统一处理,增加或删减字段
+		Kv args = enhanceToken.getArgs();
+		args.set(TokenConstant.USER_NAME, user.getAccount());
+
+		// 返回令牌
+		return enhanceToken;
+	}
+}

+ 67 - 0
src/main/java/org/springblade/modules/auth/provider/UserType.java

@@ -0,0 +1,67 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.provider;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 用户类型枚举
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum UserType {
+
+	/**
+	 * web
+	 */
+	WEB("web", 1),
+
+	/**
+	 * app
+	 */
+	APP("app", 2),
+
+	/**
+	 * other
+	 */
+	OTHER("other", 3),
+	;
+
+	final String name;
+	final int category;
+
+	public static UserType of(String name) {
+		return Arrays.stream(UserType.values())
+			.filter(userEnum -> userEnum.getName().equalsIgnoreCase(name != null ? name : "web"))
+			.findFirst()
+			.orElse(UserType.WEB); // 在没有找到匹配项时返回默认值
+	}
+}

+ 37 - 0
src/main/java/org/springblade/modules/auth/service/BladeClientDetailService.java

@@ -0,0 +1,37 @@
+package org.springblade.modules.auth.service;
+
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.service.OAuth2Client;
+import org.springblade.core.oauth2.service.impl.OAuth2ClientDetailService;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * BladeClientDetailService
+ *
+ * @author Chill
+ */
+public class BladeClientDetailService extends OAuth2ClientDetailService {
+	public BladeClientDetailService(JdbcTemplate jdbcTemplate) {
+		super(jdbcTemplate);
+	}
+
+	@Override
+	public OAuth2Client loadByClientId(String clientId) {
+		return super.loadByClientId(clientId);
+	}
+
+	@Override
+	public OAuth2Client loadByClientId(String clientId, OAuth2Request request) {
+		return super.loadByClientId(clientId, request);
+	}
+
+	@Override
+	public boolean validateClient(OAuth2Client client, String clientId, String clientSecret) {
+		return super.validateClient(client, clientId, clientSecret);
+	}
+
+	@Override
+	public boolean validateGranter(OAuth2Client client, String grantType) {
+		return super.validateGranter(client, grantType);
+	}
+}

+ 77 - 0
src/main/java/org/springblade/modules/auth/service/BladeUserDetailService.java

@@ -0,0 +1,77 @@
+package org.springblade.modules.auth.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.oauth2.service.OAuth2UserService;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.modules.auth.provider.UserType;
+import org.springblade.modules.auth.utils.TokenUtil;
+import org.springblade.modules.system.pojo.entity.UserInfo;
+import org.springblade.modules.system.service.IUserService;
+
+import java.util.Optional;
+
+/**
+ * BladeUserDetailService
+ *
+ * @author Chill
+ */
+@RequiredArgsConstructor
+public class BladeUserDetailService implements OAuth2UserService {
+	private final IUserService userService;
+
+	@Override
+	public OAuth2User loadByUserId(String userId, OAuth2Request request) {
+		// 获取用户参数
+		String userType = Optional.ofNullable(request.getUserType())
+			.filter(s -> !StringUtil.isBlank(s))
+			.orElse(UserType.WEB.getName());
+
+		// 获取用户信息
+		UserInfo userInfo = userService.userInfo(Func.toLong(userId), UserType.of(userType));
+
+		// 构建oauth2用户信息
+		return TokenUtil.convertUser(userInfo, request);
+	}
+
+	@Override
+	public OAuth2User loadByUsername(String username, OAuth2Request request) {
+		// 获取用户参数
+		String userType = Optional.ofNullable(request.getUserType())
+			.filter(s -> !StringUtil.isBlank(s))
+			.orElse(UserType.WEB.getName());
+		String tenantId = request.getTenantId();
+
+		// 获取用户信息
+		UserInfo userInfo = userService.userInfo(tenantId, username, UserType.of(userType));
+
+		// 构建oauth2用户信息
+		return TokenUtil.convertUser(userInfo, request);
+	}
+
+	@Override
+	public OAuth2User loadByPhone(String phone, OAuth2Request request) {
+		// 获取用户参数
+		String userType = Optional.ofNullable(request.getUserType())
+			.filter(s -> !StringUtil.isBlank(s))
+			.orElse(UserType.WEB.getName());
+		String tenantId = request.getTenantId();
+
+		// 获取用户信息
+		UserInfo userInfo = userService.userInfoByPhone(tenantId, phone, UserType.of(userType));
+
+		// 构建oauth2用户信息
+		return TokenUtil.convertUser(userInfo, request);
+	}
+
+	@Override
+	public boolean validateUser(OAuth2User user) {
+		return Optional.ofNullable(user)
+			.filter(u -> u.getUserId() != null && !u.getUserId().isEmpty()) // 检查userId不为空
+			.filter(u -> u.getAuthorities() != null && !u.getAuthorities().isEmpty()) // 检查authorities不为空
+			.isPresent(); // 如果上述条件都满足,则返回true,否则返回false
+	}
+
+}

+ 86 - 0
src/main/java/org/springblade/modules/auth/utils/TokenUtil.java

@@ -0,0 +1,86 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.auth.utils;
+
+import org.springblade.common.cache.SysCache;
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.oauth2.service.OAuth2User;
+import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.modules.system.pojo.entity.User;
+import org.springblade.modules.system.pojo.entity.UserInfo;
+
+/**
+ * 认证工具类
+ *
+ * @author Chill
+ */
+public class TokenUtil {
+
+	/**
+	 * 系统用户转换为OAuth2标准用户
+	 *
+	 * @param userInfo 用户信息
+	 * @param request  请求信息
+	 * @return OAuth2User
+	 */
+	public static OAuth2User convertUser(UserInfo userInfo, OAuth2Request request) {
+		// 为空则返回null
+		if (userInfo == null) {
+			return null;
+		}
+		User user = userInfo.getUser();
+		String userDept = request.getUserDept();
+		String userRole = request.getUserRole();
+		// 单独指定部门
+		if (Func.isNotEmpty(userDept) && user.getDeptId().contains(userDept)) {
+			user.setDeptId(userDept);
+		}
+		// 单独指定角色
+		if (Func.isNotEmpty(userRole) && user.getRoleId().contains(userRole)) {
+			user.setRoleId(userRole);
+			userInfo.setRoles(SysCache.getRoleAliases(userRole));
+		}
+		// 构建oauth2所需用户信息
+		OAuth2UserDetail userDetail = new OAuth2UserDetail();
+		userDetail.setUserId(String.valueOf(user.getId()));
+		userDetail.setOauthId(userInfo.getOauthId());
+		userDetail.setTenantId(user.getTenantId());
+		userDetail.setName(user.getName());
+		userDetail.setRealName(user.getRealName());
+		userDetail.setAccount(user.getAccount());
+		userDetail.setPassword(user.getPassword());
+		userDetail.setDeptId(user.getDeptId());
+		userDetail.setPostId(user.getPostId());
+		userDetail.setRoleId(user.getRoleId());
+		userDetail.setRoleName(Func.join(userInfo.getRoles()));
+		userDetail.setAvatar(user.getAvatar());
+		userDetail.setAuthorities(userInfo.getRoles());
+		userDetail.setDetail(userInfo.getDetail());
+		return userDetail;
+	}
+
+}

+ 212 - 0
src/main/java/org/springblade/modules/desk/controller/DashBoardController.java

@@ -0,0 +1,212 @@
+package org.springblade.modules.desk.controller;
+
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.support.Kv;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 首页
+ *
+ * @author Chill
+ */
+@NonDS
+@Hidden
+@RestController
+@RequestMapping(AppConstant.APPLICATION_DESK_NAME)
+@AllArgsConstructor
+@Tag(name = "首页", description = "首页")
+public class DashBoardController {
+
+	/**
+	 * 活跃用户
+	 */
+	@GetMapping("/dashboard/activities")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "活跃用户", description = "活跃用户")
+	public R activities() {
+		List<Map<String, Object>> list = new ArrayList<>();
+
+		Map<String, Object> map1 = new HashMap<>(16);
+		map1.put("id", "trend-1");
+		map1.put("updatedAt", "2019-01-01");
+		map1.put("user", Kv.create().set("name", "曲丽丽").set("avatar", "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"));
+		map1.put("group", Kv.create().set("name", "高逼格设计天团").set("link", "http://github.com/"));
+		map1.put("project", Kv.create().set("name", "六月迭代").set("link", "http://github.com/"));
+		map1.put("template", "在 @{group} 新建项目 @{project}");
+		list.add(map1);
+
+		Map<String, Object> map2 = new HashMap<>(16);
+		map2.put("id", "trend-2");
+		map2.put("updatedAt", "2019-01-01");
+		map2.put("user", Kv.create().set("name", "付小小").set("avatar", "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"));
+		map2.put("group", Kv.create().set("name", "高逼格设计天团").set("link", "http://github.com/"));
+		map2.put("project", Kv.create().set("name", "七月月迭代").set("link", "http://github.com/"));
+		map2.put("template", "在  @{group} 新建项目 @{project}");
+		list.add(map2);
+
+		return R.data(list);
+	}
+
+	/**
+	 * 用户信息
+	 */
+	@GetMapping("/dashboard/info")
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "用户信息", description = "用户信息")
+	public R info() {
+		Map<String, Object> map = new HashMap<>(16);
+		map.put("id", "trend-1");
+		map.put("updatedAt", "2019-01-01");
+		map.put("user", Kv.create().set("name", "曲丽丽").set("avatar", "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"));
+		map.put("group", Kv.create().set("name", "高逼格设计天团").set("link", "http://github.com/"));
+		map.put("project", Kv.create().set("name", "六月迭代").set("link", "http://github.com/"));
+		map.put("template", "在 @{group} 新建项目 @{project}");
+		return R.data(map);
+	}
+
+	/**
+	 * 签名信息
+	 */
+	@PostMapping("/dashboard/sign")
+	@ApiOperationSupport(order = 3)
+	@Operation(summary = "签名信息", description = "签名信息")
+	public R sign() {
+		Map<String, Object> map = new HashMap<>(16);
+		map.put("user", Kv.create().set("name", "曲丽丽").set("avatar", "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"));
+		return R.data(map);
+	}
+
+	/**
+	 * 获取消息
+	 */
+	@GetMapping("/notice/notices")
+	@ApiOperationSupport(order = 4)
+	@Operation(summary = "消息", description = "消息")
+	public R notices() {
+		List<Map<String, String>> list = new ArrayList<>();
+		Map<String, String> map1 = new HashMap<>(16);
+		map1.put("logo", "https://spring.io/img/homepage/icon-spring-framework.svg");
+		map1.put("title", "SpringBoot");
+		map1.put("description", "现在的web项目几乎都会用到spring框架,而要使用spring难免需要配置大量的xml配置文件,而 springboot的出现解   决了这一问题,一个项目甚至不用部署到服务器上直接开跑,真像springboot所说:“just run”。");
+		map1.put("member", "Chill");
+		map1.put("href", "http://spring.io/projects/spring-boot");
+		list.add(map1);
+
+		Map<String, String> map2 = new HashMap<>(16);
+		map2.put("logo", "https://spring.io/img/homepage/icon-spring-cloud.svg");
+		map2.put("title", "SpringCloud");
+		map2.put("description", "SpringCloud是基于SpringBoot的一整套实现微服务的框架。他提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。");
+		map2.put("member", "Chill");
+		map2.put("href", "http://spring.io/projects/spring-cloud");
+		list.add(map2);
+
+		Map<String, String> map3 = new HashMap<>(16);
+		map3.put("logo", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546359961068&di=05ff9406e6675ca9a58a525a7e7950b9&imgtype=jpg&src=http%3A%2F%2Fimg0.imgtn.bdimg.com%2Fit%2Fu%3D575314515%2C4268715674%26fm%3D214%26gp%3D0.jpg");
+		map3.put("title", "Mybatis");
+		map3.put("description", "MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。");
+		map3.put("member", "Chill");
+		map3.put("href", "http://www.mybatis.org/mybatis-3/getting-started.html");
+		list.add(map3);
+
+		Map<String, String> map4 = new HashMap<>(16);
+		map4.put("logo", "https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png");
+		map4.put("title", "React");
+		map4.put("description", "React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。");
+		map4.put("member", "Chill");
+		map4.put("href", "https://reactjs.org/");
+		list.add(map4);
+
+		Map<String, String> map5 = new HashMap<>(16);
+		map5.put("logo", "https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png");
+		map5.put("title", "Ant Design");
+		map5.put("description", "蚂蚁金服体验技术部经过大量的项目实践和总结,沉淀出设计语言 Ant Design,这可不单纯只是设计原则、控件规范和视觉尺寸,还配套有前端代码实现方案。也就是说采用Ant Design后,UI设计和前端界面研发可同步完成,效率大大提升。");
+		map5.put("member", "Chill");
+		map5.put("href", "https://ant.design/docs/spec/introduce-cn");
+		list.add(map5);
+
+		Map<String, String> map6 = new HashMap<>(16);
+		map6.put("logo", "https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png");
+		map6.put("title", "Ant Design Pro");
+		map6.put("description", "Ant Design Pro 是一个企业级开箱即用的中后台前端/设计解决方案。符合阿里追求的'敏捷的前端+强大的中台'的思想。");
+		map6.put("member", "Chill");
+		map6.put("href", "https://pro.ant.design");
+		list.add(map6);
+
+		return R.data(list);
+	}
+
+	/**
+	 * 获取我的消息
+	 */
+	@GetMapping("/notice/my-notices")
+	@ApiOperationSupport(order = 5)
+	@Operation(summary = "消息", description = "消息")
+	public R myNotices() {
+		List<Map<String, String>> list = new ArrayList<>();
+		Map<String, String> map1 = new HashMap<>(16);
+		map1.put("id", "000000001");
+		map1.put("avatar", "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png");
+		map1.put("title", "你收到了 14 份新周报");
+		map1.put("datetime", "2018-08-09");
+		map1.put("type", "notification");
+		list.add(map1);
+
+		Map<String, String> map2 = new HashMap<>(16);
+		map2.put("id", "000000002");
+		map2.put("avatar", "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png");
+		map2.put("title", "你推荐的 曲妮妮 已通过第三轮面试");
+		map2.put("datetime", "2018-08-08");
+		map2.put("type", "notification");
+		list.add(map2);
+
+
+		Map<String, String> map3 = new HashMap<>(16);
+		map3.put("id", "000000003");
+		map3.put("avatar", "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg");
+		map3.put("title", "曲丽丽 评论了你");
+		map3.put("description", "描述信息描述信息描述信息");
+		map3.put("datetime", "2018-08-07");
+		map3.put("type", "message");
+		map3.put("clickClose", "true");
+		list.add(map3);
+
+
+		Map<String, String> map4 = new HashMap<>(16);
+		map4.put("id", "000000004");
+		map4.put("avatar", "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg");
+		map4.put("title", "朱偏右 回复了你");
+		map4.put("description", "这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像");
+		map4.put("type", "message");
+		map4.put("datetime", "2018-08-07");
+		map4.put("clickClose", "true");
+		list.add(map4);
+
+
+		Map<String, String> map5 = new HashMap<>(16);
+		map5.put("id", "000000005");
+		map5.put("title", "任务名称");
+		map5.put("description", "任务需要在 2018-01-12 20:00 前启动");
+		map5.put("extra", "未开始");
+		map5.put("status", "todo");
+		map5.put("type", "event");
+		list.add(map5);
+
+		return R.data(list);
+	}
+
+}

+ 155 - 0
src/main/java/org/springblade/modules/desk/controller/NoticeController.java

@@ -0,0 +1,155 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.desk.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSort;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tenant.annotation.TenantDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.xss.annotation.XssIgnore;
+import org.springblade.modules.desk.pojo.entity.Notice;
+import org.springblade.modules.desk.pojo.vo.NoticeVO;
+import org.springblade.modules.desk.service.INoticeService;
+import org.springblade.modules.desk.wrapper.NoticeWrapper;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 控制器
+ *
+ * @author Chill
+ */
+@TenantDS
+@RestController
+@RequestMapping(AppConstant.APPLICATION_DESK_NAME + "/notice")
+@AllArgsConstructor
+@ApiSort(2)
+@Tag(name = "用户博客", description = "博客接口")
+public class NoticeController extends BladeController {
+
+	private final INoticeService noticeService;
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "详情", description = "传入notice")
+	public R<NoticeVO> detail(Notice notice) {
+		Notice detail = noticeService.getOne(Condition.getQueryWrapper(notice));
+		return R.data(NoticeWrapper.build().entityVO(detail));
+	}
+
+	/**
+	 * 分页
+	 */
+	@PreAuth("hasMenu('notice')")
+	@GetMapping("/list")
+	@Parameters({
+		@Parameter(name = "category", description = "公告类型", in = ParameterIn.QUERY, schema = @Schema(type = "integer")),
+		@Parameter(name = "title", description = "公告标题", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
+	})
+	@ApiOperationSupport(order = 2)
+	@Operation(summary = "分页", description = "传入notice")
+	public R<IPage<NoticeVO>> list(@Parameter(hidden = true) @RequestParam Map<String, Object> notice, Query query) {
+		NoticeWrapper.build().noticeQuery(notice);
+		IPage<Notice> pages = noticeService.page(Condition.getPage(query), Condition.getQueryWrapper(notice, Notice.class));
+		return R.data(NoticeWrapper.build().pageVO(pages));
+	}
+
+	/**
+	 * 多表联合查询自定义分页
+	 */
+	@GetMapping("/page")
+	@Parameters({
+		@Parameter(name = "category", description = "公告类型", in = ParameterIn.QUERY, schema = @Schema(type = "integer")),
+		@Parameter(name = "title", description = "公告标题", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
+	})
+	@ApiOperationSupport(order = 3)
+	@Operation(summary = "分页", description = "传入notice")
+	public R<IPage<NoticeVO>> page(@Parameter(hidden = true) NoticeVO notice, Query query) {
+		IPage<NoticeVO> pages = noticeService.selectNoticePage(Condition.getPage(query), notice);
+		return R.data(pages);
+	}
+
+	/**
+	 * 新增
+	 */
+	@PostMapping("/save")
+	@ApiOperationSupport(order = 4)
+	@Operation(summary = "新增", description = "传入notice")
+	public R save(@RequestBody Notice notice) {
+		return R.status(noticeService.save(notice));
+	}
+
+	/**
+	 * 修改
+	 */
+	@PostMapping("/update")
+	@ApiOperationSupport(order = 5)
+	@Operation(summary = "修改", description = "传入notice")
+	public R update(@RequestBody Notice notice) {
+		return R.status(noticeService.updateById(notice));
+	}
+
+	/**
+	 * 新增或修改
+	 */
+	@XssIgnore
+	@PostMapping("/submit")
+	@ApiOperationSupport(order = 6)
+	@Operation(summary = "新增或修改", description = "传入notice")
+	public R submit(@RequestBody Notice notice) {
+		return R.status(noticeService.saveOrUpdate(notice));
+	}
+
+	/**
+	 * 删除
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 7)
+	@Operation(summary = "逻辑删除", description = "传入notice")
+	public R remove(@Parameter(description = "主键集合") @RequestParam String ids) {
+		boolean temp = noticeService.deleteLogic(Func.toLongList(ids));
+		return R.status(temp);
+	}
+
+}

+ 59 - 0
src/main/java/org/springblade/modules/desk/mapper/NoticeMapper.java

@@ -0,0 +1,59 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.desk.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.modules.desk.pojo.entity.Notice;
+import org.springblade.modules.desk.pojo.vo.NoticeVO;
+
+import java.util.List;
+
+/**
+ * Mapper 接口
+ *
+ * @author Chill
+ */
+public interface NoticeMapper extends BaseMapper<Notice> {
+
+	/**
+	 * 前N条数据
+	 *
+	 * @param number 数量
+	 * @return List<Notice>
+	 */
+	List<Notice> topList(Integer number);
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page   分页
+	 * @param notice 实体
+	 * @return List<NoticeVO>
+	 */
+	List<NoticeVO> selectNoticePage(IPage page, NoticeVO notice);
+
+}

+ 53 - 0
src/main/java/org/springblade/modules/desk/mapper/NoticeMapper.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.modules.desk.mapper.NoticeMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="noticeResultMap" type="org.springblade.modules.desk.pojo.entity.Notice">
+        <result column="id" property="id"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+        <result column="release_time" property="releaseTime"/>
+        <result column="title" property="title"/>
+        <result column="content" property="content"/>
+    </resultMap>
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="noticeVOResultMap" type="org.springblade.modules.desk.pojo.vo.NoticeVO">
+        <result column="id" property="id"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+        <result column="release_time" property="releaseTime"/>
+        <result column="title" property="title"/>
+        <result column="content" property="content"/>
+    </resultMap>
+
+    <select id="topList" resultMap="noticeResultMap">
+        select * from blade_notice limit #{number}
+    </select>
+
+    <select id="selectNoticePage" resultMap="noticeVOResultMap">
+        SELECT
+        n.*,
+        d.dict_value AS categoryName
+        FROM
+        blade_notice n
+        LEFT JOIN ( SELECT * FROM blade_dict WHERE CODE = 'notice' ) d ON n.category = d.dict_key
+        WHERE
+        n.is_deleted = 0 and n.tenant_id = #{notice.tenantId}
+        <if test="notice.title!=null">
+            and n.title like concat(concat('%', #{notice.title}), '%')
+        </if>
+        <if test="notice.category!=null">
+            and n.category = #{notice.category}
+        </if>
+    </select>
+</mapper>

+ 76 - 0
src/main/java/org/springblade/modules/desk/pojo/entity/Notice.java

@@ -0,0 +1,76 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.desk.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.tenant.mp.TenantEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 实体类
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("blade_notice")
+@Schema(description = "公告实体类")
+public class Notice extends TenantEntity {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 标题
+	 */
+	@Schema(description = "标题")
+	private String title;
+
+	/**
+	 * 通知类型
+	 */
+	@Schema(description = "通知类型")
+	private Integer category;
+
+	/**
+	 * 发布日期
+	 */
+	@Schema(description = "发布日期")
+	private Date releaseTime;
+
+	/**
+	 * 内容
+	 */
+	@Schema(description = "内容")
+	private String content;
+
+
+}

+ 24 - 0
src/main/java/org/springblade/modules/desk/pojo/vo/NoticeVO.java

@@ -0,0 +1,24 @@
+package org.springblade.modules.desk.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.modules.desk.pojo.entity.Notice;
+
+/**
+ * 通知公告视图类
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "公告实体VO")
+public class NoticeVO extends Notice {
+
+	@Schema(description = "通知类型名")
+	private String categoryName;
+
+	@Schema(description = "租户编号")
+	private String tenantId;
+
+}

+ 48 - 0
src/main/java/org/springblade/modules/desk/service/INoticeService.java

@@ -0,0 +1,48 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.desk.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.modules.desk.pojo.entity.Notice;
+import org.springblade.modules.desk.pojo.vo.NoticeVO;
+
+/**
+ * 服务类
+ *
+ * @author Chill
+ */
+public interface INoticeService extends BaseService<Notice> {
+
+	/**
+	 * 自定义分页
+	 * @param page
+	 * @param notice
+	 * @return
+	 */
+	IPage<NoticeVO> selectNoticePage(IPage<NoticeVO> page, NoticeVO notice);
+
+}

+ 52 - 0
src/main/java/org/springblade/modules/desk/service/impl/NoticeServiceImpl.java

@@ -0,0 +1,52 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.desk.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.modules.desk.pojo.entity.Notice;
+import org.springblade.modules.desk.mapper.NoticeMapper;
+import org.springblade.modules.desk.service.INoticeService;
+import org.springblade.modules.desk.pojo.vo.NoticeVO;
+import org.springframework.stereotype.Service;
+
+/**
+ * 服务实现类
+ *
+ * @author Chill
+ */
+@Service
+public class NoticeServiceImpl extends BaseServiceImpl<NoticeMapper, Notice> implements INoticeService {
+
+	@Override
+	public IPage<NoticeVO> selectNoticePage(IPage<NoticeVO> page, NoticeVO notice) {
+		// 若不使用mybatis-plus自带的分页方法,则不会自动带入tenantId,所以我们需要自行注入
+		notice.setTenantId(AuthUtil.getTenantId());
+		return page.setRecords(baseMapper.selectNoticePage(page, notice));
+	}
+
+}

+ 73 - 0
src/main/java/org/springblade/modules/desk/wrapper/NoticeWrapper.java

@@ -0,0 +1,73 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ * <p>
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ * <p>
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ * <p>
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ * <p>
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ * <p>
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ * <p>
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ * <p>
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.modules.desk.wrapper;
+
+import org.springblade.common.cache.DictCache;
+import org.springblade.common.enums.DictEnum;
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.modules.desk.pojo.entity.Notice;
+import org.springblade.modules.desk.pojo.vo.NoticeVO;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Notice包装类,返回视图层所需的字段
+ *
+ * @author Chill
+ */
+public class NoticeWrapper extends BaseEntityWrapper<Notice, NoticeVO> {
+
+	public static NoticeWrapper build() {
+		return new NoticeWrapper();
+	}
+
+	@Override
+	public NoticeVO entityVO(Notice notice) {
+		NoticeVO noticeVO = Objects.requireNonNull(BeanUtil.copyProperties(notice, NoticeVO.class));
+		String dictValue = DictCache.getValue(DictEnum.NOTICE, noticeVO.getCategory());
+		noticeVO.setCategoryName(dictValue);
+		return noticeVO;
+	}
+
+	/**
+	 * 查询条件处理
+	 */
+	public void noticeQuery(Map<String, Object> notice) {
+		// 此场景仅在 pg数据库 map类型传参的情况下需要处理,entity传参已经包含数据类型,则无需关心
+		// 针对 pg数据库 int类型字段查询需要强转的处理示例
+		String searchKey = "category";
+		if (Func.isNotEmpty(notice.get(searchKey))) {
+			// 数据库字段为int类型,设置"="查询,具体查询参数请见 @org.springblade.core.mp.support.SqlKeyword
+			notice.put(searchKey.concat("_equal"), Func.toInt(notice.get(searchKey)));
+			// 默认"like"查询,pg数据库 场景会报错,所以将其删除
+			notice.remove(searchKey);
+		}
+	}
+
+}

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