Merge remote-tracking branch 'shucheng/mk'
# Conflicts: # blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/api/IApi4MK.java # blade-service-api/pom.xml # blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java # blade-service/blade-file/src/main/resources/bootstrap.yml # blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java # blade-service/blade-openapi/src/main/resources/bootstrap.yml # pom.xml
This commit is contained in:
@@ -25,6 +25,10 @@
|
|||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-scope-api</artifactId>
|
<artifactId>blade-scope-api</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -63,6 +67,10 @@
|
|||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-system-api</artifactId>
|
<artifactId>blade-system-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springblade</groupId>
|
||||||
|
<artifactId>blade-mk-api</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-resource-api</artifactId>
|
<artifactId>blade-resource-api</artifactId>
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableR
|
|||||||
public class AuthApplication {
|
public class AuthApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
// 禁用框架注入的nacos import配置、config、discovery 配置
|
||||||
|
BladeApplication.disableNacosLaunchConfig();
|
||||||
BladeApplication.run(AppConstant.APPLICATION_AUTH_NAME, AuthApplication.class, args);
|
BladeApplication.run(AppConstant.APPLICATION_AUTH_NAME, AuthApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.springblade.auth.endpoint;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springblade.core.oauth2.endpoint.AbstractOAuth2MKEndpoint;
|
||||||
|
import org.springblade.core.oauth2.granter.TokenGranterFactory;
|
||||||
|
import org.springblade.core.oauth2.handler.TokenHandler;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springblade.core.tool.utils.UrlUtil;
|
||||||
|
import org.springblade.thirdparty.mk.config.MKProperties;
|
||||||
|
import org.springblade.thirdparty.mk.service.IMKService;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/17
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "跳转mk认证", description = "跳转mk认证端点")
|
||||||
|
public class OAuth2MKEndpoint extends AbstractOAuth2MKEndpoint {
|
||||||
|
/**
|
||||||
|
* 登录链接
|
||||||
|
*/
|
||||||
|
private static final String LOGIN_URL_FORMAT = "%s%s?appId=%s&redirection_url=%s";
|
||||||
|
|
||||||
|
private final MKProperties mkProperties;
|
||||||
|
private final IMKService mkService;
|
||||||
|
|
||||||
|
public OAuth2MKEndpoint(TokenGranterFactory granterFactory, TokenHandler tokenHandler, MKProperties mkProperties, IMKService mkService) {
|
||||||
|
super(granterFactory, tokenHandler);
|
||||||
|
this.mkProperties = mkProperties;
|
||||||
|
this.mkService = mkService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String generateLoginUrl(String refererUrl) {
|
||||||
|
// 重定向到来源
|
||||||
|
return String.format(LOGIN_URL_FORMAT, mkProperties.getLoginUrl(), mkProperties.getMkSsoLoginUrl(), mkProperties.getOauthAppId(), UrlUtil.encode(refererUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAccountByMkCode(String mkCode) {
|
||||||
|
return mkService.getMKAccount(mkCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkRefererUrl(String refererUrl) {
|
||||||
|
if (!mkProperties.isCheckReferer()) {
|
||||||
|
// 未开启校验来源,校验通过
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (StringUtil.isBlank(mkProperties.getErpBaseUrls())) {
|
||||||
|
log.error("未配置erp地址,无法校验来源");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (StringUtil.isBlank(refererUrl)) {
|
||||||
|
// 来源地址为空,通过,以便用于调试
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 匹配到一个erp基础地址就行了
|
||||||
|
return Stream.of(mkProperties.getErpBaseUrls().split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.anyMatch(refererUrl::startsWith);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.springblade.auth.exception;
|
||||||
|
|
||||||
|
import org.springblade.auth.endpoint.OAuth2MKEndpoint;
|
||||||
|
import org.springblade.core.oauth2.exception.OAuth2ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/10/16
|
||||||
|
*/
|
||||||
|
@ControllerAdvice(basePackageClasses = OAuth2MKEndpoint.class)
|
||||||
|
public class OAuth2MKExceptionHandler extends OAuth2ExceptionHandler {
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#服务器端口
|
|
||||||
server:
|
|
||||||
port: 8100
|
|
||||||
|
|
||||||
#数据源配置
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: ${blade.datasource.dev.url}
|
|
||||||
username: ${blade.datasource.dev.username}
|
|
||||||
password: ${blade.datasource.dev.password}
|
|
||||||
|
|
||||||
#第三方登陆
|
|
||||||
social:
|
|
||||||
enabled: true
|
|
||||||
domain: http://127.0.0.1:2888
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#服务器端口
|
|
||||||
server:
|
|
||||||
port: 8100
|
|
||||||
|
|
||||||
#数据源配置
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: ${blade.datasource.prod.url}
|
|
||||||
username: ${blade.datasource.prod.username}
|
|
||||||
password: ${blade.datasource.prod.password}
|
|
||||||
|
|
||||||
#第三方登陆
|
|
||||||
social:
|
|
||||||
enabled: true
|
|
||||||
domain: http://127.0.0.1:2888
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#服务器端口
|
|
||||||
server:
|
|
||||||
port: 8100
|
|
||||||
|
|
||||||
#数据源配置
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: ${blade.datasource.test.url}
|
|
||||||
username: ${blade.datasource.test.username}
|
|
||||||
password: ${blade.datasource.test.password}
|
|
||||||
|
|
||||||
#第三方登陆
|
|
||||||
social:
|
|
||||||
enabled: true
|
|
||||||
domain: http://127.0.0.1:2888
|
|
||||||
@@ -1,6 +1,31 @@
|
|||||||
# 在使用Spring默认数据源Hikari的情况下配置以下配置项
|
#服务器端口
|
||||||
|
server:
|
||||||
|
port: 8100
|
||||||
spring:
|
spring:
|
||||||
|
application:
|
||||||
|
name: blade-auth
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- nacos:blade.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:blade-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:third-party-api.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: ${NACOS_USERNAME:nacos}
|
||||||
|
password: ${NACOS_PASSWORD:nacos}
|
||||||
|
server-addr: ${NACOS_HOST:127.0.0.1:8848}
|
||||||
|
discovery:
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
|
config:
|
||||||
|
# 文件后缀名
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
datasource:
|
datasource:
|
||||||
|
url: ${blade.datasource.${spring.profiles.active}.url}
|
||||||
|
username: ${blade.datasource.${spring.profiles.active}.username}
|
||||||
|
password: ${blade.datasource.${spring.profiles.active}.password}
|
||||||
|
# 在使用Spring默认数据源Hikari的情况下配置以下配置项
|
||||||
hikari:
|
hikari:
|
||||||
# 自动提交从池中返回的连接
|
# 自动提交从池中返回的连接
|
||||||
auto-commit: true
|
auto-commit: true
|
||||||
@@ -42,6 +67,8 @@ swagger:
|
|||||||
|
|
||||||
#第三方登陆
|
#第三方登陆
|
||||||
social:
|
social:
|
||||||
|
enabled: true
|
||||||
|
domain: http://127.0.0.1:2888
|
||||||
oauth:
|
oauth:
|
||||||
GITHUB:
|
GITHUB:
|
||||||
client-id: 233************
|
client-id: 233************
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package org.springblade.common.launch;
|
|||||||
|
|
||||||
import org.springblade.common.constant.LauncherConstant;
|
import org.springblade.common.constant.LauncherConstant;
|
||||||
import org.springblade.core.auto.service.AutoService;
|
import org.springblade.core.auto.service.AutoService;
|
||||||
|
import org.springblade.core.launch.BladeApplication;
|
||||||
import org.springblade.core.launch.service.LauncherService;
|
import org.springblade.core.launch.service.LauncherService;
|
||||||
import org.springblade.core.launch.utils.PropsUtil;
|
import org.springblade.core.launch.utils.PropsUtil;
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
@@ -45,6 +46,7 @@ public class LauncherServiceImpl implements LauncherService {
|
|||||||
public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
|
public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
|
||||||
Properties props = System.getProperties();
|
Properties props = System.getProperties();
|
||||||
|
|
||||||
|
if (BladeApplication.isNacosConfigEnabled()) {
|
||||||
// nacos注册中心配置
|
// nacos注册中心配置
|
||||||
PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.username", LauncherConstant.NACOS_USERNAME);
|
PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.username", LauncherConstant.NACOS_USERNAME);
|
||||||
PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.password", LauncherConstant.NACOS_PASSWORD);
|
PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.password", LauncherConstant.NACOS_PASSWORD);
|
||||||
@@ -53,6 +55,8 @@ public class LauncherServiceImpl implements LauncherService {
|
|||||||
PropsUtil.setProperty(props, "spring.cloud.nacos.config.username", LauncherConstant.NACOS_USERNAME);
|
PropsUtil.setProperty(props, "spring.cloud.nacos.config.username", LauncherConstant.NACOS_USERNAME);
|
||||||
PropsUtil.setProperty(props, "spring.cloud.nacos.config.password", LauncherConstant.NACOS_PASSWORD);
|
PropsUtil.setProperty(props, "spring.cloud.nacos.config.password", LauncherConstant.NACOS_PASSWORD);
|
||||||
PropsUtil.setProperty(props, "spring.cloud.nacos.config.server-addr", LauncherConstant.nacosAddr(profile));
|
PropsUtil.setProperty(props, "spring.cloud.nacos.config.server-addr", LauncherConstant.nacosAddr(profile));
|
||||||
|
}
|
||||||
|
|
||||||
// sentinel配置
|
// sentinel配置
|
||||||
PropsUtil.setProperty(props, "spring.cloud.sentinel.transport.dashboard", LauncherConstant.sentinelAddr(profile));
|
PropsUtil.setProperty(props, "spring.cloud.sentinel.transport.dashboard", LauncherConstant.sentinelAddr(profile));
|
||||||
// 多数据源配置
|
// 多数据源配置
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ public class AuthProvider {
|
|||||||
DEFAULT_SKIP_URL.add("/oauth/callback/**");
|
DEFAULT_SKIP_URL.add("/oauth/callback/**");
|
||||||
DEFAULT_SKIP_URL.add("/oauth/revoke/**");
|
DEFAULT_SKIP_URL.add("/oauth/revoke/**");
|
||||||
DEFAULT_SKIP_URL.add("/oauth/refresh/**");
|
DEFAULT_SKIP_URL.add("/oauth/refresh/**");
|
||||||
|
DEFAULT_SKIP_URL.add("/oauth/mk/**");
|
||||||
DEFAULT_SKIP_URL.add("/token/**");
|
DEFAULT_SKIP_URL.add("/token/**");
|
||||||
DEFAULT_SKIP_URL.add("/actuator/**");
|
DEFAULT_SKIP_URL.add("/actuator/**");
|
||||||
DEFAULT_SKIP_URL.add("/v3/api-docs/**");
|
DEFAULT_SKIP_URL.add("/v3/api-docs/**");
|
||||||
|
|||||||
@@ -83,11 +83,11 @@
|
|||||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||||
</dependency>-->
|
</dependency>-->
|
||||||
<!--Taobao-Sdk-->
|
<!--Taobao-Sdk-->
|
||||||
<dependency>
|
<!-- <dependency>
|
||||||
<groupId>com.taobao</groupId>
|
<groupId>com.taobao</groupId>
|
||||||
<artifactId>taobao-sdk</artifactId>
|
<artifactId>taobao-sdk</artifactId>
|
||||||
<version>20201116</version>
|
<version>20201116</version>
|
||||||
</dependency>
|
</dependency>-->
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
+3
-30
@@ -1,6 +1,6 @@
|
|||||||
package org.springblade.openapi.mk.api;
|
package org.springblade.openapi.mk.api;
|
||||||
|
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.FR;
|
||||||
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -14,9 +14,6 @@ public interface IApi4MK {
|
|||||||
String API_PREFIX = "/openApi/mk";
|
String API_PREFIX = "/openApi/mk";
|
||||||
String PROCESS_API_PREFIX = API_PREFIX + "/process";
|
String PROCESS_API_PREFIX = API_PREFIX + "/process";
|
||||||
String PROCESS_FINISH_CALLBACK = PROCESS_API_PREFIX + "/finishCallback";
|
String PROCESS_FINISH_CALLBACK = PROCESS_API_PREFIX + "/finishCallback";
|
||||||
String PROCESS_APPROVAL_CALLBACK = PROCESS_API_PREFIX + "/approvalCallback";
|
|
||||||
String PROCESS_REJECT_CALLBACK = PROCESS_API_PREFIX + "/rejectCallback";
|
|
||||||
String PROCESS_REVOKE_CALLBACK = PROCESS_API_PREFIX + "/revokeCallback";
|
|
||||||
String PROCESS_COMMON_CALLBACK = PROCESS_API_PREFIX + "/commonCallback";
|
String PROCESS_COMMON_CALLBACK = PROCESS_API_PREFIX + "/commonCallback";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +22,7 @@ public interface IApi4MK {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@PostMapping(PROCESS_COMMON_CALLBACK)
|
@PostMapping(PROCESS_COMMON_CALLBACK)
|
||||||
R<Boolean> processCommonCallback(@RequestBody Api4MKProcessApprovalDTO param);
|
FR<Boolean> processCommonCallback(@RequestBody Api4MKProcessApprovalDTO param);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程结束回调接口
|
* 流程结束回调接口
|
||||||
@@ -33,29 +30,5 @@ public interface IApi4MK {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@PostMapping(PROCESS_FINISH_CALLBACK)
|
@PostMapping(PROCESS_FINISH_CALLBACK)
|
||||||
R<Boolean> processFinishCallback(@RequestBody Api4MKProcessApprovalDTO param);
|
FR<Boolean> processFinishCallback(@RequestBody Api4MKProcessApprovalDTO param);
|
||||||
|
|
||||||
/**
|
|
||||||
* 流程审批同意回调接口
|
|
||||||
* @param param
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@PostMapping(PROCESS_APPROVAL_CALLBACK)
|
|
||||||
R<Boolean> processApprovalCallback(@RequestBody Api4MKProcessApprovalDTO param);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 流程审批拒绝回调接口
|
|
||||||
* @param param
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@PostMapping(PROCESS_REJECT_CALLBACK)
|
|
||||||
R<Boolean> processRejectCallback(@RequestBody Api4MKProcessApprovalDTO param);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 流程撤销回调接口
|
|
||||||
* @param param
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@PostMapping(PROCESS_REVOKE_CALLBACK)
|
|
||||||
R<Boolean> processRevokeCallback(@RequestBody Api4MKProcessApprovalDTO param);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package org.springblade.openapi.mk.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程当前处理人相关redis锁key常量类
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2026/4/9
|
||||||
|
*/
|
||||||
|
public class ProcessLockKeyConstant {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务缓存key前缀
|
||||||
|
*/
|
||||||
|
public static final String TASK_KEY_PREFIX = "process:cur-handler:task:";
|
||||||
|
/**
|
||||||
|
* 等待队列key
|
||||||
|
*/
|
||||||
|
public static final String WAITING_KEY = "process:cur-handler:waiting";
|
||||||
|
/**
|
||||||
|
* 流程锁key前缀
|
||||||
|
*/
|
||||||
|
public static final String PROCESS_LOCK_KEY_PREFIX = "process:cur-handler:lock:";
|
||||||
|
/**
|
||||||
|
* 派工锁key
|
||||||
|
*/
|
||||||
|
public static final String DISPATCH_LOCK_KEY = "process:cur-handler:dispatch-lock";
|
||||||
|
/**
|
||||||
|
* worker租约map key
|
||||||
|
*/
|
||||||
|
public static final String WORKER_LEASE_KEY = "process:cur-handler:worker-leases";
|
||||||
|
}
|
||||||
-9
@@ -69,13 +69,4 @@ public class Api4MKProcessApprovalDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String operatorLoginName;
|
private String operatorLoginName;
|
||||||
|
|
||||||
//====================非mk回调参数,回调接口设置参数===================
|
|
||||||
/**
|
|
||||||
* 是否流程已完成,非mk回调参数,回调接口设置参数
|
|
||||||
*/
|
|
||||||
private boolean complete;
|
|
||||||
/**
|
|
||||||
* 审批状态,非mk回调参数,回调接口设置参数
|
|
||||||
*/
|
|
||||||
private String approveStatus;
|
|
||||||
}
|
}
|
||||||
|
|||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
package org.springblade.openapi.mk.pojo.dto;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mk审批结束回调参数
|
|
||||||
* @author bfhuange
|
|
||||||
* @date 2024/9/5
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ApiMKProcessFinishDTO implements Serializable {
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
/**
|
|
||||||
* 流程实例id
|
|
||||||
*/
|
|
||||||
private String processInstanceId;
|
|
||||||
/**
|
|
||||||
* 表单实例id
|
|
||||||
*/
|
|
||||||
private String formInstanceId;
|
|
||||||
/**
|
|
||||||
* 模板编码,template_拼接 ProcessTypeEnum 的值
|
|
||||||
*/
|
|
||||||
private String templateCode;
|
|
||||||
/**
|
|
||||||
* 流程状态
|
|
||||||
*/
|
|
||||||
private String processStatus;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?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>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springblade</groupId>
|
||||||
|
<artifactId>blade-service-api</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>blade-process-api</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
</project>
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package org.springblade.process.feign;
|
||||||
|
|
||||||
|
import org.springblade.core.launch.constant.AppConstant;
|
||||||
|
import org.springblade.core.tool.api.FR;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessCurrentHandlerRefreshDTO;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
|
||||||
|
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessTodoVO;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 Feign接口类
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
@FeignClient(
|
||||||
|
value = AppConstant.APPLICATION_SYSTEM_NAME
|
||||||
|
)
|
||||||
|
public interface IBusinessProcessClient {
|
||||||
|
|
||||||
|
String API_PREFIX = "/feign/client/businessProcess";
|
||||||
|
String SUBMIT_BUSINESS_PROCESS = API_PREFIX + "/submitBusinessProcess";
|
||||||
|
String UPDATE_BUSINESS_PROCESS_APPROVER = API_PREFIX + "/updateBusinessProcessApprover";
|
||||||
|
String REFRESH_BUSINESS_PROCESS_CURRENT_HANDLERS = API_PREFIX + "/refreshBusinessProcessCurrentHandlers";
|
||||||
|
String UPDATE_BUSINESS_PROCESS_STATUS = API_PREFIX + "/updateBusinessProcessStatus";
|
||||||
|
String DELETE_BUSINESS_PROCESS = API_PREFIX + "/deleteBusinessProcess";
|
||||||
|
String QUERY_TODO_LIST = API_PREFIX + "/queryTodoList";
|
||||||
|
String QUERY_BUSINESS_PROCESS_SNAPSHOT = API_PREFIX + "/queryBusinessProcessSnapshot";
|
||||||
|
String QUERY_APPROVED_RECORD_LIST = API_PREFIX + "/queryApprovedRecordsNoAttachments";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交业务流程
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
@PostMapping(SUBMIT_BUSINESS_PROCESS)
|
||||||
|
FR<BusinessProcessVO> submitBusinessProcess(@Validated @RequestBody BusinessProcessSubmitDTO<?> param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改业务流程状态
|
||||||
|
* @param param
|
||||||
|
* @return 审批状态
|
||||||
|
*/
|
||||||
|
@PostMapping(UPDATE_BUSINESS_PROCESS_STATUS)
|
||||||
|
FR<String> updateBusinessProcessStatus(@Validated @RequestBody BusinessProcessUpdateDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改业务流程审批人
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping(UPDATE_BUSINESS_PROCESS_APPROVER)
|
||||||
|
FR<BusinessProcessVO> updateBusinessProcessApprover(@Validated @RequestBody BusinessProcessUpdateDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只刷新当前节点和当前处理人
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping(REFRESH_BUSINESS_PROCESS_CURRENT_HANDLERS)
|
||||||
|
FR<BusinessProcessVO> refreshBusinessProcessCurrentHandlers(@Validated @RequestBody BusinessProcessCurrentHandlerRefreshDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询流程当前待办列表
|
||||||
|
* @param processInstanceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping(QUERY_TODO_LIST)
|
||||||
|
FR<List<ProcessTodoVO>> queryTodoList(@RequestParam("processInstanceId") String processInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询业务流程当前快照
|
||||||
|
* @param processInstanceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping(QUERY_BUSINESS_PROCESS_SNAPSHOT)
|
||||||
|
FR<BusinessProcessVO> queryBusinessProcessSnapshot(@RequestParam("processInstanceId") String processInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除业务流程
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping(DELETE_BUSINESS_PROCESS)
|
||||||
|
FR<Boolean> deleteBusinessProcess(@Validated @RequestBody BusinessProcessDeleteDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询流程审批记录不处理附件
|
||||||
|
* @param bizId
|
||||||
|
* @param processInstanceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping(QUERY_APPROVED_RECORD_LIST)
|
||||||
|
FR<List<ProcessApprovedRecordVO>> queryApprovedRecordsNoAttachments(@RequestParam(name = "bizId", required = false) String bizId, @RequestParam(name = "processInstanceId", required = false) String processInstanceId);
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 附加操作信息
|
||||||
|
*
|
||||||
|
* @author linbb
|
||||||
|
*/
|
||||||
|
@Schema(description = "附加操作信息")
|
||||||
|
@Data
|
||||||
|
public class AdditionOperationParameterDTO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型
|
||||||
|
*/
|
||||||
|
private String operationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作身份
|
||||||
|
*/
|
||||||
|
private String operationIdentity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作参数
|
||||||
|
*/
|
||||||
|
private String parameter;
|
||||||
|
}
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mk审批中心查询参数
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/4/2
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "mk审批中心查询参数")
|
||||||
|
public class ApprovalDTO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 单据类型 myApproving 我的待审,myApproved 我的已审,myReading 我的待阅,myReaded 我的已阅,myRelated 我参与的,myCreated 我发起的
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "单据类型不能为空")
|
||||||
|
@Schema(description = "单据类型 myApproving 我的待审,myApproved 我的已审,myReading 我的待阅,myReaded 我的已阅,myRelated 我参与的,myCreated 我发起的")
|
||||||
|
private String docType;
|
||||||
|
/**
|
||||||
|
* 关键字
|
||||||
|
*/
|
||||||
|
@Schema(description = "关键字")
|
||||||
|
private String keyword;
|
||||||
|
/**
|
||||||
|
* 模板名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "模板名称")
|
||||||
|
private String templateName;
|
||||||
|
/**
|
||||||
|
* 申请时间开始
|
||||||
|
*/
|
||||||
|
@Schema(description = "申请时间开始")
|
||||||
|
private Date applicantTimeStart;
|
||||||
|
/**
|
||||||
|
* 申请时间结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "申请时间结束")
|
||||||
|
private Date applicantTimeEnd;
|
||||||
|
|
||||||
|
//==================================待办参数=======================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收时间开始
|
||||||
|
*/
|
||||||
|
@Schema(description = "接收时间开始")
|
||||||
|
private Date receiveTimeStart;
|
||||||
|
/**
|
||||||
|
* 接收时间结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "接收时间结束")
|
||||||
|
private Date receiveTimeEnd;
|
||||||
|
|
||||||
|
//==================================已处理参数=======================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束时间开始
|
||||||
|
*/
|
||||||
|
@Schema(description = "结束时间开始")
|
||||||
|
private Date finishTimeStart;
|
||||||
|
/**
|
||||||
|
* 结束时间结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "结束时间结束")
|
||||||
|
private Date finishTimeEnd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后处理时间开始
|
||||||
|
*/
|
||||||
|
@Schema(description = "最后处理时间开始")
|
||||||
|
private Date lastHandleStart;
|
||||||
|
/**
|
||||||
|
* 最后处理时间结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "最后处理时间结束")
|
||||||
|
private Date lastHandleEnd;
|
||||||
|
|
||||||
|
//==================================已阅参数=======================
|
||||||
|
/**
|
||||||
|
* 阅读时间开始
|
||||||
|
*/
|
||||||
|
@Schema(description = "阅读时间开始")
|
||||||
|
private Date readTimeStart;
|
||||||
|
/**
|
||||||
|
* 阅读时间结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "阅读时间结束")
|
||||||
|
private Date readTimeEnd;
|
||||||
|
|
||||||
|
//==================================我参与的参数=======================
|
||||||
|
/**
|
||||||
|
* 创建时间开始
|
||||||
|
*/
|
||||||
|
@Schema(description = "创建时间开始")
|
||||||
|
private Date createTimeStart;
|
||||||
|
/**
|
||||||
|
* 创建时间结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "创建时间结束")
|
||||||
|
private Date createTimeEnd;
|
||||||
|
/**
|
||||||
|
* 登录名
|
||||||
|
*/
|
||||||
|
@Schema(description = "登录名", hidden = true)
|
||||||
|
private String loginName;
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程当前处理人刷新参数
|
||||||
|
*
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2026/4/9
|
||||||
|
*/
|
||||||
|
@Schema(description = "业务流程当前处理人刷新参数")
|
||||||
|
@Data
|
||||||
|
public class BusinessProcessCurrentHandlerRefreshDTO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程实例id
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "流程实例id不能为空")
|
||||||
|
@Schema(description = "流程实例id")
|
||||||
|
private String processInstanceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起人登录名,可为空。
|
||||||
|
* 为空时优先从业务流程表回填;仅当业务流程不存在时,才回退使用调用方传入值。
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人登录名,可为空;为空时优先从业务流程表回填")
|
||||||
|
private String promoterLoginName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否流程已完成
|
||||||
|
*/
|
||||||
|
@Schema(description = "是否流程已完成")
|
||||||
|
private boolean complete;
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程删除参数
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-11-26
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Schema(description = "业务流程删除参数")
|
||||||
|
@Data
|
||||||
|
public class BusinessProcessDeleteDTO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 业务id
|
||||||
|
*/
|
||||||
|
@NotNull(message = "业务id不能为空")
|
||||||
|
@Schema(description = "业务id")
|
||||||
|
private Long bizId;
|
||||||
|
/**
|
||||||
|
* 发起人登录名,即erp手机号或账号
|
||||||
|
*/
|
||||||
|
// @NotBlank(message = "发起人登录名不能为空")
|
||||||
|
@Schema(description = "发起人登录名,即erp手机号或账号")
|
||||||
|
private String promoterLoginName;
|
||||||
|
|
||||||
|
public BusinessProcessDeleteDTO(Long bizId) {
|
||||||
|
this.bizId = bizId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程查询参数
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-23
|
||||||
|
*/
|
||||||
|
@Schema(description = "业务流程查询参数")
|
||||||
|
@Data
|
||||||
|
public class BusinessProcessQueryDTO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 流程类型
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批类型")
|
||||||
|
private String processType;
|
||||||
|
/**
|
||||||
|
* 文档编号
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批编号")
|
||||||
|
private String docCode;
|
||||||
|
/**
|
||||||
|
* 类型 todo:待审批,create:我创建的,done:我参与的
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "类型不能为空")
|
||||||
|
@Schema(defaultValue = "类型 todo:待审批,create:我创建的,done:我参与的")
|
||||||
|
private String type;
|
||||||
|
/**
|
||||||
|
* 当前登录人账号
|
||||||
|
*/
|
||||||
|
@Schema(hidden = true)
|
||||||
|
private String loginName;
|
||||||
|
}
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程提交参数
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
@Schema(description = "业务流程提交参数")
|
||||||
|
@Data
|
||||||
|
public class BusinessProcessSubmitDTO<T> implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务id
|
||||||
|
*/
|
||||||
|
@NotNull(message = "业务id不能为空")
|
||||||
|
@Schema(description = "业务id")
|
||||||
|
private Long bizId;
|
||||||
|
/**
|
||||||
|
* 流程类型
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "流程类型不能为空")
|
||||||
|
@Schema(description = "流程类型")
|
||||||
|
private String processType;
|
||||||
|
/**
|
||||||
|
* 文档编号
|
||||||
|
*/
|
||||||
|
@Schema(description = "文档编号")
|
||||||
|
private String docCode;
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
*/
|
||||||
|
@Schema(description = "标题")
|
||||||
|
private String subject;
|
||||||
|
/**
|
||||||
|
* 发起人id,空自动取登录人id
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人id")
|
||||||
|
private Long promoterId;
|
||||||
|
/**
|
||||||
|
* 发起人名称,空自动取登录人名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人名称")
|
||||||
|
private String promoterName;
|
||||||
|
/**
|
||||||
|
* 发起人登录名
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人登录名")
|
||||||
|
private String promoterLoginName;
|
||||||
|
/**
|
||||||
|
* 提交时间,空自动取当前时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "提交时间")
|
||||||
|
private Date submitTime;
|
||||||
|
/**
|
||||||
|
* 流程参数,如果流程没有用到参数做条件判断或动态部门,可以不传
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程参数")
|
||||||
|
private T processParam;
|
||||||
|
/**
|
||||||
|
* 流程执行参数,透传mk参数
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程执行参数")
|
||||||
|
private ProcessExecuteDTO executeParam;
|
||||||
|
/**
|
||||||
|
* 添加群组编码
|
||||||
|
*/
|
||||||
|
@Schema(description = "添加群组编码")
|
||||||
|
private boolean addGroupCode;
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程修改参数
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
@Schema(description = "业务流程修改参数")
|
||||||
|
@Data
|
||||||
|
public class BusinessProcessUpdateDTO extends BusinessProcessCurrentHandlerRefreshDTO {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 操作节点id,流程审批结束为空
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作节点id")
|
||||||
|
private String operationNodeId;
|
||||||
|
/**
|
||||||
|
* 操作节点编号,流程审批结束为空
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作节点编号")
|
||||||
|
private String operationNodeNumber;
|
||||||
|
/**
|
||||||
|
* 驳回节点id N2 是起草节点
|
||||||
|
*/
|
||||||
|
@Schema(description = "驳回节点id")
|
||||||
|
private String rejectNodeId;
|
||||||
|
/**
|
||||||
|
* 审批状态
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批状态")
|
||||||
|
private String approveStatus;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.springblade.openapi.mk.pojo.dto;
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mk 流程执行参数
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/26
|
||||||
|
*/
|
||||||
|
@Schema(description = "mk 流程执行参数")
|
||||||
|
@Data
|
||||||
|
public class ProcessExecuteDTO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 表单实例id,业务id
|
||||||
|
*/
|
||||||
|
private String formInstanceId;
|
||||||
|
/**
|
||||||
|
* 登录账号 登录用户账号/手机号
|
||||||
|
*/
|
||||||
|
private String loginName;
|
||||||
|
/**
|
||||||
|
* 流程标题
|
||||||
|
*/
|
||||||
|
private String subject;
|
||||||
|
/**
|
||||||
|
* 任务ID
|
||||||
|
*/
|
||||||
|
private String taskId;
|
||||||
|
/**
|
||||||
|
* 任务类型
|
||||||
|
*/
|
||||||
|
private String activityType;
|
||||||
|
/**
|
||||||
|
* 操作详细参数(json)
|
||||||
|
*/
|
||||||
|
private String parameter;
|
||||||
|
/**
|
||||||
|
* 流程实例ID
|
||||||
|
*/
|
||||||
|
private String processId;
|
||||||
|
/**
|
||||||
|
* 操作标识(相同操作类型和相同操作身份可能存在多个操作配置)
|
||||||
|
*/
|
||||||
|
private String operationId;
|
||||||
|
/**
|
||||||
|
* 操作类型
|
||||||
|
*/
|
||||||
|
private String operationType;
|
||||||
|
/**
|
||||||
|
* 操作身份
|
||||||
|
*/
|
||||||
|
private String operationIdentity;
|
||||||
|
/**
|
||||||
|
* 附加操作参数信息
|
||||||
|
*/
|
||||||
|
private List<AdditionOperationParameterDTO> additionParameters;
|
||||||
|
/**
|
||||||
|
* 表单实例Model Name
|
||||||
|
*/
|
||||||
|
private String formInstanceModel;
|
||||||
|
/**
|
||||||
|
* 业务表单字段值集合
|
||||||
|
*/
|
||||||
|
// private Map<String, Object> formValues;
|
||||||
|
private Object formValues;
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package org.springblade.process.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/3/10
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class ProcessNodeApprovalDTO extends ProcessApprovalDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作节点id,流程审批结束为空
|
||||||
|
*/
|
||||||
|
private String operationNodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作节点编号,流程审批结束为空
|
||||||
|
*/
|
||||||
|
private String operationNodeNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作人登录名
|
||||||
|
*/
|
||||||
|
private String operatorLoginName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程模板id
|
||||||
|
*/
|
||||||
|
private String templateId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驳回节点id N2 是起草节点
|
||||||
|
*/
|
||||||
|
private String rejectNodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否流程已完成,非mk回调参数,回调接口设置参数
|
||||||
|
*/
|
||||||
|
private boolean complete;
|
||||||
|
|
||||||
|
@Builder(toBuilder = true, builderMethodName = "subBuilder", buildMethodName = "subBuild")
|
||||||
|
public ProcessNodeApprovalDTO(String flowInstId, String formInstanceId, String approveStatus, String nextApproveUser, String operationNodeId, String operationNodeNumber, String operatorLoginName, String templateId, String rejectNodeId, boolean complete) {
|
||||||
|
super(flowInstId, formInstanceId, approveStatus, nextApproveUser);
|
||||||
|
this.operationNodeId = operationNodeId;
|
||||||
|
this.operationNodeNumber = operationNodeNumber;
|
||||||
|
this.operatorLoginName = operatorLoginName;
|
||||||
|
this.templateId = templateId;
|
||||||
|
this.rejectNodeId = rejectNodeId;
|
||||||
|
this.complete = complete;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package org.springblade.process.pojo.dto.process;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共部门编码流程参数
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/10/9
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CommonDeptCodeProcessParam implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 部门编码
|
||||||
|
*/
|
||||||
|
private String deptCode;
|
||||||
|
|
||||||
|
}
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* 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.process.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 实体类
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("blade_business_process")
|
||||||
|
@Schema(description = "BusinessProcess对象")
|
||||||
|
public class BusinessProcess implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "主键")
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务id
|
||||||
|
*/
|
||||||
|
@Schema(description = "业务id")
|
||||||
|
private Long bizId;
|
||||||
|
/**
|
||||||
|
* 流程实例id
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程实例id")
|
||||||
|
private String processInstanceId;
|
||||||
|
/**
|
||||||
|
* 流程类型
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程类型")
|
||||||
|
private String processType;
|
||||||
|
/**
|
||||||
|
* 文档编号
|
||||||
|
*/
|
||||||
|
@Schema(description = "文档编号")
|
||||||
|
private String docCode;
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
*/
|
||||||
|
@Schema(description = "标题")
|
||||||
|
private String subject;
|
||||||
|
/**
|
||||||
|
* 发起人id
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人id")
|
||||||
|
private Long promoterId;
|
||||||
|
/**
|
||||||
|
* 发起人名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人名称")
|
||||||
|
private String promoterName;
|
||||||
|
/**
|
||||||
|
* 发起人登录名
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人登录名")
|
||||||
|
private String promoterLoginName;
|
||||||
|
/**
|
||||||
|
* 提交时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "提交时间")
|
||||||
|
private Date submitTime;
|
||||||
|
/**
|
||||||
|
* 完成时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "完成时间")
|
||||||
|
private Date completeTime;
|
||||||
|
/**
|
||||||
|
* 当前节点id,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前节点id,多个用逗号拼接")
|
||||||
|
private String currentNodeIds;
|
||||||
|
/**
|
||||||
|
* 当前节点名称,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前节点名称,多个用逗号拼接")
|
||||||
|
private String currentNodeNames;
|
||||||
|
/**
|
||||||
|
* 当前处理人,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前处理人,多个用逗号拼接")
|
||||||
|
private String currentHandlers;
|
||||||
|
/**
|
||||||
|
* 接收时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "接收时间")
|
||||||
|
private Date receiveTime;
|
||||||
|
/**
|
||||||
|
* 是否已完成(0:未完成, 1:已完成)
|
||||||
|
*/
|
||||||
|
@Schema(description = "是否已完成")
|
||||||
|
private Integer isCompleted;
|
||||||
|
/**
|
||||||
|
* 审批状态
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批状态")
|
||||||
|
private String approveStatus;
|
||||||
|
/**
|
||||||
|
* 租户ID
|
||||||
|
*/
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private String tenantId;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATE)
|
||||||
|
@Schema(description = "创建时间", hidden = true)
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private Date createTime;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATE)
|
||||||
|
@Schema(description = "更新时间", hidden = true)
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
+165
@@ -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.process.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批状态
|
||||||
|
*
|
||||||
|
* @author LiuXinjie
|
||||||
|
* @apiNote 合同审批状态
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ApproveStatusEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认编号
|
||||||
|
*/
|
||||||
|
DRAFT("draft", "草稿"), //可提交
|
||||||
|
APPROVING("approval", "审批中"),
|
||||||
|
APPROVED("pass", "审批通过"),
|
||||||
|
REJECTED("reject", "审批驳回"), //通用的流程 驳回可编辑
|
||||||
|
REVOCATION("revocation", "已撤回"), //可重新提交
|
||||||
|
ABANDON("abandon", "废弃"),
|
||||||
|
;
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
public boolean match(String value){
|
||||||
|
return this.value.equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getValueByText(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (ApproveStatusEnum item : values()) {
|
||||||
|
if (Objects.equals(item.getText(), text)) {
|
||||||
|
return item.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTextByValue(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (ApproveStatusEnum item : values()) {
|
||||||
|
if (Objects.equals(item.getValue(), value)) {
|
||||||
|
return item.getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可以撤回
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean canRevoke(String value) {
|
||||||
|
return APPROVING.getValue().equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 能不能删除审批流
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean canDelAuditFlow(String value){
|
||||||
|
return REJECTED.getValue().equals(value) || REVOCATION.getValue().equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驳回或撤回
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean rejectedOrRevocation(String approveStatus) {
|
||||||
|
return canDelAuditFlow(approveStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 能不能删除数据
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean canDeleteData(String value){
|
||||||
|
return ABANDON.getValue().equals(value) || DRAFT.getValue().equals(value) || canDelAuditFlow(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNameStr(String value) {
|
||||||
|
for (ApproveStatusEnum state : values()) {
|
||||||
|
if (state.value.equals(value)) {
|
||||||
|
return state.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可以编辑表单
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean canEdit(String value) {
|
||||||
|
return DRAFT.getValue().equals(value) || REJECTED.getValue().equals(value) || REVOCATION.getValue().equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可驳回状态
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<String> buildPreviousApproveStatusList(String currentStatus) {
|
||||||
|
if (ApproveStatusEnum.APPROVING.getValue().equals(currentStatus)) {
|
||||||
|
// 变更成审批中,前置条件为 草稿或者审批中
|
||||||
|
return List.of(ApproveStatusEnum.APPROVING.getValue());
|
||||||
|
} else if (ApproveStatusEnum.APPROVED.getValue().equals(currentStatus)) {
|
||||||
|
// 变更成审批通过,前置条件为 审批中
|
||||||
|
return List.of(ApproveStatusEnum.APPROVING.getValue());
|
||||||
|
} else if (ApproveStatusEnum.REJECTED.getValue().equals(currentStatus)) {
|
||||||
|
// 变更成审批驳回,前置条件为 审批中
|
||||||
|
return List.of(ApproveStatusEnum.APPROVING.getValue());
|
||||||
|
} else if (ApproveStatusEnum.REVOCATION.getValue().equals(currentStatus)) {
|
||||||
|
// 变更成撤回,前置条件为 审批中
|
||||||
|
return List.of(ApproveStatusEnum.APPROVING.getValue());
|
||||||
|
} else {
|
||||||
|
// 其他情况,前置条件为 草稿
|
||||||
|
return List.of(ApproveStatusEnum.DRAFT.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package org.springblade.process.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待办状态枚举
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/20
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum TodoStatus {
|
||||||
|
/**
|
||||||
|
* 待办
|
||||||
|
*/
|
||||||
|
TODO("todo", "待办"),
|
||||||
|
/**
|
||||||
|
* 已办
|
||||||
|
*/
|
||||||
|
DONE("done", "已办"),
|
||||||
|
/**
|
||||||
|
* 身份重复跳过
|
||||||
|
*/
|
||||||
|
SKIP("skip", "身份重复跳过"),
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待办状态编码
|
||||||
|
*/
|
||||||
|
private final String code;
|
||||||
|
/**
|
||||||
|
* 待办状态名称
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
}
|
||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
package org.springblade.process.pojo.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/4/2
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "mk审批中心记录")
|
||||||
|
public class ApprovalVO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
@Schema(description = "id")
|
||||||
|
private String id;
|
||||||
|
/**
|
||||||
|
* 流程所属应用
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程所属应用")
|
||||||
|
private String appName;
|
||||||
|
/**
|
||||||
|
* 申请人名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "申请人名称")
|
||||||
|
private String applicantName;
|
||||||
|
/**
|
||||||
|
* 申请人登录名
|
||||||
|
*/
|
||||||
|
@Schema(description = "申请人登录名")
|
||||||
|
private String applicantLoginName;
|
||||||
|
/**
|
||||||
|
* 发起人名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人名称")
|
||||||
|
private String creator;
|
||||||
|
/**
|
||||||
|
* 处理人名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "处理人名称")
|
||||||
|
private String handlerName;
|
||||||
|
/**
|
||||||
|
* 处理人登录名
|
||||||
|
*/
|
||||||
|
@Schema(description = "处理人登录名")
|
||||||
|
private String handlerLoginName;
|
||||||
|
/**
|
||||||
|
* 当前处理人名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前处理人名称")
|
||||||
|
private String currentHandler;
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点名称")
|
||||||
|
private String nodeName;
|
||||||
|
/**
|
||||||
|
* 流程id
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程id")
|
||||||
|
private String processId;
|
||||||
|
/**
|
||||||
|
* 流程发起时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程发起时间")
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
|
||||||
|
private Date startTime;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
|
||||||
|
private Date createTime;
|
||||||
|
/**
|
||||||
|
* 如果是待办:待办接收时间 如果是待阅:传阅接收时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "如果是待办:待办接收时间 如果是待阅:传阅接收时间")
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
|
||||||
|
private Date receiveTime;
|
||||||
|
/**
|
||||||
|
* 任务结束时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "任务结束时间")
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
|
||||||
|
private Date finishTime;
|
||||||
|
/**
|
||||||
|
* 阅读时间(待阅任务)
|
||||||
|
*/
|
||||||
|
@Schema(description = "阅读时间(待阅任务)")
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
|
||||||
|
private Date readTime;
|
||||||
|
/**
|
||||||
|
* 最后处理时间(待审任务)
|
||||||
|
*/
|
||||||
|
@Schema(description = "最后处理时间(待审任务)")
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
|
||||||
|
private Date lastHandleTime;
|
||||||
|
/**
|
||||||
|
* 流程结束时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程结束时间")
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
|
||||||
|
private Date processFinishTime;
|
||||||
|
/**
|
||||||
|
* 流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束")
|
||||||
|
private String status;
|
||||||
|
/**
|
||||||
|
* 流程状态名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程状态名称")
|
||||||
|
private String statusStr;
|
||||||
|
/**
|
||||||
|
* 流程主题
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程主题")
|
||||||
|
private String subject;
|
||||||
|
/**
|
||||||
|
* 模板编码
|
||||||
|
*/
|
||||||
|
@Schema(description = "模板编码")
|
||||||
|
private String templateCode;
|
||||||
|
/**
|
||||||
|
* 任务id
|
||||||
|
*/
|
||||||
|
@Schema(description = "任务id")
|
||||||
|
private String taskId;
|
||||||
|
/**
|
||||||
|
* 任务状态 20 - 激活、30 - 结束、40 - 挂起、50 - 自动跳过
|
||||||
|
*/
|
||||||
|
@Schema(description = "任务状态 20 - 激活、30 - 结束、40 - 挂起、50 - 自动跳过")
|
||||||
|
private String taskStatus;
|
||||||
|
/**
|
||||||
|
* 任务标题
|
||||||
|
*/
|
||||||
|
@Schema(description = "任务标题")
|
||||||
|
private String taskSubject;
|
||||||
|
/**
|
||||||
|
* 催办标记
|
||||||
|
*/
|
||||||
|
@Schema(description = "催办标记")
|
||||||
|
private String urgeTab;
|
||||||
|
/**
|
||||||
|
* 任务类型 1待办,2待阅
|
||||||
|
*/
|
||||||
|
@Schema(description = "任务类型 1待办,2待阅")
|
||||||
|
private Integer taskType;
|
||||||
|
/**
|
||||||
|
* 待办优先级
|
||||||
|
*/
|
||||||
|
@Schema(description = "待办优先级")
|
||||||
|
private Integer level;
|
||||||
|
/**
|
||||||
|
* 模板名称中文
|
||||||
|
*/
|
||||||
|
@Schema(description = "模板名称中文")
|
||||||
|
private String templateNameCn;
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package org.springblade.process.pojo.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/23
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "BusinessProcessListVO")
|
||||||
|
public class BusinessProcessListVO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务id
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "业务id")
|
||||||
|
private Long bizId;
|
||||||
|
/**
|
||||||
|
* 流程实例id
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程实例id")
|
||||||
|
private String processInstanceId;
|
||||||
|
/**
|
||||||
|
* 流程类型
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批类型")
|
||||||
|
private String processType;
|
||||||
|
/**
|
||||||
|
* 流程类型名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批类型名称")
|
||||||
|
private String processTypeStr;
|
||||||
|
/**
|
||||||
|
* 文档编号
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批单号")
|
||||||
|
private String docCode;
|
||||||
|
/**
|
||||||
|
* 发起人id
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人id")
|
||||||
|
private Long promoterId;
|
||||||
|
/**
|
||||||
|
* 发起人名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "发起人名称")
|
||||||
|
private String promoterName;
|
||||||
|
/**
|
||||||
|
* 提交时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "提交时间")
|
||||||
|
private Date submitTime;
|
||||||
|
/**
|
||||||
|
* 接收时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "接收时间")
|
||||||
|
private Date receiveTime;
|
||||||
|
/**
|
||||||
|
* 完成时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "完成时间")
|
||||||
|
private Date completeTime;
|
||||||
|
/**
|
||||||
|
* 当前节点id,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前节点id,多个用逗号拼接")
|
||||||
|
private String currentNodeIds;
|
||||||
|
/**
|
||||||
|
* 当前节点名称,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前节点名称,多个用逗号拼接")
|
||||||
|
private String currentNodeNames;
|
||||||
|
/**
|
||||||
|
* 当前处理人,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前处理人,多个用逗号拼接")
|
||||||
|
private String currentHandlers;
|
||||||
|
/**
|
||||||
|
* 是否已完成(0:未完成, 1:已完成)
|
||||||
|
*/
|
||||||
|
@Schema(description = "是否已完成(0:未完成, 1:已完成)")
|
||||||
|
private Integer isCompleted;
|
||||||
|
/**
|
||||||
|
* 审批状态
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批状态")
|
||||||
|
private String approveStatus;
|
||||||
|
/**
|
||||||
|
* 审批状态名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "审批状态名称")
|
||||||
|
private String approveStatusStr;
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package org.springblade.process.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "BusinessProcessVO")
|
||||||
|
public class BusinessProcessVO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 流程实例id
|
||||||
|
*/
|
||||||
|
private String processInstanceId;
|
||||||
|
/**
|
||||||
|
* 当前节点id,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前节点id,多个用逗号拼接")
|
||||||
|
private String currentNodeIds;
|
||||||
|
/**
|
||||||
|
* 当前节点名称,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前节点名称,多个用逗号拼接")
|
||||||
|
private String currentNodeNames;
|
||||||
|
/**
|
||||||
|
* 当前处理人,多个用逗号拼接
|
||||||
|
*/
|
||||||
|
@Schema(description = "当前处理人,多个用逗号拼接")
|
||||||
|
private String currentHandlers;
|
||||||
|
/**
|
||||||
|
* 接收时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "接收时间")
|
||||||
|
private Date receiveTime;
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package org.springblade.process.pojo.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/2/24
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "流程审批记录")
|
||||||
|
public class ProcessApprovedRecordVO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* 记录主键
|
||||||
|
*/
|
||||||
|
@Schema(description = "id")
|
||||||
|
private String id;
|
||||||
|
/**
|
||||||
|
* 处理人
|
||||||
|
*/
|
||||||
|
@Schema(description = "处理人")
|
||||||
|
private String handler;
|
||||||
|
/**
|
||||||
|
* 操作
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作")
|
||||||
|
private String action;
|
||||||
|
/**
|
||||||
|
* 操作编码
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作编码")
|
||||||
|
private String actionCode;
|
||||||
|
/**
|
||||||
|
* 操作描述(系统操作)
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作描述(系统操作)")
|
||||||
|
private String actionDesc;
|
||||||
|
/**
|
||||||
|
* 操作名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作名称")
|
||||||
|
private String actionName;
|
||||||
|
/**
|
||||||
|
* 处理意见
|
||||||
|
*/
|
||||||
|
@Schema(description = "处理意见")
|
||||||
|
private String message;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createTime;
|
||||||
|
/**
|
||||||
|
* 流程实例id
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程实例id")
|
||||||
|
private String processInstanceId;
|
||||||
|
/**
|
||||||
|
* 节点实例id
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点实例id")
|
||||||
|
private String nodeInstanceId;
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点类型")
|
||||||
|
private String nodeType;
|
||||||
|
/**
|
||||||
|
* 节点id
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点id")
|
||||||
|
private String nodeId;
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点名称")
|
||||||
|
private String nodeName;
|
||||||
|
/**
|
||||||
|
* 节点编号
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点编号")
|
||||||
|
private String nodeNumber;
|
||||||
|
/**
|
||||||
|
* 抄送人列表
|
||||||
|
*/
|
||||||
|
@Schema(description = "抄送人列表")
|
||||||
|
private List<String> senders;
|
||||||
|
/**
|
||||||
|
* 附件参数
|
||||||
|
*/
|
||||||
|
@Schema(description = "附件参数")
|
||||||
|
private List<ProcessAttachmentVO> attachmentParameter;
|
||||||
|
/**
|
||||||
|
* 流程附言
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程附言")
|
||||||
|
private List<ProcessCommentVO> processComments;
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package org.springblade.process.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/2/24
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "流程附件")
|
||||||
|
public class ProcessAttachmentVO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
@Schema(description = "id")
|
||||||
|
private String id;
|
||||||
|
/**
|
||||||
|
* 附件名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "附件名称")
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 附件类型 electronicSign 电子签名,attachment 附件
|
||||||
|
*/
|
||||||
|
@Schema(description = "附件类型 electronicSign 电子签名,attachment 附件")
|
||||||
|
private String type;
|
||||||
|
/**
|
||||||
|
* 附件ID
|
||||||
|
*/
|
||||||
|
@Schema(description = "附件ID")
|
||||||
|
private String fileId;
|
||||||
|
/**
|
||||||
|
* base64
|
||||||
|
*/
|
||||||
|
@Schema(description = "base64")
|
||||||
|
private String base64;
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package org.springblade.process.pojo.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程附言
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/2/24
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "流程附言")
|
||||||
|
public class ProcessCommentVO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
@Schema(description = "id")
|
||||||
|
private String id;
|
||||||
|
/**
|
||||||
|
* 附言内容
|
||||||
|
*/
|
||||||
|
@Schema(description = "附言内容")
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createTime;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private Date updateTime;
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
@Schema(description = "用户id")
|
||||||
|
private String userId;
|
||||||
|
/**
|
||||||
|
* 用户名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "用户名称")
|
||||||
|
private String userName;
|
||||||
|
/**
|
||||||
|
* 附言附件
|
||||||
|
*/
|
||||||
|
@Schema(description = "附言附件")
|
||||||
|
private List<ProcessAttachmentVO> attachments;
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
package org.springblade.process.pojo.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/3/10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "流程待办")
|
||||||
|
public class ProcessTodoVO implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程实例id
|
||||||
|
*/
|
||||||
|
@Schema(description = "流程实例id")
|
||||||
|
private String processInstanceId;
|
||||||
|
/**
|
||||||
|
* 节点id
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点id")
|
||||||
|
private String nodeId;
|
||||||
|
/**
|
||||||
|
* 节点编号
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点编号")
|
||||||
|
private String nodeNumber;
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "节点名称")
|
||||||
|
private String nodeName;
|
||||||
|
/**
|
||||||
|
* mk登录名
|
||||||
|
*/
|
||||||
|
@Schema(description = "mk登录名")
|
||||||
|
private String loginName;
|
||||||
|
/**
|
||||||
|
* 用户姓名
|
||||||
|
*/
|
||||||
|
@Schema(description = "用户姓名")
|
||||||
|
private String userName;
|
||||||
|
/**
|
||||||
|
* 状态(todo:待办,done:已办,skip:身份重复跳过)
|
||||||
|
*/
|
||||||
|
@Schema(description = "状态(todo:待办,done:已办,skip:身份重复跳过)")
|
||||||
|
private String status;
|
||||||
|
/**
|
||||||
|
* 接收时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "接收时间")
|
||||||
|
private Date receiveTime;
|
||||||
|
/**
|
||||||
|
* 操作时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作时间")
|
||||||
|
private Date operationTime;
|
||||||
|
/**
|
||||||
|
* 操作名称
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作名称")
|
||||||
|
private String operationName;
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ package org.springblade.file;
|
|||||||
|
|
||||||
import org.springblade.core.cloud.client.BladeCloudApplication;
|
import org.springblade.core.cloud.client.BladeCloudApplication;
|
||||||
import org.springblade.core.launch.BladeApplication;
|
import org.springblade.core.launch.BladeApplication;
|
||||||
|
import org.springblade.core.launch.constant.AppConstant;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +40,9 @@ import org.springframework.context.annotation.ComponentScan;
|
|||||||
public class FileApplication {
|
public class FileApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
BladeApplication.run("blade-file", FileApplication.class, args);
|
BladeApplication.disableNacosLaunchConfig();
|
||||||
|
BladeApplication.run(AppConstant.APPLICATION_FILE_NAME, FileApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
server:
|
||||||
|
port: 8107
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: blade-file
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- nacos:blade.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:blade-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:third-party-api.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: ${NACOS_USERNAME:nacos}
|
||||||
|
password: ${NACOS_PASSWORD:nacos}
|
||||||
|
server-addr: ${NACOS_HOST:127.0.0.1:8848}
|
||||||
|
discovery:
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
|
datasource:
|
||||||
|
url: ${blade.datasource.${spring.profiles.active}.url}
|
||||||
|
username: ${blade.datasource.${spring.profiles.active}.username}
|
||||||
|
password: ${blade.datasource.${spring.profiles.active}.password}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#spring:
|
|
||||||
# cloud:
|
|
||||||
# nacos:
|
|
||||||
# username: nacos
|
|
||||||
# password: ${NACOS_PASSWORD:gr30wIs5%Hi7keQj}
|
|
||||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#spring:
|
|
||||||
# cloud:
|
|
||||||
# nacos:
|
|
||||||
# username: nacos
|
|
||||||
# password: rWrMrVTWyf%ekjuw
|
|
||||||
# server-addr: ${NACOS_ADDR:192.168.0.242:8848}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#server:
|
|
||||||
# port: 38107
|
|
||||||
#spring:
|
|
||||||
# cloud:
|
|
||||||
# nacos:
|
|
||||||
# username: nacos
|
|
||||||
# password: gr30wIs5%Hi7keQj
|
|
||||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
|
||||||
@@ -38,6 +38,20 @@
|
|||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-mk-api</artifactId>
|
<artifactId>blade-mk-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springblade</groupId>
|
||||||
|
<artifactId>blade-process-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springblade</groupId>
|
||||||
|
<artifactId>blade-core-launch</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mapstruct</groupId>
|
<groupId>org.mapstruct</groupId>
|
||||||
|
|||||||
+7
-4
@@ -25,24 +25,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.springblade.openapi;
|
package org.springblade.openapi;
|
||||||
|
|
||||||
import org.springblade.common.utils.ObsUtil;
|
|
||||||
|
import org.dromara.dynamictp.core.spring.EnableDynamicTp;
|
||||||
import org.springblade.core.cloud.client.BladeCloudApplication;
|
import org.springblade.core.cloud.client.BladeCloudApplication;
|
||||||
import org.springblade.core.launch.BladeApplication;
|
import org.springblade.core.launch.BladeApplication;
|
||||||
|
import org.springblade.core.launch.constant.AppConstant;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Desk启动器
|
* Desk启动器
|
||||||
*
|
*
|
||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
|
@EnableDynamicTp
|
||||||
@BladeCloudApplication
|
@BladeCloudApplication
|
||||||
@ComponentScan({"org.springblade.openapi", "org.springblade.**.feign"})
|
@ComponentScan({"org.springblade.openapi", "org.springblade.**.feign"})
|
||||||
@Import(ObsUtil.class)
|
|
||||||
public class OpenApiApplication {
|
public class OpenApiApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
BladeApplication.run("blade-openapi", OpenApiApplication.class, args);
|
BladeApplication.disableNacosLaunchConfig();
|
||||||
|
BladeApplication.run(AppConstant.APPLICATION_OPENAPI_NAME, OpenApiApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package org.springblade.openapi.mk;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.tool.api.FR;
|
||||||
|
import org.springblade.openapi.mk.api.IApi4MK;
|
||||||
|
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||||
|
import org.springblade.openapi.mk.pojo.enums.ProcessOperationType;
|
||||||
|
import org.springblade.openapi.mk.support.base.ProcessHandler;
|
||||||
|
import org.springblade.openapi.mk.util.ProcessTypeUtils;
|
||||||
|
import org.springblade.thirdparty.mk.config.MKProperties;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供给mk的api实现类
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/9
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Hidden
|
||||||
|
@RestController
|
||||||
|
public class Api4MK implements IApi4MK {
|
||||||
|
private final MKProperties mkProperties;
|
||||||
|
private final Map<String, ProcessHandler> handlerMap;
|
||||||
|
|
||||||
|
public Api4MK(MKProperties mkProperties, ObjectProvider<List<ProcessHandler>> handlersProvider) {
|
||||||
|
this.mkProperties = mkProperties;
|
||||||
|
handlerMap = handlersProvider.getIfAvailable(Collections::emptyList).stream()
|
||||||
|
.flatMap(handler -> handler.getProcessTypes().stream()
|
||||||
|
.collect(Collectors.toMap(Function.identity(), type -> handler, (a, b) -> {
|
||||||
|
throw new ServiceException("重复的流程类型处理器");
|
||||||
|
}))
|
||||||
|
.entrySet()
|
||||||
|
.stream())
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> {
|
||||||
|
throw new ServiceException("重复的流程类型处理器");
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<Boolean> processCommonCallback(Api4MKProcessApprovalDTO param) {
|
||||||
|
log.info("mk流程通用回调 操作名称:{} 参数:{}", ProcessOperationType.getOperationName(param.getOperation()), JSON.toJSONString(param));
|
||||||
|
callback(param, ProcessHandler::approve);
|
||||||
|
return FR.status(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<Boolean> processFinishCallback(Api4MKProcessApprovalDTO param) {
|
||||||
|
log.info("mk流程结束回调 参数:{}", JSON.toJSONString(param));
|
||||||
|
// 手动设置操作类型,兼容历史接口
|
||||||
|
param.setOperation(ProcessOperationType.PROCESS_FINISH);
|
||||||
|
callback(param, ProcessHandler::approve);
|
||||||
|
return FR.status(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取处理器
|
||||||
|
* @param processType
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ProcessHandler getHandler(String processType) {
|
||||||
|
if (StringUtils.isBlank(processType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return handlerMap.get(processType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调处理
|
||||||
|
* @param param
|
||||||
|
* @param consumer
|
||||||
|
*/
|
||||||
|
private void callback(Api4MKProcessApprovalDTO param, BiConsumer<ProcessHandler, Api4MKProcessApprovalDTO> consumer) {
|
||||||
|
String processType = ProcessTypeUtils.getProcessType(param.getTemplateCode(), mkProperties.getTemplateCodePrefix());
|
||||||
|
ProcessHandler handler = getHandler(processType);
|
||||||
|
if (handler != null) {
|
||||||
|
consumer.accept(handler, param);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.warn("未配置流程类型对应的处理器 流程类型:{}", processType);
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package org.springblade.openapi.mk.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理人刷新相关异步线程池配置。
|
||||||
|
*
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2026/4/9
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "async")
|
||||||
|
public class AsyncExecutorProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理人刷新工作线程池名称
|
||||||
|
*/
|
||||||
|
private String workerExecutorName = "mkRefreshWorkerExecutor";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理人刷新调度线程池名称
|
||||||
|
*/
|
||||||
|
private String schedulerExecutorName = "mkRefreshSchedulerExecutor";
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package org.springblade.openapi.mk.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2026/4/9
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "process.current-handler-refresh")
|
||||||
|
@Data
|
||||||
|
public class CurrentHandlerRefreshProperties {
|
||||||
|
/**
|
||||||
|
* 服务启动后的首次派工延迟,单位毫秒
|
||||||
|
*/
|
||||||
|
private long startupDispatchDelayMillis = 3000L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首次执行延迟,单位秒
|
||||||
|
*/
|
||||||
|
private long initialDelaySeconds = 1;
|
||||||
|
/**
|
||||||
|
* 轮询间隔,单位秒
|
||||||
|
*/
|
||||||
|
private long intervalSeconds = 1;
|
||||||
|
/**
|
||||||
|
* 最大重试次数
|
||||||
|
*/
|
||||||
|
private int maxAttempts = 30;
|
||||||
|
/**
|
||||||
|
* 最大worker数
|
||||||
|
*/
|
||||||
|
private int maxWorkers = 5;
|
||||||
|
/**
|
||||||
|
* worker租约秒数
|
||||||
|
*/
|
||||||
|
private long workerLeaseSeconds =15;
|
||||||
|
/**
|
||||||
|
* 锁等待秒数
|
||||||
|
*/
|
||||||
|
private long lockWaitSeconds = 1;
|
||||||
|
/**
|
||||||
|
* 完成任务TTL
|
||||||
|
*/
|
||||||
|
private long doneTtlMinutes = 5L;
|
||||||
|
/**
|
||||||
|
* 失败任务TTL
|
||||||
|
*/
|
||||||
|
private long failedTtlMinutes = 30L;
|
||||||
|
}
|
||||||
+301
@@ -0,0 +1,301 @@
|
|||||||
|
package org.springblade.openapi.mk.support.base;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.tool.api.FR;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||||
|
import org.springblade.openapi.mk.pojo.enums.ProcessCallbackType;
|
||||||
|
import org.springblade.openapi.mk.support.handler.ProcessCurrentHandlerRefreshService;
|
||||||
|
import org.springblade.openapi.mk.util.ProcessTypeUtils;
|
||||||
|
import org.springblade.process.feign.IBusinessProcessClient;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
|
||||||
|
import org.springblade.process.pojo.enums.ApproveStatusEnum;
|
||||||
|
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||||
|
import org.springblade.thirdparty.mk.config.MKProperties;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象流程操作处理器,实现公共逻辑
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/9
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractProcessOperationHandler implements ProcessHandler, ProcessOperationHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected IBusinessProcessClient processClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected ProcessCurrentHandlerRefreshService refreshService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected MKProperties mkProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getProcessTypes() {
|
||||||
|
return List.of(this.getProcessType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void approve(Api4MKProcessApprovalDTO param) {
|
||||||
|
// 入口层只接收 MK 原始回调参数,随后统一组装为内部上下文对象,
|
||||||
|
// 把流程类型、审批状态、是否完成、是否异步等内部处理语义集中收口在这里。
|
||||||
|
ProcessCallbackType callbackType = ProcessCallbackType.getCallbackType(param.getOperation());
|
||||||
|
if (callbackType == null) {
|
||||||
|
log.error("未配置事件的操作:{}", param.getOperation());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (callbackType) {
|
||||||
|
// 提交
|
||||||
|
case SUBMIT -> submit(buildSubmitContext(param));
|
||||||
|
// 审批结束
|
||||||
|
case FINISH -> approveFinish(buildFinishContext(param));
|
||||||
|
// 撤回
|
||||||
|
case RETRACT -> approveRevoke(buildRevokeContext(param));
|
||||||
|
// 通过
|
||||||
|
case PASS -> approvePass(buildPassContext(param));
|
||||||
|
// 驳回
|
||||||
|
case REJECT -> approveReject(buildRejectContext(param));
|
||||||
|
// 废弃
|
||||||
|
case ABANDON -> approveAbandon(buildAbandonContext(param));
|
||||||
|
// 修改当前处理人
|
||||||
|
case CHANGE_CUR_HANDLER -> handleCommon(buildChangeCurrentHandlerContext(param));
|
||||||
|
default -> log.error("未配置事件的操作:{}", param.getOperation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void approveFinish(ProcessOperationContext param) {
|
||||||
|
approveCommon(param, this::approveFinishBusiness);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void approvePass(ProcessOperationContext param) {
|
||||||
|
approveCommon(param, this::approvePassBusiness);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void approveReject(ProcessOperationContext param) {
|
||||||
|
approveCommon(param, this::approveRejectBusiness);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void approveRevoke(ProcessOperationContext param) {
|
||||||
|
approveCommon(param, this::approveRevokeBusiness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理提交
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void submit(ProcessOperationContext param) {
|
||||||
|
// 一般提交后只需要更新当前处理人
|
||||||
|
handleCommon(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理废弃
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void approveAbandon(ProcessOperationContext param) {
|
||||||
|
approveCommon(param, this::approveAbandonBusiness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理审批通用逻辑
|
||||||
|
* @param param
|
||||||
|
* @param businessHandler
|
||||||
|
*/
|
||||||
|
protected void approveCommon(ProcessOperationContext param, Consumer<ProcessOperationContext> businessHandler) {
|
||||||
|
// 1. 更新流程状态
|
||||||
|
updateBusinessProcessStatus(param);
|
||||||
|
// 2. 同步处理业务逻辑
|
||||||
|
businessHandler.accept(param);
|
||||||
|
// 3. 处理当前处理人刷新
|
||||||
|
handleCommon(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理公共异步逻辑
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
protected void handleCommon(ProcessOperationContext param) {
|
||||||
|
// 当前处理人支持按事件选择同步刷新或任务调度刷新
|
||||||
|
if (param.isAsync()) {
|
||||||
|
refreshService.enqueue(param);
|
||||||
|
} else {
|
||||||
|
refreshService.refreshNow(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新流程状态
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
private void updateBusinessProcessStatus(ProcessOperationContext param) {
|
||||||
|
// 更新流程状态
|
||||||
|
BusinessProcessUpdateDTO updateStatusParam = getBusinessProcessUpdateParam(param);
|
||||||
|
FR<String> statusResult = processClient.updateBusinessProcessStatus(updateStatusParam);
|
||||||
|
if (FR.isNotSuccess(statusResult)) {
|
||||||
|
log.error("更新流程状态异常 :{}", JSON.toJSONString(statusResult));
|
||||||
|
String errorMessage = Optional.ofNullable(statusResult)
|
||||||
|
.map(FR::getMsg)
|
||||||
|
.orElse("");
|
||||||
|
throw new ServiceException("更新流程状态异常:" + errorMessage);
|
||||||
|
}
|
||||||
|
// 具体审批状态要以更新 BusinessProcess 返回的为准,有些比如驳回到上一个审批节点(非起草节点)的,不需要更新状态
|
||||||
|
String approveStatus = statusResult.getData();
|
||||||
|
if (StringUtil.isBlank(approveStatus)) {
|
||||||
|
param.setApproveStatus(null);
|
||||||
|
} else {
|
||||||
|
param.setApproveStatus(approveStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流程更新参数
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private BusinessProcessUpdateDTO getBusinessProcessUpdateParam(ProcessOperationContext param) {
|
||||||
|
BusinessProcessUpdateDTO updateParam = new BusinessProcessUpdateDTO();
|
||||||
|
updateParam.setProcessInstanceId(param.getProcessInstanceId());
|
||||||
|
updateParam.setPromoterLoginName(param.getApplicantLoginName());
|
||||||
|
updateParam.setOperationNodeId(param.getCurrentNodeId());
|
||||||
|
updateParam.setOperationNodeNumber(param.getCurrentNodeNumber());
|
||||||
|
updateParam.setComplete(param.isComplete());
|
||||||
|
updateParam.setApproveStatus(param.getApproveStatus());
|
||||||
|
updateParam.setRejectNodeId(param.getRejectNodeId());
|
||||||
|
return updateParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造提交流程上下文。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildSubmitContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||||
|
return buildContext(callbackParam, ApproveStatusEnum.APPROVING.getValue(), false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造审批通过上下文。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildPassContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||||
|
return buildContext(callbackParam, ApproveStatusEnum.APPROVING.getValue(), false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造流程结束上下文。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildFinishContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||||
|
return buildContext(callbackParam, ApproveStatusEnum.APPROVED.getValue(), true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造驳回上下文。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildRejectContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||||
|
return buildContext(callbackParam, ApproveStatusEnum.REJECTED.getValue(), false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造撤回上下文。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildRevokeContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||||
|
return buildContext(callbackParam, ApproveStatusEnum.REVOCATION.getValue(), false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造废弃上下文。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildAbandonContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||||
|
return buildContext(callbackParam, ApproveStatusEnum.ABANDON.getValue(), false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造仅刷新当前处理人的上下文。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildChangeCurrentHandlerContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||||
|
return buildContext(callbackParam, null, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造流程内部处理上下文。
|
||||||
|
* 这里统一固化 processType,避免后续业务处理和异步刷新阶段再次根据模板编码反推。
|
||||||
|
*/
|
||||||
|
protected ProcessOperationContext buildContext(Api4MKProcessApprovalDTO callbackParam,
|
||||||
|
String approveStatus,
|
||||||
|
boolean complete,
|
||||||
|
boolean async) {
|
||||||
|
return ProcessOperationContext.builder()
|
||||||
|
.callbackParam(callbackParam)
|
||||||
|
.processType(ProcessTypeUtils.getProcessType(callbackParam.getTemplateCode(), mkProperties.getTemplateCodePrefix()))
|
||||||
|
.approveStatus(approveStatus)
|
||||||
|
.complete(complete)
|
||||||
|
.async(async)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流程类型
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected String getProcessType() {
|
||||||
|
throw new ServiceException("未配置流程类型");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理人刷新成功后回调各业务模块
|
||||||
|
* @param param 回调参数
|
||||||
|
* @param businessProcessVO 最新流程快照
|
||||||
|
*/
|
||||||
|
public void handleCurrentHandlerRefresh(ProcessOperationContext param, BusinessProcessVO businessProcessVO) {
|
||||||
|
if (businessProcessVO != null) {
|
||||||
|
this.commonBusiness(param, businessProcessVO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理公共业务逻辑
|
||||||
|
* @param param
|
||||||
|
* @param businessProcessVO
|
||||||
|
*/
|
||||||
|
protected abstract void commonBusiness(ProcessOperationContext param, BusinessProcessVO businessProcessVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理审批通过同步逻辑
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
protected abstract void approvePassBusiness(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理流程结束同步逻辑
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
protected abstract void approveFinishBusiness(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理审批驳回同步逻辑
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
protected abstract void approveRejectBusiness(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理审批撤回同步逻辑
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
protected abstract void approveRevokeBusiness(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理审批废弃同步逻辑 todo 为了避免代码报错,先用空实现
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
protected void approveAbandonBusiness(ProcessOperationContext param) {};
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package org.springblade.openapi.mk.support.base;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程处理器
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/9
|
||||||
|
*/
|
||||||
|
public interface ProcessHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流程类型列表
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<String> getProcessTypes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用审批
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void approve(Api4MKProcessApprovalDTO param);
|
||||||
|
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
package org.springblade.openapi.mk.support.base;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程回调内部处理上下文。
|
||||||
|
* <p>
|
||||||
|
* callbackParam 仅保留 MK 原始回调参数,
|
||||||
|
* 其余字段为 openapi 在处理过程中补充的上下文参数。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 设计目的:
|
||||||
|
* 1. 避免把内部推导字段继续堆到 MK 原始回调 DTO 上;
|
||||||
|
* 2. 对外保留原始回调对象,便于排查问题、记录日志和后续扩展;
|
||||||
|
* 3. 通过代理 getter 尽量兼容原来直接读取 DTO 字段的使用习惯,降低老流程和后续分支合并成本。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2026/4/9
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ProcessOperationContext implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MK 原始回调参数
|
||||||
|
*/
|
||||||
|
private Api4MKProcessApprovalDTO callbackParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程类型
|
||||||
|
*/
|
||||||
|
private String processType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务审批状态。
|
||||||
|
* 这是系统内部按事件语义统一补充的状态,不属于 MK 原始回调参数。
|
||||||
|
*/
|
||||||
|
private String approveStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否流程已完成。
|
||||||
|
* 这是系统内部按事件语义统一补充的状态,不属于 MK 原始回调参数。
|
||||||
|
*/
|
||||||
|
private boolean complete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否异步刷新当前处理人。
|
||||||
|
* 用于控制当前处理人更新是走同步刷新还是异步调度任务。
|
||||||
|
*/
|
||||||
|
private boolean async;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getProcessInstanceId() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getProcessInstanceId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getFormInstanceId() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getFormInstanceId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getTemplateId() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getTemplateId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getTemplateCode() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getTemplateCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getProcessStatus() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getProcessStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getApplicantLoginName() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getApplicantLoginName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getRejectNodeId() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getRejectNodeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getCurrentNodeId() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getCurrentNodeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getCurrentNodeNumber() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getCurrentNodeNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getOperation() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getOperationName() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getOperationName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getApprovalOpinion() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getApprovalOpinion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
public String getOperatorLoginName() {
|
||||||
|
return callbackParam == null ? null : callbackParam.getOperatorLoginName();
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package org.springblade.openapi.mk.support.base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程操作处理器
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2024/11/25
|
||||||
|
*/
|
||||||
|
public interface ProcessOperationHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void submit(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批结束
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void approveFinish(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批同意
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void approvePass(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批拒绝
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void approveReject(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批撤销
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void approveRevoke(ProcessOperationContext param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批废弃
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
void approveAbandon(ProcessOperationContext param);
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
package org.springblade.openapi.mk.support.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.dynamictp.core.DtpRegistry;
|
||||||
|
import org.dromara.dynamictp.core.aware.TaskEnhanceAware;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.openapi.mk.config.AsyncExecutorProperties;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/20
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class AsyncService {
|
||||||
|
private final AsyncExecutorProperties asyncExecutorProperties;
|
||||||
|
private Executor workerExecutor;
|
||||||
|
private ScheduledExecutorService schedulerExecutor;
|
||||||
|
|
||||||
|
public AsyncService(AsyncExecutorProperties asyncExecutorProperties) {
|
||||||
|
this.asyncExecutorProperties = asyncExecutorProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动完成后预加载并校验线程池配置,避免等到第一次真正执行任务时才发现线程池缺失或类型配置错误。
|
||||||
|
*/
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void initExecutors() {
|
||||||
|
this.workerExecutor = resolveWorkerExecutor();
|
||||||
|
this.schedulerExecutor = resolveSchedulerExecutor();
|
||||||
|
log.info("当前处理人刷新异步线程池初始化完成,workerExecutorName:{},schedulerExecutorName:{}",
|
||||||
|
asyncExecutorProperties.getWorkerExecutorName(), asyncExecutorProperties.getSchedulerExecutorName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立即异步执行
|
||||||
|
* @param runnable 任务
|
||||||
|
*/
|
||||||
|
public void execute(Runnable runnable) {
|
||||||
|
getWorkerExecutor().execute(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延迟执行指定毫秒数。
|
||||||
|
* <p>
|
||||||
|
* 这里改为使用 ScheduledDtpExecutor 做真正的定时调度,
|
||||||
|
* 避免再通过线程池线程 sleep 的方式占用工作线程,导致真正的业务任务迟迟无法启动。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param delayMillis 延迟毫秒数
|
||||||
|
* @param runnable 任务
|
||||||
|
*/
|
||||||
|
public void delayExecute(long delayMillis, Runnable runnable) {
|
||||||
|
if (delayMillis <= 0) {
|
||||||
|
execute(runnable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Runnable dispatchRunnable = wrapWithConfiguredTaskWrappers(
|
||||||
|
asyncExecutorProperties.getSchedulerExecutorName(),
|
||||||
|
() -> execute(runnable)
|
||||||
|
);
|
||||||
|
getSchedulerExecutor().schedule(dispatchRunnable, delayMillis, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Executor getWorkerExecutor() {
|
||||||
|
return workerExecutor != null ? workerExecutor : resolveWorkerExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledExecutorService getSchedulerExecutor() {
|
||||||
|
return schedulerExecutor != null ? schedulerExecutor : resolveSchedulerExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Executor resolveWorkerExecutor() {
|
||||||
|
return DtpRegistry.getExecutor(asyncExecutorProperties.getWorkerExecutorName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledExecutorService resolveSchedulerExecutor() {
|
||||||
|
String schedulerExecutorName = asyncExecutorProperties.getSchedulerExecutorName();
|
||||||
|
Executor executor = DtpRegistry.getExecutor(schedulerExecutorName);
|
||||||
|
if (executor instanceof ScheduledExecutorService scheduledExecutorService) {
|
||||||
|
return scheduledExecutorService;
|
||||||
|
}
|
||||||
|
String message = "线程池未按 ScheduledExecutorService 注册,name: " + schedulerExecutorName;
|
||||||
|
log.error(message);
|
||||||
|
throw new ServiceException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按线程池已配置的 task wrappers 手动包装任务。
|
||||||
|
* <p>
|
||||||
|
* 当前使用的 dynamic-tp 版本下,ScheduledDtpExecutor 对 taskWrapper 的透传存在缺口,
|
||||||
|
* 这里直接读取线程池上已生效的 wrappers,按框架默认增强链顺序主动包装一次,
|
||||||
|
* 这样既能复用现有配置,又避免手写 mdc 透传逻辑与框架实现产生偏差。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private Runnable wrapWithConfiguredTaskWrappers(String executorName, Runnable runnable) {
|
||||||
|
Executor executor = DtpRegistry.getExecutor(executorName);
|
||||||
|
if (executor instanceof TaskEnhanceAware taskEnhanceAware) {
|
||||||
|
return taskEnhanceAware.getEnhancedTask(runnable, taskEnhanceAware.getTaskWrappers());
|
||||||
|
}
|
||||||
|
return runnable;
|
||||||
|
}
|
||||||
|
}
|
||||||
+903
@@ -0,0 +1,903 @@
|
|||||||
|
package org.springblade.openapi.mk.support.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.redisson.api.RLock;
|
||||||
|
import org.redisson.api.RMapCache;
|
||||||
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
|
import org.springblade.core.redis.lock.RedisLockClient;
|
||||||
|
import org.springblade.core.tool.api.FR;
|
||||||
|
import org.springblade.openapi.mk.config.CurrentHandlerRefreshProperties;
|
||||||
|
import org.springblade.openapi.mk.constant.ProcessLockKeyConstant;
|
||||||
|
import org.springblade.openapi.mk.pojo.enums.ProcessCallbackType;
|
||||||
|
import org.springblade.openapi.mk.support.base.AbstractProcessOperationHandler;
|
||||||
|
import org.springblade.openapi.mk.support.base.ProcessOperationContext;
|
||||||
|
import org.springblade.process.feign.IBusinessProcessClient;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessCurrentHandlerRefreshDTO;
|
||||||
|
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理人刷新调度服务。
|
||||||
|
* <p>
|
||||||
|
* 背景:
|
||||||
|
* 流程引擎回调业务系统时,流程往往还没有真正流转到下一个激活节点,
|
||||||
|
* 此时立即查询当前节点/当前处理人,拿到的仍可能是上一节点的旧结果。
|
||||||
|
* 因此这里不再依赖一次性的固定延迟,而是改成“按流程实例维度入队 + 固定间隔轮询刷新”的调度模型。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 整体流程:
|
||||||
|
* 1. openapi 收到流程事件后,先同步更新业务流程状态;
|
||||||
|
* 2. 如果当前事件要求异步刷新当前处理人,则调用 {@link #enqueue(ProcessOperationContext)} 写入刷新任务;
|
||||||
|
* 3. 任务以流程实例 id 为唯一主记录保存在 Redis,记录期望版本、执行版本、基线快照、最近回调参数、重试次数等信息;
|
||||||
|
* 4. 同一个流程实例只保留一条主任务记录,新的回调不会重复创建任务,只会提升 {@code desiredVersion} 并覆盖最近一次回调参数;
|
||||||
|
* 5. 等待中的流程实例 id 会放入 Redis ZSet,score 为下次重试时间,用于按时间顺序派工;
|
||||||
|
* 6. 调度器 {@link #tryDispatch()} 会在集群范围内抢占派工锁,按配置的最大 worker 数拉起异步 worker;
|
||||||
|
* 7. worker 执行时调用 system 侧“只刷新当前节点/当前处理人”接口,并比较“当前节点 + 当前处理人”快照是否相对基线发生变化;
|
||||||
|
* 8. 如果快照未变化,说明流程大概率还没流转完成,则按固定间隔重新入队重试;
|
||||||
|
* 9. 如果快照发生变化,则回调对应业务处理器 {@link AbstractProcessOperationHandler#handleCurrentHandlerRefresh(ProcessOperationContext, BusinessProcessVO)};
|
||||||
|
* 10. 若执行期间又收到同一流程的新回调,则旧版本执行完后会把最新快照提升为新基线,并重新排到队尾,避免同一流程长期占用 worker;
|
||||||
|
* 11. 当达到最大重试次数后,任务进入失败态并保留一段时间,便于排查;
|
||||||
|
* 12. 成功完成的任务进入完成态并短期保留,随后自动过期。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 集群与并发约束:
|
||||||
|
* 1. 流程实例级别使用分布式锁,保证同一流程实例的任务状态变更串行化;
|
||||||
|
* 2. 派工使用全局分布式锁,保证多个实例不会同时超发 worker;
|
||||||
|
* 3. 活跃 worker 数通过 Redis 租约控制,服务异常中断后,租约超时即可视为 worker 失活;
|
||||||
|
* 4. 运行中的任务会持续更新心跳,如果服务升级、中断或线程异常退出,超时恢复逻辑会把任务重新转回等待态;
|
||||||
|
* 5. 启动时不会全量恢复运行中任务,避免在集群环境中误伤其他实例上仍在执行的任务。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 成功判定规则:
|
||||||
|
* 不再区分终态/非终态,也不依赖回调里传入的 complete true/false 单独判定是否成功,
|
||||||
|
* 统一以“当前节点变化 + 当前处理人变化后的最新快照”是否相对基线发生变化作为刷新成功依据。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2026/4/9
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class ProcessCurrentHandlerRefreshService {
|
||||||
|
|
||||||
|
|
||||||
|
private final AsyncService asyncService;
|
||||||
|
private final BladeRedis bladeRedis;
|
||||||
|
private final RedisLockClient redisLockClient;
|
||||||
|
private final IBusinessProcessClient processClient;
|
||||||
|
private final CurrentHandlerRefreshProperties refreshProperties;
|
||||||
|
private final ObjectProvider<List<AbstractProcessOperationHandler>> handlersProvider;
|
||||||
|
private final Map<String, AbstractProcessOperationHandler> handlerMap;
|
||||||
|
|
||||||
|
public ProcessCurrentHandlerRefreshService(AsyncService asyncService,
|
||||||
|
BladeRedis bladeRedis,
|
||||||
|
RedisLockClient redisLockClient,
|
||||||
|
IBusinessProcessClient processClient,
|
||||||
|
CurrentHandlerRefreshProperties refreshProperties,
|
||||||
|
ObjectProvider<List<AbstractProcessOperationHandler>> handlersProvider) {
|
||||||
|
this.asyncService = asyncService;
|
||||||
|
this.bladeRedis = bladeRedis;
|
||||||
|
this.redisLockClient = redisLockClient;
|
||||||
|
this.processClient = processClient;
|
||||||
|
this.refreshProperties = refreshProperties;
|
||||||
|
this.handlersProvider = handlersProvider;
|
||||||
|
this.handlerMap = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务启动后恢复未完成任务
|
||||||
|
*/
|
||||||
|
@Order
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void init() {
|
||||||
|
// 集群环境下不能在启动时无差别回收所有运行中任务,否则会误伤其他实例正在执行的任务
|
||||||
|
asyncService.delayExecute(refreshProperties.getStartupDispatchDelayMillis(), this::tryDispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按流程类型懒加载处理器,避免在bean初始化阶段提前拉起handler导致循环依赖
|
||||||
|
*/
|
||||||
|
private AbstractProcessOperationHandler getHandler(String processType) {
|
||||||
|
if (StringUtils.isBlank(processType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (handlerMap.isEmpty()) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (handlerMap.isEmpty()) {
|
||||||
|
List<AbstractProcessOperationHandler> handlers = handlersProvider.getIfAvailable(Collections::emptyList);
|
||||||
|
handlers.forEach(handler -> handler.getProcessTypes()
|
||||||
|
.forEach(type -> this.handlerMap.put(type, handler)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handlerMap.get(processType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入刷新任务
|
||||||
|
*
|
||||||
|
* @param param 回调参数
|
||||||
|
*/
|
||||||
|
public void enqueue(ProcessOperationContext param) {
|
||||||
|
if (param == null || StringUtils.isAnyBlank(param.getProcessType(), param.getProcessInstanceId())) {
|
||||||
|
log.warn("当前处理人刷新任务入队失败,上下文为空或流程类型/流程实例id为空,param:{}", JSON.toJSONString(param));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
String processInstanceId = param.getProcessInstanceId();
|
||||||
|
RLock lock = getProcessLock(processInstanceId);
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
log.warn("获取流程刷新任务锁失败,流程实例id:{}", processInstanceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProcessCurrentHandlerRefreshTask task = getTask(processInstanceId);
|
||||||
|
if (task == null) {
|
||||||
|
task = new ProcessCurrentHandlerRefreshTask();
|
||||||
|
task.setProcessInstanceId(processInstanceId);
|
||||||
|
task.setState(TaskState.STATE_WAITING);
|
||||||
|
}
|
||||||
|
task.setProcessType(param.getProcessType());
|
||||||
|
task.setContext(param);
|
||||||
|
task.setDesiredVersion(task.getDesiredVersion() + 1);
|
||||||
|
task.setLastCallbackAt(now);
|
||||||
|
if (!TaskState.STATE_RUNNING.equals(task.getState())) {
|
||||||
|
// 非运行中任务表示上一轮刷新周期已经结束或尚未开始。
|
||||||
|
// 这里必须按本次回调重新建立基线,避免撤回后再次提交时沿用上一轮旧快照,导致新一轮刷新永远无法命中成功条件。
|
||||||
|
task.setBaselineSnapshot(queryCurrentSnapshot(processInstanceId));
|
||||||
|
task.setLatestSnapshot(null);
|
||||||
|
task.setLastSuccessAt(null);
|
||||||
|
task.setState(TaskState.STATE_WAITING);
|
||||||
|
task.setAttemptCount(0);
|
||||||
|
task.setProcessingVersion(0);
|
||||||
|
task.setRunToken(null);
|
||||||
|
task.setStartedAt(null);
|
||||||
|
task.setHeartbeatAt(null);
|
||||||
|
task.setNextRetryAt(now + initialDelayMillis());
|
||||||
|
saveTask(task);
|
||||||
|
putWaitingTask(processInstanceId, task.getNextRetryAt());
|
||||||
|
log.info("当前处理人刷新任务入队,流程实例id:{},{}", processInstanceId, formatTaskLog(task));
|
||||||
|
scheduleDispatch(initialDelayMillis());
|
||||||
|
} else {
|
||||||
|
saveTask(task);
|
||||||
|
log.info("当前处理人刷新任务更新执行中版本,流程实例id:{},{}", processInstanceId, formatTaskLog(task));
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("当前处理人刷新任务入队被中断,流程实例id:{}", processInstanceId, e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("当前处理人刷新任务入队异常,流程实例id:{}", processInstanceId, e);
|
||||||
|
} finally {
|
||||||
|
unlock(lock);
|
||||||
|
}
|
||||||
|
tryDispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步立即刷新当前处理人
|
||||||
|
*
|
||||||
|
* @param param 回调参数
|
||||||
|
*/
|
||||||
|
public void refreshNow(ProcessOperationContext param) {
|
||||||
|
if (param == null || StringUtils.isAnyBlank(param.getProcessType(), param.getProcessInstanceId())) {
|
||||||
|
log.warn("同步刷新当前处理人失败,上下文为空或流程类型/流程实例id为空,param:{}", JSON.toJSONString(param));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FR<BusinessProcessVO> result = processClient.refreshBusinessProcessCurrentHandlers(buildUpdateParam(param));
|
||||||
|
if (result == null || FR.isNotSuccess(result)) {
|
||||||
|
log.warn("同步刷新当前处理人失败,转入异步队列重试,流程实例id:{} result:{}", param.getProcessInstanceId(), JSON.toJSONString(result));
|
||||||
|
enqueue(param);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AbstractProcessOperationHandler handler = getHandler(param.getProcessType());
|
||||||
|
if (handler == null) {
|
||||||
|
log.error("同步刷新当前处理人失败,未找到处理器,流程类型:{} 流程实例id:{}", param.getProcessType(), param.getProcessInstanceId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler.handleCurrentHandlerRefresh(param, result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 派发worker执行任务
|
||||||
|
*/
|
||||||
|
public void tryDispatch() {
|
||||||
|
int activeWorkerCount = getActiveWorkerCount();
|
||||||
|
if (activeWorkerCount >= refreshProperties.getMaxWorkers()) {
|
||||||
|
log.info("当前处理人刷新派工跳过,活跃worker已满,activeWorkers:{},maxWorkers:{}", activeWorkerCount, refreshProperties.getMaxWorkers());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RLock dispatchLock = getDispatchLock();
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = dispatchLock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 先恢复真正超时的运行中任务,再判断是否有可执行任务
|
||||||
|
recoverTimeoutTasks();
|
||||||
|
if (!hasWaitingTask()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activeWorkerCount = getActiveWorkerCount();
|
||||||
|
if (activeWorkerCount >= refreshProperties.getMaxWorkers()) {
|
||||||
|
log.info("当前处理人刷新派工二次检查跳过,活跃worker已满,activeWorkers:{},maxWorkers:{}", activeWorkerCount, refreshProperties.getMaxWorkers());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while ((activeWorkerCount = getActiveWorkerCount()) < refreshProperties.getMaxWorkers()) {
|
||||||
|
ProcessCurrentHandlerRefreshTask task = claimNextRunnableTaskUnderDispatchLock();
|
||||||
|
if (task == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String workerId = IdUtil.fastSimpleUUID();
|
||||||
|
refreshWorkerLease(workerId);
|
||||||
|
log.info("当前处理人刷新任务派工成功,workerId:{},activeWorkers:{},maxWorkers:{},流程实例id:{},{}",
|
||||||
|
workerId, activeWorkerCount, refreshProperties.getMaxWorkers(), task.getProcessInstanceId(), formatTaskLog(task));
|
||||||
|
// worker租约到期前再触发一次派工,用于兜底恢复异常中断任务
|
||||||
|
scheduleDispatch(workerLeaseMillis());
|
||||||
|
asyncService.execute(() -> workerLoop(workerId, task));
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("当前处理人刷新派工被中断", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("当前处理人刷新派工异常", e);
|
||||||
|
} finally {
|
||||||
|
unlock(dispatchLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* worker循环拉取任务,尽量复用已经占用的worker槽位
|
||||||
|
*
|
||||||
|
* @param workerId worker id
|
||||||
|
* @param firstTask 第一条任务
|
||||||
|
*/
|
||||||
|
private void workerLoop(String workerId, ProcessCurrentHandlerRefreshTask firstTask) {
|
||||||
|
try {
|
||||||
|
ProcessCurrentHandlerRefreshTask task = firstTask;
|
||||||
|
while (task != null) {
|
||||||
|
log.info("当前处理人刷新worker开始执行任务,workerId:{},流程实例id:{},{}", workerId, task.getProcessInstanceId(), formatTaskLog(task));
|
||||||
|
processTask(workerId, task);
|
||||||
|
refreshWorkerLease(workerId);
|
||||||
|
task = claimNextRunnableTask();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("当前处理人刷新worker执行异常,workerId:{}", workerId, e);
|
||||||
|
} finally {
|
||||||
|
removeWorkerLease(workerId);
|
||||||
|
log.info("当前处理人刷新worker结束,workerId:{}", workerId);
|
||||||
|
tryDispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行单条刷新任务
|
||||||
|
*
|
||||||
|
* @param workerId worker id
|
||||||
|
* @param task 任务
|
||||||
|
*/
|
||||||
|
private void processTask(String workerId, ProcessCurrentHandlerRefreshTask task) {
|
||||||
|
String processInstanceId = task.getProcessInstanceId();
|
||||||
|
String runToken = task.getRunToken();
|
||||||
|
if (!isTaskTokenMatched(processInstanceId, runToken)) {
|
||||||
|
log.info("当前处理人刷新任务执行前token已失效,workerId:{},流程实例id:{},runToken:{}", workerId, processInstanceId, runToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateHeartbeat(processInstanceId, runToken);
|
||||||
|
long startAt = System.currentTimeMillis();
|
||||||
|
log.info("当前处理人刷新任务开始查询,workerId:{},流程实例id:{},runToken:{},attemptCount:{},baselineSnapshot:{}",
|
||||||
|
workerId, processInstanceId, runToken, task.getAttemptCount(), task.getBaselineSnapshot());
|
||||||
|
FR<BusinessProcessVO> result = processClient.refreshBusinessProcessCurrentHandlers(buildUpdateParam(task.getContext()));
|
||||||
|
updateHeartbeat(processInstanceId, runToken);
|
||||||
|
if (!isTaskTokenMatched(processInstanceId, runToken)) {
|
||||||
|
log.info("当前处理人刷新任务查询后token已失效,workerId:{},流程实例id:{},runToken:{}", workerId, processInstanceId, runToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result == null || FR.isNotSuccess(result)) {
|
||||||
|
log.error("刷新当前处理人失败,流程实例id:{} result:{}", processInstanceId, JSON.toJSONString(result));
|
||||||
|
requeueAfterMiss(task, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BusinessProcessVO businessProcessVO = result.getData();
|
||||||
|
// 只有“提交”事件需要额外等待离开回调节点;
|
||||||
|
// 审批通过/会签等场景允许节点不变但处理人变化,不能套用同一条规则,否则会误判为一直未流转
|
||||||
|
if (shouldWaitForNextNode(task.getContext(), businessProcessVO)) {
|
||||||
|
log.info("当前处理人刷新任务命中等待下一节点条件,workerId:{},流程实例id:{},耗时:{}ms,latestSnapshot:{}",
|
||||||
|
workerId, processInstanceId, System.currentTimeMillis() - startAt, buildSnapshot(businessProcessVO));
|
||||||
|
requeueAfterMiss(task, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String latestSnapshot = buildSnapshot(businessProcessVO);
|
||||||
|
if (StringUtils.equals(latestSnapshot, task.getBaselineSnapshot())) {
|
||||||
|
log.info("当前处理人刷新任务快照未变化,workerId:{},流程实例id:{},耗时:{}ms,baselineSnapshot:{},latestSnapshot:{}",
|
||||||
|
workerId, processInstanceId, System.currentTimeMillis() - startAt, task.getBaselineSnapshot(), latestSnapshot);
|
||||||
|
requeueAfterMiss(task, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("当前处理人刷新任务命中成功条件,workerId:{},流程实例id:{},耗时:{}ms,baselineSnapshot:{},latestSnapshot:{}",
|
||||||
|
workerId, processInstanceId, System.currentTimeMillis() - startAt, task.getBaselineSnapshot(), latestSnapshot);
|
||||||
|
handleRefreshSuccess(task, businessProcessVO, latestSnapshot);
|
||||||
|
refreshWorkerLease(workerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理刷新成功
|
||||||
|
*
|
||||||
|
* @param task 任务
|
||||||
|
* @param businessProcessVO 最新流程快照
|
||||||
|
* @param latestSnapshot 最新快照
|
||||||
|
*/
|
||||||
|
private void handleRefreshSuccess(ProcessCurrentHandlerRefreshTask task, BusinessProcessVO businessProcessVO, String latestSnapshot) {
|
||||||
|
String processInstanceId = task.getProcessInstanceId();
|
||||||
|
String runToken = task.getRunToken();
|
||||||
|
AbstractProcessOperationHandler handler = getHandler(task.getProcessType());
|
||||||
|
if (handler == null) {
|
||||||
|
log.error("未找到当前处理人刷新处理器,流程类型:{} 流程实例id:{}", task.getProcessType(), processInstanceId);
|
||||||
|
requeueAfterMiss(task, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProcessOperationContext callbackParam = task.getContext();
|
||||||
|
try {
|
||||||
|
handler.handleCurrentHandlerRefresh(callbackParam, businessProcessVO);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新当前处理人后执行业务回调异常,流程实例id:{}", processInstanceId, e);
|
||||||
|
requeueAfterMiss(task, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RLock lock = getProcessLock(processInstanceId);
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
log.warn("刷新成功后回写任务失败,未获取到流程锁,流程实例id:{}", processInstanceId);
|
||||||
|
scheduleDispatch(intervalMillis());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||||
|
if (latestTask == null || !StringUtils.equals(runToken, latestTask.getRunToken())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
latestTask.setLatestSnapshot(latestSnapshot);
|
||||||
|
latestTask.setLastSuccessAt(now);
|
||||||
|
latestTask.setStartedAt(null);
|
||||||
|
latestTask.setHeartbeatAt(null);
|
||||||
|
latestTask.setRunToken(null);
|
||||||
|
if (latestTask.getDesiredVersion() > task.getProcessingVersion()) {
|
||||||
|
// 有新版本到来时,把最新快照提升为新基线,并重新排队到后面,避免一个流程长期占用worker
|
||||||
|
latestTask.setBaselineSnapshot(latestSnapshot);
|
||||||
|
latestTask.setAttemptCount(0);
|
||||||
|
latestTask.setState(TaskState.STATE_WAITING);
|
||||||
|
latestTask.setNextRetryAt(now + intervalMillis());
|
||||||
|
saveTask(latestTask);
|
||||||
|
putWaitingTask(processInstanceId, latestTask.getNextRetryAt());
|
||||||
|
log.info("当前处理人刷新任务成功后发现新版本,重新排队,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||||
|
scheduleDispatch(intervalMillis());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
latestTask.setState(TaskState.STATE_DONE);
|
||||||
|
saveTask(latestTask, Duration.ofMinutes(refreshProperties.getDoneTtlMinutes()));
|
||||||
|
removeWaitingTask(processInstanceId);
|
||||||
|
log.info("当前处理人刷新任务执行完成,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("刷新成功后回写任务被中断,流程实例id:{}", processInstanceId, e);
|
||||||
|
} finally {
|
||||||
|
unlock(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未命中最新快照时重新排队
|
||||||
|
*
|
||||||
|
* @param task 任务
|
||||||
|
* @param resetAttempt 是否重置重试次数
|
||||||
|
*/
|
||||||
|
private void requeueAfterMiss(ProcessCurrentHandlerRefreshTask task, boolean resetAttempt) {
|
||||||
|
String processInstanceId = task.getProcessInstanceId();
|
||||||
|
String runToken = task.getRunToken();
|
||||||
|
RLock lock = getProcessLock(processInstanceId);
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
log.warn("刷新任务重新排队失败,未获取到流程锁,流程实例id:{}", processInstanceId);
|
||||||
|
scheduleDispatch(intervalMillis());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||||
|
if (latestTask == null || !StringUtils.equals(runToken, latestTask.getRunToken())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
boolean hasNewVersion = latestTask.getDesiredVersion() > task.getProcessingVersion();
|
||||||
|
latestTask.setRunToken(null);
|
||||||
|
latestTask.setStartedAt(null);
|
||||||
|
latestTask.setHeartbeatAt(null);
|
||||||
|
latestTask.setState(TaskState.STATE_WAITING);
|
||||||
|
latestTask.setNextRetryAt(now + intervalMillis());
|
||||||
|
if (resetAttempt || hasNewVersion) {
|
||||||
|
latestTask.setAttemptCount(0);
|
||||||
|
} else {
|
||||||
|
latestTask.setAttemptCount(latestTask.getAttemptCount() + 1);
|
||||||
|
}
|
||||||
|
if (latestTask.getAttemptCount() >= refreshProperties.getMaxAttempts()) {
|
||||||
|
latestTask.setState(TaskState.STATE_FAILED);
|
||||||
|
saveTask(latestTask, Duration.ofMinutes(refreshProperties.getFailedTtlMinutes()));
|
||||||
|
removeWaitingTask(processInstanceId);
|
||||||
|
log.warn("当前处理人刷新任务达到最大重试次数,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveTask(latestTask);
|
||||||
|
putWaitingTask(processInstanceId, latestTask.getNextRetryAt());
|
||||||
|
log.info("当前处理人刷新任务重新排队,流程实例id:{},resetAttempt:{},hasNewVersion:{},{}",
|
||||||
|
processInstanceId, resetAttempt, hasNewVersion, formatTaskLog(latestTask));
|
||||||
|
scheduleDispatch(intervalMillis());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("刷新任务重新排队被中断,流程实例id:{}", processInstanceId, e);
|
||||||
|
} finally {
|
||||||
|
unlock(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* claim下一条可执行任务
|
||||||
|
*
|
||||||
|
* @return 任务,不存在时返回null
|
||||||
|
*/
|
||||||
|
private ProcessCurrentHandlerRefreshTask claimNextRunnableTask() {
|
||||||
|
RLock dispatchLock = getDispatchLock();
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = dispatchLock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
recoverTimeoutTasks();
|
||||||
|
return claimNextRunnableTaskUnderDispatchLock();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("claim当前处理人刷新任务被中断", e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
unlock(dispatchLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在已持有派工锁的前提下claim下一条可执行任务
|
||||||
|
*
|
||||||
|
* @return 任务,不存在时返回null
|
||||||
|
*/
|
||||||
|
private ProcessCurrentHandlerRefreshTask claimNextRunnableTaskUnderDispatchLock() {
|
||||||
|
Set<String> processIds = bladeRedis.getStringRedisTemplate().opsForZSet()
|
||||||
|
.rangeByScore(ProcessLockKeyConstant.WAITING_KEY, 0, System.currentTimeMillis(), 0, 1);
|
||||||
|
if (processIds == null || processIds.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String processInstanceId = processIds.iterator().next();
|
||||||
|
RLock processLock = getProcessLock(processInstanceId);
|
||||||
|
boolean processLocked = false;
|
||||||
|
try {
|
||||||
|
processLocked = processLock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!processLocked) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ProcessCurrentHandlerRefreshTask task = getTask(processInstanceId);
|
||||||
|
if (task == null) {
|
||||||
|
removeWaitingTask(processInstanceId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!TaskState.STATE_WAITING.equals(task.getState())) {
|
||||||
|
removeWaitingTask(processInstanceId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (task.getNextRetryAt() > System.currentTimeMillis()) {
|
||||||
|
putWaitingTask(processInstanceId, task.getNextRetryAt());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
task.setState(TaskState.STATE_RUNNING);
|
||||||
|
task.setProcessingVersion(task.getDesiredVersion());
|
||||||
|
task.setRunToken(IdUtil.fastSimpleUUID());
|
||||||
|
task.setStartedAt(System.currentTimeMillis());
|
||||||
|
task.setHeartbeatAt(task.getStartedAt());
|
||||||
|
saveTask(task);
|
||||||
|
removeWaitingTask(processInstanceId);
|
||||||
|
log.info("当前处理人刷新任务claim成功,流程实例id:{},{}", processInstanceId, formatTaskLog(task));
|
||||||
|
return task;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("claim当前处理人刷新任务被中断,流程实例id:{}", processInstanceId, e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (processLocked) {
|
||||||
|
unlock(processLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复超时的运行中任务
|
||||||
|
*/
|
||||||
|
private void recoverTimeoutTasks() {
|
||||||
|
Set<String> taskKeys = bladeRedis.getStringRedisTemplate().keys(ProcessLockKeyConstant.TASK_KEY_PREFIX + "*");
|
||||||
|
if (taskKeys == null || taskKeys.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
for (String taskKey : taskKeys) {
|
||||||
|
ProcessCurrentHandlerRefreshTask task = getTaskByKey(taskKey);
|
||||||
|
if (task == null || !TaskState.STATE_RUNNING.equals(task.getState())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Long heartbeatAt = task.getHeartbeatAt();
|
||||||
|
if (heartbeatAt != null && now - heartbeatAt <= workerLeaseMillis()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String processInstanceId = task.getProcessInstanceId();
|
||||||
|
RLock lock = getProcessLock(processInstanceId);
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||||
|
if (latestTask == null || !TaskState.STATE_RUNNING.equals(latestTask.getState())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Long latestHeartbeatAt = latestTask.getHeartbeatAt();
|
||||||
|
if (latestHeartbeatAt != null && now - latestHeartbeatAt <= workerLeaseMillis()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
latestTask.setState(TaskState.STATE_WAITING);
|
||||||
|
latestTask.setRunToken(null);
|
||||||
|
latestTask.setStartedAt(null);
|
||||||
|
latestTask.setHeartbeatAt(null);
|
||||||
|
latestTask.setNextRetryAt(now);
|
||||||
|
saveTask(latestTask);
|
||||||
|
putWaitingTask(processInstanceId, now);
|
||||||
|
log.warn("恢复超时的当前处理人刷新任务,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("恢复超时任务被中断,流程实例id:{}", processInstanceId, e);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
unlock(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询业务流程当前快照
|
||||||
|
*
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
* @return 快照字符串
|
||||||
|
*/
|
||||||
|
private String queryCurrentSnapshot(String processInstanceId) {
|
||||||
|
FR<BusinessProcessVO> result = processClient.queryBusinessProcessSnapshot(processInstanceId);
|
||||||
|
if (result == null || FR.isNotSuccess(result)) {
|
||||||
|
log.warn("查询业务流程当前快照失败,流程实例id:{} result:{}", processInstanceId, JSON.toJSONString(result));
|
||||||
|
return buildSnapshot(null);
|
||||||
|
}
|
||||||
|
return buildSnapshot(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造刷新请求参数
|
||||||
|
*
|
||||||
|
* @param callbackParam 回调参数
|
||||||
|
* @return 刷新参数
|
||||||
|
*/
|
||||||
|
private BusinessProcessCurrentHandlerRefreshDTO buildUpdateParam(ProcessOperationContext callbackParam) {
|
||||||
|
BusinessProcessCurrentHandlerRefreshDTO updateParam = new BusinessProcessCurrentHandlerRefreshDTO();
|
||||||
|
updateParam.setProcessInstanceId(callbackParam.getProcessInstanceId());
|
||||||
|
updateParam.setPromoterLoginName(callbackParam.getApplicantLoginName());
|
||||||
|
updateParam.setComplete(callbackParam.isComplete());
|
||||||
|
return updateParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造快照,统一用当前节点+当前处理人作为变更依据
|
||||||
|
*
|
||||||
|
* @param businessProcessVO 业务流程快照
|
||||||
|
* @return 快照字符串
|
||||||
|
*/
|
||||||
|
private String buildSnapshot(BusinessProcessVO businessProcessVO) {
|
||||||
|
if (businessProcessVO == null) {
|
||||||
|
return "|";
|
||||||
|
}
|
||||||
|
return normalizeCsv(businessProcessVO.getCurrentNodeIds()) + "|" + normalizeCsv(businessProcessVO.getCurrentHandlers());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交回调时,如果刷新后仍停留在本次回调节点,说明流程尚未真正流转到下一激活节点,需要继续等待。
|
||||||
|
* <p>
|
||||||
|
* 这里只针对提交事件生效,不能推广到审批通过/会签等场景:
|
||||||
|
* 会签节点在部分人审批完成后,当前节点可能仍然不变,但当前处理人已经发生变化,
|
||||||
|
* 此时应当允许按“快照变化”判定成功,而不是继续等待节点变化。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param callbackParam 回调参数
|
||||||
|
* @param businessProcessVO 最新流程快照
|
||||||
|
* @return 是否继续等待下一节点
|
||||||
|
*/
|
||||||
|
private boolean shouldWaitForNextNode(ProcessOperationContext callbackParam, BusinessProcessVO businessProcessVO) {
|
||||||
|
if (callbackParam == null || businessProcessVO == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ProcessCallbackType.SUBMIT != ProcessCallbackType.getCallbackType(callbackParam.getOperation())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String callbackNodeId = callbackParam.getCurrentNodeId();
|
||||||
|
if (StringUtils.isBlank(callbackNodeId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return containsCsvValue(businessProcessVO.getCurrentNodeIds(), callbackNodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一规范逗号拼接字段,避免比较时顺序影响结果
|
||||||
|
*
|
||||||
|
* @param value 原始值
|
||||||
|
* @return 规范化后的字符串
|
||||||
|
*/
|
||||||
|
private String normalizeCsv(String value) {
|
||||||
|
if (StringUtils.isBlank(value)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return Stream.of(value.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.distinct()
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断逗号分隔字段中是否包含指定值
|
||||||
|
*
|
||||||
|
* @param csv 逗号分隔字段
|
||||||
|
* @param target 目标值
|
||||||
|
* @return 是否包含
|
||||||
|
*/
|
||||||
|
private boolean containsCsvValue(String csv, String target) {
|
||||||
|
if (StringUtils.isAnyBlank(csv, target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Stream.of(csv.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.anyMatch(target::equals);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断任务token是否仍然有效
|
||||||
|
*
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
* @param runToken 运行token
|
||||||
|
* @return 是否匹配
|
||||||
|
*/
|
||||||
|
private boolean isTaskTokenMatched(String processInstanceId, String runToken) {
|
||||||
|
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||||
|
return latestTask != null
|
||||||
|
&& TaskState.STATE_RUNNING.equals(latestTask.getState())
|
||||||
|
&& StringUtils.equals(runToken, latestTask.getRunToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新任务心跳,表示当前worker仍然存活
|
||||||
|
*
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
* @param runToken 运行token
|
||||||
|
*/
|
||||||
|
private void updateHeartbeat(String processInstanceId, String runToken) {
|
||||||
|
RLock lock = getProcessLock(processInstanceId);
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProcessCurrentHandlerRefreshTask task = getTask(processInstanceId);
|
||||||
|
if (task == null || !StringUtils.equals(runToken, task.getRunToken())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
task.setHeartbeatAt(System.currentTimeMillis());
|
||||||
|
saveTask(task);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("更新刷新任务心跳被中断,流程实例id:{}", processInstanceId, e);
|
||||||
|
} finally {
|
||||||
|
unlock(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按默认方式保存任务
|
||||||
|
*
|
||||||
|
* @param task 任务
|
||||||
|
*/
|
||||||
|
private void saveTask(ProcessCurrentHandlerRefreshTask task) {
|
||||||
|
bladeRedis.getStringRedisTemplate().opsForValue().set(getTaskKey(task.getProcessInstanceId()), JSON.toJSONString(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按TTL保存任务
|
||||||
|
*
|
||||||
|
* @param task 任务
|
||||||
|
* @param ttl TTL
|
||||||
|
*/
|
||||||
|
private void saveTask(ProcessCurrentHandlerRefreshTask task, Duration ttl) {
|
||||||
|
bladeRedis.getStringRedisTemplate().opsForValue()
|
||||||
|
.set(getTaskKey(task.getProcessInstanceId()), JSON.toJSONString(task), ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务
|
||||||
|
*
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
* @return 任务
|
||||||
|
*/
|
||||||
|
private ProcessCurrentHandlerRefreshTask getTask(String processInstanceId) {
|
||||||
|
return getTaskByKey(getTaskKey(processInstanceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过key读取任务
|
||||||
|
*
|
||||||
|
* @param taskKey 任务key
|
||||||
|
* @return 任务
|
||||||
|
*/
|
||||||
|
private ProcessCurrentHandlerRefreshTask getTaskByKey(String taskKey) {
|
||||||
|
String content = bladeRedis.getStringRedisTemplate().opsForValue().get(taskKey);
|
||||||
|
if (StringUtils.isBlank(content)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parseObject(content, ProcessCurrentHandlerRefreshTask.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放入等待队列
|
||||||
|
*
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
* @param nextRetryAt 下次执行时间
|
||||||
|
*/
|
||||||
|
private void putWaitingTask(String processInstanceId, long nextRetryAt) {
|
||||||
|
bladeRedis.getStringRedisTemplate().opsForZSet().add(ProcessLockKeyConstant.WAITING_KEY, processInstanceId, nextRetryAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除等待队列中的任务
|
||||||
|
*
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
*/
|
||||||
|
private void removeWaitingTask(String processInstanceId) {
|
||||||
|
bladeRedis.getStringRedisTemplate().opsForZSet().remove(ProcessLockKeyConstant.WAITING_KEY, processInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否存在等待任务
|
||||||
|
*
|
||||||
|
* @return 是否存在
|
||||||
|
*/
|
||||||
|
private boolean hasWaitingTask() {
|
||||||
|
Long size = bladeRedis.getStringRedisTemplate().opsForZSet().zCard(ProcessLockKeyConstant.WAITING_KEY);
|
||||||
|
return size != null && size > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取worker租约map
|
||||||
|
*
|
||||||
|
* @return worker租约map
|
||||||
|
*/
|
||||||
|
private RMapCache<String, String> getWorkerLeaseMap() {
|
||||||
|
return redisLockClient.getRedissonClient().getMapCache(ProcessLockKeyConstant.WORKER_LEASE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前活跃worker数量
|
||||||
|
*
|
||||||
|
* @return 活跃worker数量
|
||||||
|
*/
|
||||||
|
private int getActiveWorkerCount() {
|
||||||
|
return getWorkerLeaseMap().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新worker租约
|
||||||
|
*
|
||||||
|
* @param workerId worker id
|
||||||
|
*/
|
||||||
|
private void refreshWorkerLease(String workerId) {
|
||||||
|
getWorkerLeaseMap().put(workerId, workerId, refreshProperties.getWorkerLeaseSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除worker租约
|
||||||
|
*
|
||||||
|
* @param workerId worker id
|
||||||
|
*/
|
||||||
|
private void removeWorkerLease(String workerId) {
|
||||||
|
getWorkerLeaseMap().remove(workerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安排稍后再次派工
|
||||||
|
*
|
||||||
|
* @param delayMillis 延迟毫秒数
|
||||||
|
*/
|
||||||
|
private void scheduleDispatch(long delayMillis) {
|
||||||
|
asyncService.delayExecute(delayMillis, this::tryDispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RLock getProcessLock(String processInstanceId) {
|
||||||
|
return redisLockClient.getRedissonClient().getLock(ProcessLockKeyConstant.PROCESS_LOCK_KEY_PREFIX + processInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RLock getDispatchLock() {
|
||||||
|
return redisLockClient.getRedissonClient().getLock(ProcessLockKeyConstant.DISPATCH_LOCK_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放锁
|
||||||
|
*
|
||||||
|
* @param lock
|
||||||
|
*/
|
||||||
|
private void unlock(RLock lock) {
|
||||||
|
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTaskKey(String processInstanceId) {
|
||||||
|
return ProcessLockKeyConstant.TASK_KEY_PREFIX + processInstanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long initialDelayMillis() {
|
||||||
|
return refreshProperties.getInitialDelaySeconds() * 1000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long intervalMillis() {
|
||||||
|
return refreshProperties.getIntervalSeconds() * 1000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long workerLeaseMillis() {
|
||||||
|
return refreshProperties.getWorkerLeaseSeconds() * 1000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatTaskLog(ProcessCurrentHandlerRefreshTask task) {
|
||||||
|
if (task == null) {
|
||||||
|
return "task=null";
|
||||||
|
}
|
||||||
|
return "state=" + task.getState()
|
||||||
|
+ ", desiredVersion=" + task.getDesiredVersion()
|
||||||
|
+ ", processingVersion=" + task.getProcessingVersion()
|
||||||
|
+ ", attemptCount=" + task.getAttemptCount()
|
||||||
|
+ ", nextRetryAt=" + task.getNextRetryAt()
|
||||||
|
+ ", startedAt=" + task.getStartedAt()
|
||||||
|
+ ", heartbeatAt=" + task.getHeartbeatAt()
|
||||||
|
+ ", lastCallbackAt=" + task.getLastCallbackAt()
|
||||||
|
+ ", lastSuccessAt=" + task.getLastSuccessAt();
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package org.springblade.openapi.mk.support.handler;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springblade.openapi.mk.support.base.ProcessOperationContext;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理人刷新任务
|
||||||
|
*
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2026/4/9
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ProcessCurrentHandlerRefreshTask implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程实例id
|
||||||
|
*/
|
||||||
|
private String processInstanceId;
|
||||||
|
/**
|
||||||
|
* 流程类型
|
||||||
|
*/
|
||||||
|
private String processType;
|
||||||
|
/**
|
||||||
|
* 任务状态
|
||||||
|
*/
|
||||||
|
private String state;
|
||||||
|
/**
|
||||||
|
* 期望处理版本
|
||||||
|
*/
|
||||||
|
private long desiredVersion;
|
||||||
|
/**
|
||||||
|
* 当前执行版本
|
||||||
|
*/
|
||||||
|
private long processingVersion;
|
||||||
|
/**
|
||||||
|
* 当前执行令牌
|
||||||
|
*/
|
||||||
|
private String runToken;
|
||||||
|
/**
|
||||||
|
* 当前基线快照
|
||||||
|
*/
|
||||||
|
private String baselineSnapshot;
|
||||||
|
/**
|
||||||
|
* 最近一次成功快照
|
||||||
|
*/
|
||||||
|
private String latestSnapshot;
|
||||||
|
/**
|
||||||
|
* 重试次数
|
||||||
|
*/
|
||||||
|
private int attemptCount;
|
||||||
|
/**
|
||||||
|
* 下次重试时间
|
||||||
|
*/
|
||||||
|
private long nextRetryAt;
|
||||||
|
/**
|
||||||
|
* 开始执行时间
|
||||||
|
*/
|
||||||
|
private Long startedAt;
|
||||||
|
/**
|
||||||
|
* 最近心跳时间
|
||||||
|
*/
|
||||||
|
private Long heartbeatAt;
|
||||||
|
/**
|
||||||
|
* 最近成功时间
|
||||||
|
*/
|
||||||
|
private Long lastSuccessAt;
|
||||||
|
/**
|
||||||
|
* 最近回调时间
|
||||||
|
*/
|
||||||
|
private Long lastCallbackAt;
|
||||||
|
/**
|
||||||
|
* 最近一次回调参数
|
||||||
|
*/
|
||||||
|
private ProcessOperationContext context;
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package org.springblade.openapi.mk.support.handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理人刷新任务状态常量类
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2026/4/9
|
||||||
|
*/
|
||||||
|
public class TaskState {
|
||||||
|
/**
|
||||||
|
* 等待执行
|
||||||
|
*/
|
||||||
|
public static final String STATE_WAITING = "WAITING";
|
||||||
|
/**
|
||||||
|
* 执行中
|
||||||
|
*/
|
||||||
|
public static final String STATE_RUNNING = "RUNNING";
|
||||||
|
/**
|
||||||
|
* 执行完成
|
||||||
|
*/
|
||||||
|
public static final String STATE_DONE = "DONE";
|
||||||
|
/**
|
||||||
|
* 执行失败
|
||||||
|
*/
|
||||||
|
public static final String STATE_FAILED = "FAILED";
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package org.springblade.openapi.mk.util;
|
||||||
|
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2026/7/12
|
||||||
|
*/
|
||||||
|
public class ProcessTypeUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流程类型
|
||||||
|
* @param templateCode
|
||||||
|
* @param templateCodePrefix
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getProcessType(String templateCode, String templateCodePrefix) {
|
||||||
|
if (StringUtil.isBlank(templateCode)) {
|
||||||
|
return templateCode;
|
||||||
|
}
|
||||||
|
return templateCode.replace(templateCodePrefix, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
server:
|
||||||
|
port: 8108
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: blade-openapi
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- nacos:blade.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:blade-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:third-party-api.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:blade-openapi-dynamictp.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: ${NACOS_USERNAME:nacos}
|
||||||
|
password: ${NACOS_PASSWORD:nacos}
|
||||||
|
server-addr: ${NACOS_HOST:127.0.0.1:8848}
|
||||||
|
discovery:
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
|
datasource:
|
||||||
|
url: ${blade.datasource.${spring.profiles.active}.url}
|
||||||
|
username: ${blade.datasource.${spring.profiles.active}.username}
|
||||||
|
password: ${blade.datasource.${spring.profiles.active}.password}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#spring:
|
|
||||||
# cloud:
|
|
||||||
# nacos:
|
|
||||||
# username: nacos
|
|
||||||
# password: ${NACOS_PASSWORD:gr30wIs5%Hi7keQj}
|
|
||||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#spring:
|
|
||||||
# cloud:
|
|
||||||
# nacos:
|
|
||||||
# username: nacos
|
|
||||||
# password: rWrMrVTWyf%ekjuw
|
|
||||||
# server-addr: ${NACOS_ADDR:192.168.0.242:8848}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#server:
|
|
||||||
# port: 38108
|
|
||||||
#spring:
|
|
||||||
# cloud:
|
|
||||||
# nacos:
|
|
||||||
# username: nacos
|
|
||||||
# password: gr30wIs5%Hi7keQj
|
|
||||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
|
||||||
@@ -45,6 +45,10 @@
|
|||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-user-api</artifactId>
|
<artifactId>blade-user-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springblade</groupId>
|
||||||
|
<artifactId>blade-process-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
@@ -72,8 +76,16 @@
|
|||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-core-oauth2</artifactId>
|
<artifactId>blade-core-oauth2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springblade</groupId>
|
||||||
|
<artifactId>blade-core-launch</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
package org.springblade.process.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.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Condition;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.process.pojo.dto.ApprovalDTO;
|
||||||
|
import org.springblade.process.pojo.vo.ApprovalVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||||
|
import org.springblade.process.service.IBusinessProcessService;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 控制器
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
@Valid
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping("businessProcess")
|
||||||
|
@Tag(name = "业务流程关联表", description = "业务流程关联表接口")
|
||||||
|
public class BusinessProcessController extends BladeController {
|
||||||
|
|
||||||
|
private final IBusinessProcessService businessProcessService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 分页
|
||||||
|
*/
|
||||||
|
@PostMapping("/mkList")
|
||||||
|
@ApiOperationSupport(order = 1)
|
||||||
|
@Operation(summary = "分页", description = "传入businessProcess")
|
||||||
|
public R<IPage<ApprovalVO>> mkList(@Validated @RequestBody(required = false) ApprovalDTO param, Query query) {
|
||||||
|
if (param == null) {
|
||||||
|
param = new ApprovalDTO();
|
||||||
|
}
|
||||||
|
param.setLoginName(AuthUtil.getUserAccount());
|
||||||
|
IPage<ApprovalVO> pages = businessProcessService.queryMkApprovalList(Condition.getPage(query), param);
|
||||||
|
return R.data(pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/isEditView")
|
||||||
|
@ApiOperationSupport(order = 2)
|
||||||
|
@Operation(summary = "是否编辑页", description = "传入业务id")
|
||||||
|
public R<Boolean> isEditView(@Valid @NotBlank(message = "业务id不能为空") String bizId) {
|
||||||
|
return R.data(businessProcessService.isEditView(bizId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getMKApprovalUrl")
|
||||||
|
@ApiOperationSupport(order = 3)
|
||||||
|
@Operation(summary = "获取mk审批页链接", description = "传入业务id或流程实例id")
|
||||||
|
public R<String> getMKApprovalUrl(String bizId, String processInstanceId) {
|
||||||
|
return R.data(businessProcessService.getMKApprovalUrl(bizId, processInstanceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getApprovedRecords")
|
||||||
|
@ApiOperationSupport(order = 4)
|
||||||
|
@Operation(summary = "查询审批记录", description = "传入业务id或流程实例id")
|
||||||
|
public R<List<ProcessApprovedRecordVO>> getApprovedRecords(String bizId, String processInstanceId) {
|
||||||
|
return R.data(businessProcessService.queryApprovedRecords(bizId, processInstanceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/downloadFile")
|
||||||
|
@ApiOperationSupport(order = 5)
|
||||||
|
@Operation(summary = "下载附件", description = "传入附件id")
|
||||||
|
public void downloadFile(HttpServletResponse response, @Valid @NotBlank(message = "附件id不能为空") String fileId) {
|
||||||
|
businessProcessService.downloadFile(response, fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package org.springblade.process.convert;
|
||||||
|
|
||||||
|
import org.mapstruct.*;
|
||||||
|
import org.springblade.common.constant.DictTypeEnum;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springblade.process.pojo.dto.ApprovalDTO;
|
||||||
|
import org.springblade.process.pojo.vo.ApprovalVO;
|
||||||
|
import org.springblade.system.cache.DictCache;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.vo.MKApprovalVO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.vo.MKProcessVO;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/4/3
|
||||||
|
*/
|
||||||
|
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||||
|
public interface ApprovalConvert {
|
||||||
|
|
||||||
|
@Mapping(source = "dynamicProps.templateNameCn", target = "templateNameCn")
|
||||||
|
@Mapping(source = "status", target = "statusStr", qualifiedByName = "statusStr")
|
||||||
|
ApprovalVO mk2vo(MKApprovalVO vo);
|
||||||
|
|
||||||
|
List<ApprovalVO> mk2vos(List<MKApprovalVO> vos);
|
||||||
|
|
||||||
|
@Mapping(source = "creator", target = "applicantName")
|
||||||
|
@Mapping(source = "currentNode", target = "nodeName")
|
||||||
|
@Mapping(source = "templateName", target = "templateNameCn")
|
||||||
|
@Mapping(source = "createTime", target = "startTime")
|
||||||
|
ApprovalVO mk2vo(MKProcessVO vo);
|
||||||
|
|
||||||
|
List<ApprovalVO> mkProcess2vos(List<MKProcessVO> vos);
|
||||||
|
|
||||||
|
@Mapping(source = "docType", target = "mydoc")
|
||||||
|
@Mapping(source = "applicantTimeStart", target = "createBeginTime", qualifiedByName = "date2long")
|
||||||
|
@Mapping(source = "applicantTimeEnd", target = "createEndTime", qualifiedByName = "date2long")
|
||||||
|
MKProcessDTO dto2mk(ApprovalDTO dto);
|
||||||
|
|
||||||
|
@Named("statusStr")
|
||||||
|
default String statusStr(String status) {
|
||||||
|
return StringUtil.isBlank(status) ? "" : DictCache.getValue(DictTypeEnum.MK_STATUS.getType(), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Named("date2long")
|
||||||
|
default Long date2long(Date date) {
|
||||||
|
return date == null ? null : date.getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package org.springblade.process.convert;
|
||||||
|
|
||||||
|
import org.mapstruct.*;
|
||||||
|
import org.springblade.common.constant.DictTypeEnum;
|
||||||
|
import org.springblade.core.tool.utils.CollectionUtil;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springblade.process.pojo.dto.AdditionOperationParameterDTO;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
|
||||||
|
import org.springblade.process.pojo.dto.ProcessExecuteDTO;
|
||||||
|
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||||
|
import org.springblade.process.pojo.vo.BusinessProcessListVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessAttachmentVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessCommentVO;
|
||||||
|
import org.springblade.system.cache.DictCache;
|
||||||
|
import org.springblade.thirdparty.mk.constant.MKConstant;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.MKAdditionOperationParameterDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.MKProcessExecuteDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.vo.MKAttachmentVO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.vo.MKAuditNoteVO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.vo.MKProcessCommentVO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.vo.MKUserOrgVO;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @date 2024/9/19
|
||||||
|
*/
|
||||||
|
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||||
|
public interface BusinessProcessConvert {
|
||||||
|
|
||||||
|
BusinessProcess dto2entity(BusinessProcessSubmitDTO<?> dto);
|
||||||
|
|
||||||
|
MKProcessExecuteDTO dto2mk(ProcessExecuteDTO dto);
|
||||||
|
|
||||||
|
MKAdditionOperationParameterDTO dto2mk(AdditionOperationParameterDTO dto);
|
||||||
|
|
||||||
|
ProcessApprovedRecordVO mk2vo(MKAuditNoteVO vo);
|
||||||
|
|
||||||
|
ProcessAttachmentVO mk2vo(MKAttachmentVO vo);
|
||||||
|
|
||||||
|
List<ProcessAttachmentVO> attachments2vos(List<MKAttachmentVO> vos);
|
||||||
|
|
||||||
|
@Mapping(source = "userOrgInfo", target = "userName", qualifiedByName = "userName")
|
||||||
|
ProcessCommentVO mk2vo(MKProcessCommentVO vo);
|
||||||
|
|
||||||
|
List<ProcessCommentVO> comments2vos(List<MKProcessCommentVO> vos);
|
||||||
|
|
||||||
|
default List<ProcessApprovedRecordVO> auditNotes2vos(List<MKAuditNoteVO> vos, Function<ProcessApprovedRecordVO, List<String>> senderFunction) {
|
||||||
|
if (CollectionUtil.isEmpty(vos)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return vos.stream()
|
||||||
|
.map(auditNote -> {
|
||||||
|
ProcessApprovedRecordVO record = this.mk2vo(auditNote);
|
||||||
|
if (record != null && MKConstant.NODE_TYPE_SEND.equals(record.getNodeType())) {
|
||||||
|
// 抄送节点,查询抄送人员
|
||||||
|
record.setSenders(senderFunction.apply(record));
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
default void handleDict(BusinessProcessListVO vo) {
|
||||||
|
if (vo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String processTypeStr = StringUtil.isBlank(vo.getProcessType()) ? "" : DictCache.getValue(DictTypeEnum.PROCESS_TYPE.getType(), vo.getProcessType());
|
||||||
|
vo.setProcessTypeStr(processTypeStr);
|
||||||
|
String approveStatusStr = StringUtil.isBlank(vo.getApproveStatus()) ? "" : DictCache.getValue(DictTypeEnum.APPROVE_STATUS.getType(), vo.getApproveStatus());
|
||||||
|
vo.setApproveStatusStr(approveStatusStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Named("userName")
|
||||||
|
default String userName(MKUserOrgVO userOrgInfo) {
|
||||||
|
return Optional.ofNullable(userOrgInfo)
|
||||||
|
.map(MKUserOrgVO::getName)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
package org.springblade.process.convert;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springblade.process.pojo.dto.ApprovalDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalConditionDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO.MKConditionDTOBuilder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bfhuange
|
||||||
|
* @since 2025/4/3
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum MKApprovalConvert {
|
||||||
|
/**
|
||||||
|
* 单据类型
|
||||||
|
*/
|
||||||
|
DOC_TYPE(MKApprovalConditionDTO::setMydoc, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getDocType)),
|
||||||
|
/**
|
||||||
|
* 关键字
|
||||||
|
*/
|
||||||
|
KEYWORD((condition, keyword) -> {
|
||||||
|
if (condition.getKeyword() == null) {
|
||||||
|
condition.setKeyword(new ArrayList<>());
|
||||||
|
}
|
||||||
|
condition.getKeyword().add(keyword);
|
||||||
|
}, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getKeyword)),
|
||||||
|
/**
|
||||||
|
* 模板名称
|
||||||
|
*/
|
||||||
|
TEMPLATE_NAME(MKApprovalConditionDTO::setTemplateName, compose(MKConditionDTOBuilder::contains, ApprovalDTO::getTemplateName)),
|
||||||
|
/**
|
||||||
|
* 发起时间
|
||||||
|
*/
|
||||||
|
START_TIME(MKApprovalConditionDTO::setStartTime,
|
||||||
|
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getApplicantTimeStart)),
|
||||||
|
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getApplicantTimeEnd))
|
||||||
|
),
|
||||||
|
/**
|
||||||
|
* 接收时间
|
||||||
|
*/
|
||||||
|
RECEIVE_TIME(MKApprovalConditionDTO::setReceiveTime,
|
||||||
|
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getReceiveTimeStart)),
|
||||||
|
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getReceiveTimeEnd))
|
||||||
|
),
|
||||||
|
/**
|
||||||
|
* 流程状态
|
||||||
|
*/
|
||||||
|
STATUS(MKApprovalConditionDTO::setStatus, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getStatus)),
|
||||||
|
/**
|
||||||
|
* 结束时间
|
||||||
|
*/
|
||||||
|
FINISH_TIME(MKApprovalConditionDTO::setFinishTime,
|
||||||
|
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getFinishTimeStart)),
|
||||||
|
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getFinishTimeEnd))
|
||||||
|
),
|
||||||
|
/**
|
||||||
|
* 最后处理时间
|
||||||
|
*/
|
||||||
|
LAST_HANDLE_TIME(MKApprovalConditionDTO::setLastHandleTime,
|
||||||
|
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getLastHandleStart)),
|
||||||
|
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getLastHandleEnd))
|
||||||
|
),
|
||||||
|
/**
|
||||||
|
* 阅读时间
|
||||||
|
*/
|
||||||
|
READ_TIME(MKApprovalConditionDTO::setReadTime,
|
||||||
|
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getReadTimeStart)),
|
||||||
|
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getReadTimeEnd))
|
||||||
|
),
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CREATE_TIME(MKApprovalConditionDTO::setCreateTime,
|
||||||
|
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getCreateTimeStart)),
|
||||||
|
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getCreateTimeEnd))
|
||||||
|
),
|
||||||
|
;
|
||||||
|
/**
|
||||||
|
* 最终设置条件方法
|
||||||
|
*/
|
||||||
|
private final BiConsumer<MKApprovalConditionDTO, MKConditionDTO> setter;
|
||||||
|
/**
|
||||||
|
* 组合参数
|
||||||
|
*/
|
||||||
|
private final List<Compose> composes;
|
||||||
|
|
||||||
|
MKApprovalConvert(BiConsumer<MKApprovalConditionDTO, MKConditionDTO> setter, Compose... compose) {
|
||||||
|
this.setter = setter;
|
||||||
|
this.composes = List.of(compose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取时间戳
|
||||||
|
* @param date
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getTimestamp(Date date) {
|
||||||
|
return Optional.ofNullable(date)
|
||||||
|
.map(e -> String.valueOf(e.getTime()))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* date 转 string
|
||||||
|
* @param dateGetter
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Function<ApprovalDTO, String> getGetter(Function<ApprovalDTO, Date> dateGetter) {
|
||||||
|
return approvalDTO -> getTimestamp(dateGetter.apply(approvalDTO));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工厂方法
|
||||||
|
* @param builderSetter
|
||||||
|
* @param getter
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Compose compose(BiConsumer<MKConditionDTOBuilder, String> builderSetter, Function<ApprovalDTO, String> getter) {
|
||||||
|
return new Compose(builderSetter, getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组合参数,条件和取值
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public static class Compose {
|
||||||
|
/**
|
||||||
|
* 条件builder的setter
|
||||||
|
*/
|
||||||
|
private BiConsumer<MKConditionDTOBuilder, String> builderSetter;
|
||||||
|
/**
|
||||||
|
* 从参数取值
|
||||||
|
*/
|
||||||
|
private Function<ApprovalDTO, String> getter;
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package org.springblade.process.feign;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.tool.api.FR;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessCurrentHandlerRefreshDTO;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
|
||||||
|
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
|
||||||
|
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||||
|
import org.springblade.process.pojo.vo.ProcessTodoVO;
|
||||||
|
import org.springblade.process.service.IBusinessProcessService;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 Feign实现类
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
@Valid
|
||||||
|
@Hidden
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class BusinessProcessClient implements IBusinessProcessClient {
|
||||||
|
|
||||||
|
private final IBusinessProcessService businessProcessService;
|
||||||
|
|
||||||
|
@PostMapping(SUBMIT_BUSINESS_PROCESS)
|
||||||
|
@Override
|
||||||
|
public FR<BusinessProcessVO> submitBusinessProcess(@Validated @RequestBody BusinessProcessSubmitDTO<?> param) {
|
||||||
|
return FR.data(businessProcessService.submitBusinessProcess(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<String> updateBusinessProcessStatus(@Validated @RequestBody BusinessProcessUpdateDTO param) {
|
||||||
|
return FR.data(businessProcessService.updateBusinessProcessStatus(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<BusinessProcessVO> updateBusinessProcessApprover(@Validated @RequestBody BusinessProcessUpdateDTO param) {
|
||||||
|
return FR.data(businessProcessService.updateBusinessProcessApprover(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<BusinessProcessVO> refreshBusinessProcessCurrentHandlers(@Validated @RequestBody BusinessProcessCurrentHandlerRefreshDTO param) {
|
||||||
|
return FR.data(businessProcessService.refreshBusinessProcessCurrentHandlers(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<List<ProcessTodoVO>> queryTodoList(String processInstanceId) {
|
||||||
|
return FR.data(businessProcessService.queryTodoList(processInstanceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<BusinessProcessVO> queryBusinessProcessSnapshot(String processInstanceId) {
|
||||||
|
return FR.data(businessProcessService.queryBusinessProcessSnapshot(processInstanceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(DELETE_BUSINESS_PROCESS)
|
||||||
|
@Override
|
||||||
|
public FR<Boolean> deleteBusinessProcess(@Validated @RequestBody BusinessProcessDeleteDTO param) {
|
||||||
|
return FR.data(businessProcessService.deleteBusinessProcess(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FR<List<ProcessApprovedRecordVO>> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
|
||||||
|
return FR.data(businessProcessService.queryApprovedRecordsNoAttachments(bizId, processInstanceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package org.springblade.process.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 Mapper 接口
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
public interface BusinessProcessMapper extends BaseMapper<BusinessProcess> {
|
||||||
|
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
<?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.process.mapper.BusinessProcessMapper">
|
||||||
|
|
||||||
|
<!-- 通用查询映射结果 -->
|
||||||
|
<resultMap id="businessProcessResultMap" type="org.springblade.process.pojo.entity.BusinessProcess">
|
||||||
|
<result column="id" property="id"/>
|
||||||
|
<result column="biz_id" property="bizId"/>
|
||||||
|
<result column="process_instance_id" property="processInstanceId"/>
|
||||||
|
<result column="process_type" property="processType"/>
|
||||||
|
<result column="doc_code" property="docCode"/>
|
||||||
|
<result column="subject" property="subject"/>
|
||||||
|
<result column="promoter_id" property="promoterId"/>
|
||||||
|
<result column="promoter_name" property="promoterName"/>
|
||||||
|
<result column="promoter_login_name" property="promoterLoginName"/>
|
||||||
|
<result column="submit_time" property="submitTime"/>
|
||||||
|
<result column="complete_time" property="completeTime"/>
|
||||||
|
<result column="current_node_ids" property="currentNodeIds"/>
|
||||||
|
<result column="current_node_names" property="currentNodeNames"/>
|
||||||
|
<result column="current_handlers" property="currentHandlers"/>
|
||||||
|
<result column="receive_time" property="receiveTime"/>
|
||||||
|
<result column="is_completed" property="isCompleted"/>
|
||||||
|
<result column="approve_status" property="approveStatus"/>
|
||||||
|
<result column="tenant_id" property="tenantId"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
package org.springblade.process.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springblade.process.pojo.dto.*;
|
||||||
|
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||||
|
import org.springblade.process.pojo.vo.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 服务类
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
public interface IBusinessProcessService extends IService<BusinessProcess> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交业务流程
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
BusinessProcessVO submitBusinessProcess(BusinessProcessSubmitDTO<?> param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改业务流程状态
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String updateBusinessProcessStatus(BusinessProcessUpdateDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改业务流程审批人
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
BusinessProcessVO updateBusinessProcessApprover(BusinessProcessUpdateDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只刷新当前节点和当前处理人
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
BusinessProcessVO refreshBusinessProcessCurrentHandlers(BusinessProcessCurrentHandlerRefreshDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询业务流程当前快照
|
||||||
|
* @param processInstanceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
BusinessProcessVO queryBusinessProcessSnapshot(String processInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否编辑页
|
||||||
|
* @param bizId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean isEditView(String bizId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取mk审批页面链接,业务id和流程实例id任意一个即可
|
||||||
|
* @param bizId 业务id
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getMKApprovalUrl(String bizId, String processInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除业务流程
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean deleteBusinessProcess(BusinessProcessDeleteDTO param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询流程审批记录
|
||||||
|
* @param bizId
|
||||||
|
* @param processInstanceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<ProcessApprovedRecordVO> queryApprovedRecords(String bizId, String processInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询流程审批记录不处理附件
|
||||||
|
* @param bizId
|
||||||
|
* @param processInstanceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<ProcessApprovedRecordVO> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
* @param response
|
||||||
|
* @param fileId
|
||||||
|
*/
|
||||||
|
void downloadFile(HttpServletResponse response, String fileId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询业务流程当前处理人
|
||||||
|
* @param processInstanceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<ProcessTodoVO> queryTodoList(String processInstanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询mk审批记录
|
||||||
|
* @param page
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IPage<ApprovalVO> queryMkApprovalList(IPage<ApprovalVO> page, ApprovalDTO param);
|
||||||
|
}
|
||||||
+726
@@ -0,0 +1,726 @@
|
|||||||
|
package org.springblade.process.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springblade.process.pojo.dto.*;
|
||||||
|
import org.springblade.process.pojo.enums.ApproveStatusEnum;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.log.utils.AssertUtils;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springblade.process.convert.ApprovalConvert;
|
||||||
|
import org.springblade.process.convert.BusinessProcessConvert;
|
||||||
|
import org.springblade.process.convert.MKApprovalConvert;
|
||||||
|
import org.springblade.process.mapper.BusinessProcessMapper;
|
||||||
|
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||||
|
import org.springblade.process.pojo.enums.TodoStatus;
|
||||||
|
import org.springblade.process.pojo.vo.*;
|
||||||
|
import org.springblade.process.service.IBusinessProcessService;
|
||||||
|
import org.springblade.thirdparty.mk.config.MKProperties;
|
||||||
|
import org.springblade.thirdparty.mk.constant.MKConstant;
|
||||||
|
import org.springblade.thirdparty.mk.constant.MKDoc;
|
||||||
|
import org.springblade.thirdparty.mk.exception.MKException;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.MKAuditNoteDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.MKProcessCreateDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.MKProcessExecuteDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.MKSenderDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalConditionDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.dto.sort.*;
|
||||||
|
import org.springblade.thirdparty.mk.pojo.vo.*;
|
||||||
|
import org.springblade.thirdparty.mk.service.IMKService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务流程关联表 服务实现类
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
* @since 2024-09-19
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class BusinessProcessServiceImpl extends ServiceImpl<BusinessProcessMapper, BusinessProcess> implements IBusinessProcessService {
|
||||||
|
|
||||||
|
private final BusinessProcessConvert convert;
|
||||||
|
private final IMKService mkService;
|
||||||
|
private final MKProperties mkProperties;
|
||||||
|
private final ApprovalConvert approvalConvert;
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public BusinessProcessVO submitBusinessProcess(BusinessProcessSubmitDTO<?> param) {
|
||||||
|
if (param == null) {
|
||||||
|
log.warn("提交业务流程参数为空");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Long bizId = param.getBizId();
|
||||||
|
if (bizId == null) {
|
||||||
|
log.warn("提交业务流程业务id为空");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
log.info("提交业务流程参数:{}", JSON.toJSONString(param));
|
||||||
|
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||||
|
.eq(BusinessProcess::getBizId, bizId)
|
||||||
|
);
|
||||||
|
if (businessProcess == null) {
|
||||||
|
businessProcess = convert.dto2entity(param);
|
||||||
|
}
|
||||||
|
// 设置发起人
|
||||||
|
if (businessProcess.getPromoterId() == null) {
|
||||||
|
businessProcess.setPromoterId(AuthUtil.getUserId());
|
||||||
|
}
|
||||||
|
if (businessProcess.getPromoterName() == null) {
|
||||||
|
businessProcess.setPromoterName(AuthUtil.getNickName());
|
||||||
|
}
|
||||||
|
if (businessProcess.getPromoterLoginName() == null) {
|
||||||
|
businessProcess.setPromoterLoginName(AuthUtil.getUserName());
|
||||||
|
param.setPromoterLoginName(AuthUtil.getUserName());
|
||||||
|
}
|
||||||
|
// 设置提交时间
|
||||||
|
if (businessProcess.getSubmitTime() == null) {
|
||||||
|
businessProcess.setSubmitTime(new Date());
|
||||||
|
}
|
||||||
|
// 1.提交流程
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
log.info("提交流程开始");
|
||||||
|
String processInstanceId = submitMKProcess(param);
|
||||||
|
long end = System.currentTimeMillis();
|
||||||
|
log.info("提交流程结束 耗时:{}", end - start);
|
||||||
|
businessProcess.setProcessInstanceId(processInstanceId);
|
||||||
|
// 提交是审批中状态
|
||||||
|
businessProcess.setApproveStatus(ApproveStatusEnum.APPROVING.getValue());
|
||||||
|
// 2.保存业务流程
|
||||||
|
this.saveOrUpdate(businessProcess);
|
||||||
|
|
||||||
|
// 3.查询当前节点
|
||||||
|
BusinessProcessVO businessProcessVO = new BusinessProcessVO();
|
||||||
|
businessProcessVO.setProcessInstanceId(processInstanceId);
|
||||||
|
return businessProcessVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public String updateBusinessProcessStatus(BusinessProcessUpdateDTO param) {
|
||||||
|
AssertUtils.notNull(param, "参数不能为空");
|
||||||
|
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||||
|
.eq(BusinessProcess::getProcessInstanceId, param.getProcessInstanceId())
|
||||||
|
);
|
||||||
|
AssertUtils.notNull(businessProcess, "流程实例不存在");
|
||||||
|
// if (StringUtils.isNotBlank(approveStatus) && !rejectAfterPass(approveStatus, operationNodeNumber)) {
|
||||||
|
if (StringUtils.isNotBlank(param.getApproveStatus()) && updateApproveStatus(param.getApproveStatus(), param.getRejectNodeId())) {
|
||||||
|
// 审批状态不为空且需要修改审批状态
|
||||||
|
BusinessProcess updateParam = new BusinessProcess();
|
||||||
|
updateParam.setId(businessProcess.getId());
|
||||||
|
updateParam.setApproveStatus(param.getApproveStatus());
|
||||||
|
boolean update = this.updateById(updateParam);
|
||||||
|
return update ? param.getApproveStatus() : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public BusinessProcessVO updateBusinessProcessApprover(BusinessProcessUpdateDTO param) {
|
||||||
|
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(param.getProcessInstanceId());
|
||||||
|
String promoterLoginName = resolvePromoterLoginName(businessProcess, param.getPromoterLoginName());
|
||||||
|
if (StringUtils.isBlank(promoterLoginName)) {
|
||||||
|
log.warn("修改业务流程审批人失败,发起人登录名为空,流程实例id:{}", param.getProcessInstanceId());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BusinessProcessVO businessProcessVO = this.refreshBusinessProcessCurrentHandlers(param);
|
||||||
|
if (businessProcessVO == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 再补历史已办逻辑,兼容旧代码
|
||||||
|
if (param.getOperationNodeId() != null && !MKConstant.DAFTER_NODE_ID.equals(param.getOperationNodeId())) {
|
||||||
|
MKAllHandlerVO nodeHandlers = mkService.getNodeHandlers(param.getProcessInstanceId(), promoterLoginName, param.getOperationNodeId());
|
||||||
|
this.handleNodeHandlers(param.getProcessInstanceId(), nodeHandlers, param);
|
||||||
|
}
|
||||||
|
return businessProcessVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public BusinessProcessVO refreshBusinessProcessCurrentHandlers(BusinessProcessCurrentHandlerRefreshDTO param) {
|
||||||
|
if (param == null) {
|
||||||
|
log.warn("刷新业务流程当前处理人参数为空");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(param.getProcessInstanceId());
|
||||||
|
String promoterLoginName = resolvePromoterLoginName(businessProcess, param.getPromoterLoginName());
|
||||||
|
if (StringUtils.isBlank(promoterLoginName)) {
|
||||||
|
log.warn("刷新业务流程当前处理人失败,发起人登录名为空,流程实例id:{}", param.getProcessInstanceId());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Long businessProcessId = Optional.ofNullable(businessProcess).map(BusinessProcess::getId).orElse(null);
|
||||||
|
// 1. 查询当前节点
|
||||||
|
BusinessProcessVO businessProcessVO = new BusinessProcessVO();
|
||||||
|
businessProcessVO.setProcessInstanceId(param.getProcessInstanceId());
|
||||||
|
List<MKNodeVO> currentNodes = mkService.getCurrentNodes(param.getProcessInstanceId(), promoterLoginName);
|
||||||
|
// 处理当前节点信息
|
||||||
|
BusinessProcess updateBusinessProcess = this.handleCurrentNodes(businessProcessId, param.getProcessInstanceId(), param.isComplete(), currentNodes, businessProcessVO);
|
||||||
|
if (updateBusinessProcess != null) {
|
||||||
|
// 设置当前处理人、当前节点、接收时间
|
||||||
|
businessProcessVO.setCurrentHandlers(updateBusinessProcess.getCurrentHandlers());
|
||||||
|
businessProcessVO.setCurrentNodeIds(updateBusinessProcess.getCurrentNodeIds());
|
||||||
|
businessProcessVO.setCurrentNodeNames(updateBusinessProcess.getCurrentNodeNames());
|
||||||
|
businessProcessVO.setReceiveTime(updateBusinessProcess.getReceiveTime());
|
||||||
|
}
|
||||||
|
return businessProcessVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BusinessProcessVO queryBusinessProcessSnapshot(String processInstanceId) {
|
||||||
|
AssertUtils.notBlank(processInstanceId, "流程实例id不能为空");
|
||||||
|
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
|
||||||
|
AssertUtils.notNull(businessProcess, "业务流程不存在");
|
||||||
|
return this.buildBusinessProcessVO(businessProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditView(String bizId) {
|
||||||
|
if (StringUtils.isBlank(bizId)) {
|
||||||
|
log.warn("查询是否编辑页,业务id为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BusinessProcess businessProcess = this.baseMapper.selectOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||||
|
.eq(BusinessProcess::getBizId, bizId)
|
||||||
|
.last("limit 1")
|
||||||
|
);
|
||||||
|
if (businessProcess == null) {
|
||||||
|
log.warn("查询是否编辑页,业务流程不存在 业务id:{}", bizId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String approveStatus = businessProcess.getApproveStatus();
|
||||||
|
String userAccount = AuthUtil.getUserAccount();
|
||||||
|
String promoterLoginName = businessProcess.getPromoterLoginName();
|
||||||
|
// (驳回或撤销或草稿)且当前登录人是流程提交人
|
||||||
|
boolean result = ApproveStatusEnum.canEdit(approveStatus) && userAccount.equals(promoterLoginName);
|
||||||
|
log.info("是否编辑页 审批状态:{} 当前登录人:{} 提交人:{} 结果:{}", approveStatus, userAccount, promoterLoginName, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMKApprovalUrl(String bizId, String processInstanceId) {
|
||||||
|
boolean bizIdBlank = StringUtil.isBlank(bizId);
|
||||||
|
boolean processInstanceIdBlank = StringUtil.isBlank(processInstanceId);
|
||||||
|
AssertUtils.isFalse(bizIdBlank && processInstanceIdBlank, "参数不能为空");
|
||||||
|
if (processInstanceIdBlank) {
|
||||||
|
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||||
|
.eq(BusinessProcess::getBizId, bizId)
|
||||||
|
.last("limit 1")
|
||||||
|
);
|
||||||
|
AssertUtils.notNull(businessProcess, "业务流程不存在");
|
||||||
|
processInstanceId = businessProcess.getProcessInstanceId();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String mkApprovalUrl = mkService.getMKApprovalUrl(processInstanceId, AuthUtil.getUserAccount());
|
||||||
|
AssertUtils.notBlank(mkApprovalUrl, "获取mk审批页面链接异常");
|
||||||
|
return mkApprovalUrl;
|
||||||
|
} catch (MKException e) {
|
||||||
|
throw new ServiceException("获取mk审批页面链接异常 " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public boolean deleteBusinessProcess(BusinessProcessDeleteDTO param) {
|
||||||
|
log.info("删除业务流程 参数:{}", JSON.toJSONString(param));
|
||||||
|
Long bizId = param.getBizId();
|
||||||
|
String promoterLoginName = param.getPromoterLoginName();
|
||||||
|
if (bizId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||||
|
.eq(BusinessProcess::getBizId, bizId)
|
||||||
|
);
|
||||||
|
if (businessProcess == null) {
|
||||||
|
log.warn("业务流程不存在 业务id:{}", bizId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (promoterLoginName == null) {
|
||||||
|
promoterLoginName = businessProcess.getPromoterLoginName();
|
||||||
|
}
|
||||||
|
// 1. 删除业务流程
|
||||||
|
this.removeById(businessProcess.getId());
|
||||||
|
// 2. 删除待办
|
||||||
|
if (StringUtils.isEmpty(businessProcess.getProcessInstanceId())) {
|
||||||
|
log.warn("流程id为空 id:{} 业务id:{}", businessProcess.getId(), bizId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 3. 删除流程
|
||||||
|
return mkService.processDelete(businessProcess.getProcessInstanceId(), promoterLoginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProcessApprovedRecordVO> queryApprovedRecords(String bizId, String processInstanceId) {
|
||||||
|
List<ProcessApprovedRecordVO> records = this.queryApprovedRecordsNoAttachments(bizId, processInstanceId);
|
||||||
|
if (CollectionUtil.isEmpty(records)) {
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
Map<String, String> fileMap = new HashMap<>();
|
||||||
|
for (ProcessApprovedRecordVO record : records) {
|
||||||
|
List<ProcessAttachmentVO> attachmentParameter = record.getAttachmentParameter();
|
||||||
|
// 处理电子签名base64并排序附件
|
||||||
|
attachmentParameter = handleAttachmentBase64(attachmentParameter, fileMap);
|
||||||
|
record.setAttachmentParameter(attachmentParameter);
|
||||||
|
if (CollectionUtil.isNotEmpty(record.getProcessComments())) {
|
||||||
|
for (ProcessCommentVO processComment : record.getProcessComments()) {
|
||||||
|
// 处理电子签名base64并排序附件
|
||||||
|
List<ProcessAttachmentVO> attachments = handleAttachmentBase64(processComment.getAttachments(), fileMap);
|
||||||
|
processComment.setAttachments(attachments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProcessApprovedRecordVO> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
|
||||||
|
boolean bizIdBlank = StringUtil.isBlank(bizId);
|
||||||
|
boolean processInstanceIdBlank = StringUtil.isBlank(processInstanceId);
|
||||||
|
AssertUtils.isFalse(bizIdBlank && processInstanceIdBlank, "参数不能为空");
|
||||||
|
|
||||||
|
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||||
|
.eq(!bizIdBlank, BusinessProcess::getBizId, bizId)
|
||||||
|
.eq(!processInstanceIdBlank, BusinessProcess::getProcessInstanceId, processInstanceId)
|
||||||
|
.last("limit 1")
|
||||||
|
);
|
||||||
|
AssertUtils.notNull(businessProcess, "业务流程不存在");
|
||||||
|
if (processInstanceIdBlank) {
|
||||||
|
processInstanceId = businessProcess.getProcessInstanceId();
|
||||||
|
}
|
||||||
|
// 查询审批记录
|
||||||
|
List<MKAuditNoteVO> mkAuditNotes = mkService.queryAuditNotes(new MKAuditNoteDTO(businessProcess.getPromoterLoginName(), processInstanceId));
|
||||||
|
// 转换参数
|
||||||
|
return convert.auditNotes2vos(mkAuditNotes, record -> {
|
||||||
|
List<MKSenderVO> mkSenders = mkService.querySenderList(new MKSenderDTO(record.getProcessInstanceId(), record.getNodeInstanceId()));
|
||||||
|
return mkSenders.stream()
|
||||||
|
.map(MKSenderVO::getName)
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理附件base64并排序附件
|
||||||
|
* @param attachmentParameter
|
||||||
|
* @param fileMap
|
||||||
|
*/
|
||||||
|
private List<ProcessAttachmentVO> handleAttachmentBase64(List<ProcessAttachmentVO> attachmentParameter, Map<String, String> fileMap) {
|
||||||
|
if (CollectionUtil.isEmpty(attachmentParameter)) {
|
||||||
|
return attachmentParameter;
|
||||||
|
}
|
||||||
|
// 电子签名的附件
|
||||||
|
List<ProcessAttachmentVO> signAttachments = attachmentParameter.stream()
|
||||||
|
.filter(attachment -> MKConstant.FILE_TYPE_SIGN.equals(attachment.getType()))
|
||||||
|
.peek(attachment -> {
|
||||||
|
// 电子签名,查询图片base64
|
||||||
|
if (fileMap.containsKey(attachment.getFileId())) {
|
||||||
|
attachment.setBase64(fileMap.get(attachment.getFileId()));
|
||||||
|
} else {
|
||||||
|
String fileBase64 = mkService.getFileBase64(attachment.getFileId());
|
||||||
|
fileMap.put(attachment.getFileId(), fileBase64);
|
||||||
|
attachment.setBase64(fileBase64);
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
if (CollectionUtil.isEmpty(signAttachments)) {
|
||||||
|
// 没有电子签名的附件,无需处理
|
||||||
|
return attachmentParameter;
|
||||||
|
}
|
||||||
|
List<ProcessAttachmentVO> result = new ArrayList<>();
|
||||||
|
// 纯附件,非电子签名附件
|
||||||
|
List<ProcessAttachmentVO> attachments = attachmentParameter.stream()
|
||||||
|
.filter(attachment -> !MKConstant.FILE_TYPE_SIGN.equals(attachment.getType()))
|
||||||
|
.toList();
|
||||||
|
if (CollectionUtil.isNotEmpty(attachments)) {
|
||||||
|
result.addAll(attachments);
|
||||||
|
}
|
||||||
|
// 把电子签名附件放到最后
|
||||||
|
result.addAll(signAttachments);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadFile(HttpServletResponse response, String fileId) {
|
||||||
|
mkService.downloadFile(response,fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProcessTodoVO> queryTodoList(String processInstanceId) {
|
||||||
|
AssertUtils.notNull(processInstanceId, "流程实例id不能为空");
|
||||||
|
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
|
||||||
|
if (businessProcess == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 1. 查询当前节点处理人
|
||||||
|
List<MKNodeVO> currentNodes = mkService.getCurrentNodes(processInstanceId, businessProcess.getPromoterLoginName());
|
||||||
|
if (CollectionUtil.isEmpty(currentNodes)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return getProcessTodoList(currentNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过流程实例id查询业务流程
|
||||||
|
* @param processInstanceId 流程实例id
|
||||||
|
* @return 业务流程
|
||||||
|
*/
|
||||||
|
private BusinessProcess getBusinessProcessByProcessInstanceId(String processInstanceId) {
|
||||||
|
return this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||||
|
.eq(BusinessProcess::getProcessInstanceId, processInstanceId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析最终使用的发起人登录名
|
||||||
|
* @param businessProcess 业务流程
|
||||||
|
* @param fallbackPromoterLoginName 调用方传入的发起人登录名
|
||||||
|
* @return 发起人登录名
|
||||||
|
*/
|
||||||
|
private String resolvePromoterLoginName(BusinessProcess businessProcess, String fallbackPromoterLoginName) {
|
||||||
|
return Optional.ofNullable(businessProcess)
|
||||||
|
.map(BusinessProcess::getPromoterLoginName)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.orElse(fallbackPromoterLoginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造业务流程快照
|
||||||
|
* @param businessProcess 业务流程
|
||||||
|
* @return 快照
|
||||||
|
*/
|
||||||
|
private BusinessProcessVO buildBusinessProcessVO(BusinessProcess businessProcess) {
|
||||||
|
BusinessProcessVO businessProcessVO = new BusinessProcessVO();
|
||||||
|
businessProcessVO.setProcessInstanceId(businessProcess.getProcessInstanceId());
|
||||||
|
businessProcessVO.setCurrentNodeIds(businessProcess.getCurrentNodeIds());
|
||||||
|
businessProcessVO.setCurrentNodeNames(businessProcess.getCurrentNodeNames());
|
||||||
|
businessProcessVO.setCurrentHandlers(businessProcess.getCurrentHandlers());
|
||||||
|
businessProcessVO.setReceiveTime(businessProcess.getReceiveTime());
|
||||||
|
return businessProcessVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流程待办列表
|
||||||
|
* @param currentNodes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private List<ProcessTodoVO> getProcessTodoList(List<MKNodeVO> currentNodes) {
|
||||||
|
if (CollectionUtil.isEmpty(currentNodes)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return currentNodes.stream()
|
||||||
|
.filter(node -> CollectionUtil.isNotEmpty(node.getNodeHandlers()))
|
||||||
|
.flatMap(node -> node.getNodeHandlers().stream()
|
||||||
|
// 过滤掉登录名为空的脏数据
|
||||||
|
.filter(handler -> handler.getFdHandlerOrgInfo() != null && StringUtil.isNotBlank(handler.getFdHandlerOrgInfo().getLoginName()))
|
||||||
|
.map(handler -> {
|
||||||
|
ProcessTodoVO addParam = new ProcessTodoVO();
|
||||||
|
addParam.setProcessInstanceId(node.getProcessInstanceId());
|
||||||
|
addParam.setNodeId(node.getNodeId());
|
||||||
|
addParam.setNodeNumber(node.getNodeNumber());
|
||||||
|
addParam.setNodeName(node.getNodeName());
|
||||||
|
addParam.setLoginName(handler.getFdHandlerOrgInfo().getLoginName());
|
||||||
|
addParam.setUserName(handler.getHandlerName());
|
||||||
|
addParam.setStatus(TodoStatus.TODO.getCode());
|
||||||
|
addParam.setReceiveTime(handler.getReceiveTime());
|
||||||
|
return addParam;
|
||||||
|
})
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<ApprovalVO> queryMkApprovalList(IPage<ApprovalVO> page, ApprovalDTO param) {
|
||||||
|
if (MKDoc.RELATED.getCode().equals(param.getDocType())) {
|
||||||
|
// 我参与的,调用流程列表接口
|
||||||
|
MKProcessDTO processParam = approvalConvert.dto2mk(param);
|
||||||
|
processParam.setPage((int) page.getCurrent(), (int) page.getSize());
|
||||||
|
MKPageVO<MKProcessVO> mkPage = mkService.queryProcessList(processParam);
|
||||||
|
page.setTotal(mkPage.getTotalSize());
|
||||||
|
page.setRecords(approvalConvert.mkProcess2vos(mkPage.getContent()));
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
// 非我参与的,调用审批中心接口
|
||||||
|
MKApprovalDTO approvalParam = getMkApprovalParam(param);
|
||||||
|
approvalParam.setPage((int) page.getCurrent(), (int) page.getSize());
|
||||||
|
MKPageVO<MKApprovalVO> mkPage = mkService.queryApprovalList(approvalParam);
|
||||||
|
page.setTotal(mkPage.getTotalSize());
|
||||||
|
page.setRecords(approvalConvert.mk2vos(mkPage.getContent()));
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取mk查询参数
|
||||||
|
*
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MKApprovalDTO getMkApprovalParam(ApprovalDTO param) {
|
||||||
|
String docType = param.getDocType();
|
||||||
|
// 我的待审
|
||||||
|
// mk页面接口参数 {"offset":0,"pageNo":1,"pageSize":10,"conditions":{"fdStartTime":{"$gte":1711900800000,"$lte":1746374399999},"fdReceiveTime":{"$gte":1712160000000,"$lte":1746115199999},"fdTemplateName":{"$contains":"测试"},"keyword":{"$eq":"提交"},"mydoc":{"$eq":"myApproving"}},"sorts":{"fdLevel":"asc","fdReceiveTime":"desc"}}
|
||||||
|
MKApprovalDTO approvalParam = new MKApprovalDTO();
|
||||||
|
approvalParam.setLoginName(param.getLoginName());
|
||||||
|
ISort sort = getSort(docType, approvalParam);
|
||||||
|
approvalParam.setSorts(sort);
|
||||||
|
// 获取查询条件的参数
|
||||||
|
MKApprovalConditionDTO condition = getCondition(param);
|
||||||
|
approvalParam.setConditions(condition);
|
||||||
|
return approvalParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取排序
|
||||||
|
* @param docType
|
||||||
|
* @param approvalParam
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ISort getSort(String docType, MKApprovalDTO approvalParam) {
|
||||||
|
if (MKDoc.APPROVING.getCode().equals(docType) || MKDoc.READING.getCode().equals(docType)) {
|
||||||
|
// 待办、待阅排序是相同的
|
||||||
|
return new MKApprovingSortDTO();
|
||||||
|
}
|
||||||
|
if (MKDoc.APPROVED.getCode().equals(docType)) {
|
||||||
|
// 已办
|
||||||
|
return new MKApprovedSortDTO();
|
||||||
|
}
|
||||||
|
if (MKDoc.READ.getCode().equals(docType)) {
|
||||||
|
// 已阅
|
||||||
|
return new MKReadSortDTO();
|
||||||
|
}
|
||||||
|
if (MKDoc.CREATE.getCode().equals(docType) || MKDoc.RELATED.getCode().equals(docType)) {
|
||||||
|
// 我发起的/我关联的
|
||||||
|
return new MKCreateSortDTO();
|
||||||
|
}
|
||||||
|
throw new ServiceException("不支持的单据类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取查询条件
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MKApprovalConditionDTO getCondition(ApprovalDTO param) {
|
||||||
|
MKApprovalConditionDTO condition = null;
|
||||||
|
for (MKApprovalConvert convert : MKApprovalConvert.values()) {
|
||||||
|
MKConditionDTO.MKConditionDTOBuilder builder = null;
|
||||||
|
BiConsumer<MKApprovalConditionDTO, MKConditionDTO> setter = convert.getSetter();
|
||||||
|
List<MKApprovalConvert.Compose> composes = convert.getComposes();
|
||||||
|
// 是否多个 Compose
|
||||||
|
boolean multi = composes.size() > 1;
|
||||||
|
if (multi) {
|
||||||
|
// 不是多个setter
|
||||||
|
for (MKApprovalConvert.Compose compose : composes) {
|
||||||
|
// 遍历获取参数值
|
||||||
|
String value = compose.getGetter().apply(param);
|
||||||
|
if (StringUtils.isNotBlank(value)) {
|
||||||
|
// 参数值不为空,设置到builder
|
||||||
|
if (builder == null) {
|
||||||
|
builder = MKConditionDTO.builder();
|
||||||
|
}
|
||||||
|
compose.getBuilderSetter().accept(builder, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (builder != null) {
|
||||||
|
// builder不为空,设置到最终的条件
|
||||||
|
if (condition == null) {
|
||||||
|
condition = new MKApprovalConditionDTO();
|
||||||
|
}
|
||||||
|
setter.accept(condition, builder.build());
|
||||||
|
convert.getSetter().accept(condition, builder.build());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 只有1个compose
|
||||||
|
MKApprovalConvert.Compose compose = composes.get(0);
|
||||||
|
String value = compose.getGetter().apply(param);
|
||||||
|
if (StringUtils.isNotBlank(value)) {
|
||||||
|
// 替换中文逗号为英文逗号
|
||||||
|
value = value.replace(",", ",");
|
||||||
|
// 按英文逗号拆分值
|
||||||
|
String[] values = value.split(",");
|
||||||
|
for (String singleValue: values) {
|
||||||
|
if (StringUtils.isNotBlank(value)) {
|
||||||
|
// 参数值不为空,设置到builder
|
||||||
|
if (builder == null) {
|
||||||
|
builder = MKConditionDTO.builder();
|
||||||
|
}
|
||||||
|
compose.getBuilderSetter().accept(builder, singleValue);
|
||||||
|
if (builder != null) {
|
||||||
|
// builder不为空,设置到最终的条件
|
||||||
|
if (condition == null) {
|
||||||
|
condition = new MKApprovalConditionDTO();
|
||||||
|
}
|
||||||
|
// 索引不超过setters长度,设置条件
|
||||||
|
setter.accept(condition, builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理已审批的人
|
||||||
|
*
|
||||||
|
* @param processInstanceId
|
||||||
|
* @param nodeHandlers
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
private void handleNodeHandlers(String processInstanceId, MKAllHandlerVO nodeHandlers, BusinessProcessUpdateDTO param) {
|
||||||
|
if (nodeHandlers == null) {
|
||||||
|
log.warn("修改业务流程 查询操作节点历史处理信息为空 流程实例id:{} 操作节点id:{}", param.getProcessInstanceId(), param.getOperationNodeId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<MKApprovedHandlerVO> approvedHandlers = nodeHandlers.getApprovedHandlers();
|
||||||
|
if (CollectionUtil.isEmpty(approvedHandlers)) {
|
||||||
|
// 已审批为空
|
||||||
|
log.warn("修改业务流程 查询操作节点已处理信息为空 流程实例id:{} 操作节点id:{}", param.getProcessInstanceId(), param.getOperationNodeId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理当前节点信息
|
||||||
|
*
|
||||||
|
* @param businessProcessId
|
||||||
|
* @param processInstanceId
|
||||||
|
* @param complete
|
||||||
|
* @param currentNodes
|
||||||
|
* @param businessProcessVO
|
||||||
|
*/
|
||||||
|
private BusinessProcess handleCurrentNodes(Long businessProcessId, String processInstanceId, boolean complete, List<MKNodeVO> currentNodes, BusinessProcessVO businessProcessVO) {
|
||||||
|
if (CollectionUtil.isEmpty(currentNodes)) {
|
||||||
|
log.info("修改业务流程 当前节点处理人为空");
|
||||||
|
if (businessProcessId != null) {
|
||||||
|
// 清空当前节点,当前处理人,接收时间
|
||||||
|
log.info("修改业务流程 流程结束清空当前节点,当前处理人,接收时间 业务流程id:{} 流程实例id:{}", businessProcessId, processInstanceId);
|
||||||
|
this.lambdaUpdate()
|
||||||
|
.eq(BusinessProcess::getId, businessProcessId)
|
||||||
|
.set(BusinessProcess::getCurrentNodeIds, null)
|
||||||
|
.set(BusinessProcess::getCurrentNodeNames, null)
|
||||||
|
.set(BusinessProcess::getCurrentHandlers, null)
|
||||||
|
.set(complete, BusinessProcess::getIsCompleted, true)
|
||||||
|
.set(complete, BusinessProcess::getCompleteTime, new Date())
|
||||||
|
.set(BusinessProcess::getUpdateTime, new Date())
|
||||||
|
.update();
|
||||||
|
} else {
|
||||||
|
log.warn("修改业务流程 流程结束 业务流程id为空");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 处理待办
|
||||||
|
List<ProcessTodoVO> processTodoList = getProcessTodoList(currentNodes);
|
||||||
|
|
||||||
|
// 更新业务流程
|
||||||
|
return updateBusinessProcess(businessProcessId, processTodoList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新业务流程
|
||||||
|
*
|
||||||
|
* @param businessProcessId
|
||||||
|
* @param addToDos
|
||||||
|
*/
|
||||||
|
private BusinessProcess updateBusinessProcess(Long businessProcessId, List<ProcessTodoVO> addToDos) {
|
||||||
|
if (CollectionUtil.isEmpty(addToDos)) {
|
||||||
|
log.warn("新增待办为空");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String nodeIds = addToDos.stream()
|
||||||
|
.map(ProcessTodoVO::getNodeId)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
String nodeNames = addToDos.stream()
|
||||||
|
.map(ProcessTodoVO::getNodeName)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
String usernames = addToDos.stream()
|
||||||
|
.map(ProcessTodoVO::getUserName)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
Date receiveTime = addToDos.get(0).getReceiveTime();
|
||||||
|
// 更新当前节点id,当前节点名称,当前处理人,接收时间
|
||||||
|
BusinessProcess updateParam = new BusinessProcess();
|
||||||
|
updateParam.setId(businessProcessId);
|
||||||
|
updateParam.setCurrentNodeIds(nodeIds);
|
||||||
|
updateParam.setCurrentNodeNames(nodeNames);
|
||||||
|
updateParam.setCurrentHandlers(usernames);
|
||||||
|
updateParam.setReceiveTime(receiveTime);
|
||||||
|
if (businessProcessId != null) {
|
||||||
|
this.updateById(updateParam);
|
||||||
|
} else {
|
||||||
|
log.warn("新增待办,业务流程id为空");
|
||||||
|
}
|
||||||
|
return updateParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否修改审批状态,驳回状态只修改驳回节点是起草节点的
|
||||||
|
* @param approveStatus
|
||||||
|
* @param rejectNodeId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean updateApproveStatus(String approveStatus, String rejectNodeId) {
|
||||||
|
if (!ApproveStatusEnum.REJECTED.getValue().equals(approveStatus)) {
|
||||||
|
// 不是驳回状态,直接修改
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(rejectNodeId)) {
|
||||||
|
// 驳回节点id为空说明是老流程,没有配置参数,可以修改
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 是驳回状态,只修改驳回节点id是起草节点id的
|
||||||
|
return MKConstant.DAFTER_NODE_ID.equals(rejectNodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交mk流程
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String submitMKProcess(BusinessProcessSubmitDTO<?> param) {
|
||||||
|
if (param.getExecuteParam() == null || StringUtil.isBlank(param.getExecuteParam().getProcessId())) {
|
||||||
|
// 执行参数为空,是提交
|
||||||
|
MKProcessCreateDTO processParam = new MKProcessCreateDTO();
|
||||||
|
processParam.setFormInstanceId(String.valueOf(param.getBizId()));
|
||||||
|
processParam.setLoginName(param.getPromoterLoginName());
|
||||||
|
processParam.setSubmitIdentity(param.getPromoterLoginName());
|
||||||
|
processParam.setSubject(param.getSubject());
|
||||||
|
processParam.setTemplateCode(mkProperties.getTemplateCodePrefix() + param.getProcessType());
|
||||||
|
processParam.setFormValues(param.getProcessParam());
|
||||||
|
// 临时变量设置为业务表单对象,以便不用修改mk流程表单字段,直接使用流程模板临时变量
|
||||||
|
processParam.setTempVarData(ObjectUtil.cloneByStream(param.getProcessParam()));
|
||||||
|
return mkService.processSubmit(processParam);
|
||||||
|
}
|
||||||
|
// 执行参数不为空,是驳回/撤销后提交/废弃
|
||||||
|
ProcessExecuteDTO executeParam = param.getExecuteParam();
|
||||||
|
MKProcessExecuteDTO processExecuteDTO = convert.dto2mk(executeParam);
|
||||||
|
processExecuteDTO.setLoginName(param.getPromoterLoginName());
|
||||||
|
processExecuteDTO.setFormValues(param.getProcessParam());
|
||||||
|
// 临时变量设置为业务表单对象,以便不用修改mk流程表单字段,直接使用流程模板临时变量
|
||||||
|
processExecuteDTO.setTempVarData(ObjectUtil.cloneByStream(param.getProcessParam()));
|
||||||
|
// 重新设置标题,防止标题变了
|
||||||
|
processExecuteDTO.setSubject(param.getSubject());
|
||||||
|
mkService.processExecute(processExecuteDTO);
|
||||||
|
return executeParam.getProcessId();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,15 +28,18 @@ package org.springblade.system;
|
|||||||
import org.springblade.core.cloud.client.BladeCloudApplication;
|
import org.springblade.core.cloud.client.BladeCloudApplication;
|
||||||
import org.springblade.core.launch.BladeApplication;
|
import org.springblade.core.launch.BladeApplication;
|
||||||
import org.springblade.core.launch.constant.AppConstant;
|
import org.springblade.core.launch.constant.AppConstant;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统模块启动器
|
* 系统模块启动器
|
||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
|
@ComponentScan(basePackages = {"org.springblade.system", "org.springblade.process"})
|
||||||
@BladeCloudApplication
|
@BladeCloudApplication
|
||||||
public class SystemApplication {
|
public class SystemApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
BladeApplication.disableNacosLaunchConfig();
|
||||||
BladeApplication.run(AppConstant.APPLICATION_SYSTEM_NAME, SystemApplication.class, args);
|
BladeApplication.run(AppConstant.APPLICATION_SYSTEM_NAME, SystemApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
#服务器端口
|
|
||||||
server:
|
|
||||||
port: 8106
|
|
||||||
|
|
||||||
#数据源配置
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: ${blade.datasource.dev.url}
|
|
||||||
username: ${blade.datasource.dev.username}
|
|
||||||
password: ${blade.datasource.dev.password}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#服务器端口
|
|
||||||
server:
|
|
||||||
port: 8106
|
|
||||||
|
|
||||||
#数据源配置
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: ${blade.datasource.prod.url}
|
|
||||||
username: ${blade.datasource.prod.username}
|
|
||||||
password: ${blade.datasource.prod.password}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#服务器端口
|
|
||||||
server:
|
|
||||||
port: 8106
|
|
||||||
|
|
||||||
#数据源配置
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: ${blade.datasource.test.url}
|
|
||||||
username: ${blade.datasource.test.username}
|
|
||||||
password: ${blade.datasource.test.password}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
server:
|
||||||
|
port: 8106
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: blade-system
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- nacos:blade.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:blade-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- nacos:third-party-api.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: ${NACOS_USERNAME:nacos}
|
||||||
|
password: ${NACOS_PASSWORD:nacos}
|
||||||
|
server-addr: ${NACOS_HOST:127.0.0.1:8848}
|
||||||
|
discovery:
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: ${NACOS_NAMESPACE:${spring.profiles.active}}
|
||||||
|
datasource:
|
||||||
|
url: ${blade.datasource.${spring.profiles.active}.url}
|
||||||
|
username: ${blade.datasource.${spring.profiles.active}.username}
|
||||||
|
password: ${blade.datasource.${spring.profiles.active}.password}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-- 业务流程表
|
||||||
|
CREATE TABLE `blade_business_process`
|
||||||
|
(
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`biz_id` bigint NOT NULL COMMENT '业务id',
|
||||||
|
`process_instance_id` varchar(40) NOT NULL COMMENT '流程实例id',
|
||||||
|
`process_type` varchar(40) NOT NULL COMMENT '流程类型',
|
||||||
|
`doc_code` varchar(50) DEFAULT NULL COMMENT '文档编号',
|
||||||
|
`subject` varchar(500) DEFAULT NULL COMMENT '标题',
|
||||||
|
`promoter_id` bigint NOT NULL COMMENT '发起人id',
|
||||||
|
`promoter_name` varchar(40) NOT NULL COMMENT '发起人名称',
|
||||||
|
`promoter_login_name` varchar(40) DEFAULT NULL COMMENT '发起人登录名',
|
||||||
|
`submit_time` datetime NOT NULL COMMENT '提交时间',
|
||||||
|
`complete_time` datetime DEFAULT NULL COMMENT '完成时间',
|
||||||
|
`current_node_ids` varchar(40) DEFAULT NULL COMMENT '当前节点id,多个用逗号拼接',
|
||||||
|
`current_node_names` varchar(40) DEFAULT NULL COMMENT '当前节点名称,多个用逗号拼接',
|
||||||
|
`current_handlers` varchar(255) DEFAULT NULL COMMENT '当前处理人,多个用逗号拼接',
|
||||||
|
`receive_time` datetime DEFAULT NULL COMMENT '接收时间',
|
||||||
|
`is_completed` tinyint(1) DEFAULT '0' COMMENT '是否已完成',
|
||||||
|
`approve_status` varchar(20) DEFAULT NULL COMMENT '审批状态',
|
||||||
|
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_blade_business_process_process_instance_id` (`process_instance_id`) USING BTREE,
|
||||||
|
KEY `idx_blade_business_process_promoter_login_name` (`promoter_login_name`) USING BTREE
|
||||||
|
) ENGINE=InnoDB COMMENT='业务流程关联表';
|
||||||
|
|
||||||
|
-- 新增mk client配置
|
||||||
|
INSERT INTO blade_client (id,client_id,client_secret,resource_ids,`scope`,authorized_grant_types,web_server_redirect_uri,authorities,access_token_validity,refresh_token_validity,additional_information,autoapprove,create_user,create_dept,create_time,update_user,update_time,status,is_deleted) VALUES
|
||||||
|
(1834798269409857538,'mk-oauth','mk_oauth_secret','','all','refresh_token,password,authorization_code,captcha,social,sms_code,register','http://localhost:2888/login','',604800,604800,NULL,'true',1123598821738675201,1828387593663762436,'2024-09-14 11:36:11',1123598821738675201,'2024-09-14 11:36:11',1,0);
|
||||||
Reference in New Issue
Block a user